2009-04-03 Jian Li <jianli@chromium.org>
[WebKit-https.git] / WebCore / bindings / v8 / WorkerContextExecutionProxy.cpp
1 /*
2  * Copyright (C) 2009 Google 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 are
6  * met:
7  *
8  *     * Redistributions of source code must retain the above copyright
9  * notice, this list of conditions and the following disclaimer.
10  *     * Redistributions in binary form must reproduce the above
11  * copyright notice, this list of conditions and the following disclaimer
12  * in the documentation and/or other materials provided with the
13  * distribution.
14  *     * Neither the name of Google Inc. nor the names of its
15  * contributors may be used to endorse or promote products derived from
16  * this software without specific prior written permission.
17  *
18  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29  */
30
31
32 #include "config.h"
33
34 #if ENABLE(WORKERS)
35
36 #include "WorkerContextExecutionProxy.h"
37
38 #include "V8Binding.h"
39 #include "V8Proxy.h"
40 #include "Event.h"
41 #include "V8WorkerContextEventListener.h"
42 #include "WorkerContext.h"
43 #include "WorkerLocation.h"
44 #include "WorkerNavigator.h"
45 #include "WorkerScriptController.h"
46
47 namespace WebCore {
48
49 static bool isWorkersEnabled = false;
50
51 bool WorkerContextExecutionProxy::isWebWorkersEnabled()
52 {
53     return isWorkersEnabled;
54 }
55
56 void WorkerContextExecutionProxy::setIsWebWorkersEnabled(bool value)
57 {
58     isWorkersEnabled = value;
59 }
60
61 WorkerContextExecutionProxy::WorkerContextExecutionProxy(WorkerContext* workerContext)
62     : m_workerContext(workerContext)
63     , m_recursion(0)
64 {
65     // Need to use lock since V8EventListenerList constructor creates HandleScope.
66     v8::Locker locker;
67     m_listeners.set(new V8EventListenerList("m_listeners"));
68 }
69
70 WorkerContextExecutionProxy::~WorkerContextExecutionProxy()
71 {
72     dispose();
73 }
74
75 void WorkerContextExecutionProxy::dispose()
76 {
77     // Disconnect all event listeners.
78     for (V8EventListenerList::iterator iterator(m_listeners->begin()); iterator != m_listeners->end(); ++iterator)
79        static_cast<V8WorkerContextEventListener*>(*iterator)->disconnect();
80
81     {
82         // Need to use lock since V8EventListenerList::clear() creates HandleScope.
83         v8::Locker locker;
84         m_listeners->clear();
85     }
86
87     // Detach all events from their JS wrappers.
88     for (size_t eventIndex = 0; eventIndex < m_events.size(); ++eventIndex) {
89         Event* event = m_events[eventIndex];
90         if (forgetV8EventObject(event))
91           event->deref();
92     }
93     m_events.clear();
94
95     // Dispose the context.
96     if (!m_context.IsEmpty()) {
97         m_context.Dispose();
98         m_context.Clear();
99     }
100
101     // Remove the wrapping between JS object and DOM object. This is because
102     // the worker context object is going to be disposed immediately when a
103     // worker thread is tearing down. We do not want to re-delete the real object
104     // when JS object is garbage collected.
105     v8::Locker locker;
106     v8::HandleScope scope;
107     v8::Persistent<v8::Object> wrapper = domObjectMap().get(m_workerContext);
108     if (!wrapper.IsEmpty())
109         V8Proxy::SetDOMWrapper(wrapper, V8ClassIndex::INVALID_CLASS_INDEX, NULL);
110     domObjectMap().forget(m_workerContext);
111 }
112
113 WorkerContextExecutionProxy* WorkerContextExecutionProxy::retrieve()
114 {
115     v8::Handle<v8::Context> context = v8::Context::GetCurrent();
116     v8::Handle<v8::Object> global = context->Global();
117     global = V8Proxy::LookupDOMWrapper(V8ClassIndex::WORKERCONTEXT, global);
118     ASSERT(!global.IsEmpty());
119     WorkerContext* workerContext = V8Proxy::ToNativeObject<WorkerContext>(V8ClassIndex::WORKERCONTEXT, global);
120     return workerContext->script()->proxy();
121 }
122
123 void WorkerContextExecutionProxy::initContextIfNeeded()
124 {
125     // Bail out if the context has already been initialized.
126     if (!m_context.IsEmpty())
127         return;
128
129     // Create a new environment
130     v8::Persistent<v8::ObjectTemplate> globalTemplate;
131     m_context = v8::Context::New(NULL, globalTemplate);
132
133     // Starting from now, use local context only.
134     v8::Local<v8::Context> context = v8::Local<v8::Context>::New(m_context);
135     v8::Context::Scope scope(context);
136
137     // Allocate strings used during initialization.
138     v8::Handle<v8::String> implicitProtoString = v8::String::New("__proto__");
139
140     // Create a new JS object and use it as the prototype for the shadow global object.
141     v8::Handle<v8::Function> workerContextConstructor = GetConstructor(V8ClassIndex::WORKERCONTEXT);
142     v8::Local<v8::Object> jsWorkerContext = SafeAllocation::NewInstance(workerContextConstructor);
143     // Bail out if allocation failed.
144     if (jsWorkerContext.IsEmpty()) {
145         dispose();
146         return;
147     }
148
149     // Wrap the object.
150     V8Proxy::SetDOMWrapper(jsWorkerContext, V8ClassIndex::ToInt(V8ClassIndex::WORKERCONTEXT), m_workerContext);
151
152     V8Proxy::SetJSWrapperForDOMObject(m_workerContext, v8::Persistent<v8::Object>::New(jsWorkerContext));
153
154     // Insert the object instance as the prototype of the shadow object.
155     v8::Handle<v8::Object> globalObject = m_context->Global();
156     globalObject->Set(implicitProtoString, jsWorkerContext);
157 }
158
159 v8::Local<v8::Function> WorkerContextExecutionProxy::GetConstructor(V8ClassIndex::V8WrapperType type)
160 {
161     // Enter the context of the proxy to make sure that the function is
162     // constructed in the context corresponding to this proxy.
163     v8::Context::Scope scope(m_context);
164     v8::Handle<v8::FunctionTemplate> functionTemplate = V8Proxy::GetTemplate(type);
165
166     // Getting the function might fail if we're running out of stack or memory.
167     v8::TryCatch tryCatch;
168     v8::Local<v8::Function> value = functionTemplate->GetFunction();
169     if (value.IsEmpty())
170         return v8::Local<v8::Function>();
171
172     return value;
173 }
174
175 v8::Handle<v8::Value> WorkerContextExecutionProxy::ToV8Object(V8ClassIndex::V8WrapperType type, void* impl)
176 {
177     if (!impl)
178         return v8::Null();
179
180     if (type == V8ClassIndex::WORKERCONTEXT)
181         return WorkerContextToV8Object(static_cast<WorkerContext*>(impl));
182
183     // Non DOM node
184     v8::Persistent<v8::Object> result = domObjectMap().get(impl);
185     if (result.IsEmpty()) {
186         v8::Local<v8::Object> object = toV8(type, type, impl);
187         if (!object.IsEmpty()) {
188             switch (type) {
189             case V8ClassIndex::WORKERLOCATION:
190                 static_cast<WorkerLocation*>(impl)->ref();
191                 break;
192             case V8ClassIndex::WORKERNAVIGATOR:
193                 static_cast<WorkerNavigator*>(impl)->ref();
194                 break;
195             default:
196                 ASSERT(false);
197             }
198             result = v8::Persistent<v8::Object>::New(object);
199             V8Proxy::SetJSWrapperForDOMObject(impl, result);
200         }
201     }
202     return result;
203 }
204
205 v8::Handle<v8::Value> WorkerContextExecutionProxy::EventToV8Object(Event* event)
206 {
207     if (!event)
208         return v8::Null();
209
210     v8::Handle<v8::Object> wrapper = domObjectMap().get(event);
211     if (!wrapper.IsEmpty())
212         return wrapper;
213
214     V8ClassIndex::V8WrapperType type = V8ClassIndex::EVENT;
215
216     if (event->isMessageEvent())
217         type = V8ClassIndex::MESSAGEEVENT;
218
219     v8::Handle<v8::Object> result = toV8(type, V8ClassIndex::EVENT, event);
220     if (result.IsEmpty()) {
221         // Instantiation failed. Avoid updating the DOM object map and return null which
222         // is already handled by callers of this function in case the event is NULL.
223         return v8::Null();
224     }
225
226     event->ref();  // fast ref
227     V8Proxy::SetJSWrapperForDOMObject(event, v8::Persistent<v8::Object>::New(result));
228
229     return result;
230 }
231
232 // A JS object of type EventTarget in the worker context can only be WorkerContext.
233 v8::Handle<v8::Value> WorkerContextExecutionProxy::EventTargetToV8Object(EventTarget* target)
234 {
235     if (!target)
236         return v8::Null();
237
238     WorkerContext* workerContext = target->toWorkerContext();
239     if (workerContext)
240         return WorkerContextToV8Object(workerContext);
241
242     ASSERT_NOT_REACHED();
243     return v8::Handle<v8::Value>();
244 }
245
246 v8::Handle<v8::Value> WorkerContextExecutionProxy::WorkerContextToV8Object(WorkerContext* workerContext)
247 {
248     if (!workerContext)
249         return v8::Null();
250
251     v8::Handle<v8::Context> context = workerContext->script()->proxy()->GetContext();
252
253     v8::Handle<v8::Object> global = context->Global();
254     ASSERT(!global.IsEmpty());
255     return global;
256 }
257
258 v8::Local<v8::Object> WorkerContextExecutionProxy::toV8(V8ClassIndex::V8WrapperType descType, V8ClassIndex::V8WrapperType cptrType, void* impl)
259 {
260     v8::Local<v8::Function> function;
261     WorkerContextExecutionProxy* proxy = retrieve();
262     if (proxy)
263         function = proxy->GetConstructor(descType);
264     else
265         function = V8Proxy::GetTemplate(descType)->GetFunction();
266
267     v8::Local<v8::Object> instance = SafeAllocation::NewInstance(function);
268     if (!instance.IsEmpty()) {
269         // Avoid setting the DOM wrapper for failed allocations.
270         V8Proxy::SetDOMWrapper(instance, V8ClassIndex::ToInt(cptrType), impl);
271     }
272     return instance;
273 }
274
275 bool WorkerContextExecutionProxy::forgetV8EventObject(Event* event)
276 {
277     if (domObjectMap().contains(event)) {
278         domObjectMap().forget(event);
279         return true;
280     } else
281         return false;
282 }
283
284 v8::Local<v8::Value> WorkerContextExecutionProxy::evaluate(const String& script, const String& fileName, int baseLine)
285 {
286     v8::Locker locker;
287     v8::HandleScope hs;
288
289     initContextIfNeeded();
290     v8::Context::Scope scope(m_context);
291
292     v8::Local<v8::String> scriptString = v8ExternalString(script);
293     v8::Handle<v8::Script> compiledScript = V8Proxy::CompileScript(scriptString, fileName, baseLine);
294     return runScript(compiledScript);
295 }
296
297 v8::Local<v8::Value> WorkerContextExecutionProxy::runScript(v8::Handle<v8::Script> script)
298 {
299     if (script.IsEmpty())
300         return v8::Local<v8::Value>();
301
302     // Compute the source string and prevent against infinite recursion.
303     if (m_recursion >= kMaxRecursionDepth) {
304         v8::Local<v8::String> code = v8ExternalString("throw RangeError('Recursion too deep')");
305         script = V8Proxy::CompileScript(code, "", 0);
306     }
307
308     if (V8Proxy::HandleOutOfMemory())
309         ASSERT(script.IsEmpty());
310
311     if (script.IsEmpty())
312         return v8::Local<v8::Value>();
313
314     // Run the script and keep track of the current recursion depth.
315     v8::Local<v8::Value> result;
316     {
317         m_recursion++;
318         result = script->Run();
319         m_recursion--;
320     }
321
322     // Handle V8 internal error situation (Out-of-memory).
323     if (result.IsEmpty())
324         return v8::Local<v8::Value>();
325
326     return result;
327 }
328
329 PassRefPtr<V8EventListener> WorkerContextExecutionProxy::FindOrCreateEventListener(v8::Local<v8::Value> object, bool isInline, bool findOnly)
330 {
331     if (!object->IsObject())
332         return 0;
333
334     V8EventListener* listener = m_listeners->find(object->ToObject(), isInline);
335     if (findOnly)
336         return listener;
337
338     // Create a new one, and add to cache.
339     RefPtr<V8WorkerContextEventListener> newListener = V8WorkerContextEventListener::create(this, v8::Local<v8::Object>::Cast(object), isInline);
340     {
341         // Need to use lock since V8EventListenerList::add() creates HandleScope.
342         v8::Locker locker;
343         m_listeners->add(newListener.get());
344     }
345
346     return newListener.release();
347 }
348
349 void WorkerContextExecutionProxy::RemoveEventListener(V8EventListener* listener)
350 {
351     // Need to use lock since V8EventListenerList::remove() creates HandleScope.
352     v8::Locker locker;
353     m_listeners->remove(listener);
354 }
355
356 void WorkerContextExecutionProxy::trackEvent(Event* event)
357 {
358     m_events.append(event);
359 }
360
361 } // namespace WebCore
362
363 #endif // ENABLE(WORKERS)