2 * Copyright (C) 2009 Google Inc. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions are
8 * * Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * * Redistributions in binary form must reproduce the above
11 * copyright notice, this list of conditions and the following disclaimer
12 * in the documentation and/or other materials provided with the
14 * * Neither the name of Google Inc. nor the names of its
15 * contributors may be used to endorse or promote products derived from
16 * this software without specific prior written permission.
18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
32 #include "V8GCController.h"
34 #include "ActiveDOMObject.h"
36 #include "DOMDataStore.h"
37 #include "DOMImplementation.h"
38 #include "HTMLImageElement.h"
39 #include "HTMLNames.h"
40 #include "MessagePort.h"
41 #include "PlatformSupport.h"
42 #include "RetainedDOMInfo.h"
43 #include "RetainedObjectInfo.h"
44 #include "V8Binding.h"
45 #include "V8CSSRule.h"
46 #include "V8CSSRuleList.h"
47 #include "V8CSSStyleDeclaration.h"
48 #include "V8DOMImplementation.h"
49 #include "V8MessagePort.h"
50 #include "V8StyleSheet.h"
51 #include "V8StyleSheetList.h"
52 #include "WrapperTypeInfo.h"
57 #include <wtf/HashMap.h>
58 #include <wtf/StdLibExtras.h>
59 #include <wtf/UnusedParam.h>
64 // Keeps track of global handles created (not JS wrappers
65 // of DOM objects). Often these global handles are source
68 // If you want to let a C++ object hold a persistent handle
69 // to a JS object, you should register the handle here to
70 // keep track of leaks.
72 // When creating a persistent handle, call:
75 // V8GCController::registerGlobalHandle(type, host, handle);
78 // When releasing the handle, call:
81 // V8GCController::unregisterGlobalHandle(type, host, handle);
85 static GlobalHandleMap& currentGlobalHandleMap()
87 return V8BindingPerIsolateData::current()->globalHandleMap();
90 // The function is the place to set the break point to inspect
91 // live global handles. Leaks are often come from leaked global handles.
92 static void enumerateGlobalHandles()
94 GlobalHandleMap& globalHandleMap = currentGlobalHandleMap();
95 for (GlobalHandleMap::iterator it = globalHandleMap.begin(), end = globalHandleMap.end(); it != end; ++it) {
96 GlobalHandleInfo* info = it->second;
98 v8::Value* handle = it->first;
103 void V8GCController::registerGlobalHandle(GlobalHandleType type, void* host, v8::Persistent<v8::Value> handle)
105 GlobalHandleMap& globalHandleMap = currentGlobalHandleMap();
106 ASSERT(!globalHandleMap.contains(*handle));
107 globalHandleMap.set(*handle, new GlobalHandleInfo(host, type));
110 void V8GCController::unregisterGlobalHandle(void* host, v8::Persistent<v8::Value> handle)
112 GlobalHandleMap& globalHandleMap = currentGlobalHandleMap();
113 ASSERT(globalHandleMap.contains(*handle));
114 GlobalHandleInfo* info = globalHandleMap.take(*handle);
115 ASSERT(info->m_host == host);
118 #endif // ifndef NDEBUG
120 typedef HashMap<Node*, v8::Object*> DOMNodeMap;
121 typedef HashMap<void*, v8::Object*> DOMObjectMap;
125 class DOMObjectVisitor : public DOMWrapperMap<void>::Visitor {
127 void visitDOMWrapper(DOMDataStore* store, void* object, v8::Persistent<v8::Object> wrapper)
129 WrapperTypeInfo* type = V8DOMWrapper::domWrapperType(wrapper);
131 UNUSED_PARAM(object);
135 class EnsureWeakDOMNodeVisitor : public DOMWrapperMap<Node>::Visitor {
137 void visitDOMWrapper(DOMDataStore* store, Node* object, v8::Persistent<v8::Object> wrapper)
139 UNUSED_PARAM(object);
140 ASSERT(wrapper.IsWeak());
146 class GCPrologueVisitor : public DOMWrapperMap<void>::Visitor {
148 void visitDOMWrapper(DOMDataStore* store, void* object, v8::Persistent<v8::Object> wrapper)
150 WrapperTypeInfo* typeInfo = V8DOMWrapper::domWrapperType(wrapper);
152 // Additional handling of message port ensuring that entangled ports also
153 // have their wrappers entangled. This should ideally be handled when the
154 // ports are actually entangled in MessagePort::entangle, but to avoid
155 // forking MessagePort.* this is postponed to GC time. Having this postponed
156 // has the drawback that the wrappers are "entangled/unentangled" for each
157 // GC even though their entaglement most likely is still the same.
158 if (V8MessagePort::info.equals(typeInfo)) {
159 // Mark each port as in-use if it's entangled. For simplicity's sake, we assume all ports are remotely entangled,
160 // since the Chromium port implementation can't tell the difference.
161 MessagePort* port1 = static_cast<MessagePort*>(object);
162 if (port1->isEntangled() || port1->hasPendingActivity())
165 ActiveDOMObject* activeDOMObject = typeInfo->toActiveDOMObject(wrapper);
166 if (activeDOMObject && activeDOMObject->hasPendingActivity())
172 // Implements v8::RetainedObjectInfo.
173 class UnspecifiedGroup : public RetainedObjectInfo {
175 explicit UnspecifiedGroup(void* object)
181 virtual void Dispose() { delete this; }
183 virtual bool IsEquivalent(v8::RetainedObjectInfo* other)
186 return other == this || static_cast<WebCore::RetainedObjectInfo*>(other)->GetEquivalenceClass() == this->GetEquivalenceClass();
189 virtual intptr_t GetHash()
191 return reinterpret_cast<intptr_t>(m_object);
194 virtual const char* GetLabel()
196 return "Object group";
199 virtual intptr_t GetEquivalenceClass()
201 return reinterpret_cast<intptr_t>(m_object);
210 GroupId() : m_type(NullType), m_groupId(0) {}
211 GroupId(Node* node) : m_type(NodeType), m_node(node) {}
212 GroupId(void* other) : m_type(OtherType), m_other(other) {}
213 bool operator!() const { return m_type == NullType; }
214 uintptr_t groupId() const { return m_groupId; }
215 RetainedObjectInfo* createRetainedObjectInfo() const
221 return new RetainedDOMInfo(m_node);
223 return new UnspecifiedGroup(m_other);
245 GrouperItem(GroupId groupId, v8::Persistent<v8::Object> wrapper) : m_groupId(groupId), m_wrapper(wrapper) {}
246 uintptr_t groupId() const { return m_groupId.groupId(); }
247 RetainedObjectInfo* createRetainedObjectInfo() const { return m_groupId.createRetainedObjectInfo(); }
248 v8::Persistent<v8::Object> wrapper() const { return m_wrapper; }
252 v8::Persistent<v8::Object> m_wrapper;
255 bool operator<(const GrouperItem& a, const GrouperItem& b)
257 return a.groupId() < b.groupId();
260 typedef Vector<GrouperItem> GrouperList;
262 // If the node is in document, put it in the ownerDocument's object group.
264 // If an image element was created by JavaScript "new Image",
265 // it is not in a document. However, if the load event has not
266 // been fired (still onloading), it is treated as in the document.
268 // Otherwise, the node is put in an object group identified by the root
269 // element of the tree to which it belongs.
270 static GroupId calculateGroupId(Node* node)
272 if (node->inDocument() || (node->hasTagName(HTMLNames::imgTag) && !static_cast<HTMLImageElement*>(node)->haveFiredLoadEvent()))
273 return GroupId(node->document());
276 if (node->isAttributeNode()) {
277 root = static_cast<Attr*>(node)->ownerElement();
278 // If the attribute has no element, no need to put it in the group,
279 // because it'll always be a group of 1.
283 while (Node* parent = root->parentOrHostNode())
287 return GroupId(root);
290 class GrouperVisitor : public DOMWrapperMap<Node>::Visitor, public DOMWrapperMap<void>::Visitor {
292 void visitDOMWrapper(DOMDataStore* store, Node* node, v8::Persistent<v8::Object> wrapper)
294 if (node->hasEventListeners()) {
295 Vector<v8::Persistent<v8::Value> > listeners;
296 EventListenerIterator iterator(node);
297 while (EventListener* listener = iterator.nextListener()) {
298 if (listener->type() != EventListener::JSEventListenerType)
300 V8AbstractEventListener* v8listener = static_cast<V8AbstractEventListener*>(listener);
301 if (!v8listener->hasExistingListenerObject())
303 listeners.append(v8listener->existingListenerObjectPersistentHandle());
305 if (!listeners.isEmpty())
306 v8::V8::AddImplicitReferences(wrapper, listeners.data(), listeners.size());
309 GroupId groupId = calculateGroupId(node);
312 m_grouper.append(GrouperItem(groupId, wrapper));
315 void visitDOMWrapper(DOMDataStore* store, void* object, v8::Persistent<v8::Object> wrapper)
321 // Group by sorting by the group id.
322 std::sort(m_grouper.begin(), m_grouper.end());
324 for (size_t i = 0; i < m_grouper.size(); ) {
325 // Seek to the next key (or the end of the list).
326 size_t nextKeyIndex = m_grouper.size();
327 for (size_t j = i; j < m_grouper.size(); ++j) {
328 if (m_grouper[i].groupId() != m_grouper[j].groupId()) {
334 ASSERT(nextKeyIndex > i);
336 // We only care about a group if it has more than one object. If it only
337 // has one object, it has nothing else that needs to be kept alive.
338 if (nextKeyIndex - i <= 1) {
343 size_t rootIndex = i;
345 Vector<v8::Persistent<v8::Value> > group;
346 group.reserveCapacity(nextKeyIndex - i);
347 for (; i < nextKeyIndex; ++i) {
348 v8::Persistent<v8::Value> wrapper = m_grouper[i].wrapper();
349 if (!wrapper.IsEmpty())
350 group.append(wrapper);
353 if (group.size() > 1)
354 v8::V8::AddObjectGroup(&group[0], group.size(), m_grouper[rootIndex].createRetainedObjectInfo());
356 ASSERT(i == nextKeyIndex);
361 GrouperList m_grouper;
364 // Create object groups for DOM tree nodes.
365 void V8GCController::gcPrologue()
367 v8::HandleScope scope;
370 DOMObjectVisitor domObjectVisitor;
371 visitDOMObjects(&domObjectVisitor);
374 // Run through all objects with possible pending activity making their
375 // wrappers non weak if there is pending activity.
376 GCPrologueVisitor prologueVisitor;
377 visitActiveDOMObjects(&prologueVisitor);
379 // Create object groups.
380 GrouperVisitor grouperVisitor;
381 visitDOMNodes(&grouperVisitor);
382 visitDOMObjects(&grouperVisitor);
383 grouperVisitor.applyGrouping();
385 // Clean single element cache for string conversions.
386 V8BindingPerIsolateData* data = V8BindingPerIsolateData::current();
387 data->stringCache()->clearOnGC();
390 class GCEpilogueVisitor : public DOMWrapperMap<void>::Visitor {
392 void visitDOMWrapper(DOMDataStore* store, void* object, v8::Persistent<v8::Object> wrapper)
394 WrapperTypeInfo* typeInfo = V8DOMWrapper::domWrapperType(wrapper);
395 if (V8MessagePort::info.equals(typeInfo)) {
396 MessagePort* port1 = static_cast<MessagePort*>(object);
397 // We marked this port as reachable in GCPrologueVisitor. Undo this now since the
398 // port could be not reachable in the future if it gets disentangled (and also
399 // GCPrologueVisitor expects to see all handles marked as weak).
400 if ((!wrapper.IsWeak() && !wrapper.IsNearDeath()) || port1->hasPendingActivity())
401 wrapper.MakeWeak(port1, &DOMDataStore::weakActiveDOMObjectCallback);
403 ActiveDOMObject* activeDOMObject = typeInfo->toActiveDOMObject(wrapper);
404 if (activeDOMObject && activeDOMObject->hasPendingActivity()) {
405 ASSERT(!wrapper.IsWeak());
406 // NOTE: To re-enable weak status of the active object we use
407 // |object| from the map and not |activeDOMObject|. The latter
408 // may be a different pointer (in case ActiveDOMObject is not
409 // the main base class of the object's class) and pointer
410 // identity is required by DOM map functions.
411 wrapper.MakeWeak(object, &DOMDataStore::weakActiveDOMObjectCallback);
417 int V8GCController::workingSetEstimateMB = 0;
421 int getMemoryUsageInMB()
423 #if PLATFORM(CHROMIUM)
424 return PlatformSupport::memoryUsageMB();
430 int getActualMemoryUsageInMB()
432 #if PLATFORM(CHROMIUM)
433 return PlatformSupport::actualMemoryUsageMB();
439 } // anonymous namespace
441 void V8GCController::gcEpilogue()
443 v8::HandleScope scope;
445 // Run through all objects with pending activity making their wrappers weak
447 GCEpilogueVisitor epilogueVisitor;
448 visitActiveDOMObjects(&epilogueVisitor);
450 workingSetEstimateMB = getActualMemoryUsageInMB();
453 // Check all survivals are weak.
454 DOMObjectVisitor domObjectVisitor;
455 visitDOMObjects(&domObjectVisitor);
457 EnsureWeakDOMNodeVisitor weakDOMNodeVisitor;
458 visitDOMNodes(&weakDOMNodeVisitor);
460 enumerateGlobalHandles();
464 void V8GCController::checkMemoryUsage()
466 #if PLATFORM(CHROMIUM) || PLATFORM(QT) && !OS(SYMBIAN)
467 const int lowMemoryUsageMB = PlatformSupport::lowMemoryUsageMB();
468 const int highMemoryUsageMB = PlatformSupport::highMemoryUsageMB();
469 const int highUsageDeltaMB = PlatformSupport::highUsageDeltaMB();
474 int memoryUsageMB = getMemoryUsageInMB();
475 if ((memoryUsageMB > lowMemoryUsageMB && memoryUsageMB > 2 * workingSetEstimateMB) || (memoryUsageMB > highMemoryUsageMB && memoryUsageMB > workingSetEstimateMB + highUsageDeltaMB))
476 v8::V8::LowMemoryNotification();
480 } // namespace WebCore