Skip to content

Commit a9c771c

Browse files
ci_lynxjianliang00
authored andcommitted
[Optimize][Clay] Support a11y navigation in scrollable components as list and scroll-view.
A11y navigation in <scoll-view> and <list> is special. When a11y navigation focuses on the rect that refers to the last scoll-item visible on screen, the next navigation should also trigger the component scrolling. This commit supports this behavior on clay iOS and especially optimize it with sroll-view and list-container (C++ list in lynx). SkipChecks: macro issue: m-6932034740
1 parent 44766c9 commit a9c771c

18 files changed

Lines changed: 460 additions & 154 deletions

clay/ui/component/base_view.cc

Lines changed: 225 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,13 @@
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
84202
bool 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+
373517
void 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-
37623917
int32_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

37843971
int32_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>()) {

clay/ui/component/base_view.h

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -449,18 +449,13 @@ class BaseView : public TypeIdentifiable<BaseView>,
449449
void FocusHasChanged(bool focus, bool is_leaf) override;
450450
// Return true if need to propagate scrolling, or false to stop propagate.
451451
virtual bool OnScrollToVisible() { return true; }
452-
virtual bool OnScrollToMiddle(BaseView* target_view) { return true; }
453452
// Make a view visible in scrollable container.
454453
void ScrollToVisible() {
455454
if (OnScrollToVisible() && Parent()) {
456455
Parent()->ScrollToVisible();
457456
}
458457
}
459-
void ScrollToMiddle(BaseView* target_view) {
460-
if (OnScrollToMiddle(target_view) && Parent()) {
461-
Parent()->ScrollToMiddle(target_view);
462-
}
463-
}
458+
void ScrollToMiddle(BaseView* target_view);
464459
void SetHasDefaultFocusRing(bool has_focus_ring) override;
465460
FloatRect CalcFocusRect() const override;
466461
bool DispatchKeyEventOnFocusNode(const KeyEvent* event) override;
@@ -629,7 +624,7 @@ class BaseView : public TypeIdentifiable<BaseView>,
629624
void DecodeImagesRecursively();
630625

631626
#ifdef ENABLE_ACCESSIBILITY
632-
virtual FloatRect GetSemanticsBounds() const;
627+
FloatRect GetSemanticsBounds() const;
633628
// Whether enables a11y for this view.
634629
void SetAccessibilityElement(bool value);
635630
virtual bool IsAccessibilityElement() const;
@@ -700,9 +695,13 @@ class BaseView : public TypeIdentifiable<BaseView>,
700695
#ifdef ENABLE_ACCESSIBILITY
701696
// Some views can update its specific semantics data.
702697
void UpdateSemanticsData(BaseView* parent_node_view);
703-
virtual int32_t GetA11yScrollChildren() const;
704-
virtual int32_t GetSemanticsActions() const;
705-
virtual int32_t GetSemanticsFlags() const;
698+
void PrepareSemanticsWithChildren(
699+
const std::vector<BaseView*>& children,
700+
fml::RefPtr<SemanticsNode> parent_node,
701+
std::vector<fml::RefPtr<SemanticsNode>>& result,
702+
const std::vector<std::string>& ancestor_a11y_elements);
703+
int32_t GetSemanticsActions() const;
704+
int32_t GetSemanticsFlags() const;
706705

707706
SemanticsOwner* GetSemanticsOwner() const;
708707

0 commit comments

Comments
 (0)