• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2008, 2009, 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  *
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 "AccessibilityARIAGridCell.h"
34 #include "AccessibilityARIAGridRow.h"
35 #include "AccessibilityImageMapLink.h"
36 #include "AccessibilityList.h"
37 #include "AccessibilityListBox.h"
38 #include "AccessibilityListBoxOption.h"
39 #include "AccessibilityMediaControls.h"
40 #include "AccessibilityMenuList.h"
41 #include "AccessibilityMenuListOption.h"
42 #include "AccessibilityMenuListPopup.h"
43 #include "AccessibilityProgressIndicator.h"
44 #include "AccessibilityRenderObject.h"
45 #include "AccessibilityScrollView.h"
46 #include "AccessibilityScrollbar.h"
47 #include "AccessibilitySlider.h"
48 #include "AccessibilityTable.h"
49 #include "AccessibilityTableCell.h"
50 #include "AccessibilityTableColumn.h"
51 #include "AccessibilityTableHeaderContainer.h"
52 #include "AccessibilityTableRow.h"
53 #include "Document.h"
54 #include "FocusController.h"
55 #include "Frame.h"
56 #include "HTMLAreaElement.h"
57 #include "HTMLImageElement.h"
58 #include "HTMLNames.h"
59 #if ENABLE(VIDEO)
60 #include "MediaControlElements.h"
61 #endif
62 #include "InputElement.h"
63 #include "Page.h"
64 #include "RenderListBox.h"
65 #include "RenderMenuList.h"
66 #include "RenderProgress.h"
67 #include "RenderSlider.h"
68 #include "RenderTable.h"
69 #include "RenderTableCell.h"
70 #include "RenderTableRow.h"
71 #include "RenderView.h"
72 #include "ScrollView.h"
73 
74 #include <wtf/PassRefPtr.h>
75 
76 namespace WebCore {
77 
78 using namespace HTMLNames;
79 
80 bool AXObjectCache::gAccessibilityEnabled = false;
81 bool AXObjectCache::gAccessibilityEnhancedUserInterfaceEnabled = false;
82 
AXObjectCache(const Document * doc)83 AXObjectCache::AXObjectCache(const Document* doc)
84     : m_notificationPostTimer(this, &AXObjectCache::notificationPostTimerFired)
85 {
86     m_document = const_cast<Document*>(doc);
87 }
88 
~AXObjectCache()89 AXObjectCache::~AXObjectCache()
90 {
91     HashMap<AXID, RefPtr<AccessibilityObject> >::iterator end = m_objects.end();
92     for (HashMap<AXID, RefPtr<AccessibilityObject> >::iterator it = m_objects.begin(); it != end; ++it) {
93         AccessibilityObject* obj = (*it).second.get();
94         detachWrapper(obj);
95         obj->detach();
96         removeAXID(obj);
97     }
98 }
99 
focusedImageMapUIElement(HTMLAreaElement * areaElement)100 AccessibilityObject* AXObjectCache::focusedImageMapUIElement(HTMLAreaElement* areaElement)
101 {
102     // Find the corresponding accessibility object for the HTMLAreaElement. This should be
103     // in the list of children for its corresponding image.
104     if (!areaElement)
105         return 0;
106 
107     HTMLImageElement* imageElement = areaElement->imageElement();
108     if (!imageElement)
109         return 0;
110 
111     AccessibilityObject* axRenderImage = areaElement->document()->axObjectCache()->getOrCreate(imageElement->renderer());
112     if (!axRenderImage)
113         return 0;
114 
115     AccessibilityObject::AccessibilityChildrenVector imageChildren = axRenderImage->children();
116     unsigned count = imageChildren.size();
117     for (unsigned k = 0; k < count; ++k) {
118         AccessibilityObject* child = imageChildren[k].get();
119         if (!child->isImageMapLink())
120             continue;
121 
122         if (static_cast<AccessibilityImageMapLink*>(child)->areaElement() == areaElement)
123             return child;
124     }
125 
126     return 0;
127 }
128 
focusedUIElementForPage(const Page * page)129 AccessibilityObject* AXObjectCache::focusedUIElementForPage(const Page* page)
130 {
131     // get the focused node in the page
132     Document* focusedDocument = page->focusController()->focusedOrMainFrame()->document();
133     Node* focusedNode = focusedDocument->focusedNode();
134     if (!focusedNode)
135         focusedNode = focusedDocument;
136 
137     if (focusedNode->hasTagName(areaTag))
138         return focusedImageMapUIElement(static_cast<HTMLAreaElement*>(focusedNode));
139 
140     RenderObject* focusedNodeRenderer = focusedNode->renderer();
141     if (!focusedNodeRenderer)
142         return 0;
143 
144     AccessibilityObject* obj = focusedNodeRenderer->document()->axObjectCache()->getOrCreate(focusedNodeRenderer);
145 
146     if (obj->shouldFocusActiveDescendant()) {
147         if (AccessibilityObject* descendant = obj->activeDescendant())
148             obj = descendant;
149     }
150 
151     // the HTML element, for example, is focusable but has an AX object that is ignored
152     if (obj->accessibilityIsIgnored())
153         obj = obj->parentObjectUnignored();
154 
155     return obj;
156 }
157 
get(Widget * widget)158 AccessibilityObject* AXObjectCache::get(Widget* widget)
159 {
160     if (!widget)
161         return 0;
162 
163     AXID axID = m_widgetObjectMapping.get(widget);
164     ASSERT(!HashTraits<AXID>::isDeletedValue(axID));
165     if (!axID)
166         return 0;
167 
168     return m_objects.get(axID).get();
169 }
170 
get(RenderObject * renderer)171 AccessibilityObject* AXObjectCache::get(RenderObject* renderer)
172 {
173     if (!renderer)
174         return 0;
175 
176     AXID axID = m_renderObjectMapping.get(renderer);
177     ASSERT(!HashTraits<AXID>::isDeletedValue(axID));
178     if (!axID)
179         return 0;
180 
181     return m_objects.get(axID).get();
182 }
183 
184 // FIXME: This probably belongs on Node.
185 // FIXME: This should take a const char*, but one caller passes nullAtom.
nodeHasRole(Node * node,const String & role)186 bool nodeHasRole(Node* node, const String& role)
187 {
188     if (!node || !node->isElementNode())
189         return false;
190 
191     return equalIgnoringCase(static_cast<Element*>(node)->getAttribute(roleAttr), role);
192 }
193 
createFromRenderer(RenderObject * renderer)194 static PassRefPtr<AccessibilityObject> createFromRenderer(RenderObject* renderer)
195 {
196     // FIXME: How could renderer->node() ever not be an Element?
197     Node* node = renderer->node();
198 
199     // If the node is aria role="list" or the aria role is empty and its a
200     // ul/ol/dl type (it shouldn't be a list if aria says otherwise).
201     if (node && ((nodeHasRole(node, "list") || nodeHasRole(node, "directory"))
202                       || (nodeHasRole(node, nullAtom) && (node->hasTagName(ulTag) || node->hasTagName(olTag) || node->hasTagName(dlTag)))))
203         return AccessibilityList::create(renderer);
204 
205     // aria tables
206     if (nodeHasRole(node, "grid") || nodeHasRole(node, "treegrid"))
207         return AccessibilityARIAGrid::create(renderer);
208     if (nodeHasRole(node, "row"))
209         return AccessibilityARIAGridRow::create(renderer);
210     if (nodeHasRole(node, "gridcell") || nodeHasRole(node, "columnheader") || nodeHasRole(node, "rowheader"))
211         return AccessibilityARIAGridCell::create(renderer);
212 
213 #if ENABLE(VIDEO)
214     // media controls
215     if (node && node->isMediaControlElement())
216         return AccessibilityMediaControl::create(renderer);
217 #endif
218 
219     if (renderer->isBoxModelObject()) {
220         RenderBoxModelObject* cssBox = toRenderBoxModelObject(renderer);
221         if (cssBox->isListBox())
222             return AccessibilityListBox::create(toRenderListBox(cssBox));
223         if (cssBox->isMenuList())
224             return AccessibilityMenuList::create(toRenderMenuList(cssBox));
225 
226         // standard tables
227         if (cssBox->isTable())
228             return AccessibilityTable::create(toRenderTable(cssBox));
229         if (cssBox->isTableRow())
230             return AccessibilityTableRow::create(toRenderTableRow(cssBox));
231         if (cssBox->isTableCell())
232             return AccessibilityTableCell::create(toRenderTableCell(cssBox));
233 
234 #if ENABLE(PROGRESS_TAG)
235         // progress bar
236         if (cssBox->isProgress())
237             return AccessibilityProgressIndicator::create(toRenderProgress(cssBox));
238 #endif
239 
240         // input type=range
241         if (cssBox->isSlider())
242             return AccessibilitySlider::create(toRenderSlider(cssBox));
243     }
244 
245     return AccessibilityRenderObject::create(renderer);
246 }
247 
getOrCreate(Widget * widget)248 AccessibilityObject* AXObjectCache::getOrCreate(Widget* widget)
249 {
250     if (!widget)
251         return 0;
252 
253     if (AccessibilityObject* obj = get(widget))
254         return obj;
255 
256     RefPtr<AccessibilityObject> newObj = 0;
257     if (widget->isFrameView())
258         newObj = AccessibilityScrollView::create(static_cast<ScrollView*>(widget));
259     else if (widget->isScrollbar())
260         newObj = AccessibilityScrollbar::create(static_cast<Scrollbar*>(widget));
261 
262     getAXID(newObj.get());
263 
264     m_widgetObjectMapping.set(widget, newObj->axObjectID());
265     m_objects.set(newObj->axObjectID(), newObj);
266     attachWrapper(newObj.get());
267     return newObj.get();
268 }
269 
getOrCreate(RenderObject * renderer)270 AccessibilityObject* AXObjectCache::getOrCreate(RenderObject* renderer)
271 {
272     if (!renderer)
273         return 0;
274 
275     if (AccessibilityObject* obj = get(renderer))
276         return obj;
277 
278     RefPtr<AccessibilityObject> newObj = createFromRenderer(renderer);
279 
280     getAXID(newObj.get());
281 
282     m_renderObjectMapping.set(renderer, newObj->axObjectID());
283     m_objects.set(newObj->axObjectID(), newObj);
284     attachWrapper(newObj.get());
285     return newObj.get();
286 }
287 
rootObject()288 AccessibilityObject* AXObjectCache::rootObject()
289 {
290     return getOrCreate(m_document->view());
291 }
292 
rootObjectForFrame(Frame * frame)293 AccessibilityObject* AXObjectCache::rootObjectForFrame(Frame* frame)
294 {
295     if (!frame)
296         return 0;
297     return getOrCreate(frame->view());
298 }
299 
getOrCreate(AccessibilityRole role)300 AccessibilityObject* AXObjectCache::getOrCreate(AccessibilityRole role)
301 {
302     RefPtr<AccessibilityObject> obj = 0;
303 
304     // will be filled in...
305     switch (role) {
306     case ListBoxOptionRole:
307         obj = AccessibilityListBoxOption::create();
308         break;
309     case ImageMapLinkRole:
310         obj = AccessibilityImageMapLink::create();
311         break;
312     case ColumnRole:
313         obj = AccessibilityTableColumn::create();
314         break;
315     case TableHeaderContainerRole:
316         obj = AccessibilityTableHeaderContainer::create();
317         break;
318     case SliderThumbRole:
319         obj = AccessibilitySliderThumb::create();
320         break;
321     case MenuListPopupRole:
322         obj = AccessibilityMenuListPopup::create();
323         break;
324     case MenuListOptionRole:
325         obj = AccessibilityMenuListOption::create();
326         break;
327     default:
328         obj = 0;
329     }
330 
331     if (obj)
332         getAXID(obj.get());
333     else
334         return 0;
335 
336     m_objects.set(obj->axObjectID(), obj);
337     attachWrapper(obj.get());
338     return obj.get();
339 }
340 
remove(AXID axID)341 void AXObjectCache::remove(AXID axID)
342 {
343     if (!axID)
344         return;
345 
346     // first fetch object to operate some cleanup functions on it
347     AccessibilityObject* obj = m_objects.get(axID).get();
348     if (!obj)
349         return;
350 
351     detachWrapper(obj);
352     obj->detach();
353     removeAXID(obj);
354 
355     // finally remove the object
356     if (!m_objects.take(axID))
357         return;
358 
359     ASSERT(m_objects.size() >= m_idsInUse.size());
360 }
361 
remove(RenderObject * renderer)362 void AXObjectCache::remove(RenderObject* renderer)
363 {
364     if (!renderer)
365         return;
366 
367     AXID axID = m_renderObjectMapping.get(renderer);
368     remove(axID);
369     m_renderObjectMapping.remove(renderer);
370 }
371 
remove(Widget * view)372 void AXObjectCache::remove(Widget* view)
373 {
374     if (!view)
375         return;
376 
377     AXID axID = m_widgetObjectMapping.get(view);
378     remove(axID);
379     m_widgetObjectMapping.remove(view);
380 }
381 
382 
383 #if !PLATFORM(WIN) || OS(WINCE)
platformGenerateAXID() const384 AXID AXObjectCache::platformGenerateAXID() const
385 {
386     static AXID lastUsedID = 0;
387 
388     // Generate a new ID.
389     AXID objID = lastUsedID;
390     do {
391         ++objID;
392     } while (!objID || HashTraits<AXID>::isDeletedValue(objID) || m_idsInUse.contains(objID));
393 
394     lastUsedID = objID;
395 
396     return objID;
397 }
398 #endif
399 
getAXID(AccessibilityObject * obj)400 AXID AXObjectCache::getAXID(AccessibilityObject* obj)
401 {
402     // check for already-assigned ID
403     AXID objID = obj->axObjectID();
404     if (objID) {
405         ASSERT(m_idsInUse.contains(objID));
406         return objID;
407     }
408 
409     objID = platformGenerateAXID();
410 
411     m_idsInUse.add(objID);
412     obj->setAXObjectID(objID);
413 
414     return objID;
415 }
416 
removeAXID(AccessibilityObject * object)417 void AXObjectCache::removeAXID(AccessibilityObject* object)
418 {
419     if (!object)
420         return;
421 
422     AXID objID = object->axObjectID();
423     if (!objID)
424         return;
425     ASSERT(!HashTraits<AXID>::isDeletedValue(objID));
426     ASSERT(m_idsInUse.contains(objID));
427     object->setAXObjectID(0);
428     m_idsInUse.remove(objID);
429 }
430 
431 #if HAVE(ACCESSIBILITY)
contentChanged(RenderObject * renderer)432 void AXObjectCache::contentChanged(RenderObject* renderer)
433 {
434     AccessibilityObject* object = getOrCreate(renderer);
435     if (object)
436         object->contentChanged();
437 }
438 #endif
439 
childrenChanged(RenderObject * renderer)440 void AXObjectCache::childrenChanged(RenderObject* renderer)
441 {
442     if (!renderer)
443         return;
444 
445     AXID axID = m_renderObjectMapping.get(renderer);
446     if (!axID)
447         return;
448 
449     AccessibilityObject* obj = m_objects.get(axID).get();
450     if (obj)
451         obj->childrenChanged();
452 }
453 
notificationPostTimerFired(Timer<AXObjectCache> *)454 void AXObjectCache::notificationPostTimerFired(Timer<AXObjectCache>*)
455 {
456     m_notificationPostTimer.stop();
457 
458     unsigned i = 0, count = m_notificationsToPost.size();
459     for (i = 0; i < count; ++i) {
460         AccessibilityObject* obj = m_notificationsToPost[i].first.get();
461 #ifndef NDEBUG
462         // Make sure none of the render views are in the process of being layed out.
463         // Notifications should only be sent after the renderer has finished
464         if (obj->isAccessibilityRenderObject()) {
465             AccessibilityRenderObject* renderObj = static_cast<AccessibilityRenderObject*>(obj);
466             RenderObject* renderer = renderObj->renderer();
467             if (renderer && renderer->view())
468                 ASSERT(!renderer->view()->layoutState());
469         }
470 #endif
471 
472         postPlatformNotification(obj, m_notificationsToPost[i].second);
473     }
474 
475     m_notificationsToPost.clear();
476 }
477 
478 #if HAVE(ACCESSIBILITY)
postNotification(RenderObject * renderer,AXNotification notification,bool postToElement,PostType postType)479 void AXObjectCache::postNotification(RenderObject* renderer, AXNotification notification, bool postToElement, PostType postType)
480 {
481     // Notifications for text input objects are sent to that object.
482     // All others are sent to the top WebArea.
483     if (!renderer)
484         return;
485 
486     // Get an accessibility object that already exists. One should not be created here
487     // because a render update may be in progress and creating an AX object can re-trigger a layout
488     RefPtr<AccessibilityObject> object = get(renderer);
489     while (!object && renderer) {
490         renderer = renderer->parent();
491         object = get(renderer);
492     }
493 
494     if (!renderer)
495         return;
496 
497     postNotification(object.get(), renderer->document(), notification, postToElement, postType);
498 }
499 
postNotification(AccessibilityObject * object,Document * document,AXNotification notification,bool postToElement,PostType postType)500 void AXObjectCache::postNotification(AccessibilityObject* object, Document* document, AXNotification notification, bool postToElement, PostType postType)
501 {
502     if (object && !postToElement)
503         object = object->observableObject();
504 
505     if (!object && document)
506         object = get(document->renderer());
507 
508     if (!object)
509         return;
510 
511     if (postType == PostAsynchronously) {
512         m_notificationsToPost.append(make_pair(object, notification));
513         if (!m_notificationPostTimer.isActive())
514             m_notificationPostTimer.startOneShot(0);
515     } else
516         postPlatformNotification(object, notification);
517 }
518 
selectedChildrenChanged(RenderObject * renderer)519 void AXObjectCache::selectedChildrenChanged(RenderObject* renderer)
520 {
521     // postToElement is false so that you can pass in any child of an element and it will go up the parent tree
522     // to find the container which should send out the notification.
523     postNotification(renderer, AXSelectedChildrenChanged, false);
524 }
525 
nodeTextChangeNotification(RenderObject * renderer,AXTextChange textChange,unsigned offset,unsigned count)526 void AXObjectCache::nodeTextChangeNotification(RenderObject* renderer, AXTextChange textChange, unsigned offset, unsigned count)
527 {
528     if (!renderer)
529         return;
530 
531     // Delegate on the right platform
532     AccessibilityObject* obj = getOrCreate(renderer);
533     nodeTextChangePlatformNotification(obj, textChange, offset, count);
534 }
535 #endif
536 
537 #if HAVE(ACCESSIBILITY)
538 
handleScrollbarUpdate(ScrollView * view)539 void AXObjectCache::handleScrollbarUpdate(ScrollView* view)
540 {
541     if (!view)
542         return;
543 
544     // We don't want to create a scroll view from this method, only update an existing one.
545     AccessibilityObject* scrollViewObject = get(view);
546     if (scrollViewObject)
547         scrollViewObject->updateChildrenIfNecessary();
548 }
549 
handleAriaExpandedChange(RenderObject * renderer)550 void AXObjectCache::handleAriaExpandedChange(RenderObject *renderer)
551 {
552     if (!renderer)
553         return;
554     AccessibilityObject* obj = getOrCreate(renderer);
555     if (obj)
556         obj->handleAriaExpandedChanged();
557 }
558 
handleActiveDescendantChanged(RenderObject * renderer)559 void AXObjectCache::handleActiveDescendantChanged(RenderObject* renderer)
560 {
561     if (!renderer)
562         return;
563     AccessibilityObject* obj = getOrCreate(renderer);
564     if (obj)
565         obj->handleActiveDescendantChanged();
566 }
567 
handleAriaRoleChanged(RenderObject * renderer)568 void AXObjectCache::handleAriaRoleChanged(RenderObject* renderer)
569 {
570     if (!renderer)
571         return;
572     AccessibilityObject* obj = getOrCreate(renderer);
573     if (obj && obj->isAccessibilityRenderObject())
574         static_cast<AccessibilityRenderObject*>(obj)->updateAccessibilityRole();
575 }
576 #endif
577 
visiblePositionForTextMarkerData(TextMarkerData & textMarkerData)578 VisiblePosition AXObjectCache::visiblePositionForTextMarkerData(TextMarkerData& textMarkerData)
579 {
580     if (!isNodeInUse(textMarkerData.node))
581         return VisiblePosition();
582 
583     // FIXME: Accessability should make it clear these are DOM-compliant offsets or store Position objects.
584     VisiblePosition visiblePos = VisiblePosition(Position(textMarkerData.node, textMarkerData.offset), textMarkerData.affinity);
585     Position deepPos = visiblePos.deepEquivalent();
586     if (deepPos.isNull())
587         return VisiblePosition();
588 
589     RenderObject* renderer = deepPos.deprecatedNode()->renderer();
590     if (!renderer)
591         return VisiblePosition();
592 
593     AXObjectCache* cache = renderer->document()->axObjectCache();
594     if (!cache->isIDinUse(textMarkerData.axID))
595         return VisiblePosition();
596 
597     if (deepPos.deprecatedNode() != textMarkerData.node || deepPos.deprecatedEditingOffset() != textMarkerData.offset)
598         return VisiblePosition();
599 
600     return visiblePos;
601 }
602 
textMarkerDataForVisiblePosition(TextMarkerData & textMarkerData,const VisiblePosition & visiblePos)603 void AXObjectCache::textMarkerDataForVisiblePosition(TextMarkerData& textMarkerData, const VisiblePosition& visiblePos)
604 {
605     // This memory must be bzero'd so instances of TextMarkerData can be tested for byte-equivalence.
606     // This also allows callers to check for failure by looking at textMarkerData upon return.
607     memset(&textMarkerData, 0, sizeof(TextMarkerData));
608 
609     if (visiblePos.isNull())
610         return;
611 
612     Position deepPos = visiblePos.deepEquivalent();
613     Node* domNode = deepPos.deprecatedNode();
614     ASSERT(domNode);
615     if (!domNode)
616         return;
617 
618     if (domNode->isHTMLElement()) {
619         InputElement* inputElement = domNode->toInputElement();
620         if (inputElement && inputElement->isPasswordField())
621             return;
622     }
623 
624     // locate the renderer, which must exist for a visible dom node
625     RenderObject* renderer = domNode->renderer();
626     ASSERT(renderer);
627 
628     // find or create an accessibility object for this renderer
629     AXObjectCache* cache = renderer->document()->axObjectCache();
630     RefPtr<AccessibilityObject> obj = cache->getOrCreate(renderer);
631 
632     textMarkerData.axID = obj.get()->axObjectID();
633     textMarkerData.node = domNode;
634     textMarkerData.offset = deepPos.deprecatedEditingOffset();
635     textMarkerData.affinity = visiblePos.affinity();
636 
637     cache->setNodeInUse(domNode);
638 }
639 
640 } // namespace WebCore
641