/* * Copyright (C) 2009 Google Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the following disclaimer * in the documentation and/or other materials provided with the * distribution. * * Neither the name of Google Inc. nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "config.h" #include "V8DOMMap.h" #include "DOMObjectsInclude.h" #include #include #include #include #include #include #include namespace WebCore { // DOM binding algorithm: // // There are two kinds of DOM objects: // 1. DOM tree nodes, such as Document, HTMLElement, ... // there classes implements TreeShared interface; // 2. Non-node DOM objects, such as CSSRule, Location, etc. // these classes implement a ref-counted scheme. // // A DOM object may have a JS wrapper object. If a tree node // is alive, its JS wrapper must be kept alive even it is not // reachable from JS roots. // However, JS wrappers of non-node objects can go away if // not reachable from other JS objects. It works like a cache. // // DOM objects are ref-counted, and JS objects are traced from // a set of root objects. They can create a cycle. To break // cycles, we do following: // Handles from DOM objects to JS wrappers are always weak, // so JS wrappers of non-node object cannot create a cycle. // Before starting a global GC, we create a virtual connection // between nodes in the same tree in the JS heap. If the wrapper // of one node in a tree is alive, wrappers of all nodes in // the same tree are considered alive. This is done by creating // object groups in GC prologue callbacks. The mark-compact // collector will remove these groups after each GC. // // DOM objects should be deref-ed from the owning thread, not the GC thread // that does not own them. In V8, GC can kick in from any thread. To ensure // that DOM objects are always deref-ed from the owning thread when running // V8 in multi-threading environment, we do following: // 1. Maintain a thread specific DOM wrapper map for each object map. // (We're using TLS support from WTF instead of base since V8Bindings // does not depend on base. We further assume that all child threads // running V8 instances are created by WTF and thus a destructor will // be called to clean up all thread specific data.) // 2. When GC happens: // 2.1. If the dead object is in GC thread's map, remove the JS reference // and deref the DOM object. // 2.2. Otherwise, go through all thread maps to find the owning thread. // Remove the JS reference from the owning thread's map and move the // DOM object to a delayed queue. Post a task to the owning thread // to have it deref-ed from the owning thread at later time. // 3. When a thread is tearing down, invoke a cleanup routine to go through // all objects in the delayed queue and the thread map and deref all of // them. static void weakDOMObjectCallback(v8::Persistent v8Object, void* domObject); static void weakNodeCallback(v8::Persistent v8Object, void* domObject); #if ENABLE(SVG) static void weakSVGElementInstanceCallback(v8::Persistent v8Object, void* domObject); // SVG non-node elements may have a reference to a context node which should be notified when the element is change. static void weakSVGObjectWithContextCallback(v8::Persistent v8Object, void* domObject); #endif // This is to ensure that we will deref DOM objects from the owning thread, not the GC thread. // The helper function will be scheduled by the GC thread to get called from the owning thread. static void derefDelayedObjectsInCurrentThread(void*); // This should be called to remove all DOM objects associated with the current thread when it is tearing down. static void removeAllDOMObjectsInCurrentThread(); // A map from a thread ID to thread's specific data. class ThreadSpecificDOMData; typedef WTF::HashMap DOMThreadMap; static DOMThreadMap& domThreadMap() { DEFINE_STATIC_LOCAL(DOMThreadMap, staticDOMThreadMap, ()); return staticDOMThreadMap; } // Mutex to protect against concurrent access of domThreadMap. static WTF::Mutex& domThreadMapMutex() { DEFINE_STATIC_LOCAL(WTF::Mutex, staticDOMThreadMapMutex, ()); return staticDOMThreadMapMutex; } class ThreadSpecificDOMData : Noncopyable { public: enum DOMWrapperMapType { DOMNodeMap, DOMObjectMap, ActiveDOMObjectMap, #if ENABLE(SVG) DOMSVGElementInstanceMap, DOMSVGObjectWithContextMap #endif }; typedef WTF::HashMap DelayedObjectMap; template class InternalDOMWrapperMap : public DOMWrapperMap { public: InternalDOMWrapperMap(v8::WeakReferenceCallback callback) : DOMWrapperMap(callback) { } virtual void forget(KeyType*); void forgetOnly(KeyType* object) { DOMWrapperMap::forget(object); } }; ThreadSpecificDOMData() : m_domNodeMap(0) , m_domObjectMap(0) , m_activeDomObjectMap(0) #if ENABLE(SVG) , m_domSvgElementInstanceMap(0) , m_domSvgObjectWithContextMap(0) #endif , m_delayedProcessingScheduled(false) , m_isMainThread(WTF::isMainThread()) { WTF::MutexLocker locker(domThreadMapMutex()); domThreadMap().set(WTF::currentThread(), this); } virtual ~ThreadSpecificDOMData() { WTF::MutexLocker locker(domThreadMapMutex()); domThreadMap().remove(WTF::currentThread()); } void* getDOMWrapperMap(DOMWrapperMapType type) { switch (type) { case DOMNodeMap: return m_domNodeMap; case DOMObjectMap: return m_domObjectMap; case ActiveDOMObjectMap: return m_activeDomObjectMap; #if ENABLE(SVG) case DOMSVGElementInstanceMap: return m_domSvgElementInstanceMap; case DOMSVGObjectWithContextMap: return m_domSvgObjectWithContextMap; #endif } ASSERT_NOT_REACHED(); return 0; } InternalDOMWrapperMap& domNodeMap() { return *m_domNodeMap; } InternalDOMWrapperMap& domObjectMap() { return *m_domObjectMap; } InternalDOMWrapperMap& activeDomObjectMap() { return *m_activeDomObjectMap; } #if ENABLE(SVG) InternalDOMWrapperMap& domSvgElementInstanceMap() { return *m_domSvgElementInstanceMap; } InternalDOMWrapperMap& domSvgObjectWithContextMap() { return *m_domSvgObjectWithContextMap; } #endif DelayedObjectMap& delayedObjectMap() { return m_delayedObjectMap; } bool delayedProcessingScheduled() const { return m_delayedProcessingScheduled; } void setDelayedProcessingScheduled(bool value) { m_delayedProcessingScheduled = value; } bool isMainThread() const { return m_isMainThread; } protected: InternalDOMWrapperMap* m_domNodeMap; InternalDOMWrapperMap* m_domObjectMap; InternalDOMWrapperMap* m_activeDomObjectMap; #if ENABLE(SVG) InternalDOMWrapperMap* m_domSvgElementInstanceMap; InternalDOMWrapperMap* m_domSvgObjectWithContextMap; #endif // Stores all the DOM objects that are delayed to be processed when the owning thread gains control. DelayedObjectMap m_delayedObjectMap; // The flag to indicate if the task to do the delayed process has already been posted. bool m_delayedProcessingScheduled; bool m_isMainThread; }; // This encapsulates thread-specific DOM data for non-main thread. All the maps in it are created dynamically. class NonMainThreadSpecificDOMData : public ThreadSpecificDOMData { public: NonMainThreadSpecificDOMData() { m_domNodeMap = new InternalDOMWrapperMap(&weakNodeCallback); m_domObjectMap = new InternalDOMWrapperMap(weakDOMObjectCallback); m_activeDomObjectMap = new InternalDOMWrapperMap(weakActiveDOMObjectCallback); #if ENABLE(SVG) m_domSvgElementInstanceMap = new InternalDOMWrapperMap(weakSVGElementInstanceCallback); m_domSvgObjectWithContextMap = new InternalDOMWrapperMap(weakSVGObjectWithContextCallback); #endif } // This is called when WTF thread is tearing down. // We assume that all child threads running V8 instances are created by WTF. virtual ~NonMainThreadSpecificDOMData() { removeAllDOMObjectsInCurrentThread(); delete m_domNodeMap; delete m_domObjectMap; delete m_activeDomObjectMap; #if ENABLE(SVG) delete m_domSvgElementInstanceMap; delete m_domSvgObjectWithContextMap; #endif } }; // This encapsulates thread-specific DOM data for the main thread. All the maps in it are static. // This is because we are unable to rely on WTF::ThreadSpecificThreadExit to do the cleanup since the place that tears down the main thread can not call any WTF functions. class MainThreadSpecificDOMData : public ThreadSpecificDOMData { public: MainThreadSpecificDOMData() : m_staticDomNodeMap(weakNodeCallback) , m_staticDomObjectMap(weakDOMObjectCallback) , m_staticActiveDomObjectMap(weakActiveDOMObjectCallback) #if ENABLE(SVG) , m_staticDomSvgElementInstanceMap(weakSVGElementInstanceCallback) , m_staticDomSvgObjectWithContextMap(weakSVGObjectWithContextCallback) #endif { m_domNodeMap = &m_staticDomNodeMap; m_domObjectMap = &m_staticDomObjectMap; m_activeDomObjectMap = &m_staticActiveDomObjectMap; #if ENABLE(SVG) m_domSvgElementInstanceMap = &m_staticDomSvgElementInstanceMap; m_domSvgObjectWithContextMap = &m_staticDomSvgObjectWithContextMap; #endif } private: InternalDOMWrapperMap m_staticDomNodeMap; InternalDOMWrapperMap m_staticDomObjectMap; InternalDOMWrapperMap m_staticActiveDomObjectMap; #if ENABLE(SVG) InternalDOMWrapperMap m_staticDomSvgElementInstanceMap; InternalDOMWrapperMap m_staticDomSvgObjectWithContextMap; #endif }; DEFINE_STATIC_LOCAL(WTF::ThreadSpecific, threadSpecificDOMData, ()); template static void handleWeakObjectInOwningThread(ThreadSpecificDOMData::DOMWrapperMapType mapType, V8ClassIndex::V8WrapperType objectType, T*); ThreadSpecificDOMData& getThreadSpecificDOMData() { if (WTF::isMainThread()) { DEFINE_STATIC_LOCAL(MainThreadSpecificDOMData, mainThreadSpecificDOMData, ()); return mainThreadSpecificDOMData; } return *threadSpecificDOMData; } template void ThreadSpecificDOMData::InternalDOMWrapperMap::forget(KeyType* object) { DOMWrapperMap::forget(object); ThreadSpecificDOMData::DelayedObjectMap& delayedObjectMap = getThreadSpecificDOMData().delayedObjectMap(); delayedObjectMap.take(object); } DOMWrapperMap& getDOMNodeMap() { return getThreadSpecificDOMData().domNodeMap(); } DOMWrapperMap& getDOMObjectMap() { return getThreadSpecificDOMData().domObjectMap(); } DOMWrapperMap& getActiveDOMObjectMap() { return getThreadSpecificDOMData().activeDomObjectMap(); } #if ENABLE(SVG) DOMWrapperMap& getDOMSVGElementInstanceMap() { return getThreadSpecificDOMData().domSvgElementInstanceMap(); } static void weakSVGElementInstanceCallback(v8::Persistent v8Object, void* domObject) { SVGElementInstance* instance = static_cast(domObject); ThreadSpecificDOMData::InternalDOMWrapperMap& map = static_cast&>(getDOMSVGElementInstanceMap()); if (map.contains(instance)) { instance->deref(); map.forgetOnly(instance); } else handleWeakObjectInOwningThread(ThreadSpecificDOMData::DOMSVGElementInstanceMap, V8ClassIndex::SVGELEMENTINSTANCE, instance); } // Map of SVG objects with contexts to V8 objects DOMWrapperMap& getDOMSVGObjectWithContextMap() { return getThreadSpecificDOMData().domSvgObjectWithContextMap(); } static void weakSVGObjectWithContextCallback(v8::Persistent v8Object, void* domObject) { v8::HandleScope scope; ASSERT(v8Object->IsObject()); V8ClassIndex::V8WrapperType type = V8Proxy::GetDOMWrapperType(v8::Handle::Cast(v8Object)); ThreadSpecificDOMData::InternalDOMWrapperMap& map = static_cast&>(getDOMSVGObjectWithContextMap()); if (map.contains(domObject)) { // The forget function removes object from the map and disposes the wrapper. map.forgetOnly(domObject); switch (type) { #define MakeCase(type, name) \ case V8ClassIndex::type: static_cast(domObject)->deref(); break; SVG_OBJECT_TYPES(MakeCase) #undef MakeCase #define MakeCase(type, name) \ case V8ClassIndex::type: \ static_cast*>(domObject)->deref(); break; SVG_POD_NATIVE_TYPES(MakeCase) #undef MakeCase default: ASSERT_NOT_REACHED(); break; } } else handleWeakObjectInOwningThread(ThreadSpecificDOMData::DOMSVGObjectWithContextMap, type, domObject); } #endif // ENABLE(SVG) // Called when the dead object is not in GC thread's map. Go through all thread maps to find the one containing it. // Then clear the JS reference and push the DOM object into the delayed queue for it to be deref-ed at later time from the owning thread. // * This is called when the GC thread is not the owning thread. // * This can be called on any thread that has GC running. // * Only one V8 instance is running at a time due to V8::Locker. So we don't need to worry about concurrency. template static void handleWeakObjectInOwningThread(ThreadSpecificDOMData::DOMWrapperMapType mapType, V8ClassIndex::V8WrapperType objectType, T* object) { WTF::MutexLocker locker(domThreadMapMutex()); for (typename DOMThreadMap::iterator iter(domThreadMap().begin()); iter != domThreadMap().end(); ++iter) { WTF::ThreadIdentifier threadID = iter->first; ThreadSpecificDOMData* threadData = iter->second; // Skip the current thread that is GC thread. if (threadID == WTF::currentThread()) { ASSERT(!static_cast*>(threadData->getDOMWrapperMap(mapType))->contains(object)); continue; } ThreadSpecificDOMData::InternalDOMWrapperMap* domMap = static_cast*>(threadData->getDOMWrapperMap(mapType)); if (domMap->contains(object)) { // Clear the JS reference. domMap->forgetOnly(object); // Push into the delayed queue. threadData->delayedObjectMap().set(object, objectType); // Post a task to the owning thread in order to process the delayed queue. // FIXME: For now, we can only post to main thread due to WTF task posting limitation. We will fix this when we work on nested worker. if (!threadData->delayedProcessingScheduled()) { threadData->setDelayedProcessingScheduled(true); if (threadData->isMainThread()) WTF::callOnMainThread(&derefDelayedObjectsInCurrentThread, 0); } break; } } } // Called when the object is near death (not reachable from JS roots). // It is time to remove the entry from the table and dispose the handle. static void weakDOMObjectCallback(v8::Persistent v8Object, void* domObject) { v8::HandleScope scope; ASSERT(v8Object->IsObject()); V8ClassIndex::V8WrapperType type = V8Proxy::GetDOMWrapperType(v8::Handle::Cast(v8Object)); ThreadSpecificDOMData::InternalDOMWrapperMap& map = static_cast&>(getDOMObjectMap()); if (map.contains(domObject)) { // The forget function removes object from the map and disposes the wrapper. map.forgetOnly(domObject); switch (type) { #define MakeCase(type, name) \ case V8ClassIndex::type: static_cast(domObject)->deref(); break; DOM_OBJECT_TYPES(MakeCase) #undef MakeCase default: ASSERT_NOT_REACHED(); break; } } else handleWeakObjectInOwningThread(ThreadSpecificDOMData::DOMObjectMap, type, domObject); } void weakActiveDOMObjectCallback(v8::Persistent v8Object, void* domObject) { v8::HandleScope scope; ASSERT(v8Object->IsObject()); V8ClassIndex::V8WrapperType type = V8Proxy::GetDOMWrapperType(v8::Handle::Cast(v8Object)); ThreadSpecificDOMData::InternalDOMWrapperMap& map = static_cast&>(getActiveDOMObjectMap()); if (map.contains(domObject)) { // The forget function removes object from the map and disposes the wrapper. map.forgetOnly(domObject); switch (type) { #define MakeCase(type, name) \ case V8ClassIndex::type: static_cast(domObject)->deref(); break; ACTIVE_DOM_OBJECT_TYPES(MakeCase) #undef MakeCase default: ASSERT_NOT_REACHED(); break; } } else handleWeakObjectInOwningThread(ThreadSpecificDOMData::ActiveDOMObjectMap, type, domObject); } static void weakNodeCallback(v8::Persistent v8Object, void* domObject) { Node* node = static_cast(domObject); ThreadSpecificDOMData::InternalDOMWrapperMap& map = static_cast&>(getDOMNodeMap()); if (map.contains(node)) { map.forgetOnly(node); node->deref(); } else handleWeakObjectInOwningThread(ThreadSpecificDOMData::DOMNodeMap, V8ClassIndex::NODE, node); } static void derefObject(V8ClassIndex::V8WrapperType type, void* domObject) { switch (type) { case V8ClassIndex::NODE: static_cast(domObject)->deref(); break; #define MakeCase(type, name) \ case V8ClassIndex::type: static_cast(domObject)->deref(); break; DOM_OBJECT_TYPES(MakeCase) // This includes both active and non-active. #undef MakeCase #if ENABLE(SVG) #define MakeCase(type, name) \ case V8ClassIndex::type: static_cast(domObject)->deref(); break; SVG_OBJECT_TYPES(MakeCase) // This also includes SVGElementInstance. #undef MakeCase #define MakeCase(type, name) \ case V8ClassIndex::type: \ static_cast*>(domObject)->deref(); break; SVG_POD_NATIVE_TYPES(MakeCase) #undef MakeCase #endif default: ASSERT_NOT_REACHED(); break; } } static void derefDelayedObjects() { WTF::MutexLocker locker(domThreadMapMutex()); getThreadSpecificDOMData().setDelayedProcessingScheduled(false); ThreadSpecificDOMData::DelayedObjectMap& delayedObjectMap = getThreadSpecificDOMData().delayedObjectMap(); for (ThreadSpecificDOMData::DelayedObjectMap::iterator iter(delayedObjectMap.begin()); iter != delayedObjectMap.end(); ++iter) { derefObject(iter->second, iter->first); } delayedObjectMap.clear(); } static void derefDelayedObjectsInCurrentThread(void*) { derefDelayedObjects(); } template static void removeObjectsFromWrapperMap(DOMWrapperMap& domMap) { for (typename WTF::HashMap::iterator iter(domMap.impl().begin()); iter != domMap.impl().end(); ++iter) { T* domObject = static_cast(iter->first); v8::Persistent v8Object(iter->second); V8ClassIndex::V8WrapperType type = V8Proxy::GetDOMWrapperType(v8::Handle::Cast(v8Object)); // Deref the DOM object. derefObject(type, domObject); // Clear the JS wrapper. v8Object.Dispose(); } domMap.impl().clear(); } static void removeAllDOMObjectsInCurrentThreadHelper() { v8::HandleScope scope; // Deref all objects in the delayed queue. derefDelayedObjects(); // Remove all DOM nodes. removeObjectsFromWrapperMap(getDOMNodeMap()); // Remove all DOM objects in the wrapper map. removeObjectsFromWrapperMap(getDOMObjectMap()); // Remove all active DOM objects in the wrapper map. removeObjectsFromWrapperMap(getActiveDOMObjectMap()); #if ENABLE(SVG) // Remove all SVG element instances in the wrapper map. removeObjectsFromWrapperMap(getDOMSVGElementInstanceMap()); // Remove all SVG objects with context in the wrapper map. removeObjectsFromWrapperMap(getDOMSVGObjectWithContextMap()); #endif } static void removeAllDOMObjectsInCurrentThread() { // Use the locker only if it has already been invoked before, as by worker thread. if (v8::Locker::IsActive()) { v8::Locker locker; removeAllDOMObjectsInCurrentThreadHelper(); } else removeAllDOMObjectsInCurrentThreadHelper(); } } // namespace WebCore