| /* |
| * 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 "DOMDataStore.h" |
| |
| #include "DOMData.h" |
| |
| namespace WebCore { |
| |
| // DOM binding algorithm: |
| // |
| // There are two kinds of DOM objects: |
| // 1. DOM tree nodes, such as Document, HTMLElement, ... |
| // there classes implement TreeShared<T> 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. |
| |
| |
| DOMDataStore::DOMDataStore(DOMData* domData) |
| : m_domNodeMap(0) |
| , m_domObjectMap(0) |
| , m_activeDomObjectMap(0) |
| #if ENABLE(SVG) |
| , m_domSvgElementInstanceMap(0) |
| #endif |
| , m_domData(domData) |
| { |
| WTF::MutexLocker locker(DOMDataStore::allStoresMutex()); |
| DOMDataStore::allStores().append(this); |
| } |
| |
| DOMDataStore::~DOMDataStore() |
| { |
| WTF::MutexLocker locker(DOMDataStore::allStoresMutex()); |
| DOMDataStore::allStores().remove(DOMDataStore::allStores().find(this)); |
| } |
| |
| DOMDataList& DOMDataStore::allStores() |
| { |
| DEFINE_STATIC_LOCAL(DOMDataList, staticDOMDataList, ()); |
| return staticDOMDataList; |
| } |
| |
| WTF::Mutex& DOMDataStore::allStoresMutex() |
| { |
| DEFINE_STATIC_LOCAL(WTF::Mutex, staticDOMDataListMutex, ()); |
| return staticDOMDataListMutex; |
| } |
| |
| void* DOMDataStore::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; |
| #endif |
| } |
| |
| ASSERT_NOT_REACHED(); |
| return 0; |
| } |
| |
| // 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. |
| void DOMDataStore::weakDOMObjectCallback(v8::Persistent<v8::Value> v8Object, void* domObject) |
| { |
| v8::HandleScope scope; |
| ASSERT(v8Object->IsObject()); |
| DOMData::handleWeakObject(DOMDataStore::DOMObjectMap, v8::Persistent<v8::Object>::Cast(v8Object), domObject); |
| } |
| |
| void DOMDataStore::weakActiveDOMObjectCallback(v8::Persistent<v8::Value> v8Object, void* domObject) |
| { |
| v8::HandleScope scope; |
| ASSERT(v8Object->IsObject()); |
| DOMData::handleWeakObject(DOMDataStore::ActiveDOMObjectMap, v8::Persistent<v8::Object>::Cast(v8Object), domObject); |
| } |
| |
| void DOMDataStore::weakNodeCallback(v8::Persistent<v8::Value> v8Object, void* domObject) |
| { |
| ASSERT(WTF::isMainThread()); |
| |
| Node* node = static_cast<Node*>(domObject); |
| |
| WTF::MutexLocker locker(DOMDataStore::allStoresMutex()); |
| DOMDataList& list = DOMDataStore::allStores(); |
| for (size_t i = 0; i < list.size(); ++i) { |
| DOMDataStore* store = list[i]; |
| if (store->domNodeMap().removeIfPresent(node, v8Object)) { |
| ASSERT(store->domData()->owningThread() == WTF::currentThread()); |
| node->deref(); // Nobody overrides Node::deref so it's safe |
| return; // There might be at most one wrapper for the node in world's maps |
| } |
| } |
| |
| // If not found, it means map for the wrapper has been already destroyed, just dispose the |
| // handle and deref the object to fight memory leak. |
| v8Object.Dispose(); |
| node->deref(); // Nobody overrides Node::deref so it's safe |
| } |
| |
| bool DOMDataStore::IntrusiveDOMWrapperMap::removeIfPresent(Node* obj, v8::Persistent<v8::Data> value) |
| { |
| ASSERT(obj); |
| v8::Persistent<v8::Object>* entry = obj->wrapper(); |
| if (!entry) |
| return false; |
| if (*entry != value) |
| return false; |
| obj->clearWrapper(); |
| m_table.remove(entry); |
| value.Dispose(); |
| return true; |
| } |
| |
| #if ENABLE(SVG) |
| |
| void DOMDataStore::weakSVGElementInstanceCallback(v8::Persistent<v8::Value> v8Object, void* domObject) |
| { |
| v8::HandleScope scope; |
| ASSERT(v8Object->IsObject()); |
| DOMData::handleWeakObject(DOMDataStore::DOMSVGElementInstanceMap, v8::Persistent<v8::Object>::Cast(v8Object), static_cast<SVGElementInstance*>(domObject)); |
| } |
| |
| #endif // ENABLE(SVG) |
| |
| } // namespace WebCore |