1 /*
2 * Copyright (C) 2010 Apple Inc. All Rights Reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 * 1. Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
12 *
13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
14 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
17 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24 */
25
26 #include "third_party/blink/renderer/modules/accessibility/ax_menu_list_option.h"
27
28 #include "third_party/blink/renderer/core/aom/accessible_node.h"
29 #include "third_party/blink/renderer/core/html/forms/html_select_element.h"
30 #include "third_party/blink/renderer/modules/accessibility/ax_menu_list.h"
31 #include "third_party/blink/renderer/modules/accessibility/ax_menu_list_popup.h"
32 #include "third_party/blink/renderer/modules/accessibility/ax_object_cache_impl.h"
33 #include "third_party/skia/include/core/SkMatrix44.h"
34
35 namespace blink {
36
AXMenuListOption(HTMLOptionElement * element,AXObjectCacheImpl & ax_object_cache)37 AXMenuListOption::AXMenuListOption(HTMLOptionElement* element,
38 AXObjectCacheImpl& ax_object_cache)
39 : AXNodeObject(element, ax_object_cache), element_(element) {}
40
~AXMenuListOption()41 AXMenuListOption::~AXMenuListOption() {
42 DCHECK(!element_);
43 }
44
Detach()45 void AXMenuListOption::Detach() {
46 element_ = nullptr;
47 AXNodeObject::Detach();
48 }
49
DocumentFrameView() const50 LocalFrameView* AXMenuListOption::DocumentFrameView() const {
51 if (IsDetached())
52 return nullptr;
53 return element_->GetDocument().View();
54 }
55
RoleValue() const56 ax::mojom::Role AXMenuListOption::RoleValue() const {
57 const AtomicString& aria_role =
58 GetAOMPropertyOrARIAAttribute(AOMStringProperty::kRole);
59 if (aria_role.IsEmpty())
60 return ax::mojom::Role::kMenuListOption;
61
62 ax::mojom::Role role = AriaRoleToWebCoreRole(aria_role);
63 if (role != ax::mojom::Role::kUnknown)
64 return role;
65 return ax::mojom::Role::kMenuListOption;
66 }
67
ActionElement() const68 Element* AXMenuListOption::ActionElement() const {
69 return element_;
70 }
71
ComputeParent() const72 AXObject* AXMenuListOption::ComputeParent() const {
73 Node* node = GetNode();
74 if (!node)
75 return nullptr;
76 auto* select = To<HTMLOptionElement>(node)->OwnerSelectElement();
77 if (!select)
78 return nullptr;
79 AXObject* select_ax_object = AXObjectCache().GetOrCreate(select);
80 if (!select_ax_object)
81 return nullptr;
82
83 // This happens if the <select> is not rendered. Return it and move on.
84 auto* menu_list = DynamicTo<AXMenuList>(select_ax_object);
85 if (!menu_list)
86 return select_ax_object;
87
88 if (menu_list->HasChildren()) {
89 const auto& child_objects = menu_list->ChildrenIncludingIgnored();
90 if (child_objects.IsEmpty())
91 return nullptr;
92 DCHECK_EQ(child_objects.size(), 1UL);
93 DCHECK(IsA<AXMenuListPopup>(child_objects[0].Get()));
94 To<AXMenuListPopup>(child_objects[0].Get())->UpdateChildrenIfNecessary();
95 } else {
96 menu_list->UpdateChildrenIfNecessary();
97 }
98 return parent_.Get();
99 }
100
IsVisible() const101 bool AXMenuListOption::IsVisible() const {
102 if (!parent_)
103 return false;
104
105 // In a single-option select with the popup collapsed, only the selected
106 // item is considered visible.
107 return !parent_->IsOffScreen() ||
108 ((IsSelected() == kSelectedStateTrue) ? true : false);
109 }
110
IsOffScreen() const111 bool AXMenuListOption::IsOffScreen() const {
112 // Invisible list options are considered to be offscreen.
113 return !IsVisible();
114 }
115
IsSelected() const116 AccessibilitySelectedState AXMenuListOption::IsSelected() const {
117 if (!GetNode() || !CanSetSelectedAttribute())
118 return kSelectedStateUndefined;
119
120 AXMenuListPopup* parent = static_cast<AXMenuListPopup*>(ParentObject());
121 if (parent && !parent->IsOffScreen()) {
122 return ((parent->ActiveDescendant() == this) ? kSelectedStateTrue
123 : kSelectedStateFalse);
124 }
125 return ((element_ && element_->Selected()) ? kSelectedStateTrue
126 : kSelectedStateFalse);
127 }
128
OnNativeClickAction()129 bool AXMenuListOption::OnNativeClickAction() {
130 if (!element_)
131 return false;
132
133 if (IsA<AXMenuListPopup>(ParentObject())) {
134 // Clicking on an option within a menu list should first select that item
135 // (which should include firing `input` and `change` events), then toggle
136 // whether the menu list is showing.
137 static_cast<HTMLElement*>(element_)->AccessKeyAction(true);
138
139 // Calling OnNativeClickAction on the parent select element will toggle
140 // it open or closed.
141 return ParentObject()->OnNativeClickAction();
142 }
143
144 return AXNodeObject::OnNativeClickAction();
145 }
146
OnNativeSetSelectedAction(bool b)147 bool AXMenuListOption::OnNativeSetSelectedAction(bool b) {
148 if (!element_ || !CanSetSelectedAttribute())
149 return false;
150
151 element_->SetSelected(b);
152 return true;
153 }
154
ComputeAccessibilityIsIgnored(IgnoredReasons * ignored_reasons) const155 bool AXMenuListOption::ComputeAccessibilityIsIgnored(
156 IgnoredReasons* ignored_reasons) const {
157 if (IsInertOrAriaHidden()) {
158 if (ignored_reasons)
159 ComputeIsInertOrAriaHidden(ignored_reasons);
160 return true;
161 }
162
163 if (DynamicTo<HTMLOptionElement>(GetNode())->FastHasAttribute(
164 html_names::kHiddenAttr))
165 return true;
166
167 return AccessibilityIsIgnoredByDefault(ignored_reasons);
168 }
169
GetRelativeBounds(AXObject ** out_container,FloatRect & out_bounds_in_container,SkMatrix44 & out_container_transform,bool * clips_children) const170 void AXMenuListOption::GetRelativeBounds(AXObject** out_container,
171 FloatRect& out_bounds_in_container,
172 SkMatrix44& out_container_transform,
173 bool* clips_children) const {
174 *out_container = nullptr;
175 out_bounds_in_container = FloatRect();
176 out_container_transform.setIdentity();
177
178 AXObject* parent = ParentObject();
179 if (!parent)
180 return;
181 DCHECK(IsA<AXMenuListPopup>(parent));
182
183 AXObject* grandparent = parent->ParentObject();
184 if (!grandparent)
185 return;
186 DCHECK(grandparent->IsMenuList());
187 grandparent->GetRelativeBounds(out_container, out_bounds_in_container,
188 out_container_transform, clips_children);
189 }
190
TextAlternative(bool recursive,bool in_aria_labelled_by_traversal,AXObjectSet & visited,ax::mojom::NameFrom & name_from,AXRelatedObjectVector * related_objects,NameSources * name_sources) const191 String AXMenuListOption::TextAlternative(bool recursive,
192 bool in_aria_labelled_by_traversal,
193 AXObjectSet& visited,
194 ax::mojom::NameFrom& name_from,
195 AXRelatedObjectVector* related_objects,
196 NameSources* name_sources) const {
197 // If nameSources is non-null, relatedObjects is used in filling it in, so it
198 // must be non-null as well.
199 if (name_sources)
200 DCHECK(related_objects);
201
202 if (!GetNode())
203 return String();
204
205 bool found_text_alternative = false;
206 String text_alternative = AriaTextAlternative(
207 recursive, in_aria_labelled_by_traversal, visited, name_from,
208 related_objects, name_sources, &found_text_alternative);
209 if (found_text_alternative && !name_sources)
210 return text_alternative;
211
212 name_from = ax::mojom::NameFrom::kContents;
213 text_alternative = element_->DisplayLabel();
214 if (name_sources) {
215 name_sources->push_back(NameSource(found_text_alternative));
216 name_sources->back().type = name_from;
217 name_sources->back().text = text_alternative;
218 found_text_alternative = true;
219 }
220
221 return text_alternative;
222 }
223
ParentSelectNode() const224 HTMLSelectElement* AXMenuListOption::ParentSelectNode() const {
225 if (!GetNode())
226 return nullptr;
227
228 if (auto* option = DynamicTo<HTMLOptionElement>(GetNode()))
229 return option->OwnerSelectElement();
230
231 return nullptr;
232 }
233
Trace(Visitor * visitor) const234 void AXMenuListOption::Trace(Visitor* visitor) const {
235 visitor->Trace(element_);
236 AXNodeObject::Trace(visitor);
237 }
238
239 } // namespace blink
240