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