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(children_.size() == 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
78 AXObject* popup = cache.GetOrCreate(ax::mojom::Role::kMenuListPopup);
79 if (!popup)
80 return;
81
82 To<AXMockObject>(popup)->SetParent(this);
83 if (!popup->AccessibilityIsIncludedInTree()) {
84 cache.Remove(popup->AXObjectID());
85 return;
86 }
87
88 children_.push_back(popup);
89
90 popup->AddChildren();
91 }
92
IsCollapsed() const93 bool AXMenuList::IsCollapsed() const {
94 // Collapsed is the "default" state, so if the LayoutObject doesn't exist
95 // this makes slightly more sense than returning false.
96 if (!layout_object_)
97 return true;
98
99 return !To<HTMLSelectElement>(GetNode())->PopupIsVisible();
100 }
101
IsExpanded() const102 AccessibilityExpanded AXMenuList::IsExpanded() const {
103 if (IsCollapsed())
104 return kExpandedCollapsed;
105
106 return kExpandedExpanded;
107 }
108
DidUpdateActiveOption(int option_index)109 void AXMenuList::DidUpdateActiveOption(int option_index) {
110 bool suppress_notifications =
111 (GetNode() && !GetNode()->IsFinishedParsingChildren());
112
113 if (HasChildren()) {
114 const auto& child_objects = Children();
115 if (!child_objects.IsEmpty()) {
116 DCHECK_EQ(child_objects.size(), 1ul);
117 DCHECK(IsA<AXMenuListPopup>(child_objects[0].Get()));
118
119 if (auto* popup = DynamicTo<AXMenuListPopup>(child_objects[0].Get()))
120 popup->DidUpdateActiveOption(option_index, !suppress_notifications);
121 }
122 }
123
124 AXObjectCache().PostNotification(this,
125 ax::mojom::Event::kMenuListValueChanged);
126 }
127
DidShowPopup()128 void AXMenuList::DidShowPopup() {
129 if (Children().size() != 1)
130 return;
131
132 auto* popup = To<AXMenuListPopup>(Children()[0].Get());
133 popup->DidShow();
134 }
135
DidHidePopup()136 void AXMenuList::DidHidePopup() {
137 if (Children().size() != 1)
138 return;
139
140 auto* popup = To<AXMenuListPopup>(Children()[0].Get());
141 popup->DidHide();
142
143 if (GetNode() && GetNode()->IsFocused())
144 AXObjectCache().PostNotification(this, ax::mojom::Event::kFocus);
145 }
146
147 } // namespace blink
148