3232#include " clay/ui/component/expose_manager/expose_observer.h"
3333#include " clay/ui/component/inline_image_view.h"
3434#include " clay/ui/component/intersection_observer.h"
35+ #ifdef ENABLE_ACCESSIBILITY
36+ #include " clay/ui/component/list/base_list_view.h"
37+ #include " clay/ui/component/list/list_container/list_container_view.h"
38+ #include " clay/ui/component/list/list_container/list_container_wrapper.h"
39+ #include " clay/ui/component/list/list_wrapper.h"
40+ #include " clay/ui/component/scroll_wrapper.h"
41+ #endif
3542#include " clay/ui/component/native_view.h"
3643#include " clay/ui/component/page_view.h"
3744#include " clay/ui/component/scroll_view.h"
@@ -80,6 +87,117 @@ bool ShouldPassEventToNativeInherited(BaseView* view) {
8087 }
8188}
8289
90+ #ifdef ENABLE_ACCESSIBILITY
91+ BaseView* A11yScrollTargetForSemantics (BaseView* view) {
92+ if (!view) {
93+ return nullptr ;
94+ }
95+ if (view->Is <ListWrapper>()) {
96+ return static_cast <ListWrapper*>(view)->GetListView ();
97+ }
98+ if (view->Is <ListContainerWrapper>()) {
99+ return static_cast <ListContainerWrapper*>(view)->GetListContainerView ();
100+ }
101+ if (view->Is <ScrollWrapper>()) {
102+ return static_cast <ScrollWrapper*>(view)->GetScrollView ();
103+ }
104+ if (view->Is <ScrollView>()) {
105+ return view;
106+ }
107+ if (view->Is <BaseListView>()) {
108+ return view;
109+ }
110+ return nullptr ;
111+ }
112+
113+ int32_t CountAccessibleDescendants (BaseView* view) {
114+ if (!view) {
115+ return 0 ;
116+ }
117+ int32_t count = 0 ;
118+ for (auto * child : view->GetChildren ()) {
119+ if (!child) {
120+ continue ;
121+ }
122+ if (child->IsAccessibilityElement ()) {
123+ ++count;
124+ } else {
125+ count += CountAccessibleDescendants (child);
126+ }
127+ }
128+ return count;
129+ }
130+
131+ int32_t A11yScrollTargetActions (BaseView* target) {
132+ if (!target) {
133+ return 0 ;
134+ }
135+ if (target->Is <ScrollView>()) {
136+ return static_cast <ScrollView*>(target)->GetSemanticsActions ();
137+ }
138+ #ifndef ENABLE_NATIVE_LIST
139+ if (target->Is <BaseListView>()) {
140+ return static_cast <BaseListView*>(target)->GetSemanticsActions ();
141+ }
142+ #endif
143+ return 0 ;
144+ }
145+
146+ int32_t A11yScrollTargetFlags (BaseView* target) {
147+ if (!target) {
148+ return 0 ;
149+ }
150+ if (target->Is <ScrollView>()) {
151+ return static_cast <ScrollView*>(target)->GetSemanticsFlags ();
152+ }
153+ #ifndef ENABLE_NATIVE_LIST
154+ if (target->Is <BaseListView>()) {
155+ return static_cast <BaseListView*>(target)->GetSemanticsFlags ();
156+ }
157+ #endif
158+ return 0 ;
159+ }
160+
161+ float A11yScrollPosition (BaseView* target) {
162+ if (!target) {
163+ return 0 .f ;
164+ }
165+ if (target->Is <ScrollView>()) {
166+ auto * scroll_view = static_cast <ScrollView*>(target);
167+ return scroll_view->CanScrollY () ? scroll_view->GetScrollOffset ().y ()
168+ : scroll_view->GetScrollOffset ().x ();
169+ }
170+ #ifndef ENABLE_NATIVE_LIST
171+ if (target->Is <BaseListView>()) {
172+ return static_cast <BaseListView*>(target)->GetScrollbarScrollOffset ();
173+ }
174+ #endif
175+ return 0 .f ;
176+ }
177+
178+ float A11yScrollExtentMax (BaseView* target) {
179+ if (!target) {
180+ return 0 .f ;
181+ }
182+ if (target->Is <ScrollView>()) {
183+ auto * scroll_view = static_cast <ScrollView*>(target);
184+ return scroll_view->CanScrollY ()
185+ ? scroll_view->GetRenderScroll ()->MaxScrollHeight ()
186+ : scroll_view->GetRenderScroll ()->MaxScrollWidth ();
187+ }
188+ #ifndef ENABLE_NATIVE_LIST
189+ if (target->Is <BaseListView>()) {
190+ auto * list_view = static_cast <BaseListView*>(target);
191+ const float viewport_length = list_view->CanDragScrollOnY ()
192+ ? list_view->Height ()
193+ : list_view->Width ();
194+ return std::max (0 .f , list_view->GetTotalScrollLength () - viewport_length);
195+ }
196+ #endif
197+ return 0 .f ;
198+ }
199+ #endif
200+
83201#if OS_IOS
84202bool IsBoundsTransitionProperty (ClayAnimationPropertyType type) {
85203 switch (type) {
@@ -370,6 +488,32 @@ Scrollable* BaseView::FindAncestorScrollableView(BaseView* child) {
370488 return nullptr ;
371489}
372490
491+ void BaseView::ScrollToMiddle (BaseView* target_view) {
492+ #ifdef ENABLE_ACCESSIBILITY
493+ bool should_propagate = true ;
494+ if (Is<ListContainerView>()) {
495+ should_propagate =
496+ static_cast <ListContainerView*>(this )->OnScrollToMiddle (target_view);
497+ }
498+ #ifndef ENABLE_NATIVE_LIST
499+ else if (Is<BaseListView>()) {
500+ should_propagate =
501+ static_cast <BaseListView*>(this )->OnScrollToMiddle (target_view);
502+ }
503+ #endif
504+ else if (Is<ScrollView>()) {
505+ should_propagate =
506+ static_cast <ScrollView*>(this )->OnScrollToMiddle (target_view);
507+ }
508+ if (!should_propagate) {
509+ return ;
510+ }
511+ #endif
512+ if (Parent ()) {
513+ Parent ()->ScrollToMiddle (target_view);
514+ }
515+ }
516+
373517void BaseView::AddGestureRecognizer (
374518 std::unique_ptr<GestureRecognizer> recognizer) {
375519 gesture_recognizers_.push_back (std::move (recognizer));
@@ -3741,11 +3885,24 @@ void BaseView::UpdateSemanticsData(BaseView* parent_node_view) {
37413885 // SemanticsData has changed.
37423886 semantics_->TransferCurrentData ();
37433887
3744- semantics_->GetSemanticsData ().actions = GetSemanticsActions ();
3745- semantics_->GetSemanticsData ().flags = GetSemanticsFlags ();
3746- semantics_->GetSemanticsData ().scroll_children = GetA11yScrollChildren ();
3888+ BaseView* a11y_scroll_target = A11yScrollTargetForSemantics (this );
3889+ semantics_->GetSemanticsData ().actions =
3890+ a11y_scroll_target ? A11yScrollTargetActions (a11y_scroll_target)
3891+ : GetSemanticsActions ();
3892+ semantics_->GetSemanticsData ().flags =
3893+ a11y_scroll_target ? A11yScrollTargetFlags (a11y_scroll_target)
3894+ : GetSemanticsFlags ();
3895+ semantics_->GetSemanticsData ().scroll_children =
3896+ CountAccessibleDescendants (a11y_scroll_target);
3897+ semantics_->GetSemanticsData ().scroll_position =
3898+ A11yScrollPosition (a11y_scroll_target);
3899+ semantics_->GetSemanticsData ().scroll_extent_max =
3900+ A11yScrollExtentMax (a11y_scroll_target);
3901+ semantics_->GetSemanticsData ().scroll_extent_min = 0 .f ;
37473902 // CAUTION: SemanticsBounds need to be the global bounds to PageView.
3748- semantics_->GetSemanticsData ().semantics_bounds = GetSemanticsBounds ();
3903+ semantics_->GetSemanticsData ().semantics_bounds =
3904+ a11y_scroll_target ? a11y_scroll_target->GetSemanticsBounds ()
3905+ : GetSemanticsBounds ();
37493906 semantics_->GetSemanticsData ().transform =
37503907 AccumulateTransformFromView (parent_node_view);
37513908
@@ -3757,8 +3914,6 @@ void BaseView::UpdateSemanticsData(BaseView* parent_node_view) {
37573914 semantics_->GetSemanticsData ().label = GetAccessibilityLabel ();
37583915}
37593916
3760- int32_t BaseView::GetA11yScrollChildren () const { return 0 ; }
3761-
37623917int32_t BaseView::GetSemanticsActions () const {
37633918 int32_t actions = 0 ;
37643919 if (HasTapEvent () || HasTapGestureRecognizer ()) {
@@ -3767,25 +3922,60 @@ int32_t BaseView::GetSemanticsActions() const {
37673922 if (HasLongPressEvent () || HasLongPressGestureRecognizer ()) {
37683923 actions |= static_cast <int32_t >(SemanticsNode::SemanticsAction::kLongPress );
37693924 }
3770- if (Parent () && Parent ()->Is <ScrollView>()) {
3771- if (static_cast <ScrollView*>(Parent ())->CanScrollX ()) {
3772- actions |=
3773- static_cast <int32_t >(SemanticsNode::SemanticsAction::kScrollLeft ) |
3774- static_cast <int32_t >(SemanticsNode::SemanticsAction::kScrollRight );
3775- } else if (static_cast <ScrollView*>(Parent ())->CanScrollY ()) {
3776- actions |=
3777- static_cast <int32_t >(SemanticsNode::SemanticsAction::kScrollUp ) |
3778- static_cast <int32_t >(SemanticsNode::SemanticsAction::kScrollDown );
3925+ auto add_scroll_actions = [&actions](ScrollableDirection direction,
3926+ bool can_scroll_x, bool can_scroll_y) {
3927+ if (can_scroll_x) {
3928+ if ((direction & ScrollableDirection::kLeftwards ) !=
3929+ ScrollableDirection::kNone ) {
3930+ actions |=
3931+ static_cast <int32_t >(SemanticsNode::SemanticsAction::kScrollLeft );
3932+ }
3933+ if ((direction & ScrollableDirection::kRightwards ) !=
3934+ ScrollableDirection::kNone ) {
3935+ actions |=
3936+ static_cast <int32_t >(SemanticsNode::SemanticsAction::kScrollRight );
3937+ }
3938+ } else if (can_scroll_y) {
3939+ if ((direction & ScrollableDirection::kUpwards ) !=
3940+ ScrollableDirection::kNone ) {
3941+ actions |=
3942+ static_cast <int32_t >(SemanticsNode::SemanticsAction::kScrollUp );
3943+ }
3944+ if ((direction & ScrollableDirection::kDownwards ) !=
3945+ ScrollableDirection::kNone ) {
3946+ actions |=
3947+ static_cast <int32_t >(SemanticsNode::SemanticsAction::kScrollDown );
3948+ }
3949+ }
3950+ };
3951+ for (BaseView* parent = Parent (); parent; parent = parent->Parent ()) {
3952+ if (parent->Is <ScrollView>()) {
3953+ auto * scroll_view = static_cast <ScrollView*>(parent);
3954+ add_scroll_actions (scroll_view->GetScrollableDirection (),
3955+ scroll_view->CanScrollX (), scroll_view->CanScrollY ());
3956+ break ;
37793957 }
3958+ #ifndef ENABLE_NATIVE_LIST
3959+ if (parent->Is <BaseListView>()) {
3960+ auto * list_view = static_cast <BaseListView*>(parent);
3961+ add_scroll_actions (list_view->GetScrollableDirection (),
3962+ list_view->CanDragScrollOnX (),
3963+ list_view->CanDragScrollOnY ());
3964+ break ;
3965+ }
3966+ #endif
37803967 }
37813968 return actions;
37823969}
37833970
37843971int32_t BaseView::GetSemanticsFlags () const {
37853972 int32_t flags = 0 ;
3786- if (Parent () && Parent ()->Is <ScrollView>()) {
3787- flags |= static_cast <int32_t >(
3788- SemanticsNode::SemanticsFlag::kHasImplicitScrolling );
3973+ for (BaseView* parent = Parent (); parent; parent = parent->Parent ()) {
3974+ if (parent->Is <ScrollView>() || parent->Is <BaseListView>()) {
3975+ flags |= static_cast <int32_t >(
3976+ SemanticsNode::SemanticsFlag::kHasImplicitScrolling );
3977+ break ;
3978+ }
37893979 }
37903980 return flags;
37913981}
@@ -3811,14 +4001,23 @@ void BaseView::PrepareSemantics(
38114001 FML_DCHECK (GetSemanticsOwner () &&
38124002 GetSemanticsOwner ()->NeedRebuildSemanticsTree ());
38134003 FML_DCHECK (Parent ());
4004+ PrepareSemanticsWithChildren (children_, parent_node, result,
4005+ ancestor_a11y_elements);
4006+ }
4007+
4008+ void BaseView::PrepareSemanticsWithChildren (
4009+ const std::vector<BaseView*>& children,
4010+ fml::RefPtr<SemanticsNode> parent_node,
4011+ std::vector<fml::RefPtr<SemanticsNode>>& result,
4012+ const std::vector<std::string>& ancestor_a11y_elements) {
38144013 // This view is not a11y visible when its ancestors have specified
38154014 // accessibility-elements and its id_selector is not inside it.
38164015 if (!IsAccessibilityElement () ||
38174016 (ancestor_a11y_elements.size () > 0 &&
38184017 (id_selector_.empty () ||
38194018 std::find (ancestor_a11y_elements.begin (), ancestor_a11y_elements.end (),
38204019 id_selector_) == ancestor_a11y_elements.end ()))) {
3821- for (auto * view : children_ ) {
4020+ for (auto * view : children ) {
38224021 view->PrepareSemantics (parent_node, result, ancestor_a11y_elements);
38234022 }
38244023 return ;
@@ -3834,7 +4033,7 @@ void BaseView::PrepareSemantics(
38344033 std::vector<fml::RefPtr<SemanticsNode>> new_children;
38354034 std::vector<std::string> a11y_elements =
38364035 accessibility_elements_.value_or (ancestor_a11y_elements);
3837- for (auto * view : children_ ) {
4036+ for (auto * view : children ) {
38384037 view->PrepareSemantics (semantics_, new_children, a11y_elements);
38394038 }
38404039 UpdateSemantics (new_children, parent_node->OwnerView (), true , true );
@@ -3854,6 +4053,12 @@ bool BaseView::IsAccessibilityElement() const {
38544053 Is<TextAreaView>() || Is<TextAreaNGView>()) {
38554054 return accessibility_element_.value_or (true );
38564055 }
4056+ BaseView* a11y_scroll_target =
4057+ A11yScrollTargetForSemantics (const_cast <BaseView*>(this ));
4058+ if (!IsInternalView () && a11y_scroll_target &&
4059+ CountAccessibleDescendants (a11y_scroll_target) > 0 ) {
4060+ return true ;
4061+ }
38574062 // Only views created from lynx should be visible to a11y system.
38584063 // RawTextView is also internal view but id is not -1.
38594064 if (IsInternalView () || Is<RawTextView>()) {
0 commit comments