• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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