]>
Commit | Line | Data |
---|---|---|
b37bf2e1 A |
1 | /* |
2 | * Copyright (C) 2004 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 | #include "config.h" | |
27 | #include "runtime_root.h" | |
28 | ||
29 | #include "JSGlobalObject.h" | |
30 | #include "object.h" | |
31 | #include "runtime.h" | |
32 | #include "runtime_object.h" | |
33 | #include <wtf/HashCountedSet.h> | |
34 | #include <wtf/HashSet.h> | |
35 | ||
36 | namespace KJS { namespace Bindings { | |
37 | ||
38 | // This code attempts to solve two problems: (1) plug-ins leaking references to | |
39 | // JS and the DOM; (2) plug-ins holding stale references to JS and the DOM. Previous | |
40 | // comments in this file claimed that problem #1 was an issue in Java, in particular, | |
41 | // because Java, allegedly, didn't always call finalize when collecting an object. | |
42 | ||
43 | typedef HashSet<RootObject*> RootObjectSet; | |
44 | ||
45 | static RootObjectSet* rootObjectSet() | |
46 | { | |
47 | static RootObjectSet staticRootObjectSet; | |
48 | return &staticRootObjectSet; | |
49 | } | |
50 | ||
51 | // FIXME: These two functions are a potential performance problem. We could | |
52 | // fix them by adding a JSObject to RootObject dictionary. | |
53 | ||
54 | RootObject* findProtectingRootObject(JSObject* jsObject) | |
55 | { | |
56 | RootObjectSet::const_iterator end = rootObjectSet()->end(); | |
57 | for (RootObjectSet::const_iterator it = rootObjectSet()->begin(); it != end; ++it) { | |
58 | if ((*it)->gcIsProtected(jsObject)) | |
59 | return *it; | |
60 | } | |
61 | return 0; | |
62 | } | |
63 | ||
64 | RootObject* findRootObject(JSGlobalObject* globalObject) | |
65 | { | |
66 | RootObjectSet::const_iterator end = rootObjectSet()->end(); | |
67 | for (RootObjectSet::const_iterator it = rootObjectSet()->begin(); it != end; ++it) { | |
68 | if ((*it)->globalObject() == globalObject) | |
69 | return *it; | |
70 | } | |
71 | return 0; | |
72 | } | |
73 | ||
74 | // May only be set by dispatchToJavaScriptThread(). | |
75 | #if ENABLE(JAVA_BINDINGS) | |
76 | static CFRunLoopSourceRef completionSource; | |
77 | ||
78 | static void completedJavaScriptAccess (void *i) | |
79 | { | |
80 | assert (CFRunLoopGetCurrent() != RootObject::runLoop()); | |
81 | ||
82 | JSObjectCallContext *callContext = (JSObjectCallContext *)i; | |
83 | CFRunLoopRef runLoop = (CFRunLoopRef)callContext->originatingLoop; | |
84 | ||
85 | assert (CFRunLoopGetCurrent() == runLoop); | |
86 | ||
87 | CFRunLoopStop(runLoop); | |
88 | } | |
89 | ||
90 | static pthread_once_t javaScriptAccessLockOnce = PTHREAD_ONCE_INIT; | |
91 | static pthread_mutex_t javaScriptAccessLock; | |
92 | static int javaScriptAccessLockCount = 0; | |
93 | ||
94 | static void initializeJavaScriptAccessLock() | |
95 | { | |
96 | pthread_mutexattr_t attr; | |
97 | ||
98 | pthread_mutexattr_init(&attr); | |
99 | pthread_mutexattr_settype (&attr, PTHREAD_MUTEX_RECURSIVE); | |
100 | ||
101 | pthread_mutex_init(&javaScriptAccessLock, &attr); | |
102 | } | |
103 | ||
104 | static inline void lockJavaScriptAccess() | |
105 | { | |
106 | // Perhaps add deadlock detection? | |
107 | pthread_once(&javaScriptAccessLockOnce, initializeJavaScriptAccessLock); | |
108 | pthread_mutex_lock(&javaScriptAccessLock); | |
109 | javaScriptAccessLockCount++; | |
110 | } | |
111 | ||
112 | static inline void unlockJavaScriptAccess() | |
113 | { | |
114 | javaScriptAccessLockCount--; | |
115 | pthread_mutex_unlock(&javaScriptAccessLock); | |
116 | } | |
117 | ||
118 | ||
119 | void RootObject::dispatchToJavaScriptThread(JSObjectCallContext *context) | |
120 | { | |
121 | // This lock guarantees that only one thread can invoke | |
122 | // at a time, and also guarantees that completionSource; | |
123 | // won't get clobbered. | |
124 | lockJavaScriptAccess(); | |
125 | ||
126 | CFRunLoopRef currentRunLoop = CFRunLoopGetCurrent(); | |
127 | ||
128 | assert (currentRunLoop != RootObject::runLoop()); | |
129 | ||
130 | // Setup a source to signal once the invocation of the JavaScript | |
131 | // call completes. | |
132 | // | |
133 | // FIXME: This could be a potential performance issue. Creating and | |
134 | // adding run loop sources is expensive. We could create one source | |
135 | // per thread, as needed, instead. | |
136 | context->originatingLoop = currentRunLoop; | |
137 | CFRunLoopSourceContext sourceContext = {0, context, NULL, NULL, NULL, NULL, NULL, NULL, NULL, completedJavaScriptAccess}; | |
138 | completionSource = CFRunLoopSourceCreate(NULL, 0, &sourceContext); | |
139 | CFRunLoopAddSource(currentRunLoop, completionSource, kCFRunLoopDefaultMode); | |
140 | ||
141 | // Wakeup JavaScript access thread and make it do it's work. | |
142 | CFRunLoopSourceSignal(RootObject::performJavaScriptSource()); | |
143 | if (CFRunLoopIsWaiting(RootObject::runLoop())) { | |
144 | CFRunLoopWakeUp(RootObject::runLoop()); | |
145 | } | |
146 | ||
147 | // Wait until the JavaScript access thread is done. | |
148 | CFRunLoopRun (); | |
149 | ||
150 | CFRunLoopRemoveSource(currentRunLoop, completionSource, kCFRunLoopDefaultMode); | |
151 | CFRelease (completionSource); | |
152 | ||
153 | unlockJavaScriptAccess(); | |
154 | } | |
155 | ||
156 | static void performJavaScriptAccess(void*) | |
157 | { | |
158 | assert (CFRunLoopGetCurrent() == RootObject::runLoop()); | |
159 | ||
160 | // Dispatch JavaScript calls here. | |
161 | CFRunLoopSourceContext sourceContext; | |
162 | CFRunLoopSourceGetContext (completionSource, &sourceContext); | |
163 | JSObjectCallContext *callContext = (JSObjectCallContext *)sourceContext.info; | |
164 | CFRunLoopRef originatingLoop = callContext->originatingLoop; | |
165 | ||
166 | JavaJSObject::invoke (callContext); | |
167 | ||
168 | // Signal the originating thread that we're done. | |
169 | CFRunLoopSourceSignal (completionSource); | |
170 | if (CFRunLoopIsWaiting(originatingLoop)) { | |
171 | CFRunLoopWakeUp(originatingLoop); | |
172 | } | |
173 | } | |
174 | #endif // ENABLE(JAVA_BINDINGS) | |
175 | ||
176 | CreateRootObjectFunction RootObject::_createRootObject = 0; | |
177 | CFRunLoopRef RootObject::_runLoop = 0; | |
178 | CFRunLoopSourceRef RootObject::_performJavaScriptSource = 0; | |
179 | ||
180 | // Must be called from the thread that will be used to access JavaScript. | |
181 | void RootObject::setCreateRootObject(CreateRootObjectFunction createRootObject) { | |
182 | // Should only be called once. | |
183 | ASSERT(!_createRootObject); | |
184 | ||
185 | _createRootObject = createRootObject; | |
186 | ||
187 | // Assume that we can retain this run loop forever. It'll most | |
188 | // likely (always?) be the main loop. | |
189 | _runLoop = (CFRunLoopRef)CFRetain (CFRunLoopGetCurrent ()); | |
190 | ||
191 | // Setup a source the other threads can use to signal the _runLoop | |
192 | // thread that a JavaScript call needs to be invoked. | |
193 | ||
194 | #if ENABLE(JAVA_BINDINGS) | |
195 | CFRunLoopSourceContext sourceContext = {0, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, performJavaScriptAccess}; | |
196 | RootObject::_performJavaScriptSource = CFRunLoopSourceCreate(NULL, 0, &sourceContext); | |
197 | CFRunLoopAddSource(RootObject::_runLoop, RootObject::_performJavaScriptSource, kCFRunLoopDefaultMode); | |
198 | #endif // ENABLE(JAVA_BINDINGS) | |
199 | } | |
200 | ||
201 | ||
202 | PassRefPtr<RootObject> RootObject::create(const void* nativeHandle, JSGlobalObject* globalObject) | |
203 | { | |
204 | return new RootObject(nativeHandle, globalObject); | |
205 | } | |
206 | ||
207 | RootObject::RootObject(const void* nativeHandle, JSGlobalObject* globalObject) | |
208 | : m_isValid(true) | |
209 | , m_nativeHandle(nativeHandle) | |
210 | , m_globalObject(globalObject) | |
211 | { | |
212 | ASSERT(globalObject); | |
213 | rootObjectSet()->add(this); | |
214 | } | |
215 | ||
216 | RootObject::~RootObject() | |
217 | { | |
218 | if (m_isValid) | |
219 | invalidate(); | |
220 | } | |
221 | ||
222 | void RootObject::invalidate() | |
223 | { | |
224 | if (!m_isValid) | |
225 | return; | |
226 | ||
227 | { | |
228 | HashSet<RuntimeObjectImp*>::iterator end = m_runtimeObjects.end(); | |
229 | for (HashSet<RuntimeObjectImp*>::iterator it = m_runtimeObjects.begin(); it != end; ++it) | |
230 | (*it)->invalidate(); | |
231 | ||
232 | m_runtimeObjects.clear(); | |
233 | } | |
234 | ||
235 | m_isValid = false; | |
236 | ||
237 | m_nativeHandle = 0; | |
238 | m_globalObject = 0; | |
239 | ||
240 | ProtectCountSet::iterator end = m_protectCountSet.end(); | |
241 | for (ProtectCountSet::iterator it = m_protectCountSet.begin(); it != end; ++it) { | |
242 | JSLock lock; | |
243 | KJS::gcUnprotect(it->first); | |
244 | } | |
245 | m_protectCountSet.clear(); | |
246 | ||
247 | rootObjectSet()->remove(this); | |
248 | } | |
249 | ||
250 | void RootObject::gcProtect(JSObject* jsObject) | |
251 | { | |
252 | ASSERT(m_isValid); | |
253 | ||
254 | if (!m_protectCountSet.contains(jsObject)) { | |
255 | JSLock lock; | |
256 | KJS::gcProtect(jsObject); | |
257 | } | |
258 | m_protectCountSet.add(jsObject); | |
259 | } | |
260 | ||
261 | void RootObject::gcUnprotect(JSObject* jsObject) | |
262 | { | |
263 | ASSERT(m_isValid); | |
264 | ||
265 | if (!jsObject) | |
266 | return; | |
267 | ||
268 | if (m_protectCountSet.count(jsObject) == 1) { | |
269 | JSLock lock; | |
270 | KJS::gcUnprotect(jsObject); | |
271 | } | |
272 | m_protectCountSet.remove(jsObject); | |
273 | } | |
274 | ||
275 | bool RootObject::gcIsProtected(JSObject* jsObject) | |
276 | { | |
277 | ASSERT(m_isValid); | |
278 | return m_protectCountSet.contains(jsObject); | |
279 | } | |
280 | ||
281 | const void* RootObject::nativeHandle() const | |
282 | { | |
283 | ASSERT(m_isValid); | |
284 | return m_nativeHandle; | |
285 | } | |
286 | ||
287 | JSGlobalObject* RootObject::globalObject() const | |
288 | { | |
289 | ASSERT(m_isValid); | |
290 | return m_globalObject; | |
291 | } | |
292 | ||
293 | void RootObject::addRuntimeObject(RuntimeObjectImp* object) | |
294 | { | |
295 | ASSERT(m_isValid); | |
296 | ASSERT(!m_runtimeObjects.contains(object)); | |
297 | ||
298 | m_runtimeObjects.add(object); | |
299 | } | |
300 | ||
301 | void RootObject::removeRuntimeObject(RuntimeObjectImp* object) | |
302 | { | |
303 | ASSERT(m_isValid); | |
304 | ASSERT(m_runtimeObjects.contains(object)); | |
305 | ||
306 | m_runtimeObjects.remove(object); | |
307 | } | |
308 | ||
309 | } } // namespace KJS::Bindings |