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