Home | History | Annotate | Download | only in js
      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