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);
|
expectIdentical(runZone, innerZone);
|
||||||
|
expectEquals(action.viewId, 456);
|
||||||
expectEquals(action.nodeId, 1234);
|
expectEquals(action.nodeId, 1234);
|
||||||
expectEquals(action.type.index, 4);
|
expectEquals(action.type.index, 4);
|
||||||
});
|
});
|
||||||
|
@ -283,8 +283,8 @@ void _dispatchPointerDataPacket(ByteData packet) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@pragma('vm:entry-point')
|
@pragma('vm:entry-point')
|
||||||
void _dispatchSemanticsAction(int nodeId, int action, ByteData? args) {
|
void _dispatchSemanticsAction(int viewId, int nodeId, int action, ByteData? args) {
|
||||||
PlatformDispatcher.instance._dispatchSemanticsAction(nodeId, action, args);
|
PlatformDispatcher.instance._dispatchSemanticsAction(viewId, nodeId, action, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
@pragma('vm:entry-point')
|
@pragma('vm:entry-point')
|
||||||
|
@ -939,10 +939,12 @@ class PlatformDispatcher {
|
|||||||
call `updateSemantics`.
|
call `updateSemantics`.
|
||||||
''')
|
''')
|
||||||
void updateSemantics(SemanticsUpdate update) =>
|
void updateSemantics(SemanticsUpdate update) =>
|
||||||
_updateSemantics(update as _NativeSemanticsUpdate);
|
_updateSemantics(_implicitViewId!, update as _NativeSemanticsUpdate);
|
||||||
|
|
||||||
@Native<Void Function(Pointer<Void>)>(symbol: 'PlatformConfigurationNativeApi::UpdateSemantics')
|
@Native<Void Function(Int64, Pointer<Void>)>(
|
||||||
external static void _updateSemantics(_NativeSemanticsUpdate update);
|
symbol: 'PlatformConfigurationNativeApi::UpdateSemantics',
|
||||||
|
)
|
||||||
|
external static void _updateSemantics(int viewId, _NativeSemanticsUpdate update);
|
||||||
|
|
||||||
/// The system-reported default locale of the device.
|
/// The system-reported default locale of the device.
|
||||||
///
|
///
|
||||||
@ -1332,14 +1334,14 @@ class PlatformDispatcher {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Called from the engine, via hooks.dart
|
// Called from the engine, via hooks.dart
|
||||||
void _dispatchSemanticsAction(int nodeId, int action, ByteData? args) {
|
void _dispatchSemanticsAction(int viewId, int nodeId, int action, ByteData? args) {
|
||||||
_invoke1<SemanticsActionEvent>(
|
_invoke1<SemanticsActionEvent>(
|
||||||
onSemanticsActionEvent,
|
onSemanticsActionEvent,
|
||||||
_onSemanticsActionEventZone,
|
_onSemanticsActionEventZone,
|
||||||
SemanticsActionEvent(
|
SemanticsActionEvent(
|
||||||
type: SemanticsAction.fromIndex(action)!,
|
type: SemanticsAction.fromIndex(action)!,
|
||||||
nodeId: nodeId,
|
nodeId: nodeId,
|
||||||
viewId: 0, // TODO(goderbauer): Wire up the real view ID.
|
viewId: viewId,
|
||||||
arguments: args,
|
arguments: args,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
@ -397,10 +397,12 @@ class FlutterView {
|
|||||||
/// This function disposes the given update, which means the semantics update
|
/// This function disposes the given update, which means the semantics update
|
||||||
/// cannot be used further.
|
/// cannot be used further.
|
||||||
void updateSemantics(SemanticsUpdate update) =>
|
void updateSemantics(SemanticsUpdate update) =>
|
||||||
_updateSemantics(update as _NativeSemanticsUpdate);
|
_updateSemantics(viewId, update as _NativeSemanticsUpdate);
|
||||||
|
|
||||||
@Native<Void Function(Pointer<Void>)>(symbol: 'PlatformConfigurationNativeApi::UpdateSemantics')
|
@Native<Void Function(Int64, Pointer<Void>)>(
|
||||||
external static void _updateSemantics(_NativeSemanticsUpdate update);
|
symbol: 'PlatformConfigurationNativeApi::UpdateSemantics',
|
||||||
|
)
|
||||||
|
external static void _updateSemantics(int viewId, _NativeSemanticsUpdate update);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() => 'FlutterView(id: $viewId)';
|
String toString() => 'FlutterView(id: $viewId)';
|
||||||
|
@ -380,7 +380,8 @@ void PlatformConfiguration::DispatchPointerDataPacket(
|
|||||||
tonic::DartInvoke(dispatch_pointer_data_packet_.Get(), {data_handle}));
|
tonic::DartInvoke(dispatch_pointer_data_packet_.Get(), {data_handle}));
|
||||||
}
|
}
|
||||||
|
|
||||||
void PlatformConfiguration::DispatchSemanticsAction(int32_t node_id,
|
void PlatformConfiguration::DispatchSemanticsAction(int64_t view_id,
|
||||||
|
int32_t node_id,
|
||||||
SemanticsAction action,
|
SemanticsAction action,
|
||||||
fml::MallocMapping args) {
|
fml::MallocMapping args) {
|
||||||
std::shared_ptr<tonic::DartState> dart_state =
|
std::shared_ptr<tonic::DartState> dart_state =
|
||||||
@ -399,8 +400,8 @@ void PlatformConfiguration::DispatchSemanticsAction(int32_t node_id,
|
|||||||
|
|
||||||
tonic::CheckAndHandleError(tonic::DartInvoke(
|
tonic::CheckAndHandleError(tonic::DartInvoke(
|
||||||
dispatch_semantics_action_.Get(),
|
dispatch_semantics_action_.Get(),
|
||||||
{tonic::ToDart(node_id), tonic::ToDart(static_cast<int32_t>(action)),
|
{tonic::ToDart(view_id), tonic::ToDart(node_id),
|
||||||
args_handle}));
|
tonic::ToDart(static_cast<int32_t>(action)), args_handle}));
|
||||||
}
|
}
|
||||||
|
|
||||||
void PlatformConfiguration::BeginFrame(fml::TimePoint frameTime,
|
void PlatformConfiguration::BeginFrame(fml::TimePoint frameTime,
|
||||||
@ -661,10 +662,11 @@ void PlatformConfigurationNativeApi::EndWarmUpFrame() {
|
|||||||
UIDartState::Current()->platform_configuration()->client()->EndWarmUpFrame();
|
UIDartState::Current()->platform_configuration()->client()->EndWarmUpFrame();
|
||||||
}
|
}
|
||||||
|
|
||||||
void PlatformConfigurationNativeApi::UpdateSemantics(SemanticsUpdate* update) {
|
void PlatformConfigurationNativeApi::UpdateSemantics(int64_t view_id,
|
||||||
|
SemanticsUpdate* update) {
|
||||||
UIDartState::ThrowIfUIOperationsProhibited();
|
UIDartState::ThrowIfUIOperationsProhibited();
|
||||||
UIDartState::Current()->platform_configuration()->client()->UpdateSemantics(
|
UIDartState::Current()->platform_configuration()->client()->UpdateSemantics(
|
||||||
update);
|
view_id, update);
|
||||||
}
|
}
|
||||||
|
|
||||||
Dart_Handle PlatformConfigurationNativeApi::ComputePlatformResolvedLocale(
|
Dart_Handle PlatformConfigurationNativeApi::ComputePlatformResolvedLocale(
|
||||||
|
@ -92,9 +92,10 @@ class PlatformConfigurationClient {
|
|||||||
//--------------------------------------------------------------------------
|
//--------------------------------------------------------------------------
|
||||||
/// @brief Receives an updated semantics tree from the Framework.
|
/// @brief Receives an updated semantics tree from the Framework.
|
||||||
///
|
///
|
||||||
|
/// @param[in] viewId The identifier of the view to update.
|
||||||
/// @param[in] update The updated semantic tree to apply.
|
/// @param[in] update The updated semantic tree to apply.
|
||||||
///
|
///
|
||||||
virtual void UpdateSemantics(SemanticsUpdate* update) = 0;
|
virtual void UpdateSemantics(int64_t viewId, SemanticsUpdate* update) = 0;
|
||||||
|
|
||||||
//--------------------------------------------------------------------------
|
//--------------------------------------------------------------------------
|
||||||
/// @brief When the Flutter application has a message to send to the
|
/// @brief When the Flutter application has a message to send to the
|
||||||
@ -451,12 +452,14 @@ class PlatformConfiguration final {
|
|||||||
/// originates on the platform view and has been forwarded to the
|
/// originates on the platform view and has been forwarded to the
|
||||||
/// platform configuration here by the engine.
|
/// platform configuration here by the engine.
|
||||||
///
|
///
|
||||||
|
/// @param[in] view_id The identifier of the view.
|
||||||
/// @param[in] node_id The identifier of the accessibility node.
|
/// @param[in] node_id The identifier of the accessibility node.
|
||||||
/// @param[in] action The accessibility related action performed on the
|
/// @param[in] action The accessibility related action performed on the
|
||||||
/// node of the specified ID.
|
/// node of the specified ID.
|
||||||
/// @param[in] args Optional data that applies to the specified action.
|
/// @param[in] args Optional data that applies to the specified action.
|
||||||
///
|
///
|
||||||
void DispatchSemanticsAction(int32_t node_id,
|
void DispatchSemanticsAction(int64_t view_id,
|
||||||
|
int32_t node_id,
|
||||||
SemanticsAction action,
|
SemanticsAction action,
|
||||||
fml::MallocMapping args);
|
fml::MallocMapping args);
|
||||||
|
|
||||||
@ -620,7 +623,7 @@ class PlatformConfigurationNativeApi {
|
|||||||
double width,
|
double width,
|
||||||
double height);
|
double height);
|
||||||
|
|
||||||
static void UpdateSemantics(SemanticsUpdate* update);
|
static void UpdateSemantics(int64_t viewId, SemanticsUpdate* update);
|
||||||
|
|
||||||
static void SetNeedsReportTimings(bool value);
|
static void SetNeedsReportTimings(bool value);
|
||||||
|
|
||||||
|
@ -711,7 +711,7 @@ class FakePlatformConfigurationClient : public PlatformConfigurationClient {
|
|||||||
Scene* scene,
|
Scene* scene,
|
||||||
double width,
|
double width,
|
||||||
double height) override {}
|
double height) override {}
|
||||||
void UpdateSemantics(SemanticsUpdate* update) override {}
|
void UpdateSemantics(int64_t view_id, SemanticsUpdate* update) override {}
|
||||||
void HandlePlatformMessage(
|
void HandlePlatformMessage(
|
||||||
std::unique_ptr<PlatformMessage> message) override {}
|
std::unique_ptr<PlatformMessage> message) override {}
|
||||||
FontCollection& GetFontCollection() override {
|
FontCollection& GetFontCollection() override {
|
||||||
|
@ -383,13 +383,14 @@ bool RuntimeController::DispatchPointerDataPacket(
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool RuntimeController::DispatchSemanticsAction(int32_t node_id,
|
bool RuntimeController::DispatchSemanticsAction(int64_t view_id,
|
||||||
|
int32_t node_id,
|
||||||
SemanticsAction action,
|
SemanticsAction action,
|
||||||
fml::MallocMapping args) {
|
fml::MallocMapping args) {
|
||||||
TRACE_EVENT1("flutter", "RuntimeController::DispatchSemanticsAction", "mode",
|
TRACE_EVENT1("flutter", "RuntimeController::DispatchSemanticsAction", "mode",
|
||||||
"basic");
|
"basic");
|
||||||
if (auto* platform_configuration = GetPlatformConfigurationIfAvailable()) {
|
if (auto* platform_configuration = GetPlatformConfigurationIfAvailable()) {
|
||||||
platform_configuration->DispatchSemanticsAction(node_id, action,
|
platform_configuration->DispatchSemanticsAction(view_id, node_id, action,
|
||||||
std::move(args));
|
std::move(args));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -447,9 +448,11 @@ void RuntimeController::CheckIfAllViewsRendered() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// |PlatformConfigurationClient|
|
// |PlatformConfigurationClient|
|
||||||
void RuntimeController::UpdateSemantics(SemanticsUpdate* update) {
|
void RuntimeController::UpdateSemantics(int64_t view_id,
|
||||||
|
SemanticsUpdate* update) {
|
||||||
if (platform_data_.semantics_enabled) {
|
if (platform_data_.semantics_enabled) {
|
||||||
client_.UpdateSemantics(update->takeNodes(), update->takeActions());
|
client_.UpdateSemantics(view_id, update->takeNodes(),
|
||||||
|
update->takeActions());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -490,7 +490,8 @@ class RuntimeController : public PlatformConfigurationClient,
|
|||||||
/// @brief Dispatch the semantics action to the specified accessibility
|
/// @brief Dispatch the semantics action to the specified accessibility
|
||||||
/// node.
|
/// node.
|
||||||
///
|
///
|
||||||
/// @param[in] node_id The identified of the accessibility node.
|
/// @param[in] view_id The identifier of the view.
|
||||||
|
/// @param[in] node_id The identifier of the accessibility node.
|
||||||
/// @param[in] action The semantics action to perform on the specified
|
/// @param[in] action The semantics action to perform on the specified
|
||||||
/// accessibility node.
|
/// accessibility node.
|
||||||
/// @param[in] args Optional data that applies to the specified action.
|
/// @param[in] args Optional data that applies to the specified action.
|
||||||
@ -498,7 +499,8 @@ class RuntimeController : public PlatformConfigurationClient,
|
|||||||
/// @return If the semantics action was dispatched. This may fail if an
|
/// @return If the semantics action was dispatched. This may fail if an
|
||||||
/// isolate is not running.
|
/// isolate is not running.
|
||||||
///
|
///
|
||||||
bool DispatchSemanticsAction(int32_t node_id,
|
bool DispatchSemanticsAction(int64_t view_id,
|
||||||
|
int32_t node_id,
|
||||||
SemanticsAction action,
|
SemanticsAction action,
|
||||||
fml::MallocMapping args);
|
fml::MallocMapping args);
|
||||||
|
|
||||||
@ -767,7 +769,7 @@ class RuntimeController : public PlatformConfigurationClient,
|
|||||||
double height) override;
|
double height) override;
|
||||||
|
|
||||||
// |PlatformConfigurationClient|
|
// |PlatformConfigurationClient|
|
||||||
void UpdateSemantics(SemanticsUpdate* update) override;
|
void UpdateSemantics(int64_t view_id, SemanticsUpdate* update) override;
|
||||||
|
|
||||||
// |PlatformConfigurationClient|
|
// |PlatformConfigurationClient|
|
||||||
void HandlePlatformMessage(std::unique_ptr<PlatformMessage> message) override;
|
void HandlePlatformMessage(std::unique_ptr<PlatformMessage> message) override;
|
||||||
|
@ -32,7 +32,8 @@ class RuntimeDelegate {
|
|||||||
std::unique_ptr<flutter::LayerTree> layer_tree,
|
std::unique_ptr<flutter::LayerTree> layer_tree,
|
||||||
float device_pixel_ratio) = 0;
|
float device_pixel_ratio) = 0;
|
||||||
|
|
||||||
virtual void UpdateSemantics(SemanticsNodeUpdates update,
|
virtual void UpdateSemantics(int64_t view_id,
|
||||||
|
SemanticsNodeUpdates update,
|
||||||
CustomAccessibilityActionUpdates actions) = 0;
|
CustomAccessibilityActionUpdates actions) = 0;
|
||||||
|
|
||||||
virtual void HandlePlatformMessage(
|
virtual void HandlePlatformMessage(
|
||||||
|
@ -450,10 +450,11 @@ void Engine::DispatchPointerDataPacket(
|
|||||||
pointer_data_dispatcher_->DispatchPacket(std::move(packet), trace_flow_id);
|
pointer_data_dispatcher_->DispatchPacket(std::move(packet), trace_flow_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Engine::DispatchSemanticsAction(int node_id,
|
void Engine::DispatchSemanticsAction(int64_t view_id,
|
||||||
|
int node_id,
|
||||||
SemanticsAction action,
|
SemanticsAction action,
|
||||||
fml::MallocMapping args) {
|
fml::MallocMapping args) {
|
||||||
runtime_controller_->DispatchSemanticsAction(node_id, action,
|
runtime_controller_->DispatchSemanticsAction(view_id, node_id, action,
|
||||||
std::move(args));
|
std::move(args));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -495,9 +496,11 @@ void Engine::Render(int64_t view_id,
|
|||||||
animator_->Render(view_id, std::move(layer_tree), device_pixel_ratio);
|
animator_->Render(view_id, std::move(layer_tree), device_pixel_ratio);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Engine::UpdateSemantics(SemanticsNodeUpdates update,
|
void Engine::UpdateSemantics(int64_t view_id,
|
||||||
|
SemanticsNodeUpdates update,
|
||||||
CustomAccessibilityActionUpdates actions) {
|
CustomAccessibilityActionUpdates actions) {
|
||||||
delegate_.OnEngineUpdateSemantics(std::move(update), std::move(actions));
|
delegate_.OnEngineUpdateSemantics(view_id, std::move(update),
|
||||||
|
std::move(actions));
|
||||||
}
|
}
|
||||||
|
|
||||||
void Engine::HandlePlatformMessage(std::unique_ptr<PlatformMessage> message) {
|
void Engine::HandlePlatformMessage(std::unique_ptr<PlatformMessage> message) {
|
||||||
|
@ -150,12 +150,14 @@ class Engine final : public RuntimeDelegate, PointerDataDispatcher::Delegate {
|
|||||||
/// `CustomAccessibilityActionUpdates`,
|
/// `CustomAccessibilityActionUpdates`,
|
||||||
/// `PlatformView::UpdateSemantics`
|
/// `PlatformView::UpdateSemantics`
|
||||||
///
|
///
|
||||||
|
/// @param[in] view_id The ID of the view that this update is for
|
||||||
/// @param[in] updates A map with the stable semantics node identifier as
|
/// @param[in] updates A map with the stable semantics node identifier as
|
||||||
/// key and the node properties as the value.
|
/// key and the node properties as the value.
|
||||||
/// @param[in] actions A map with the stable semantics node identifier as
|
/// @param[in] actions A map with the stable semantics node identifier as
|
||||||
/// key and the custom node action as the value.
|
/// key and the custom node action as the value.
|
||||||
///
|
///
|
||||||
virtual void OnEngineUpdateSemantics(
|
virtual void OnEngineUpdateSemantics(
|
||||||
|
int64_t view_id,
|
||||||
SemanticsNodeUpdates updates,
|
SemanticsNodeUpdates updates,
|
||||||
CustomAccessibilityActionUpdates actions) = 0;
|
CustomAccessibilityActionUpdates actions) = 0;
|
||||||
|
|
||||||
@ -819,12 +821,14 @@ class Engine final : public RuntimeDelegate, PointerDataDispatcher::Delegate {
|
|||||||
/// originates on the platform view and has been forwarded to the
|
/// originates on the platform view and has been forwarded to the
|
||||||
/// engine here on the UI task runner by the shell.
|
/// engine here on the UI task runner by the shell.
|
||||||
///
|
///
|
||||||
|
/// @param[in] view_id The identifier of the view.
|
||||||
/// @param[in] node_id The identifier of the accessibility node.
|
/// @param[in] node_id The identifier of the accessibility node.
|
||||||
/// @param[in] action The accessibility related action performed on the
|
/// @param[in] action The accessibility related action performed on the
|
||||||
/// node of the specified ID.
|
/// node of the specified ID.
|
||||||
/// @param[in] args Optional data that applies to the specified action.
|
/// @param[in] args Optional data that applies to the specified action.
|
||||||
///
|
///
|
||||||
void DispatchSemanticsAction(int node_id,
|
void DispatchSemanticsAction(int64_t view_id,
|
||||||
|
int node_id,
|
||||||
SemanticsAction action,
|
SemanticsAction action,
|
||||||
fml::MallocMapping args);
|
fml::MallocMapping args);
|
||||||
|
|
||||||
@ -1008,7 +1012,8 @@ class Engine final : public RuntimeDelegate, PointerDataDispatcher::Delegate {
|
|||||||
float device_pixel_ratio) override;
|
float device_pixel_ratio) override;
|
||||||
|
|
||||||
// |RuntimeDelegate|
|
// |RuntimeDelegate|
|
||||||
void UpdateSemantics(SemanticsNodeUpdates update,
|
void UpdateSemantics(int64_t view_id,
|
||||||
|
SemanticsNodeUpdates update,
|
||||||
CustomAccessibilityActionUpdates actions) override;
|
CustomAccessibilityActionUpdates actions) override;
|
||||||
|
|
||||||
// |RuntimeDelegate|
|
// |RuntimeDelegate|
|
||||||
|
@ -54,7 +54,7 @@ class MockDelegate : public Engine::Delegate {
|
|||||||
public:
|
public:
|
||||||
MOCK_METHOD(void,
|
MOCK_METHOD(void,
|
||||||
OnEngineUpdateSemantics,
|
OnEngineUpdateSemantics,
|
||||||
(SemanticsNodeUpdates, CustomAccessibilityActionUpdates),
|
(int64_t, SemanticsNodeUpdates, CustomAccessibilityActionUpdates),
|
||||||
(override));
|
(override));
|
||||||
MOCK_METHOD(void,
|
MOCK_METHOD(void,
|
||||||
OnEngineHandlePlatformMessage,
|
OnEngineHandlePlatformMessage,
|
||||||
|
@ -62,7 +62,7 @@ class MockDelegate : public Engine::Delegate {
|
|||||||
public:
|
public:
|
||||||
MOCK_METHOD(void,
|
MOCK_METHOD(void,
|
||||||
OnEngineUpdateSemantics,
|
OnEngineUpdateSemantics,
|
||||||
(SemanticsNodeUpdates, CustomAccessibilityActionUpdates),
|
(int64_t, SemanticsNodeUpdates, CustomAccessibilityActionUpdates),
|
||||||
(override));
|
(override));
|
||||||
MOCK_METHOD(void,
|
MOCK_METHOD(void,
|
||||||
OnEngineHandlePlatformMessage,
|
OnEngineHandlePlatformMessage,
|
||||||
@ -113,7 +113,7 @@ class MockRuntimeDelegate : public RuntimeDelegate {
|
|||||||
(override));
|
(override));
|
||||||
MOCK_METHOD(void,
|
MOCK_METHOD(void,
|
||||||
UpdateSemantics,
|
UpdateSemantics,
|
||||||
(SemanticsNodeUpdates, CustomAccessibilityActionUpdates),
|
(int64_t, SemanticsNodeUpdates, CustomAccessibilityActionUpdates),
|
||||||
(override));
|
(override));
|
||||||
MOCK_METHOD(void,
|
MOCK_METHOD(void,
|
||||||
HandlePlatformMessage,
|
HandlePlatformMessage,
|
||||||
|
@ -35,10 +35,11 @@ void PlatformView::DispatchPointerDataPacket(
|
|||||||
delegate_.OnPlatformViewDispatchPointerDataPacket(std::move(packet));
|
delegate_.OnPlatformViewDispatchPointerDataPacket(std::move(packet));
|
||||||
}
|
}
|
||||||
|
|
||||||
void PlatformView::DispatchSemanticsAction(int32_t node_id,
|
void PlatformView::DispatchSemanticsAction(int64_t view_id,
|
||||||
|
int32_t node_id,
|
||||||
SemanticsAction action,
|
SemanticsAction action,
|
||||||
fml::MallocMapping args) {
|
fml::MallocMapping args) {
|
||||||
delegate_.OnPlatformViewDispatchSemanticsAction(node_id, action,
|
delegate_.OnPlatformViewDispatchSemanticsAction(view_id, node_id, action,
|
||||||
std::move(args));
|
std::move(args));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -124,6 +125,7 @@ fml::WeakPtr<PlatformView> PlatformView::GetWeakPtr() const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void PlatformView::UpdateSemantics(
|
void PlatformView::UpdateSemantics(
|
||||||
|
int64_t view_id,
|
||||||
SemanticsNodeUpdates update, // NOLINT(performance-unnecessary-value-param)
|
SemanticsNodeUpdates update, // NOLINT(performance-unnecessary-value-param)
|
||||||
// NOLINTNEXTLINE(performance-unnecessary-value-param)
|
// NOLINTNEXTLINE(performance-unnecessary-value-param)
|
||||||
CustomAccessibilityActionUpdates actions) {}
|
CustomAccessibilityActionUpdates actions) {}
|
||||||
|
@ -188,6 +188,7 @@ class PlatformView {
|
|||||||
/// event must be forwarded to the running root isolate hosted
|
/// event must be forwarded to the running root isolate hosted
|
||||||
/// by the engine on the UI thread.
|
/// by the engine on the UI thread.
|
||||||
///
|
///
|
||||||
|
/// @param[in] view_id The identifier of the view that contains this node.
|
||||||
/// @param[in] node_id The identifier of the accessibility node.
|
/// @param[in] node_id The identifier of the accessibility node.
|
||||||
/// @param[in] action The accessibility related action performed on the
|
/// @param[in] action The accessibility related action performed on the
|
||||||
/// node of the specified ID.
|
/// node of the specified ID.
|
||||||
@ -195,6 +196,7 @@ class PlatformView {
|
|||||||
/// specified action.
|
/// specified action.
|
||||||
///
|
///
|
||||||
virtual void OnPlatformViewDispatchSemanticsAction(
|
virtual void OnPlatformViewDispatchSemanticsAction(
|
||||||
|
int64_t view_id,
|
||||||
int32_t node_id,
|
int32_t node_id,
|
||||||
SemanticsAction action,
|
SemanticsAction action,
|
||||||
fml::MallocMapping args) = 0;
|
fml::MallocMapping args) = 0;
|
||||||
@ -450,12 +452,14 @@ class PlatformView {
|
|||||||
/// @brief Used by embedders to dispatch an accessibility action to a
|
/// @brief Used by embedders to dispatch an accessibility action to a
|
||||||
/// running isolate hosted by the engine.
|
/// running isolate hosted by the engine.
|
||||||
///
|
///
|
||||||
|
/// @param[in] view_id The identifier of the view.
|
||||||
/// @param[in] node_id The identifier of the accessibility node on which to
|
/// @param[in] node_id The identifier of the accessibility node on which to
|
||||||
/// perform the action.
|
/// perform the action.
|
||||||
/// @param[in] action The action
|
/// @param[in] action The action
|
||||||
/// @param[in] args The arguments
|
/// @param[in] args The arguments
|
||||||
///
|
///
|
||||||
void DispatchSemanticsAction(int32_t node_id,
|
void DispatchSemanticsAction(int64_t view_id,
|
||||||
|
int32_t node_id,
|
||||||
SemanticsAction action,
|
SemanticsAction action,
|
||||||
fml::MallocMapping args);
|
fml::MallocMapping args);
|
||||||
|
|
||||||
@ -500,12 +504,14 @@ class PlatformView {
|
|||||||
/// @see SemanticsNode, SemticsNodeUpdates,
|
/// @see SemanticsNode, SemticsNodeUpdates,
|
||||||
/// CustomAccessibilityActionUpdates
|
/// CustomAccessibilityActionUpdates
|
||||||
///
|
///
|
||||||
|
/// @param[in] view_id The ID of the view that this update is for
|
||||||
/// @param[in] updates A map with the stable semantics node identifier as
|
/// @param[in] updates A map with the stable semantics node identifier as
|
||||||
/// key and the node properties as the value.
|
/// key and the node properties as the value.
|
||||||
/// @param[in] actions A map with the stable semantics node identifier as
|
/// @param[in] actions A map with the stable semantics node identifier as
|
||||||
/// key and the custom node action as the value.
|
/// key and the custom node action as the value.
|
||||||
///
|
///
|
||||||
virtual void UpdateSemantics(SemanticsNodeUpdates updates,
|
virtual void UpdateSemantics(int64_t view_id,
|
||||||
|
SemanticsNodeUpdates updates,
|
||||||
CustomAccessibilityActionUpdates actions);
|
CustomAccessibilityActionUpdates actions);
|
||||||
|
|
||||||
//----------------------------------------------------------------------------
|
//----------------------------------------------------------------------------
|
||||||
|
@ -1107,7 +1107,8 @@ void Shell::OnPlatformViewDispatchPointerDataPacket(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// |PlatformView::Delegate|
|
// |PlatformView::Delegate|
|
||||||
void Shell::OnPlatformViewDispatchSemanticsAction(int32_t node_id,
|
void Shell::OnPlatformViewDispatchSemanticsAction(int64_t view_id,
|
||||||
|
int32_t node_id,
|
||||||
SemanticsAction action,
|
SemanticsAction action,
|
||||||
fml::MallocMapping args) {
|
fml::MallocMapping args) {
|
||||||
FML_DCHECK(is_set_up_);
|
FML_DCHECK(is_set_up_);
|
||||||
@ -1115,10 +1116,11 @@ void Shell::OnPlatformViewDispatchSemanticsAction(int32_t node_id,
|
|||||||
|
|
||||||
fml::TaskRunner::RunNowAndFlushMessages(
|
fml::TaskRunner::RunNowAndFlushMessages(
|
||||||
task_runners_.GetUITaskRunner(),
|
task_runners_.GetUITaskRunner(),
|
||||||
fml::MakeCopyable([engine = engine_->GetWeakPtr(), node_id, action,
|
fml::MakeCopyable([engine = engine_->GetWeakPtr(), view_id, node_id,
|
||||||
args = std::move(args)]() mutable {
|
action, args = std::move(args)]() mutable {
|
||||||
if (engine) {
|
if (engine) {
|
||||||
engine->DispatchSemanticsAction(node_id, action, std::move(args));
|
engine->DispatchSemanticsAction(view_id, node_id, action,
|
||||||
|
std::move(args));
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
@ -1315,7 +1317,8 @@ void Shell::OnAnimatorDrawLastLayerTrees(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// |Engine::Delegate|
|
// |Engine::Delegate|
|
||||||
void Shell::OnEngineUpdateSemantics(SemanticsNodeUpdates update,
|
void Shell::OnEngineUpdateSemantics(int64_t view_id,
|
||||||
|
SemanticsNodeUpdates update,
|
||||||
CustomAccessibilityActionUpdates actions) {
|
CustomAccessibilityActionUpdates actions) {
|
||||||
FML_DCHECK(is_set_up_);
|
FML_DCHECK(is_set_up_);
|
||||||
FML_DCHECK(task_runners_.GetUITaskRunner()->RunsTasksOnCurrentThread());
|
FML_DCHECK(task_runners_.GetUITaskRunner()->RunsTasksOnCurrentThread());
|
||||||
@ -1323,9 +1326,9 @@ void Shell::OnEngineUpdateSemantics(SemanticsNodeUpdates update,
|
|||||||
task_runners_.GetPlatformTaskRunner()->RunNowOrPostTask(
|
task_runners_.GetPlatformTaskRunner()->RunNowOrPostTask(
|
||||||
task_runners_.GetPlatformTaskRunner(),
|
task_runners_.GetPlatformTaskRunner(),
|
||||||
[view = platform_view_->GetWeakPtr(), update = std::move(update),
|
[view = platform_view_->GetWeakPtr(), update = std::move(update),
|
||||||
actions = std::move(actions)] {
|
actions = std::move(actions), view_id = view_id] {
|
||||||
if (view) {
|
if (view) {
|
||||||
view->UpdateSemantics(update, actions);
|
view->UpdateSemantics(view_id, update, actions);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -605,7 +605,8 @@ class Shell final : public PlatformView::Delegate,
|
|||||||
std::unique_ptr<PointerDataPacket> packet) override;
|
std::unique_ptr<PointerDataPacket> packet) override;
|
||||||
|
|
||||||
// |PlatformView::Delegate|
|
// |PlatformView::Delegate|
|
||||||
void OnPlatformViewDispatchSemanticsAction(int32_t node_id,
|
void OnPlatformViewDispatchSemanticsAction(int64_t view_id,
|
||||||
|
int32_t node_id,
|
||||||
SemanticsAction action,
|
SemanticsAction action,
|
||||||
fml::MallocMapping args) override;
|
fml::MallocMapping args) override;
|
||||||
|
|
||||||
@ -666,6 +667,7 @@ class Shell final : public PlatformView::Delegate,
|
|||||||
|
|
||||||
// |Engine::Delegate|
|
// |Engine::Delegate|
|
||||||
void OnEngineUpdateSemantics(
|
void OnEngineUpdateSemantics(
|
||||||
|
int64_t view_id,
|
||||||
SemanticsNodeUpdates update,
|
SemanticsNodeUpdates update,
|
||||||
CustomAccessibilityActionUpdates actions) override;
|
CustomAccessibilityActionUpdates actions) override;
|
||||||
|
|
||||||
|
@ -66,10 +66,11 @@ void ShellTest::SendPlatformMessage(Shell* shell,
|
|||||||
}
|
}
|
||||||
|
|
||||||
void ShellTest::SendSemanticsAction(Shell* shell,
|
void ShellTest::SendSemanticsAction(Shell* shell,
|
||||||
|
int64_t view_id,
|
||||||
int32_t node_id,
|
int32_t node_id,
|
||||||
SemanticsAction action,
|
SemanticsAction action,
|
||||||
fml::MallocMapping args) {
|
fml::MallocMapping args) {
|
||||||
shell->OnPlatformViewDispatchSemanticsAction(node_id, action,
|
shell->OnPlatformViewDispatchSemanticsAction(view_id, node_id, action,
|
||||||
std::move(args));
|
std::move(args));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -92,6 +92,7 @@ class ShellTest : public FixtureTest {
|
|||||||
std::unique_ptr<PlatformMessage> message);
|
std::unique_ptr<PlatformMessage> message);
|
||||||
|
|
||||||
void SendSemanticsAction(Shell* shell,
|
void SendSemanticsAction(Shell* shell,
|
||||||
|
int64_t view_id,
|
||||||
int32_t node_id,
|
int32_t node_id,
|
||||||
SemanticsAction action,
|
SemanticsAction action,
|
||||||
fml::MallocMapping args);
|
fml::MallocMapping args);
|
||||||
|
@ -143,7 +143,10 @@ class MockPlatformViewDelegate : public PlatformView::Delegate {
|
|||||||
|
|
||||||
MOCK_METHOD(void,
|
MOCK_METHOD(void,
|
||||||
OnPlatformViewDispatchSemanticsAction,
|
OnPlatformViewDispatchSemanticsAction,
|
||||||
(int32_t id, SemanticsAction action, fml::MallocMapping args),
|
(int64_t view_id,
|
||||||
|
int32_t id,
|
||||||
|
SemanticsAction action,
|
||||||
|
fml::MallocMapping args),
|
||||||
(override));
|
(override));
|
||||||
|
|
||||||
MOCK_METHOD(void,
|
MOCK_METHOD(void,
|
||||||
@ -4341,7 +4344,7 @@ TEST_F(ShellTest, SemanticsActionsFlushMessageLoop) {
|
|||||||
CREATE_NATIVE_ENTRY([&](auto args) { latch.CountDown(); }));
|
CREATE_NATIVE_ENTRY([&](auto args) { latch.CountDown(); }));
|
||||||
|
|
||||||
task_runners.GetPlatformTaskRunner()->PostTask([&] {
|
task_runners.GetPlatformTaskRunner()->PostTask([&] {
|
||||||
SendSemanticsAction(shell.get(), 0, SemanticsAction::kTap,
|
SendSemanticsAction(shell.get(), 456, 0, SemanticsAction::kTap,
|
||||||
fml::MallocMapping(nullptr, 0));
|
fml::MallocMapping(nullptr, 0));
|
||||||
});
|
});
|
||||||
latch.Wait();
|
latch.Wait();
|
||||||
|
@ -44,6 +44,7 @@ namespace flutter {
|
|||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
static constexpr int kMinAPILevelHCPP = 34;
|
static constexpr int kMinAPILevelHCPP = 34;
|
||||||
|
static constexpr int64_t kImplicitViewId = 0;
|
||||||
|
|
||||||
AndroidContext::ContextSettings CreateContextSettings(
|
AndroidContext::ContextSettings CreateContextSettings(
|
||||||
const Settings& p_settings) {
|
const Settings& p_settings) {
|
||||||
@ -265,13 +266,15 @@ void PlatformViewAndroid::OnPreEngineRestart() const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void PlatformViewAndroid::DispatchSemanticsAction(JNIEnv* env,
|
void PlatformViewAndroid::DispatchSemanticsAction(JNIEnv* env,
|
||||||
jint id,
|
jint node_id,
|
||||||
jint action,
|
jint action,
|
||||||
jobject args,
|
jobject args,
|
||||||
jint args_position) {
|
jint args_position) {
|
||||||
|
// TODO(team-android): Remove implicit view assumption.
|
||||||
|
// https://github.com/flutter/flutter/issues/142845
|
||||||
if (env->IsSameObject(args, NULL)) {
|
if (env->IsSameObject(args, NULL)) {
|
||||||
PlatformView::DispatchSemanticsAction(
|
PlatformView::DispatchSemanticsAction(
|
||||||
id, static_cast<flutter::SemanticsAction>(action),
|
kImplicitViewId, node_id, static_cast<flutter::SemanticsAction>(action),
|
||||||
fml::MallocMapping());
|
fml::MallocMapping());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -280,12 +283,13 @@ void PlatformViewAndroid::DispatchSemanticsAction(JNIEnv* env,
|
|||||||
auto args_vector = fml::MallocMapping::Copy(args_data, args_position);
|
auto args_vector = fml::MallocMapping::Copy(args_data, args_position);
|
||||||
|
|
||||||
PlatformView::DispatchSemanticsAction(
|
PlatformView::DispatchSemanticsAction(
|
||||||
id, static_cast<flutter::SemanticsAction>(action),
|
kImplicitViewId, node_id, static_cast<flutter::SemanticsAction>(action),
|
||||||
std::move(args_vector));
|
std::move(args_vector));
|
||||||
}
|
}
|
||||||
|
|
||||||
// |PlatformView|
|
// |PlatformView|
|
||||||
void PlatformViewAndroid::UpdateSemantics(
|
void PlatformViewAndroid::UpdateSemantics(
|
||||||
|
int64_t view_id,
|
||||||
flutter::SemanticsNodeUpdates update,
|
flutter::SemanticsNodeUpdates update,
|
||||||
flutter::CustomAccessibilityActionUpdates actions) {
|
flutter::CustomAccessibilityActionUpdates actions) {
|
||||||
platform_view_android_delegate_.UpdateSemantics(update, actions);
|
platform_view_android_delegate_.UpdateSemantics(update, actions);
|
||||||
|
@ -133,6 +133,7 @@ class PlatformViewAndroid final : public PlatformView {
|
|||||||
|
|
||||||
// |PlatformView|
|
// |PlatformView|
|
||||||
void UpdateSemantics(
|
void UpdateSemantics(
|
||||||
|
int64_t view_id,
|
||||||
flutter::SemanticsNodeUpdates update,
|
flutter::SemanticsNodeUpdates update,
|
||||||
flutter::CustomAccessibilityActionUpdates actions) override;
|
flutter::CustomAccessibilityActionUpdates actions) override;
|
||||||
|
|
||||||
|
@ -35,7 +35,8 @@ class FakeDelegate : public PlatformView::Delegate {
|
|||||||
void OnPlatformViewDispatchPlatformMessage(std::unique_ptr<PlatformMessage> message) override {}
|
void OnPlatformViewDispatchPlatformMessage(std::unique_ptr<PlatformMessage> message) override {}
|
||||||
void OnPlatformViewDispatchPointerDataPacket(std::unique_ptr<PointerDataPacket> packet) override {
|
void OnPlatformViewDispatchPointerDataPacket(std::unique_ptr<PointerDataPacket> packet) override {
|
||||||
}
|
}
|
||||||
void OnPlatformViewDispatchSemanticsAction(int32_t id,
|
void OnPlatformViewDispatchSemanticsAction(int64_t view_id,
|
||||||
|
int32_t node_id,
|
||||||
SemanticsAction action,
|
SemanticsAction action,
|
||||||
fml::MallocMapping args) override {}
|
fml::MallocMapping args) override {}
|
||||||
void OnPlatformViewSetSemanticsEnabled(bool enabled) override {}
|
void OnPlatformViewSetSemanticsEnabled(bool enabled) override {}
|
||||||
|
@ -270,7 +270,8 @@ class FlutterPlatformViewsTestMockPlatformViewDelegate : public PlatformView::De
|
|||||||
void OnPlatformViewDispatchPlatformMessage(std::unique_ptr<PlatformMessage> message) override {}
|
void OnPlatformViewDispatchPlatformMessage(std::unique_ptr<PlatformMessage> message) override {}
|
||||||
void OnPlatformViewDispatchPointerDataPacket(std::unique_ptr<PointerDataPacket> packet) override {
|
void OnPlatformViewDispatchPointerDataPacket(std::unique_ptr<PointerDataPacket> packet) override {
|
||||||
}
|
}
|
||||||
void OnPlatformViewDispatchSemanticsAction(int32_t id,
|
void OnPlatformViewDispatchSemanticsAction(int64_t view_id,
|
||||||
|
int32_t node_id,
|
||||||
SemanticsAction action,
|
SemanticsAction action,
|
||||||
fml::MallocMapping args) override {}
|
fml::MallocMapping args) override {}
|
||||||
void OnPlatformViewSetSemanticsEnabled(bool enabled) override {}
|
void OnPlatformViewSetSemanticsEnabled(bool enabled) override {}
|
||||||
|
@ -12,6 +12,8 @@
|
|||||||
#import "flutter/shell/platform/darwin/ios/framework/Source/TextInputSemanticsObject.h"
|
#import "flutter/shell/platform/darwin/ios/framework/Source/TextInputSemanticsObject.h"
|
||||||
#import "flutter/shell/platform/darwin/ios/platform_view_ios.h"
|
#import "flutter/shell/platform/darwin/ios/platform_view_ios.h"
|
||||||
|
|
||||||
|
#include "flutter/common/constants.h"
|
||||||
|
|
||||||
#pragma GCC diagnostic error "-Wundeclared-selector"
|
#pragma GCC diagnostic error "-Wundeclared-selector"
|
||||||
|
|
||||||
FLUTTER_ASSERT_ARC
|
FLUTTER_ASSERT_ARC
|
||||||
@ -235,14 +237,20 @@ void AccessibilityBridge::UpdateSemantics(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void AccessibilityBridge::DispatchSemanticsAction(int32_t uid, flutter::SemanticsAction action) {
|
void AccessibilityBridge::DispatchSemanticsAction(int32_t node_uid,
|
||||||
platform_view_->DispatchSemanticsAction(uid, action, {});
|
flutter::SemanticsAction action) {
|
||||||
|
// TODO(team-ios): Remove implicit view assumption.
|
||||||
|
// https://github.com/flutter/flutter/issues/142845
|
||||||
|
platform_view_->DispatchSemanticsAction(kFlutterImplicitViewId, node_uid, action, {});
|
||||||
}
|
}
|
||||||
|
|
||||||
void AccessibilityBridge::DispatchSemanticsAction(int32_t uid,
|
void AccessibilityBridge::DispatchSemanticsAction(int32_t node_uid,
|
||||||
flutter::SemanticsAction action,
|
flutter::SemanticsAction action,
|
||||||
fml::MallocMapping args) {
|
fml::MallocMapping args) {
|
||||||
platform_view_->DispatchSemanticsAction(uid, action, std::move(args));
|
// TODO(team-ios): Remove implicit view assumption.
|
||||||
|
// https://github.com/flutter/flutter/issues/142845
|
||||||
|
platform_view_->DispatchSemanticsAction(kFlutterImplicitViewId, node_uid, action,
|
||||||
|
std::move(args));
|
||||||
}
|
}
|
||||||
|
|
||||||
static void ReplaceSemanticsObject(SemanticsObject* oldObject,
|
static void ReplaceSemanticsObject(SemanticsObject* oldObject,
|
||||||
|
@ -82,7 +82,8 @@ class MockDelegate : public PlatformView::Delegate {
|
|||||||
void OnPlatformViewDispatchPlatformMessage(std::unique_ptr<PlatformMessage> message) override {}
|
void OnPlatformViewDispatchPlatformMessage(std::unique_ptr<PlatformMessage> message) override {}
|
||||||
void OnPlatformViewDispatchPointerDataPacket(std::unique_ptr<PointerDataPacket> packet) override {
|
void OnPlatformViewDispatchPointerDataPacket(std::unique_ptr<PointerDataPacket> packet) override {
|
||||||
}
|
}
|
||||||
void OnPlatformViewDispatchSemanticsAction(int32_t id,
|
void OnPlatformViewDispatchSemanticsAction(int64_t view_id,
|
||||||
|
int32_t node_id,
|
||||||
SemanticsAction action,
|
SemanticsAction action,
|
||||||
fml::MallocMapping args) override {}
|
fml::MallocMapping args) override {}
|
||||||
void OnPlatformViewSetSemanticsEnabled(bool enabled) override {}
|
void OnPlatformViewSetSemanticsEnabled(bool enabled) override {}
|
||||||
|
@ -103,7 +103,8 @@ class PlatformViewIOS final : public PlatformView {
|
|||||||
void SetAccessibilityFeatures(int32_t flags) override;
|
void SetAccessibilityFeatures(int32_t flags) override;
|
||||||
|
|
||||||
// |PlatformView|
|
// |PlatformView|
|
||||||
void UpdateSemantics(flutter::SemanticsNodeUpdates update,
|
void UpdateSemantics(int64_t view_id,
|
||||||
|
flutter::SemanticsNodeUpdates update,
|
||||||
flutter::CustomAccessibilityActionUpdates actions) override;
|
flutter::CustomAccessibilityActionUpdates actions) override;
|
||||||
|
|
||||||
// |PlatformView|
|
// |PlatformView|
|
||||||
|
@ -181,7 +181,8 @@ void PlatformViewIOS::SetAccessibilityFeatures(int32_t flags) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// |PlatformView|
|
// |PlatformView|
|
||||||
void PlatformViewIOS::UpdateSemantics(flutter::SemanticsNodeUpdates update,
|
void PlatformViewIOS::UpdateSemantics(int64_t view_id,
|
||||||
|
flutter::SemanticsNodeUpdates update,
|
||||||
flutter::CustomAccessibilityActionUpdates actions) {
|
flutter::CustomAccessibilityActionUpdates actions) {
|
||||||
FML_DCHECK(owner_controller_);
|
FML_DCHECK(owner_controller_);
|
||||||
if (accessibility_bridge_) {
|
if (accessibility_bridge_) {
|
||||||
|
@ -332,6 +332,8 @@ AccessibilityBridgeMac::MacOSEventsFromAXEvent(ui::AXEventGenerator::Event event
|
|||||||
void AccessibilityBridgeMac::DispatchAccessibilityAction(ui::AXNode::AXID target,
|
void AccessibilityBridgeMac::DispatchAccessibilityAction(ui::AXNode::AXID target,
|
||||||
FlutterSemanticsAction action,
|
FlutterSemanticsAction action,
|
||||||
fml::MallocMapping data) {
|
fml::MallocMapping data) {
|
||||||
|
// TODO(mattkae): Remove implicit view assumption.
|
||||||
|
// https://github.com/flutter/flutter/issues/142845
|
||||||
NSCAssert(flutter_engine_, @"Flutter engine should not be deallocated");
|
NSCAssert(flutter_engine_, @"Flutter engine should not be deallocated");
|
||||||
NSCAssert(view_controller_.viewLoaded && view_controller_.view.window,
|
NSCAssert(view_controller_.viewLoaded && view_controller_.view.window,
|
||||||
@"The accessibility bridge should not receive accessibility actions if the flutter view"
|
@"The accessibility bridge should not receive accessibility actions if the flutter view"
|
||||||
|
@ -1843,9 +1843,9 @@ CreateEmbedderSemanticsUpdateCallbackV1(
|
|||||||
update_semantics_custom_action_callback,
|
update_semantics_custom_action_callback,
|
||||||
void* user_data) {
|
void* user_data) {
|
||||||
return [update_semantics_node_callback,
|
return [update_semantics_node_callback,
|
||||||
update_semantics_custom_action_callback,
|
update_semantics_custom_action_callback, user_data](
|
||||||
user_data](const flutter::SemanticsNodeUpdates& nodes,
|
int64_t view_id, const flutter::SemanticsNodeUpdates& nodes,
|
||||||
const flutter::CustomAccessibilityActionUpdates& actions) {
|
const flutter::CustomAccessibilityActionUpdates& actions) {
|
||||||
flutter::EmbedderSemanticsUpdate update{nodes, actions};
|
flutter::EmbedderSemanticsUpdate update{nodes, actions};
|
||||||
FlutterSemanticsUpdate* update_ptr = update.get();
|
FlutterSemanticsUpdate* update_ptr = update.get();
|
||||||
|
|
||||||
@ -1890,7 +1890,7 @@ CreateEmbedderSemanticsUpdateCallbackV2(
|
|||||||
FlutterUpdateSemanticsCallback update_semantics_callback,
|
FlutterUpdateSemanticsCallback update_semantics_callback,
|
||||||
void* user_data) {
|
void* user_data) {
|
||||||
return [update_semantics_callback, user_data](
|
return [update_semantics_callback, user_data](
|
||||||
const flutter::SemanticsNodeUpdates& nodes,
|
int64_t view_id, const flutter::SemanticsNodeUpdates& nodes,
|
||||||
const flutter::CustomAccessibilityActionUpdates& actions) {
|
const flutter::CustomAccessibilityActionUpdates& actions) {
|
||||||
flutter::EmbedderSemanticsUpdate update{nodes, actions};
|
flutter::EmbedderSemanticsUpdate update{nodes, actions};
|
||||||
|
|
||||||
@ -1905,9 +1905,9 @@ CreateEmbedderSemanticsUpdateCallbackV3(
|
|||||||
FlutterUpdateSemanticsCallback2 update_semantics_callback,
|
FlutterUpdateSemanticsCallback2 update_semantics_callback,
|
||||||
void* user_data) {
|
void* user_data) {
|
||||||
return [update_semantics_callback, user_data](
|
return [update_semantics_callback, user_data](
|
||||||
const flutter::SemanticsNodeUpdates& nodes,
|
int64_t view_id, const flutter::SemanticsNodeUpdates& nodes,
|
||||||
const flutter::CustomAccessibilityActionUpdates& actions) {
|
const flutter::CustomAccessibilityActionUpdates& actions) {
|
||||||
flutter::EmbedderSemanticsUpdate2 update{nodes, actions};
|
flutter::EmbedderSemanticsUpdate2 update{view_id, nodes, actions};
|
||||||
|
|
||||||
update_semantics_callback(update.get(), user_data);
|
update_semantics_callback(update.get(), user_data);
|
||||||
};
|
};
|
||||||
@ -3189,14 +3189,27 @@ FlutterEngineResult FlutterEngineDispatchSemanticsAction(
|
|||||||
FlutterSemanticsAction action,
|
FlutterSemanticsAction action,
|
||||||
const uint8_t* data,
|
const uint8_t* data,
|
||||||
size_t data_length) {
|
size_t data_length) {
|
||||||
|
FlutterSendSemanticsActionInfo info{
|
||||||
|
.struct_size = sizeof(FlutterSendSemanticsActionInfo),
|
||||||
|
.view_id = kFlutterImplicitViewId,
|
||||||
|
.node_id = node_id,
|
||||||
|
.action = action,
|
||||||
|
.data = data,
|
||||||
|
.data_length = data_length};
|
||||||
|
return FlutterEngineSendSemanticsAction(engine, &info);
|
||||||
|
}
|
||||||
|
|
||||||
|
FlutterEngineResult FlutterEngineSendSemanticsAction(
|
||||||
|
FLUTTER_API_SYMBOL(FlutterEngine) engine,
|
||||||
|
const FlutterSendSemanticsActionInfo* info) {
|
||||||
if (engine == nullptr) {
|
if (engine == nullptr) {
|
||||||
return LOG_EMBEDDER_ERROR(kInvalidArguments, "Invalid engine handle.");
|
return LOG_EMBEDDER_ERROR(kInvalidArguments, "Invalid engine handle.");
|
||||||
}
|
}
|
||||||
auto engine_action = static_cast<flutter::SemanticsAction>(action);
|
auto engine_action = static_cast<flutter::SemanticsAction>(info->action);
|
||||||
if (!reinterpret_cast<flutter::EmbedderEngine*>(engine)
|
if (!reinterpret_cast<flutter::EmbedderEngine*>(engine)
|
||||||
->DispatchSemanticsAction(
|
->DispatchSemanticsAction(
|
||||||
node_id, engine_action,
|
info->view_id, info->node_id, engine_action,
|
||||||
fml::MallocMapping::Copy(data, data_length))) {
|
fml::MallocMapping::Copy(info->data, info->data_length))) {
|
||||||
return LOG_EMBEDDER_ERROR(kInternalInconsistency,
|
return LOG_EMBEDDER_ERROR(kInternalInconsistency,
|
||||||
"Could not dispatch semantics action.");
|
"Could not dispatch semantics action.");
|
||||||
}
|
}
|
||||||
@ -3705,6 +3718,7 @@ FlutterEngineResult FlutterEngineGetProcAddresses(
|
|||||||
SET_PROC(UpdateAccessibilityFeatures,
|
SET_PROC(UpdateAccessibilityFeatures,
|
||||||
FlutterEngineUpdateAccessibilityFeatures);
|
FlutterEngineUpdateAccessibilityFeatures);
|
||||||
SET_PROC(DispatchSemanticsAction, FlutterEngineDispatchSemanticsAction);
|
SET_PROC(DispatchSemanticsAction, FlutterEngineDispatchSemanticsAction);
|
||||||
|
SET_PROC(SendSemanticsAction, FlutterEngineSendSemanticsAction);
|
||||||
SET_PROC(OnVsync, FlutterEngineOnVsync);
|
SET_PROC(OnVsync, FlutterEngineOnVsync);
|
||||||
SET_PROC(ReloadSystemFonts, FlutterEngineReloadSystemFonts);
|
SET_PROC(ReloadSystemFonts, FlutterEngineReloadSystemFonts);
|
||||||
SET_PROC(TraceEventDurationBegin, FlutterEngineTraceEventDurationBegin);
|
SET_PROC(TraceEventDurationBegin, FlutterEngineTraceEventDurationBegin);
|
||||||
|
@ -1687,6 +1687,8 @@ typedef struct {
|
|||||||
/// Array of semantics custom action pointers. Has length
|
/// Array of semantics custom action pointers. Has length
|
||||||
/// `custom_action_count`.
|
/// `custom_action_count`.
|
||||||
FlutterSemanticsCustomAction2** custom_actions;
|
FlutterSemanticsCustomAction2** custom_actions;
|
||||||
|
// The ID of the view that this update is associated with.
|
||||||
|
FlutterViewId view_id;
|
||||||
} FlutterSemanticsUpdate2;
|
} FlutterSemanticsUpdate2;
|
||||||
|
|
||||||
typedef void (*FlutterUpdateSemanticsNodeCallback)(
|
typedef void (*FlutterUpdateSemanticsNodeCallback)(
|
||||||
@ -2645,6 +2647,27 @@ typedef struct {
|
|||||||
int64_t engine_id;
|
int64_t engine_id;
|
||||||
} FlutterProjectArgs;
|
} FlutterProjectArgs;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
/// The size of this struct. Must be
|
||||||
|
/// sizeof(FlutterSendSemanticsActionInfo).
|
||||||
|
size_t struct_size;
|
||||||
|
|
||||||
|
/// The ID of the view that includes the node.
|
||||||
|
FlutterViewId view_id;
|
||||||
|
|
||||||
|
/// The semantics node identifier.
|
||||||
|
uint64_t node_id;
|
||||||
|
|
||||||
|
/// The semantics action.
|
||||||
|
FlutterSemanticsAction action;
|
||||||
|
|
||||||
|
/// Data associated with the action.
|
||||||
|
const uint8_t* data;
|
||||||
|
|
||||||
|
/// The data length.
|
||||||
|
size_t data_length;
|
||||||
|
} FlutterSendSemanticsActionInfo;
|
||||||
|
|
||||||
#ifndef FLUTTER_ENGINE_NO_PROTOTYPES
|
#ifndef FLUTTER_ENGINE_NO_PROTOTYPES
|
||||||
|
|
||||||
// NOLINTBEGIN(google-objc-function-naming)
|
// NOLINTBEGIN(google-objc-function-naming)
|
||||||
@ -3078,7 +3101,10 @@ FlutterEngineResult FlutterEngineUpdateAccessibilityFeatures(
|
|||||||
FlutterAccessibilityFeature features);
|
FlutterAccessibilityFeature features);
|
||||||
|
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
/// @brief Dispatch a semantics action to the specified semantics node.
|
/// @brief Dispatch a semantics action to the specified semantics node
|
||||||
|
/// in the implicit view.
|
||||||
|
///
|
||||||
|
/// @deprecated Use `FlutterEngineSendSemanticsAction` instead.
|
||||||
///
|
///
|
||||||
/// @param[in] engine A running engine instance.
|
/// @param[in] engine A running engine instance.
|
||||||
/// @param[in] node_id The semantics node identifier.
|
/// @param[in] node_id The semantics node identifier.
|
||||||
@ -3096,6 +3122,22 @@ FlutterEngineResult FlutterEngineDispatchSemanticsAction(
|
|||||||
const uint8_t* data,
|
const uint8_t* data,
|
||||||
size_t data_length);
|
size_t data_length);
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
/// @brief Dispatch a semantics action to the specified semantics node
|
||||||
|
/// within a specific view.
|
||||||
|
///
|
||||||
|
/// @param[in] engine A running engine instance.
|
||||||
|
/// @param[in] info The dispatch semantics on view arguments.
|
||||||
|
/// This can be deallocated once
|
||||||
|
/// |FlutterEngineSendSemanticsAction| returns.
|
||||||
|
///
|
||||||
|
/// @return The result of the call.
|
||||||
|
///
|
||||||
|
FLUTTER_EXPORT
|
||||||
|
FlutterEngineResult FlutterEngineSendSemanticsAction(
|
||||||
|
FLUTTER_API_SYMBOL(FlutterEngine) engine,
|
||||||
|
const FlutterSendSemanticsActionInfo* info);
|
||||||
|
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
/// @brief Notify the engine that a vsync event occurred. A baton passed to
|
/// @brief Notify the engine that a vsync event occurred. A baton passed to
|
||||||
/// the platform via the vsync callback must be returned. This call
|
/// the platform via the vsync callback must be returned. This call
|
||||||
@ -3483,6 +3525,9 @@ typedef FlutterEngineResult (*FlutterEngineDispatchSemanticsActionFnPtr)(
|
|||||||
FlutterSemanticsAction action,
|
FlutterSemanticsAction action,
|
||||||
const uint8_t* data,
|
const uint8_t* data,
|
||||||
size_t data_length);
|
size_t data_length);
|
||||||
|
typedef FlutterEngineResult (*FlutterEngineSendSemanticsActionFnPtr)(
|
||||||
|
FLUTTER_API_SYMBOL(FlutterEngine) engine,
|
||||||
|
const FlutterSendSemanticsActionInfo* info);
|
||||||
typedef FlutterEngineResult (*FlutterEngineOnVsyncFnPtr)(
|
typedef FlutterEngineResult (*FlutterEngineOnVsyncFnPtr)(
|
||||||
FLUTTER_API_SYMBOL(FlutterEngine) engine,
|
FLUTTER_API_SYMBOL(FlutterEngine) engine,
|
||||||
intptr_t baton,
|
intptr_t baton,
|
||||||
@ -3585,6 +3630,7 @@ typedef struct {
|
|||||||
FlutterEngineAddViewFnPtr AddView;
|
FlutterEngineAddViewFnPtr AddView;
|
||||||
FlutterEngineRemoveViewFnPtr RemoveView;
|
FlutterEngineRemoveViewFnPtr RemoveView;
|
||||||
FlutterEngineSendViewFocusEventFnPtr SendViewFocusEvent;
|
FlutterEngineSendViewFocusEventFnPtr SendViewFocusEvent;
|
||||||
|
FlutterEngineSendSemanticsActionFnPtr SendSemanticsAction;
|
||||||
} FlutterEngineProcTable;
|
} FlutterEngineProcTable;
|
||||||
|
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
|
@ -235,7 +235,8 @@ bool EmbedderEngine::SetAccessibilityFeatures(int32_t flags) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool EmbedderEngine::DispatchSemanticsAction(int node_id,
|
bool EmbedderEngine::DispatchSemanticsAction(int64_t view_id,
|
||||||
|
int node_id,
|
||||||
flutter::SemanticsAction action,
|
flutter::SemanticsAction action,
|
||||||
fml::MallocMapping args) {
|
fml::MallocMapping args) {
|
||||||
if (!IsValid()) {
|
if (!IsValid()) {
|
||||||
@ -245,7 +246,8 @@ bool EmbedderEngine::DispatchSemanticsAction(int node_id,
|
|||||||
if (!platform_view) {
|
if (!platform_view) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
platform_view->DispatchSemanticsAction(node_id, action, std::move(args));
|
platform_view->DispatchSemanticsAction(view_id, node_id, action,
|
||||||
|
std::move(args));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -68,7 +68,8 @@ class EmbedderEngine {
|
|||||||
|
|
||||||
bool SetAccessibilityFeatures(int32_t flags);
|
bool SetAccessibilityFeatures(int32_t flags);
|
||||||
|
|
||||||
bool DispatchSemanticsAction(int node_id,
|
bool DispatchSemanticsAction(int64_t view_id,
|
||||||
|
int node_id,
|
||||||
flutter::SemanticsAction action,
|
flutter::SemanticsAction action,
|
||||||
fml::MallocMapping args);
|
fml::MallocMapping args);
|
||||||
|
|
||||||
|
@ -88,6 +88,7 @@ void EmbedderSemanticsUpdate::AddAction(
|
|||||||
EmbedderSemanticsUpdate::~EmbedderSemanticsUpdate() {}
|
EmbedderSemanticsUpdate::~EmbedderSemanticsUpdate() {}
|
||||||
|
|
||||||
EmbedderSemanticsUpdate2::EmbedderSemanticsUpdate2(
|
EmbedderSemanticsUpdate2::EmbedderSemanticsUpdate2(
|
||||||
|
int64_t view_id,
|
||||||
const SemanticsNodeUpdates& nodes,
|
const SemanticsNodeUpdates& nodes,
|
||||||
const CustomAccessibilityActionUpdates& actions) {
|
const CustomAccessibilityActionUpdates& actions) {
|
||||||
nodes_.reserve(nodes.size());
|
nodes_.reserve(nodes.size());
|
||||||
@ -111,13 +112,12 @@ EmbedderSemanticsUpdate2::EmbedderSemanticsUpdate2(
|
|||||||
action_pointers_.push_back(&actions_[i]);
|
action_pointers_.push_back(&actions_[i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
update_ = {
|
update_ = {.struct_size = sizeof(FlutterSemanticsUpdate2),
|
||||||
.struct_size = sizeof(FlutterSemanticsUpdate2),
|
.node_count = node_pointers_.size(),
|
||||||
.node_count = node_pointers_.size(),
|
.nodes = node_pointers_.data(),
|
||||||
.nodes = node_pointers_.data(),
|
.custom_action_count = action_pointers_.size(),
|
||||||
.custom_action_count = action_pointers_.size(),
|
.custom_actions = action_pointers_.data(),
|
||||||
.custom_actions = action_pointers_.data(),
|
.view_id = view_id};
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
EmbedderSemanticsUpdate2::~EmbedderSemanticsUpdate2() {}
|
EmbedderSemanticsUpdate2::~EmbedderSemanticsUpdate2() {}
|
||||||
|
@ -47,7 +47,8 @@ class EmbedderSemanticsUpdate {
|
|||||||
// and the temporary embedder-specific objects are automatically cleaned up.
|
// and the temporary embedder-specific objects are automatically cleaned up.
|
||||||
class EmbedderSemanticsUpdate2 {
|
class EmbedderSemanticsUpdate2 {
|
||||||
public:
|
public:
|
||||||
EmbedderSemanticsUpdate2(const SemanticsNodeUpdates& nodes,
|
EmbedderSemanticsUpdate2(int64_t view_id,
|
||||||
|
const SemanticsNodeUpdates& nodes,
|
||||||
const CustomAccessibilityActionUpdates& actions);
|
const CustomAccessibilityActionUpdates& actions);
|
||||||
|
|
||||||
~EmbedderSemanticsUpdate2();
|
~EmbedderSemanticsUpdate2();
|
||||||
|
@ -1632,3 +1632,70 @@ void testSendViewFocusChangeRequest() {
|
|||||||
direction: ViewFocusDirection.backward,
|
direction: ViewFocusDirection.backward,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@pragma('vm:entry-point')
|
||||||
|
// ignore: non_constant_identifier_names
|
||||||
|
Future<void> a11y_main_multi_view() async {
|
||||||
|
// 1: Return initial state (semantics disabled).
|
||||||
|
notifySemanticsEnabled(PlatformDispatcher.instance.semanticsEnabled);
|
||||||
|
|
||||||
|
// 2: Add the first view (implicitly handled by PlatformDispatcher).
|
||||||
|
// 3: Add the second view (implicitly handled by PlatformDispatcher).
|
||||||
|
|
||||||
|
// 4: Await semantics enabled from embedder.
|
||||||
|
await semanticsChanged;
|
||||||
|
notifySemanticsEnabled(PlatformDispatcher.instance.semanticsEnabled);
|
||||||
|
|
||||||
|
// 5: Return initial state of accessibility features.
|
||||||
|
notifyAccessibilityFeatures(PlatformDispatcher.instance.accessibilityFeatures.reduceMotion);
|
||||||
|
|
||||||
|
// 6: Fire semantics updates.
|
||||||
|
SemanticsUpdateBuilder createForView(FlutterView view) {
|
||||||
|
return SemanticsUpdateBuilder()..updateNode(
|
||||||
|
id: view.viewId + 1, // For simplicity, give each node an id of viewId + 1
|
||||||
|
identifier: '',
|
||||||
|
label: 'A: root',
|
||||||
|
labelAttributes: <StringAttribute>[],
|
||||||
|
rect: const Rect.fromLTRB(0.0, 0.0, 10.0, 10.0),
|
||||||
|
transform: kTestTransform,
|
||||||
|
childrenInTraversalOrder: Int32List.fromList(<int>[84, 96]),
|
||||||
|
childrenInHitTestOrder: Int32List.fromList(<int>[96, 84]),
|
||||||
|
actions: 0,
|
||||||
|
flags: 0,
|
||||||
|
maxValueLength: 0,
|
||||||
|
currentValueLength: 0,
|
||||||
|
textSelectionBase: 0,
|
||||||
|
textSelectionExtent: 0,
|
||||||
|
platformViewId: 0,
|
||||||
|
scrollChildren: 0,
|
||||||
|
scrollIndex: 0,
|
||||||
|
scrollPosition: 0.0,
|
||||||
|
scrollExtentMax: 0.0,
|
||||||
|
scrollExtentMin: 0.0,
|
||||||
|
elevation: 0.0,
|
||||||
|
thickness: 0.0,
|
||||||
|
hint: '',
|
||||||
|
hintAttributes: <StringAttribute>[],
|
||||||
|
value: '',
|
||||||
|
valueAttributes: <StringAttribute>[],
|
||||||
|
increasedValue: '',
|
||||||
|
increasedValueAttributes: <StringAttribute>[],
|
||||||
|
decreasedValue: '',
|
||||||
|
decreasedValueAttributes: <StringAttribute>[],
|
||||||
|
tooltip: 'tooltip',
|
||||||
|
textDirection: TextDirection.ltr,
|
||||||
|
additionalActions: Int32List(0),
|
||||||
|
controlsNodes: null,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (final view in PlatformDispatcher.instance.views) {
|
||||||
|
view.updateSemantics(createForView(view).build());
|
||||||
|
}
|
||||||
|
|
||||||
|
signalNativeTest();
|
||||||
|
|
||||||
|
// 7: Await semantics disabled from embedder.
|
||||||
|
await semanticsChanged;
|
||||||
|
notifySemanticsEnabled(PlatformDispatcher.instance.semanticsEnabled);
|
||||||
|
}
|
||||||
|
@ -113,11 +113,12 @@ PlatformViewEmbedder::PlatformViewEmbedder(
|
|||||||
PlatformViewEmbedder::~PlatformViewEmbedder() = default;
|
PlatformViewEmbedder::~PlatformViewEmbedder() = default;
|
||||||
|
|
||||||
void PlatformViewEmbedder::UpdateSemantics(
|
void PlatformViewEmbedder::UpdateSemantics(
|
||||||
|
int64_t view_id,
|
||||||
flutter::SemanticsNodeUpdates update,
|
flutter::SemanticsNodeUpdates update,
|
||||||
flutter::CustomAccessibilityActionUpdates actions) {
|
flutter::CustomAccessibilityActionUpdates actions) {
|
||||||
if (platform_dispatch_table_.update_semantics_callback != nullptr) {
|
if (platform_dispatch_table_.update_semantics_callback != nullptr) {
|
||||||
platform_dispatch_table_.update_semantics_callback(std::move(update),
|
platform_dispatch_table_.update_semantics_callback(
|
||||||
std::move(actions));
|
view_id, std::move(update), std::move(actions));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -36,7 +36,8 @@ namespace flutter {
|
|||||||
class PlatformViewEmbedder final : public PlatformView {
|
class PlatformViewEmbedder final : public PlatformView {
|
||||||
public:
|
public:
|
||||||
using UpdateSemanticsCallback =
|
using UpdateSemanticsCallback =
|
||||||
std::function<void(flutter::SemanticsNodeUpdates update,
|
std::function<void(int64_t view_id,
|
||||||
|
flutter::SemanticsNodeUpdates update,
|
||||||
flutter::CustomAccessibilityActionUpdates actions)>;
|
flutter::CustomAccessibilityActionUpdates actions)>;
|
||||||
using PlatformMessageResponseCallback =
|
using PlatformMessageResponseCallback =
|
||||||
std::function<void(std::unique_ptr<PlatformMessage>)>;
|
std::function<void(std::unique_ptr<PlatformMessage>)>;
|
||||||
@ -104,6 +105,7 @@ class PlatformViewEmbedder final : public PlatformView {
|
|||||||
|
|
||||||
// |PlatformView|
|
// |PlatformView|
|
||||||
void UpdateSemantics(
|
void UpdateSemantics(
|
||||||
|
int64_t view_id,
|
||||||
flutter::SemanticsNodeUpdates update,
|
flutter::SemanticsNodeUpdates update,
|
||||||
flutter::CustomAccessibilityActionUpdates actions) override;
|
flutter::CustomAccessibilityActionUpdates actions) override;
|
||||||
|
|
||||||
|
@ -54,7 +54,10 @@ class MockDelegate : public PlatformView::Delegate {
|
|||||||
(override));
|
(override));
|
||||||
MOCK_METHOD(void,
|
MOCK_METHOD(void,
|
||||||
OnPlatformViewDispatchSemanticsAction,
|
OnPlatformViewDispatchSemanticsAction,
|
||||||
(int32_t id, SemanticsAction action, fml::MallocMapping args),
|
(int64_t view_id,
|
||||||
|
int32_t node_id,
|
||||||
|
SemanticsAction action,
|
||||||
|
fml::MallocMapping args),
|
||||||
(override));
|
(override));
|
||||||
MOCK_METHOD(void,
|
MOCK_METHOD(void,
|
||||||
OnPlatformViewSetSemanticsEnabled,
|
OnPlatformViewSetSemanticsEnabled,
|
||||||
|
@ -774,6 +774,184 @@ TEST_F(EmbedderA11yTest, A11yTreeIsConsistentUsingV1Callbacks) {
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_F(EmbedderA11yTest, A11yTreesAreConsistentWithMultipleViews) {
|
||||||
|
#if defined(OS_FUCHSIA) && (FLUTTER_RUNTIME_MODE != FLUTTER_RUNTIME_MODE_DEBUG)
|
||||||
|
GTEST_SKIP() << "Dart_LoadELF is not implemented on Fuchsia.";
|
||||||
|
#else
|
||||||
|
auto& context = GetEmbedderContext<EmbedderTestContextSoftware>();
|
||||||
|
|
||||||
|
fml::AutoResetWaitableEvent signal_native_latch;
|
||||||
|
|
||||||
|
// Called by the Dart text fixture on the UI thread to signal that the C++
|
||||||
|
// unittest should resume.
|
||||||
|
context.AddNativeCallback(
|
||||||
|
"SignalNativeTest",
|
||||||
|
CREATE_NATIVE_ENTRY(([&signal_native_latch](Dart_NativeArguments) {
|
||||||
|
signal_native_latch.Signal();
|
||||||
|
})));
|
||||||
|
|
||||||
|
// Called by test fixture on UI thread to pass data back to this test.
|
||||||
|
NativeEntry notify_semantics_enabled_callback;
|
||||||
|
context.AddNativeCallback(
|
||||||
|
"NotifySemanticsEnabled",
|
||||||
|
CREATE_NATIVE_ENTRY(
|
||||||
|
([¬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 testing
|
||||||
} // namespace flutter
|
} // namespace flutter
|
||||||
|
|
||||||
|
@ -42,6 +42,7 @@
|
|||||||
|
|
||||||
namespace flutter_runner {
|
namespace flutter_runner {
|
||||||
namespace {
|
namespace {
|
||||||
|
constexpr static int64_t kImplicitViewId = 0;
|
||||||
|
|
||||||
zx_koid_t GetKoid(const fuchsia::ui::views::ViewRef& view_ref) {
|
zx_koid_t GetKoid(const fuchsia::ui::views::ViewRef& view_ref) {
|
||||||
zx_handle_t handle = view_ref.reference.get();
|
zx_handle_t handle = view_ref.reference.get();
|
||||||
@ -393,7 +394,10 @@ void Engine::Initialize(
|
|||||||
auto platform_view = shell_->GetPlatformView();
|
auto platform_view = shell_->GetPlatformView();
|
||||||
|
|
||||||
if (platform_view) {
|
if (platform_view) {
|
||||||
platform_view->DispatchSemanticsAction(node_id, action, {});
|
// TODO(fuchsia): Remove implicit view assumption.
|
||||||
|
// https://github.com/flutter/flutter/issues/142845
|
||||||
|
platform_view->DispatchSemanticsAction(kImplicitViewId, node_id,
|
||||||
|
action, {});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -676,6 +676,7 @@ void PlatformView::SetSemanticsEnabled(bool enabled) {
|
|||||||
|
|
||||||
// |flutter::PlatformView|
|
// |flutter::PlatformView|
|
||||||
void PlatformView::UpdateSemantics(
|
void PlatformView::UpdateSemantics(
|
||||||
|
int64_t view_id,
|
||||||
flutter::SemanticsNodeUpdates update,
|
flutter::SemanticsNodeUpdates update,
|
||||||
flutter::CustomAccessibilityActionUpdates actions) {
|
flutter::CustomAccessibilityActionUpdates actions) {
|
||||||
const float pixel_ratio =
|
const float pixel_ratio =
|
||||||
|
@ -134,6 +134,7 @@ class PlatformView : public flutter::PlatformView {
|
|||||||
|
|
||||||
// |flutter::PlatformView|
|
// |flutter::PlatformView|
|
||||||
void UpdateSemantics(
|
void UpdateSemantics(
|
||||||
|
int64_t view_id,
|
||||||
flutter::SemanticsNodeUpdates update,
|
flutter::SemanticsNodeUpdates update,
|
||||||
flutter::CustomAccessibilityActionUpdates actions) override;
|
flutter::CustomAccessibilityActionUpdates actions) override;
|
||||||
|
|
||||||
|
@ -121,7 +121,8 @@ class MockPlatformViewDelegate : public flutter::PlatformView::Delegate {
|
|||||||
std::unique_ptr<flutter::KeyDataPacket> packet,
|
std::unique_ptr<flutter::KeyDataPacket> packet,
|
||||||
std::function<void(bool)> callback) {}
|
std::function<void(bool)> callback) {}
|
||||||
// |flutter::PlatformView::Delegate|
|
// |flutter::PlatformView::Delegate|
|
||||||
void OnPlatformViewDispatchSemanticsAction(int32_t id,
|
void OnPlatformViewDispatchSemanticsAction(int64_t view_id,
|
||||||
|
int32_t node_id,
|
||||||
flutter::SemanticsAction action,
|
flutter::SemanticsAction action,
|
||||||
fml::MallocMapping args) {}
|
fml::MallocMapping args) {}
|
||||||
// |flutter::PlatformView::Delegate|
|
// |flutter::PlatformView::Delegate|
|
||||||
|
@ -68,10 +68,13 @@ struct FlAccessibleNodePrivate {
|
|||||||
// Weak reference to the engine this node is created for.
|
// Weak reference to the engine this node is created for.
|
||||||
FlEngine* engine;
|
FlEngine* engine;
|
||||||
|
|
||||||
|
/// The unique identifier of the view to which this node belongs.
|
||||||
|
FlutterViewId view_id;
|
||||||
|
|
||||||
// Weak reference to the parent node of this one or %NULL.
|
// Weak reference to the parent node of this one or %NULL.
|
||||||
AtkObject* parent;
|
AtkObject* parent;
|
||||||
|
|
||||||
int32_t id;
|
int32_t node_id;
|
||||||
gchar* name;
|
gchar* name;
|
||||||
gint index;
|
gint index;
|
||||||
gint x, y, width, height;
|
gint x, y, width, height;
|
||||||
@ -81,7 +84,7 @@ struct FlAccessibleNodePrivate {
|
|||||||
FlutterSemanticsFlag flags;
|
FlutterSemanticsFlag flags;
|
||||||
};
|
};
|
||||||
|
|
||||||
enum { PROP_0, PROP_ENGINE, PROP_ID, PROP_LAST };
|
enum { PROP_0, PROP_ENGINE, PROP_VIEW_ID, PROP_ID, PROP_LAST };
|
||||||
|
|
||||||
#define FL_ACCESSIBLE_NODE_GET_PRIVATE(node) \
|
#define FL_ACCESSIBLE_NODE_GET_PRIVATE(node) \
|
||||||
((FlAccessibleNodePrivate*)fl_accessible_node_get_instance_private( \
|
((FlAccessibleNodePrivate*)fl_accessible_node_get_instance_private( \
|
||||||
@ -151,8 +154,11 @@ static void fl_accessible_node_set_property(GObject* object,
|
|||||||
g_object_add_weak_pointer(object,
|
g_object_add_weak_pointer(object,
|
||||||
reinterpret_cast<gpointer*>(&priv->engine));
|
reinterpret_cast<gpointer*>(&priv->engine));
|
||||||
break;
|
break;
|
||||||
|
case PROP_VIEW_ID:
|
||||||
|
priv->view_id = g_value_get_int64(value);
|
||||||
|
break;
|
||||||
case PROP_ID:
|
case PROP_ID:
|
||||||
priv->id = g_value_get_int(value);
|
priv->node_id = g_value_get_int(value);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
|
G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
|
||||||
@ -417,7 +423,8 @@ static void fl_accessible_node_perform_action_impl(
|
|||||||
FlutterSemanticsAction action,
|
FlutterSemanticsAction action,
|
||||||
GBytes* data) {
|
GBytes* data) {
|
||||||
FlAccessibleNodePrivate* priv = FL_ACCESSIBLE_NODE_GET_PRIVATE(self);
|
FlAccessibleNodePrivate* priv = FL_ACCESSIBLE_NODE_GET_PRIVATE(self);
|
||||||
fl_engine_dispatch_semantics_action(priv->engine, priv->id, action, data);
|
fl_engine_dispatch_semantics_action(priv->engine, priv->view_id,
|
||||||
|
priv->node_id, action, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void fl_accessible_node_class_init(FlAccessibleNodeClass* klass) {
|
static void fl_accessible_node_class_init(FlAccessibleNodeClass* klass) {
|
||||||
@ -453,10 +460,16 @@ static void fl_accessible_node_class_init(FlAccessibleNodeClass* klass) {
|
|||||||
"engine", "engine", "Flutter engine", fl_engine_get_type(),
|
"engine", "engine", "Flutter engine", fl_engine_get_type(),
|
||||||
static_cast<GParamFlags>(G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY |
|
static_cast<GParamFlags>(G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY |
|
||||||
G_PARAM_STATIC_STRINGS)));
|
G_PARAM_STATIC_STRINGS)));
|
||||||
|
g_object_class_install_property(
|
||||||
|
G_OBJECT_CLASS(klass), PROP_VIEW_ID,
|
||||||
|
g_param_spec_int64(
|
||||||
|
"view-id", "view-id", "View ID that this node belongs to", 0,
|
||||||
|
G_MAXINT64, 0,
|
||||||
|
static_cast<GParamFlags>(G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY)));
|
||||||
g_object_class_install_property(
|
g_object_class_install_property(
|
||||||
G_OBJECT_CLASS(klass), PROP_ID,
|
G_OBJECT_CLASS(klass), PROP_ID,
|
||||||
g_param_spec_int(
|
g_param_spec_int(
|
||||||
"id", "id", "Accessibility node ID", 0, G_MAXINT, 0,
|
"node-id", "node-id", "Accessibility node ID", 0, G_MAXINT, 0,
|
||||||
static_cast<GParamFlags>(G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY |
|
static_cast<GParamFlags>(G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY |
|
||||||
G_PARAM_STATIC_STRINGS)));
|
G_PARAM_STATIC_STRINGS)));
|
||||||
}
|
}
|
||||||
@ -479,9 +492,12 @@ static void fl_accessible_node_init(FlAccessibleNode* self) {
|
|||||||
priv->children = g_ptr_array_new_with_free_func(g_object_unref);
|
priv->children = g_ptr_array_new_with_free_func(g_object_unref);
|
||||||
}
|
}
|
||||||
|
|
||||||
FlAccessibleNode* fl_accessible_node_new(FlEngine* engine, int32_t id) {
|
FlAccessibleNode* fl_accessible_node_new(FlEngine* engine,
|
||||||
FlAccessibleNode* self = FL_ACCESSIBLE_NODE(g_object_new(
|
FlutterViewId view_id,
|
||||||
fl_accessible_node_get_type(), "engine", engine, "id", id, nullptr));
|
int32_t node_id) {
|
||||||
|
FlAccessibleNode* self = FL_ACCESSIBLE_NODE(
|
||||||
|
g_object_new(fl_accessible_node_get_type(), "engine", engine, "view-id",
|
||||||
|
view_id, "node-id", node_id, nullptr));
|
||||||
return self;
|
return self;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -55,14 +55,17 @@ struct _FlAccessibleNodeClass {
|
|||||||
/**
|
/**
|
||||||
* fl_accessible_node_new:
|
* fl_accessible_node_new:
|
||||||
* @engine: the #FlEngine this node came from.
|
* @engine: the #FlEngine this node came from.
|
||||||
* @id: the semantics node ID this object represents.
|
* @view_id: the view ID this object represents.
|
||||||
|
* @node_id: the semantics node ID this object represents.
|
||||||
*
|
*
|
||||||
* Creates a new accessibility object that exposes Flutter accessibility
|
* Creates a new accessibility object that exposes Flutter accessibility
|
||||||
* information to ATK.
|
* information to ATK.
|
||||||
*
|
*
|
||||||
* Returns: a new #FlAccessibleNode.
|
* Returns: a new #FlAccessibleNode.
|
||||||
*/
|
*/
|
||||||
FlAccessibleNode* fl_accessible_node_new(FlEngine* engine, int32_t id);
|
FlAccessibleNode* fl_accessible_node_new(FlEngine* engine,
|
||||||
|
FlutterViewId view_id,
|
||||||
|
int32_t node_id);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* fl_accessible_node_set_parent:
|
* fl_accessible_node_set_parent:
|
||||||
|
@ -12,10 +12,13 @@ TEST(FlAccessibleNodeTest, BuildTree) {
|
|||||||
g_autoptr(FlDartProject) project = fl_dart_project_new();
|
g_autoptr(FlDartProject) project = fl_dart_project_new();
|
||||||
g_autoptr(FlEngine) engine = fl_engine_new(project);
|
g_autoptr(FlEngine) engine = fl_engine_new(project);
|
||||||
|
|
||||||
g_autoptr(FlAccessibleNode) root = fl_accessible_node_new(engine, 0);
|
int64_t view_id = 123;
|
||||||
g_autoptr(FlAccessibleNode) child1 = fl_accessible_node_new(engine, 1);
|
g_autoptr(FlAccessibleNode) root = fl_accessible_node_new(engine, view_id, 0);
|
||||||
|
g_autoptr(FlAccessibleNode) child1 =
|
||||||
|
fl_accessible_node_new(engine, view_id, 1);
|
||||||
fl_accessible_node_set_parent(child1, ATK_OBJECT(root), 0);
|
fl_accessible_node_set_parent(child1, ATK_OBJECT(root), 0);
|
||||||
g_autoptr(FlAccessibleNode) child2 = fl_accessible_node_new(engine, 1);
|
g_autoptr(FlAccessibleNode) child2 =
|
||||||
|
fl_accessible_node_new(engine, view_id, 1);
|
||||||
fl_accessible_node_set_parent(child2, ATK_OBJECT(root), 1);
|
fl_accessible_node_set_parent(child2, ATK_OBJECT(root), 1);
|
||||||
g_autoptr(GPtrArray) children =
|
g_autoptr(GPtrArray) children =
|
||||||
g_ptr_array_new_with_free_func(g_object_unref);
|
g_ptr_array_new_with_free_func(g_object_unref);
|
||||||
@ -47,7 +50,7 @@ TEST(FlAccessibleNodeTest, SetName) {
|
|||||||
g_autoptr(FlDartProject) project = fl_dart_project_new();
|
g_autoptr(FlDartProject) project = fl_dart_project_new();
|
||||||
g_autoptr(FlEngine) engine = fl_engine_new(project);
|
g_autoptr(FlEngine) engine = fl_engine_new(project);
|
||||||
|
|
||||||
g_autoptr(FlAccessibleNode) node = fl_accessible_node_new(engine, 0);
|
g_autoptr(FlAccessibleNode) node = fl_accessible_node_new(engine, 123, 0);
|
||||||
fl_accessible_node_set_name(node, "test");
|
fl_accessible_node_set_name(node, "test");
|
||||||
EXPECT_STREQ(atk_object_get_name(ATK_OBJECT(node)), "test");
|
EXPECT_STREQ(atk_object_get_name(ATK_OBJECT(node)), "test");
|
||||||
}
|
}
|
||||||
@ -57,7 +60,7 @@ TEST(FlAccessibleNodeTest, SetExtents) {
|
|||||||
g_autoptr(FlDartProject) project = fl_dart_project_new();
|
g_autoptr(FlDartProject) project = fl_dart_project_new();
|
||||||
g_autoptr(FlEngine) engine = fl_engine_new(project);
|
g_autoptr(FlEngine) engine = fl_engine_new(project);
|
||||||
|
|
||||||
g_autoptr(FlAccessibleNode) node = fl_accessible_node_new(engine, 0);
|
g_autoptr(FlAccessibleNode) node = fl_accessible_node_new(engine, 123, 0);
|
||||||
fl_accessible_node_set_extents(node, 1, 2, 3, 4);
|
fl_accessible_node_set_extents(node, 1, 2, 3, 4);
|
||||||
gint x, y, width, height;
|
gint x, y, width, height;
|
||||||
atk_component_get_extents(ATK_COMPONENT(node), &x, &y, &width, &height,
|
atk_component_get_extents(ATK_COMPONENT(node), &x, &y, &width, &height,
|
||||||
@ -73,7 +76,7 @@ TEST(FlAccessibleNodeTest, SetFlags) {
|
|||||||
g_autoptr(FlDartProject) project = fl_dart_project_new();
|
g_autoptr(FlDartProject) project = fl_dart_project_new();
|
||||||
g_autoptr(FlEngine) engine = fl_engine_new(project);
|
g_autoptr(FlEngine) engine = fl_engine_new(project);
|
||||||
|
|
||||||
g_autoptr(FlAccessibleNode) node = fl_accessible_node_new(engine, 0);
|
g_autoptr(FlAccessibleNode) node = fl_accessible_node_new(engine, 123, 0);
|
||||||
fl_accessible_node_set_flags(
|
fl_accessible_node_set_flags(
|
||||||
node, static_cast<FlutterSemanticsFlag>(kFlutterSemanticsFlagIsEnabled |
|
node, static_cast<FlutterSemanticsFlag>(kFlutterSemanticsFlagIsEnabled |
|
||||||
kFlutterSemanticsFlagIsFocusable |
|
kFlutterSemanticsFlagIsFocusable |
|
||||||
@ -93,7 +96,7 @@ TEST(FlAccessibleNodeTest, GetRole) {
|
|||||||
g_autoptr(FlDartProject) project = fl_dart_project_new();
|
g_autoptr(FlDartProject) project = fl_dart_project_new();
|
||||||
g_autoptr(FlEngine) engine = fl_engine_new(project);
|
g_autoptr(FlEngine) engine = fl_engine_new(project);
|
||||||
|
|
||||||
g_autoptr(FlAccessibleNode) node = fl_accessible_node_new(engine, 0);
|
g_autoptr(FlAccessibleNode) node = fl_accessible_node_new(engine, 123, 0);
|
||||||
|
|
||||||
fl_accessible_node_set_flags(
|
fl_accessible_node_set_flags(
|
||||||
node, static_cast<FlutterSemanticsFlag>(kFlutterSemanticsFlagIsButton));
|
node, static_cast<FlutterSemanticsFlag>(kFlutterSemanticsFlagIsButton));
|
||||||
@ -127,7 +130,7 @@ TEST(FlAccessibleNodeTest, SetActions) {
|
|||||||
g_autoptr(FlDartProject) project = fl_dart_project_new();
|
g_autoptr(FlDartProject) project = fl_dart_project_new();
|
||||||
g_autoptr(FlEngine) engine = fl_engine_new(project);
|
g_autoptr(FlEngine) engine = fl_engine_new(project);
|
||||||
|
|
||||||
g_autoptr(FlAccessibleNode) node = fl_accessible_node_new(engine, 0);
|
g_autoptr(FlAccessibleNode) node = fl_accessible_node_new(engine, 123, 0);
|
||||||
fl_accessible_node_set_actions(
|
fl_accessible_node_set_actions(
|
||||||
node, static_cast<FlutterSemanticsAction>(
|
node, static_cast<FlutterSemanticsAction>(
|
||||||
kFlutterSemanticsActionTap | kFlutterSemanticsActionLongPress));
|
kFlutterSemanticsActionTap | kFlutterSemanticsActionLongPress));
|
||||||
|
@ -615,7 +615,10 @@ static void fl_accessible_text_field_init(FlAccessibleTextField* self) {
|
|||||||
self, G_CONNECT_SWAPPED);
|
self, G_CONNECT_SWAPPED);
|
||||||
}
|
}
|
||||||
|
|
||||||
FlAccessibleNode* fl_accessible_text_field_new(FlEngine* engine, int32_t id) {
|
FlAccessibleNode* fl_accessible_text_field_new(FlEngine* engine,
|
||||||
|
FlutterViewId view_id,
|
||||||
|
int32_t id) {
|
||||||
return FL_ACCESSIBLE_NODE(g_object_new(fl_accessible_text_field_get_type(),
|
return FL_ACCESSIBLE_NODE(g_object_new(fl_accessible_text_field_get_type(),
|
||||||
"engine", engine, "id", id, nullptr));
|
"engine", engine, "view-id", view_id,
|
||||||
|
"node-id", id, nullptr));
|
||||||
}
|
}
|
||||||
|
@ -20,6 +20,7 @@ G_DECLARE_FINAL_TYPE(FlAccessibleTextField,
|
|||||||
/**
|
/**
|
||||||
* fl_accessible_text_field_new:
|
* fl_accessible_text_field_new:
|
||||||
* @engine: the #FlEngine this node came from.
|
* @engine: the #FlEngine this node came from.
|
||||||
|
* @view_id: the ID of the view that contains this semantics node.
|
||||||
* @id: the semantics node ID this object represents.
|
* @id: the semantics node ID this object represents.
|
||||||
*
|
*
|
||||||
* Creates a new accessibility object that exposes an editable Flutter text
|
* Creates a new accessibility object that exposes an editable Flutter text
|
||||||
@ -27,7 +28,9 @@ G_DECLARE_FINAL_TYPE(FlAccessibleTextField,
|
|||||||
*
|
*
|
||||||
* Returns: a new #FlAccessibleNode.
|
* Returns: a new #FlAccessibleNode.
|
||||||
*/
|
*/
|
||||||
FlAccessibleNode* fl_accessible_text_field_new(FlEngine* engine, int32_t id);
|
FlAccessibleNode* fl_accessible_text_field_new(FlEngine* engine,
|
||||||
|
FlutterViewId view_id,
|
||||||
|
int32_t id);
|
||||||
|
|
||||||
G_END_DECLS
|
G_END_DECLS
|
||||||
|
|
||||||
|
@ -26,7 +26,8 @@ static FlValue* decode_semantic_data(const uint8_t* data, size_t data_length) {
|
|||||||
TEST(FlAccessibleTextFieldTest, SetValue) {
|
TEST(FlAccessibleTextFieldTest, SetValue) {
|
||||||
g_autoptr(FlDartProject) project = fl_dart_project_new();
|
g_autoptr(FlDartProject) project = fl_dart_project_new();
|
||||||
g_autoptr(FlEngine) engine = fl_engine_new(project);
|
g_autoptr(FlEngine) engine = fl_engine_new(project);
|
||||||
g_autoptr(FlAccessibleNode) node = fl_accessible_text_field_new(engine, 1);
|
g_autoptr(FlAccessibleNode) node =
|
||||||
|
fl_accessible_text_field_new(engine, 123, 1);
|
||||||
|
|
||||||
// "" -> "Flutter"
|
// "" -> "Flutter"
|
||||||
{
|
{
|
||||||
@ -83,7 +84,8 @@ TEST(FlAccessibleTextFieldTest, SetValue) {
|
|||||||
TEST(FlAccessibleTextFieldTest, SetTextSelection) {
|
TEST(FlAccessibleTextFieldTest, SetTextSelection) {
|
||||||
g_autoptr(FlDartProject) project = fl_dart_project_new();
|
g_autoptr(FlDartProject) project = fl_dart_project_new();
|
||||||
g_autoptr(FlEngine) engine = fl_engine_new(project);
|
g_autoptr(FlEngine) engine = fl_engine_new(project);
|
||||||
g_autoptr(FlAccessibleNode) node = fl_accessible_text_field_new(engine, 1);
|
g_autoptr(FlAccessibleNode) node =
|
||||||
|
fl_accessible_text_field_new(engine, 123, 1);
|
||||||
|
|
||||||
// [-1,-1] -> [2,3]
|
// [-1,-1] -> [2,3]
|
||||||
{
|
{
|
||||||
@ -151,18 +153,17 @@ TEST(FlAccessibleTextFieldTest, PerformAction) {
|
|||||||
EXPECT_TRUE(fl_engine_start(engine, &error));
|
EXPECT_TRUE(fl_engine_start(engine, &error));
|
||||||
EXPECT_EQ(error, nullptr);
|
EXPECT_EQ(error, nullptr);
|
||||||
|
|
||||||
fl_engine_get_embedder_api(engine)->DispatchSemanticsAction =
|
fl_engine_get_embedder_api(engine)->SendSemanticsAction = MOCK_ENGINE_PROC(
|
||||||
MOCK_ENGINE_PROC(
|
SendSemanticsAction,
|
||||||
DispatchSemanticsAction,
|
([&action_datas](auto engine,
|
||||||
([&action_datas](auto engine, uint64_t id,
|
const FlutterSendSemanticsActionInfo* info) {
|
||||||
FlutterSemanticsAction action, const uint8_t* data,
|
g_ptr_array_add(action_datas,
|
||||||
size_t data_length) {
|
decode_semantic_data(info->data, info->data_length));
|
||||||
g_ptr_array_add(action_datas,
|
return kSuccess;
|
||||||
decode_semantic_data(data, data_length));
|
}));
|
||||||
return kSuccess;
|
|
||||||
}));
|
|
||||||
|
|
||||||
g_autoptr(FlAccessibleNode) node = fl_accessible_text_field_new(engine, 1);
|
g_autoptr(FlAccessibleNode) node =
|
||||||
|
fl_accessible_text_field_new(engine, 123, 1);
|
||||||
fl_accessible_node_set_actions(
|
fl_accessible_node_set_actions(
|
||||||
node, static_cast<FlutterSemanticsAction>(
|
node, static_cast<FlutterSemanticsAction>(
|
||||||
kFlutterSemanticsActionMoveCursorForwardByCharacter |
|
kFlutterSemanticsActionMoveCursorForwardByCharacter |
|
||||||
@ -185,7 +186,8 @@ TEST(FlAccessibleTextFieldTest, PerformAction) {
|
|||||||
TEST(FlAccessibleTextFieldTest, GetCharacterCount) {
|
TEST(FlAccessibleTextFieldTest, GetCharacterCount) {
|
||||||
g_autoptr(FlDartProject) project = fl_dart_project_new();
|
g_autoptr(FlDartProject) project = fl_dart_project_new();
|
||||||
g_autoptr(FlEngine) engine = fl_engine_new(project);
|
g_autoptr(FlEngine) engine = fl_engine_new(project);
|
||||||
g_autoptr(FlAccessibleNode) node = fl_accessible_text_field_new(engine, 1);
|
g_autoptr(FlAccessibleNode) node =
|
||||||
|
fl_accessible_text_field_new(engine, 123, 1);
|
||||||
|
|
||||||
EXPECT_EQ(atk_text_get_character_count(ATK_TEXT(node)), 0);
|
EXPECT_EQ(atk_text_get_character_count(ATK_TEXT(node)), 0);
|
||||||
|
|
||||||
@ -198,7 +200,8 @@ TEST(FlAccessibleTextFieldTest, GetCharacterCount) {
|
|||||||
TEST(FlAccessibleTextFieldTest, GetText) {
|
TEST(FlAccessibleTextFieldTest, GetText) {
|
||||||
g_autoptr(FlDartProject) project = fl_dart_project_new();
|
g_autoptr(FlDartProject) project = fl_dart_project_new();
|
||||||
g_autoptr(FlEngine) engine = fl_engine_new(project);
|
g_autoptr(FlEngine) engine = fl_engine_new(project);
|
||||||
g_autoptr(FlAccessibleNode) node = fl_accessible_text_field_new(engine, 1);
|
g_autoptr(FlAccessibleNode) node =
|
||||||
|
fl_accessible_text_field_new(engine, 123, 1);
|
||||||
|
|
||||||
g_autofree gchar* empty = atk_text_get_text(ATK_TEXT(node), 0, -1);
|
g_autofree gchar* empty = atk_text_get_text(ATK_TEXT(node), 0, -1);
|
||||||
EXPECT_STREQ(empty, "");
|
EXPECT_STREQ(empty, "");
|
||||||
@ -219,7 +222,8 @@ TEST(FlAccessibleTextFieldTest, GetText) {
|
|||||||
TEST(FlAccessibleTextFieldTest, GetCaretOffset) {
|
TEST(FlAccessibleTextFieldTest, GetCaretOffset) {
|
||||||
g_autoptr(FlDartProject) project = fl_dart_project_new();
|
g_autoptr(FlDartProject) project = fl_dart_project_new();
|
||||||
g_autoptr(FlEngine) engine = fl_engine_new(project);
|
g_autoptr(FlEngine) engine = fl_engine_new(project);
|
||||||
g_autoptr(FlAccessibleNode) node = fl_accessible_text_field_new(engine, 1);
|
g_autoptr(FlAccessibleNode) node =
|
||||||
|
fl_accessible_text_field_new(engine, 123, 1);
|
||||||
|
|
||||||
EXPECT_EQ(atk_text_get_caret_offset(ATK_TEXT(node)), -1);
|
EXPECT_EQ(atk_text_get_caret_offset(ATK_TEXT(node)), -1);
|
||||||
|
|
||||||
@ -240,21 +244,21 @@ TEST(FlAccessibleTextFieldTest, SetCaretOffset) {
|
|||||||
EXPECT_TRUE(fl_engine_start(engine, &error));
|
EXPECT_TRUE(fl_engine_start(engine, &error));
|
||||||
EXPECT_EQ(error, nullptr);
|
EXPECT_EQ(error, nullptr);
|
||||||
|
|
||||||
fl_engine_get_embedder_api(engine)->DispatchSemanticsAction =
|
fl_engine_get_embedder_api(engine)->SendSemanticsAction = MOCK_ENGINE_PROC(
|
||||||
MOCK_ENGINE_PROC(
|
SendSemanticsAction,
|
||||||
DispatchSemanticsAction,
|
([&base, &extent](auto engine,
|
||||||
([&base, &extent](auto engine, uint64_t id,
|
const FlutterSendSemanticsActionInfo* info) {
|
||||||
FlutterSemanticsAction action, const uint8_t* data,
|
EXPECT_EQ(info->action, kFlutterSemanticsActionSetSelection);
|
||||||
size_t data_length) {
|
g_autoptr(FlValue) value =
|
||||||
EXPECT_EQ(action, kFlutterSemanticsActionSetSelection);
|
decode_semantic_data(info->data, info->data_length);
|
||||||
g_autoptr(FlValue) value = decode_semantic_data(data, data_length);
|
EXPECT_EQ(fl_value_get_type(value), FL_VALUE_TYPE_MAP);
|
||||||
EXPECT_EQ(fl_value_get_type(value), FL_VALUE_TYPE_MAP);
|
base = fl_value_get_int(fl_value_lookup_string(value, "base"));
|
||||||
base = fl_value_get_int(fl_value_lookup_string(value, "base"));
|
extent = fl_value_get_int(fl_value_lookup_string(value, "extent"));
|
||||||
extent = fl_value_get_int(fl_value_lookup_string(value, "extent"));
|
return kSuccess;
|
||||||
return kSuccess;
|
}));
|
||||||
}));
|
|
||||||
|
|
||||||
g_autoptr(FlAccessibleNode) node = fl_accessible_text_field_new(engine, 1);
|
g_autoptr(FlAccessibleNode) node =
|
||||||
|
fl_accessible_text_field_new(engine, 123, 1);
|
||||||
|
|
||||||
EXPECT_TRUE(atk_text_set_caret_offset(ATK_TEXT(node), 3));
|
EXPECT_TRUE(atk_text_set_caret_offset(ATK_TEXT(node), 3));
|
||||||
EXPECT_EQ(base, 3);
|
EXPECT_EQ(base, 3);
|
||||||
@ -265,7 +269,8 @@ TEST(FlAccessibleTextFieldTest, SetCaretOffset) {
|
|||||||
TEST(FlAccessibleTextFieldTest, GetNSelections) {
|
TEST(FlAccessibleTextFieldTest, GetNSelections) {
|
||||||
g_autoptr(FlDartProject) project = fl_dart_project_new();
|
g_autoptr(FlDartProject) project = fl_dart_project_new();
|
||||||
g_autoptr(FlEngine) engine = fl_engine_new(project);
|
g_autoptr(FlEngine) engine = fl_engine_new(project);
|
||||||
g_autoptr(FlAccessibleNode) node = fl_accessible_text_field_new(engine, 1);
|
g_autoptr(FlAccessibleNode) node =
|
||||||
|
fl_accessible_text_field_new(engine, 123, 1);
|
||||||
|
|
||||||
EXPECT_EQ(atk_text_get_n_selections(ATK_TEXT(node)), 0);
|
EXPECT_EQ(atk_text_get_n_selections(ATK_TEXT(node)), 0);
|
||||||
|
|
||||||
@ -278,7 +283,8 @@ TEST(FlAccessibleTextFieldTest, GetNSelections) {
|
|||||||
TEST(FlAccessibleTextFieldTest, GetSelection) {
|
TEST(FlAccessibleTextFieldTest, GetSelection) {
|
||||||
g_autoptr(FlDartProject) project = fl_dart_project_new();
|
g_autoptr(FlDartProject) project = fl_dart_project_new();
|
||||||
g_autoptr(FlEngine) engine = fl_engine_new(project);
|
g_autoptr(FlEngine) engine = fl_engine_new(project);
|
||||||
g_autoptr(FlAccessibleNode) node = fl_accessible_text_field_new(engine, 1);
|
g_autoptr(FlAccessibleNode) node =
|
||||||
|
fl_accessible_text_field_new(engine, 123, 1);
|
||||||
|
|
||||||
EXPECT_EQ(atk_text_get_selection(ATK_TEXT(node), 0, nullptr, nullptr),
|
EXPECT_EQ(atk_text_get_selection(ATK_TEXT(node), 0, nullptr, nullptr),
|
||||||
nullptr);
|
nullptr);
|
||||||
@ -321,21 +327,21 @@ TEST(FlAccessibleTextFieldTest, AddSelection) {
|
|||||||
EXPECT_TRUE(fl_engine_start(engine, &error));
|
EXPECT_TRUE(fl_engine_start(engine, &error));
|
||||||
EXPECT_EQ(error, nullptr);
|
EXPECT_EQ(error, nullptr);
|
||||||
|
|
||||||
fl_engine_get_embedder_api(engine)->DispatchSemanticsAction =
|
fl_engine_get_embedder_api(engine)->SendSemanticsAction = MOCK_ENGINE_PROC(
|
||||||
MOCK_ENGINE_PROC(
|
SendSemanticsAction,
|
||||||
DispatchSemanticsAction,
|
([&base, &extent](auto engine,
|
||||||
([&base, &extent](auto engine, uint64_t id,
|
const FlutterSendSemanticsActionInfo* info) {
|
||||||
FlutterSemanticsAction action, const uint8_t* data,
|
EXPECT_EQ(info->action, kFlutterSemanticsActionSetSelection);
|
||||||
size_t data_length) {
|
g_autoptr(FlValue) value =
|
||||||
EXPECT_EQ(action, kFlutterSemanticsActionSetSelection);
|
decode_semantic_data(info->data, info->data_length);
|
||||||
g_autoptr(FlValue) value = decode_semantic_data(data, data_length);
|
EXPECT_EQ(fl_value_get_type(value), FL_VALUE_TYPE_MAP);
|
||||||
EXPECT_EQ(fl_value_get_type(value), FL_VALUE_TYPE_MAP);
|
base = fl_value_get_int(fl_value_lookup_string(value, "base"));
|
||||||
base = fl_value_get_int(fl_value_lookup_string(value, "base"));
|
extent = fl_value_get_int(fl_value_lookup_string(value, "extent"));
|
||||||
extent = fl_value_get_int(fl_value_lookup_string(value, "extent"));
|
return kSuccess;
|
||||||
return kSuccess;
|
}));
|
||||||
}));
|
|
||||||
|
|
||||||
g_autoptr(FlAccessibleNode) node = fl_accessible_text_field_new(engine, 1);
|
g_autoptr(FlAccessibleNode) node =
|
||||||
|
fl_accessible_text_field_new(engine, 123, 1);
|
||||||
|
|
||||||
EXPECT_TRUE(atk_text_add_selection(ATK_TEXT(node), 2, 4));
|
EXPECT_TRUE(atk_text_add_selection(ATK_TEXT(node), 2, 4));
|
||||||
EXPECT_EQ(base, 2);
|
EXPECT_EQ(base, 2);
|
||||||
@ -361,21 +367,21 @@ TEST(FlAccessibleTextFieldTest, RemoveSelection) {
|
|||||||
EXPECT_TRUE(fl_engine_start(engine, &error));
|
EXPECT_TRUE(fl_engine_start(engine, &error));
|
||||||
EXPECT_EQ(error, nullptr);
|
EXPECT_EQ(error, nullptr);
|
||||||
|
|
||||||
fl_engine_get_embedder_api(engine)->DispatchSemanticsAction =
|
fl_engine_get_embedder_api(engine)->SendSemanticsAction = MOCK_ENGINE_PROC(
|
||||||
MOCK_ENGINE_PROC(
|
SendSemanticsAction,
|
||||||
DispatchSemanticsAction,
|
([&base, &extent](auto engine,
|
||||||
([&base, &extent](auto engine, uint64_t id,
|
const FlutterSendSemanticsActionInfo* info) {
|
||||||
FlutterSemanticsAction action, const uint8_t* data,
|
EXPECT_EQ(info->action, kFlutterSemanticsActionSetSelection);
|
||||||
size_t data_length) {
|
g_autoptr(FlValue) value =
|
||||||
EXPECT_EQ(action, kFlutterSemanticsActionSetSelection);
|
decode_semantic_data(info->data, info->data_length);
|
||||||
g_autoptr(FlValue) value = decode_semantic_data(data, data_length);
|
EXPECT_EQ(fl_value_get_type(value), FL_VALUE_TYPE_MAP);
|
||||||
EXPECT_EQ(fl_value_get_type(value), FL_VALUE_TYPE_MAP);
|
base = fl_value_get_int(fl_value_lookup_string(value, "base"));
|
||||||
base = fl_value_get_int(fl_value_lookup_string(value, "base"));
|
extent = fl_value_get_int(fl_value_lookup_string(value, "extent"));
|
||||||
extent = fl_value_get_int(fl_value_lookup_string(value, "extent"));
|
return kSuccess;
|
||||||
return kSuccess;
|
}));
|
||||||
}));
|
|
||||||
|
|
||||||
g_autoptr(FlAccessibleNode) node = fl_accessible_text_field_new(engine, 1);
|
g_autoptr(FlAccessibleNode) node =
|
||||||
|
fl_accessible_text_field_new(engine, 123, 1);
|
||||||
|
|
||||||
// no selection
|
// no selection
|
||||||
EXPECT_FALSE(atk_text_remove_selection(ATK_TEXT(node), 0));
|
EXPECT_FALSE(atk_text_remove_selection(ATK_TEXT(node), 0));
|
||||||
@ -407,21 +413,21 @@ TEST(FlAccessibleTextFieldTest, SetSelection) {
|
|||||||
EXPECT_TRUE(fl_engine_start(engine, &error));
|
EXPECT_TRUE(fl_engine_start(engine, &error));
|
||||||
EXPECT_EQ(error, nullptr);
|
EXPECT_EQ(error, nullptr);
|
||||||
|
|
||||||
fl_engine_get_embedder_api(engine)->DispatchSemanticsAction =
|
fl_engine_get_embedder_api(engine)->SendSemanticsAction = MOCK_ENGINE_PROC(
|
||||||
MOCK_ENGINE_PROC(
|
SendSemanticsAction,
|
||||||
DispatchSemanticsAction,
|
([&base, &extent](auto engine,
|
||||||
([&base, &extent](auto engine, uint64_t id,
|
const FlutterSendSemanticsActionInfo* info) {
|
||||||
FlutterSemanticsAction action, const uint8_t* data,
|
EXPECT_EQ(info->action, kFlutterSemanticsActionSetSelection);
|
||||||
size_t data_length) {
|
g_autoptr(FlValue) value =
|
||||||
EXPECT_EQ(action, kFlutterSemanticsActionSetSelection);
|
decode_semantic_data(info->data, info->data_length);
|
||||||
g_autoptr(FlValue) value = decode_semantic_data(data, data_length);
|
EXPECT_EQ(fl_value_get_type(value), FL_VALUE_TYPE_MAP);
|
||||||
EXPECT_EQ(fl_value_get_type(value), FL_VALUE_TYPE_MAP);
|
base = fl_value_get_int(fl_value_lookup_string(value, "base"));
|
||||||
base = fl_value_get_int(fl_value_lookup_string(value, "base"));
|
extent = fl_value_get_int(fl_value_lookup_string(value, "extent"));
|
||||||
extent = fl_value_get_int(fl_value_lookup_string(value, "extent"));
|
return kSuccess;
|
||||||
return kSuccess;
|
}));
|
||||||
}));
|
|
||||||
|
|
||||||
g_autoptr(FlAccessibleNode) node = fl_accessible_text_field_new(engine, 1);
|
g_autoptr(FlAccessibleNode) node =
|
||||||
|
fl_accessible_text_field_new(engine, 123, 1);
|
||||||
|
|
||||||
// selection num != 0
|
// selection num != 0
|
||||||
EXPECT_FALSE(atk_text_set_selection(ATK_TEXT(node), 1, 2, 4));
|
EXPECT_FALSE(atk_text_set_selection(ATK_TEXT(node), 1, 2, 4));
|
||||||
@ -448,19 +454,19 @@ TEST(FlAccessibleTextFieldTest, SetTextContents) {
|
|||||||
EXPECT_TRUE(fl_engine_start(engine, &error));
|
EXPECT_TRUE(fl_engine_start(engine, &error));
|
||||||
EXPECT_EQ(error, nullptr);
|
EXPECT_EQ(error, nullptr);
|
||||||
|
|
||||||
fl_engine_get_embedder_api(engine)->DispatchSemanticsAction =
|
fl_engine_get_embedder_api(engine)->SendSemanticsAction = MOCK_ENGINE_PROC(
|
||||||
MOCK_ENGINE_PROC(
|
SendSemanticsAction,
|
||||||
DispatchSemanticsAction,
|
([&text](auto engine, const FlutterSendSemanticsActionInfo* info) {
|
||||||
([&text](auto engine, uint64_t id, FlutterSemanticsAction action,
|
EXPECT_EQ(info->action, kFlutterSemanticsActionSetText);
|
||||||
const uint8_t* data, size_t data_length) {
|
g_autoptr(FlValue) value =
|
||||||
EXPECT_EQ(action, kFlutterSemanticsActionSetText);
|
decode_semantic_data(info->data, info->data_length);
|
||||||
g_autoptr(FlValue) value = decode_semantic_data(data, data_length);
|
EXPECT_EQ(fl_value_get_type(value), FL_VALUE_TYPE_STRING);
|
||||||
EXPECT_EQ(fl_value_get_type(value), FL_VALUE_TYPE_STRING);
|
text = g_strdup(fl_value_get_string(value));
|
||||||
text = g_strdup(fl_value_get_string(value));
|
return kSuccess;
|
||||||
return kSuccess;
|
}));
|
||||||
}));
|
|
||||||
|
|
||||||
g_autoptr(FlAccessibleNode) node = fl_accessible_text_field_new(engine, 1);
|
g_autoptr(FlAccessibleNode) node =
|
||||||
|
fl_accessible_text_field_new(engine, 123, 1);
|
||||||
|
|
||||||
atk_editable_text_set_text_contents(ATK_EDITABLE_TEXT(node), "Flutter");
|
atk_editable_text_set_text_contents(ATK_EDITABLE_TEXT(node), "Flutter");
|
||||||
EXPECT_STREQ(text, "Flutter");
|
EXPECT_STREQ(text, "Flutter");
|
||||||
@ -479,33 +485,31 @@ TEST(FlAccessibleTextFieldTest, InsertDeleteText) {
|
|||||||
EXPECT_TRUE(fl_engine_start(engine, &error));
|
EXPECT_TRUE(fl_engine_start(engine, &error));
|
||||||
EXPECT_EQ(error, nullptr);
|
EXPECT_EQ(error, nullptr);
|
||||||
|
|
||||||
fl_engine_get_embedder_api(engine)->DispatchSemanticsAction =
|
fl_engine_get_embedder_api(engine)->SendSemanticsAction = MOCK_ENGINE_PROC(
|
||||||
MOCK_ENGINE_PROC(
|
SendSemanticsAction,
|
||||||
DispatchSemanticsAction,
|
([&text, &base, &extent](auto engine,
|
||||||
([&text, &base, &extent](auto engine, uint64_t id,
|
const FlutterSendSemanticsActionInfo* info) {
|
||||||
FlutterSemanticsAction action,
|
EXPECT_THAT(info->action,
|
||||||
const uint8_t* data, size_t data_length) {
|
::testing::AnyOf(kFlutterSemanticsActionSetText,
|
||||||
EXPECT_THAT(action,
|
kFlutterSemanticsActionSetSelection));
|
||||||
::testing::AnyOf(kFlutterSemanticsActionSetText,
|
if (info->action == kFlutterSemanticsActionSetText) {
|
||||||
kFlutterSemanticsActionSetSelection));
|
g_autoptr(FlValue) value =
|
||||||
if (action == kFlutterSemanticsActionSetText) {
|
decode_semantic_data(info->data, info->data_length);
|
||||||
g_autoptr(FlValue) value =
|
EXPECT_EQ(fl_value_get_type(value), FL_VALUE_TYPE_STRING);
|
||||||
decode_semantic_data(data, data_length);
|
g_free(text);
|
||||||
EXPECT_EQ(fl_value_get_type(value), FL_VALUE_TYPE_STRING);
|
text = g_strdup(fl_value_get_string(value));
|
||||||
g_free(text);
|
} else {
|
||||||
text = g_strdup(fl_value_get_string(value));
|
g_autoptr(FlValue) value =
|
||||||
} else {
|
decode_semantic_data(info->data, info->data_length);
|
||||||
g_autoptr(FlValue) value =
|
EXPECT_EQ(fl_value_get_type(value), FL_VALUE_TYPE_MAP);
|
||||||
decode_semantic_data(data, data_length);
|
base = fl_value_get_int(fl_value_lookup_string(value, "base"));
|
||||||
EXPECT_EQ(fl_value_get_type(value), FL_VALUE_TYPE_MAP);
|
extent = fl_value_get_int(fl_value_lookup_string(value, "extent"));
|
||||||
base = fl_value_get_int(fl_value_lookup_string(value, "base"));
|
}
|
||||||
extent =
|
return kSuccess;
|
||||||
fl_value_get_int(fl_value_lookup_string(value, "extent"));
|
}));
|
||||||
}
|
|
||||||
return kSuccess;
|
|
||||||
}));
|
|
||||||
|
|
||||||
g_autoptr(FlAccessibleNode) node = fl_accessible_text_field_new(engine, 1);
|
g_autoptr(FlAccessibleNode) node =
|
||||||
|
fl_accessible_text_field_new(engine, 123, 1);
|
||||||
fl_accessible_node_set_value(node, "Fler");
|
fl_accessible_node_set_value(node, "Fler");
|
||||||
|
|
||||||
gint pos = 2;
|
gint pos = 2;
|
||||||
@ -534,30 +538,28 @@ TEST(FlAccessibleTextFieldTest, CopyCutPasteText) {
|
|||||||
EXPECT_TRUE(fl_engine_start(engine, &error));
|
EXPECT_TRUE(fl_engine_start(engine, &error));
|
||||||
EXPECT_EQ(error, nullptr);
|
EXPECT_EQ(error, nullptr);
|
||||||
|
|
||||||
fl_engine_get_embedder_api(engine)->DispatchSemanticsAction =
|
fl_engine_get_embedder_api(engine)->SendSemanticsAction = MOCK_ENGINE_PROC(
|
||||||
MOCK_ENGINE_PROC(
|
SendSemanticsAction,
|
||||||
DispatchSemanticsAction,
|
([&act, &base, &extent](auto engine,
|
||||||
([&act, &base, &extent](auto engine, uint64_t id,
|
const FlutterSendSemanticsActionInfo* info) {
|
||||||
FlutterSemanticsAction action,
|
EXPECT_THAT(info->action,
|
||||||
const uint8_t* data, size_t data_length) {
|
::testing::AnyOf(kFlutterSemanticsActionCut,
|
||||||
EXPECT_THAT(action,
|
kFlutterSemanticsActionCopy,
|
||||||
::testing::AnyOf(kFlutterSemanticsActionCut,
|
kFlutterSemanticsActionPaste,
|
||||||
kFlutterSemanticsActionCopy,
|
kFlutterSemanticsActionSetSelection));
|
||||||
kFlutterSemanticsActionPaste,
|
act = info->action;
|
||||||
kFlutterSemanticsActionSetSelection));
|
if (info->action == kFlutterSemanticsActionSetSelection) {
|
||||||
act = action;
|
g_autoptr(FlValue) value =
|
||||||
if (action == kFlutterSemanticsActionSetSelection) {
|
decode_semantic_data(info->data, info->data_length);
|
||||||
g_autoptr(FlValue) value =
|
EXPECT_EQ(fl_value_get_type(value), FL_VALUE_TYPE_MAP);
|
||||||
decode_semantic_data(data, data_length);
|
base = fl_value_get_int(fl_value_lookup_string(value, "base"));
|
||||||
EXPECT_EQ(fl_value_get_type(value), FL_VALUE_TYPE_MAP);
|
extent = fl_value_get_int(fl_value_lookup_string(value, "extent"));
|
||||||
base = fl_value_get_int(fl_value_lookup_string(value, "base"));
|
}
|
||||||
extent =
|
return kSuccess;
|
||||||
fl_value_get_int(fl_value_lookup_string(value, "extent"));
|
}));
|
||||||
}
|
|
||||||
return kSuccess;
|
|
||||||
}));
|
|
||||||
|
|
||||||
g_autoptr(FlAccessibleNode) node = fl_accessible_text_field_new(engine, 1);
|
g_autoptr(FlAccessibleNode) node =
|
||||||
|
fl_accessible_text_field_new(engine, 123, 1);
|
||||||
|
|
||||||
atk_editable_text_copy_text(ATK_EDITABLE_TEXT(node), 2, 5);
|
atk_editable_text_copy_text(ATK_EDITABLE_TEXT(node), 2, 5);
|
||||||
EXPECT_EQ(base, 2);
|
EXPECT_EQ(base, 2);
|
||||||
@ -578,7 +580,8 @@ TEST(FlAccessibleTextFieldTest, CopyCutPasteText) {
|
|||||||
TEST(FlAccessibleTextFieldTest, TextBoundary) {
|
TEST(FlAccessibleTextFieldTest, TextBoundary) {
|
||||||
g_autoptr(FlDartProject) project = fl_dart_project_new();
|
g_autoptr(FlDartProject) project = fl_dart_project_new();
|
||||||
g_autoptr(FlEngine) engine = fl_engine_new(project);
|
g_autoptr(FlEngine) engine = fl_engine_new(project);
|
||||||
g_autoptr(FlAccessibleNode) node = fl_accessible_text_field_new(engine, 1);
|
g_autoptr(FlAccessibleNode) node =
|
||||||
|
fl_accessible_text_field_new(engine, 123, 1);
|
||||||
|
|
||||||
fl_accessible_node_set_value(node,
|
fl_accessible_node_set_value(node,
|
||||||
"Lorem ipsum.\nDolor sit amet. Praesent commodo?"
|
"Lorem ipsum.\nDolor sit amet. Praesent commodo?"
|
||||||
|
@ -1201,7 +1201,8 @@ gboolean fl_engine_send_key_event_finish(FlEngine* self,
|
|||||||
}
|
}
|
||||||
|
|
||||||
void fl_engine_dispatch_semantics_action(FlEngine* self,
|
void fl_engine_dispatch_semantics_action(FlEngine* self,
|
||||||
uint64_t id,
|
FlutterViewId view_id,
|
||||||
|
uint64_t node_id,
|
||||||
FlutterSemanticsAction action,
|
FlutterSemanticsAction action,
|
||||||
GBytes* data) {
|
GBytes* data) {
|
||||||
g_return_if_fail(FL_IS_ENGINE(self));
|
g_return_if_fail(FL_IS_ENGINE(self));
|
||||||
@ -1217,8 +1218,14 @@ void fl_engine_dispatch_semantics_action(FlEngine* self,
|
|||||||
g_bytes_get_data(data, &action_data_length));
|
g_bytes_get_data(data, &action_data_length));
|
||||||
}
|
}
|
||||||
|
|
||||||
self->embedder_api.DispatchSemanticsAction(self->engine, id, action,
|
FlutterSendSemanticsActionInfo info;
|
||||||
action_data, action_data_length);
|
info.struct_size = sizeof(FlutterSendSemanticsActionInfo);
|
||||||
|
info.view_id = view_id;
|
||||||
|
info.node_id = node_id;
|
||||||
|
info.action = action;
|
||||||
|
info.data = action_data;
|
||||||
|
info.data_length = action_data_length;
|
||||||
|
self->embedder_api.SendSemanticsAction(self->engine, &info);
|
||||||
}
|
}
|
||||||
|
|
||||||
gboolean fl_engine_mark_texture_frame_available(FlEngine* self,
|
gboolean fl_engine_mark_texture_frame_available(FlEngine* self,
|
||||||
|
@ -414,12 +414,14 @@ gboolean fl_engine_send_key_event_finish(FlEngine* engine,
|
|||||||
/**
|
/**
|
||||||
* fl_engine_dispatch_semantics_action:
|
* fl_engine_dispatch_semantics_action:
|
||||||
* @engine: an #FlEngine.
|
* @engine: an #FlEngine.
|
||||||
* @id: the semantics action identifier.
|
* @view_id: the view that the event occured on.
|
||||||
|
* @node_id: the semantics action identifier.
|
||||||
* @action: the action being dispatched.
|
* @action: the action being dispatched.
|
||||||
* @data: (allow-none): data associated with the action.
|
* @data: (allow-none): data associated with the action.
|
||||||
*/
|
*/
|
||||||
void fl_engine_dispatch_semantics_action(FlEngine* engine,
|
void fl_engine_dispatch_semantics_action(FlEngine* engine,
|
||||||
uint64_t id,
|
FlutterViewId view_id,
|
||||||
|
uint64_t node_id,
|
||||||
FlutterSemanticsAction action,
|
FlutterSemanticsAction action,
|
||||||
GBytes* data);
|
GBytes* data);
|
||||||
|
|
||||||
|
@ -180,29 +180,28 @@ TEST(FlEngineTest, DispatchSemanticsAction) {
|
|||||||
g_autoptr(FlEngine) engine = fl_engine_new(project);
|
g_autoptr(FlEngine) engine = fl_engine_new(project);
|
||||||
|
|
||||||
bool called = false;
|
bool called = false;
|
||||||
fl_engine_get_embedder_api(engine)->DispatchSemanticsAction =
|
fl_engine_get_embedder_api(engine)->SendSemanticsAction = MOCK_ENGINE_PROC(
|
||||||
MOCK_ENGINE_PROC(
|
SendSemanticsAction,
|
||||||
DispatchSemanticsAction,
|
([&called](auto engine, const FlutterSendSemanticsActionInfo* info) {
|
||||||
([&called](auto engine, uint64_t id, FlutterSemanticsAction action,
|
EXPECT_EQ(info->view_id, static_cast<int64_t>(456));
|
||||||
const uint8_t* data, size_t data_length) {
|
EXPECT_EQ(info->node_id, static_cast<uint64_t>(42));
|
||||||
EXPECT_EQ(id, static_cast<uint64_t>(42));
|
EXPECT_EQ(info->action, kFlutterSemanticsActionTap);
|
||||||
EXPECT_EQ(action, kFlutterSemanticsActionTap);
|
EXPECT_EQ(info->data_length, static_cast<size_t>(4));
|
||||||
EXPECT_EQ(data_length, static_cast<size_t>(4));
|
EXPECT_EQ(info->data[0], 't');
|
||||||
EXPECT_EQ(data[0], 't');
|
EXPECT_EQ(info->data[1], 'e');
|
||||||
EXPECT_EQ(data[1], 'e');
|
EXPECT_EQ(info->data[2], 's');
|
||||||
EXPECT_EQ(data[2], 's');
|
EXPECT_EQ(info->data[3], 't');
|
||||||
EXPECT_EQ(data[3], 't');
|
called = true;
|
||||||
called = true;
|
|
||||||
|
|
||||||
return kSuccess;
|
return kSuccess;
|
||||||
}));
|
}));
|
||||||
|
|
||||||
g_autoptr(GError) error = nullptr;
|
g_autoptr(GError) error = nullptr;
|
||||||
EXPECT_TRUE(fl_engine_start(engine, &error));
|
EXPECT_TRUE(fl_engine_start(engine, &error));
|
||||||
EXPECT_EQ(error, nullptr);
|
EXPECT_EQ(error, nullptr);
|
||||||
g_autoptr(GBytes) data = g_bytes_new_static("test", 4);
|
g_autoptr(GBytes) data = g_bytes_new_static("test", 4);
|
||||||
fl_engine_dispatch_semantics_action(engine, 42, kFlutterSemanticsActionTap,
|
fl_engine_dispatch_semantics_action(engine, 456, 42,
|
||||||
data);
|
kFlutterSemanticsActionTap, data);
|
||||||
|
|
||||||
EXPECT_TRUE(called);
|
EXPECT_TRUE(called);
|
||||||
}
|
}
|
||||||
|
@ -222,8 +222,16 @@ static void view_added_cb(GObject* object,
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Called when the engine updates accessibility.
|
// Called when the engine updates accessibility.
|
||||||
static void update_semantics_cb(FlView* self,
|
static void update_semantics_cb(FlEngine* engine,
|
||||||
const FlutterSemanticsUpdate2* update) {
|
const FlutterSemanticsUpdate2* update,
|
||||||
|
gpointer user_data) {
|
||||||
|
FlView* self = FL_VIEW(user_data);
|
||||||
|
|
||||||
|
// A semantics update is routed to a particular view.
|
||||||
|
if (update->view_id != self->view_id) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
fl_view_accessible_handle_update_semantics(self->view_accessible, update);
|
fl_view_accessible_handle_update_semantics(self->view_accessible, update);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -489,7 +497,7 @@ static void realize_cb(FlView* self) {
|
|||||||
|
|
||||||
handle_geometry_changed(self);
|
handle_geometry_changed(self);
|
||||||
|
|
||||||
self->view_accessible = fl_view_accessible_new(self->engine);
|
self->view_accessible = fl_view_accessible_new(self->engine, self->view_id);
|
||||||
fl_socket_accessible_embed(
|
fl_socket_accessible_embed(
|
||||||
FL_SOCKET_ACCESSIBLE(gtk_widget_get_accessible(GTK_WIDGET(self))),
|
FL_SOCKET_ACCESSIBLE(gtk_widget_get_accessible(GTK_WIDGET(self))),
|
||||||
atk_plug_get_id(ATK_PLUG(self->view_accessible)));
|
atk_plug_get_id(ATK_PLUG(self->view_accessible)));
|
||||||
@ -779,6 +787,7 @@ G_MODULE_EXPORT FlView* fl_view_new_for_engine(FlEngine* engine) {
|
|||||||
|
|
||||||
self->view_id = fl_engine_add_view(engine, 1, 1, 1.0, self->cancellable,
|
self->view_id = fl_engine_add_view(engine, 1, 1, 1.0, self->cancellable,
|
||||||
view_added_cb, self);
|
view_added_cb, self);
|
||||||
|
|
||||||
fl_renderer_add_renderable(FL_RENDERER(self->renderer), self->view_id,
|
fl_renderer_add_renderable(FL_RENDERER(self->renderer), self->view_id,
|
||||||
FL_RENDERABLE(self));
|
FL_RENDERABLE(self));
|
||||||
|
|
||||||
@ -786,7 +795,6 @@ G_MODULE_EXPORT FlView* fl_view_new_for_engine(FlEngine* engine) {
|
|||||||
|
|
||||||
g_signal_connect_swapped(self->gl_area, "realize",
|
g_signal_connect_swapped(self->gl_area, "realize",
|
||||||
G_CALLBACK(secondary_realize_cb), self);
|
G_CALLBACK(secondary_realize_cb), self);
|
||||||
|
|
||||||
return self;
|
return self;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -15,6 +15,8 @@ struct _FlViewAccessible {
|
|||||||
|
|
||||||
GWeakRef engine;
|
GWeakRef engine;
|
||||||
|
|
||||||
|
FlutterViewId view_id;
|
||||||
|
|
||||||
// Semantics nodes keyed by ID
|
// Semantics nodes keyed by ID
|
||||||
GHashTable* semantics_nodes_by_id;
|
GHashTable* semantics_nodes_by_id;
|
||||||
|
|
||||||
@ -32,10 +34,10 @@ static FlAccessibleNode* create_node(FlViewAccessible* self,
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (semantics->flags & kFlutterSemanticsFlagIsTextField) {
|
if (semantics->flags & kFlutterSemanticsFlagIsTextField) {
|
||||||
return fl_accessible_text_field_new(engine, semantics->id);
|
return fl_accessible_text_field_new(engine, self->view_id, semantics->id);
|
||||||
}
|
}
|
||||||
|
|
||||||
return fl_accessible_node_new(engine, semantics->id);
|
return fl_accessible_node_new(engine, self->view_id, semantics->id);
|
||||||
}
|
}
|
||||||
|
|
||||||
static FlAccessibleNode* lookup_node(FlViewAccessible* self, int32_t id) {
|
static FlAccessibleNode* lookup_node(FlViewAccessible* self, int32_t id) {
|
||||||
@ -129,10 +131,12 @@ static void fl_view_accessible_init(FlViewAccessible* self) {
|
|||||||
g_direct_hash, g_direct_equal, nullptr, g_object_unref);
|
g_direct_hash, g_direct_equal, nullptr, g_object_unref);
|
||||||
}
|
}
|
||||||
|
|
||||||
FlViewAccessible* fl_view_accessible_new(FlEngine* engine) {
|
FlViewAccessible* fl_view_accessible_new(FlEngine* engine,
|
||||||
|
FlutterViewId view_id) {
|
||||||
FlViewAccessible* self =
|
FlViewAccessible* self =
|
||||||
FL_VIEW_ACCESSIBLE(g_object_new(fl_view_accessible_get_type(), nullptr));
|
FL_VIEW_ACCESSIBLE(g_object_new(fl_view_accessible_get_type(), nullptr));
|
||||||
g_weak_ref_init(&self->engine, engine);
|
g_weak_ref_init(&self->engine, engine);
|
||||||
|
self->view_id = view_id;
|
||||||
return self;
|
return self;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -37,13 +37,16 @@ G_DECLARE_FINAL_TYPE(FlViewAccessible,
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* fl_view_accessible_new:
|
* fl_view_accessible_new:
|
||||||
|
* @engine: the #FlEngine.
|
||||||
|
* @view_id: the Flutter view id.
|
||||||
*
|
*
|
||||||
* Creates a new accessibility object that exposes Flutter accessibility
|
* Creates a new accessibility object that exposes Flutter accessibility
|
||||||
* information to ATK.
|
* information to ATK.
|
||||||
*
|
*
|
||||||
* Returns: a new #FlViewAccessible.
|
* Returns: a new #FlViewAccessible.
|
||||||
*/
|
*/
|
||||||
FlViewAccessible* fl_view_accessible_new(FlEngine* engine);
|
FlViewAccessible* fl_view_accessible_new(FlEngine* engine,
|
||||||
|
FlutterViewId view_id);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* fl_view_accessible_handle_update_semantics:
|
* fl_view_accessible_handle_update_semantics:
|
||||||
|
@ -12,7 +12,7 @@
|
|||||||
TEST(FlViewAccessibleTest, BuildTree) {
|
TEST(FlViewAccessibleTest, BuildTree) {
|
||||||
g_autoptr(FlDartProject) project = fl_dart_project_new();
|
g_autoptr(FlDartProject) project = fl_dart_project_new();
|
||||||
g_autoptr(FlEngine) engine = fl_engine_new(project);
|
g_autoptr(FlEngine) engine = fl_engine_new(project);
|
||||||
g_autoptr(FlViewAccessible) accessible = fl_view_accessible_new(engine);
|
g_autoptr(FlViewAccessible) accessible = fl_view_accessible_new(engine, 456);
|
||||||
|
|
||||||
int32_t children[] = {111, 222};
|
int32_t children[] = {111, 222};
|
||||||
FlutterSemanticsNode2 root_node = {
|
FlutterSemanticsNode2 root_node = {
|
||||||
@ -49,7 +49,7 @@ TEST(FlViewAccessibleTest, BuildTree) {
|
|||||||
TEST(FlViewAccessibleTest, AddRemoveChildren) {
|
TEST(FlViewAccessibleTest, AddRemoveChildren) {
|
||||||
g_autoptr(FlDartProject) project = fl_dart_project_new();
|
g_autoptr(FlDartProject) project = fl_dart_project_new();
|
||||||
g_autoptr(FlEngine) engine = fl_engine_new(project);
|
g_autoptr(FlEngine) engine = fl_engine_new(project);
|
||||||
g_autoptr(FlViewAccessible) accessible = fl_view_accessible_new(engine);
|
g_autoptr(FlViewAccessible) accessible = fl_view_accessible_new(engine, 456);
|
||||||
|
|
||||||
FlutterSemanticsNode2 root_node = {
|
FlutterSemanticsNode2 root_node = {
|
||||||
.id = 0,
|
.id = 0,
|
||||||
|
@ -150,12 +150,9 @@ FlutterEngineResult FlutterEngineUpdateAccessibilityFeatures(
|
|||||||
return kSuccess;
|
return kSuccess;
|
||||||
}
|
}
|
||||||
|
|
||||||
FlutterEngineResult FlutterEngineDispatchSemanticsAction(
|
FlutterEngineResult FlutterEngineSendSemanticsAction(
|
||||||
FLUTTER_API_SYMBOL(FlutterEngine) engine,
|
FLUTTER_API_SYMBOL(FlutterEngine) engine,
|
||||||
uint64_t id,
|
const FlutterSendSemanticsActionInfo* info) {
|
||||||
FlutterSemanticsAction action,
|
|
||||||
const uint8_t* data,
|
|
||||||
size_t data_length) {
|
|
||||||
return kSuccess;
|
return kSuccess;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -228,7 +225,7 @@ FlutterEngineResult FlutterEngineGetProcAddresses(
|
|||||||
table->RunTask = &FlutterEngineRunTask;
|
table->RunTask = &FlutterEngineRunTask;
|
||||||
table->UpdateLocales = &FlutterEngineUpdateLocales;
|
table->UpdateLocales = &FlutterEngineUpdateLocales;
|
||||||
table->UpdateSemanticsEnabled = &FlutterEngineUpdateSemanticsEnabled;
|
table->UpdateSemanticsEnabled = &FlutterEngineUpdateSemanticsEnabled;
|
||||||
table->DispatchSemanticsAction = &FlutterEngineDispatchSemanticsAction;
|
table->SendSemanticsAction = &FlutterEngineSendSemanticsAction;
|
||||||
table->RunsAOTCompiledDartCode = &FlutterEngineRunsAOTCompiledDartCode;
|
table->RunsAOTCompiledDartCode = &FlutterEngineRunsAOTCompiledDartCode;
|
||||||
table->RegisterExternalTexture = &FlutterEngineRegisterExternalTexture;
|
table->RegisterExternalTexture = &FlutterEngineRegisterExternalTexture;
|
||||||
table->MarkExternalTextureFrameAvailable =
|
table->MarkExternalTextureFrameAvailable =
|
||||||
|
@ -163,7 +163,8 @@ void AccessibilityBridgeWindows::DispatchAccessibilityAction(
|
|||||||
AccessibilityNodeId target,
|
AccessibilityNodeId target,
|
||||||
FlutterSemanticsAction action,
|
FlutterSemanticsAction action,
|
||||||
fml::MallocMapping data) {
|
fml::MallocMapping data) {
|
||||||
view_->GetEngine()->DispatchSemanticsAction(target, action, std::move(data));
|
view_->GetEngine()->DispatchSemanticsAction(view_->view_id(), target, action,
|
||||||
|
std::move(data));
|
||||||
}
|
}
|
||||||
|
|
||||||
std::shared_ptr<FlutterPlatformNodeDelegate>
|
std::shared_ptr<FlutterPlatformNodeDelegate>
|
||||||
|
@ -278,12 +278,11 @@ TEST(AccessibilityBridgeWindows, DispatchAccessibilityAction) {
|
|||||||
PopulateAXTree(bridge);
|
PopulateAXTree(bridge);
|
||||||
|
|
||||||
FlutterSemanticsAction actual_action = kFlutterSemanticsActionTap;
|
FlutterSemanticsAction actual_action = kFlutterSemanticsActionTap;
|
||||||
modifier.embedder_api().DispatchSemanticsAction = MOCK_ENGINE_PROC(
|
modifier.embedder_api().SendSemanticsAction = MOCK_ENGINE_PROC(
|
||||||
DispatchSemanticsAction,
|
SendSemanticsAction,
|
||||||
([&actual_action](FLUTTER_API_SYMBOL(FlutterEngine) engine, uint64_t id,
|
([&actual_action](FLUTTER_API_SYMBOL(FlutterEngine) engine,
|
||||||
FlutterSemanticsAction action, const uint8_t* data,
|
const FlutterSendSemanticsActionInfo* info) {
|
||||||
size_t data_length) {
|
actual_action = info->action;
|
||||||
actual_action = action;
|
|
||||||
return kSuccess;
|
return kSuccess;
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'dart:io' as io;
|
import 'dart:io' as io;
|
||||||
import 'dart:typed_data' show ByteData, Uint8List;
|
import 'dart:typed_data' show ByteData, Float64List, Int32List, Uint8List;
|
||||||
import 'dart:ui' as ui;
|
import 'dart:ui' as ui;
|
||||||
|
|
||||||
// Signals a waiting latch in the native test.
|
// Signals a waiting latch in the native test.
|
||||||
@ -404,3 +404,74 @@ external void notifyEngineId(int? handle);
|
|||||||
void testEngineId() {
|
void testEngineId() {
|
||||||
notifyEngineId(ui.PlatformDispatcher.instance.engineId);
|
notifyEngineId(ui.PlatformDispatcher.instance.engineId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@pragma('vm:entry-point')
|
||||||
|
Future<void> sendSemanticsTreeInfo() async {
|
||||||
|
// Wait until semantics are enabled.
|
||||||
|
if (!ui.PlatformDispatcher.instance.semanticsEnabled) {
|
||||||
|
await semanticsChanged;
|
||||||
|
}
|
||||||
|
|
||||||
|
final Iterable<ui.FlutterView> views = ui.PlatformDispatcher.instance.views;
|
||||||
|
final ui.FlutterView view1 = views.firstWhere(
|
||||||
|
(final ui.FlutterView view) => view != ui.PlatformDispatcher.instance.implicitView,
|
||||||
|
);
|
||||||
|
final ui.FlutterView view2 = views.firstWhere(
|
||||||
|
(final ui.FlutterView view) =>
|
||||||
|
view != view1 && view != ui.PlatformDispatcher.instance.implicitView,
|
||||||
|
);
|
||||||
|
|
||||||
|
ui.SemanticsUpdate createSemanticsUpdate(int nodeId) {
|
||||||
|
final ui.SemanticsUpdateBuilder builder = ui.SemanticsUpdateBuilder();
|
||||||
|
final Float64List transform = Float64List(16);
|
||||||
|
final Int32List childrenInTraversalOrder = Int32List(0);
|
||||||
|
final Int32List childrenInHitTestOrder = Int32List(0);
|
||||||
|
final Int32List additionalActions = Int32List(0);
|
||||||
|
// Identity matrix 4x4.
|
||||||
|
transform[0] = 1;
|
||||||
|
transform[5] = 1;
|
||||||
|
transform[10] = 1;
|
||||||
|
builder.updateNode(
|
||||||
|
id: nodeId,
|
||||||
|
flags: 0,
|
||||||
|
actions: 0,
|
||||||
|
maxValueLength: 0,
|
||||||
|
currentValueLength: 0,
|
||||||
|
textSelectionBase: -1,
|
||||||
|
textSelectionExtent: -1,
|
||||||
|
platformViewId: -1,
|
||||||
|
scrollChildren: 0,
|
||||||
|
scrollIndex: 0,
|
||||||
|
scrollPosition: 0,
|
||||||
|
scrollExtentMax: 0,
|
||||||
|
scrollExtentMin: 0,
|
||||||
|
rect: const ui.Rect.fromLTRB(0, 0, 10, 10),
|
||||||
|
elevation: 0,
|
||||||
|
thickness: 0,
|
||||||
|
identifier: 'identifier',
|
||||||
|
label: 'label',
|
||||||
|
labelAttributes: const <ui.StringAttribute>[],
|
||||||
|
value: 'value',
|
||||||
|
valueAttributes: const <ui.StringAttribute>[],
|
||||||
|
increasedValue: 'increasedValue',
|
||||||
|
increasedValueAttributes: const <ui.StringAttribute>[],
|
||||||
|
decreasedValue: 'decreasedValue',
|
||||||
|
decreasedValueAttributes: const <ui.StringAttribute>[],
|
||||||
|
hint: 'hint',
|
||||||
|
hintAttributes: const <ui.StringAttribute>[],
|
||||||
|
tooltip: 'tooltip',
|
||||||
|
textDirection: ui.TextDirection.ltr,
|
||||||
|
transform: transform,
|
||||||
|
childrenInTraversalOrder: childrenInTraversalOrder,
|
||||||
|
childrenInHitTestOrder: childrenInHitTestOrder,
|
||||||
|
additionalActions: additionalActions,
|
||||||
|
role: ui.SemanticsRole.tab,
|
||||||
|
controlsNodes: null,
|
||||||
|
);
|
||||||
|
return builder.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
view1.updateSemantics(createSemanticsUpdate(view1.viewId + 1));
|
||||||
|
view2.updateSemantics(createSemanticsUpdate(view2.viewId + 1));
|
||||||
|
signal();
|
||||||
|
}
|
||||||
|
@ -354,9 +354,7 @@ bool FlutterWindowsEngine::Run(std::string_view entrypoint) {
|
|||||||
void* user_data) {
|
void* user_data) {
|
||||||
auto host = static_cast<FlutterWindowsEngine*>(user_data);
|
auto host = static_cast<FlutterWindowsEngine*>(user_data);
|
||||||
|
|
||||||
// TODO(loicsharma): Remove implicit view assumption.
|
auto view = host->view(update->view_id);
|
||||||
// https://github.com/flutter/flutter/issues/142845
|
|
||||||
auto view = host->view(kImplicitViewId);
|
|
||||||
if (!view) {
|
if (!view) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -518,6 +516,7 @@ std::unique_ptr<FlutterWindowsView> FlutterWindowsEngine::CreateView(
|
|||||||
view_id, this, std::move(window), windows_proc_table_);
|
view_id, this, std::move(window), windows_proc_table_);
|
||||||
|
|
||||||
view->CreateRenderSurface();
|
view->CreateRenderSurface();
|
||||||
|
view->UpdateSemanticsEnabled(semantics_enabled_);
|
||||||
|
|
||||||
next_view_id_++;
|
next_view_id_++;
|
||||||
|
|
||||||
@ -932,12 +931,19 @@ bool FlutterWindowsEngine::PostRasterThreadTask(fml::closure callback) const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool FlutterWindowsEngine::DispatchSemanticsAction(
|
bool FlutterWindowsEngine::DispatchSemanticsAction(
|
||||||
|
FlutterViewId view_id,
|
||||||
uint64_t target,
|
uint64_t target,
|
||||||
FlutterSemanticsAction action,
|
FlutterSemanticsAction action,
|
||||||
fml::MallocMapping data) {
|
fml::MallocMapping data) {
|
||||||
return (embedder_api_.DispatchSemanticsAction(engine_, target, action,
|
FlutterSendSemanticsActionInfo info{
|
||||||
data.GetMapping(),
|
.struct_size = sizeof(FlutterSendSemanticsActionInfo),
|
||||||
data.GetSize()) == kSuccess);
|
.view_id = view_id,
|
||||||
|
.node_id = target,
|
||||||
|
.action = action,
|
||||||
|
.data = data.GetMapping(),
|
||||||
|
.data_length = data.GetSize(),
|
||||||
|
};
|
||||||
|
return (embedder_api_.SendSemanticsAction(engine_, &info));
|
||||||
}
|
}
|
||||||
|
|
||||||
void FlutterWindowsEngine::UpdateSemanticsEnabled(bool enabled) {
|
void FlutterWindowsEngine::UpdateSemanticsEnabled(bool enabled) {
|
||||||
|
@ -237,7 +237,8 @@ class FlutterWindowsEngine {
|
|||||||
void OnVsync(intptr_t baton);
|
void OnVsync(intptr_t baton);
|
||||||
|
|
||||||
// Dispatches a semantics action to the specified semantics node.
|
// Dispatches a semantics action to the specified semantics node.
|
||||||
bool DispatchSemanticsAction(uint64_t id,
|
bool DispatchSemanticsAction(FlutterViewId view_id,
|
||||||
|
uint64_t node_id,
|
||||||
FlutterSemanticsAction action,
|
FlutterSemanticsAction action,
|
||||||
fml::MallocMapping data);
|
fml::MallocMapping data);
|
||||||
|
|
||||||
|
@ -482,20 +482,19 @@ TEST_F(FlutterWindowsEngineTest, DispatchSemanticsAction) {
|
|||||||
|
|
||||||
bool called = false;
|
bool called = false;
|
||||||
std::string message = "Hello";
|
std::string message = "Hello";
|
||||||
modifier.embedder_api().DispatchSemanticsAction = MOCK_ENGINE_PROC(
|
modifier.embedder_api().SendSemanticsAction = MOCK_ENGINE_PROC(
|
||||||
DispatchSemanticsAction,
|
SendSemanticsAction, ([&called, &message](auto engine, auto info) {
|
||||||
([&called, &message](auto engine, auto target, auto action, auto data,
|
|
||||||
auto data_length) {
|
|
||||||
called = true;
|
called = true;
|
||||||
EXPECT_EQ(target, 42);
|
EXPECT_EQ(info->view_id, 456);
|
||||||
EXPECT_EQ(action, kFlutterSemanticsActionDismiss);
|
EXPECT_EQ(info->node_id, 42);
|
||||||
EXPECT_EQ(memcmp(data, message.c_str(), message.size()), 0);
|
EXPECT_EQ(info->action, kFlutterSemanticsActionDismiss);
|
||||||
EXPECT_EQ(data_length, message.size());
|
EXPECT_EQ(memcmp(info->data, message.c_str(), message.size()), 0);
|
||||||
|
EXPECT_EQ(info->data_length, message.size());
|
||||||
return kSuccess;
|
return kSuccess;
|
||||||
}));
|
}));
|
||||||
|
|
||||||
auto data = fml::MallocMapping::Copy(message.c_str(), message.size());
|
auto data = fml::MallocMapping::Copy(message.c_str(), message.size());
|
||||||
engine->DispatchSemanticsAction(42, kFlutterSemanticsActionDismiss,
|
engine->DispatchSemanticsAction(456, 42, kFlutterSemanticsActionDismiss,
|
||||||
std::move(data));
|
std::move(data));
|
||||||
EXPECT_TRUE(called);
|
EXPECT_TRUE(called);
|
||||||
}
|
}
|
||||||
@ -1379,5 +1378,77 @@ TEST_F(FlutterWindowsEngineTest, OnViewFocusChangeRequest) {
|
|||||||
modifier.OnViewFocusChangeRequest(&request);
|
modifier.OnViewFocusChangeRequest(&request);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_F(FlutterWindowsEngineTest, UpdateSemanticsMultiView) {
|
||||||
|
auto& context = GetContext();
|
||||||
|
WindowsConfigBuilder builder{context};
|
||||||
|
builder.SetDartEntrypoint("sendSemanticsTreeInfo");
|
||||||
|
|
||||||
|
// Setup: a signal for when we have send out all of our semantics updates
|
||||||
|
bool done = false;
|
||||||
|
auto native_entry =
|
||||||
|
CREATE_NATIVE_ENTRY([&](Dart_NativeArguments args) { done = true; });
|
||||||
|
context.AddNativeFunction("Signal", native_entry);
|
||||||
|
|
||||||
|
// Setup: Create the engine and two views + enable semantics
|
||||||
|
EnginePtr engine{builder.RunHeadless()};
|
||||||
|
ASSERT_NE(engine, nullptr);
|
||||||
|
|
||||||
|
auto window_binding_handler1 =
|
||||||
|
std::make_unique<NiceMock<MockWindowBindingHandler>>();
|
||||||
|
auto window_binding_handler2 =
|
||||||
|
std::make_unique<NiceMock<MockWindowBindingHandler>>();
|
||||||
|
|
||||||
|
// The following mocks are required by
|
||||||
|
// FlutterWindowsView::CreateWindowMetricsEvent so that we create a valid
|
||||||
|
// view.
|
||||||
|
EXPECT_CALL(*window_binding_handler1, GetPhysicalWindowBounds)
|
||||||
|
.WillRepeatedly(testing::Return(PhysicalWindowBounds{100, 100}));
|
||||||
|
EXPECT_CALL(*window_binding_handler1, GetDpiScale)
|
||||||
|
.WillRepeatedly(testing::Return(96.0));
|
||||||
|
EXPECT_CALL(*window_binding_handler2, GetPhysicalWindowBounds)
|
||||||
|
.WillRepeatedly(testing::Return(PhysicalWindowBounds{200, 200}));
|
||||||
|
EXPECT_CALL(*window_binding_handler2, GetDpiScale)
|
||||||
|
.WillRepeatedly(testing::Return(96.0));
|
||||||
|
|
||||||
|
auto windows_engine = reinterpret_cast<FlutterWindowsEngine*>(engine.get());
|
||||||
|
EngineModifier modifier{windows_engine};
|
||||||
|
modifier.embedder_api().RunsAOTCompiledDartCode = []() { return false; };
|
||||||
|
|
||||||
|
// We want to avoid adding an implicit view as the first view
|
||||||
|
modifier.SetNextViewId(kImplicitViewId + 1);
|
||||||
|
|
||||||
|
auto view1 = windows_engine->CreateView(std::move(window_binding_handler1));
|
||||||
|
auto view2 = windows_engine->CreateView(std::move(window_binding_handler2));
|
||||||
|
|
||||||
|
// Act: UpdateSemanticsEnabled will trigger the semantics updates
|
||||||
|
// to get sent.
|
||||||
|
windows_engine->UpdateSemanticsEnabled(true);
|
||||||
|
|
||||||
|
while (!done) {
|
||||||
|
windows_engine->task_runner()->ProcessTasks();
|
||||||
|
}
|
||||||
|
|
||||||
|
auto accessibility_bridge1 = view1->accessibility_bridge().lock();
|
||||||
|
auto accessibility_bridge2 = view2->accessibility_bridge().lock();
|
||||||
|
|
||||||
|
// Expect: that the semantics trees are updated with their
|
||||||
|
// respective nodes.
|
||||||
|
while (
|
||||||
|
!accessibility_bridge1->GetPlatformNodeFromTree(view1->view_id() + 1)) {
|
||||||
|
windows_engine->task_runner()->ProcessTasks();
|
||||||
|
}
|
||||||
|
|
||||||
|
while (
|
||||||
|
!accessibility_bridge2->GetPlatformNodeFromTree(view2->view_id() + 1)) {
|
||||||
|
windows_engine->task_runner()->ProcessTasks();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rely on timeout mechanism in CI.
|
||||||
|
auto tree1 = accessibility_bridge1->GetTree();
|
||||||
|
auto tree2 = accessibility_bridge2->GetTree();
|
||||||
|
EXPECT_NE(tree1->GetFromId(view1->view_id() + 1), nullptr);
|
||||||
|
EXPECT_NE(tree2->GetFromId(view2->view_id() + 1), nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace testing
|
} // namespace testing
|
||||||
} // namespace flutter
|
} // namespace flutter
|
||||||
|
@ -91,6 +91,10 @@ class EngineModifier {
|
|||||||
engine_->OnViewFocusChangeRequest(request);
|
engine_->OnViewFocusChangeRequest(request);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void SetNextViewId(FlutterViewId view_id) {
|
||||||
|
engine_->next_view_id_ = view_id;
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
FlutterWindowsEngine* engine_;
|
FlutterWindowsEngine* engine_;
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user