diff options
Diffstat (limited to 'Source/WebCore/bindings/v8/V8GCController.cpp')
-rw-r--r-- | Source/WebCore/bindings/v8/V8GCController.cpp | 535 |
1 files changed, 535 insertions, 0 deletions
diff --git a/Source/WebCore/bindings/v8/V8GCController.cpp b/Source/WebCore/bindings/v8/V8GCController.cpp new file mode 100644 index 0000000..f296b8f --- /dev/null +++ b/Source/WebCore/bindings/v8/V8GCController.cpp @@ -0,0 +1,535 @@ +/* + * 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 "V8GCController.h" + +#include "ActiveDOMObject.h" +#include "Attr.h" +#include "DOMDataStore.h" +#include "Frame.h" +#include "HTMLImageElement.h" +#include "HTMLNames.h" +#include "MessagePort.h" +#include "PlatformBridge.h" +#include "SVGElement.h" +#include "V8Binding.h" +#include "V8CSSCharsetRule.h" +#include "V8CSSFontFaceRule.h" +#include "V8CSSImportRule.h" +#include "V8CSSMediaRule.h" +#include "V8CSSRuleList.h" +#include "V8CSSStyleDeclaration.h" +#include "V8CSSStyleRule.h" +#include "V8CSSStyleSheet.h" +#include "V8DOMMap.h" +#include "V8HTMLLinkElement.h" +#include "V8HTMLStyleElement.h" +#include "V8MessagePort.h" +#include "V8ProcessingInstruction.h" +#include "V8Proxy.h" +#include "V8StyleSheetList.h" +#include "WrapperTypeInfo.h" + +#include <algorithm> +#include <utility> +#include <v8-debug.h> +#include <wtf/HashMap.h> +#include <wtf/StdLibExtras.h> +#include <wtf/UnusedParam.h> + +namespace WebCore { + +#ifndef NDEBUG +// Keeps track of global handles created (not JS wrappers +// of DOM objects). Often these global handles are source +// of leaks. +// +// If you want to let a C++ object hold a persistent handle +// to a JS object, you should register the handle here to +// keep track of leaks. +// +// When creating a persistent handle, call: +// +// #ifndef NDEBUG +// V8GCController::registerGlobalHandle(type, host, handle); +// #endif +// +// When releasing the handle, call: +// +// #ifndef NDEBUG +// V8GCController::unregisterGlobalHandle(type, host, handle); +// #endif +// +typedef HashMap<v8::Value*, GlobalHandleInfo*> GlobalHandleMap; + +static GlobalHandleMap& globalHandleMap() +{ + DEFINE_STATIC_LOCAL(GlobalHandleMap, staticGlobalHandleMap, ()); + return staticGlobalHandleMap; +} + +// The function is the place to set the break point to inspect +// live global handles. Leaks are often come from leaked global handles. +static void enumerateGlobalHandles() +{ + for (GlobalHandleMap::iterator it = globalHandleMap().begin(), end = globalHandleMap().end(); it != end; ++it) { + GlobalHandleInfo* info = it->second; + UNUSED_PARAM(info); + v8::Value* handle = it->first; + UNUSED_PARAM(handle); + } +} + +void V8GCController::registerGlobalHandle(GlobalHandleType type, void* host, v8::Persistent<v8::Value> handle) +{ + ASSERT(!globalHandleMap().contains(*handle)); + globalHandleMap().set(*handle, new GlobalHandleInfo(host, type)); +} + +void V8GCController::unregisterGlobalHandle(void* host, v8::Persistent<v8::Value> handle) +{ + ASSERT(globalHandleMap().contains(*handle)); + GlobalHandleInfo* info = globalHandleMap().take(*handle); + ASSERT(info->m_host == host); + delete info; +} +#endif // ifndef NDEBUG + +typedef HashMap<Node*, v8::Object*> DOMNodeMap; +typedef HashMap<void*, v8::Object*> DOMObjectMap; + +#ifndef NDEBUG + +class DOMObjectVisitor : public DOMWrapperMap<void>::Visitor { +public: + void visitDOMWrapper(DOMDataStore* store, void* object, v8::Persistent<v8::Object> wrapper) + { + WrapperTypeInfo* type = V8DOMWrapper::domWrapperType(wrapper); + UNUSED_PARAM(type); + UNUSED_PARAM(object); + } +}; + +class EnsureWeakDOMNodeVisitor : public DOMWrapperMap<Node>::Visitor { +public: + void visitDOMWrapper(DOMDataStore* store, Node* object, v8::Persistent<v8::Object> wrapper) + { + UNUSED_PARAM(object); + ASSERT(wrapper.IsWeak()); + } +}; + +#endif // NDEBUG + +class GCPrologueVisitor : public DOMWrapperMap<void>::Visitor { +public: + void visitDOMWrapper(DOMDataStore* store, void* object, v8::Persistent<v8::Object> wrapper) + { + WrapperTypeInfo* typeInfo = V8DOMWrapper::domWrapperType(wrapper); + + // Additional handling of message port ensuring that entangled ports also + // have their wrappers entangled. This should ideally be handled when the + // ports are actually entangled in MessagePort::entangle, but to avoid + // forking MessagePort.* this is postponed to GC time. Having this postponed + // has the drawback that the wrappers are "entangled/unentangled" for each + // GC even though their entaglement most likely is still the same. + if (V8MessagePort::info.equals(typeInfo)) { + // Mark each port as in-use if it's entangled. For simplicity's sake, we assume all ports are remotely entangled, + // since the Chromium port implementation can't tell the difference. + MessagePort* port1 = static_cast<MessagePort*>(object); + if (port1->isEntangled() || port1->hasPendingActivity()) + wrapper.ClearWeak(); + } else { + ActiveDOMObject* activeDOMObject = typeInfo->toActiveDOMObject(wrapper); + if (activeDOMObject && activeDOMObject->hasPendingActivity()) + wrapper.ClearWeak(); + } + } +}; + +class GrouperItem { +public: + GrouperItem(uintptr_t groupId, v8::Persistent<v8::Object> wrapper) + : m_groupId(groupId) + , m_wrapper(wrapper) + { + } + + uintptr_t groupId() const { return m_groupId; } + v8::Persistent<v8::Object> wrapper() const { return m_wrapper; } + +private: + uintptr_t m_groupId; + v8::Persistent<v8::Object> m_wrapper; +}; + +bool operator<(const GrouperItem& a, const GrouperItem& b) +{ + return a.groupId() < b.groupId(); +} + +typedef Vector<GrouperItem> GrouperList; + +void makeV8ObjectGroups(GrouperList& grouper) +{ + // Group by sorting by the group id. + std::sort(grouper.begin(), grouper.end()); + + // FIXME Should probably work in iterators here, but indexes were easier for my simple mind. + for (size_t i = 0; i < grouper.size(); ) { + // Seek to the next key (or the end of the list). + size_t nextKeyIndex = grouper.size(); + for (size_t j = i; j < grouper.size(); ++j) { + if (grouper[i].groupId() != grouper[j].groupId()) { + nextKeyIndex = j; + break; + } + } + + ASSERT(nextKeyIndex > i); + + // We only care about a group if it has more than one object. If it only + // has one object, it has nothing else that needs to be kept alive. + if (nextKeyIndex - i <= 1) { + i = nextKeyIndex; + continue; + } + + Vector<v8::Persistent<v8::Value> > group; + group.reserveCapacity(nextKeyIndex - i); + for (; i < nextKeyIndex; ++i) { + v8::Persistent<v8::Value> wrapper = grouper[i].wrapper(); + if (!wrapper.IsEmpty()) + group.append(wrapper); + } + + if (group.size() > 1) + v8::V8::AddObjectGroup(&group[0], group.size()); + + ASSERT(i == nextKeyIndex); + } +} + +class NodeGrouperVisitor : public DOMWrapperMap<Node>::Visitor { +public: + NodeGrouperVisitor() + { + // FIXME: grouper_.reserveCapacity(node_map.size()); ? + } + + void visitDOMWrapper(DOMDataStore* store, Node* node, v8::Persistent<v8::Object> wrapper) + { + // If the node is in document, put it in the ownerDocument's object group. + // + // If an image element was created by JavaScript "new Image", + // it is not in a document. However, if the load event has not + // been fired (still onloading), it is treated as in the document. + // + // Otherwise, the node is put in an object group identified by the root + // element of the tree to which it belongs. + uintptr_t groupId; + if (node->inDocument() || (node->hasTagName(HTMLNames::imgTag) && !static_cast<HTMLImageElement*>(node)->haveFiredLoadEvent())) + groupId = reinterpret_cast<uintptr_t>(node->document()); + else { + Node* root = node; + if (node->isAttributeNode()) { + root = static_cast<Attr*>(node)->ownerElement(); + // If the attribute has no element, no need to put it in the group, + // because it'll always be a group of 1. + if (!root) + return; + } else { + while (root->parentNode()) + root = root->parentNode(); + + // If the node is alone in its DOM tree (doesn't have a parent or any + // children) then the group will be filtered out later anyway. + if (root == node && !node->hasChildNodes() && !node->hasAttributes()) + return; + } + groupId = reinterpret_cast<uintptr_t>(root); + } + m_grouper.append(GrouperItem(groupId, wrapper)); + + // If the node is styled and there is a wrapper for the inline + // style declaration, we need to keep that style declaration + // wrapper alive as well, so we add it to the object group. + if (node->isStyledElement()) { + StyledElement* element = reinterpret_cast<StyledElement*>(node); + addDOMObjectToGroup(store, groupId, element->inlineStyleDecl()); + } + + if (node->isDocumentNode()) { + Document* document = reinterpret_cast<Document*>(node); + addDOMObjectToGroup(store, groupId, document->styleSheets()); + } + + WrapperTypeInfo* typeInfo = V8DOMWrapper::domWrapperType(wrapper); + + if (V8HTMLLinkElement::info.equals(typeInfo)) { + HTMLLinkElement* htmlLinkElement = static_cast<HTMLLinkElement*>(node); + addDOMObjectToGroup(store, groupId, htmlLinkElement->sheet()); + } + + if (V8HTMLStyleElement::info.equals(typeInfo)) { + HTMLStyleElement* htmlStyleElement = static_cast<HTMLStyleElement*>(node); + addDOMObjectToGroup(store, groupId, htmlStyleElement->sheet()); + } + + if (V8ProcessingInstruction::info.equals(typeInfo)) { + ProcessingInstruction* processingInstruction = static_cast<ProcessingInstruction*>(node); + addDOMObjectToGroup(store, groupId, processingInstruction->sheet()); + } + } + + void applyGrouping() + { + makeV8ObjectGroups(m_grouper); + } + +private: + GrouperList m_grouper; + + void addDOMObjectToGroup(DOMDataStore* store, uintptr_t groupId, void* object) + { + if (!object) + return; + v8::Persistent<v8::Object> wrapper = store->domObjectMap().get(object); + if (!wrapper.IsEmpty()) + m_grouper.append(GrouperItem(groupId, wrapper)); + } +}; + +class DOMObjectGrouperVisitor : public DOMWrapperMap<void>::Visitor { +public: + DOMObjectGrouperVisitor() + { + } + + void startMap() + { + m_grouper.shrink(0); + } + + void endMap() + { + makeV8ObjectGroups(m_grouper); + } + + void visitDOMWrapper(DOMDataStore* store, void* object, v8::Persistent<v8::Object> wrapper) + { + WrapperTypeInfo* typeInfo = V8DOMWrapper::domWrapperType(wrapper); + // FIXME: extend WrapperTypeInfo with isStyle to simplify the check below or consider + // adding a virtual method to WrapperTypeInfo which would know how to group objects. + // FIXME: check if there are other StyleBase wrappers we should care of. + if (V8CSSStyleSheet::info.equals(typeInfo) + || V8CSSStyleDeclaration::info.equals(typeInfo) + || V8CSSCharsetRule::info.equals(typeInfo) + || V8CSSFontFaceRule::info.equals(typeInfo) + || V8CSSStyleRule::info.equals(typeInfo) + || V8CSSImportRule::info.equals(typeInfo) + || V8CSSMediaRule::info.equals(typeInfo)) { + StyleBase* styleBase = static_cast<StyleBase*>(object); + + // We put the whole tree of style elements into a single object group. + // To achieve that we group elements by the roots of their trees. + StyleBase* root = styleBase; + ASSERT(root); + while (true) { + StyleBase* parent = root->parent(); + if (!parent) + break; + root = parent; + } + // Group id is an address of the root. + uintptr_t groupId = reinterpret_cast<uintptr_t>(root); + m_grouper.append(GrouperItem(groupId, wrapper)); + + if (V8CSSStyleDeclaration::info.equals(typeInfo)) { + CSSStyleDeclaration* cssStyleDeclaration = static_cast<CSSStyleDeclaration*>(styleBase); + if (cssStyleDeclaration->isMutableStyleDeclaration()) { + CSSMutableStyleDeclaration* cssMutableStyleDeclaration = static_cast<CSSMutableStyleDeclaration*>(cssStyleDeclaration); + CSSMutableStyleDeclaration::const_iterator end = cssMutableStyleDeclaration->end(); + for (CSSMutableStyleDeclaration::const_iterator it = cssMutableStyleDeclaration->begin(); it != end; ++it) { + wrapper = store->domObjectMap().get(it->value()); + if (!wrapper.IsEmpty()) + m_grouper.append(GrouperItem(groupId, wrapper)); + } + } + } + } else if (V8StyleSheetList::info.equals(typeInfo)) { + addAllItems(store, static_cast<StyleSheetList*>(object), wrapper); + } else if (V8CSSRuleList::info.equals(typeInfo)) { + addAllItems(store, static_cast<CSSRuleList*>(object), wrapper); + } + } + +private: + GrouperList m_grouper; + + template <class C> + void addAllItems(DOMDataStore* store, C* collection, v8::Persistent<v8::Object> wrapper) + { + uintptr_t groupId = reinterpret_cast<uintptr_t>(collection); + m_grouper.append(GrouperItem(groupId, wrapper)); + for (unsigned i = 0; i < collection->length(); i++) { + wrapper = store->domObjectMap().get(collection->item(i)); + if (!wrapper.IsEmpty()) + m_grouper.append(GrouperItem(groupId, wrapper)); + } + } +}; + +// Create object groups for DOM tree nodes. +void V8GCController::gcPrologue() +{ + v8::HandleScope scope; + +#ifndef NDEBUG + DOMObjectVisitor domObjectVisitor; + visitDOMObjectsInCurrentThread(&domObjectVisitor); +#endif + + // Run through all objects with possible pending activity making their + // wrappers non weak if there is pending activity. + GCPrologueVisitor prologueVisitor; + visitActiveDOMObjectsInCurrentThread(&prologueVisitor); + + // Create object groups. + NodeGrouperVisitor nodeGrouperVisitor; + visitDOMNodesInCurrentThread(&nodeGrouperVisitor); + nodeGrouperVisitor.applyGrouping(); + + DOMObjectGrouperVisitor domObjectGrouperVisitor; + visitDOMObjectsInCurrentThread(&domObjectGrouperVisitor); + + // Clean single element cache for string conversions. + lastStringImpl = 0; + lastV8String.Clear(); +} + +class GCEpilogueVisitor : public DOMWrapperMap<void>::Visitor { +public: + void visitDOMWrapper(DOMDataStore* store, void* object, v8::Persistent<v8::Object> wrapper) + { + WrapperTypeInfo* typeInfo = V8DOMWrapper::domWrapperType(wrapper); + if (V8MessagePort::info.equals(typeInfo)) { + MessagePort* port1 = static_cast<MessagePort*>(object); + // We marked this port as reachable in GCPrologueVisitor. Undo this now since the + // port could be not reachable in the future if it gets disentangled (and also + // GCPrologueVisitor expects to see all handles marked as weak). + if ((!wrapper.IsWeak() && !wrapper.IsNearDeath()) || port1->hasPendingActivity()) + wrapper.MakeWeak(port1, &DOMDataStore::weakActiveDOMObjectCallback); + } else { + ActiveDOMObject* activeDOMObject = typeInfo->toActiveDOMObject(wrapper); + if (activeDOMObject && activeDOMObject->hasPendingActivity()) { + ASSERT(!wrapper.IsWeak()); + // NOTE: To re-enable weak status of the active object we use + // |object| from the map and not |activeDOMObject|. The latter + // may be a different pointer (in case ActiveDOMObject is not + // the main base class of the object's class) and pointer + // identity is required by DOM map functions. + wrapper.MakeWeak(object, &DOMDataStore::weakActiveDOMObjectCallback); + } + } + } +}; + +int V8GCController::workingSetEstimateMB = 0; + +namespace { + +int getMemoryUsageInMB() +{ +#if PLATFORM(CHROMIUM) || PLATFORM(ANDROID) + return PlatformBridge::memoryUsageMB(); +#else + return 0; +#endif +} + +int getActualMemoryUsageInMB() +{ +#if PLATFORM(CHROMIUM) || PLATFORM(ANDROID) + return PlatformBridge::actualMemoryUsageMB(); +#else + return 0; +#endif +} + +} // anonymous namespace + +void V8GCController::gcEpilogue() +{ + v8::HandleScope scope; + + // Run through all objects with pending activity making their wrappers weak + // again. + GCEpilogueVisitor epilogueVisitor; + visitActiveDOMObjectsInCurrentThread(&epilogueVisitor); + + workingSetEstimateMB = getActualMemoryUsageInMB(); + +#ifndef NDEBUG + // Check all survivals are weak. + DOMObjectVisitor domObjectVisitor; + visitDOMObjectsInCurrentThread(&domObjectVisitor); + + EnsureWeakDOMNodeVisitor weakDOMNodeVisitor; + visitDOMNodesInCurrentThread(&weakDOMNodeVisitor); + + enumerateGlobalHandles(); +#endif +} + +void V8GCController::checkMemoryUsage() +{ +#if PLATFORM(CHROMIUM) || PLATFORM(QT) && !OS(SYMBIAN) + // These values are appropriate for Chromium only. + const int lowUsageMB = 256; // If memory usage is below this threshold, do not bother forcing GC. + const int highUsageMB = 1024; // If memory usage is above this threshold, force GC more aggresively. + const int highUsageDeltaMB = 128; // Delta of memory usage growth (vs. last workingSetEstimateMB) to force GC when memory usage is high. +#elif PLATFORM(ANDROID) + // Query the PlatformBridge for memory thresholds as these vary device to device. + static const int lowUsageMB = PlatformBridge::lowMemoryUsageMB(); + static const int highUsageMB = PlatformBridge::highMemoryUsageMB(); + static const int highUsageDeltaMB = PlatformBridge::highUsageDeltaMB(); +#else + return; +#endif + + int memoryUsageMB = getMemoryUsageInMB(); + if ((memoryUsageMB > lowUsageMB && memoryUsageMB > 2 * workingSetEstimateMB) || (memoryUsageMB > highUsageMB && memoryUsageMB > workingSetEstimateMB + highUsageDeltaMB)) + v8::V8::LowMemoryNotification(); +} + + +} // namespace WebCore |