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.h"
27
28 #include "third_party/blink/renderer/core/html/forms/html_select_element.h"
29 #include "third_party/blink/renderer/core/layout/layout_object.h"
30 #include "third_party/blink/renderer/modules/accessibility/ax_menu_list_popup.h"
31 #include "third_party/blink/renderer/modules/accessibility/ax_object_cache_impl.h"
32
33 namespace blink {
34
AXMenuList(LayoutObject * layout_object,AXObjectCacheImpl & ax_object_cache)35 AXMenuList::AXMenuList(LayoutObject* layout_object,
36 AXObjectCacheImpl& ax_object_cache)
37 : AXLayoutObject(layout_object, ax_object_cache) {
38 DCHECK(IsA<HTMLSelectElement>(layout_object->GetNode()));
39 }
40
DetermineAccessibilityRole()41 ax::mojom::Role AXMenuList::DetermineAccessibilityRole() {
42 if ((aria_role_ = DetermineAriaRoleAttribute()) != ax::mojom::Role::kUnknown)
43 return aria_role_;
44
45 return ax::mojom::Role::kPopUpButton;
46 }
47
OnNativeClickAction()48 bool AXMenuList::OnNativeClickAction() {
49 if (!layout_object_)
50 return false;
51
52 HTMLSelectElement* select = To<HTMLSelectElement>(GetNode());
53 if (select->PopupIsVisible())
54 select->HidePopup();
55 else
56 select->ShowPopup();
57 return true;
58 }
59
ClearChildren()60 void AXMenuList::ClearChildren() {
61 children_dirty_ = false;
62 if (children_.IsEmpty())
63 return;
64
65 // There's no reason to clear our AXMenuListPopup child. If we get a
66 // call to clearChildren, it's because the options might have changed,
67 // so call it on our popup.
68 DCHECK_EQ(ChildCountIncludingIgnored(), 1);
69 children_[0]->ClearChildren();
70 }
71
AddChildren()72 void AXMenuList::AddChildren() {
73 DCHECK(!IsDetached());
74 have_children_ = true;
75
76 AXObjectCacheImpl& cache = AXObjectCache();
77 AXObject* popup = cache.GetOrCreate(ax::mojom::Role::kMenuListPopup);
78 DCHECK(popup);
79
80 To<AXMockObject>(popup)->SetParent(this);
81 if (!popup->AccessibilityIsIncludedInTree()) {
82 cache.Remove(popup->AXObjectID());
83 return;
84 }
85
86 children_.push_back(popup);
87 popup->AddChildren();
88 }
89
IsCollapsed() const90 bool AXMenuList::IsCollapsed() const {
91 // Collapsed is the "default" state, so if the LayoutObject doesn't exist
92 // this makes slightly more sense than returning false.
93 if (!layout_object_)
94 return true;
95
96 return !To<HTMLSelectElement>(GetNode())->PopupIsVisible();
97 }
98
IsExpanded() const99 AccessibilityExpanded AXMenuList::IsExpanded() const {
100 if (IsCollapsed())
101 return kExpandedCollapsed;
102
103 return kExpandedExpanded;
104 }
105
DidUpdateActiveOption(int option_index)106 void AXMenuList::DidUpdateActiveOption(int option_index) {
107 bool suppress_notifications =
108 (GetNode() && !GetNode()->IsFinishedParsingChildren());
109
110 if (HasChildren()) {
111 const auto& child_objects = ChildrenIncludingIgnored();
112 if (!child_objects.IsEmpty()) {
113 DCHECK_EQ(child_objects.size(), 1ul);
114 DCHECK(IsA<AXMenuListPopup>(child_objects[0].Get()));
115
116 if (auto* popup = DynamicTo<AXMenuListPopup>(child_objects[0].Get()))
117 popup->DidUpdateActiveOption(option_index, !suppress_notifications);
118 }
119 }
120
121 AXObjectCache().PostNotification(this,
122 ax::mojom::Event::kMenuListValueChanged);
123 }
124
DidShowPopup()125 void AXMenuList::DidShowPopup() {
126 if (ChildCountIncludingIgnored() != 1)
127 return;
128
129 auto* popup = To<AXMenuListPopup>(ChildAtIncludingIgnored(0));
130 popup->DidShow();
131 }
132
DidHidePopup()133 void AXMenuList::DidHidePopup() {
134 if (ChildCountIncludingIgnored() != 1)
135 return;
136
137 auto* popup = To<AXMenuListPopup>(ChildAtIncludingIgnored(0));
138 popup->DidHide();
139
140 if (GetNode() && GetNode()->IsFocused())
141 AXObjectCache().PostNotification(this, ax::mojom::Event::kFocus);
142 }
143
144 } // namespace blink
145