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