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:
parent
9451e1dcfd
commit
554c814ada
@ -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);
|
||||
});
|
||||
|
@ -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')
|
||||
|
@ -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,
|
||||
),
|
||||
);
|
||||
|
@ -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)';
|
||||
|
@ -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(
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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 {
|
||||
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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(
|
||||
|
@ -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) {
|
||||
|
@ -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|
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
|
@ -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) {}
|
||||
|
@ -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);
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
|
@ -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);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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));
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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();
|
||||
|
@ -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);
|
||||
|
@ -133,6 +133,7 @@ class PlatformViewAndroid final : public PlatformView {
|
||||
|
||||
// |PlatformView|
|
||||
void UpdateSemantics(
|
||||
int64_t view_id,
|
||||
flutter::SemanticsNodeUpdates update,
|
||||
flutter::CustomAccessibilityActionUpdates actions) override;
|
||||
|
||||
|
@ -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 {}
|
||||
|
@ -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 {}
|
||||
|
@ -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,
|
||||
|
@ -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 {}
|
||||
|
@ -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|
|
||||
|
@ -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_) {
|
||||
|
@ -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"
|
||||
|
@ -1843,8 +1843,8 @@ 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,
|
||||
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);
|
||||
|
@ -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;
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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),
|
||||
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() {}
|
||||
|
@ -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();
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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(
|
||||
([¬ify_semantics_enabled_callback](Dart_NativeArguments args) {
|
||||
ASSERT_NE(notify_semantics_enabled_callback, nullptr);
|
||||
notify_semantics_enabled_callback(args);
|
||||
})));
|
||||
|
||||
NativeEntry notify_accessibility_features_callback;
|
||||
context.AddNativeCallback(
|
||||
"NotifyAccessibilityFeatures",
|
||||
CREATE_NATIVE_ENTRY((
|
||||
[¬ify_accessibility_features_callback](Dart_NativeArguments args) {
|
||||
ASSERT_NE(notify_accessibility_features_callback, nullptr);
|
||||
notify_accessibility_features_callback(args);
|
||||
})));
|
||||
|
||||
int num_times_set_semantics_update_callback2_called = 0;
|
||||
fml::AutoResetWaitableEvent semantics_update_latch;
|
||||
context.SetSemanticsUpdateCallback2(
|
||||
[&](const FlutterSemanticsUpdate2* update) {
|
||||
num_times_set_semantics_update_callback2_called++;
|
||||
ASSERT_EQ(size_t(1), update->node_count);
|
||||
|
||||
for (size_t i = 0; i < update->node_count; i++) {
|
||||
const FlutterSemanticsNode2* node = update->nodes[i];
|
||||
|
||||
// The node ID should be the view_id + 1
|
||||
ASSERT_EQ(node->id, update->view_id + 1);
|
||||
ASSERT_EQ(1.0, node->transform.scaleX);
|
||||
ASSERT_EQ(2.0, node->transform.skewX);
|
||||
ASSERT_EQ(3.0, node->transform.transX);
|
||||
ASSERT_EQ(4.0, node->transform.skewY);
|
||||
ASSERT_EQ(5.0, node->transform.scaleY);
|
||||
ASSERT_EQ(6.0, node->transform.transY);
|
||||
ASSERT_EQ(7.0, node->transform.pers0);
|
||||
ASSERT_EQ(8.0, node->transform.pers1);
|
||||
ASSERT_EQ(9.0, node->transform.pers2);
|
||||
ASSERT_EQ(std::strncmp(kTooltip, node->tooltip, sizeof(kTooltip) - 1),
|
||||
0);
|
||||
}
|
||||
|
||||
if (num_times_set_semantics_update_callback2_called == 3) {
|
||||
semantics_update_latch.Signal();
|
||||
}
|
||||
});
|
||||
|
||||
EmbedderConfigBuilder builder(context);
|
||||
builder.SetSurface(SkISize::Make(1, 1));
|
||||
builder.SetDartEntrypoint("a11y_main_multi_view");
|
||||
|
||||
auto engine = builder.LaunchEngine();
|
||||
ASSERT_TRUE(engine.is_valid());
|
||||
|
||||
// 1: Wait for initial notifySemanticsEnabled(false).
|
||||
fml::AutoResetWaitableEvent notify_semantics_enabled_latch;
|
||||
notify_semantics_enabled_callback = [&](Dart_NativeArguments args) {
|
||||
Dart_Handle exception = nullptr;
|
||||
bool enabled =
|
||||
::tonic::DartConverter<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 = ¬ify_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
|
||||
|
||||
|
@ -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, {});
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -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 =
|
||||
|
@ -134,6 +134,7 @@ class PlatformView : public flutter::PlatformView {
|
||||
|
||||
// |flutter::PlatformView|
|
||||
void UpdateSemantics(
|
||||
int64_t view_id,
|
||||
flutter::SemanticsNodeUpdates update,
|
||||
flutter::CustomAccessibilityActionUpdates actions) override;
|
||||
|
||||
|
@ -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|
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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:
|
||||
|
@ -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));
|
||||
|
@ -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));
|
||||
}
|
||||
|
@ -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
|
||||
|
||||
|
@ -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) {
|
||||
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(data, data_length));
|
||||
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);
|
||||
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);
|
||||
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);
|
||||
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);
|
||||
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);
|
||||
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,
|
||||
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 (action == kFlutterSemanticsActionSetText) {
|
||||
if (info->action == kFlutterSemanticsActionSetText) {
|
||||
g_autoptr(FlValue) value =
|
||||
decode_semantic_data(data, data_length);
|
||||
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(data, data_length);
|
||||
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"));
|
||||
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,
|
||||
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 = action;
|
||||
if (action == kFlutterSemanticsActionSetSelection) {
|
||||
act = info->action;
|
||||
if (info->action == kFlutterSemanticsActionSetSelection) {
|
||||
g_autoptr(FlValue) value =
|
||||
decode_semantic_data(data, data_length);
|
||||
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"));
|
||||
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?"
|
||||
|
@ -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,
|
||||
|
@ -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);
|
||||
|
||||
|
@ -180,18 +180,17 @@ 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');
|
||||
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;
|
||||
@ -201,8 +200,8 @@ TEST(FlEngineTest, DispatchSemanticsAction) {
|
||||
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);
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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:
|
||||
|
@ -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,
|
||||
|
@ -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 =
|
||||
|
@ -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>
|
||||
|
@ -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;
|
||||
}));
|
||||
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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
|
||||
|
@ -91,6 +91,10 @@ class EngineModifier {
|
||||
engine_->OnViewFocusChangeRequest(request);
|
||||
}
|
||||
|
||||
void SetNextViewId(FlutterViewId view_id) {
|
||||
engine_->next_view_id_ = view_id;
|
||||
}
|
||||
|
||||
private:
|
||||
FlutterWindowsEngine* engine_;
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user