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