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