|
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 #include "config.h" |
|
32 #include "V8DOMWrapper.h" |
|
33 |
|
34 #include "CSSMutableStyleDeclaration.h" |
|
35 #include "DOMDataStore.h" |
|
36 #include "DocumentLoader.h" |
|
37 #include "FrameLoaderClient.h" |
|
38 #include "Notification.h" |
|
39 #include "ScriptController.h" |
|
40 #include "V8AbstractEventListener.h" |
|
41 #include "V8Binding.h" |
|
42 #include "V8Collection.h" |
|
43 #include "V8CustomEventListener.h" |
|
44 #include "V8DedicatedWorkerContext.h" |
|
45 #include "V8DOMApplicationCache.h" |
|
46 #include "V8DOMMap.h" |
|
47 #include "V8DOMWindow.h" |
|
48 #include "V8EventListenerList.h" |
|
49 #include "V8EventSource.h" |
|
50 #include "V8FileReader.h" |
|
51 #include "V8HTMLCollection.h" |
|
52 #include "V8HTMLDocument.h" |
|
53 #include "V8IDBRequest.h" |
|
54 #include "V8IsolatedContext.h" |
|
55 #include "V8Location.h" |
|
56 #include "V8MessageChannel.h" |
|
57 #include "V8NamedNodeMap.h" |
|
58 #include "V8Node.h" |
|
59 #include "V8NodeFilterCondition.h" |
|
60 #include "V8NodeList.h" |
|
61 #include "V8Notification.h" |
|
62 #include "V8Proxy.h" |
|
63 #include "V8SharedWorker.h" |
|
64 #include "V8SharedWorkerContext.h" |
|
65 #include "V8StyleSheet.h" |
|
66 #include "V8WebSocket.h" |
|
67 #include "V8Worker.h" |
|
68 #include "V8WorkerContext.h" |
|
69 #include "V8WorkerContextEventListener.h" |
|
70 #include "V8XMLHttpRequest.h" |
|
71 #include "ArrayBufferView.h" |
|
72 #include "WebGLContextAttributes.h" |
|
73 #include "WebGLUniformLocation.h" |
|
74 #include "WorkerContextExecutionProxy.h" |
|
75 #include "WrapperTypeInfo.h" |
|
76 |
|
77 #if ENABLE(SVG) |
|
78 #include "SVGElementInstance.h" |
|
79 #include "SVGPathSeg.h" |
|
80 #include "V8SVGElementInstance.h" |
|
81 #endif |
|
82 |
|
83 #include <algorithm> |
|
84 #include <utility> |
|
85 #include <v8-debug.h> |
|
86 #include <wtf/Assertions.h> |
|
87 #include <wtf/OwnArrayPtr.h> |
|
88 #include <wtf/StdLibExtras.h> |
|
89 #include <wtf/UnusedParam.h> |
|
90 |
|
91 namespace WebCore { |
|
92 |
|
93 typedef HashMap<Node*, v8::Object*> DOMNodeMap; |
|
94 typedef HashMap<void*, v8::Object*> DOMObjectMap; |
|
95 |
|
96 // The caller must have increased obj's ref count. |
|
97 void V8DOMWrapper::setJSWrapperForDOMObject(void* object, v8::Persistent<v8::Object> wrapper) |
|
98 { |
|
99 ASSERT(V8DOMWrapper::maybeDOMWrapper(wrapper)); |
|
100 ASSERT(!domWrapperType(wrapper)->toActiveDOMObjectFunction); |
|
101 getDOMObjectMap().set(object, wrapper); |
|
102 } |
|
103 |
|
104 // The caller must have increased obj's ref count. |
|
105 void V8DOMWrapper::setJSWrapperForActiveDOMObject(void* object, v8::Persistent<v8::Object> wrapper) |
|
106 { |
|
107 ASSERT(V8DOMWrapper::maybeDOMWrapper(wrapper)); |
|
108 ASSERT(domWrapperType(wrapper)->toActiveDOMObjectFunction); |
|
109 getActiveDOMObjectMap().set(object, wrapper); |
|
110 } |
|
111 |
|
112 // The caller must have increased node's ref count. |
|
113 void V8DOMWrapper::setJSWrapperForDOMNode(Node* node, v8::Persistent<v8::Object> wrapper) |
|
114 { |
|
115 ASSERT(V8DOMWrapper::maybeDOMWrapper(wrapper)); |
|
116 getDOMNodeMap().set(node, wrapper); |
|
117 } |
|
118 |
|
119 v8::Local<v8::Function> V8DOMWrapper::getConstructor(WrapperTypeInfo* type, v8::Handle<v8::Value> objectPrototype) |
|
120 { |
|
121 // A DOM constructor is a function instance created from a DOM constructor |
|
122 // template. There is one instance per context. A DOM constructor is |
|
123 // different from a normal function in two ways: |
|
124 // 1) it cannot be called as constructor (aka, used to create a DOM object) |
|
125 // 2) its __proto__ points to Object.prototype rather than |
|
126 // Function.prototype. |
|
127 // The reason for 2) is that, in Safari, a DOM constructor is a normal JS |
|
128 // object, but not a function. Hotmail relies on the fact that, in Safari, |
|
129 // HTMLElement.__proto__ == Object.prototype. |
|
130 v8::Handle<v8::FunctionTemplate> functionTemplate = type->getTemplate(); |
|
131 // Getting the function might fail if we're running out of |
|
132 // stack or memory. |
|
133 v8::TryCatch tryCatch; |
|
134 v8::Local<v8::Function> value = functionTemplate->GetFunction(); |
|
135 if (value.IsEmpty()) |
|
136 return v8::Local<v8::Function>(); |
|
137 // Hotmail fix, see comments above. |
|
138 if (!objectPrototype.IsEmpty()) |
|
139 value->SetPrototype(objectPrototype); |
|
140 return value; |
|
141 } |
|
142 |
|
143 v8::Local<v8::Function> V8DOMWrapper::getConstructorForContext(WrapperTypeInfo* type, v8::Handle<v8::Context> context) |
|
144 { |
|
145 // Enter the scope for this context to get the correct constructor. |
|
146 v8::Context::Scope scope(context); |
|
147 |
|
148 return getConstructor(type, V8DOMWindowShell::getHiddenObjectPrototype(context)); |
|
149 } |
|
150 |
|
151 v8::Local<v8::Function> V8DOMWrapper::getConstructor(WrapperTypeInfo* type, DOMWindow* window) |
|
152 { |
|
153 Frame* frame = window->frame(); |
|
154 if (!frame) |
|
155 return v8::Local<v8::Function>(); |
|
156 |
|
157 v8::Handle<v8::Context> context = V8Proxy::context(frame); |
|
158 if (context.IsEmpty()) |
|
159 return v8::Local<v8::Function>(); |
|
160 |
|
161 return getConstructorForContext(type, context); |
|
162 } |
|
163 |
|
164 #if ENABLE(WORKERS) |
|
165 v8::Local<v8::Function> V8DOMWrapper::getConstructor(WrapperTypeInfo* type, WorkerContext*) |
|
166 { |
|
167 WorkerScriptController* controller = WorkerScriptController::controllerForContext(); |
|
168 WorkerContextExecutionProxy* proxy = controller ? controller->proxy() : 0; |
|
169 if (!proxy) |
|
170 return v8::Local<v8::Function>(); |
|
171 |
|
172 v8::Handle<v8::Context> context = proxy->context(); |
|
173 if (context.IsEmpty()) |
|
174 return v8::Local<v8::Function>(); |
|
175 |
|
176 return getConstructorForContext(type, context); |
|
177 } |
|
178 #endif |
|
179 |
|
180 void V8DOMWrapper::setHiddenReference(v8::Handle<v8::Object> parent, v8::Handle<v8::Value> child) |
|
181 { |
|
182 v8::Local<v8::Value> hiddenReferenceObject = parent->GetInternalField(v8DOMHiddenReferenceArrayIndex); |
|
183 if (hiddenReferenceObject->IsNull() || hiddenReferenceObject->IsUndefined()) { |
|
184 hiddenReferenceObject = v8::Array::New(); |
|
185 parent->SetInternalField(v8DOMHiddenReferenceArrayIndex, hiddenReferenceObject); |
|
186 } |
|
187 v8::Local<v8::Array> hiddenReferenceArray = v8::Local<v8::Array>::Cast(hiddenReferenceObject); |
|
188 hiddenReferenceArray->Set(v8::Integer::New(hiddenReferenceArray->Length()), child); |
|
189 } |
|
190 |
|
191 void V8DOMWrapper::setHiddenWindowReference(Frame* frame, v8::Handle<v8::Value> jsObject) |
|
192 { |
|
193 // Get DOMWindow |
|
194 if (!frame) |
|
195 return; // Object might be detached from window |
|
196 v8::Handle<v8::Context> context = V8Proxy::context(frame); |
|
197 if (context.IsEmpty()) |
|
198 return; |
|
199 |
|
200 v8::Handle<v8::Object> global = context->Global(); |
|
201 // Look for real DOM wrapper. |
|
202 global = V8DOMWrapper::lookupDOMWrapper(V8DOMWindow::GetTemplate(), global); |
|
203 ASSERT(!global.IsEmpty()); |
|
204 |
|
205 setHiddenReference(global, jsObject); |
|
206 } |
|
207 |
|
208 WrapperTypeInfo* V8DOMWrapper::domWrapperType(v8::Handle<v8::Object> object) |
|
209 { |
|
210 ASSERT(V8DOMWrapper::maybeDOMWrapper(object)); |
|
211 return static_cast<WrapperTypeInfo*>(object->GetPointerFromInternalField(v8DOMWrapperTypeIndex)); |
|
212 } |
|
213 |
|
214 PassRefPtr<NodeFilter> V8DOMWrapper::wrapNativeNodeFilter(v8::Handle<v8::Value> filter) |
|
215 { |
|
216 // A NodeFilter is used when walking through a DOM tree or iterating tree |
|
217 // nodes. |
|
218 // FIXME: we may want to cache NodeFilterCondition and NodeFilter |
|
219 // object, but it is minor. |
|
220 // NodeFilter is passed to NodeIterator that has a ref counted pointer |
|
221 // to NodeFilter. NodeFilter has a ref counted pointer to NodeFilterCondition. |
|
222 // In NodeFilterCondition, filter object is persisted in its constructor, |
|
223 // and disposed in its destructor. |
|
224 if (!filter->IsFunction()) |
|
225 return 0; |
|
226 |
|
227 return NodeFilter::create(V8NodeFilterCondition::create(filter)); |
|
228 } |
|
229 |
|
230 static bool globalObjectPrototypeIsDOMWindow(v8::Handle<v8::Object> objectPrototype) |
|
231 { |
|
232 // We can identify what type of context the global object is wrapping by looking at the |
|
233 // internal field count of its prototype. This assumes WorkerContexts and DOMWindows have different numbers |
|
234 // of internal fields, so a COMPILE_ASSERT is included to warn if this ever changes. |
|
235 #if ENABLE(WORKERS) |
|
236 COMPILE_ASSERT(V8DOMWindow::internalFieldCount != V8WorkerContext::internalFieldCount, |
|
237 DOMWindowAndWorkerContextHaveUnequalFieldCounts); |
|
238 COMPILE_ASSERT(V8DOMWindow::internalFieldCount != V8DedicatedWorkerContext::internalFieldCount, |
|
239 DOMWindowAndDedicatedWorkerContextHaveUnequalFieldCounts); |
|
240 #endif |
|
241 #if ENABLE(SHARED_WORKERS) |
|
242 COMPILE_ASSERT(V8DOMWindow::internalFieldCount != V8SharedWorkerContext::internalFieldCount, |
|
243 DOMWindowAndSharedWorkerContextHaveUnequalFieldCounts); |
|
244 #endif |
|
245 return objectPrototype->InternalFieldCount() == V8DOMWindow::internalFieldCount; |
|
246 } |
|
247 |
|
248 v8::Local<v8::Object> V8DOMWrapper::instantiateV8Object(V8Proxy* proxy, WrapperTypeInfo* type, void* impl) |
|
249 { |
|
250 WorkerContext* workerContext = 0; |
|
251 if (V8IsolatedContext::getEntered()) { |
|
252 // This effectively disables the wrapper cache for isolated worlds. |
|
253 proxy = 0; |
|
254 // FIXME: Do we need a wrapper cache for the isolated world? We should |
|
255 // see if the performance gains are worth while. |
|
256 // We'll get one once we give the isolated context a proper window shell. |
|
257 } else if (!proxy) { |
|
258 v8::Handle<v8::Context> context = v8::Context::GetCurrent(); |
|
259 if (!context.IsEmpty()) { |
|
260 v8::Handle<v8::Object> globalPrototype = v8::Handle<v8::Object>::Cast(context->Global()->GetPrototype()); |
|
261 if (globalObjectPrototypeIsDOMWindow(globalPrototype)) |
|
262 proxy = V8Proxy::retrieve(V8DOMWindow::toNative(globalPrototype)->frame()); |
|
263 #if ENABLE(WORKERS) |
|
264 else |
|
265 workerContext = V8WorkerContext::toNative(lookupDOMWrapper(V8WorkerContext::GetTemplate(), context->Global())); |
|
266 #endif |
|
267 } |
|
268 } |
|
269 |
|
270 v8::Local<v8::Object> instance; |
|
271 if (proxy) |
|
272 // FIXME: Fix this to work properly with isolated worlds (see above). |
|
273 instance = proxy->windowShell()->createWrapperFromCache(type); |
|
274 else { |
|
275 v8::Local<v8::Function> function; |
|
276 #if ENABLE(WORKERS) |
|
277 if (workerContext) |
|
278 function = getConstructor(type, workerContext); |
|
279 else |
|
280 #endif |
|
281 function = type->getTemplate()->GetFunction(); |
|
282 instance = SafeAllocation::newInstance(function); |
|
283 } |
|
284 if (!instance.IsEmpty()) { |
|
285 // Avoid setting the DOM wrapper for failed allocations. |
|
286 setDOMWrapper(instance, type, impl); |
|
287 } |
|
288 return instance; |
|
289 } |
|
290 |
|
291 #ifndef NDEBUG |
|
292 bool V8DOMWrapper::maybeDOMWrapper(v8::Handle<v8::Value> value) |
|
293 { |
|
294 if (value.IsEmpty() || !value->IsObject()) |
|
295 return false; |
|
296 |
|
297 v8::Handle<v8::Object> object = v8::Handle<v8::Object>::Cast(value); |
|
298 if (!object->InternalFieldCount()) |
|
299 return false; |
|
300 |
|
301 ASSERT(object->InternalFieldCount() >= v8DefaultWrapperInternalFieldCount); |
|
302 |
|
303 v8::Handle<v8::Value> wrapper = object->GetInternalField(v8DOMWrapperObjectIndex); |
|
304 ASSERT(wrapper->IsNumber() || wrapper->IsExternal()); |
|
305 |
|
306 return true; |
|
307 } |
|
308 #endif |
|
309 |
|
310 bool V8DOMWrapper::isValidDOMObject(v8::Handle<v8::Value> value) |
|
311 { |
|
312 if (value.IsEmpty() || !value->IsObject()) |
|
313 return false; |
|
314 return v8::Handle<v8::Object>::Cast(value)->InternalFieldCount(); |
|
315 } |
|
316 |
|
317 bool V8DOMWrapper::isWrapperOfType(v8::Handle<v8::Value> value, WrapperTypeInfo* type) |
|
318 { |
|
319 if (!isValidDOMObject(value)) |
|
320 return false; |
|
321 |
|
322 v8::Handle<v8::Object> object = v8::Handle<v8::Object>::Cast(value); |
|
323 ASSERT(object->InternalFieldCount() >= v8DefaultWrapperInternalFieldCount); |
|
324 |
|
325 v8::Handle<v8::Value> wrapper = object->GetInternalField(v8DOMWrapperObjectIndex); |
|
326 ASSERT(wrapper->IsNumber() || wrapper->IsExternal()); |
|
327 |
|
328 WrapperTypeInfo* typeInfo = static_cast<WrapperTypeInfo*>(object->GetPointerFromInternalField(v8DOMWrapperTypeIndex)); |
|
329 return typeInfo == type; |
|
330 } |
|
331 |
|
332 v8::Handle<v8::Object> V8DOMWrapper::getWrapper(Node* node) |
|
333 { |
|
334 ASSERT(WTF::isMainThread()); |
|
335 V8IsolatedContext* context = V8IsolatedContext::getEntered(); |
|
336 if (LIKELY(!context)) { |
|
337 v8::Persistent<v8::Object>* wrapper = node->wrapper(); |
|
338 if (!wrapper) |
|
339 return v8::Handle<v8::Object>(); |
|
340 return *wrapper; |
|
341 } |
|
342 |
|
343 DOMNodeMapping& domNodeMap = context->world()->domDataStore()->domNodeMap(); |
|
344 return domNodeMap.get(node); |
|
345 } |
|
346 |
|
347 // A JS object of type EventTarget is limited to a small number of possible classes. |
|
348 // Check EventTarget.h for new type conversion methods |
|
349 v8::Handle<v8::Value> V8DOMWrapper::convertEventTargetToV8Object(EventTarget* target) |
|
350 { |
|
351 if (!target) |
|
352 return v8::Null(); |
|
353 |
|
354 #if ENABLE(SVG) |
|
355 if (SVGElementInstance* instance = target->toSVGElementInstance()) |
|
356 return toV8(instance); |
|
357 #endif |
|
358 |
|
359 #if ENABLE(WORKERS) |
|
360 if (Worker* worker = target->toWorker()) |
|
361 return toV8(worker); |
|
362 |
|
363 if (DedicatedWorkerContext* workerContext = target->toDedicatedWorkerContext()) |
|
364 return toV8(workerContext); |
|
365 #endif // WORKERS |
|
366 |
|
367 #if ENABLE(SHARED_WORKERS) |
|
368 if (SharedWorker* sharedWorker = target->toSharedWorker()) |
|
369 return toV8(sharedWorker); |
|
370 |
|
371 if (SharedWorkerContext* sharedWorkerContext = target->toSharedWorkerContext()) |
|
372 return toV8(sharedWorkerContext); |
|
373 #endif // SHARED_WORKERS |
|
374 |
|
375 #if ENABLE(NOTIFICATIONS) |
|
376 if (Notification* notification = target->toNotification()) |
|
377 return toV8(notification); |
|
378 #endif |
|
379 |
|
380 #if ENABLE(INDEXED_DATABASE) |
|
381 if (IDBRequest* idbRequest = target->toIDBRequest()) |
|
382 return toV8(idbRequest); |
|
383 #endif |
|
384 |
|
385 #if ENABLE(WEB_SOCKETS) |
|
386 if (WebSocket* webSocket = target->toWebSocket()) |
|
387 return toV8(webSocket); |
|
388 #endif |
|
389 |
|
390 if (Node* node = target->toNode()) |
|
391 return toV8(node); |
|
392 |
|
393 if (DOMWindow* domWindow = target->toDOMWindow()) |
|
394 return toV8(domWindow); |
|
395 |
|
396 // XMLHttpRequest is created within its JS counterpart. |
|
397 if (XMLHttpRequest* xmlHttpRequest = target->toXMLHttpRequest()) { |
|
398 v8::Handle<v8::Object> wrapper = getActiveDOMObjectMap().get(xmlHttpRequest); |
|
399 ASSERT(!wrapper.IsEmpty()); |
|
400 return wrapper; |
|
401 } |
|
402 |
|
403 // MessagePort is created within its JS counterpart |
|
404 if (MessagePort* port = target->toMessagePort()) { |
|
405 v8::Handle<v8::Object> wrapper = getActiveDOMObjectMap().get(port); |
|
406 ASSERT(!wrapper.IsEmpty()); |
|
407 return wrapper; |
|
408 } |
|
409 |
|
410 if (XMLHttpRequestUpload* upload = target->toXMLHttpRequestUpload()) { |
|
411 v8::Handle<v8::Object> wrapper = getDOMObjectMap().get(upload); |
|
412 ASSERT(!wrapper.IsEmpty()); |
|
413 return wrapper; |
|
414 } |
|
415 |
|
416 #if ENABLE(OFFLINE_WEB_APPLICATIONS) |
|
417 if (DOMApplicationCache* domAppCache = target->toDOMApplicationCache()) |
|
418 return toV8(domAppCache); |
|
419 #endif |
|
420 |
|
421 #if ENABLE(EVENTSOURCE) |
|
422 if (EventSource* eventSource = target->toEventSource()) |
|
423 return toV8(eventSource); |
|
424 #endif |
|
425 |
|
426 #if ENABLE(FILE_READER) |
|
427 if (FileReader* fileReader = target->toFileReader()) |
|
428 return toV8(fileReader); |
|
429 #endif |
|
430 |
|
431 ASSERT(0); |
|
432 return notHandledByInterceptor(); |
|
433 } |
|
434 |
|
435 PassRefPtr<EventListener> V8DOMWrapper::getEventListener(v8::Local<v8::Value> value, bool isAttribute, ListenerLookupType lookup) |
|
436 { |
|
437 v8::Handle<v8::Context> context = v8::Context::GetCurrent(); |
|
438 if (context.IsEmpty()) |
|
439 return 0; |
|
440 if (lookup == ListenerFindOnly) |
|
441 return V8EventListenerList::findWrapper(value, isAttribute); |
|
442 v8::Handle<v8::Object> globalPrototype = v8::Handle<v8::Object>::Cast(context->Global()->GetPrototype()); |
|
443 if (globalObjectPrototypeIsDOMWindow(globalPrototype)) |
|
444 return V8EventListenerList::findOrCreateWrapper<V8EventListener>(value, isAttribute); |
|
445 #if ENABLE(WORKERS) |
|
446 return V8EventListenerList::findOrCreateWrapper<V8WorkerContextEventListener>(value, isAttribute); |
|
447 #else |
|
448 return 0; |
|
449 #endif |
|
450 } |
|
451 |
|
452 } // namespace WebCore |