1 /*
2  * Copyright (C) 2008 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  *
8  * 1.  Redistributions of source code must retain the above copyright
9  *     notice, this list of conditions and the following disclaimer.
10  * 2.  Redistributions in binary form must reproduce the above copyright
11  *     notice, this list of conditions and the following disclaimer in the
12  *     documentation and/or other materials provided with the distribution.
13  * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
14  *     its contributors may be used to endorse or promote products derived
15  *     from this software without specific prior written permission.
16  *
17  * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
18  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
19  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
20  * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
21  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
22  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
23  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
24  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27  */
28 
29 #include "third_party/blink/renderer/modules/accessibility/ax_list_box_option.h"
30 
31 #include "third_party/blink/renderer/core/aom/accessible_node.h"
32 #include "third_party/blink/renderer/core/html/forms/html_option_element.h"
33 #include "third_party/blink/renderer/core/html/forms/html_select_element.h"
34 #include "third_party/blink/renderer/core/layout/layout_object.h"
35 #include "third_party/blink/renderer/modules/accessibility/ax_object_cache_impl.h"
36 
37 namespace blink {
38 
AXListBoxOption(LayoutObject * layout_object,AXObjectCacheImpl & ax_object_cache)39 AXListBoxOption::AXListBoxOption(LayoutObject* layout_object,
40                                  AXObjectCacheImpl& ax_object_cache)
41     : AXLayoutObject(layout_object, ax_object_cache) {}
42 
43 AXListBoxOption::~AXListBoxOption() = default;
44 
DetermineAccessibilityRole()45 ax::mojom::Role AXListBoxOption::DetermineAccessibilityRole() {
46   if ((aria_role_ = DetermineAriaRoleAttribute()) != ax::mojom::Role::kUnknown)
47     return aria_role_;
48 
49   // http://www.w3.org/TR/wai-aria/complete#presentation
50   // ARIA spec says that the presentation role causes a given element to be
51   // treated as having no role or to be removed from the accessibility tree, but
52   // does not cause the content contained within the element to be removed from
53   // the accessibility tree.
54   if (IsParentPresentationalRole())
55     return ax::mojom::Role::kStaticText;
56 
57   return ax::mojom::Role::kListBoxOption;
58 }
59 
IsParentPresentationalRole() const60 bool AXListBoxOption::IsParentPresentationalRole() const {
61   LayoutObject* parent_layout_object = GetLayoutObject()->Parent();
62   if (!parent_layout_object)
63     return false;
64 
65   AXObject* parent = AXObjectCache().GetOrCreate(parent_layout_object);
66   if (!parent)
67     return false;
68 
69   if (IsListBox(parent_layout_object) &&
70       parent->HasInheritedPresentationalRole())
71     return true;
72 
73   return false;
74 }
75 
IsSelected() const76 AccessibilitySelectedState AXListBoxOption::IsSelected() const {
77   if (!GetNode() || !CanSetSelectedAttribute())
78     return kSelectedStateUndefined;
79 
80   auto* option_element = DynamicTo<HTMLOptionElement>(GetNode());
81   return (option_element && option_element->Selected()) ? kSelectedStateTrue
82                                                         : kSelectedStateFalse;
83 }
84 
IsSelectedOptionActive() const85 bool AXListBoxOption::IsSelectedOptionActive() const {
86   HTMLSelectElement* list_box_parent_node = ListBoxOptionParentNode();
87   if (!list_box_parent_node)
88     return false;
89 
90   return list_box_parent_node->ActiveSelectionEnd() == GetNode();
91 }
92 
ComputeAccessibilityIsIgnored(IgnoredReasons * ignored_reasons) const93 bool AXListBoxOption::ComputeAccessibilityIsIgnored(
94     IgnoredReasons* ignored_reasons) const {
95   if (!GetNode())
96     return true;
97 
98   if (AccessibilityIsIgnoredByDefault(ignored_reasons))
99     return true;
100 
101   return false;
102 }
103 
TextAlternative(bool recursive,bool in_aria_labelled_by_traversal,AXObjectSet & visited,ax::mojom::NameFrom & name_from,AXRelatedObjectVector * related_objects,NameSources * name_sources) const104 String AXListBoxOption::TextAlternative(bool recursive,
105                                         bool in_aria_labelled_by_traversal,
106                                         AXObjectSet& visited,
107                                         ax::mojom::NameFrom& name_from,
108                                         AXRelatedObjectVector* related_objects,
109                                         NameSources* name_sources) const {
110   // If nameSources is non-null, relatedObjects is used in filling it in, so it
111   // must be non-null as well.
112   if (name_sources)
113     DCHECK(related_objects);
114 
115   if (!GetNode())
116     return String();
117 
118   bool found_text_alternative = false;
119   String text_alternative = AriaTextAlternative(
120       recursive, in_aria_labelled_by_traversal, visited, name_from,
121       related_objects, name_sources, &found_text_alternative);
122   if (found_text_alternative && !name_sources)
123     return text_alternative;
124 
125   name_from = ax::mojom::NameFrom::kContents;
126   text_alternative = To<HTMLOptionElement>(GetNode())->DisplayLabel();
127   if (name_sources) {
128     name_sources->push_back(NameSource(found_text_alternative));
129     name_sources->back().type = name_from;
130     name_sources->back().text = text_alternative;
131     found_text_alternative = true;
132   }
133 
134   return text_alternative;
135 }
136 
OnNativeSetSelectedAction(bool selected)137 bool AXListBoxOption::OnNativeSetSelectedAction(bool selected) {
138   HTMLSelectElement* select_element = ListBoxOptionParentNode();
139   if (!select_element)
140     return false;
141 
142   if (!CanSetSelectedAttribute())
143     return false;
144 
145   AccessibilitySelectedState is_option_selected = IsSelected();
146   if (is_option_selected == kSelectedStateUndefined)
147     return false;
148 
149   bool is_selected = (is_option_selected == kSelectedStateTrue) ? true : false;
150   if ((is_selected && selected) || (!is_selected && !selected))
151     return false;
152 
153   select_element->SelectOptionByAccessKey(To<HTMLOptionElement>(GetNode()));
154   return true;
155 }
156 
ListBoxOptionParentNode() const157 HTMLSelectElement* AXListBoxOption::ListBoxOptionParentNode() const {
158   if (!GetNode())
159     return nullptr;
160 
161   if (auto* option = DynamicTo<HTMLOptionElement>(GetNode()))
162     return option->OwnerSelectElement();
163 
164   return nullptr;
165 }
166 
167 }  // namespace blink
168