1 /* 2 * Copyright (C) 1999-2001 Harri Porten (porten (at) kde.org) 3 * Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Apple Inc. All rights reserved. 4 * Copyright (C) 2007 Samuel Weinig <sam (at) webkit.org> 5 * Copyright (C) 2009 Google, Inc. All rights reserved. 6 * 7 * This library is free software; you can redistribute it and/or 8 * modify it under the terms of the GNU Lesser General Public 9 * License as published by the Free Software Foundation; either 10 * version 2 of the License, or (at your option) any later version. 11 * 12 * This library is distributed in the hope that it will be useful, 13 * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15 * Lesser General Public License for more details. 16 * 17 * You should have received a copy of the GNU Lesser General Public 18 * License along with this library; if not, write to the Free Software 19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 20 */ 21 22 #ifndef JSDOMBinding_h 23 #define JSDOMBinding_h 24 25 #include "JSDOMGlobalObject.h" 26 #include "JSSVGContextCache.h" 27 #include "Document.h" 28 #include <runtime/Completion.h> 29 #include <runtime/Lookup.h> 30 #include <runtime/WeakGCMap.h> 31 #include <wtf/Noncopyable.h> 32 33 namespace JSC { 34 class JSGlobalData; 35 class DebuggerCallFrame; 36 } 37 38 namespace WebCore { 39 40 class Document; 41 class Frame; 42 class JSNode; 43 class KURL; 44 class Node; 45 class String; 46 class ScriptController; 47 class ScriptCachedFrameData; 48 49 typedef int ExceptionCode; 50 51 #if ENABLE(SVG) 52 class SVGElement; 53 #endif 54 55 // Base class for all objects in this binding except Window. 56 class DOMObject : public JSC::JSObject { 57 protected: 58 explicit DOMObject(NonNullPassRefPtr<JSC::Structure> structure) 59 : JSObject(structure) 60 { 61 } 62 63 virtual bool defineOwnProperty(JSC::ExecState*, const JSC::Identifier&, JSC::PropertyDescriptor&, bool); 64 65 #ifndef NDEBUG 66 virtual ~DOMObject(); 67 #endif 68 }; 69 70 // FIXME: This class should collapse into DOMObject once all DOMObjects are 71 // updated to store a globalObject pointer. 72 class DOMObjectWithGlobalPointer : public DOMObject { 73 public: 74 JSDOMGlobalObject* globalObject() const { return static_cast<JSDOMGlobalObject*>(getAnonymousValue(GlobalObjectSlot).asCell()); } 75 76 ScriptExecutionContext* scriptExecutionContext() const 77 { 78 // FIXME: Should never be 0, but can be due to bug 27640. 79 return globalObject()->scriptExecutionContext(); 80 } 81 82 static PassRefPtr<JSC::Structure> createStructure(JSC::JSValue prototype) 83 { 84 return JSC::Structure::create(prototype, JSC::TypeInfo(JSC::ObjectType, StructureFlags), AnonymousSlotCount); 85 } 86 87 protected: 88 static const unsigned AnonymousSlotCount = 1 + DOMObject::AnonymousSlotCount; 89 static const unsigned GlobalObjectSlot = AnonymousSlotCount - 1; 90 91 DOMObjectWithGlobalPointer(NonNullPassRefPtr<JSC::Structure> structure, JSDOMGlobalObject* globalObject) 92 : DOMObject(structure) 93 { 94 // FIXME: This ASSERT is valid, but fires in fast/dom/gc-6.html when trying to create 95 // new JavaScript objects on detached windows due to DOMWindow::document() 96 // needing to reach through the frame to get to the Document*. See bug 27640. 97 // ASSERT(globalObject->scriptExecutionContext()); 98 putAnonymousValue(GlobalObjectSlot, globalObject); 99 } 100 virtual ~DOMObjectWithGlobalPointer() { } 101 }; 102 103 // Base class for all constructor objects in the JSC bindings. 104 class DOMConstructorObject : public DOMObjectWithGlobalPointer { 105 public: 106 static PassRefPtr<JSC::Structure> createStructure(JSC::JSValue prototype) 107 { 108 return JSC::Structure::create(prototype, JSC::TypeInfo(JSC::ObjectType, StructureFlags), AnonymousSlotCount); 109 } 110 111 protected: 112 static const unsigned StructureFlags = JSC::ImplementsHasInstance | JSC::OverridesMarkChildren | DOMObjectWithGlobalPointer::StructureFlags; 113 DOMConstructorObject(NonNullPassRefPtr<JSC::Structure> structure, JSDOMGlobalObject* globalObject) 114 : DOMObjectWithGlobalPointer(structure, globalObject) 115 { 116 } 117 }; 118 119 // Constructors using this base class depend on being in a Document and 120 // can never be used from a WorkerContext. 121 class DOMConstructorWithDocument : public DOMConstructorObject { 122 public: 123 Document* document() const 124 { 125 return static_cast<Document*>(scriptExecutionContext()); 126 } 127 128 protected: 129 DOMConstructorWithDocument(NonNullPassRefPtr<JSC::Structure> structure, JSDOMGlobalObject* globalObject) 130 : DOMConstructorObject(structure, globalObject) 131 { 132 ASSERT(globalObject->scriptExecutionContext()->isDocument()); 133 } 134 }; 135 136 typedef JSC::WeakGCMap<void*, DOMObject*> DOMObjectWrapperMap; 137 typedef JSC::WeakGCMap<StringImpl*, JSC::JSString*> JSStringCache; 138 139 class DOMWrapperWorld : public RefCounted<DOMWrapperWorld> { 140 public: 141 static PassRefPtr<DOMWrapperWorld> create(JSC::JSGlobalData* globalData, bool isNormal) 142 { 143 return adoptRef(new DOMWrapperWorld(globalData, isNormal)); 144 } 145 ~DOMWrapperWorld(); 146 147 void detachFromGlobalData() { m_globalData = 0; } 148 void rememberDocument(Document* document) { documentsWithWrappers.add(document); } 149 void forgetDocument(Document* document) { documentsWithWrappers.remove(document); } 150 151 // FIXME: can we make this private? 152 DOMObjectWrapperMap m_wrappers; 153 JSStringCache m_stringCache; 154 155 bool isNormal() const { return m_isNormal; } 156 157 protected: 158 DOMWrapperWorld(JSC::JSGlobalData*, bool isNormal); 159 160 private: 161 JSC::JSGlobalData* m_globalData; 162 HashSet<Document*> documentsWithWrappers; 163 bool m_isNormal; 164 }; 165 166 // Map from static HashTable instances to per-GlobalData ones. 167 class DOMObjectHashTableMap { 168 public: 169 static DOMObjectHashTableMap& mapFor(JSC::JSGlobalData&); 170 171 ~DOMObjectHashTableMap() 172 { 173 HashMap<const JSC::HashTable*, JSC::HashTable>::iterator mapEnd = m_map.end(); 174 for (HashMap<const JSC::HashTable*, JSC::HashTable>::iterator iter = m_map.begin(); iter != m_map.end(); ++iter) 175 iter->second.deleteTable(); 176 } 177 178 const JSC::HashTable* get(const JSC::HashTable* staticTable) 179 { 180 HashMap<const JSC::HashTable*, JSC::HashTable>::iterator iter = m_map.find(staticTable); 181 if (iter != m_map.end()) 182 return &iter->second; 183 return &m_map.set(staticTable, JSC::HashTable(*staticTable)).first->second; 184 } 185 186 private: 187 HashMap<const JSC::HashTable*, JSC::HashTable> m_map; 188 }; 189 190 class WebCoreJSClientData : public JSC::JSGlobalData::ClientData, public Noncopyable { 191 friend class JSGlobalDataWorldIterator; 192 193 public: 194 WebCoreJSClientData(JSC::JSGlobalData* globalData) 195 : m_normalWorld(DOMWrapperWorld::create(globalData, true)) 196 { 197 m_worldSet.add(m_normalWorld.get()); 198 } 199 200 virtual ~WebCoreJSClientData() 201 { 202 ASSERT(m_worldSet.contains(m_normalWorld.get())); 203 ASSERT(m_worldSet.size() == 1); 204 m_normalWorld->detachFromGlobalData(); 205 } 206 207 DOMWrapperWorld* normalWorld() { return m_normalWorld.get(); } 208 209 void getAllWorlds(Vector<DOMWrapperWorld*>& worlds) 210 { 211 copyToVector(m_worldSet, worlds); 212 } 213 214 void rememberWorld(DOMWrapperWorld* world) 215 { 216 ASSERT(!m_worldSet.contains(world)); 217 m_worldSet.add(world); 218 } 219 void forgetWorld(DOMWrapperWorld* world) 220 { 221 ASSERT(m_worldSet.contains(world)); 222 m_worldSet.remove(world); 223 } 224 225 DOMObjectHashTableMap hashTableMap; 226 private: 227 HashSet<DOMWrapperWorld*> m_worldSet; 228 RefPtr<DOMWrapperWorld> m_normalWorld; 229 }; 230 231 DOMObject* getCachedDOMObjectWrapper(JSC::ExecState*, void* objectHandle); 232 bool hasCachedDOMObjectWrapper(JSC::JSGlobalData*, void* objectHandle); 233 void cacheDOMObjectWrapper(JSC::ExecState*, void* objectHandle, DOMObject* wrapper); 234 void forgetDOMNode(JSNode* wrapper, Node* node, Document* document); 235 void forgetDOMObject(DOMObject* wrapper, void* objectHandle); 236 237 JSNode* getCachedDOMNodeWrapper(JSC::ExecState*, Document*, Node*); 238 void cacheDOMNodeWrapper(JSC::ExecState*, Document*, Node*, JSNode* wrapper); 239 void forgetAllDOMNodesForDocument(Document*); 240 void forgetWorldOfDOMNodesForDocument(Document*, DOMWrapperWorld*); 241 void updateDOMNodeDocument(Node*, Document* oldDocument, Document* newDocument); 242 243 void markDOMNodesForDocument(JSC::MarkStack&, Document*); 244 void markActiveObjectsForContext(JSC::MarkStack&, JSC::JSGlobalData&, ScriptExecutionContext*); 245 void markDOMObjectWrapper(JSC::MarkStack&, JSC::JSGlobalData& globalData, void* object); 246 void markDOMNodeWrapper(JSC::MarkStack& markStack, Document* document, Node* node); 247 bool hasCachedDOMObjectWrapperUnchecked(JSC::JSGlobalData*, void* objectHandle); 248 bool hasCachedDOMNodeWrapperUnchecked(Document*, Node*); 249 250 JSC::Structure* getCachedDOMStructure(JSDOMGlobalObject*, const JSC::ClassInfo*); 251 JSC::Structure* cacheDOMStructure(JSDOMGlobalObject*, NonNullPassRefPtr<JSC::Structure>, const JSC::ClassInfo*); 252 JSC::Structure* getCachedDOMStructure(JSC::ExecState*, const JSC::ClassInfo*); 253 JSC::Structure* cacheDOMStructure(JSC::ExecState*, NonNullPassRefPtr<JSC::Structure>, const JSC::ClassInfo*); 254 255 DOMWrapperWorld* currentWorld(JSC::ExecState*); 256 DOMWrapperWorld* normalWorld(JSC::JSGlobalData&); 257 DOMWrapperWorld* mainThreadNormalWorld(); 258 inline DOMWrapperWorld* debuggerWorld() { return mainThreadNormalWorld(); } 259 inline DOMWrapperWorld* pluginWorld() { return mainThreadNormalWorld(); } 260 261 JSC::JSObject* getCachedDOMConstructor(JSC::ExecState*, const JSC::ClassInfo*); 262 void cacheDOMConstructor(JSC::ExecState*, const JSC::ClassInfo*, JSC::JSObject* constructor); 263 264 inline JSDOMGlobalObject* deprecatedGlobalObjectForPrototype(JSC::ExecState* exec) 265 { 266 // FIXME: Callers to this function should be using the global object 267 // from which the object is being created, instead of assuming the lexical one. 268 // e.g. subframe.document.body should use the subframe's global object, not the lexical one. 269 return static_cast<JSDOMGlobalObject*>(exec->lexicalGlobalObject()); 270 } 271 272 template<class WrapperClass> inline JSC::Structure* getDOMStructure(JSC::ExecState* exec, JSDOMGlobalObject* globalObject) 273 { 274 if (JSC::Structure* structure = getCachedDOMStructure(globalObject, &WrapperClass::s_info)) 275 return structure; 276 return cacheDOMStructure(globalObject, WrapperClass::createStructure(WrapperClass::createPrototype(exec, globalObject)), &WrapperClass::s_info); 277 } 278 template<class WrapperClass> inline JSC::Structure* deprecatedGetDOMStructure(JSC::ExecState* exec) 279 { 280 // FIXME: This function is wrong. It uses the wrong global object for creating the prototype structure. 281 return getDOMStructure<WrapperClass>(exec, deprecatedGlobalObjectForPrototype(exec)); 282 } 283 template<class WrapperClass> inline JSC::JSObject* getDOMPrototype(JSC::ExecState* exec, JSC::JSGlobalObject* globalObject) 284 { 285 return static_cast<JSC::JSObject*>(asObject(getDOMStructure<WrapperClass>(exec, static_cast<JSDOMGlobalObject*>(globalObject))->storedPrototype())); 286 } 287 #define CREATE_DOM_OBJECT_WRAPPER(exec, globalObject, className, object) createDOMObjectWrapper<JS##className>(exec, globalObject, static_cast<className*>(object)) 288 template<class WrapperClass, class DOMClass> inline DOMObject* createDOMObjectWrapper(JSC::ExecState* exec, JSDOMGlobalObject* globalObject, DOMClass* object) 289 { 290 ASSERT(object); 291 ASSERT(!getCachedDOMObjectWrapper(exec, object)); 292 // FIXME: new (exec) could use a different globalData than the globalData this wrapper is cached on. 293 WrapperClass* wrapper = new (exec) WrapperClass(getDOMStructure<WrapperClass>(exec, globalObject), globalObject, object); 294 cacheDOMObjectWrapper(exec, object, wrapper); 295 return wrapper; 296 } 297 template<class WrapperClass, class DOMClass> inline JSC::JSValue getDOMObjectWrapper(JSC::ExecState* exec, JSDOMGlobalObject* globalObject, DOMClass* object) 298 { 299 if (!object) 300 return JSC::jsNull(); 301 if (DOMObject* wrapper = getCachedDOMObjectWrapper(exec, object)) 302 return wrapper; 303 return createDOMObjectWrapper<WrapperClass>(exec, globalObject, object); 304 } 305 306 #if ENABLE(SVG) 307 #define CREATE_SVG_OBJECT_WRAPPER(exec, globalObject, className, object, context) createDOMObjectWrapper<JS##className>(exec, globalObject, static_cast<className*>(object), context) 308 template<class WrapperClass, class DOMClass> inline DOMObject* createDOMObjectWrapper(JSC::ExecState* exec, JSDOMGlobalObject* globalObject, DOMClass* object, SVGElement* context) 309 { 310 DOMObject* wrapper = createDOMObjectWrapper<WrapperClass, DOMClass>(exec, globalObject, object); 311 ASSERT(wrapper); 312 if (context) 313 JSSVGContextCache::addWrapper(wrapper, context); 314 return wrapper; 315 } 316 template<class WrapperClass, class DOMClass> inline JSC::JSValue getDOMObjectWrapper(JSC::ExecState* exec, JSDOMGlobalObject* globalObject, DOMClass* object, SVGElement* context) 317 { 318 if (!object) 319 return JSC::jsNull(); 320 if (DOMObject* wrapper = getCachedDOMObjectWrapper(exec, object)) { 321 ASSERT(JSSVGContextCache::svgContextForDOMObject(wrapper) == context); 322 return wrapper; 323 } 324 return createDOMObjectWrapper<WrapperClass, DOMClass>(exec, globalObject, object, context); 325 } 326 #endif 327 328 #define CREATE_DOM_NODE_WRAPPER(exec, globalObject, className, object) createDOMNodeWrapper<JS##className>(exec, globalObject, static_cast<className*>(object)) 329 template<class WrapperClass, class DOMClass> inline JSNode* createDOMNodeWrapper(JSC::ExecState* exec, JSDOMGlobalObject* globalObject, DOMClass* node) 330 { 331 ASSERT(node); 332 ASSERT(!getCachedDOMNodeWrapper(exec, node->document(), node)); 333 WrapperClass* wrapper = new (exec) WrapperClass(getDOMStructure<WrapperClass>(exec, globalObject), globalObject, node); 334 // FIXME: The entire function can be removed, once we fix caching. 335 // This function is a one-off hack to make Nodes cache in the right global object. 336 cacheDOMNodeWrapper(exec, node->document(), node, wrapper); 337 return wrapper; 338 } 339 template<class WrapperClass, class DOMClass> inline JSC::JSValue getDOMNodeWrapper(JSC::ExecState* exec, JSDOMGlobalObject* globalObject, DOMClass* node) 340 { 341 if (!node) 342 return JSC::jsNull(); 343 if (JSNode* wrapper = getCachedDOMNodeWrapper(exec, node->document(), node)) 344 return wrapper; 345 return createDOMNodeWrapper<WrapperClass>(exec, globalObject, node); 346 } 347 348 const JSC::HashTable* getHashTableForGlobalData(JSC::JSGlobalData&, const JSC::HashTable* staticTable); 349 350 void reportException(JSC::ExecState*, JSC::JSValue exception); 351 void reportCurrentException(JSC::ExecState*); 352 353 // Convert a DOM implementation exception code into a JavaScript exception in the execution state. 354 void setDOMException(JSC::ExecState*, ExceptionCode); 355 356 JSC::JSValue jsString(JSC::ExecState*, const String&); // empty if the string is null 357 JSC::JSValue jsStringSlowCase(JSC::ExecState*, JSStringCache&, StringImpl*); 358 JSC::JSValue jsString(JSC::ExecState*, const KURL&); // empty if the URL is null 359 inline JSC::JSValue jsString(JSC::ExecState* exec, const AtomicString& s) 360 { 361 return jsString(exec, s.string()); 362 } 363 364 JSC::JSValue jsStringOrNull(JSC::ExecState*, const String&); // null if the string is null 365 JSC::JSValue jsStringOrNull(JSC::ExecState*, const KURL&); // null if the URL is null 366 367 JSC::JSValue jsStringOrUndefined(JSC::ExecState*, const String&); // undefined if the string is null 368 JSC::JSValue jsStringOrUndefined(JSC::ExecState*, const KURL&); // undefined if the URL is null 369 370 JSC::JSValue jsStringOrFalse(JSC::ExecState*, const String&); // boolean false if the string is null 371 JSC::JSValue jsStringOrFalse(JSC::ExecState*, const KURL&); // boolean false if the URL is null 372 373 // See JavaScriptCore for explanation: Should be used for any UString that is already owned by another 374 // object, to let the engine know that collecting the JSString wrapper is unlikely to save memory. 375 JSC::JSValue jsOwnedStringOrNull(JSC::ExecState*, const JSC::UString&); 376 377 JSC::UString valueToStringWithNullCheck(JSC::ExecState*, JSC::JSValue); // null if the value is null 378 JSC::UString valueToStringWithUndefinedOrNullCheck(JSC::ExecState*, JSC::JSValue); // null if the value is null or undefined 379 380 // Returns a Date instance for the specified value, or null if the value is NaN or infinity. 381 JSC::JSValue jsDateOrNull(JSC::ExecState*, double); 382 // NaN if the value can't be converted to a date. 383 double valueToDate(JSC::ExecState*, JSC::JSValue); 384 385 // FIXME: These are a stop-gap until all toJS calls can be converted to pass a globalObject 386 template <typename T> 387 inline JSC::JSValue toJS(JSC::ExecState* exec, T* ptr) 388 { 389 return toJS(exec, deprecatedGlobalObjectForPrototype(exec), ptr); 390 } 391 template <typename T> 392 inline JSC::JSValue toJS(JSC::ExecState* exec, PassRefPtr<T> ptr) 393 { 394 return toJS(exec, deprecatedGlobalObjectForPrototype(exec), ptr.get()); 395 } 396 template <typename T> 397 inline JSC::JSValue toJSNewlyCreated(JSC::ExecState* exec, T* ptr) 398 { 399 return toJSNewlyCreated(exec, deprecatedGlobalObjectForPrototype(exec), ptr); 400 } 401 402 template <typename T> 403 inline JSC::JSValue toJS(JSC::ExecState* exec, JSDOMGlobalObject* globalObject, PassRefPtr<T> ptr) 404 { 405 return toJS(exec, globalObject, ptr.get()); 406 } 407 408 // Validates that the passed object is a sequence type per section 4.1.13 of the WebIDL spec. 409 JSC::JSObject* toJSSequence(JSC::ExecState*, JSC::JSValue, unsigned&); 410 411 bool checkNodeSecurity(JSC::ExecState*, Node*); 412 413 // Helpers for Window, History, and Location classes to implement cross-domain policy. 414 // Besides the cross-domain check, they need non-caching versions of staticFunctionGetter for 415 // because we do not want current property values involved at all. 416 bool allowsAccessFromFrame(JSC::ExecState*, Frame*); 417 bool allowsAccessFromFrame(JSC::ExecState*, Frame*, String& message); 418 bool shouldAllowNavigation(JSC::ExecState*, Frame*); 419 void printErrorMessageForFrame(Frame*, const String& message); 420 JSC::JSValue objectToStringFunctionGetter(JSC::ExecState*, const JSC::Identifier& propertyName, const JSC::PropertySlot&); 421 422 Frame* toLexicalFrame(JSC::ExecState*); 423 Frame* toDynamicFrame(JSC::ExecState*); 424 bool processingUserGesture(JSC::ExecState*); 425 KURL completeURL(JSC::ExecState*, const String& relativeURL); 426 427 inline DOMWrapperWorld* currentWorld(JSC::ExecState* exec) 428 { 429 return static_cast<JSDOMGlobalObject*>(exec->lexicalGlobalObject())->world(); 430 } 431 432 inline JSC::JSValue jsString(JSC::ExecState* exec, const String& s) 433 { 434 StringImpl* stringImpl = s.impl(); 435 if (!stringImpl || !stringImpl->length()) 436 return jsEmptyString(exec); 437 438 if (stringImpl->length() == 1 && stringImpl->characters()[0] <= 0xFF) 439 return jsString(exec, stringImpl->ustring()); 440 441 JSStringCache& stringCache = currentWorld(exec)->m_stringCache; 442 if (JSC::JSString* wrapper = stringCache.get(stringImpl)) 443 return wrapper; 444 445 return jsStringSlowCase(exec, stringCache, stringImpl); 446 } 447 448 } // namespace WebCore 449 450 #endif // JSDOMBinding_h 451