2009-04-09 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 "V8WorkerContextObjectEventListener.h"
43 #include "WorkerContext.h"
44 #include "WorkerLocation.h"
45 #include "WorkerNavigator.h"
46 #include "WorkerScriptController.h"
47
48 namespace WebCore {
49
50 static bool isWorkersEnabled = false;
51
52 bool WorkerContextExecutionProxy::isWebWorkersEnabled()
53 {
54     return isWorkersEnabled;
55 }
56
57 void WorkerContextExecutionProxy::setIsWebWorkersEnabled(bool value)
58 {
59     isWorkersEnabled = value;
60 }
61
62 WorkerContextExecutionProxy::WorkerContextExecutionProxy(WorkerContext* workerContext)
63     : m_workerContext(workerContext)
64     , m_recursion(0)
65 {
66     // Need to use lock since V8EventListenerList constructor creates HandleScope.
67     v8::Locker locker;
68     m_listeners.set(new V8EventListenerList("m_listeners"));
69 }
70
71 WorkerContextExecutionProxy::~WorkerContextExecutionProxy()
72 {
73     dispose();
74 }
75
76 void WorkerContextExecutionProxy::dispose()
77 {
78     // Disconnect all event listeners.
79     for (V8EventListenerList::iterator iterator(m_listeners->begin()); iterator != m_listeners->end(); ++iterator)
80        static_cast<V8WorkerContextEventListener*>(*iterator)->disconnect();
81
82     {
83         // Need to use lock since V8EventListenerList::clear() creates HandleScope.
84         v8::Locker locker;
85         m_listeners->clear();
86     }
87
88     // Detach all events from their JS wrappers.
89     for (size_t eventIndex = 0; eventIndex < m_events.size(); ++eventIndex) {
90         Event* event = m_events[eventIndex];
91         if (forgetV8EventObject(event))
92           event->deref();
93     }
94     m_events.clear();
95
96     // Dispose the context.
97     if (!m_context.IsEmpty()) {
98         m_context.Dispose();
99         m_context.Clear();
100     }
101
102     // Remove the wrapping between JS object and DOM object. This is because
103     // the worker context object is going to be disposed immediately when a
104     // worker thread is tearing down. We do not want to re-delete the real object
105     // when JS object is garbage collected.
106     v8::Locker locker;
107     v8::HandleScope scope;
108     v8::Persistent<v8::Object> wrapper = domObjectMap().get(m_workerContext);
109     if (!wrapper.IsEmpty())
110         V8Proxy::SetDOMWrapper(wrapper, V8ClassIndex::INVALID_CLASS_INDEX, 0);
111     domObjectMap().forget(m_workerContext);
112 }
113
114 WorkerContextExecutionProxy* WorkerContextExecutionProxy::retrieve()
115 {
116     v8::Handle<v8::Context> context = v8::Context::GetCurrent();
117     v8::Handle<v8::Object> global = context->Global();
118     global = V8Proxy::LookupDOMWrapper(V8ClassIndex::WORKERCONTEXT, global);
119     // Return 0 if the current executing context is not the worker context.
120     if (global.IsEmpty())
121         return 0;
122     WorkerContext* workerContext = V8Proxy::ToNativeObject<WorkerContext>(V8ClassIndex::WORKERCONTEXT, global);
123     return workerContext->script()->proxy();
124 }
125
126 void WorkerContextExecutionProxy::initContextIfNeeded()
127 {
128     // Bail out if the context has already been initialized.
129     if (!m_context.IsEmpty())
130         return;
131
132     // Create a new environment
133     v8::Persistent<v8::ObjectTemplate> globalTemplate;
134     m_context = v8::Context::New(0, globalTemplate);
135
136     // Starting from now, use local context only.
137     v8::Local<v8::Context> context = v8::Local<v8::Context>::New(m_context);
138     v8::Context::Scope scope(context);
139
140     // Allocate strings used during initialization.
141     v8::Handle<v8::String> implicitProtoString = v8::String::New("__proto__");
142
143     // Create a new JS object and use it as the prototype for the shadow global object.
144     v8::Handle<v8::Function> workerContextConstructor = GetConstructor(V8ClassIndex::WORKERCONTEXT);
145     v8::Local<v8::Object> jsWorkerContext = SafeAllocation::NewInstance(workerContextConstructor);
146     // Bail out if allocation failed.
147     if (jsWorkerContext.IsEmpty()) {
148         dispose();
149         return;
150     }
151
152     // Wrap the object.
153     V8Proxy::SetDOMWrapper(jsWorkerContext, V8ClassIndex::ToInt(V8ClassIndex::WORKERCONTEXT), m_workerContext);
154
155     V8Proxy::SetJSWrapperForDOMObject(m_workerContext, v8::Persistent<v8::Object>::New(jsWorkerContext));
156
157     // Insert the object instance as the prototype of the shadow object.
158     v8::Handle<v8::Object> globalObject = m_context->Global();
159     globalObject->Set(implicitProtoString, jsWorkerContext);
160 }
161
162 v8::Local<v8::Function> WorkerContextExecutionProxy::GetConstructor(V8ClassIndex::V8WrapperType type)
163 {
164     // Enter the context of the proxy to make sure that the function is
165     // constructed in the context corresponding to this proxy.
166     v8::Context::Scope scope(m_context);
167     v8::Handle<v8::FunctionTemplate> functionTemplate = V8Proxy::GetTemplate(type);
168
169     // Getting the function might fail if we're running out of stack or memory.
170     v8::TryCatch tryCatch;
171     v8::Local<v8::Function> value = functionTemplate->GetFunction();
172     if (value.IsEmpty())
173         return v8::Local<v8::Function>();
174
175     return value;
176 }
177
178 v8::Handle<v8::Value> WorkerContextExecutionProxy::ToV8Object(V8ClassIndex::V8WrapperType type, void* impl)
179 {
180     if (!impl)
181         return v8::Null();
182
183     if (type == V8ClassIndex::WORKERCONTEXT)
184         return WorkerContextToV8Object(static_cast<WorkerContext*>(impl));
185
186     // Non DOM node
187     v8::Persistent<v8::Object> result = domObjectMap().get(impl);
188     if (result.IsEmpty()) {
189         v8::Local<v8::Object> object = toV8(type, type, impl);
190         if (!object.IsEmpty()) {
191             switch (type) {
192             case V8ClassIndex::WORKERLOCATION:
193                 static_cast<WorkerLocation*>(impl)->ref();
194                 break;
195             case V8ClassIndex::WORKERNAVIGATOR:
196                 static_cast<WorkerNavigator*>(impl)->ref();
197                 break;
198             default:
199                 ASSERT(false);
200             }
201             result = v8::Persistent<v8::Object>::New(object);
202             V8Proxy::SetJSWrapperForDOMObject(impl, result);
203         }
204     }
205     return result;
206 }
207
208 v8::Handle<v8::Value> WorkerContextExecutionProxy::EventToV8Object(Event* event)
209 {
210     if (!event)
211         return v8::Null();
212
213     v8::Handle<v8::Object> wrapper = domObjectMap().get(event);
214     if (!wrapper.IsEmpty())
215         return wrapper;
216
217     V8ClassIndex::V8WrapperType type = V8ClassIndex::EVENT;
218
219     if (event->isMessageEvent())
220         type = V8ClassIndex::MESSAGEEVENT;
221
222     v8::Handle<v8::Object> result = toV8(type, V8ClassIndex::EVENT, event);
223     if (result.IsEmpty()) {
224         // Instantiation failed. Avoid updating the DOM object map and return null which
225         // is already handled by callers of this function in case the event is NULL.
226         return v8::Null();
227     }
228
229     event->ref();  // fast ref
230     V8Proxy::SetJSWrapperForDOMObject(event, v8::Persistent<v8::Object>::New(result));
231
232     return result;
233 }
234
235 // A JS object of type EventTarget in the worker context can only be WorkerContext.
236 v8::Handle<v8::Value> WorkerContextExecutionProxy::EventTargetToV8Object(EventTarget* target)
237 {
238     if (!target)
239         return v8::Null();
240
241     WorkerContext* workerContext = target->toWorkerContext();
242     if (workerContext)
243         return WorkerContextToV8Object(workerContext);
244
245     ASSERT_NOT_REACHED();
246     return v8::Handle<v8::Value>();
247 }
248
249 v8::Handle<v8::Value> WorkerContextExecutionProxy::WorkerContextToV8Object(WorkerContext* workerContext)
250 {
251     if (!workerContext)
252         return v8::Null();
253
254     v8::Handle<v8::Context> context = workerContext->script()->proxy()->GetContext();
255
256     v8::Handle<v8::Object> global = context->Global();
257     ASSERT(!global.IsEmpty());
258     return global;
259 }
260
261 v8::Local<v8::Object> WorkerContextExecutionProxy::toV8(V8ClassIndex::V8WrapperType descType, V8ClassIndex::V8WrapperType cptrType, void* impl)
262 {
263     v8::Local<v8::Function> function;
264     WorkerContextExecutionProxy* proxy = retrieve();
265     if (proxy)
266         function = proxy->GetConstructor(descType);
267     else
268         function = V8Proxy::GetTemplate(descType)->GetFunction();
269
270     v8::Local<v8::Object> instance = SafeAllocation::NewInstance(function);
271     if (!instance.IsEmpty()) {
272         // Avoid setting the DOM wrapper for failed allocations.
273         V8Proxy::SetDOMWrapper(instance, V8ClassIndex::ToInt(cptrType), impl);
274     }
275     return instance;
276 }
277
278 bool WorkerContextExecutionProxy::forgetV8EventObject(Event* event)
279 {
280     if (domObjectMap().contains(event)) {
281         domObjectMap().forget(event);
282         return true;
283     } else
284         return false;
285 }
286
287 v8::Local<v8::Value> WorkerContextExecutionProxy::evaluate(const String& script, const String& fileName, int baseLine)
288 {
289     v8::Locker locker;
290     v8::HandleScope hs;
291
292     // Enable preemption so that one worker will not be blocked by another long-running worker.
293     const int workerThreadPreemptionIntervalMs = 100;
294     v8::Locker::StartPreemption(workerThreadPreemptionIntervalMs);
295
296     initContextIfNeeded();
297     v8::Context::Scope scope(m_context);
298
299     v8::Local<v8::String> scriptString = v8ExternalString(script);
300     v8::Handle<v8::Script> compiledScript = V8Proxy::CompileScript(scriptString, fileName, baseLine);
301     return runScript(compiledScript);
302 }
303
304 v8::Local<v8::Value> WorkerContextExecutionProxy::runScript(v8::Handle<v8::Script> script)
305 {
306     if (script.IsEmpty())
307         return v8::Local<v8::Value>();
308
309     // Compute the source string and prevent against infinite recursion.
310     if (m_recursion >= kMaxRecursionDepth) {
311         v8::Local<v8::String> code = v8ExternalString("throw RangeError('Recursion too deep')");
312         script = V8Proxy::CompileScript(code, "", 0);
313     }
314
315     if (V8Proxy::HandleOutOfMemory())
316         ASSERT(script.IsEmpty());
317
318     if (script.IsEmpty())
319         return v8::Local<v8::Value>();
320
321     // Run the script and keep track of the current recursion depth.
322     v8::Local<v8::Value> result;
323     {
324         m_recursion++;
325         result = script->Run();
326         m_recursion--;
327     }
328
329     // Handle V8 internal error situation (Out-of-memory).
330     if (result.IsEmpty())
331         return v8::Local<v8::Value>();
332
333     return result;
334 }
335
336 PassRefPtr<V8EventListener> WorkerContextExecutionProxy::findOrCreateEventListenerHelper(v8::Local<v8::Value> object, bool isInline, bool findOnly, bool createObjectEventListener)
337 {
338     if (!object->IsObject())
339         return 0;
340
341     V8EventListener* listener = m_listeners->find(object->ToObject(), isInline);
342     if (findOnly)
343         return listener;
344
345     // Create a new one, and add to cache.
346     RefPtr<V8EventListener> newListener;
347     if (createObjectEventListener)
348         newListener = V8WorkerContextObjectEventListener::create(this, v8::Local<v8::Object>::Cast(object), isInline);
349     else
350         newListener = V8WorkerContextEventListener::create(this, v8::Local<v8::Object>::Cast(object), isInline);
351     {
352         // Need to use lock since V8EventListenerList::add() creates HandleScope.
353         v8::Locker locker;
354         m_listeners->add(newListener.get());
355     }
356
357     return newListener.release();
358 }
359
360 PassRefPtr<V8EventListener> WorkerContextExecutionProxy::FindOrCreateEventListener(v8::Local<v8::Value> object, bool isInline, bool findOnly)
361 {
362     return findOrCreateEventListenerHelper(object, isInline, findOnly, false);
363 }
364
365 PassRefPtr<V8EventListener> WorkerContextExecutionProxy::findOrCreateObjectEventListener(v8::Local<v8::Value> object, bool isInline, bool findOnly)
366 {
367     return findOrCreateEventListenerHelper(object, isInline, findOnly, true);
368 }
369
370 void WorkerContextExecutionProxy::RemoveEventListener(V8EventListener* listener)
371 {
372     // Need to use lock since V8EventListenerList::remove() creates HandleScope.
373     v8::Locker locker;
374     m_listeners->remove(listener);
375 }
376
377 void WorkerContextExecutionProxy::trackEvent(Event* event)
378 {
379     m_events.append(event);
380 }
381
382 } // namespace WebCore
383
384 #endif // ENABLE(WORKERS)