(#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);
expectEquals(action.viewId, 456);
expectEquals(action.nodeId, 1234);
expectEquals(action.type.index, 4);
});

View File

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

View File

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

View File

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

View File

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

View File

@ -92,9 +92,10 @@ class PlatformConfigurationClient {
//--------------------------------------------------------------------------
/// @brief Receives an updated semantics tree from the Framework.
///
/// @param[in] viewId The identifier of the view to update.
/// @param[in] update The updated semantic tree to apply.
///
virtual void UpdateSemantics(SemanticsUpdate* update) = 0;
virtual void UpdateSemantics(int64_t viewId, SemanticsUpdate* update) = 0;
//--------------------------------------------------------------------------
/// @brief When the Flutter application has a message to send to the
@ -451,12 +452,14 @@ class PlatformConfiguration final {
/// originates on the platform view and has been forwarded to the
/// platform configuration here by the engine.
///
/// @param[in] view_id The identifier of the view.
/// @param[in] node_id The identifier of the accessibility node.
/// @param[in] action The accessibility related action performed on the
/// node of the specified ID.
/// @param[in] args Optional data that applies to the specified action.
///
void DispatchSemanticsAction(int32_t node_id,
void DispatchSemanticsAction(int64_t view_id,
int32_t node_id,
SemanticsAction action,
fml::MallocMapping args);
@ -620,7 +623,7 @@ class PlatformConfigurationNativeApi {
double width,
double height);
static void UpdateSemantics(SemanticsUpdate* update);
static void UpdateSemantics(int64_t viewId, SemanticsUpdate* update);
static void SetNeedsReportTimings(bool value);

View File

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

View File

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

View File

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

View File

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

View File

@ -450,10 +450,11 @@ void Engine::DispatchPointerDataPacket(
pointer_data_dispatcher_->DispatchPacket(std::move(packet), trace_flow_id);
}
void Engine::DispatchSemanticsAction(int node_id,
void Engine::DispatchSemanticsAction(int64_t view_id,
int node_id,
SemanticsAction action,
fml::MallocMapping args) {
runtime_controller_->DispatchSemanticsAction(node_id, action,
runtime_controller_->DispatchSemanticsAction(view_id, node_id, action,
std::move(args));
}
@ -495,9 +496,11 @@ void Engine::Render(int64_t view_id,
animator_->Render(view_id, std::move(layer_tree), device_pixel_ratio);
}
void Engine::UpdateSemantics(SemanticsNodeUpdates update,
void Engine::UpdateSemantics(int64_t view_id,
SemanticsNodeUpdates update,
CustomAccessibilityActionUpdates actions) {
delegate_.OnEngineUpdateSemantics(std::move(update), std::move(actions));
delegate_.OnEngineUpdateSemantics(view_id, std::move(update),
std::move(actions));
}
void Engine::HandlePlatformMessage(std::unique_ptr<PlatformMessage> message) {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -12,6 +12,8 @@
#import "flutter/shell/platform/darwin/ios/framework/Source/TextInputSemanticsObject.h"
#import "flutter/shell/platform/darwin/ios/platform_view_ios.h"
#include "flutter/common/constants.h"
#pragma GCC diagnostic error "-Wundeclared-selector"
FLUTTER_ASSERT_ARC
@ -235,14 +237,20 @@ void AccessibilityBridge::UpdateSemantics(
}
}
void AccessibilityBridge::DispatchSemanticsAction(int32_t uid, flutter::SemanticsAction action) {
platform_view_->DispatchSemanticsAction(uid, action, {});
void AccessibilityBridge::DispatchSemanticsAction(int32_t node_uid,
flutter::SemanticsAction action) {
// TODO(team-ios): Remove implicit view assumption.
// https://github.com/flutter/flutter/issues/142845
platform_view_->DispatchSemanticsAction(kFlutterImplicitViewId, node_uid, action, {});
}
void AccessibilityBridge::DispatchSemanticsAction(int32_t uid,
void AccessibilityBridge::DispatchSemanticsAction(int32_t node_uid,
flutter::SemanticsAction action,
fml::MallocMapping args) {
platform_view_->DispatchSemanticsAction(uid, action, std::move(args));
// TODO(team-ios): Remove implicit view assumption.
// https://github.com/flutter/flutter/issues/142845
platform_view_->DispatchSemanticsAction(kFlutterImplicitViewId, node_uid, action,
std::move(args));
}
static void ReplaceSemanticsObject(SemanticsObject* oldObject,

View File

@ -82,7 +82,8 @@ class MockDelegate : public PlatformView::Delegate {
void OnPlatformViewDispatchPlatformMessage(std::unique_ptr<PlatformMessage> message) 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,
fml::MallocMapping args) override {}
void OnPlatformViewSetSemanticsEnabled(bool enabled) override {}

View File

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

View File

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

View File

@ -332,6 +332,8 @@ AccessibilityBridgeMac::MacOSEventsFromAXEvent(ui::AXEventGenerator::Event event
void AccessibilityBridgeMac::DispatchAccessibilityAction(ui::AXNode::AXID target,
FlutterSemanticsAction action,
fml::MallocMapping data) {
// TODO(mattkae): Remove implicit view assumption.
// https://github.com/flutter/flutter/issues/142845
NSCAssert(flutter_engine_, @"Flutter engine should not be deallocated");
NSCAssert(view_controller_.viewLoaded && view_controller_.view.window,
@"The accessibility bridge should not receive accessibility actions if the flutter view"

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1632,3 +1632,70 @@ void testSendViewFocusChangeRequest() {
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;
void PlatformViewEmbedder::UpdateSemantics(
int64_t view_id,
flutter::SemanticsNodeUpdates update,
flutter::CustomAccessibilityActionUpdates actions) {
if (platform_dispatch_table_.update_semantics_callback != nullptr) {
platform_dispatch_table_.update_semantics_callback(std::move(update),
std::move(actions));
platform_dispatch_table_.update_semantics_callback(
view_id, std::move(update), std::move(actions));
}
}

View File

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

View File

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

View File

@ -774,6 +774,184 @@ TEST_F(EmbedderA11yTest, A11yTreeIsConsistentUsingV1Callbacks) {
#endif
}
TEST_F(EmbedderA11yTest, A11yTreesAreConsistentWithMultipleViews) {
#if defined(OS_FUCHSIA) && (FLUTTER_RUNTIME_MODE != FLUTTER_RUNTIME_MODE_DEBUG)
GTEST_SKIP() << "Dart_LoadELF is not implemented on Fuchsia.";
#else
auto& context = GetEmbedderContext<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 flutter

View File

@ -42,6 +42,7 @@
namespace flutter_runner {
namespace {
constexpr static int64_t kImplicitViewId = 0;
zx_koid_t GetKoid(const fuchsia::ui::views::ViewRef& view_ref) {
zx_handle_t handle = view_ref.reference.get();
@ -393,7 +394,10 @@ void Engine::Initialize(
auto platform_view = shell_->GetPlatformView();
if (platform_view) {
platform_view->DispatchSemanticsAction(node_id, action, {});
// TODO(fuchsia): Remove implicit view assumption.
// https://github.com/flutter/flutter/issues/142845
platform_view->DispatchSemanticsAction(kImplicitViewId, node_id,
action, {});
}
};

View File

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

View File

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

View File

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

View File

@ -68,10 +68,13 @@ struct FlAccessibleNodePrivate {
// Weak reference to the engine this node is created for.
FlEngine* engine;
/// The unique identifier of the view to which this node belongs.
FlutterViewId view_id;
// Weak reference to the parent node of this one or %NULL.
AtkObject* parent;
int32_t id;
int32_t node_id;
gchar* name;
gint index;
gint x, y, width, height;
@ -81,7 +84,7 @@ struct FlAccessibleNodePrivate {
FlutterSemanticsFlag flags;
};
enum { PROP_0, PROP_ENGINE, PROP_ID, PROP_LAST };
enum { PROP_0, PROP_ENGINE, PROP_VIEW_ID, PROP_ID, PROP_LAST };
#define FL_ACCESSIBLE_NODE_GET_PRIVATE(node) \
((FlAccessibleNodePrivate*)fl_accessible_node_get_instance_private( \
@ -151,8 +154,11 @@ static void fl_accessible_node_set_property(GObject* object,
g_object_add_weak_pointer(object,
reinterpret_cast<gpointer*>(&priv->engine));
break;
case PROP_VIEW_ID:
priv->view_id = g_value_get_int64(value);
break;
case PROP_ID:
priv->id = g_value_get_int(value);
priv->node_id = g_value_get_int(value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
@ -417,7 +423,8 @@ static void fl_accessible_node_perform_action_impl(
FlutterSemanticsAction action,
GBytes* data) {
FlAccessibleNodePrivate* priv = FL_ACCESSIBLE_NODE_GET_PRIVATE(self);
fl_engine_dispatch_semantics_action(priv->engine, priv->id, action, data);
fl_engine_dispatch_semantics_action(priv->engine, priv->view_id,
priv->node_id, action, data);
}
static void fl_accessible_node_class_init(FlAccessibleNodeClass* klass) {
@ -453,10 +460,16 @@ static void fl_accessible_node_class_init(FlAccessibleNodeClass* klass) {
"engine", "engine", "Flutter engine", fl_engine_get_type(),
static_cast<GParamFlags>(G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY |
G_PARAM_STATIC_STRINGS)));
g_object_class_install_property(
G_OBJECT_CLASS(klass), PROP_VIEW_ID,
g_param_spec_int64(
"view-id", "view-id", "View ID that this node belongs to", 0,
G_MAXINT64, 0,
static_cast<GParamFlags>(G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY)));
g_object_class_install_property(
G_OBJECT_CLASS(klass), PROP_ID,
g_param_spec_int(
"id", "id", "Accessibility node ID", 0, G_MAXINT, 0,
"node-id", "node-id", "Accessibility node ID", 0, G_MAXINT, 0,
static_cast<GParamFlags>(G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY |
G_PARAM_STATIC_STRINGS)));
}
@ -479,9 +492,12 @@ static void fl_accessible_node_init(FlAccessibleNode* self) {
priv->children = g_ptr_array_new_with_free_func(g_object_unref);
}
FlAccessibleNode* fl_accessible_node_new(FlEngine* engine, int32_t id) {
FlAccessibleNode* self = FL_ACCESSIBLE_NODE(g_object_new(
fl_accessible_node_get_type(), "engine", engine, "id", id, nullptr));
FlAccessibleNode* fl_accessible_node_new(FlEngine* engine,
FlutterViewId view_id,
int32_t node_id) {
FlAccessibleNode* self = FL_ACCESSIBLE_NODE(
g_object_new(fl_accessible_node_get_type(), "engine", engine, "view-id",
view_id, "node-id", node_id, nullptr));
return self;
}

View File

@ -55,14 +55,17 @@ struct _FlAccessibleNodeClass {
/**
* fl_accessible_node_new:
* @engine: the #FlEngine this node came from.
* @id: the semantics node ID this object represents.
* @view_id: the view ID this object represents.
* @node_id: the semantics node ID this object represents.
*
* Creates a new accessibility object that exposes Flutter accessibility
* information to ATK.
*
* Returns: a new #FlAccessibleNode.
*/
FlAccessibleNode* fl_accessible_node_new(FlEngine* engine, int32_t id);
FlAccessibleNode* fl_accessible_node_new(FlEngine* engine,
FlutterViewId view_id,
int32_t node_id);
/**
* fl_accessible_node_set_parent:

View File

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

View File

@ -615,7 +615,10 @@ static void fl_accessible_text_field_init(FlAccessibleTextField* self) {
self, G_CONNECT_SWAPPED);
}
FlAccessibleNode* fl_accessible_text_field_new(FlEngine* engine, int32_t id) {
FlAccessibleNode* fl_accessible_text_field_new(FlEngine* engine,
FlutterViewId view_id,
int32_t id) {
return FL_ACCESSIBLE_NODE(g_object_new(fl_accessible_text_field_get_type(),
"engine", engine, "id", id, nullptr));
"engine", engine, "view-id", view_id,
"node-id", id, nullptr));
}

View File

@ -20,6 +20,7 @@ G_DECLARE_FINAL_TYPE(FlAccessibleTextField,
/**
* fl_accessible_text_field_new:
* @engine: the #FlEngine this node came from.
* @view_id: the ID of the view that contains this semantics node.
* @id: the semantics node ID this object represents.
*
* Creates a new accessibility object that exposes an editable Flutter text
@ -27,7 +28,9 @@ G_DECLARE_FINAL_TYPE(FlAccessibleTextField,
*
* Returns: a new #FlAccessibleNode.
*/
FlAccessibleNode* fl_accessible_text_field_new(FlEngine* engine, int32_t id);
FlAccessibleNode* fl_accessible_text_field_new(FlEngine* engine,
FlutterViewId view_id,
int32_t id);
G_END_DECLS

View File

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

View File

@ -1201,7 +1201,8 @@ gboolean fl_engine_send_key_event_finish(FlEngine* self,
}
void fl_engine_dispatch_semantics_action(FlEngine* self,
uint64_t id,
FlutterViewId view_id,
uint64_t node_id,
FlutterSemanticsAction action,
GBytes* data) {
g_return_if_fail(FL_IS_ENGINE(self));
@ -1217,8 +1218,14 @@ void fl_engine_dispatch_semantics_action(FlEngine* self,
g_bytes_get_data(data, &action_data_length));
}
self->embedder_api.DispatchSemanticsAction(self->engine, id, action,
action_data, action_data_length);
FlutterSendSemanticsActionInfo info;
info.struct_size = sizeof(FlutterSendSemanticsActionInfo);
info.view_id = view_id;
info.node_id = node_id;
info.action = action;
info.data = action_data;
info.data_length = action_data_length;
self->embedder_api.SendSemanticsAction(self->engine, &info);
}
gboolean fl_engine_mark_texture_frame_available(FlEngine* self,

View File

@ -414,12 +414,14 @@ gboolean fl_engine_send_key_event_finish(FlEngine* engine,
/**
* fl_engine_dispatch_semantics_action:
* @engine: an #FlEngine.
* @id: the semantics action identifier.
* @view_id: the view that the event occured on.
* @node_id: the semantics action identifier.
* @action: the action being dispatched.
* @data: (allow-none): data associated with the action.
*/
void fl_engine_dispatch_semantics_action(FlEngine* engine,
uint64_t id,
FlutterViewId view_id,
uint64_t node_id,
FlutterSemanticsAction action,
GBytes* data);

View File

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

View File

@ -222,8 +222,16 @@ static void view_added_cb(GObject* object,
}
// Called when the engine updates accessibility.
static void update_semantics_cb(FlView* self,
const FlutterSemanticsUpdate2* update) {
static void update_semantics_cb(FlEngine* engine,
const FlutterSemanticsUpdate2* update,
gpointer user_data) {
FlView* self = FL_VIEW(user_data);
// A semantics update is routed to a particular view.
if (update->view_id != self->view_id) {
return;
}
fl_view_accessible_handle_update_semantics(self->view_accessible, update);
}
@ -489,7 +497,7 @@ static void realize_cb(FlView* self) {
handle_geometry_changed(self);
self->view_accessible = fl_view_accessible_new(self->engine);
self->view_accessible = fl_view_accessible_new(self->engine, self->view_id);
fl_socket_accessible_embed(
FL_SOCKET_ACCESSIBLE(gtk_widget_get_accessible(GTK_WIDGET(self))),
atk_plug_get_id(ATK_PLUG(self->view_accessible)));
@ -779,6 +787,7 @@ G_MODULE_EXPORT FlView* fl_view_new_for_engine(FlEngine* engine) {
self->view_id = fl_engine_add_view(engine, 1, 1, 1.0, self->cancellable,
view_added_cb, self);
fl_renderer_add_renderable(FL_RENDERER(self->renderer), self->view_id,
FL_RENDERABLE(self));
@ -786,7 +795,6 @@ G_MODULE_EXPORT FlView* fl_view_new_for_engine(FlEngine* engine) {
g_signal_connect_swapped(self->gl_area, "realize",
G_CALLBACK(secondary_realize_cb), self);
return self;
}

View File

@ -15,6 +15,8 @@ struct _FlViewAccessible {
GWeakRef engine;
FlutterViewId view_id;
// Semantics nodes keyed by ID
GHashTable* semantics_nodes_by_id;
@ -32,10 +34,10 @@ static FlAccessibleNode* create_node(FlViewAccessible* self,
}
if (semantics->flags & kFlutterSemanticsFlagIsTextField) {
return fl_accessible_text_field_new(engine, semantics->id);
return fl_accessible_text_field_new(engine, self->view_id, semantics->id);
}
return fl_accessible_node_new(engine, semantics->id);
return fl_accessible_node_new(engine, self->view_id, semantics->id);
}
static FlAccessibleNode* lookup_node(FlViewAccessible* self, int32_t id) {
@ -129,10 +131,12 @@ static void fl_view_accessible_init(FlViewAccessible* self) {
g_direct_hash, g_direct_equal, nullptr, g_object_unref);
}
FlViewAccessible* fl_view_accessible_new(FlEngine* engine) {
FlViewAccessible* fl_view_accessible_new(FlEngine* engine,
FlutterViewId view_id) {
FlViewAccessible* self =
FL_VIEW_ACCESSIBLE(g_object_new(fl_view_accessible_get_type(), nullptr));
g_weak_ref_init(&self->engine, engine);
self->view_id = view_id;
return self;
}

View File

@ -37,13 +37,16 @@ G_DECLARE_FINAL_TYPE(FlViewAccessible,
/**
* fl_view_accessible_new:
* @engine: the #FlEngine.
* @view_id: the Flutter view id.
*
* Creates a new accessibility object that exposes Flutter accessibility
* information to ATK.
*
* Returns: a new #FlViewAccessible.
*/
FlViewAccessible* fl_view_accessible_new(FlEngine* engine);
FlViewAccessible* fl_view_accessible_new(FlEngine* engine,
FlutterViewId view_id);
/**
* fl_view_accessible_handle_update_semantics:

View File

@ -12,7 +12,7 @@
TEST(FlViewAccessibleTest, BuildTree) {
g_autoptr(FlDartProject) project = fl_dart_project_new();
g_autoptr(FlEngine) engine = fl_engine_new(project);
g_autoptr(FlViewAccessible) accessible = fl_view_accessible_new(engine);
g_autoptr(FlViewAccessible) accessible = fl_view_accessible_new(engine, 456);
int32_t children[] = {111, 222};
FlutterSemanticsNode2 root_node = {
@ -49,7 +49,7 @@ TEST(FlViewAccessibleTest, BuildTree) {
TEST(FlViewAccessibleTest, AddRemoveChildren) {
g_autoptr(FlDartProject) project = fl_dart_project_new();
g_autoptr(FlEngine) engine = fl_engine_new(project);
g_autoptr(FlViewAccessible) accessible = fl_view_accessible_new(engine);
g_autoptr(FlViewAccessible) accessible = fl_view_accessible_new(engine, 456);
FlutterSemanticsNode2 root_node = {
.id = 0,

View File

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

View File

@ -163,7 +163,8 @@ void AccessibilityBridgeWindows::DispatchAccessibilityAction(
AccessibilityNodeId target,
FlutterSemanticsAction action,
fml::MallocMapping data) {
view_->GetEngine()->DispatchSemanticsAction(target, action, std::move(data));
view_->GetEngine()->DispatchSemanticsAction(view_->view_id(), target, action,
std::move(data));
}
std::shared_ptr<FlutterPlatformNodeDelegate>

View File

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

View File

@ -5,7 +5,7 @@
import 'dart:async';
import 'dart:convert';
import 'dart:io' as io;
import 'dart:typed_data' show ByteData, Uint8List;
import 'dart:typed_data' show ByteData, Float64List, Int32List, Uint8List;
import 'dart:ui' as ui;
// Signals a waiting latch in the native test.
@ -404,3 +404,74 @@ external void notifyEngineId(int? handle);
void testEngineId() {
notifyEngineId(ui.PlatformDispatcher.instance.engineId);
}
@pragma('vm:entry-point')
Future<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) {
auto host = static_cast<FlutterWindowsEngine*>(user_data);
// TODO(loicsharma): Remove implicit view assumption.
// https://github.com/flutter/flutter/issues/142845
auto view = host->view(kImplicitViewId);
auto view = host->view(update->view_id);
if (!view) {
return;
}
@ -518,6 +516,7 @@ std::unique_ptr<FlutterWindowsView> FlutterWindowsEngine::CreateView(
view_id, this, std::move(window), windows_proc_table_);
view->CreateRenderSurface();
view->UpdateSemanticsEnabled(semantics_enabled_);
next_view_id_++;
@ -932,12 +931,19 @@ bool FlutterWindowsEngine::PostRasterThreadTask(fml::closure callback) const {
}
bool FlutterWindowsEngine::DispatchSemanticsAction(
FlutterViewId view_id,
uint64_t target,
FlutterSemanticsAction action,
fml::MallocMapping data) {
return (embedder_api_.DispatchSemanticsAction(engine_, target, action,
data.GetMapping(),
data.GetSize()) == kSuccess);
FlutterSendSemanticsActionInfo info{
.struct_size = sizeof(FlutterSendSemanticsActionInfo),
.view_id = view_id,
.node_id = target,
.action = action,
.data = data.GetMapping(),
.data_length = data.GetSize(),
};
return (embedder_api_.SendSemanticsAction(engine_, &info));
}
void FlutterWindowsEngine::UpdateSemanticsEnabled(bool enabled) {

View File

@ -237,7 +237,8 @@ class FlutterWindowsEngine {
void OnVsync(intptr_t baton);
// Dispatches a semantics action to the specified semantics node.
bool DispatchSemanticsAction(uint64_t id,
bool DispatchSemanticsAction(FlutterViewId view_id,
uint64_t node_id,
FlutterSemanticsAction action,
fml::MallocMapping data);

View File

@ -482,20 +482,19 @@ TEST_F(FlutterWindowsEngineTest, DispatchSemanticsAction) {
bool called = false;
std::string message = "Hello";
modifier.embedder_api().DispatchSemanticsAction = MOCK_ENGINE_PROC(
DispatchSemanticsAction,
([&called, &message](auto engine, auto target, auto action, auto data,
auto data_length) {
modifier.embedder_api().SendSemanticsAction = MOCK_ENGINE_PROC(
SendSemanticsAction, ([&called, &message](auto engine, auto info) {
called = true;
EXPECT_EQ(target, 42);
EXPECT_EQ(action, kFlutterSemanticsActionDismiss);
EXPECT_EQ(memcmp(data, message.c_str(), message.size()), 0);
EXPECT_EQ(data_length, message.size());
EXPECT_EQ(info->view_id, 456);
EXPECT_EQ(info->node_id, 42);
EXPECT_EQ(info->action, kFlutterSemanticsActionDismiss);
EXPECT_EQ(memcmp(info->data, message.c_str(), message.size()), 0);
EXPECT_EQ(info->data_length, message.size());
return kSuccess;
}));
auto data = fml::MallocMapping::Copy(message.c_str(), message.size());
engine->DispatchSemanticsAction(42, kFlutterSemanticsActionDismiss,
engine->DispatchSemanticsAction(456, 42, kFlutterSemanticsActionDismiss,
std::move(data));
EXPECT_TRUE(called);
}
@ -1379,5 +1378,77 @@ TEST_F(FlutterWindowsEngineTest, OnViewFocusChangeRequest) {
modifier.OnViewFocusChangeRequest(&request);
}
TEST_F(FlutterWindowsEngineTest, UpdateSemanticsMultiView) {
auto& context = GetContext();
WindowsConfigBuilder builder{context};
builder.SetDartEntrypoint("sendSemanticsTreeInfo");
// Setup: a signal for when we have send out all of our semantics updates
bool done = false;
auto native_entry =
CREATE_NATIVE_ENTRY([&](Dart_NativeArguments args) { done = true; });
context.AddNativeFunction("Signal", native_entry);
// Setup: Create the engine and two views + enable semantics
EnginePtr engine{builder.RunHeadless()};
ASSERT_NE(engine, nullptr);
auto window_binding_handler1 =
std::make_unique<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 flutter

View File

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