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