2cf043dc6559dd88ec110cd6c6a5765828be0f02
[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 || type == V8ClassIndex::XMLHTTPREQUEST) {
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     XMLHttpRequest* xhr = target->toXMLHttpRequest();
297     if (xhr)
298         return ToV8Object(V8ClassIndex::XMLHTTPREQUEST, xhr);
299
300     ASSERT_NOT_REACHED();
301     return v8::Handle<v8::Value>();
302 }
303
304 v8::Handle<v8::Value> WorkerContextExecutionProxy::WorkerContextToV8Object(WorkerContext* workerContext)
305 {
306     if (!workerContext)
307         return v8::Null();
308
309     v8::Handle<v8::Context> context = workerContext->script()->proxy()->GetContext();
310
311     v8::Handle<v8::Object> global = context->Global();
312     ASSERT(!global.IsEmpty());
313     return global;
314 }
315
316 v8::Local<v8::Object> WorkerContextExecutionProxy::toV8(V8ClassIndex::V8WrapperType descType, V8ClassIndex::V8WrapperType cptrType, void* impl)
317 {
318     v8::Local<v8::Function> function;
319     WorkerContextExecutionProxy* proxy = retrieve();
320     if (proxy)
321         function = proxy->GetConstructor(descType);
322     else
323         function = V8Proxy::GetTemplate(descType)->GetFunction();
324
325     v8::Local<v8::Object> instance = SafeAllocation::newInstance(function);
326     if (!instance.IsEmpty()) {
327         // Avoid setting the DOM wrapper for failed allocations.
328         V8Proxy::SetDOMWrapper(instance, V8ClassIndex::ToInt(cptrType), impl);
329     }
330     return instance;
331 }
332
333 bool WorkerContextExecutionProxy::forgetV8EventObject(Event* event)
334 {
335     if (domObjectMap().contains(event)) {
336         domObjectMap().forget(event);
337         return true;
338     } else
339         return false;
340 }
341
342 v8::Local<v8::Value> WorkerContextExecutionProxy::evaluate(const String& script, const String& fileName, int baseLine)
343 {
344     v8::HandleScope hs;
345
346     initContextIfNeeded();
347     v8::Context::Scope scope(m_context);
348
349     v8::Local<v8::String> scriptString = v8ExternalString(script);
350     v8::Handle<v8::Script> compiledScript = V8Proxy::CompileScript(scriptString, fileName, baseLine);
351     return runScript(compiledScript);
352 }
353
354 v8::Local<v8::Value> WorkerContextExecutionProxy::runScript(v8::Handle<v8::Script> script)
355 {
356     if (script.IsEmpty())
357         return v8::Local<v8::Value>();
358
359     // Compute the source string and prevent against infinite recursion.
360     if (m_recursion >= kMaxRecursionDepth) {
361         v8::Local<v8::String> code = v8ExternalString("throw RangeError('Recursion too deep')");
362         script = V8Proxy::CompileScript(code, "", 0);
363     }
364
365     if (V8Proxy::HandleOutOfMemory())
366         ASSERT(script.IsEmpty());
367
368     if (script.IsEmpty())
369         return v8::Local<v8::Value>();
370
371     // Run the script and keep track of the current recursion depth.
372     v8::Local<v8::Value> result;
373     {
374         m_recursion++;
375         result = script->Run();
376         m_recursion--;
377     }
378
379     // Handle V8 internal error situation (Out-of-memory).
380     if (result.IsEmpty())
381         return v8::Local<v8::Value>();
382
383     return result;
384 }
385
386 PassRefPtr<V8EventListener> WorkerContextExecutionProxy::findOrCreateEventListenerHelper(v8::Local<v8::Value> object, bool isInline, bool findOnly, bool createObjectEventListener)
387 {
388     if (!object->IsObject())
389         return 0;
390
391     V8EventListener* listener = m_listeners->find(object->ToObject(), isInline);
392     if (findOnly)
393         return listener;
394
395     // Create a new one, and add to cache.
396     RefPtr<V8EventListener> newListener;
397     if (createObjectEventListener)
398         newListener = V8WorkerContextObjectEventListener::create(this, v8::Local<v8::Object>::Cast(object), isInline);
399     else
400         newListener = V8WorkerContextEventListener::create(this, v8::Local<v8::Object>::Cast(object), isInline);
401     m_listeners->add(newListener.get());
402
403     return newListener.release();
404 }
405
406 PassRefPtr<V8EventListener> WorkerContextExecutionProxy::findOrCreateEventListener(v8::Local<v8::Value> object, bool isInline, bool findOnly)
407 {
408     return findOrCreateEventListenerHelper(object, isInline, findOnly, false);
409 }
410
411 PassRefPtr<V8EventListener> WorkerContextExecutionProxy::findOrCreateObjectEventListener(v8::Local<v8::Value> object, bool isInline, bool findOnly)
412 {
413     return findOrCreateEventListenerHelper(object, isInline, findOnly, true);
414 }
415
416 void WorkerContextExecutionProxy::RemoveEventListener(V8EventListener* listener)
417 {
418     m_listeners->remove(listener);
419 }
420
421 void WorkerContextExecutionProxy::trackEvent(Event* event)
422 {
423     m_events.append(event);
424 }
425
426 } // namespace WebCore
427
428 #endif // ENABLE(WORKERS)