1/*
2 * Copyright (C) 2008 Nuanti Ltd.
3 * Copyright (C) 2009 Jan Alonzo
4 * Copyright (C) 2010, 2011, 2012 Igalia S.L.
5 *
6 * Portions from Mozilla a11y, copyright as follows:
7 *
8 * The Original Code is mozilla.org code.
9 *
10 * The Initial Developer of the Original Code is
11 * Sun Microsystems, Inc.
12 * Portions created by the Initial Developer are Copyright (C) 2002
13 * the Initial Developer. All Rights Reserved.
14 *
15 * This library is free software; you can redistribute it and/or
16 * modify it under the terms of the GNU Library General Public
17 * License as published by the Free Software Foundation; either
18 * version 2 of the License, or (at your option) any later version.
19 *
20 * This library is distributed in the hope that it will be useful,
21 * but WITHOUT ANY WARRANTY; without even the implied warranty of
22 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
23 * Library General Public License for more details.
24 *
25 * You should have received a copy of the GNU Library General Public License
26 * along with this library; see the file COPYING.LIB. If not, write to
27 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
28 * Boston, MA 02110-1301, USA.
29 */
30
31#include "config.h"
32#include "WebKitAccessibleInterfaceSelection.h"
33
34#if HAVE(ACCESSIBILITY)
35
36#include "AccessibilityListBox.h"
37#include "AccessibilityObject.h"
38#include "HTMLSelectElement.h"
39#include "RenderObject.h"
40#include "WebKitAccessible.h"
41#include "WebKitAccessibleUtil.h"
42
43using namespace WebCore;
44
45static AccessibilityObject* core(AtkSelection* selection)
46{
47 if (!WEBKIT_IS_ACCESSIBLE(selection))
48 return nullptr;
49
50 return &webkitAccessibleGetAccessibilityObject(WEBKIT_ACCESSIBLE(selection));
51}
52
53static AccessibilityObject* listObjectForSelection(AtkSelection* selection)
54{
55 AccessibilityObject* coreSelection = core(selection);
56
57 // Only list boxes and menu lists supported so far.
58 if (!coreSelection->isListBox() && !coreSelection->isMenuList())
59 return nullptr;
60
61 // For list boxes the list object is just itself.
62 if (coreSelection->isListBox())
63 return coreSelection;
64
65 // For menu lists we need to return the first accessible child,
66 // with role MenuListPopupRole, since that's the one holding the list
67 // of items with role MenuListOptionRole.
68 const AccessibilityObject::AccessibilityChildrenVector& children = coreSelection->children();
69 if (!children.size())
70 return nullptr;
71
72 AccessibilityObject* listObject = children.at(0).get();
73 if (!listObject->isMenuListPopup())
74 return nullptr;
75
76 return listObject;
77}
78
79static AccessibilityObject* optionFromList(AtkSelection* selection, gint index)
80{
81 AccessibilityObject* coreSelection = core(selection);
82 if (!coreSelection || index < 0)
83 return nullptr;
84
85 // Need to select the proper list object depending on the type.
86 AccessibilityObject* listObject = listObjectForSelection(selection);
87 if (!listObject)
88 return nullptr;
89
90 const AccessibilityObject::AccessibilityChildrenVector& options = listObject->children();
91 if (index < static_cast<gint>(options.size()))
92 return options.at(index).get();
93
94 return nullptr;
95}
96
97static AccessibilityObject* optionFromSelection(AtkSelection* selection, gint index)
98{
99 AccessibilityObject* coreSelection = core(selection);
100 if (!coreSelection || !coreSelection->isAccessibilityRenderObject() || index < 0)
101 return nullptr;
102
103 // This method provides the functionality expected by atk_selection_ref_selection().
104 // According to the ATK documentation for this method, the index is "a gint specifying
105 // the index in the selection set. (e.g. the ith selection as opposed to the ith child)."
106 // There is different API, namely atk_object_ref_accessible_child(), when the ith child
107 // from the set of all children is sought.
108 AccessibilityObject::AccessibilityChildrenVector options;
109 coreSelection->selectedChildren(options);
110 if (index < static_cast<gint>(options.size()))
111 return options.at(index).get();
112
113 return nullptr;
114}
115
116static gboolean webkitAccessibleSelectionAddSelection(AtkSelection* selection, gint index)
117{
118 g_return_val_if_fail(ATK_SELECTION(selection), FALSE);
119 returnValIfWebKitAccessibleIsInvalid(WEBKIT_ACCESSIBLE(selection), FALSE);
120
121 AccessibilityObject* coreSelection = core(selection);
122 if (!coreSelection)
123 return FALSE;
124
125 AccessibilityObject* option = optionFromList(selection, index);
126 if (option && (coreSelection->isListBox() || coreSelection->isMenuList())) {
127 option->setSelected(true);
128 return option->isSelected();
129 }
130
131 return FALSE;
132}
133
134static gboolean webkitAccessibleSelectionClearSelection(AtkSelection* selection)
135{
136 g_return_val_if_fail(ATK_SELECTION(selection), FALSE);
137 returnValIfWebKitAccessibleIsInvalid(WEBKIT_ACCESSIBLE(selection), FALSE);
138
139 AccessibilityObject* coreSelection = core(selection);
140 if (!coreSelection)
141 return FALSE;
142
143 AccessibilityObject::AccessibilityChildrenVector selectedItems;
144 if (is<AccessibilityListBox>(*coreSelection)) {
145 // Set the list of selected items to an empty list; then verify that it worked.
146 auto& listBox = downcast<AccessibilityListBox>(*coreSelection);
147 listBox.setSelectedChildren(selectedItems);
148 listBox.selectedChildren(selectedItems);
149 return selectedItems.isEmpty();
150 }
151 return FALSE;
152}
153
154static AtkObject* webkitAccessibleSelectionRefSelection(AtkSelection* selection, gint index)
155{
156 g_return_val_if_fail(ATK_SELECTION(selection), nullptr);
157 returnValIfWebKitAccessibleIsInvalid(WEBKIT_ACCESSIBLE(selection), nullptr);
158
159 AccessibilityObject* option = optionFromSelection(selection, index);
160 if (option) {
161 auto* child = option->wrapper();
162 g_object_ref(child);
163 return ATK_OBJECT(child);
164 }
165
166 return nullptr;
167}
168
169static gint webkitAccessibleSelectionGetSelectionCount(AtkSelection* selection)
170{
171 g_return_val_if_fail(ATK_SELECTION(selection), 0);
172 returnValIfWebKitAccessibleIsInvalid(WEBKIT_ACCESSIBLE(selection), 0);
173
174 AccessibilityObject* coreSelection = core(selection);
175 if (!coreSelection || !coreSelection->isAccessibilityRenderObject())
176 return 0;
177
178 if (coreSelection->isMenuList()) {
179 RenderObject* renderer = coreSelection->renderer();
180 if (!renderer)
181 return 0;
182
183 int selectedIndex = downcast<HTMLSelectElement>(renderer->node())->selectedIndex();
184 return selectedIndex >= 0 && selectedIndex < static_cast<int>(downcast<HTMLSelectElement>(renderer->node())->listItems().size());
185 }
186
187 AccessibilityObject::AccessibilityChildrenVector selectedItems;
188 coreSelection->selectedChildren(selectedItems);
189 return static_cast<gint>(selectedItems.size());
190}
191
192static gboolean webkitAccessibleSelectionIsChildSelected(AtkSelection* selection, gint index)
193{
194 g_return_val_if_fail(ATK_SELECTION(selection), FALSE);
195 returnValIfWebKitAccessibleIsInvalid(WEBKIT_ACCESSIBLE(selection), FALSE);
196
197 AccessibilityObject* coreSelection = core(selection);
198 if (!coreSelection)
199 return FALSE;
200
201 AccessibilityObject* option = optionFromList(selection, index);
202 if (option && (coreSelection->isListBox() || coreSelection->isMenuList()))
203 return option->isSelected();
204
205 return FALSE;
206}
207
208static gboolean webkitAccessibleSelectionRemoveSelection(AtkSelection* selection, gint index)
209{
210 g_return_val_if_fail(ATK_SELECTION(selection), FALSE);
211 returnValIfWebKitAccessibleIsInvalid(WEBKIT_ACCESSIBLE(selection), FALSE);
212
213 AccessibilityObject* coreSelection = core(selection);
214 if (!coreSelection)
215 return FALSE;
216
217 AccessibilityObject* option = optionFromSelection(selection, index);
218 if (option && (coreSelection->isListBox() || coreSelection->isMenuList())) {
219 option->setSelected(false);
220 return !option->isSelected();
221 }
222
223 return FALSE;
224}
225
226static gboolean webkitAccessibleSelectionSelectAllSelection(AtkSelection* selection)
227{
228 g_return_val_if_fail(ATK_SELECTION(selection), FALSE);
229 returnValIfWebKitAccessibleIsInvalid(WEBKIT_ACCESSIBLE(selection), FALSE);
230
231 AccessibilityObject* coreSelection = core(selection);
232 if (!coreSelection || !coreSelection->isMultiSelectable())
233 return FALSE;
234
235 if (is<AccessibilityListBox>(*coreSelection)) {
236 const AccessibilityObject::AccessibilityChildrenVector& children = coreSelection->children();
237 AccessibilityListBox& listBox = downcast<AccessibilityListBox>(*coreSelection);
238 listBox.setSelectedChildren(children);
239 AccessibilityObject::AccessibilityChildrenVector selectedItems;
240 listBox.selectedChildren(selectedItems);
241 return selectedItems.size() == children.size();
242 }
243
244 return FALSE;
245}
246
247void webkitAccessibleSelectionInterfaceInit(AtkSelectionIface* iface)
248{
249 iface->add_selection = webkitAccessibleSelectionAddSelection;
250 iface->clear_selection = webkitAccessibleSelectionClearSelection;
251 iface->ref_selection = webkitAccessibleSelectionRefSelection;
252 iface->get_selection_count = webkitAccessibleSelectionGetSelectionCount;
253 iface->is_child_selected = webkitAccessibleSelectionIsChildSelected;
254 iface->remove_selection = webkitAccessibleSelectionRemoveSelection;
255 iface->select_all_selection = webkitAccessibleSelectionSelectAllSelection;
256}
257
258#endif
259