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 "bindings/v8/V8GCController.h"
35 #include "V8MutationObserver.h"
37 #include "V8ScriptRunner.h"
38 #include "bindings/v8/RetainedDOMInfo.h"
39 #include "bindings/v8/V8AbstractEventListener.h"
40 #include "bindings/v8/V8Binding.h"
41 #include "bindings/v8/WrapperTypeInfo.h"
42 #include "core/dom/Attr.h"
43 #include "core/dom/NodeTraversal.h"
44 #include "core/dom/TemplateContentDocumentFragment.h"
45 #include "core/dom/shadow/ElementShadow.h"
46 #include "core/dom/shadow/ShadowRoot.h"
47 #include "core/html/HTMLImageElement.h"
48 #include "core/html/HTMLTemplateElement.h"
49 #include "core/svg/SVGElement.h"
50 #include "platform/TraceEvent.h"
54 // FIXME: This should use opaque GC roots.
55 static void addReferencesForNodeWithEventListeners(v8::Isolate* isolate, Node* node, const v8::Persistent<v8::Object>& wrapper)
57 ASSERT(node->hasEventListeners());
59 EventListenerIterator iterator(node);
60 while (EventListener* listener = iterator.nextListener()) {
61 if (listener->type() != EventListener::JSEventListenerType)
63 V8AbstractEventListener* v8listener = static_cast<V8AbstractEventListener*>(listener);
64 if (!v8listener->hasExistingListenerObject())
67 isolate->SetReference(wrapper, v8::Persistent<v8::Value>::Cast(v8listener->existingListenerObjectPersistentHandle()));
71 Node* V8GCController::opaqueRootForGC(Node* node, v8::Isolate*)
73 // FIXME: Remove the special handling for image elements.
74 // The same special handling is in V8GCController::gcTree().
75 // Maybe should image elements be active DOM nodes?
76 // See https://code.google.com/p/chromium/issues/detail?id=164882
77 if (node->inDocument() || (node->hasTagName(HTMLNames::imgTag) && toHTMLImageElement(node)->hasPendingActivity()))
78 return &node->document();
80 if (node->isAttributeNode()) {
81 Node* ownerElement = toAttr(node)->ownerElement();
87 while (Node* parent = node->parentOrShadowHostOrTemplateHostNode())
93 // Regarding a minor GC algorithm for DOM nodes, see this document:
94 // https://docs.google.com/a/google.com/presentation/d/1uifwVYGNYTZDoGLyCb7sXa7g49mWNMW2gaWvMN5NLk8/edit#slide=id.p
95 class MinorGCWrapperVisitor : public v8::PersistentHandleVisitor {
97 explicit MinorGCWrapperVisitor(v8::Isolate* isolate)
101 virtual void VisitPersistentHandle(v8::Persistent<v8::Value>* value, uint16_t classId) OVERRIDE
103 // A minor DOM GC can collect only Nodes.
104 if (classId != v8DOMNodeClassId)
107 // To make minor GC cycle time bounded, we limit the number of wrappers handled
108 // by each minor GC cycle to 10000. This value was selected so that the minor
109 // GC cycle time is bounded to 20 ms in a case where the new space size
110 // is 16 MB and it is full of wrappers (which is almost the worst case).
111 // Practically speaking, as far as I crawled real web applications,
112 // the number of wrappers handled by each minor GC cycle is at most 3000.
113 // So this limit is mainly for pathological micro benchmarks.
114 const unsigned wrappersHandledByEachMinorGC = 10000;
115 if (m_nodesInNewSpace.size() >= wrappersHandledByEachMinorGC)
118 // Casting to a Handle is safe here, since the Persistent doesn't get GCd
119 // during the GC prologue.
120 ASSERT((*reinterpret_cast<v8::Handle<v8::Value>*>(value))->IsObject());
121 v8::Handle<v8::Object>* wrapper = reinterpret_cast<v8::Handle<v8::Object>*>(value);
122 ASSERT(V8DOMWrapper::isDOMWrapper(*wrapper));
123 ASSERT(V8Node::hasInstance(*wrapper, m_isolate));
124 Node* node = V8Node::toNative(*wrapper);
125 // A minor DOM GC can handle only node wrappers in the main world.
126 // Note that node->wrapper().IsEmpty() returns true for nodes that
127 // do not have wrappers in the main world.
128 if (node->containsWrapper()) {
129 const WrapperTypeInfo* type = toWrapperTypeInfo(*wrapper);
130 ActiveDOMObject* activeDOMObject = type->toActiveDOMObject(*wrapper);
131 if (activeDOMObject && activeDOMObject->hasPendingActivity())
133 // FIXME: Remove the special handling for image elements.
134 // The same special handling is in V8GCController::opaqueRootForGC().
135 // Maybe should image elements be active DOM nodes?
136 // See https://code.google.com/p/chromium/issues/detail?id=164882
137 if (node->hasTagName(HTMLNames::imgTag) && toHTMLImageElement(node)->hasPendingActivity())
139 // FIXME: Remove the special handling for SVG context elements.
140 if (node->isSVGElement() && toSVGElement(node)->isContextElement())
143 m_nodesInNewSpace.append(node);
144 node->markV8CollectableDuringMinorGC();
148 void notifyFinished()
150 Node** nodeIterator = m_nodesInNewSpace.begin();
151 Node** nodeIteratorEnd = m_nodesInNewSpace.end();
152 for (; nodeIterator < nodeIteratorEnd; ++nodeIterator) {
153 Node* node = *nodeIterator;
154 ASSERT(node->containsWrapper());
155 if (node->isV8CollectableDuringMinorGC()) { // This branch is just for performance.
156 gcTree(m_isolate, node);
157 node->clearV8CollectableDuringMinorGC();
163 bool traverseTree(Node* rootNode, Vector<Node*, initialNodeVectorSize>* partiallyDependentNodes)
165 // To make each minor GC time bounded, we might need to give up
166 // traversing at some point for a large DOM tree. That being said,
167 // I could not observe the need even in pathological test cases.
168 for (Node* node = rootNode; node; node = NodeTraversal::next(*node)) {
169 if (node->containsWrapper()) {
170 if (!node->isV8CollectableDuringMinorGC()) {
171 // This node is not in the new space of V8. This indicates that
172 // the minor GC cannot anyway judge reachability of this DOM tree.
173 // Thus we give up traversing the DOM tree.
176 node->clearV8CollectableDuringMinorGC();
177 partiallyDependentNodes->append(node);
179 if (ShadowRoot* shadowRoot = node->youngestShadowRoot()) {
180 if (!traverseTree(shadowRoot, partiallyDependentNodes))
182 } else if (node->isShadowRoot()) {
183 if (ShadowRoot* shadowRoot = toShadowRoot(node)->olderShadowRoot()) {
184 if (!traverseTree(shadowRoot, partiallyDependentNodes))
188 // <template> has a |content| property holding a DOM fragment which we must traverse,
189 // just like we do for the shadow trees above.
190 if (node->hasTagName(HTMLNames::templateTag)) {
191 if (!traverseTree(toHTMLTemplateElement(node)->content(), partiallyDependentNodes))
198 void gcTree(v8::Isolate* isolate, Node* startNode)
200 Vector<Node*, initialNodeVectorSize> partiallyDependentNodes;
202 Node* node = startNode;
203 while (Node* parent = node->parentOrShadowHostOrTemplateHostNode())
206 if (!traverseTree(node, &partiallyDependentNodes))
209 // We completed the DOM tree traversal. All wrappers in the DOM tree are
210 // stored in partiallyDependentNodes and are expected to exist in the new space of V8.
211 // We report those wrappers to V8 as an object group.
212 Node** nodeIterator = partiallyDependentNodes.begin();
213 Node** const nodeIteratorEnd = partiallyDependentNodes.end();
214 if (nodeIterator == nodeIteratorEnd)
216 v8::UniqueId id(reinterpret_cast<intptr_t>((*nodeIterator)->unsafePersistent().value()));
217 for (; nodeIterator != nodeIteratorEnd; ++nodeIterator) {
218 // This is safe because we know that GC won't happen before we
219 // dispose the UnsafePersistent (we're just preparing a GC). Though,
220 // we need to keep the UnsafePersistent alive until we're done with
222 UnsafePersistent<v8::Object> unsafeWrapper = (*nodeIterator)->unsafePersistent();
223 v8::Persistent<v8::Object>* wrapper = unsafeWrapper.persistent();
224 wrapper->MarkPartiallyDependent();
225 isolate->SetObjectGroupId(v8::Persistent<v8::Value>::Cast(*wrapper), id);
229 Vector<Node*> m_nodesInNewSpace;
230 v8::Isolate* m_isolate;
233 class MajorGCWrapperVisitor : public v8::PersistentHandleVisitor {
235 explicit MajorGCWrapperVisitor(v8::Isolate* isolate, bool constructRetainedObjectInfos)
237 , m_liveRootGroupIdSet(false)
238 , m_constructRetainedObjectInfos(constructRetainedObjectInfos)
242 virtual void VisitPersistentHandle(v8::Persistent<v8::Value>* value, uint16_t classId) OVERRIDE
244 if (classId != v8DOMNodeClassId && classId != v8DOMObjectClassId)
247 // Casting to a Handle is safe here, since the Persistent doesn't get GCd
248 // during the GC prologue.
249 ASSERT((*reinterpret_cast<v8::Handle<v8::Value>*>(value))->IsObject());
250 v8::Handle<v8::Object>* wrapper = reinterpret_cast<v8::Handle<v8::Object>*>(value);
251 ASSERT(V8DOMWrapper::isDOMWrapper(*wrapper));
253 if (value->IsIndependent())
256 const WrapperTypeInfo* type = toWrapperTypeInfo(*wrapper);
257 void* object = toNative(*wrapper);
259 ActiveDOMObject* activeDOMObject = type->toActiveDOMObject(*wrapper);
260 if (activeDOMObject && activeDOMObject->hasPendingActivity())
261 m_isolate->SetObjectGroupId(*value, liveRootId());
263 if (classId == v8DOMNodeClassId) {
264 ASSERT(V8Node::hasInstance(*wrapper, m_isolate));
265 Node* node = static_cast<Node*>(object);
266 if (node->hasEventListeners())
267 addReferencesForNodeWithEventListeners(m_isolate, node, v8::Persistent<v8::Object>::Cast(*value));
268 Node* root = V8GCController::opaqueRootForGC(node, m_isolate);
269 m_isolate->SetObjectGroupId(*value, v8::UniqueId(reinterpret_cast<intptr_t>(root)));
270 if (m_constructRetainedObjectInfos)
271 m_groupsWhichNeedRetainerInfo.append(root);
272 } else if (classId == v8DOMObjectClassId) {
273 type->visitDOMWrapper(object, v8::Persistent<v8::Object>::Cast(*value), m_isolate);
275 ASSERT_NOT_REACHED();
279 void notifyFinished()
281 if (!m_constructRetainedObjectInfos)
283 std::sort(m_groupsWhichNeedRetainerInfo.begin(), m_groupsWhichNeedRetainerInfo.end());
284 Node* alreadyAdded = 0;
285 v8::HeapProfiler* profiler = m_isolate->GetHeapProfiler();
286 for (size_t i = 0; i < m_groupsWhichNeedRetainerInfo.size(); ++i) {
287 Node* root = m_groupsWhichNeedRetainerInfo[i];
288 if (root != alreadyAdded) {
289 profiler->SetRetainedObjectInfo(v8::UniqueId(reinterpret_cast<intptr_t>(root)), new RetainedDOMInfo(root));
296 v8::UniqueId liveRootId()
298 const v8::Persistent<v8::Value>& liveRoot = V8PerIsolateData::from(m_isolate)->ensureLiveRoot();
299 const intptr_t* idPointer = reinterpret_cast<const intptr_t*>(&liveRoot);
300 v8::UniqueId id(*idPointer);
301 if (!m_liveRootGroupIdSet) {
302 m_isolate->SetObjectGroupId(liveRoot, id);
303 m_liveRootGroupIdSet = true;
308 v8::Isolate* m_isolate;
309 Vector<Node*> m_groupsWhichNeedRetainerInfo;
310 bool m_liveRootGroupIdSet;
311 bool m_constructRetainedObjectInfos;
314 void V8GCController::gcPrologue(v8::GCType type, v8::GCCallbackFlags flags)
316 // FIXME: It would be nice if the GC callbacks passed the Isolate directly....
317 v8::Isolate* isolate = v8::Isolate::GetCurrent();
318 if (type == v8::kGCTypeScavenge)
319 minorGCPrologue(isolate);
320 else if (type == v8::kGCTypeMarkSweepCompact)
321 majorGCPrologue(flags & v8::kGCCallbackFlagConstructRetainedObjectInfos, isolate);
324 void V8GCController::minorGCPrologue(v8::Isolate* isolate)
326 TRACE_EVENT_BEGIN0("v8", "minorGC");
327 if (isMainThread()) {
329 TRACE_EVENT_SCOPED_SAMPLING_STATE("Blink", "DOMMinorGC");
330 v8::HandleScope scope(isolate);
331 MinorGCWrapperVisitor visitor(isolate);
332 v8::V8::VisitHandlesForPartialDependence(isolate, &visitor);
333 visitor.notifyFinished();
335 V8PerIsolateData::from(isolate)->setPreviousSamplingState(TRACE_EVENT_GET_SAMPLING_STATE());
336 TRACE_EVENT_SET_SAMPLING_STATE("V8", "V8MinorGC");
340 // Create object groups for DOM tree nodes.
341 void V8GCController::majorGCPrologue(bool constructRetainedObjectInfos, v8::Isolate* isolate)
343 v8::HandleScope scope(isolate);
344 TRACE_EVENT_BEGIN0("v8", "majorGC");
345 if (isMainThread()) {
347 TRACE_EVENT_SCOPED_SAMPLING_STATE("Blink", "DOMMajorGC");
348 MajorGCWrapperVisitor visitor(isolate, constructRetainedObjectInfos);
349 v8::V8::VisitHandlesWithClassIds(&visitor);
350 visitor.notifyFinished();
352 V8PerIsolateData::from(isolate)->setPreviousSamplingState(TRACE_EVENT_GET_SAMPLING_STATE());
353 TRACE_EVENT_SET_SAMPLING_STATE("V8", "V8MajorGC");
355 MajorGCWrapperVisitor visitor(isolate, constructRetainedObjectInfos);
356 v8::V8::VisitHandlesWithClassIds(&visitor);
357 visitor.notifyFinished();
361 void V8GCController::gcEpilogue(v8::GCType type, v8::GCCallbackFlags flags)
363 // FIXME: It would be nice if the GC callbacks passed the Isolate directly....
364 v8::Isolate* isolate = v8::Isolate::GetCurrent();
365 if (type == v8::kGCTypeScavenge)
366 minorGCEpilogue(isolate);
367 else if (type == v8::kGCTypeMarkSweepCompact)
368 majorGCEpilogue(isolate);
370 // Force a Blink heap garbage collection when a garbage collection
371 // was forced from V8. This is used for tests that force GCs from
372 // JavaScript to verify that objects die when expected.
373 if (flags & v8::kGCCallbackFlagForced)
374 Heap::collectGarbage(ThreadState::HeapPointersOnStack, Heap::ForcedForTesting);
377 void V8GCController::minorGCEpilogue(v8::Isolate* isolate)
379 TRACE_EVENT_END0("v8", "minorGC");
381 TRACE_EVENT_SET_NONCONST_SAMPLING_STATE(V8PerIsolateData::from(isolate)->previousSamplingState());
384 void V8GCController::majorGCEpilogue(v8::Isolate* isolate)
386 v8::HandleScope scope(isolate);
388 TRACE_EVENT_END0("v8", "majorGC");
390 TRACE_EVENT_SET_NONCONST_SAMPLING_STATE(V8PerIsolateData::from(isolate)->previousSamplingState());
393 void V8GCController::hintForCollectGarbage()
395 V8PerIsolateData* data = V8PerIsolateData::current();
396 if (!data->shouldCollectGarbageSoon())
398 const int longIdlePauseInMS = 1000;
399 data->clearShouldCollectGarbageSoon();
400 v8::V8::ContextDisposedNotification();
401 v8::V8::IdleNotification(longIdlePauseInMS);
404 void V8GCController::collectGarbage(v8::Isolate* isolate)
406 v8::HandleScope handleScope(isolate);
407 v8::Local<v8::Context> context = v8::Context::New(isolate);
408 if (context.IsEmpty())
410 v8::Context::Scope contextScope(context);
411 V8ScriptRunner::compileAndRunInternalScript(v8String(isolate, "if (gc) gc();"), isolate);
414 } // namespace WebCore