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