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/core/v8/V8GCController.h"
33
34#include "bindings/core/v8/RetainedDOMInfo.h"
35#include "bindings/core/v8/V8AbstractEventListener.h"
36#include "bindings/core/v8/V8Binding.h"
37#include "bindings/core/v8/V8MutationObserver.h"
38#include "bindings/core/v8/V8Node.h"
39#include "bindings/core/v8/V8ScriptRunner.h"
40#include "bindings/core/v8/WrapperTypeInfo.h"
41#include "core/dom/Attr.h"
42#include "core/dom/Document.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/html/imports/HTMLImportsController.h"
50#include "core/inspector/InspectorTraceEvents.h"
51#include "core/svg/SVGElement.h"
52#include "platform/Partitions.h"
53#include "platform/TraceEvent.h"
54#include "wtf/Vector.h"
55#include <algorithm>
56
57namespace blink {
58
59// FIXME: This should use opaque GC roots.
60static void addReferencesForNodeWithEventListeners(v8::Isolate* isolate, Node* node, const v8::Persistent<v8::Object>& wrapper)
61{
62    ASSERT(node->hasEventListeners());
63
64    EventListenerIterator iterator(node);
65    while (EventListener* listener = iterator.nextListener()) {
66        if (listener->type() != EventListener::JSEventListenerType)
67            continue;
68        V8AbstractEventListener* v8listener = static_cast<V8AbstractEventListener*>(listener);
69        if (!v8listener->hasExistingListenerObject())
70            continue;
71
72        isolate->SetReference(wrapper, v8::Persistent<v8::Value>::Cast(v8listener->existingListenerObjectPersistentHandle()));
73    }
74}
75
76Node* V8GCController::opaqueRootForGC(Node* node, v8::Isolate*)
77{
78    ASSERT(node);
79    // FIXME: Remove the special handling for image elements.
80    // The same special handling is in V8GCController::gcTree().
81    // Maybe should image elements be active DOM nodes?
82    // See https://code.google.com/p/chromium/issues/detail?id=164882
83    if (node->inDocument() || (isHTMLImageElement(*node) && toHTMLImageElement(*node).hasPendingActivity())) {
84        Document& document = node->document();
85        if (HTMLImportsController* controller = document.importsController())
86            return controller->master();
87        return &document;
88    }
89
90    if (node->isAttributeNode()) {
91        Node* ownerElement = toAttr(node)->ownerElement();
92        if (!ownerElement)
93            return node;
94        node = ownerElement;
95    }
96
97    while (Node* parent = node->parentOrShadowHostOrTemplateHostNode())
98        node = parent;
99
100    return node;
101}
102
103// Regarding a minor GC algorithm for DOM nodes, see this document:
104// https://docs.google.com/a/google.com/presentation/d/1uifwVYGNYTZDoGLyCb7sXa7g49mWNMW2gaWvMN5NLk8/edit#slide=id.p
105class MinorGCWrapperVisitor : public v8::PersistentHandleVisitor {
106public:
107    explicit MinorGCWrapperVisitor(v8::Isolate* isolate)
108        : m_isolate(isolate)
109    { }
110
111    virtual void VisitPersistentHandle(v8::Persistent<v8::Value>* value, uint16_t classId) OVERRIDE
112    {
113        // A minor DOM GC can collect only Nodes.
114        if (classId != WrapperTypeInfo::NodeClassId)
115            return;
116
117        // To make minor GC cycle time bounded, we limit the number of wrappers handled
118        // by each minor GC cycle to 10000. This value was selected so that the minor
119        // GC cycle time is bounded to 20 ms in a case where the new space size
120        // is 16 MB and it is full of wrappers (which is almost the worst case).
121        // Practically speaking, as far as I crawled real web applications,
122        // the number of wrappers handled by each minor GC cycle is at most 3000.
123        // So this limit is mainly for pathological micro benchmarks.
124        const unsigned wrappersHandledByEachMinorGC = 10000;
125        if (m_nodesInNewSpace.size() >= wrappersHandledByEachMinorGC)
126            return;
127
128        // Casting to a Handle is safe here, since the Persistent doesn't get GCd
129        // during the GC prologue.
130        ASSERT((*reinterpret_cast<v8::Handle<v8::Value>*>(value))->IsObject());
131        v8::Handle<v8::Object>* wrapper = reinterpret_cast<v8::Handle<v8::Object>*>(value);
132        ASSERT(V8DOMWrapper::isDOMWrapper(*wrapper));
133        ASSERT(V8Node::hasInstance(*wrapper, m_isolate));
134        Node* node = V8Node::toImpl(*wrapper);
135        // A minor DOM GC can handle only node wrappers in the main world.
136        // Note that node->wrapper().IsEmpty() returns true for nodes that
137        // do not have wrappers in the main world.
138        if (node->containsWrapper()) {
139            const WrapperTypeInfo* type = toWrapperTypeInfo(*wrapper);
140            ActiveDOMObject* activeDOMObject = type->toActiveDOMObject(*wrapper);
141            if (activeDOMObject && activeDOMObject->hasPendingActivity())
142                return;
143            // FIXME: Remove the special handling for image elements.
144            // The same special handling is in V8GCController::opaqueRootForGC().
145            // Maybe should image elements be active DOM nodes?
146            // See https://code.google.com/p/chromium/issues/detail?id=164882
147            if (isHTMLImageElement(*node) && toHTMLImageElement(*node).hasPendingActivity())
148                return;
149            // FIXME: Remove the special handling for SVG elements.
150            // We currently can't collect SVG Elements from minor gc, as we have
151            // strong references from SVG property tear-offs keeping context SVG element alive.
152            if (node->isSVGElement())
153                return;
154
155            m_nodesInNewSpace.append(node);
156            node->markV8CollectableDuringMinorGC();
157        }
158    }
159
160    void notifyFinished()
161    {
162        for (size_t i = 0; i < m_nodesInNewSpace.size(); i++) {
163            Node* node = m_nodesInNewSpace[i];
164            ASSERT(node->containsWrapper());
165            if (node->isV8CollectableDuringMinorGC()) { // This branch is just for performance.
166                gcTree(m_isolate, node);
167                node->clearV8CollectableDuringMinorGC();
168            }
169        }
170    }
171
172private:
173    bool traverseTree(Node* rootNode, WillBeHeapVector<RawPtrWillBeMember<Node>, initialNodeVectorSize>* partiallyDependentNodes)
174    {
175        // To make each minor GC time bounded, we might need to give up
176        // traversing at some point for a large DOM tree. That being said,
177        // I could not observe the need even in pathological test cases.
178        for (Node* node = rootNode; node; node = NodeTraversal::next(*node)) {
179            if (node->containsWrapper()) {
180                if (!node->isV8CollectableDuringMinorGC()) {
181                    // This node is not in the new space of V8. This indicates that
182                    // the minor GC cannot anyway judge reachability of this DOM tree.
183                    // Thus we give up traversing the DOM tree.
184                    return false;
185                }
186                node->clearV8CollectableDuringMinorGC();
187                partiallyDependentNodes->append(node);
188            }
189            if (ShadowRoot* shadowRoot = node->youngestShadowRoot()) {
190                if (!traverseTree(shadowRoot, partiallyDependentNodes))
191                    return false;
192            } else if (node->isShadowRoot()) {
193                if (ShadowRoot* shadowRoot = toShadowRoot(node)->olderShadowRoot()) {
194                    if (!traverseTree(shadowRoot, partiallyDependentNodes))
195                        return false;
196                }
197            }
198            // <template> has a |content| property holding a DOM fragment which we must traverse,
199            // just like we do for the shadow trees above.
200            if (isHTMLTemplateElement(*node)) {
201                if (!traverseTree(toHTMLTemplateElement(*node).content(), partiallyDependentNodes))
202                    return false;
203            }
204
205            // Document maintains the list of imported documents through HTMLImportsController.
206            if (node->isDocumentNode()) {
207                Document* document = toDocument(node);
208                HTMLImportsController* controller = document->importsController();
209                if (controller && document == controller->master()) {
210                    for (unsigned i = 0; i < controller->loaderCount(); ++i) {
211                        if (!traverseTree(controller->loaderDocumentAt(i), partiallyDependentNodes))
212                            return false;
213                    }
214                }
215            }
216        }
217        return true;
218    }
219
220    void gcTree(v8::Isolate* isolate, Node* startNode)
221    {
222        WillBeHeapVector<RawPtrWillBeMember<Node>, initialNodeVectorSize> partiallyDependentNodes;
223
224        Node* node = startNode;
225        while (Node* parent = node->parentOrShadowHostOrTemplateHostNode())
226            node = parent;
227
228        if (!traverseTree(node, &partiallyDependentNodes))
229            return;
230
231        // We completed the DOM tree traversal. All wrappers in the DOM tree are
232        // stored in partiallyDependentNodes and are expected to exist in the new space of V8.
233        // We report those wrappers to V8 as an object group.
234        if (!partiallyDependentNodes.size())
235            return;
236        Node* groupRoot = partiallyDependentNodes[0];
237        for (size_t i = 0; i < partiallyDependentNodes.size(); i++) {
238            partiallyDependentNodes[i]->markAsDependentGroup(groupRoot, isolate);
239        }
240    }
241
242    WillBePersistentHeapVector<RawPtrWillBeMember<Node> > m_nodesInNewSpace;
243    v8::Isolate* m_isolate;
244};
245
246class MajorGCWrapperVisitor : public v8::PersistentHandleVisitor {
247public:
248    explicit MajorGCWrapperVisitor(v8::Isolate* isolate, bool constructRetainedObjectInfos)
249        : m_isolate(isolate)
250        , m_liveRootGroupIdSet(false)
251        , m_constructRetainedObjectInfos(constructRetainedObjectInfos)
252    {
253    }
254
255    virtual void VisitPersistentHandle(v8::Persistent<v8::Value>* value, uint16_t classId) OVERRIDE
256    {
257        if (classId != WrapperTypeInfo::NodeClassId && classId != WrapperTypeInfo::ObjectClassId)
258            return;
259
260        // Casting to a Handle is safe here, since the Persistent doesn't get GCd
261        // during the GC prologue.
262        ASSERT((*reinterpret_cast<v8::Handle<v8::Value>*>(value))->IsObject());
263        v8::Handle<v8::Object>* wrapper = reinterpret_cast<v8::Handle<v8::Object>*>(value);
264        ASSERT(V8DOMWrapper::isDOMWrapper(*wrapper));
265
266        if (value->IsIndependent())
267            return;
268
269        const WrapperTypeInfo* type = toWrapperTypeInfo(*wrapper);
270
271        ActiveDOMObject* activeDOMObject = type->toActiveDOMObject(*wrapper);
272        if (activeDOMObject && activeDOMObject->hasPendingActivity())
273            m_isolate->SetObjectGroupId(*value, liveRootId());
274
275        if (classId == WrapperTypeInfo::NodeClassId) {
276            ASSERT(V8Node::hasInstance(*wrapper, m_isolate));
277            Node* node = V8Node::toImpl(*wrapper);
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 == WrapperTypeInfo::ObjectClassId) {
285            type->visitDOMWrapper(toScriptWrappableBase(*wrapper), v8::Persistent<v8::Object>::Cast(*value), m_isolate);
286        } else {
287            ASSERT_NOT_REACHED();
288        }
289    }
290
291    void notifyFinished()
292    {
293        if (!m_constructRetainedObjectInfos)
294            return;
295        std::sort(m_groupsWhichNeedRetainerInfo.begin(), m_groupsWhichNeedRetainerInfo.end());
296        Node* alreadyAdded = 0;
297        v8::HeapProfiler* profiler = m_isolate->GetHeapProfiler();
298        for (size_t i = 0; i < m_groupsWhichNeedRetainerInfo.size(); ++i) {
299            Node* root = m_groupsWhichNeedRetainerInfo[i];
300            if (root != alreadyAdded) {
301                profiler->SetRetainedObjectInfo(v8::UniqueId(reinterpret_cast<intptr_t>(root)), new RetainedDOMInfo(root));
302                alreadyAdded = root;
303            }
304        }
305    }
306
307private:
308    v8::UniqueId liveRootId()
309    {
310        const v8::Persistent<v8::Value>& liveRoot = V8PerIsolateData::from(m_isolate)->ensureLiveRoot();
311        const intptr_t* idPointer = reinterpret_cast<const intptr_t*>(&liveRoot);
312        v8::UniqueId id(*idPointer);
313        if (!m_liveRootGroupIdSet) {
314            m_isolate->SetObjectGroupId(liveRoot, id);
315            m_liveRootGroupIdSet = true;
316        }
317        return id;
318    }
319
320    v8::Isolate* m_isolate;
321    WillBePersistentHeapVector<RawPtrWillBeMember<Node> > m_groupsWhichNeedRetainerInfo;
322    bool m_liveRootGroupIdSet;
323    bool m_constructRetainedObjectInfos;
324};
325
326static unsigned long long usedHeapSize(v8::Isolate* isolate)
327{
328    v8::HeapStatistics heapStatistics;
329    isolate->GetHeapStatistics(&heapStatistics);
330    return heapStatistics.used_heap_size();
331}
332
333void V8GCController::gcPrologue(v8::GCType type, v8::GCCallbackFlags flags)
334{
335    // FIXME: It would be nice if the GC callbacks passed the Isolate directly....
336    v8::Isolate* isolate = v8::Isolate::GetCurrent();
337    TRACE_EVENT_BEGIN1(TRACE_DISABLED_BY_DEFAULT("devtools.timeline"), "GCEvent", "usedHeapSizeBefore", usedHeapSize(isolate));
338    if (type == v8::kGCTypeScavenge)
339        minorGCPrologue(isolate);
340    else if (type == v8::kGCTypeMarkSweepCompact)
341        majorGCPrologue(flags & v8::kGCCallbackFlagConstructRetainedObjectInfos, isolate);
342}
343
344void V8GCController::minorGCPrologue(v8::Isolate* isolate)
345{
346    TRACE_EVENT_BEGIN0("v8", "minorGC");
347    if (isMainThread()) {
348        ScriptForbiddenScope::enter();
349        {
350            TRACE_EVENT_SCOPED_SAMPLING_STATE("blink", "DOMMinorGC");
351            v8::HandleScope scope(isolate);
352            MinorGCWrapperVisitor visitor(isolate);
353            v8::V8::VisitHandlesForPartialDependence(isolate, &visitor);
354            visitor.notifyFinished();
355        }
356        V8PerIsolateData::from(isolate)->setPreviousSamplingState(TRACE_EVENT_GET_SAMPLING_STATE());
357        TRACE_EVENT_SET_SAMPLING_STATE("v8", "V8MinorGC");
358    }
359}
360
361// Create object groups for DOM tree nodes.
362void V8GCController::majorGCPrologue(bool constructRetainedObjectInfos, v8::Isolate* isolate)
363{
364    v8::HandleScope scope(isolate);
365    TRACE_EVENT_BEGIN0("v8", "majorGC");
366    if (isMainThread()) {
367        ScriptForbiddenScope::enter();
368        {
369            TRACE_EVENT_SCOPED_SAMPLING_STATE("blink", "DOMMajorGC");
370            MajorGCWrapperVisitor visitor(isolate, constructRetainedObjectInfos);
371            v8::V8::VisitHandlesWithClassIds(&visitor);
372            visitor.notifyFinished();
373        }
374        V8PerIsolateData::from(isolate)->setPreviousSamplingState(TRACE_EVENT_GET_SAMPLING_STATE());
375        TRACE_EVENT_SET_SAMPLING_STATE("v8", "V8MajorGC");
376    } else {
377        MajorGCWrapperVisitor visitor(isolate, constructRetainedObjectInfos);
378        v8::V8::VisitHandlesWithClassIds(&visitor);
379        visitor.notifyFinished();
380    }
381}
382
383void V8GCController::gcEpilogue(v8::GCType type, v8::GCCallbackFlags flags)
384{
385    // FIXME: It would be nice if the GC callbacks passed the Isolate directly....
386    v8::Isolate* isolate = v8::Isolate::GetCurrent();
387    if (type == v8::kGCTypeScavenge)
388        minorGCEpilogue(isolate);
389    else if (type == v8::kGCTypeMarkSweepCompact)
390        majorGCEpilogue(isolate);
391
392    // Forces a Blink heap garbage collection when a garbage collection
393    // was forced from V8. This is used for tests that force GCs from
394    // JavaScript to verify that objects die when expected.
395    if (flags & v8::kGCCallbackFlagForced) {
396        // This single GC is not enough for two reasons:
397        //   (1) The GC is not precise because the GC scans on-stack pointers conservatively.
398        //   (2) One GC is not enough to break a chain of persistent handles. It's possible that
399        //       some heap allocated objects own objects that contain persistent handles
400        //       pointing to other heap allocated objects. To break the chain, we need multiple GCs.
401        //
402        // Regarding (1), we force a precise GC at the end of the current event loop. So if you want
403        // to collect all garbage, you need to wait until the next event loop.
404        // Regarding (2), it would be OK in practice to trigger only one GC per gcEpilogue, because
405        // GCController.collectAll() forces 7 V8's GC.
406        Heap::collectGarbage(ThreadState::HeapPointersOnStack, ThreadState::ForcedGC);
407
408        // Forces a precise GC at the end of the current event loop.
409        Heap::setForcePreciseGCForTesting();
410    }
411
412    TRACE_EVENT_END1(TRACE_DISABLED_BY_DEFAULT("devtools.timeline"), "GCEvent", "usedHeapSizeAfter", usedHeapSize(isolate));
413    TRACE_EVENT_INSTANT1(TRACE_DISABLED_BY_DEFAULT("devtools.timeline"), "UpdateCounters", "data", InspectorUpdateCountersEvent::data());
414}
415
416void V8GCController::minorGCEpilogue(v8::Isolate* isolate)
417{
418    TRACE_EVENT_END0("v8", "minorGC");
419    if (isMainThread()) {
420        TRACE_EVENT_SET_NONCONST_SAMPLING_STATE(V8PerIsolateData::from(isolate)->previousSamplingState());
421        ScriptForbiddenScope::exit();
422    }
423}
424
425void V8GCController::majorGCEpilogue(v8::Isolate* isolate)
426{
427    TRACE_EVENT_END0("v8", "majorGC");
428    if (isMainThread()) {
429        TRACE_EVENT_SET_NONCONST_SAMPLING_STATE(V8PerIsolateData::from(isolate)->previousSamplingState());
430        ScriptForbiddenScope::exit();
431
432        // Schedule a precise GC to avoid the following scenario:
433        // (1) A DOM object X holds a v8::Persistent to a V8 object.
434        //     Assume that X is small but the V8 object is huge.
435        //     The v8::Persistent is released when X is destructed.
436        // (2) X's DOM wrapper is created.
437        // (3) The DOM wrapper becomes unreachable.
438        // (4) V8 triggers a GC. The V8's GC collects the DOM wrapper.
439        //     However, X is not collected until a next Oilpan's GC is
440        //     triggered.
441        // (5) If a lot of such DOM objects are created, we end up with
442        //     a situation where V8's GC collects the DOM wrappers but
443        //     the DOM objects are not collected forever. (Note that
444        //     Oilpan's GC is not triggered unless Oilpan's heap gets full.)
445        // (6) V8 hits OOM.
446        ThreadState::current()->setGCRequested();
447    }
448}
449
450void V8GCController::collectGarbage(v8::Isolate* isolate)
451{
452    v8::HandleScope handleScope(isolate);
453    RefPtr<ScriptState> scriptState = ScriptState::create(v8::Context::New(isolate), DOMWrapperWorld::create());
454    ScriptState::Scope scope(scriptState.get());
455    V8ScriptRunner::compileAndRunInternalScript(v8String(isolate, "if (gc) gc();"), isolate);
456    scriptState->disposePerContextData();
457}
458
459void V8GCController::reportDOMMemoryUsageToV8(v8::Isolate* isolate)
460{
461    if (!isMainThread())
462        return;
463
464    static size_t lastUsageReportedToV8 = 0;
465
466    size_t currentUsage = Partitions::currentDOMMemoryUsage();
467    int64_t diff = static_cast<int64_t>(currentUsage) - static_cast<int64_t>(lastUsageReportedToV8);
468    isolate->AdjustAmountOfExternalAllocatedMemory(diff);
469
470    lastUsageReportedToV8 = currentUsage;
471}
472
473} // namespace blink
474