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