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