(#112207) Adding view_id parameter to DispatchSemanticsAction and UpdateSemantics (#164577)

fixes #112207

## What's new?
- Added a `view_id` on `UpdateSemantics`
- Added a `view_id` on `DispatchSemanticsAction`
- Piped the `view_id` all over creation
- Updated tests for these actions across the different platforms
- Added `FlutterEngineSendSemanticsAction` to the embedder API in order
to not break `FlutterEngineDispatchSemanticsAction`
- Using this view ID properly on the Windows platform (see
`engine/src/flutter/shell/platform/windows/flutter_windows_engine.cc`)

## How to test
1. Checkout
[foundation-plus-framework](https://github.com/canonical/flutter/pull/36)
from canonical/flutter
2. Merge this branch into it
3. Enable the "Narrator" screen reader on windows
4. Run the Multi window reference app (see
[PR](https://github.com/canonical/flutter/pull/36) for details)
5. Open up another window, and note that the right buttons and things
are being highlighted, as the screenreader would expect 🎉

## Pre-launch Checklist
- [x] I read the [Contributor Guide] and followed the process outlined
there for submitting PRs.
- [x] I read the [Tree Hygiene] wiki page, which explains my
responsibilities.
- [x] I read and followed the [Flutter Style Guide], including [Features
we expect every widget to implement].
- [x] I signed the [CLA].
- [x] I listed at least one issue that this PR fixes in the description
above.
- [x] I updated/added relevant documentation (doc comments with `///`).
- [x] I added new tests to check the change I am making, or this PR is
[test-exempt].
- [x] I followed the [breaking change policy] and added [Data Driven
Fixes] where supported.
- [x] All existing and new tests are passing.
This commit is contained in:
Matthew Kosarek 2025-03-20 12:30:07 -04:00 committed by GitHub
parent 9451e1dcfd
commit 554c814ada
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
66 changed files with 898 additions and 310 deletions

View File

@ -1060,8 +1060,9 @@ void hooksTests() async {
}; };
}); });
_callHook('_dispatchSemanticsAction', 3, 1234, 4, null); _callHook('_dispatchSemanticsAction', 4, 456, 1234, 4, null);
expectIdentical(runZone, innerZone); expectIdentical(runZone, innerZone);
expectEquals(action.viewId, 456);
expectEquals(action.nodeId, 1234); expectEquals(action.nodeId, 1234);
expectEquals(action.type.index, 4); expectEquals(action.type.index, 4);
}); });

View File

@ -283,8 +283,8 @@ void _dispatchPointerDataPacket(ByteData packet) {
} }
@pragma('vm:entry-point') @pragma('vm:entry-point')
void _dispatchSemanticsAction(int nodeId, int action, ByteData? args) { void _dispatchSemanticsAction(int viewId, int nodeId, int action, ByteData? args) {
PlatformDispatcher.instance._dispatchSemanticsAction(nodeId, action, args); PlatformDispatcher.instance._dispatchSemanticsAction(viewId, nodeId, action, args);
} }
@pragma('vm:entry-point') @pragma('vm:entry-point')

View File

@ -939,10 +939,12 @@ class PlatformDispatcher {
call `updateSemantics`. call `updateSemantics`.
''') ''')
void updateSemantics(SemanticsUpdate update) => void updateSemantics(SemanticsUpdate update) =>
_updateSemantics(update as _NativeSemanticsUpdate); _updateSemantics(_implicitViewId!, update as _NativeSemanticsUpdate);
@Native<Void Function(Pointer<Void>)>(symbol: 'PlatformConfigurationNativeApi::UpdateSemantics') @Native<Void Function(Int64, Pointer<Void>)>(
external static void _updateSemantics(_NativeSemanticsUpdate update); symbol: 'PlatformConfigurationNativeApi::UpdateSemantics',
)
external static void _updateSemantics(int viewId, _NativeSemanticsUpdate update);
/// The system-reported default locale of the device. /// The system-reported default locale of the device.
/// ///
@ -1332,14 +1334,14 @@ class PlatformDispatcher {
} }
// Called from the engine, via hooks.dart // 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<SemanticsActionEvent>( _invoke1<SemanticsActionEvent>(
onSemanticsActionEvent, onSemanticsActionEvent,
_onSemanticsActionEventZone, _onSemanticsActionEventZone,
SemanticsActionEvent( SemanticsActionEvent(
type: SemanticsAction.fromIndex(action)!, type: SemanticsAction.fromIndex(action)!,
nodeId: nodeId, nodeId: nodeId,
viewId: 0, // TODO(goderbauer): Wire up the real view ID. viewId: viewId,
arguments: args, arguments: args,
), ),
); );

View File

@ -397,10 +397,12 @@ class FlutterView {
/// This function disposes the given update, which means the semantics update /// This function disposes the given update, which means the semantics update
/// cannot be used further. /// cannot be used further.
void updateSemantics(SemanticsUpdate update) => void updateSemantics(SemanticsUpdate update) =>
_updateSemantics(update as _NativeSemanticsUpdate); _updateSemantics(viewId, update as _NativeSemanticsUpdate);
@Native<Void Function(Pointer<Void>)>(symbol: 'PlatformConfigurationNativeApi::UpdateSemantics') @Native<Void Function(Int64, Pointer<Void>)>(
external static void _updateSemantics(_NativeSemanticsUpdate update); symbol: 'PlatformConfigurationNativeApi::UpdateSemantics',
)
external static void _updateSemantics(int viewId, _NativeSemanticsUpdate update);
@override @override
String toString() => 'FlutterView(id: $viewId)'; String toString() => 'FlutterView(id: $viewId)';

View File

@ -380,7 +380,8 @@ void PlatformConfiguration::DispatchPointerDataPacket(
tonic::DartInvoke(dispatch_pointer_data_packet_.Get(), {data_handle})); 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, SemanticsAction action,
fml::MallocMapping args) { fml::MallocMapping args) {
std::shared_ptr<tonic::DartState> dart_state = std::shared_ptr<tonic::DartState> dart_state =
@ -399,8 +400,8 @@ void PlatformConfiguration::DispatchSemanticsAction(int32_t node_id,
tonic::CheckAndHandleError(tonic::DartInvoke( tonic::CheckAndHandleError(tonic::DartInvoke(
dispatch_semantics_action_.Get(), dispatch_semantics_action_.Get(),
{tonic::ToDart(node_id), tonic::ToDart(static_cast<int32_t>(action)), {tonic::ToDart(view_id), tonic::ToDart(node_id),
args_handle})); tonic::ToDart(static_cast<int32_t>(action)), args_handle}));
} }
void PlatformConfiguration::BeginFrame(fml::TimePoint frameTime, void PlatformConfiguration::BeginFrame(fml::TimePoint frameTime,
@ -661,10 +662,11 @@ void PlatformConfigurationNativeApi::EndWarmUpFrame() {
UIDartState::Current()->platform_configuration()->client()->EndWarmUpFrame(); UIDartState::Current()->platform_configuration()->client()->EndWarmUpFrame();
} }
void PlatformConfigurationNativeApi::UpdateSemantics(SemanticsUpdate* update) { void PlatformConfigurationNativeApi::UpdateSemantics(int64_t view_id,
SemanticsUpdate* update) {
UIDartState::ThrowIfUIOperationsProhibited(); UIDartState::ThrowIfUIOperationsProhibited();
UIDartState::Current()->platform_configuration()->client()->UpdateSemantics( UIDartState::Current()->platform_configuration()->client()->UpdateSemantics(
update); view_id, update);
} }
Dart_Handle PlatformConfigurationNativeApi::ComputePlatformResolvedLocale( Dart_Handle PlatformConfigurationNativeApi::ComputePlatformResolvedLocale(

View File

@ -92,9 +92,10 @@ class PlatformConfigurationClient {
//-------------------------------------------------------------------------- //--------------------------------------------------------------------------
/// @brief Receives an updated semantics tree from the Framework. /// @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. /// @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 /// @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 /// originates on the platform view and has been forwarded to the
/// platform configuration here by the engine. /// 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] node_id The identifier of the accessibility node.
/// @param[in] action The accessibility related action performed on the /// @param[in] action The accessibility related action performed on the
/// node of the specified ID. /// node of the specified ID.
/// @param[in] args Optional data that applies to the specified action. /// @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, SemanticsAction action,
fml::MallocMapping args); fml::MallocMapping args);
@ -620,7 +623,7 @@ class PlatformConfigurationNativeApi {
double width, double width,
double height); double height);
static void UpdateSemantics(SemanticsUpdate* update); static void UpdateSemantics(int64_t viewId, SemanticsUpdate* update);
static void SetNeedsReportTimings(bool value); static void SetNeedsReportTimings(bool value);

View File

@ -711,7 +711,7 @@ class FakePlatformConfigurationClient : public PlatformConfigurationClient {
Scene* scene, Scene* scene,
double width, double width,
double height) override {} double height) override {}
void UpdateSemantics(SemanticsUpdate* update) override {} void UpdateSemantics(int64_t view_id, SemanticsUpdate* update) override {}
void HandlePlatformMessage( void HandlePlatformMessage(
std::unique_ptr<PlatformMessage> message) override {} std::unique_ptr<PlatformMessage> message) override {}
FontCollection& GetFontCollection() override { FontCollection& GetFontCollection() override {

View File

@ -383,13 +383,14 @@ bool RuntimeController::DispatchPointerDataPacket(
return false; return false;
} }
bool RuntimeController::DispatchSemanticsAction(int32_t node_id, bool RuntimeController::DispatchSemanticsAction(int64_t view_id,
int32_t node_id,
SemanticsAction action, SemanticsAction action,
fml::MallocMapping args) { fml::MallocMapping args) {
TRACE_EVENT1("flutter", "RuntimeController::DispatchSemanticsAction", "mode", TRACE_EVENT1("flutter", "RuntimeController::DispatchSemanticsAction", "mode",
"basic"); "basic");
if (auto* platform_configuration = GetPlatformConfigurationIfAvailable()) { if (auto* platform_configuration = GetPlatformConfigurationIfAvailable()) {
platform_configuration->DispatchSemanticsAction(node_id, action, platform_configuration->DispatchSemanticsAction(view_id, node_id, action,
std::move(args)); std::move(args));
return true; return true;
} }
@ -447,9 +448,11 @@ void RuntimeController::CheckIfAllViewsRendered() {
} }
// |PlatformConfigurationClient| // |PlatformConfigurationClient|
void RuntimeController::UpdateSemantics(SemanticsUpdate* update) { void RuntimeController::UpdateSemantics(int64_t view_id,
SemanticsUpdate* update) {
if (platform_data_.semantics_enabled) { if (platform_data_.semantics_enabled) {
client_.UpdateSemantics(update->takeNodes(), update->takeActions()); client_.UpdateSemantics(view_id, update->takeNodes(),
update->takeActions());
} }
} }

View File

@ -490,7 +490,8 @@ class RuntimeController : public PlatformConfigurationClient,
/// @brief Dispatch the semantics action to the specified accessibility /// @brief Dispatch the semantics action to the specified accessibility
/// node. /// 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 /// @param[in] action The semantics action to perform on the specified
/// accessibility node. /// accessibility node.
/// @param[in] args Optional data that applies to the specified action. /// @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 /// @return If the semantics action was dispatched. This may fail if an
/// isolate is not running. /// isolate is not running.
/// ///
bool DispatchSemanticsAction(int32_t node_id, bool DispatchSemanticsAction(int64_t view_id,
int32_t node_id,
SemanticsAction action, SemanticsAction action,
fml::MallocMapping args); fml::MallocMapping args);
@ -767,7 +769,7 @@ class RuntimeController : public PlatformConfigurationClient,
double height) override; double height) override;
// |PlatformConfigurationClient| // |PlatformConfigurationClient|
void UpdateSemantics(SemanticsUpdate* update) override; void UpdateSemantics(int64_t view_id, SemanticsUpdate* update) override;
// |PlatformConfigurationClient| // |PlatformConfigurationClient|
void HandlePlatformMessage(std::unique_ptr<PlatformMessage> message) override; void HandlePlatformMessage(std::unique_ptr<PlatformMessage> message) override;

