Reviewed by Darin.
[WebKit-https.git] / WebCore / bridge / mac / AXObjectCacheMac.mm
1 /*
2  * Copyright (C) 2003, 2004, 2005, 2006 Apple Computer, 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 COMPUTER, 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 COMPUTER, 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 #import "config.h"
27 #import "AXObjectCache.h"
28
29 #import "Document.h"
30 #import "FoundationExtras.h"
31 #import "RenderObject.h"
32 #import "WebCoreAXObject.h"
33 #import "WebCoreViewFactory.h"
34
35 // The simple Cocoa calls in this file don't throw exceptions.
36
37 namespace WebCore {
38
39 struct TextMarkerData  {
40     AXID axID;
41     Node* node;
42     int offset;
43     EAffinity affinity;
44 };
45
46 bool AXObjectCache::gAccessibilityEnabled = false;
47
48 AXObjectCache::~AXObjectCache()
49 {
50     HashMap<RenderObject*, WebCoreAXObject*>::iterator end = m_objects.end();
51     for (HashMap<RenderObject*, WebCoreAXObject*>::iterator it = m_objects.begin(); it != end; ++it) {
52         WebCoreAXObject* obj = (*it).second;
53         [obj detach];
54         HardRelease(obj);
55     }
56 }
57
58 WebCoreAXObject* AXObjectCache::get(RenderObject* renderer)
59 {
60     WebCoreAXObject* obj = m_objects.get(renderer);
61     if (obj)
62         return obj;
63
64     obj = [[WebCoreAXObject alloc] initWithRenderer:renderer];
65     HardRetainWithNSRelease(obj);
66     m_objects.set(renderer, obj);
67     return obj;
68 }
69
70 void AXObjectCache::remove(RenderObject* renderer)
71 {
72     HashMap<RenderObject*, WebCoreAXObject*>::iterator it = m_objects.find(renderer);
73     if (it == m_objects.end())
74         return;
75     WebCoreAXObject* obj = (*it).second;
76     [obj detach];
77     HardRelease(obj);
78     m_objects.remove(it);
79
80     ASSERT(m_objects.size() >= m_idsInUse.size());
81 }
82
83 AXID AXObjectCache::getAXID(WebCoreAXObject* obj)
84 {
85     // check for already-assigned ID
86     AXID objID = [obj axObjectID];
87     if (objID) {
88         ASSERT(m_idsInUse.contains(objID));
89         return objID;
90     }
91
92     // generate a new ID
93     static AXID lastUsedID = 0;
94     objID = lastUsedID;
95     do
96         ++objID;
97     while (objID == 0 || objID == AXIDHashTraits::deletedValue() || m_idsInUse.contains(objID));
98     m_idsInUse.add(objID);
99     lastUsedID = objID;
100     [obj setAXObjectID:objID];
101
102     return objID;
103 }
104
105 void AXObjectCache::removeAXID(WebCoreAXObject* obj)
106 {
107     AXID objID = [obj axObjectID];
108     if (objID == 0)
109         return;
110     ASSERT(objID != AXIDHashTraits::deletedValue());
111     ASSERT(m_idsInUse.contains(objID));
112     [obj setAXObjectID:0];
113     m_idsInUse.remove(objID);
114 }
115
116 WebCoreTextMarker* AXObjectCache::textMarkerForVisiblePosition(const VisiblePosition& visiblePos)
117 {
118     Position deepPos = visiblePos.deepEquivalent();
119     Node* domNode = deepPos.node();
120     ASSERT(domNode);
121     if (!domNode)
122         return nil;
123     
124     // locate the renderer, which must exist for a visible dom node
125     RenderObject* renderer = domNode->renderer();
126     ASSERT(renderer);
127     
128     // find or create an accessibility object for this renderer
129     WebCoreAXObject* obj = get(renderer);
130     
131     // create a text marker, adding an ID for the WebCoreAXObject if needed
132     TextMarkerData textMarkerData;
133     textMarkerData.axID = getAXID(obj);
134     textMarkerData.node = domNode;
135     textMarkerData.offset = deepPos.offset();
136     textMarkerData.affinity = visiblePos.affinity();
137     return [[WebCoreViewFactory sharedFactory] textMarkerWithBytes:&textMarkerData length:sizeof(textMarkerData)]; 
138 }
139
140 VisiblePosition AXObjectCache::visiblePositionForTextMarker(WebCoreTextMarker* textMarker)
141 {
142     TextMarkerData textMarkerData;
143     
144     if (![[WebCoreViewFactory sharedFactory] getBytes:&textMarkerData fromTextMarker:textMarker length:sizeof(textMarkerData)])
145         return VisiblePosition();
146
147     // return empty position if the text marker is no longer valid
148     if (!m_idsInUse.contains(textMarkerData.axID))
149         return VisiblePosition();
150
151     // generate a VisiblePosition from the data we stored earlier
152     VisiblePosition visiblePos = VisiblePosition(textMarkerData.node, textMarkerData.offset, textMarkerData.affinity);
153
154     // make sure the node and offset still match (catches stale markers). affinity is not critical for this.
155     Position deepPos = visiblePos.deepEquivalent();
156     if (deepPos.node() != textMarkerData.node || deepPos.offset() != textMarkerData.offset)
157         return VisiblePosition();
158     
159     return visiblePos;
160 }
161
162 void AXObjectCache::childrenChanged(RenderObject* renderer)
163 {
164     WebCoreAXObject* obj = m_objects.get(renderer);
165     if (obj)
166         [obj childrenChanged];
167 }
168
169 void AXObjectCache::postNotification(RenderObject* renderer, const String& message)
170 {
171     if (!renderer)
172         return;
173     
174     // notifications for text input objects are sent to that object
175     // all others are sent to the top WebArea
176     WebCoreAXObject* obj = [get(renderer) observableObject];
177     if (obj)
178         NSAccessibilityPostNotification(obj, message);
179     else
180         NSAccessibilityPostNotification(get(renderer->document()->topDocument()->renderer()), message);
181 }
182
183 void AXObjectCache::postNotificationToElement(RenderObject* renderer, const String& message)
184 {
185     // send the notification to the specified element itself, not one of its ancestors
186     if (renderer)
187         NSAccessibilityPostNotification(get(renderer), message);
188 }
189
190 void AXObjectCache::handleFocusedUIElementChanged()
191 {
192     [[WebCoreViewFactory sharedFactory] accessibilityHandleFocusChanged];
193 }
194
195 }