• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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