View File

@ -32,7 +32,8 @@ class RuntimeDelegate {
std::unique_ptr<flutter::LayerTree> layer_tree, std::unique_ptr<flutter::LayerTree> layer_tree,
float device_pixel_ratio) = 0; float device_pixel_ratio) = 0;
virtual void UpdateSemantics(SemanticsNodeUpdates update, virtual void UpdateSemantics(int64_t view_id,
SemanticsNodeUpdates update,
CustomAccessibilityActionUpdates actions) = 0; CustomAccessibilityActionUpdates actions) = 0;
virtual void HandlePlatformMessage( virtual void HandlePlatformMessage(

View File

@ -450,10 +450,11 @@ void Engine::DispatchPointerDataPacket(
pointer_data_dispatcher_->DispatchPacket(std::move(packet), trace_flow_id); 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, SemanticsAction action,
fml::MallocMapping args) { fml::MallocMapping args) {
runtime_controller_->DispatchSemanticsAction(node_id, action, runtime_controller_->DispatchSemanticsAction(view_id, node_id, action,
std::move(args)); 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); 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) { 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<PlatformMessage> message) { void Engine::HandlePlatformMessage(std::unique_ptr<PlatformMessage> message) {

View File

@ -150,12 +150,14 @@ class Engine final : public RuntimeDelegate, PointerDataDispatcher::Delegate {
/// `CustomAccessibilityActionUpdates`, /// `CustomAccessibilityActionUpdates`,
/// `PlatformView::UpdateSemantics` /// `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 /// @param[in] updates A map with the stable semantics node identifier as
/// key and the node properties as the value. /// key and the node properties as the value.
/// @param[in] actions A map with the stable semantics node identifier as /// @param[in] actions A map with the stable semantics node identifier as
/// key and the custom node action as the value. /// key and the custom node action as the value.
/// ///
virtual void OnEngineUpdateSemantics( virtual void OnEngineUpdateSemantics(
int64_t view_id,
SemanticsNodeUpdates updates, SemanticsNodeUpdates updates,
CustomAccessibilityActionUpdates actions) = 0; 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 /// originates on the platform view and has been forwarded to the
/// engine here on the UI task runner by the shell. /// 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] node_id The identifier of the accessibility node.
/// @param[in] action The accessibility related action performed on the /// @param[in] action The accessibility related action performed on the
/// node of the specified ID. /// node of the specified ID.
/// @param[in] args Optional data that applies to the specified action. /// @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, SemanticsAction action,
fml::MallocMapping args); fml::MallocMapping args);
@ -1008,7 +1012,8 @@ class Engine final : public RuntimeDelegate, PointerDataDispatcher::Delegate {
float device_pixel_ratio) override; float device_pixel_ratio) override;
// |RuntimeDelegate| // |RuntimeDelegate|
void UpdateSemantics(SemanticsNodeUpdates update, void UpdateSemantics(int64_t view_id,
SemanticsNodeUpdates update,
CustomAccessibilityActionUpdates actions) override; CustomAccessibilityActionUpdates actions) override;
// |RuntimeDelegate| // |RuntimeDelegate|

View File

@ -54,7 +54,7 @@ class MockDelegate : public Engine::Delegate {
public: public:
MOCK_METHOD(void, MOCK_METHOD(void,
OnEngineUpdateSemantics, OnEngineUpdateSemantics,
(SemanticsNodeUpdates, CustomAccessibilityActionUpdates), (int64_t, SemanticsNodeUpdates, CustomAccessibilityActionUpdates),
(override)); (override));
MOCK_METHOD(void, MOCK_METHOD(void,
OnEngineHandlePlatformMessage, OnEngineHandlePlatformMessage,

View File

@ -62,7 +62,7 @@ class MockDelegate : public Engine::Delegate {
public: public:
MOCK_METHOD(void, MOCK_METHOD(void,
OnEngineUpdateSemantics, OnEngineUpdateSemantics,
(SemanticsNodeUpdates, CustomAccessibilityActionUpdates), (int64_t, SemanticsNodeUpdates, CustomAccessibilityActionUpdates),
(override)); (override));
MOCK_METHOD(void, MOCK_METHOD(void,
OnEngineHandlePlatformMessage, OnEngineHandlePlatformMessage,
@ -113,7 +113,7 @@ class MockRuntimeDelegate : public RuntimeDelegate {
(override)); (override));
MOCK_METHOD(void, MOCK_METHOD(void,
UpdateSemantics, UpdateSemantics,
(SemanticsNodeUpdates, CustomAccessibilityActionUpdates), (int64_t, SemanticsNodeUpdates, CustomAccessibilityActionUpdates),
(override)); (override));
MOCK_METHOD(void, MOCK_METHOD(void,
HandlePlatformMessage, HandlePlatformMessage,

View File

@ -35,10 +35,11 @@ void PlatformView::DispatchPointerDataPacket(
delegate_.OnPlatformViewDispatchPointerDataPacket(std::move(packet)); 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, SemanticsAction action,
fml::MallocMapping args) { fml::MallocMapping args) {
delegate_.OnPlatformViewDispatchSemanticsAction(node_id, action, delegate_.OnPlatformViewDispatchSemanticsAction(view_id, node_id, action,
std::move(args)); std::move(args));
} }
@ -124,6 +125,7 @@ fml::WeakPtr<PlatformView> PlatformView::GetWeakPtr() const {
} }
void PlatformView::UpdateSemantics( void PlatformView::UpdateSemantics(
int64_t view_id,
SemanticsNodeUpdates update, // NOLINT(performance-unnecessary-value-param) SemanticsNodeUpdates update, // NOLINT(performance-unnecessary-value-param)
// NOLINTNEXTLINE(performance-unnecessary-value-param) // NOLINTNEXTLINE(performance-unnecessary-value-param)
CustomAccessibilityActionUpdates actions) {} CustomAccessibilityActionUpdates actions) {}

View File

@ -188,6 +188,7 @@ class PlatformView {
/// event must be forwarded to the running root isolate hosted /// event must be forwarded to the running root isolate hosted
/// by the engine on the UI thread. /// 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] node_id The identifier of the accessibility node.
/// @param[in] action The accessibility related action performed on the /// @param[in] action The accessibility related action performed on the
/// node of the specified ID. /// node of the specified ID.
@ -195,6 +196,7 @@ class PlatformView {
/// specified action. /// specified action.
/// ///
virtual void OnPlatformViewDispatchSemanticsAction( virtual void OnPlatformViewDispatchSemanticsAction(
int64_t view_id,
int32_t node_id, int32_t node_id,
SemanticsAction action, SemanticsAction action,
fml::MallocMapping args) = 0; fml::MallocMapping args) = 0;
@ -450,12 +452,14 @@ class PlatformView {
/// @brief Used by embedders to dispatch an accessibility action to a /// @brief Used by embedders to dispatch an accessibility action to a
/// running isolate hosted by the engine. /// 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 /// @param[in] node_id The identifier of the accessibility node on which to
/// perform the action. /// perform the action.
/// @param[in] action The action /// @param[in] action The action
/// @param[in] args The arguments /// @param[in] args The arguments
/// ///
void DispatchSemanticsAction(int32_t node_id, void DispatchSemanticsAction(int64_t view_id,
int32_t node_id,
SemanticsAction action, SemanticsAction action,
fml::MallocMapping args); fml::MallocMapping args);
@ -500,12 +504,14 @@ class PlatformView {
/// @see SemanticsNode, SemticsNodeUpdates, /// @see SemanticsNode, SemticsNodeUpdates,
/// CustomAccessibilityActionUpdates /// 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 /// @param[in] updates A map with the stable semantics node identifier as
/// key and the node properties as the value. /// key and the node properties as the value.
/// @param[in] actions A map with the stable semantics node identifier as /// @param[in] actions A map with the stable semantics node identifier as
/// key and the custom node action as the value. /// 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); CustomAccessibilityActionUpdates actions);
//---------------------------------------------------------------------------- //----------------------------------------------------------------------------

View File

@ -1107,7 +1107,8 @@ void Shell::OnPlatformViewDispatchPointerDataPacket(
} }
// |PlatformView::Delegate| // |PlatformView::Delegate|
void Shell::OnPlatformViewDispatchSemanticsAction(int32_t node_id, void Shell::OnPlatformViewDispatchSemanticsAction(int64_t view_id,
int32_t node_id,
SemanticsAction action, SemanticsAction action,
fml::MallocMapping args) { fml::MallocMapping args) {
FML_DCHECK(is_set_up_); FML_DCHECK(is_set_up_);
@ -1115,10 +1116,11 @@ void Shell::OnPlatformViewDispatchSemanticsAction(int32_t node_id,
fml::TaskRunner::RunNowAndFlushMessages( fml::TaskRunner::RunNowAndFlushMessages(
task_runners_.GetUITaskRunner(), task_runners_.GetUITaskRunner(),
fml::MakeCopyable([engine = engine_->GetWeakPtr(), node_id, action, fml::MakeCopyable([engine = engine_->GetWeakPtr(), view_id, node_id,
args = std::move(args)]() mutable { action, args = std::move(args)]() mutable {
if (engine) { 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| // |Engine::Delegate|
void Shell::OnEngineUpdateSemantics(SemanticsNodeUpdates update, void Shell::OnEngineUpdateSemantics(int64_t view_id,
SemanticsNodeUpdates update,
CustomAccessibilityActionUpdates actions) { CustomAccessibilityActionUpdates actions) {
FML_DCHECK(is_set_up_); FML_DCHECK(is_set_up_);
FML_DCHECK(task_runners_.GetUITaskRunner()->RunsTasksOnCurrentThread()); FML_DCHECK(task_runners_.GetUITaskRunner()->RunsTasksOnCurrentThread());
@ -1323,9 +1326,9 @@ void Shell::OnEngineUpdateSemantics(SemanticsNodeUpdates update,
task_runners_.GetPlatformTaskRunner()->RunNowOrPostTask( task_runners_.GetPlatformTaskRunner()->RunNowOrPostTask(
task_runners_.GetPlatformTaskRunner(), task_runners_.GetPlatformTaskRunner(),
[view = platform_view_->GetWeakPtr(), update = std::move(update), [view = platform_view_->GetWeakPtr(), update = std::move(update),
actions = std::move(actions)] { actions = std::move(actions), view_id = view_id] {
if (view) { if (view) {
view->UpdateSemantics(update, actions); view->UpdateSemantics(view_id, update, actions);
} }
}); });
} }

View File

@ -605,7 +605,8 @@ class Shell final : public PlatformView::Delegate,
std::unique_ptr<PointerDataPacket> packet) override; std::unique_ptr<PointerDataPacket> packet) override;
// |PlatformView::Delegate| // |PlatformView::Delegate|
void OnPlatformViewDispatchSemanticsAction(int32_t node_id, void OnPlatformViewDispatchSemanticsAction(int64_t view_id,
int32_t node_id,
SemanticsAction action, SemanticsAction action,
fml::MallocMapping args) override; fml::MallocMapping args) override;
@ -666,6 +667,7 @@ class Shell final : public PlatformView::Delegate,
// |Engine::Delegate| // |Engine::Delegate|
void OnEngineUpdateSemantics( void OnEngineUpdateSemantics(
int64_t view_id,
SemanticsNodeUpdates update, SemanticsNodeUpdates update,
CustomAccessibilityActionUpdates actions) override; CustomAccessibilityActionUpdates actions) override;

View File

@ -66,10 +66,11 @@ void ShellTest::SendPlatformMessage(Shell* shell,
} }
void ShellTest::SendSemanticsAction(Shell* shell, void ShellTest::SendSemanticsAction(Shell* shell,
int64_t view_id,
int32_t node_id, int32_t node_id,
SemanticsAction action, SemanticsAction action,
fml::MallocMapping args) { fml::MallocMapping args) {
shell->OnPlatformViewDispatchSemanticsAction(node_id, action, shell->OnPlatformViewDispatchSemanticsAction(view_id, node_id, action,
std::move(args)); std::move(args));
} }

View File

@ -92,6 +92,7 @@ class ShellTest : public FixtureTest {
std::unique_ptr<PlatformMessage> message); std::unique_ptr<PlatformMessage> message);
void SendSemanticsAction(Shell* shell, void SendSemanticsAction(Shell* shell,
int64_t view_id,
int32_t node_id, int32_t node_id,
SemanticsAction action, SemanticsAction action,
fml::MallocMapping args); fml::MallocMapping args);

View File

@ -143,7 +143,10 @@ class MockPlatformViewDelegate : public PlatformView::Delegate {
MOCK_METHOD(void, MOCK_METHOD(void,
OnPlatformViewDispatchSemanticsAction, OnPlatformViewDispatchSemanticsAction,
(int32_t id, SemanticsAction action, fml::MallocMapping args), (int64_t view_id,
int32_t id,
SemanticsAction action,
fml::MallocMapping args),
(override)); (override));
MOCK_METHOD(void, MOCK_METHOD(void,
@ -4341,7 +4344,7 @@ TEST_F(ShellTest, SemanticsActionsFlushMessageLoop) {
CREATE_NATIVE_ENTRY([&](auto args) { latch.CountDown(); })); CREATE_NATIVE_ENTRY([&](auto args) { latch.CountDown(); }));
task_runners.GetPlatformTaskRunner()->PostTask([&] { task_runners.GetPlatformTaskRunner()->PostTask([&] {
SendSemanticsAction(shell.get(), 0, SemanticsAction::kTap, SendSemanticsAction(shell.get(), 456, 0, SemanticsAction::kTap,
fml::MallocMapping(nullptr, 0)); fml::MallocMapping(nullptr, 0));
}); });
latch.Wait(); latch.Wait();

View File

@ -44,6 +44,7 @@ namespace flutter {
namespace { namespace {
static constexpr int kMinAPILevelHCPP = 34; static constexpr int kMinAPILevelHCPP = 34;
static constexpr int64_t kImplicitViewId = 0;
AndroidContext::ContextSettings CreateContextSettings( AndroidContext::ContextSettings CreateContextSettings(
const Settings& p_settings) { const Settings& p_settings) {
@ -265,13 +266,15 @@ void PlatformViewAndroid::OnPreEngineRestart() const {
} }
void PlatformViewAndroid::DispatchSemanticsAction(JNIEnv* env, void PlatformViewAndroid::DispatchSemanticsAction(JNIEnv* env,
jint id, jint node_id,
jint action, jint action,
jobject args, jobject args,
jint args_position) { jint args_position) {
// TODO(team-android): Remove implicit view assumption.
// https://github.com/flutter/flutter/issues/142845
if (env->IsSameObject(args, NULL)) { if (env->IsSameObject(args, NULL)) {
PlatformView::DispatchSemanticsAction( PlatformView::DispatchSemanticsAction(
id, static_cast<flutter::SemanticsAction>(action), kImplicitViewId, node_id, static_cast<flutter::SemanticsAction>(action),
fml::MallocMapping()); fml::MallocMapping());
return; return;
} }
@ -280,12 +283,13 @@ void PlatformViewAndroid::DispatchSemanticsAction(JNIEnv* env,
auto args_vector = fml::MallocMapping::Copy(args_data, args_position); auto args_vector = fml::MallocMapping::Copy(args_data, args_position);
PlatformView::DispatchSemanticsAction( PlatformView::DispatchSemanticsAction(
id, static_cast<flutter::SemanticsAction>(action), kImplicitViewId, node_id, static_cast<flutter::SemanticsAction>(action),
std::move(args_vector)); std::move(args_vector));
} }
// |PlatformView| // |PlatformView|
void PlatformViewAndroid::UpdateSemantics( void PlatformViewAndroid::UpdateSemantics(
int64_t view_id,
flutter::SemanticsNodeUpdates update, flutter::SemanticsNodeUpdates update,
flutter::CustomAccessibilityActionUpdates actions) { flutter::CustomAccessibilityActionUpdates actions) {
platform_view_android_delegate_.UpdateSemantics(update, actions); platform_view_android_delegate_.UpdateSemantics(update, actions);

View File

@ -133,6 +133,7 @@ class PlatformViewAndroid final : public PlatformView {
// |PlatformView| // |PlatformView|
void UpdateSemantics( void UpdateSemantics(
int64_t view_id,
flutter::SemanticsNodeUpdates update, flutter::SemanticsNodeUpdates update,
flutter::CustomAccessibilityActionUpdates actions) override; flutter::CustomAccessibilityActionUpdates actions) override;

View File

@ -35,7 +35,8 @@ class FakeDelegate : public PlatformView::Delegate {
void OnPlatformViewDispatchPlatformMessage(std::unique_ptr<PlatformMessage> message) override {} void OnPlatformViewDispatchPlatformMessage(std::unique_ptr<PlatformMessage> message) override {}
void OnPlatformViewDispatchPointerDataPacket(std::unique_ptr<PointerDataPacket> packet) override { void OnPlatformViewDispatchPointerDataPacket(std::unique_ptr<PointerDataPacket> packet) override {
} }
void OnPlatformViewDispatchSemanticsAction(int32_t id, void OnPlatformViewDispatchSemanticsAction(int64_t view_id,
int32_t node_id,
SemanticsAction action, SemanticsAction action,
fml::MallocMapping args) override {} fml::MallocMapping args) override {}
void OnPlatformViewSetSemanticsEnabled(bool enabled) override {} void OnPlatformViewSetSemanticsEnabled(bool enabled) override {}

View File

@ -270,7 +270,8 @@ class FlutterPlatformViewsTestMockPlatformViewDelegate : public PlatformView::De
void OnPlatformViewDispatchPlatformMessage(std::unique_ptr<PlatformMessage> message) override {} void OnPlatformViewDispatchPlatformMessage(std::unique_ptr<PlatformMessage> message) override {}
void OnPlatformViewDispatchPointerDataPacket(std::unique_ptr<PointerDataPacket> packet) override { void OnPlatformViewDispatchPointerDataPacket(std::unique_ptr<PointerDataPacket> packet) override {
} }
void OnPlatformViewDispatchSemanticsAction(int32_t id, void OnPlatformViewDispatchSemanticsAction(int64_t view_id,
int32_t node_id,
SemanticsAction action, SemanticsAction action,
fml::MallocMapping args) override {} fml::MallocMapping args) override {}
void OnPlatformViewSetSemanticsEnabled(bool enabled) override {} void OnPlatformViewSetSemanticsEnabled(bool enabled) override {}

View File

@ -12,6 +12,8 @@
#import "flutter/shell/platform/darwin/ios/framework/Source/TextInputSemanticsObject.h" #import "flutter/shell/platform/darwin/ios/framework/Source/TextInputSemanticsObject.h"
#import "flutter/shell/platform/darwin/ios/platform_view_ios.h" #import "flutter/shell/platform/darwin/ios/platform_view_ios.h"
#include "flutter/common/constants.h"
#pragma GCC diagnostic error "-Wundeclared-selector" #pragma GCC diagnostic error "-Wundeclared-selector"
FLUTTER_ASSERT_ARC FLUTTER_ASSERT_ARC
@ -235,14 +237,20 @@ void AccessibilityBridge::UpdateSemantics(
} }
} }
void AccessibilityBridge::DispatchSemanticsAction(int32_t uid, flutter::SemanticsAction action) { void AccessibilityBridge::DispatchSemanticsAction(int32_t node_uid,
platform_view_->DispatchSemanticsAction(uid, action, {}); 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, flutter::SemanticsAction action,
fml::MallocMapping args) { 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, static void ReplaceSemanticsObject(SemanticsObject* oldObject,

View File

@ -82,7 +82,8 @@ class MockDelegate : public PlatformView::Delegate {
void OnPlatformViewDispatchPlatformMessage(std::unique_ptr<PlatformMessage> message) override {} void OnPlatformViewDispatchPlatformMessage(std::unique_ptr<PlatformMessage> message) override {}
void OnPlatformViewDispatchPointerDataPacket(std::unique_ptr<PointerDataPacket> packet) override { void OnPlatformViewDispatchPointerDataPacket(std::unique_ptr<PointerDataPacket> packet) override {
} }
void OnPlatformViewDispatchSemanticsAction(int32_t id, void OnPlatformViewDispatchSemanticsAction(int64_t view_id,
int32_t node_id,
SemanticsAction action, SemanticsAction action,
fml::MallocMapping args) override {} fml::MallocMapping args) override {}
void OnPlatformViewSetSemanticsEnabled(bool enabled) override {} void OnPlatformViewSetSemanticsEnabled(bool enabled) override {}

View File

@ -103,7 +103,8 @@ class PlatformViewIOS final : public PlatformView {
void SetAccessibilityFeatures(int32_t flags) override; void SetAccessibilityFeatures(int32_t flags) override;
// |PlatformView| // |PlatformView|
void UpdateSemantics(flutter::SemanticsNodeUpdates update, void UpdateSemantics(int64_t view_id,
flutter::SemanticsNodeUpdates update,
flutter::CustomAccessibilityActionUpdates actions) override; flutter::CustomAccessibilityActionUpdates actions) override;
// |PlatformView| // |PlatformView|

View File

@ -181,7 +181,8 @@ void PlatformViewIOS::SetAccessibilityFeatures(int32_t flags) {
} }
// |PlatformView| // |PlatformView|
void PlatformViewIOS::UpdateSemantics(flutter::SemanticsNodeUpdates update, void PlatformViewIOS::UpdateSemantics(int64_t view_id,
flutter::SemanticsNodeUpdates update,
flutter::CustomAccessibilityActionUpdates actions) { flutter::CustomAccessibilityActionUpdates actions) {
FML_DCHECK(owner_controller_); FML_DCHECK(owner_controller_);
if (accessibility_bridge_) { if (accessibility_bridge_) {

View File

@ -332,6 +332,8 @@ AccessibilityBridgeMac::MacOSEventsFromAXEvent(ui::AXEventGenerator::Event event
void AccessibilityBridgeMac::DispatchAccessibilityAction(ui::AXNode::AXID target, void AccessibilityBridgeMac::DispatchAccessibilityAction(ui::AXNode::AXID target,
FlutterSemanticsAction action, FlutterSemanticsAction action,
fml::MallocMapping data) { 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(flutter_engine_, @"Flutter engine should not be deallocated");
NSCAssert(view_controller_.viewLoaded && view_controller_.view.window, NSCAssert(view_controller_.viewLoaded && view_controller_.view.window,
@"The accessibility bridge should not receive accessibility actions if the flutter view" @"The accessibility bridge should not receive accessibility actions if the flutter view"

View File

@ -1843,9 +1843,9 @@ CreateEmbedderSemanticsUpdateCallbackV1(
update_semantics_custom_action_callback, update_semantics_custom_action_callback,
void* user_data) { void* user_data) {
return [update_semantics_node_callback, return [update_semantics_node_callback,
update_semantics_custom_action_callback, update_semantics_custom_action_callback, user_data](
user_data](const flutter::SemanticsNodeUpdates& nodes, int64_t view_id, const flutter::SemanticsNodeUpdates& nodes,
const flutter::CustomAccessibilityActionUpdates& actions) { const flutter::CustomAccessibilityActionUpdates& actions) {
flutter::EmbedderSemanticsUpdate update{nodes, actions}; flutter::EmbedderSemanticsUpdate update{nodes, actions};
FlutterSemanticsUpdate* update_ptr = update.get(); FlutterSemanticsUpdate* update_ptr = update.get();
@ -1890,7 +1890,7 @@ CreateEmbedderSemanticsUpdateCallbackV2(
FlutterUpdateSemanticsCallback update_semantics_callback, FlutterUpdateSemanticsCallback update_semantics_callback,
void* user_data) { void* user_data) {
return [update_semantics_callback, user_data]( return [update_semantics_callback, user_data](
const flutter::SemanticsNodeUpdates& nodes, int64_t view_id, const flutter::SemanticsNodeUpdates& nodes,
const flutter::CustomAccessibilityActionUpdates& actions) { const flutter::CustomAccessibilityActionUpdates& actions) {
flutter::EmbedderSemanticsUpdate update{nodes, actions}; flutter::EmbedderSemanticsUpdate update{nodes, actions};
@ -1905,9 +1905,9 @@ CreateEmbedderSemanticsUpdateCallbackV3(
FlutterUpdateSemanticsCallback2 update_semantics_callback, FlutterUpdateSemanticsCallback2 update_semantics_callback,
void* user_data) { void* user_data) {
return [update_semantics_callback, user_data]( return [update_semantics_callback, user_data](
const flutter::SemanticsNodeUpdates& nodes, int64_t view_id, const flutter::SemanticsNodeUpdates& nodes,
const flutter::CustomAccessibilityActionUpdates& actions) { const flutter::CustomAccessibilityActionUpdates& actions) {
flutter::EmbedderSemanticsUpdate2 update{nodes, actions}; flutter::EmbedderSemanticsUpdate2 update{view_id, nodes, actions};
update_semantics_callback(update.get(), user_data); update_semantics_callback(update.get(), user_data);
}; };
@ -3189,14 +3189,27 @@ FlutterEngineResult FlutterEngineDispatchSemanticsAction(
FlutterSemanticsAction action, FlutterSemanticsAction action,
const uint8_t* data, const uint8_t* data,
size_t data_length) { 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) { if (engine == nullptr) {
return LOG_EMBEDDER_ERROR(kInvalidArguments, "Invalid engine handle."); return LOG_EMBEDDER_ERROR(kInvalidArguments, "Invalid engine handle.");
} }
auto engine_action = static_cast<flutter::SemanticsAction>(action); auto engine_action = static_cast<flutter::SemanticsAction>(info->action);
if (!reinterpret_cast<flutter::EmbedderEngine*>(engine) if (!reinterpret_cast<flutter::EmbedderEngine*>(engine)
->DispatchSemanticsAction( ->DispatchSemanticsAction(
node_id, engine_action, info->view_id, info->node_id, engine_action,
fml::MallocMapping::Copy(data, data_length))) { fml::MallocMapping::Copy(info->data, info->data_length))) {
return LOG_EMBEDDER_ERROR(kInternalInconsistency, return LOG_EMBEDDER_ERROR(kInternalInconsistency,
"Could not dispatch semantics action."); "Could not dispatch semantics action.");
} }
@ -3705,6 +3718,7 @@ FlutterEngineResult FlutterEngineGetProcAddresses(
SET_PROC(UpdateAccessibilityFeatures, SET_PROC(UpdateAccessibilityFeatures,
FlutterEngineUpdateAccessibilityFeatures); FlutterEngineUpdateAccessibilityFeatures);
SET_PROC(DispatchSemanticsAction, FlutterEngineDispatchSemanticsAction); SET_PROC(DispatchSemanticsAction, FlutterEngineDispatchSemanticsAction);
SET_PROC(SendSemanticsAction, FlutterEngineSendSemanticsAction);
SET_PROC(OnVsync, FlutterEngineOnVsync); SET_PROC(OnVsync, FlutterEngineOnVsync);
SET_PROC(ReloadSystemFonts, FlutterEngineReloadSystemFonts); SET_PROC(ReloadSystemFonts, FlutterEngineReloadSystemFonts);
SET_PROC(TraceEventDurationBegin, FlutterEngineTraceEventDurationBegin); SET_PROC(TraceEventDurationBegin, FlutterEngineTraceEventDurationBegin);

View File

@ -1687,6 +1687,8 @@ typedef struct {
/// Array of semantics custom action pointers. Has length /// Array of semantics custom action pointers. Has length
/// `custom_action_count`. /// `custom_action_count`.
FlutterSemanticsCustomAction2** custom_actions; FlutterSemanticsCustomAction2** custom_actions;
// The ID of the view that this update is associated with.
FlutterViewId view_id;
} FlutterSemanticsUpdate2; } FlutterSemanticsUpdate2;
typedef void (*FlutterUpdateSemanticsNodeCallback)( typedef void (*FlutterUpdateSemanticsNodeCallback)(
@ -2645,6 +2647,27 @@ typedef struct {
int64_t engine_id; int64_t engine_id;
} FlutterProjectArgs; } 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 #ifndef FLUTTER_ENGINE_NO_PROTOTYPES
// NOLINTBEGIN(google-objc-function-naming) // NOLINTBEGIN(google-objc-function-naming)
@ -3078,7 +3101,10 @@ FlutterEngineResult FlutterEngineUpdateAccessibilityFeatures(
FlutterAccessibilityFeature features); 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] engine A running engine instance.
/// @param[in] node_id The semantics node identifier. /// @param[in] node_id The semantics node identifier.
@ -3096,6 +3122,22 @@ FlutterEngineResult FlutterEngineDispatchSemanticsAction(
const uint8_t* data, const uint8_t* data,
size_t data_length); 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 /// @brief Notify the engine that a vsync event occurred. A baton passed to
/// the platform via the vsync callback must be returned. This call /// the platform via the vsync callback must be returned. This call
@ -3483,6 +3525,9 @@ typedef FlutterEngineResult (*FlutterEngineDispatchSemanticsActionFnPtr)(
FlutterSemanticsAction action, FlutterSemanticsAction action,
const uint8_t* data, const uint8_t* data,
size_t data_length); size_t data_length);
typedef FlutterEngineResult (*FlutterEngineSendSemanticsActionFnPtr)(
FLUTTER_API_SYMBOL(FlutterEngine) engine,
const FlutterSendSemanticsActionInfo* info);
typedef FlutterEngineResult (*FlutterEngineOnVsyncFnPtr)( typedef FlutterEngineResult (*FlutterEngineOnVsyncFnPtr)(
FLUTTER_API_SYMBOL(FlutterEngine) engine, FLUTTER_API_SYMBOL(FlutterEngine) engine,
intptr_t baton, intptr_t baton,
@ -3585,6 +3630,7 @@ typedef struct {
FlutterEngineAddViewFnPtr AddView; FlutterEngineAddViewFnPtr AddView;
FlutterEngineRemoveViewFnPtr RemoveView; FlutterEngineRemoveViewFnPtr RemoveView;
FlutterEngineSendViewFocusEventFnPtr SendViewFocusEvent; FlutterEngineSendViewFocusEventFnPtr SendViewFocusEvent;
FlutterEngineSendSemanticsActionFnPtr SendSemanticsAction;
} FlutterEngineProcTable; } FlutterEngineProcTable;
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------

View File

@ -235,7 +235,8 @@ bool EmbedderEngine::SetAccessibilityFeatures(int32_t flags) {
return true; return true;
} }
bool EmbedderEngine::DispatchSemanticsAction(int node_id, bool EmbedderEngine::DispatchSemanticsAction(int64_t view_id,
int node_id,
flutter::SemanticsAction action, flutter::SemanticsAction action,
fml::MallocMapping args) { fml::MallocMapping args) {
if (!IsValid()) { if (!IsValid()) {
@ -245,7 +246,8 @@ bool EmbedderEngine::DispatchSemanticsAction(int node_id,
if (!platform_view) { if (!platform_view) {
return false; return false;
} }
platform_view->DispatchSemanticsAction(node_id, action, std::move(args)); platform_view->DispatchSemanticsAction(view_id, node_id, action,
std::move(args));
return true; return true;
} }

View File

@ -68,7 +68,8 @@ class EmbedderEngine {
bool SetAccessibilityFeatures(int32_t flags); bool SetAccessibilityFeatures(int32_t flags);
bool DispatchSemanticsAction(int node_id, bool DispatchSemanticsAction(int64_t view_id,
int node_id,
flutter::SemanticsAction action, flutter::SemanticsAction action,
fml::MallocMapping args); fml::MallocMapping args);

View File

@ -88,6 +88,7 @@ void EmbedderSemanticsUpdate::AddAction(
EmbedderSemanticsUpdate::~EmbedderSemanticsUpdate() {} EmbedderSemanticsUpdate::~EmbedderSemanticsUpdate() {}
EmbedderSemanticsUpdate2::EmbedderSemanticsUpdate2( EmbedderSemanticsUpdate2::EmbedderSemanticsUpdate2(
int64_t view_id,
const SemanticsNodeUpdates& nodes, const SemanticsNodeUpdates& nodes,
const CustomAccessibilityActionUpdates& actions) { const CustomAccessibilityActionUpdates& actions) {
nodes_.reserve(nodes.size()); nodes_.reserve(nodes.size());
@ -111,13 +112,12 @@ EmbedderSemanticsUpdate2::EmbedderSemanticsUpdate2(
action_pointers_.push_back(&actions_[i]); action_pointers_.push_back(&actions_[i]);
} }
update_ = { update_ = {.struct_size = sizeof(FlutterSemanticsUpdate2),
.struct_size = sizeof(FlutterSemanticsUpdate2), .node_count = node_pointers_.size(),
.node_count = node_pointers_.size(), .nodes = node_pointers_.data(),
.nodes = node_pointers_.data(), .custom_action_count = action_pointers_.size(),
.custom_action_count = action_pointers_.size(), .custom_actions = action_pointers_.data(),
.custom_actions = action_pointers_.data(), .view_id = view_id};
};
} }
EmbedderSemanticsUpdate2::~EmbedderSemanticsUpdate2() {} EmbedderSemanticsUpdate2::~EmbedderSemanticsUpdate2() {}

View File

@ -47,7 +47,8 @@ class EmbedderSemanticsUpdate {
// and the temporary embedder-specific objects are automatically cleaned up. // and the temporary embedder-specific objects are automatically cleaned up.
class EmbedderSemanticsUpdate2 { class EmbedderSemanticsUpdate2 {
public: public:
EmbedderSemanticsUpdate2(const SemanticsNodeUpdates& nodes, EmbedderSemanticsUpdate2(int64_t view_id,
const SemanticsNodeUpdates& nodes,
const CustomAccessibilityActionUpdates& actions); const CustomAccessibilityActionUpdates& actions);
~EmbedderSemanticsUpdate2(); ~EmbedderSemanticsUpdate2();

View File

@ -1632,3 +1632,70 @@ void testSendViewFocusChangeRequest() {
direction: ViewFocusDirection.backward, direction: ViewFocusDirection.backward,
); );
} }
@pragma('vm:entry-point')
// ignore: non_constant_identifier_names
Future<void> 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: <StringAttribute>[],
rect: const Rect.fromLTRB(0.0, 0.0, 10.0, 10.0),
transform: kTestTransform,
childrenInTraversalOrder: Int32List.fromList(<int>[84, 96]),
childrenInHitTestOrder: Int32List.fromList(<int>[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: <StringAttribute>[],
value: '',
valueAttributes: <StringAttribute>[],
increasedValue: '',
increasedValueAttributes: <StringAttribute>[],
decreasedValue: '',
decreasedValueAttributes: <StringAttribute>[],
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);
}

View File

@ -113,11 +113,12 @@ PlatformViewEmbedder::PlatformViewEmbedder(
PlatformViewEmbedder::~PlatformViewEmbedder() = default; PlatformViewEmbedder::~PlatformViewEmbedder() = default;
void PlatformViewEmbedder::UpdateSemantics( void PlatformViewEmbedder::UpdateSemantics(
int64_t view_id,
flutter::SemanticsNodeUpdates update, flutter::SemanticsNodeUpdates update,
flutter::CustomAccessibilityActionUpdates actions) { flutter::CustomAccessibilityActionUpdates actions) {
if (platform_dispatch_table_.update_semantics_callback != nullptr) { if (platform_dispatch_table_.update_semantics_callback != nullptr) {
platform_dispatch_table_.update_semantics_callback(std::move(update), platform_dispatch_table_.update_semantics_callback(
std::move(actions)); view_id, std::move(update), std::move(actions));
} }
} }

View File

@ -36,7 +36,8 @@ namespace flutter {
class PlatformViewEmbedder final : public PlatformView { class PlatformViewEmbedder final : public PlatformView {
public: public:
using UpdateSemanticsCallback = using UpdateSemanticsCallback =
std::function<void(flutter::SemanticsNodeUpdates update, std::function<void(int64_t view_id,
flutter::SemanticsNodeUpdates update,
flutter::CustomAccessibilityActionUpdates actions)>; flutter::CustomAccessibilityActionUpdates actions)>;
using PlatformMessageResponseCallback = using PlatformMessageResponseCallback =
std::function<void(std::unique_ptr<PlatformMessage>)>; std::function<void(std::unique_ptr<PlatformMessage>)>;
@ -104,6 +105,7 @@ class PlatformViewEmbedder final : public PlatformView {
// |PlatformView| // |PlatformView|
void UpdateSemantics( void UpdateSemantics(
int64_t view_id,
flutter::SemanticsNodeUpdates update, flutter::SemanticsNodeUpdates update,
flutter::CustomAccessibilityActionUpdates actions) override; flutter::CustomAccessibilityActionUpdates actions) override;

View File

@ -54,7 +54,10 @@ class MockDelegate : public PlatformView::Delegate {
(override)); (override));
MOCK_METHOD(void, MOCK_METHOD(void,
OnPlatformViewDispatchSemanticsAction, OnPlatformViewDispatchSemanticsAction,
(int32_t id, SemanticsAction action, fml::MallocMapping args), (int64_t view_id,
int32_t node_id,
SemanticsAction action,
fml::MallocMapping args),
(override)); (override));
MOCK_METHOD(void, MOCK_METHOD(void,
OnPlatformViewSetSemanticsEnabled, OnPlatformViewSetSemanticsEnabled,

View File

@ -774,6 +774,184 @@ TEST_F(EmbedderA11yTest, A11yTreeIsConsistentUsingV1Callbacks) {
#endif #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<EmbedderTestContextSoftware>();
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(
([&notify_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((
[&notify_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<bool>::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 = &notify_add_view_latch;
add_view_info.add_view_callback = [](const FlutterAddViewResult* result) {
EXPECT_TRUE(result->added);
auto latch =
reinterpret_cast<fml::AutoResetWaitableEvent*>(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<fml::AutoResetWaitableEvent*>(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<bool>::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<bool>::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<bool>::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 testing
} // namespace flutter } // namespace flutter

View File

@ -42,6 +42,7 @@
namespace flutter_runner { namespace flutter_runner {
namespace { namespace {
constexpr static int64_t kImplicitViewId = 0;
zx_koid_t GetKoid(const fuchsia::ui::views::ViewRef& view_ref) { zx_koid_t GetKoid(const fuchsia::ui::views::ViewRef& view_ref) {
zx_handle_t handle = view_ref.reference.get(); zx_handle_t handle = view_ref.reference.get();
@ -393,7 +394,10 @@ void Engine::Initialize(
auto platform_view = shell_->GetPlatformView(); auto platform_view = shell_->GetPlatformView();
if (platform_view) { 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, {});
} }
}; };

View File

@ -676,6 +676,7 @@ void PlatformView::SetSemanticsEnabled(bool enabled) {
// |flutter::PlatformView| // |flutter::PlatformView|
void PlatformView::UpdateSemantics( void PlatformView::UpdateSemantics(
int64_t view_id,
flutter::SemanticsNodeUpdates update, flutter::SemanticsNodeUpdates update,
flutter::CustomAccessibilityActionUpdates actions) { flutter::CustomAccessibilityActionUpdates actions) {
const float pixel_ratio = const float pixel_ratio =

View File

@ -134,6 +134,7 @@ class PlatformView : public flutter::PlatformView {
// |flutter::PlatformView| // |flutter::PlatformView|
void UpdateSemantics( void UpdateSemantics(
int64_t view_id,
flutter::SemanticsNodeUpdates update, flutter::SemanticsNodeUpdates update,
flutter::CustomAccessibilityActionUpdates actions) override; flutter::CustomAccessibilityActionUpdates actions) override;

View File

@ -121,7 +121,8 @@ class MockPlatformViewDelegate : public flutter::PlatformView::Delegate {
std::unique_ptr<flutter::KeyDataPacket> packet, std::unique_ptr<flutter::KeyDataPacket> packet,
std::function<void(bool)> callback) {} std::function<void(bool)> callback) {}
// |flutter::PlatformView::Delegate| // |flutter::PlatformView::Delegate|
void OnPlatformViewDispatchSemanticsAction(int32_t id, void OnPlatformViewDispatchSemanticsAction(int64_t view_id,
int32_t node_id,
flutter::SemanticsAction action, flutter::SemanticsAction action,
fml::MallocMapping args) {} fml::MallocMapping args) {}
// |flutter::PlatformView::Delegate| // |flutter::PlatformView::Delegate|

View File

@ -68,10 +68,13 @@ struct FlAccessibleNodePrivate {
// Weak reference to the engine this node is created for. // Weak reference to the engine this node is created for.
FlEngine* engine; 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. // Weak reference to the parent node of this one or %NULL.
AtkObject* parent; AtkObject* parent;
int32_t id; int32_t node_id;
gchar* name; gchar* name;
gint index; gint index;
gint x, y, width, height; gint x, y, width, height;
@ -81,7 +84,7 @@ struct FlAccessibleNodePrivate {
FlutterSemanticsFlag flags; 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) \ #define FL_ACCESSIBLE_NODE_GET_PRIVATE(node) \
((FlAccessibleNodePrivate*)fl_accessible_node_get_instance_private( \ ((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, g_object_add_weak_pointer(object,
reinterpret_cast<gpointer*>(&priv->engine)); reinterpret_cast<gpointer*>(&priv->engine));
break; break;
case PROP_VIEW_ID:
priv->view_id = g_value_get_int64(value);
break;
case PROP_ID: case PROP_ID:
priv->id = g_value_get_int(value); priv->node_id = g_value_get_int(value);
break; break;
default: default:
G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
@ -417,7 +423,8 @@ static void fl_accessible_node_perform_action_impl(
FlutterSemanticsAction action, FlutterSemanticsAction action,
GBytes* data) { GBytes* data) {
FlAccessibleNodePrivate* priv = FL_ACCESSIBLE_NODE_GET_PRIVATE(self); 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) { 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(), "engine", "engine", "Flutter engine", fl_engine_get_type(),
static_cast<GParamFlags>(G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | static_cast<GParamFlags>(G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY |
G_PARAM_STATIC_STRINGS))); 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<GParamFlags>(G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY)));
g_object_class_install_property( g_object_class_install_property(
G_OBJECT_CLASS(klass), PROP_ID, G_OBJECT_CLASS(klass), PROP_ID,
g_param_spec_int( 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<GParamFlags>(G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | static_cast<GParamFlags>(G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY |
G_PARAM_STATIC_STRINGS))); 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); priv->children = g_ptr_array_new_with_free_func(g_object_unref);
} }
FlAccessibleNode* fl_accessible_node_new(FlEngine* engine, int32_t id) { FlAccessibleNode* fl_accessible_node_new(FlEngine* engine,
FlAccessibleNode* self = FL_ACCESSIBLE_NODE(g_object_new( FlutterViewId view_id,
fl_accessible_node_get_type(), "engine", engine, "id", id, nullptr)); 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; return self;
} }

View File

@ -55,14 +55,17 @@ struct _FlAccessibleNodeClass {
/** /**
* fl_accessible_node_new: * fl_accessible_node_new:
* @engine: the #FlEngine this node came from. * @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 * Creates a new accessibility object that exposes Flutter accessibility
* information to ATK. * information to ATK.
* *
* Returns: a new #FlAccessibleNode. * 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: * fl_accessible_node_set_parent:

View File

@ -12,10 +12,13 @@ TEST(FlAccessibleNodeTest, BuildTree) {
g_autoptr(FlDartProject) project = fl_dart_project_new(); g_autoptr(FlDartProject) project = fl_dart_project_new();
g_autoptr(FlEngine) engine = fl_engine_new(project); g_autoptr(FlEngine) engine = fl_engine_new(project);
g_autoptr(FlAccessibleNode) root = fl_accessible_node_new(engine, 0); int64_t view_id = 123;
g_autoptr(FlAccessibleNode) child1 = fl_accessible_node_new(engine, 1); 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); 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); fl_accessible_node_set_parent(child2, ATK_OBJECT(root), 1);
g_autoptr(GPtrArray) children = g_autoptr(GPtrArray) children =
g_ptr_array_new_with_free_func(g_object_unref); 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(FlDartProject) project = fl_dart_project_new();
g_autoptr(FlEngine) engine = fl_engine_new(project); 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"); fl_accessible_node_set_name(node, "test");
EXPECT_STREQ(atk_object_get_name(ATK_OBJECT(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(FlDartProject) project = fl_dart_project_new();
g_autoptr(FlEngine) engine = fl_engine_new(project); 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); fl_accessible_node_set_extents(node, 1, 2, 3, 4);
gint x, y, width, height; gint x, y, width, height;
atk_component_get_extents(ATK_COMPONENT(node), &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(FlDartProject) project = fl_dart_project_new();
g_autoptr(FlEngine) engine = fl_engine_new(project); 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( fl_accessible_node_set_flags(
node, static_cast<FlutterSemanticsFlag>(kFlutterSemanticsFlagIsEnabled | node, static_cast<FlutterSemanticsFlag>(kFlutterSemanticsFlagIsEnabled |
kFlutterSemanticsFlagIsFocusable | kFlutterSemanticsFlagIsFocusable |
@ -93,7 +96,7 @@ TEST(FlAccessibleNodeTest, GetRole) {
g_autoptr(FlDartProject) project = fl_dart_project_new(); g_autoptr(FlDartProject) project = fl_dart_project_new();
g_autoptr(FlEngine) engine = fl_engine_new(project); 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( fl_accessible_node_set_flags(
node, static_cast<FlutterSemanticsFlag>(kFlutterSemanticsFlagIsButton)); node, static_cast<FlutterSemanticsFlag>(kFlutterSemanticsFlagIsButton));
@ -127,7 +130,7 @@ TEST(FlAccessibleNodeTest, SetActions) {
g_autoptr(FlDartProject) project = fl_dart_project_new(); g_autoptr(FlDartProject) project = fl_dart_project_new();
g_autoptr(FlEngine) engine = fl_engine_new(project); 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( fl_accessible_node_set_actions(
node, static_cast<FlutterSemanticsAction>( node, static_cast<FlutterSemanticsAction>(
kFlutterSemanticsActionTap | kFlutterSemanticsActionLongPress)); kFlutterSemanticsActionTap | kFlutterSemanticsActionLongPress));

View File

@ -615,7 +615,10 @@ static void fl_accessible_text_field_init(FlAccessibleTextField* self) {
self, G_CONNECT_SWAPPED); 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(), 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));
} }

View File

@ -20,6 +20,7 @@ G_DECLARE_FINAL_TYPE(FlAccessibleTextField,
/** /**
* fl_accessible_text_field_new: * fl_accessible_text_field_new:
* @engine: the #FlEngine this node came from. * @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. * @id: the semantics node ID this object represents.
* *
* Creates a new accessibility object that exposes an editable Flutter text * Creates a new accessibility object that exposes an editable Flutter text
@ -27,7 +28,9 @@ G_DECLARE_FINAL_TYPE(FlAccessibleTextField,
* *
* Returns: a new #FlAccessibleNode. * 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 G_END_DECLS

View File

@ -26,7 +26,8 @@ static FlValue* decode_semantic_data(const uint8_t* data, size_t data_length) {
TEST(FlAccessibleTextFieldTest, SetValue) { TEST(FlAccessibleTextFieldTest, SetValue) {
g_autoptr(FlDartProject) project = fl_dart_project_new(); g_autoptr(FlDartProject) project = fl_dart_project_new();
g_autoptr(FlEngine) engine = fl_engine_new(project); 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" // "" -> "Flutter"
{ {
@ -83,7 +84,8 @@ TEST(FlAccessibleTextFieldTest, SetValue) {
TEST(FlAccessibleTextFieldTest, SetTextSelection) { TEST(FlAccessibleTextFieldTest, SetTextSelection) {
g_autoptr(FlDartProject) project = fl_dart_project_new(); g_autoptr(FlDartProject) project = fl_dart_project_new();
g_autoptr(FlEngine) engine = fl_engine_new(project); 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] // [-1,-1] -> [2,3]
{ {
@ -151,18 +153,17 @@ TEST(FlAccessibleTextFieldTest, PerformAction) {
EXPECT_TRUE(fl_engine_start(engine, &error)); EXPECT_TRUE(fl_engine_start(engine, &error));
EXPECT_EQ(error, nullptr); EXPECT_EQ(error, nullptr);
fl_engine_get_embedder_api(engine)->DispatchSemanticsAction = fl_engine_get_embedder_api(engine)->SendSemanticsAction = MOCK_ENGINE_PROC(
MOCK_ENGINE_PROC( SendSemanticsAction,
DispatchSemanticsAction, ([&action_datas](auto engine,
([&action_datas](auto engine, uint64_t id, const FlutterSendSemanticsActionInfo* info) {
FlutterSemanticsAction action, const uint8_t* data, g_ptr_array_add(action_datas,
size_t data_length) { decode_semantic_data(info->data, info->data_length));
g_ptr_array_add(action_datas, return kSuccess;
decode_semantic_data(data, 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( fl_accessible_node_set_actions(
node, static_cast<FlutterSemanticsAction>( node, static_cast<FlutterSemanticsAction>(
kFlutterSemanticsActionMoveCursorForwardByCharacter | kFlutterSemanticsActionMoveCursorForwardByCharacter |
@ -185,7 +186,8 @@ TEST(FlAccessibleTextFieldTest, PerformAction) {
TEST(FlAccessibleTextFieldTest, GetCharacterCount) { TEST(FlAccessibleTextFieldTest, GetCharacterCount) {
g_autoptr(FlDartProject) project = fl_dart_project_new(); g_autoptr(FlDartProject) project = fl_dart_project_new();
g_autoptr(FlEngine) engine = fl_engine_new(project); 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); EXPECT_EQ(atk_text_get_character_count(ATK_TEXT(node)), 0);
@ -198,7 +200,8 @@ TEST(FlAccessibleTextFieldTest, GetCharacterCount) {
TEST(FlAccessibleTextFieldTest, GetText) { TEST(FlAccessibleTextFieldTest, GetText) {
g_autoptr(FlDartProject) project = fl_dart_project_new(); g_autoptr(FlDartProject) project = fl_dart_project_new();
g_autoptr(FlEngine) engine = fl_engine_new(project); 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); g_autofree gchar* empty = atk_text_get_text(ATK_TEXT(node), 0, -1);
EXPECT_STREQ(empty, ""); EXPECT_STREQ(empty, "");
@ -219,7 +222,8 @@ TEST(FlAccessibleTextFieldTest, GetText) {
TEST(FlAccessibleTextFieldTest, GetCaretOffset) { TEST(FlAccessibleTextFieldTest, GetCaretOffset) {
g_autoptr(FlDartProject) project = fl_dart_project_new(); g_autoptr(FlDartProject) project = fl_dart_project_new();
g_autoptr(FlEngine) engine = fl_engine_new(project); 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); 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_TRUE(fl_engine_start(engine, &error));
EXPECT_EQ(error, nullptr); EXPECT_EQ(error, nullptr);
fl_engine_get_embedder_api(engine)->DispatchSemanticsAction = fl_engine_get_embedder_api(engine)->SendSemanticsAction = MOCK_ENGINE_PROC(
MOCK_ENGINE_PROC( SendSemanticsAction,
DispatchSemanticsAction, ([&base, &extent](auto engine,
([&base, &extent](auto engine, uint64_t id, const FlutterSendSemanticsActionInfo* info) {
FlutterSemanticsAction action, const uint8_t* data, EXPECT_EQ(info->action, kFlutterSemanticsActionSetSelection);
size_t data_length) { g_autoptr(FlValue) value =
EXPECT_EQ(action, kFlutterSemanticsActionSetSelection); decode_semantic_data(info->data, info->data_length);
g_autoptr(FlValue) value = decode_semantic_data(data, data_length); EXPECT_EQ(fl_value_get_type(value), FL_VALUE_TYPE_MAP);
EXPECT_EQ(fl_value_get_type(value), FL_VALUE_TYPE_MAP); base = fl_value_get_int(fl_value_lookup_string(value, "base"));
base = fl_value_get_int(fl_value_lookup_string(value, "base")); extent = fl_value_get_int(fl_value_lookup_string(value, "extent"));
extent = fl_value_get_int(fl_value_lookup_string(value, "extent")); return kSuccess;
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_TRUE(atk_text_set_caret_offset(ATK_TEXT(node), 3));
EXPECT_EQ(base, 3); EXPECT_EQ(base, 3);
@ -265,7 +269,8 @@ TEST(FlAccessibleTextFieldTest, SetCaretOffset) {
TEST(FlAccessibleTextFieldTest, GetNSelections) { TEST(FlAccessibleTextFieldTest, GetNSelections) {
g_autoptr(FlDartProject) project = fl_dart_project_new(); g_autoptr(FlDartProject) project = fl_dart_project_new();
g_autoptr(FlEngine) engine = fl_engine_new(project); 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); EXPECT_EQ(atk_text_get_n_selections(ATK_TEXT(node)), 0);
@ -278,7 +283,8 @@ TEST(FlAccessibleTextFieldTest, GetNSelections) {
TEST(FlAccessibleTextFieldTest, GetSelection) { TEST(FlAccessibleTextFieldTest, GetSelection) {
g_autoptr(FlDartProject) project = fl_dart_project_new(); g_autoptr(FlDartProject) project = fl_dart_project_new();
g_autoptr(FlEngine) engine = fl_engine_new(project); 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), EXPECT_EQ(atk_text_get_selection(ATK_TEXT(node), 0, nullptr, nullptr),
nullptr); nullptr);
@ -321,21 +327,21 @@ TEST(FlAccessibleTextFieldTest, AddSelection) {
EXPECT_TRUE(fl_engine_start(engine, &error)); EXPECT_TRUE(fl_engine_start(engine, &error));
EXPECT_EQ(error, nullptr); EXPECT_EQ(error, nullptr);
fl_engine_get_embedder_api(engine)->DispatchSemanticsAction = fl_engine_get_embedder_api(engine)->SendSemanticsAction = MOCK_ENGINE_PROC(
MOCK_ENGINE_PROC( SendSemanticsAction,
DispatchSemanticsAction, ([&base, &extent](auto engine,
([&base, &extent](auto engine, uint64_t id, const FlutterSendSemanticsActionInfo* info) {
FlutterSemanticsAction action, const uint8_t* data, EXPECT_EQ(info->action, kFlutterSemanticsActionSetSelection);
size_t data_length) { g_autoptr(FlValue) value =
EXPECT_EQ(action, kFlutterSemanticsActionSetSelection); decode_semantic_data(info->data, info->data_length);
g_autoptr(FlValue) value = decode_semantic_data(data, data_length); EXPECT_EQ(fl_value_get_type(value), FL_VALUE_TYPE_MAP);
EXPECT_EQ(fl_value_get_type(value), FL_VALUE_TYPE_MAP); base = fl_value_get_int(fl_value_lookup_string(value, "base"));
base = fl_value_get_int(fl_value_lookup_string(value, "base")); extent = fl_value_get_int(fl_value_lookup_string(value, "extent"));
extent = fl_value_get_int(fl_value_lookup_string(value, "extent")); return kSuccess;
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_TRUE(atk_text_add_selection(ATK_TEXT(node), 2, 4));
EXPECT_EQ(base, 2); EXPECT_EQ(base, 2);
@ -361,21 +367,21 @@ TEST(FlAccessibleTextFieldTest, RemoveSelection) {
EXPECT_TRUE(fl_engine_start(engine, &error)); EXPECT_TRUE(fl_engine_start(engine, &error));
EXPECT_EQ(error, nullptr); EXPECT_EQ(error, nullptr);
fl_engine_get_embedder_api(engine)->DispatchSemanticsAction = fl_engine_get_embedder_api(engine)->SendSemanticsAction = MOCK_ENGINE_PROC(
MOCK_ENGINE_PROC( SendSemanticsAction,
DispatchSemanticsAction, ([&base, &extent](auto engine,
([&base, &extent](auto engine, uint64_t id, const FlutterSendSemanticsActionInfo* info) {
FlutterSemanticsAction action, const uint8_t* data, EXPECT_EQ(info->action, kFlutterSemanticsActionSetSelection);
size_t data_length) { g_autoptr(FlValue) value =
EXPECT_EQ(action, kFlutterSemanticsActionSetSelection); decode_semantic_data(info->data, info->data_length);
g_autoptr(FlValue) value = decode_semantic_data(data, data_length); EXPECT_EQ(fl_value_get_type(value), FL_VALUE_TYPE_MAP);
EXPECT_EQ(fl_value_get_type(value), FL_VALUE_TYPE_MAP); base = fl_value_get_int(fl_value_lookup_string(value, "base"));
base = fl_value_get_int(fl_value_lookup_string(value, "base")); extent = fl_value_get_int(fl_value_lookup_string(value, "extent"));
extent = fl_value_get_int(fl_value_lookup_string(value, "extent")); return kSuccess;
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 // no selection
EXPECT_FALSE(atk_text_remove_selection(ATK_TEXT(node), 0)); 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_TRUE(fl_engine_start(engine, &error));
EXPECT_EQ(error, nullptr); EXPECT_EQ(error, nullptr);
fl_engine_get_embedder_api(engine)->DispatchSemanticsAction = fl_engine_get_embedder_api(engine)->SendSemanticsAction = MOCK_ENGINE_PROC(
MOCK_ENGINE_PROC( SendSemanticsAction,
DispatchSemanticsAction, ([&base, &extent](auto engine,
([&base, &extent](auto engine, uint64_t id, const FlutterSendSemanticsActionInfo* info) {
FlutterSemanticsAction action, const uint8_t* data, EXPECT_EQ(info->action, kFlutterSemanticsActionSetSelection);
size_t data_length) { g_autoptr(FlValue) value =
EXPECT_EQ(action, kFlutterSemanticsActionSetSelection); decode_semantic_data(info->data, info->data_length);
g_autoptr(FlValue) value = decode_semantic_data(data, data_length); EXPECT_EQ(fl_value_get_type(value), FL_VALUE_TYPE_MAP);
EXPECT_EQ(fl_value_get_type(value), FL_VALUE_TYPE_MAP); base = fl_value_get_int(fl_value_lookup_string(value, "base"));
base = fl_value_get_int(fl_value_lookup_string(value, "base")); extent = fl_value_get_int(fl_value_lookup_string(value, "extent"));
extent = fl_value_get_int(fl_value_lookup_string(value, "extent")); return kSuccess;
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 // selection num != 0
EXPECT_FALSE(atk_text_set_selection(ATK_TEXT(node), 1, 2, 4)); 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_TRUE(fl_engine_start(engine, &error));
EXPECT_EQ(error, nullptr); EXPECT_EQ(error, nullptr);
fl_engine_get_embedder_api(engine)->DispatchSemanticsAction = fl_engine_get_embedder_api(engine)->SendSemanticsAction = MOCK_ENGINE_PROC(
MOCK_ENGINE_PROC( SendSemanticsAction,
DispatchSemanticsAction, ([&text](auto engine, const FlutterSendSemanticsActionInfo* info) {
([&text](auto engine, uint64_t id, FlutterSemanticsAction action, EXPECT_EQ(info->action, kFlutterSemanticsActionSetText);
const uint8_t* data, size_t data_length) { g_autoptr(FlValue) value =
EXPECT_EQ(action, kFlutterSemanticsActionSetText); decode_semantic_data(info->data, info->data_length);
g_autoptr(FlValue) value = decode_semantic_data(data, data_length); EXPECT_EQ(fl_value_get_type(value), FL_VALUE_TYPE_STRING);
EXPECT_EQ(fl_value_get_type(value), FL_VALUE_TYPE_STRING); text = g_strdup(fl_value_get_string(value));
text = g_strdup(fl_value_get_string(value)); return kSuccess;
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"); atk_editable_text_set_text_contents(ATK_EDITABLE_TEXT(node), "Flutter");
EXPECT_STREQ(text, "Flutter"); EXPECT_STREQ(text, "Flutter");
@ -479,33 +485,31 @@ TEST(FlAccessibleTextFieldTest, InsertDeleteText) {
EXPECT_TRUE(fl_engine_start(engine, &error)); EXPECT_TRUE(fl_engine_start(engine, &error));
EXPECT_EQ(error, nullptr); EXPECT_EQ(error, nullptr);
fl_engine_get_embedder_api(engine)->DispatchSemanticsAction = fl_engine_get_embedder_api(engine)->SendSemanticsAction = MOCK_ENGINE_PROC(
MOCK_ENGINE_PROC( SendSemanticsAction,
DispatchSemanticsAction, ([&text, &base, &extent](auto engine,
([&text, &base, &extent](auto engine, uint64_t id, const FlutterSendSemanticsActionInfo* info) {
FlutterSemanticsAction action, EXPECT_THAT(info->action,
const uint8_t* data, size_t data_length) { ::testing::AnyOf(kFlutterSemanticsActionSetText,
EXPECT_THAT(action, kFlutterSemanticsActionSetSelection));
::testing::AnyOf(kFlutterSemanticsActionSetText, if (info->action == kFlutterSemanticsActionSetText) {
kFlutterSemanticsActionSetSelection)); g_autoptr(FlValue) value =
if (action == kFlutterSemanticsActionSetText) { decode_semantic_data(info->data, info->data_length);
g_autoptr(FlValue) value = EXPECT_EQ(fl_value_get_type(value), FL_VALUE_TYPE_STRING);
decode_semantic_data(data, data_length); g_free(text);
EXPECT_EQ(fl_value_get_type(value), FL_VALUE_TYPE_STRING); text = g_strdup(fl_value_get_string(value));
g_free(text); } else {
text = g_strdup(fl_value_get_string(value)); g_autoptr(FlValue) value =
} else { decode_semantic_data(info->data, info->data_length);
g_autoptr(FlValue) value = EXPECT_EQ(fl_value_get_type(value), FL_VALUE_TYPE_MAP);
decode_semantic_data(data, data_length); base = fl_value_get_int(fl_value_lookup_string(value, "base"));
EXPECT_EQ(fl_value_get_type(value), FL_VALUE_TYPE_MAP); extent = fl_value_get_int(fl_value_lookup_string(value, "extent"));
base = fl_value_get_int(fl_value_lookup_string(value, "base")); }
extent = return kSuccess;
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"); fl_accessible_node_set_value(node, "Fler");
gint pos = 2; gint pos = 2;
@ -534,30 +538,28 @@ TEST(FlAccessibleTextFieldTest, CopyCutPasteText) {
EXPECT_TRUE(fl_engine_start(engine, &error)); EXPECT_TRUE(fl_engine_start(engine, &error));
EXPECT_EQ(error, nullptr); EXPECT_EQ(error, nullptr);
fl_engine_get_embedder_api(engine)->DispatchSemanticsAction = fl_engine_get_embedder_api(engine)->SendSemanticsAction = MOCK_ENGINE_PROC(
MOCK_ENGINE_PROC( SendSemanticsAction,
DispatchSemanticsAction, ([&act, &base, &extent](auto engine,
([&act, &base, &extent](auto engine, uint64_t id, const FlutterSendSemanticsActionInfo* info) {
FlutterSemanticsAction action, EXPECT_THAT(info->action,
const uint8_t* data, size_t data_length) { ::testing::AnyOf(kFlutterSemanticsActionCut,
EXPECT_THAT(action, kFlutterSemanticsActionCopy,
::testing::AnyOf(kFlutterSemanticsActionCut, kFlutterSemanticsActionPaste,
kFlutterSemanticsActionCopy, kFlutterSemanticsActionSetSelection));
kFlutterSemanticsActionPaste, act = info->action;
kFlutterSemanticsActionSetSelection)); if (info->action == kFlutterSemanticsActionSetSelection) {
act = action; g_autoptr(FlValue) value =
if (action == kFlutterSemanticsActionSetSelection) { decode_semantic_data(info->data, info->data_length);
g_autoptr(FlValue) value = EXPECT_EQ(fl_value_get_type(value), FL_VALUE_TYPE_MAP);
decode_semantic_data(data, data_length); base = fl_value_get_int(fl_value_lookup_string(value, "base"));
EXPECT_EQ(fl_value_get_type(value), FL_VALUE_TYPE_MAP); extent = fl_value_get_int(fl_value_lookup_string(value, "extent"));
base = fl_value_get_int(fl_value_lookup_string(value, "base")); }
extent = return kSuccess;
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); atk_editable_text_copy_text(ATK_EDITABLE_TEXT(node), 2, 5);
EXPECT_EQ(base, 2); EXPECT_EQ(base, 2);
@ -578,7 +580,8 @@ TEST(FlAccessibleTextFieldTest, CopyCutPasteText) {
TEST(FlAccessibleTextFieldTest, TextBoundary) { TEST(FlAccessibleTextFieldTest, TextBoundary) {
g_autoptr(FlDartProject) project = fl_dart_project_new(); g_autoptr(FlDartProject) project = fl_dart_project_new();
g_autoptr(FlEngine) engine = fl_engine_new(project); 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, fl_accessible_node_set_value(node,
"Lorem ipsum.\nDolor sit amet. Praesent commodo?" "Lorem ipsum.\nDolor sit amet. Praesent commodo?"

View File

@ -1201,7 +1201,8 @@ gboolean fl_engine_send_key_event_finish(FlEngine* self,
} }
void fl_engine_dispatch_semantics_action(FlEngine* self, void fl_engine_dispatch_semantics_action(FlEngine* self,
uint64_t id, FlutterViewId view_id,
uint64_t node_id,
FlutterSemanticsAction action, FlutterSemanticsAction action,
GBytes* data) { GBytes* data) {
g_return_if_fail(FL_IS_ENGINE(self)); 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)); g_bytes_get_data(data, &action_data_length));
} }
self->embedder_api.DispatchSemanticsAction(self->engine, id, action, FlutterSendSemanticsActionInfo info;
action_data, action_data_length); 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, gboolean fl_engine_mark_texture_frame_available(FlEngine* self,

View File

@ -414,12 +414,14 @@ gboolean fl_engine_send_key_event_finish(FlEngine* engine,
/** /**
* fl_engine_dispatch_semantics_action: * fl_engine_dispatch_semantics_action:
* @engine: an #FlEngine. * @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. * @action: the action being dispatched.
* @data: (allow-none): data associated with the action. * @data: (allow-none): data associated with the action.
*/ */
void fl_engine_dispatch_semantics_action(FlEngine* engine, void fl_engine_dispatch_semantics_action(FlEngine* engine,
uint64_t id, FlutterViewId view_id,
uint64_t node_id,
FlutterSemanticsAction action, FlutterSemanticsAction action,
GBytes* data); GBytes* data);

View File

@ -180,29 +180,28 @@ TEST(FlEngineTest, DispatchSemanticsAction) {
g_autoptr(FlEngine) engine = fl_engine_new(project); g_autoptr(FlEngine) engine = fl_engine_new(project);
bool called = false; bool called = false;
fl_engine_get_embedder_api(engine)->DispatchSemanticsAction = fl_engine_get_embedder_api(engine)->SendSemanticsAction = MOCK_ENGINE_PROC(
MOCK_ENGINE_PROC( SendSemanticsAction,
DispatchSemanticsAction, ([&called](auto engine, const FlutterSendSemanticsActionInfo* info) {
([&called](auto engine, uint64_t id, FlutterSemanticsAction action, EXPECT_EQ(info->view_id, static_cast<int64_t>(456));
const uint8_t* data, size_t data_length) { EXPECT_EQ(info->node_id, static_cast<uint64_t>(42));
EXPECT_EQ(id, static_cast<uint64_t>(42)); EXPECT_EQ(info->action, kFlutterSemanticsActionTap);
EXPECT_EQ(action, kFlutterSemanticsActionTap); EXPECT_EQ(info->data_length, static_cast<size_t>(4));
EXPECT_EQ(data_length, static_cast<size_t>(4)); EXPECT_EQ(info->data[0], 't');
EXPECT_EQ(data[0], 't'); EXPECT_EQ(info->data[1], 'e');
EXPECT_EQ(data[1], 'e'); EXPECT_EQ(info->data[2], 's');
EXPECT_EQ(data[2], 's'); EXPECT_EQ(info->data[3], 't');
EXPECT_EQ(data[3], 't'); called = true;
called = true;
return kSuccess; return kSuccess;
})); }));
g_autoptr(GError) error = nullptr; g_autoptr(GError) error = nullptr;
EXPECT_TRUE(fl_engine_start(engine, &error)); EXPECT_TRUE(fl_engine_start(engine, &error));
EXPECT_EQ(error, nullptr); EXPECT_EQ(error, nullptr);
g_autoptr(GBytes) data = g_bytes_new_static("test", 4); g_autoptr(GBytes) data = g_bytes_new_static("test", 4);
fl_engine_dispatch_semantics_action(engine, 42, kFlutterSemanticsActionTap, fl_engine_dispatch_semantics_action(engine, 456, 42,
data); kFlutterSemanticsActionTap, data);
EXPECT_TRUE(called); EXPECT_TRUE(called);
} }

View File

@ -222,8 +222,16 @@ static void view_added_cb(GObject* object,
} }
// Called when the engine updates accessibility. // Called when the engine updates accessibility.
static void update_semantics_cb(FlView* self, static void update_semantics_cb(FlEngine* engine,
const FlutterSemanticsUpdate2* update) { 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); fl_view_accessible_handle_update_semantics(self->view_accessible, update);
} }
@ -489,7 +497,7 @@ static void realize_cb(FlView* self) {
handle_geometry_changed(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_embed(
FL_SOCKET_ACCESSIBLE(gtk_widget_get_accessible(GTK_WIDGET(self))), FL_SOCKET_ACCESSIBLE(gtk_widget_get_accessible(GTK_WIDGET(self))),
atk_plug_get_id(ATK_PLUG(self->view_accessible))); 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, self->view_id = fl_engine_add_view(engine, 1, 1, 1.0, self->cancellable,
view_added_cb, self); view_added_cb, self);
fl_renderer_add_renderable(FL_RENDERER(self->renderer), self->view_id, fl_renderer_add_renderable(FL_RENDERER(self->renderer), self->view_id,
FL_RENDERABLE(self)); 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_signal_connect_swapped(self->gl_area, "realize",
G_CALLBACK(secondary_realize_cb), self); G_CALLBACK(secondary_realize_cb), self);
return self; return self;
} }

View File

@ -15,6 +15,8 @@ struct _FlViewAccessible {
GWeakRef engine; GWeakRef engine;
FlutterViewId view_id;
// Semantics nodes keyed by ID // Semantics nodes keyed by ID
GHashTable* semantics_nodes_by_id; GHashTable* semantics_nodes_by_id;
@ -32,10 +34,10 @@ static FlAccessibleNode* create_node(FlViewAccessible* self,
} }
if (semantics->flags & kFlutterSemanticsFlagIsTextField) { 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) { 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); 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 = FlViewAccessible* self =
FL_VIEW_ACCESSIBLE(g_object_new(fl_view_accessible_get_type(), nullptr)); FL_VIEW_ACCESSIBLE(g_object_new(fl_view_accessible_get_type(), nullptr));
g_weak_ref_init(&self->engine, engine); g_weak_ref_init(&self->engine, engine);
self->view_id = view_id;
return self; return self;
} }

View File

@ -37,13 +37,16 @@ G_DECLARE_FINAL_TYPE(FlViewAccessible,
/** /**
* fl_view_accessible_new: * fl_view_accessible_new:
* @engine: the #FlEngine.
* @view_id: the Flutter view id.
* *
* Creates a new accessibility object that exposes Flutter accessibility * Creates a new accessibility object that exposes Flutter accessibility
* information to ATK. * information to ATK.
* *
* Returns: a new #FlViewAccessible. * 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: * fl_view_accessible_handle_update_semantics:

View File

@ -12,7 +12,7 @@
TEST(FlViewAccessibleTest, BuildTree) { TEST(FlViewAccessibleTest, BuildTree) {
g_autoptr(FlDartProject) project = fl_dart_project_new(); g_autoptr(FlDartProject) project = fl_dart_project_new();
g_autoptr(FlEngine) engine = fl_engine_new(project); 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}; int32_t children[] = {111, 222};
FlutterSemanticsNode2 root_node = { FlutterSemanticsNode2 root_node = {
@ -49,7 +49,7 @@ TEST(FlViewAccessibleTest, BuildTree) {
TEST(FlViewAccessibleTest, AddRemoveChildren) { TEST(FlViewAccessibleTest, AddRemoveChildren) {
g_autoptr(FlDartProject) project = fl_dart_project_new(); g_autoptr(FlDartProject) project = fl_dart_project_new();
g_autoptr(FlEngine) engine = fl_engine_new(project); 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 = { FlutterSemanticsNode2 root_node = {
.id = 0, .id = 0,

View File

@ -150,12 +150,9 @@ FlutterEngineResult FlutterEngineUpdateAccessibilityFeatures(
return kSuccess; return kSuccess;
} }
FlutterEngineResult FlutterEngineDispatchSemanticsAction( FlutterEngineResult FlutterEngineSendSemanticsAction(
FLUTTER_API_SYMBOL(FlutterEngine) engine, FLUTTER_API_SYMBOL(FlutterEngine) engine,
uint64_t id, const FlutterSendSemanticsActionInfo* info) {
FlutterSemanticsAction action,
const uint8_t* data,
size_t data_length) {
return kSuccess; return kSuccess;
} }
@ -228,7 +225,7 @@ FlutterEngineResult FlutterEngineGetProcAddresses(
table->RunTask = &FlutterEngineRunTask; table->RunTask = &FlutterEngineRunTask;
table->UpdateLocales = &FlutterEngineUpdateLocales; table->UpdateLocales = &FlutterEngineUpdateLocales;
table->UpdateSemanticsEnabled = &FlutterEngineUpdateSemanticsEnabled; table->UpdateSemanticsEnabled = &FlutterEngineUpdateSemanticsEnabled;
table->DispatchSemanticsAction = &FlutterEngineDispatchSemanticsAction; table->SendSemanticsAction = &FlutterEngineSendSemanticsAction;
table->RunsAOTCompiledDartCode = &FlutterEngineRunsAOTCompiledDartCode; table->RunsAOTCompiledDartCode = &FlutterEngineRunsAOTCompiledDartCode;
table->RegisterExternalTexture = &FlutterEngineRegisterExternalTexture; table->RegisterExternalTexture = &FlutterEngineRegisterExternalTexture;
table->MarkExternalTextureFrameAvailable = table->MarkExternalTextureFrameAvailable =

View File

@ -163,7 +163,8 @@ void AccessibilityBridgeWindows::DispatchAccessibilityAction(
AccessibilityNodeId target, AccessibilityNodeId target,
FlutterSemanticsAction action, FlutterSemanticsAction action,
fml::MallocMapping data) { 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<FlutterPlatformNodeDelegate> std::shared_ptr<FlutterPlatformNodeDelegate>

View File

@ -278,12 +278,11 @@ TEST(AccessibilityBridgeWindows, DispatchAccessibilityAction) {
PopulateAXTree(bridge); PopulateAXTree(bridge);
FlutterSemanticsAction actual_action = kFlutterSemanticsActionTap; FlutterSemanticsAction actual_action = kFlutterSemanticsActionTap;
modifier.embedder_api().DispatchSemanticsAction = MOCK_ENGINE_PROC( modifier.embedder_api().SendSemanticsAction = MOCK_ENGINE_PROC(
DispatchSemanticsAction, SendSemanticsAction,
([&actual_action](FLUTTER_API_SYMBOL(FlutterEngine) engine, uint64_t id, ([&actual_action](FLUTTER_API_SYMBOL(FlutterEngine) engine,
FlutterSemanticsAction action, const uint8_t* data, const FlutterSendSemanticsActionInfo* info) {
size_t data_length) { actual_action = info->action;
actual_action = action;
return kSuccess; return kSuccess;
})); }));

View File

@ -5,7 +5,7 @@
import 'dart:async'; import 'dart:async';
import 'dart:convert'; import 'dart:convert';
import 'dart:io' as io; 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; import 'dart:ui' as ui;
// Signals a waiting latch in the native test. // Signals a waiting latch in the native test.
@ -404,3 +404,74 @@ external void notifyEngineId(int? handle);
void testEngineId() { void testEngineId() {
notifyEngineId(ui.PlatformDispatcher.instance.engineId); notifyEngineId(ui.PlatformDispatcher.instance.engineId);
} }
@pragma('vm:entry-point')
Future<void> sendSemanticsTreeInfo() async {
// Wait until semantics are enabled.
if (!ui.PlatformDispatcher.instance.semanticsEnabled) {
await semanticsChanged;
}
final Iterable<ui.FlutterView> 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 <ui.StringAttribute>[],
value: 'value',
valueAttributes: const <ui.StringAttribute>[],
increasedValue: 'increasedValue',
increasedValueAttributes: const <ui.StringAttribute>[],
decreasedValue: 'decreasedValue',
decreasedValueAttributes: const <ui.StringAttribute>[],
hint: 'hint',
hintAttributes: const <ui.StringAttribute>[],
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();
}

View File

@ -354,9 +354,7 @@ bool FlutterWindowsEngine::Run(std::string_view entrypoint) {
void* user_data) { void* user_data) {
auto host = static_cast<FlutterWindowsEngine*>(user_data); auto host = static_cast<FlutterWindowsEngine*>(user_data);
// TODO(loicsharma): Remove implicit view assumption. auto view = host->view(update->view_id);
// https://github.com/flutter/flutter/issues/142845
auto view = host->view(kImplicitViewId);
if (!view) { if (!view) {
return; return;
} }
@ -518,6 +516,7 @@ std::unique_ptr<FlutterWindowsView> FlutterWindowsEngine::CreateView(
view_id, this, std::move(window), windows_proc_table_); view_id, this, std::move(window), windows_proc_table_);
view->CreateRenderSurface(); view->CreateRenderSurface();
view->UpdateSemanticsEnabled(semantics_enabled_);
next_view_id_++; next_view_id_++;
@ -932,12 +931,19 @@ bool FlutterWindowsEngine::PostRasterThreadTask(fml::closure callback) const {
} }
bool FlutterWindowsEngine::DispatchSemanticsAction( bool FlutterWindowsEngine::DispatchSemanticsAction(
FlutterViewId view_id,
uint64_t target, uint64_t target,
FlutterSemanticsAction action, FlutterSemanticsAction action,
fml::MallocMapping data) { fml::MallocMapping data) {
return (embedder_api_.DispatchSemanticsAction(engine_, target, action, FlutterSendSemanticsActionInfo info{
data.GetMapping(), .struct_size = sizeof(FlutterSendSemanticsActionInfo),
data.GetSize()) == kSuccess); .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) { void FlutterWindowsEngine::UpdateSemanticsEnabled(bool enabled) {

View File

@ -237,7 +237,8 @@ class FlutterWindowsEngine {
void OnVsync(intptr_t baton); void OnVsync(intptr_t baton);
// Dispatches a semantics action to the specified semantics node. // 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, FlutterSemanticsAction action,
fml::MallocMapping data); fml::MallocMapping data);

View File

@ -482,20 +482,19 @@ TEST_F(FlutterWindowsEngineTest, DispatchSemanticsAction) {
bool called = false; bool called = false;
std::string message = "Hello"; std::string message = "Hello";
modifier.embedder_api().DispatchSemanticsAction = MOCK_ENGINE_PROC( modifier.embedder_api().SendSemanticsAction = MOCK_ENGINE_PROC(
DispatchSemanticsAction, SendSemanticsAction, ([&called, &message](auto engine, auto info) {
([&called, &message](auto engine, auto target, auto action, auto data,
auto data_length) {
called = true; called = true;
EXPECT_EQ(target, 42); EXPECT_EQ(info->view_id, 456);
EXPECT_EQ(action, kFlutterSemanticsActionDismiss); EXPECT_EQ(info->node_id, 42);
EXPECT_EQ(memcmp(data, message.c_str(), message.size()), 0); EXPECT_EQ(info->action, kFlutterSemanticsActionDismiss);
EXPECT_EQ(data_length, message.size()); EXPECT_EQ(memcmp(info->data, message.c_str(), message.size()), 0);
EXPECT_EQ(info->data_length, message.size());
return kSuccess; return kSuccess;
})); }));
auto data = fml::MallocMapping::Copy(message.c_str(), message.size()); auto data = fml::MallocMapping::Copy(message.c_str(), message.size());
engine->DispatchSemanticsAction(42, kFlutterSemanticsActionDismiss, engine->DispatchSemanticsAction(456, 42, kFlutterSemanticsActionDismiss,
std::move(data)); std::move(data));
EXPECT_TRUE(called); EXPECT_TRUE(called);
} }
@ -1379,5 +1378,77 @@ TEST_F(FlutterWindowsEngineTest, OnViewFocusChangeRequest) {
modifier.OnViewFocusChangeRequest(&request); 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<NiceMock<MockWindowBindingHandler>>();
auto window_binding_handler2 =
std::make_unique<NiceMock<MockWindowBindingHandler>>();
// 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<FlutterWindowsEngine*>(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 testing
} // namespace flutter } // namespace flutter

View File

@ -91,6 +91,10 @@ class EngineModifier {
engine_->OnViewFocusChangeRequest(request); engine_->OnViewFocusChangeRequest(request);
} }
void SetNextViewId(FlutterViewId view_id) {
engine_->next_view_id_ = view_id;
}
private: private:
FlutterWindowsEngine* engine_; FlutterWindowsEngine* engine_;