1 /*
2 * Copyright (C) 2009 Google Inc. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions are
6 * met:
7 *
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
13 * distribution.
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.
17 *
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.
29 */
30
31 #include "config.h"
32 #include "bindings/v8/V8GCController.h"
33
34 #include <algorithm>
35 #include "V8MutationObserver.h"
36 #include "V8Node.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"
51
52 namespace WebCore {
53
54 // FIXME: This should use opaque GC roots.
addReferencesForNodeWithEventListeners(v8::Isolate * isolate,Node * node,const v8::Persistent<v8::Object> & wrapper)55 static void addReferencesForNodeWithEventListeners(v8::Isolate* isolate, Node* node, const v8::Persistent<v8::Object>& wrapper)
56 {
57 ASSERT(node->hasEventListeners());
58
59 EventListenerIterator iterator(node);
60 while (EventListener* listener = iterator.nextListener()) {
61 if (listener->type() != EventListener::JSEventListenerType)
62 continue;
63 V8AbstractEventListener* v8listener = static_cast<V8AbstractEventListener*>(listener);
64 if (!v8listener->hasExistingListenerObject())
65 continue;
66
67 // FIXME: update this to use the upcasting function which v8 will provide.
68 v8::Persistent<v8::Value>* value = reinterpret_cast<v8::Persistent<v8::Value>*>(&(v8listener->existingListenerObjectPersistentHandle()));
69 isolate->SetReference(wrapper, *value);
70 }
71 }
72
opaqueRootForGC(Node * node,v8::Isolate *)73 Node* V8GCController::opaqueRootForGC(Node* node, v8::Isolate*)
74 {
75 // FIXME: Remove the special handling for image elements.
76 // The same special handling is in V8GCController::gcTree().
77 // Maybe should image elements be active DOM nodes?
78 // See https://code.google.com/p/chromium/issues/detail?id=164882
79 if (node->inDocument() || (node->hasTagName(HTMLNames::imgTag) && toHTMLImageElement(node)->hasPendingActivity()))
80 return &node->document();
81
82 if (node->isAttributeNode()) {
83 Node* ownerElement = toAttr(node)->ownerElement();
84 if (!ownerElement)
85 return node;
86 node = ownerElement;
87 }
88
89 while (Node* parent = node->parentOrShadowHostOrTemplateHostNode())
90 node = parent;
91
92 return node;
93 }
94
95 // Regarding a minor GC algorithm for DOM nodes, see this document:
96 // https://docs.google.com/a/google.com/presentation/d/1uifwVYGNYTZDoGLyCb7sXa7g49mWNMW2gaWvMN5NLk8/edit#slide=id.p
97 class MinorGCWrapperVisitor : public v8::PersistentHandleVisitor {
98 public:
MinorGCWrapperVisitor(v8::Isolate * isolate)99 explicit MinorGCWrapperVisitor(v8::Isolate* isolate)
100 : m_isolate(isolate)
101 { }
102
VisitPersistentHandle(v8::Persistent<v8::Value> * value,uint16_t classId)103 virtual void VisitPersistentHandle(v8::Persistent<v8::Value>* value, uint16_t classId) OVERRIDE
104 {
105 // A minor DOM GC can collect only Nodes.
106 if (classId != v8DOMNodeClassId)
107 return;
108
109 // To make minor GC cycle time bounded, we limit the number of wrappers handled
110 // by each minor GC cycle to 10000. This value was selected so that the minor
111 // GC cycle time is bounded to 20 ms in a case where the new space size
112 // is 16 MB and it is full of wrappers (which is almost the worst case).
113 // Practically speaking, as far as I crawled real web applications,
114 // the number of wrappers handled by each minor GC cycle is at most 3000.
115 // So this limit is mainly for pathological micro benchmarks.
116 const unsigned wrappersHandledByEachMinorGC = 10000;
117 if (m_nodesInNewSpace.size() >= wrappersHandledByEachMinorGC)
118 return;
119
120 // Casting to a Handle is safe here, since the Persistent cannot get GCd
121 // during the GC prologue.
122 ASSERT((*reinterpret_cast<v8::Handle<v8::Value>*>(value))->IsObject());
123 v8::Handle<v8::Object>* wrapper = reinterpret_cast<v8::Handle<v8::Object>*>(value);
124 ASSERT(V8DOMWrapper::maybeDOMWrapper(*wrapper));
125 ASSERT(V8Node::hasInstanceInAnyWorld(*wrapper, m_isolate));
126 Node* node = V8Node::toNative(*wrapper);
127 // A minor DOM GC can handle only node wrappers in the main world.
128 // Note that node->wrapper().IsEmpty() returns true for nodes that
129 // do not have wrappers in the main world.
130 if (node->containsWrapper()) {
131 const WrapperTypeInfo* type = toWrapperTypeInfo(*wrapper);
132 ActiveDOMObject* activeDOMObject = type->toActiveDOMObject(*wrapper);
133 if (activeDOMObject && activeDOMObject->hasPendingActivity())
134 return;
135 m_nodesInNewSpace.append(node);
136 node->setV8CollectableDuringMinorGC(true);
137 }
138 }
139
notifyFinished()140 void notifyFinished()
141 {
142 Node** nodeIterator = m_nodesInNewSpace.begin();
143 Node** nodeIteratorEnd = m_nodesInNewSpace.end();
144 for (; nodeIterator < nodeIteratorEnd; ++nodeIterator) {
145 Node* node = *nodeIterator;
146 ASSERT(node->containsWrapper());
147 if (node->isV8CollectableDuringMinorGC()) // This branch is just for performance.
148 gcTree(m_isolate, node);
149 }
150 }
151
152 private:
traverseTree(Node * rootNode,Vector<Node *,initialNodeVectorSize> * newSpaceNodes)153 bool traverseTree(Node* rootNode, Vector<Node*, initialNodeVectorSize>* newSpaceNodes)
154 {
155 // To make each minor GC time bounded, we might need to give up
156 // traversing at some point for a large DOM tree. That being said,
157 // I could not observe the need even in pathological test cases.
158 for (Node* node = rootNode; node; node = NodeTraversal::next(*node)) {
159 if (node->containsWrapper()) {
160 // FIXME: Remove the special handling for image elements.
161 // FIXME: Remove the special handling for SVG context elements.
162 // The same special handling is in V8GCController::opaqueRootForGC().
163 // Maybe should image elements be active DOM nodes?
164 // See https://code.google.com/p/chromium/issues/detail?id=164882
165 if (!node->isV8CollectableDuringMinorGC() || (node->hasTagName(HTMLNames::imgTag) && toHTMLImageElement(node)->hasPendingActivity()) || (node->isSVGElement() && toSVGElement(node)->isContextElement())) {
166 // This node is not in the new space of V8. This indicates that
167 // the minor GC cannot anyway judge reachability of this DOM tree.
168 // Thus we give up traversing the DOM tree.
169 return false;
170 }
171 node->setV8CollectableDuringMinorGC(false);
172 newSpaceNodes->append(node);
173 }
174 if (ShadowRoot* shadowRoot = node->youngestShadowRoot()) {
175 if (!traverseTree(shadowRoot, newSpaceNodes))
176 return false;
177 } else if (node->isShadowRoot()) {
178 if (ShadowRoot* shadowRoot = toShadowRoot(node)->olderShadowRoot()) {
179 if (!traverseTree(shadowRoot, newSpaceNodes))
180 return false;
181 }
182 }
183 // <template> has a |content| property holding a DOM fragment which we must traverse,
184 // just like we do for the shadow trees above.
185 if (node->hasTagName(HTMLNames::templateTag)) {
186 if (!traverseTree(toHTMLTemplateElement(node)->content(), newSpaceNodes))
187 return false;
188 }
189 }
190 return true;
191 }
192
gcTree(v8::Isolate * isolate,Node * startNode)193 void gcTree(v8::Isolate* isolate, Node* startNode)
194 {
195 Vector<Node*, initialNodeVectorSize> newSpaceNodes;
196
197 Node* node = startNode;
198 while (Node* parent = node->parentOrShadowHostOrTemplateHostNode())
199 node = parent;
200
201 if (!traverseTree(node, &newSpaceNodes))
202 return;
203
204 // We completed the DOM tree traversal. All wrappers in the DOM tree are
205 // stored in newSpaceNodes and are expected to exist in the new space of V8.
206 // We report those wrappers to V8 as an object group.
207 Node** nodeIterator = newSpaceNodes.begin();
208 Node** const nodeIteratorEnd = newSpaceNodes.end();
209 if (nodeIterator == nodeIteratorEnd)
210 return;
211 v8::UniqueId id(reinterpret_cast<intptr_t>((*nodeIterator)->unsafePersistent().value()));
212 for (; nodeIterator != nodeIteratorEnd; ++nodeIterator) {
213 // This is safe because we know that GC won't happen before we
214 // dispose the UnsafePersistent (we're just preparing a GC). Though,
215 // we need to keep the UnsafePersistent alive until we're done with
216 // v8::Persistent.
217 UnsafePersistent<v8::Object> unsafeWrapper = (*nodeIterator)->unsafePersistent();
218 v8::Persistent<v8::Object>* wrapper = unsafeWrapper.persistent();
219 wrapper->MarkPartiallyDependent();
220 // FIXME: update this to use the upcasting function which v8 will provide
221 v8::Persistent<v8::Value>* value = reinterpret_cast<v8::Persistent<v8::Value>*>(wrapper);
222 isolate->SetObjectGroupId(*value, id);
223 }
224 }
225
226 Vector<Node*> m_nodesInNewSpace;
227 v8::Isolate* m_isolate;
228 };
229
230 class MajorGCWrapperVisitor : public v8::PersistentHandleVisitor {
231 public:
MajorGCWrapperVisitor(v8::Isolate * isolate,bool constructRetainedObjectInfos)232 explicit MajorGCWrapperVisitor(v8::Isolate* isolate, bool constructRetainedObjectInfos)
233 : m_isolate(isolate)
234 , m_liveRootGroupIdSet(false)
235 , m_constructRetainedObjectInfos(constructRetainedObjectInfos)
236 {
237 }
238
VisitPersistentHandle(v8::Persistent<v8::Value> * value,uint16_t classId)239 virtual void VisitPersistentHandle(v8::Persistent<v8::Value>* value, uint16_t classId) OVERRIDE
240 {
241 // Casting to a Handle is safe here, since the Persistent cannot get GCd
242 // during the GC prologue.
243 ASSERT((*reinterpret_cast<v8::Handle<v8::Value>*>(value))->IsObject());
244
245 if (classId != v8DOMNodeClassId && classId != v8DOMObjectClassId)
246 return;
247
248 v8::Handle<v8::Object>* wrapper = reinterpret_cast<v8::Handle<v8::Object>*>(value);
249
250 ASSERT(V8DOMWrapper::maybeDOMWrapper(*wrapper));
251
252 if (value->IsIndependent())
253 return;
254
255 const WrapperTypeInfo* type = toWrapperTypeInfo(*wrapper);
256 void* object = toNative(*wrapper);
257
258 if (V8MutationObserver::wrapperTypeInfo.equals(type)) {
259 // FIXME: Allow opaqueRootForGC to operate on multiple roots and move this logic into V8MutationObserverCustom.
260 MutationObserver* observer = static_cast<MutationObserver*>(object);
261 HashSet<Node*> observedNodes = observer->getObservedNodes();
262 for (HashSet<Node*>::iterator it = observedNodes.begin(); it != observedNodes.end(); ++it) {
263 v8::UniqueId id(reinterpret_cast<intptr_t>(V8GCController::opaqueRootForGC(*it, m_isolate)));
264 m_isolate->SetReferenceFromGroup(id, *value);
265 }
266 } else {
267 ActiveDOMObject* activeDOMObject = type->toActiveDOMObject(*wrapper);
268 if (activeDOMObject && activeDOMObject->hasPendingActivity())
269 m_isolate->SetObjectGroupId(*value, liveRootId());
270 }
271
272 if (classId == v8DOMNodeClassId) {
273 ASSERT(V8Node::hasInstanceInAnyWorld(*wrapper, m_isolate));
274 ASSERT(!value->IsIndependent());
275
276 Node* node = static_cast<Node*>(object);
277
278 if (node->hasEventListeners())
279 addReferencesForNodeWithEventListeners(m_isolate, node, v8::Persistent<v8::Object>::Cast(*value));
280 Node* root = V8GCController::opaqueRootForGC(node, m_isolate);
281 m_isolate->SetObjectGroupId(*value, v8::UniqueId(reinterpret_cast<intptr_t>(root)));
282 if (m_constructRetainedObjectInfos)
283 m_groupsWhichNeedRetainerInfo.append(root);
284 } else if (classId == v8DOMObjectClassId) {
285 ASSERT(!value->IsIndependent());
286 v8::Persistent<v8::Object>* wrapperPersistent = reinterpret_cast<v8::Persistent<v8::Object>*>(value);
287 type->visitDOMWrapper(object, *wrapperPersistent, m_isolate);
288 } else {
289 ASSERT_NOT_REACHED();
290 }
291 }
292
notifyFinished()293 void notifyFinished()
294 {
295 if (!m_constructRetainedObjectInfos)
296 return;
297 std::sort(m_groupsWhichNeedRetainerInfo.begin(), m_groupsWhichNeedRetainerInfo.end());
298 Node* alreadyAdded = 0;
299 v8::HeapProfiler* profiler = m_isolate->GetHeapProfiler();
300 for (size_t i = 0; i < m_groupsWhichNeedRetainerInfo.size(); ++i) {
301 Node* root = m_groupsWhichNeedRetainerInfo[i];
302 if (root != alreadyAdded) {
303 profiler->SetRetainedObjectInfo(v8::UniqueId(reinterpret_cast<intptr_t>(root)), new RetainedDOMInfo(root));
304 alreadyAdded = root;
305 }
306 }
307 }
308
309 private:
liveRootId()310 v8::UniqueId liveRootId()
311 {
312 const v8::Persistent<v8::Value>& liveRoot = V8PerIsolateData::from(m_isolate)->ensureLiveRoot();
313 const intptr_t* idPointer = reinterpret_cast<const intptr_t*>(&liveRoot);
314 v8::UniqueId id(*idPointer);
315 if (!m_liveRootGroupIdSet) {
316 m_isolate->SetObjectGroupId(liveRoot, id);
317 m_liveRootGroupIdSet = true;
318 }
319 return id;
320 }
321
322 v8::Isolate* m_isolate;
323 Vector<Node*> m_groupsWhichNeedRetainerInfo;
324 bool m_liveRootGroupIdSet;
325 bool m_constructRetainedObjectInfos;
326 };
327
gcPrologue(v8::GCType type,v8::GCCallbackFlags flags)328 void V8GCController::gcPrologue(v8::GCType type, v8::GCCallbackFlags flags)
329 {
330 // FIXME: It would be nice if the GC callbacks passed the Isolate directly....
331 v8::Isolate* isolate = v8::Isolate::GetCurrent();
332 if (type == v8::kGCTypeScavenge)
333 minorGCPrologue(isolate);
334 else if (type == v8::kGCTypeMarkSweepCompact)
335 majorGCPrologue(flags & v8::kGCCallbackFlagConstructRetainedObjectInfos, isolate);
336 }
337
minorGCPrologue(v8::Isolate * isolate)338 void V8GCController::minorGCPrologue(v8::Isolate* isolate)
339 {
340 TRACE_EVENT_BEGIN0("v8", "minorGC");
341 if (isMainThread()) {
342 {
343 TRACE_EVENT_SCOPED_SAMPLING_STATE("Blink", "MinorGC");
344 v8::HandleScope scope(isolate);
345 MinorGCWrapperVisitor visitor(isolate);
346 v8::V8::VisitHandlesForPartialDependence(isolate, &visitor);
347 visitor.notifyFinished();
348 }
349 V8PerIsolateData::from(isolate)->setPreviousSamplingState(TRACE_EVENT_GET_SAMPLING_STATE());
350 TRACE_EVENT_SET_SAMPLING_STATE("V8", "MinorGC");
351 }
352 }
353
354 // Create object groups for DOM tree nodes.
majorGCPrologue(bool constructRetainedObjectInfos,v8::Isolate * isolate)355 void V8GCController::majorGCPrologue(bool constructRetainedObjectInfos, v8::Isolate* isolate)
356 {
357 v8::HandleScope scope(isolate);
358 TRACE_EVENT_BEGIN0("v8", "majorGC");
359 if (isMainThread()) {
360 {
361 TRACE_EVENT_SCOPED_SAMPLING_STATE("Blink", "MajorGC");
362 MajorGCWrapperVisitor visitor(isolate, constructRetainedObjectInfos);
363 v8::V8::VisitHandlesWithClassIds(&visitor);
364 visitor.notifyFinished();
365 }
366 V8PerIsolateData::from(isolate)->setPreviousSamplingState(TRACE_EVENT_GET_SAMPLING_STATE());
367 TRACE_EVENT_SET_SAMPLING_STATE("V8", "MajorGC");
368 } else {
369 MajorGCWrapperVisitor visitor(isolate, constructRetainedObjectInfos);
370 v8::V8::VisitHandlesWithClassIds(&visitor);
371 visitor.notifyFinished();
372 }
373 }
374
gcEpilogue(v8::GCType type,v8::GCCallbackFlags flags)375 void V8GCController::gcEpilogue(v8::GCType type, v8::GCCallbackFlags flags)
376 {
377 // FIXME: It would be nice if the GC callbacks passed the Isolate directly....
378 v8::Isolate* isolate = v8::Isolate::GetCurrent();
379 if (type == v8::kGCTypeScavenge)
380 minorGCEpilogue(isolate);
381 else if (type == v8::kGCTypeMarkSweepCompact)
382 majorGCEpilogue(isolate);
383 }
384
minorGCEpilogue(v8::Isolate * isolate)385 void V8GCController::minorGCEpilogue(v8::Isolate* isolate)
386 {
387 TRACE_EVENT_END0("v8", "minorGC");
388 if (isMainThread())
389 TRACE_EVENT_SET_NONCONST_SAMPLING_STATE(V8PerIsolateData::from(isolate)->previousSamplingState());
390 }
391
majorGCEpilogue(v8::Isolate * isolate)392 void V8GCController::majorGCEpilogue(v8::Isolate* isolate)
393 {
394 v8::HandleScope scope(isolate);
395
396 TRACE_EVENT_END0("v8", "majorGC");
397 if (isMainThread())
398 TRACE_EVENT_SET_NONCONST_SAMPLING_STATE(V8PerIsolateData::from(isolate)->previousSamplingState());
399 }
400
hintForCollectGarbage()401 void V8GCController::hintForCollectGarbage()
402 {
403 V8PerIsolateData* data = V8PerIsolateData::current();
404 if (!data->shouldCollectGarbageSoon())
405 return;
406 const int longIdlePauseInMS = 1000;
407 data->clearShouldCollectGarbageSoon();
408 v8::V8::ContextDisposedNotification();
409 v8::V8::IdleNotification(longIdlePauseInMS);
410 }
411
collectGarbage(v8::Isolate * isolate)412 void V8GCController::collectGarbage(v8::Isolate* isolate)
413 {
414 v8::HandleScope handleScope(isolate);
415 v8::Local<v8::Context> context = v8::Context::New(isolate);
416 if (context.IsEmpty())
417 return;
418 v8::Context::Scope contextScope(context);
419 V8ScriptRunner::compileAndRunInternalScript(v8String(isolate, "if (gc) gc();"), isolate);
420 }
421
422 } // namespace WebCore
423