1 /*
2 * Copyright (C) 2008 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 *
8 * 1. Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 * notice, this list of conditions and the following disclaimer in the
12 * documentation and/or other materials provided with the distribution.
13 * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of
14 * its contributors may be used to endorse or promote products derived
15 * from this software without specific prior written permission.
16 *
17 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
18 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
19 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
20 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
21 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
22 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
23 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
24 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 */
28
29 #include "config.h"
30 #include "AXObjectCache.h"
31
32 #include "AccessibilityARIAGrid.h"
33 #include "AccessibilityARIAGridRow.h"
34 #include "AccessibilityARIAGridCell.h"
35 #include "AccessibilityList.h"
36 #include "AccessibilityListBox.h"
37 #include "AccessibilityListBoxOption.h"
38 #include "AccessibilityImageMapLink.h"
39 #include "AccessibilityRenderObject.h"
40 #include "AccessibilitySlider.h"
41 #include "AccessibilityTable.h"
42 #include "AccessibilityTableCell.h"
43 #include "AccessibilityTableColumn.h"
44 #include "AccessibilityTableHeaderContainer.h"
45 #include "AccessibilityTableRow.h"
46 #include "InputElement.h"
47 #include "HTMLNames.h"
48 #include "RenderObject.h"
49 #include "RenderView.h"
50
51 #include <wtf/PassRefPtr.h>
52
53 namespace WebCore {
54
55 using namespace HTMLNames;
56
57 bool AXObjectCache::gAccessibilityEnabled = false;
58 bool AXObjectCache::gAccessibilityEnhancedUserInterfaceEnabled = false;
59
AXObjectCache()60 AXObjectCache::AXObjectCache()
61 : m_notificationPostTimer(this, &AXObjectCache::notificationPostTimerFired)
62 {
63 }
64
~AXObjectCache()65 AXObjectCache::~AXObjectCache()
66 {
67 HashMap<AXID, RefPtr<AccessibilityObject> >::iterator end = m_objects.end();
68 for (HashMap<AXID, RefPtr<AccessibilityObject> >::iterator it = m_objects.begin(); it != end; ++it) {
69 AccessibilityObject* obj = (*it).second.get();
70 detachWrapper(obj);
71 obj->detach();
72 removeAXID(obj);
73 }
74 }
75
get(RenderObject * renderer)76 AccessibilityObject* AXObjectCache::get(RenderObject* renderer)
77 {
78 if (!renderer)
79 return 0;
80
81 AccessibilityObject* obj = 0;
82 AXID axID = m_renderObjectMapping.get(renderer);
83 ASSERT(!HashTraits<AXID>::isDeletedValue(axID));
84
85 if (axID)
86 obj = m_objects.get(axID).get();
87
88 return obj;
89 }
90
nodeIsAriaType(Node * node,String role)91 bool AXObjectCache::nodeIsAriaType(Node* node, String role)
92 {
93 if (!node || !node->isElementNode())
94 return false;
95
96 return equalIgnoringCase(static_cast<Element*>(node)->getAttribute(roleAttr), role);
97 }
98
getOrCreate(RenderObject * renderer)99 AccessibilityObject* AXObjectCache::getOrCreate(RenderObject* renderer)
100 {
101 if (!renderer)
102 return 0;
103
104 AccessibilityObject* obj = get(renderer);
105
106 if (!obj) {
107 Node* node = renderer->node();
108 RefPtr<AccessibilityObject> newObj = 0;
109 if (renderer->isListBox())
110 newObj = AccessibilityListBox::create(renderer);
111 else if (node && (node->hasTagName(ulTag) || node->hasTagName(olTag) || node->hasTagName(dlTag)))
112 newObj = AccessibilityList::create(renderer);
113
114 // aria tables
115 else if (nodeIsAriaType(node, "grid"))
116 newObj = AccessibilityARIAGrid::create(renderer);
117 else if (nodeIsAriaType(node, "row"))
118 newObj = AccessibilityARIAGridRow::create(renderer);
119 else if (nodeIsAriaType(node, "gridcell") || nodeIsAriaType(node, "columnheader") || nodeIsAriaType(node, "rowheader"))
120 newObj = AccessibilityARIAGridCell::create(renderer);
121
122 // standard tables
123 else if (renderer->isTable())
124 newObj = AccessibilityTable::create(renderer);
125 else if (renderer->isTableRow())
126 newObj = AccessibilityTableRow::create(renderer);
127 else if (renderer->isTableCell())
128 newObj = AccessibilityTableCell::create(renderer);
129
130 // input type=range
131 else if (renderer->isSlider())
132 newObj = AccessibilitySlider::create(renderer);
133
134 else
135 newObj = AccessibilityRenderObject::create(renderer);
136
137 obj = newObj.get();
138
139 getAXID(obj);
140
141 m_renderObjectMapping.set(renderer, obj->axObjectID());
142 m_objects.set(obj->axObjectID(), obj);
143 attachWrapper(obj);
144 }
145
146 return obj;
147 }
148
getOrCreate(AccessibilityRole role)149 AccessibilityObject* AXObjectCache::getOrCreate(AccessibilityRole role)
150 {
151 RefPtr<AccessibilityObject> obj = 0;
152
153 // will be filled in...
154 switch (role) {
155 case ListBoxOptionRole:
156 obj = AccessibilityListBoxOption::create();
157 break;
158 case ImageMapLinkRole:
159 obj = AccessibilityImageMapLink::create();
160 break;
161 case ColumnRole:
162 obj = AccessibilityTableColumn::create();
163 break;
164 case TableHeaderContainerRole:
165 obj = AccessibilityTableHeaderContainer::create();
166 break;
167 case SliderThumbRole:
168 obj = AccessibilitySliderThumb::create();
169 break;
170 default:
171 obj = 0;
172 }
173
174 if (obj)
175 getAXID(obj.get());
176 else
177 return 0;
178
179 m_objects.set(obj->axObjectID(), obj);
180 attachWrapper(obj.get());
181 return obj.get();
182 }
183
remove(AXID axID)184 void AXObjectCache::remove(AXID axID)
185 {
186 if (!axID)
187 return;
188
189 // first fetch object to operate some cleanup functions on it
190 AccessibilityObject* obj = m_objects.get(axID).get();
191 if (!obj)
192 return;
193
194 detachWrapper(obj);
195 obj->detach();
196 removeAXID(obj);
197
198 // finally remove the object
199 if (!m_objects.take(axID)) {
200 return;
201 }
202
203 ASSERT(m_objects.size() >= m_idsInUse.size());
204 }
205
remove(RenderObject * renderer)206 void AXObjectCache::remove(RenderObject* renderer)
207 {
208 if (!renderer)
209 return;
210
211 AXID axID = m_renderObjectMapping.get(renderer);
212 remove(axID);
213 m_renderObjectMapping.remove(renderer);
214 }
215
getAXID(AccessibilityObject * obj)216 AXID AXObjectCache::getAXID(AccessibilityObject* obj)
217 {
218 // check for already-assigned ID
219 AXID objID = obj->axObjectID();
220 if (objID) {
221 ASSERT(m_idsInUse.contains(objID));
222 return objID;
223 }
224
225 // generate a new ID
226 static AXID lastUsedID = 0;
227 objID = lastUsedID;
228 do
229 ++objID;
230 while (objID == 0 || HashTraits<AXID>::isDeletedValue(objID) || m_idsInUse.contains(objID));
231 m_idsInUse.add(objID);
232 lastUsedID = objID;
233 obj->setAXObjectID(objID);
234
235 return objID;
236 }
237
removeAXID(AccessibilityObject * obj)238 void AXObjectCache::removeAXID(AccessibilityObject* obj)
239 {
240 if (!obj)
241 return;
242
243 AXID objID = obj->axObjectID();
244 if (objID == 0)
245 return;
246 ASSERT(!HashTraits<AXID>::isDeletedValue(objID));
247 ASSERT(m_idsInUse.contains(objID));
248 obj->setAXObjectID(0);
249 m_idsInUse.remove(objID);
250 }
251
childrenChanged(RenderObject * renderer)252 void AXObjectCache::childrenChanged(RenderObject* renderer)
253 {
254 if (!renderer)
255 return;
256
257 AXID axID = m_renderObjectMapping.get(renderer);
258 if (!axID)
259 return;
260
261 AccessibilityObject* obj = m_objects.get(axID).get();
262 if (obj)
263 obj->childrenChanged();
264 }
265
notificationPostTimerFired(Timer<AXObjectCache> *)266 void AXObjectCache::notificationPostTimerFired(Timer<AXObjectCache>*)
267 {
268 m_notificationPostTimer.stop();
269
270 unsigned i = 0, count = m_notificationsToPost.size();
271 for (i = 0; i < count; ++i) {
272 AccessibilityObject* obj = m_notificationsToPost[i].first.get();
273 #ifndef NDEBUG
274 // Make sure none of the render views are in the process of being layed out.
275 // Notifications should only be sent after the renderer has finished
276 if (obj->isAccessibilityRenderObject()) {
277 AccessibilityRenderObject* renderObj = static_cast<AccessibilityRenderObject*>(obj);
278 RenderObject* renderer = renderObj->renderer();
279 if (renderer && renderer->view())
280 ASSERT(!renderer->view()->layoutState());
281 }
282 #endif
283
284 postPlatformNotification(obj, m_notificationsToPost[i].second);
285 }
286
287 m_notificationsToPost.clear();
288 }
289
290 #if HAVE(ACCESSIBILITY)
postNotification(RenderObject * renderer,const String & message,bool postToElement)291 void AXObjectCache::postNotification(RenderObject* renderer, const String& message, bool postToElement)
292 {
293 // Notifications for text input objects are sent to that object.
294 // All others are sent to the top WebArea.
295 if (!renderer)
296 return;
297
298 // Get an accessibility object that already exists. One should not be created here
299 // because a render update may be in progress and creating an AX object can re-trigger a layout
300 RefPtr<AccessibilityObject> obj = get(renderer);
301 while (!obj && renderer) {
302 renderer = renderer->parent();
303 obj = get(renderer);
304 }
305
306 if (!renderer)
307 return;
308
309 if (obj && !postToElement)
310 obj = obj->observableObject();
311
312 Document* document = renderer->document();
313 if (!obj && document)
314 obj = get(document->renderer());
315
316 if (!obj)
317 return;
318
319 m_notificationsToPost.append(make_pair(obj, message));
320 if (!m_notificationPostTimer.isActive())
321 m_notificationPostTimer.startOneShot(0);
322 }
323
selectedChildrenChanged(RenderObject * renderer)324 void AXObjectCache::selectedChildrenChanged(RenderObject* renderer)
325 {
326 postNotification(renderer, "AXSelectedChildrenChanged", true);
327 }
328 #endif
329
330 #if HAVE(ACCESSIBILITY)
handleActiveDescendantChanged(RenderObject * renderer)331 void AXObjectCache::handleActiveDescendantChanged(RenderObject* renderer)
332 {
333 if (!renderer)
334 return;
335 AccessibilityObject* obj = getOrCreate(renderer);
336 if (obj)
337 obj->handleActiveDescendantChanged();
338 }
339
handleAriaRoleChanged(RenderObject * renderer)340 void AXObjectCache::handleAriaRoleChanged(RenderObject* renderer)
341 {
342 if (!renderer)
343 return;
344 AccessibilityObject* obj = getOrCreate(renderer);
345 if (obj && obj->isAccessibilityRenderObject())
346 static_cast<AccessibilityRenderObject*>(obj)->updateAccessibilityRole();
347 }
348 #endif
349
visiblePositionForTextMarkerData(TextMarkerData & textMarkerData)350 VisiblePosition AXObjectCache::visiblePositionForTextMarkerData(TextMarkerData& textMarkerData)
351 {
352 VisiblePosition visiblePos = VisiblePosition(textMarkerData.node, textMarkerData.offset, textMarkerData.affinity);
353 Position deepPos = visiblePos.deepEquivalent();
354 if (deepPos.isNull())
355 return VisiblePosition();
356
357 RenderObject* renderer = deepPos.node()->renderer();
358 if (!renderer)
359 return VisiblePosition();
360
361 AXObjectCache* cache = renderer->document()->axObjectCache();
362 if (!cache->isIDinUse(textMarkerData.axID))
363 return VisiblePosition();
364
365 if (deepPos.node() != textMarkerData.node || deepPos.deprecatedEditingOffset() != textMarkerData.offset)
366 return VisiblePosition();
367
368 return visiblePos;
369 }
370
textMarkerDataForVisiblePosition(TextMarkerData & textMarkerData,const VisiblePosition & visiblePos)371 void AXObjectCache::textMarkerDataForVisiblePosition(TextMarkerData& textMarkerData, const VisiblePosition& visiblePos)
372 {
373 // This memory must be bzero'd so instances of TextMarkerData can be tested for byte-equivalence.
374 // This also allows callers to check for failure by looking at textMarkerData upon return.
375 memset(&textMarkerData, 0, sizeof(TextMarkerData));
376
377 if (visiblePos.isNull())
378 return;
379
380 Position deepPos = visiblePos.deepEquivalent();
381 Node* domNode = deepPos.node();
382 ASSERT(domNode);
383 if (!domNode)
384 return;
385
386 if (domNode->isHTMLElement()) {
387 InputElement* inputElement = toInputElement(static_cast<Element*>(domNode));
388 if (inputElement && inputElement->isPasswordField())
389 return;
390 }
391
392 // locate the renderer, which must exist for a visible dom node
393 RenderObject* renderer = domNode->renderer();
394 ASSERT(renderer);
395
396 // find or create an accessibility object for this renderer
397 AXObjectCache* cache = renderer->document()->axObjectCache();
398 RefPtr<AccessibilityObject> obj = cache->getOrCreate(renderer);
399
400 textMarkerData.axID = obj.get()->axObjectID();
401 textMarkerData.node = domNode;
402 textMarkerData.offset = deepPos.deprecatedEditingOffset();
403 textMarkerData.affinity = visiblePos.affinity();
404 }
405
406 } // namespace WebCore
407