Update To 11.40.268.0
[platform/framework/web/crosswalk.git] / src / third_party / WebKit / Source / bindings / core / v8 / V8GCController.cpp
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
57 namespace blink {
58
59 // FIXME: This should use opaque GC roots.
60 static 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
76 Node* V8GCController::opaqueRootForGC(v8::Isolate*, Node* node)
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
105 class MinorGCWrapperVisitor : public v8::PersistentHandleVisitor {
106 public:
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
172 private:
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 : NodeTraversal::startsAt(rootNode)) {
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
246 class MajorGCWrapperVisitor : public v8::PersistentHandleVisitor {
247 public:
248     explicit MajorGCWrapperVisitor(v8::Isolate* isolate, bool constructRetainedObjectInfos)
249         : m_isolate(isolate)
250         , m_domObjectsWithPendingActivity(0)
251         , m_liveRootGroupIdSet(false)
252         , m_constructRetainedObjectInfos(constructRetainedObjectInfos)
253     {
254     }
255
256     virtual void VisitPersistentHandle(v8::Persistent<v8::Value>* value, uint16_t classId) override
257     {
258         if (classId != WrapperTypeInfo::NodeClassId && classId != WrapperTypeInfo::ObjectClassId)
259             return;
260
261         // Casting to a Handle is safe here, since the Persistent doesn't get GCd
262         // during the GC prologue.
263         ASSERT((*reinterpret_cast<v8::Handle<v8::Value>*>(value))->IsObject());
264         v8::Handle<v8::Object>* wrapper = reinterpret_cast<v8::Handle<v8::Object>*>(value);
265         ASSERT(V8DOMWrapper::isDOMWrapper(*wrapper));
266
267         if (value->IsIndependent())
268             return;
269
270         const WrapperTypeInfo* type = toWrapperTypeInfo(*wrapper);
271
272         ActiveDOMObject* activeDOMObject = type->toActiveDOMObject(*wrapper);
273         if (activeDOMObject && activeDOMObject->hasPendingActivity()) {
274             m_isolate->SetObjectGroupId(*value, liveRootId());
275             ++m_domObjectsWithPendingActivity;
276         }
277
278         if (classId == WrapperTypeInfo::NodeClassId) {
279             ASSERT(V8Node::hasInstance(*wrapper, m_isolate));
280             Node* node = V8Node::toImpl(*wrapper);
281             if (node->hasEventListeners())
282                 addReferencesForNodeWithEventListeners(m_isolate, node, v8::Persistent<v8::Object>::Cast(*value));
283             Node* root = V8GCController::opaqueRootForGC(m_isolate, node);
284             m_isolate->SetObjectGroupId(*value, v8::UniqueId(reinterpret_cast<intptr_t>(root)));
285             if (m_constructRetainedObjectInfos)
286                 m_groupsWhichNeedRetainerInfo.append(root);
287         } else if (classId == WrapperTypeInfo::ObjectClassId) {
288             type->visitDOMWrapper(m_isolate, toScriptWrappableBase(*wrapper), v8::Persistent<v8::Object>::Cast(*value));
289         } else {
290             ASSERT_NOT_REACHED();
291         }
292     }
293
294     void notifyFinished()
295     {
296         if (!m_constructRetainedObjectInfos)
297             return;
298         std::sort(m_groupsWhichNeedRetainerInfo.begin(), m_groupsWhichNeedRetainerInfo.end());
299         Node* alreadyAdded = 0;
300         v8::HeapProfiler* profiler = m_isolate->GetHeapProfiler();
301         for (size_t i = 0; i < m_groupsWhichNeedRetainerInfo.size(); ++i) {
302             Node* root = m_groupsWhichNeedRetainerInfo[i];
303             if (root != alreadyAdded) {
304                 profiler->SetRetainedObjectInfo(v8::UniqueId(reinterpret_cast<intptr_t>(root)), new RetainedDOMInfo(root));
305                 alreadyAdded = root;
306             }
307         }
308         if (m_liveRootGroupIdSet)
309             profiler->SetRetainedObjectInfo(liveRootId(), new ActiveDOMObjectsInfo(m_domObjectsWithPendingActivity));
310     }
311
312 private:
313     v8::UniqueId liveRootId()
314     {
315         const v8::Persistent<v8::Value>& liveRoot = V8PerIsolateData::from(m_isolate)->ensureLiveRoot();
316         const intptr_t* idPointer = reinterpret_cast<const intptr_t*>(&liveRoot);
317         v8::UniqueId id(*idPointer);
318         if (!m_liveRootGroupIdSet) {
319             m_isolate->SetObjectGroupId(liveRoot, id);
320             m_liveRootGroupIdSet = true;
321             ++m_domObjectsWithPendingActivity;
322         }
323         return id;
324     }
325
326     v8::Isolate* m_isolate;
327     WillBePersistentHeapVector<RawPtrWillBeMember<Node> > m_groupsWhichNeedRetainerInfo;
328     int m_domObjectsWithPendingActivity;
329     bool m_liveRootGroupIdSet;
330     bool m_constructRetainedObjectInfos;
331 };
332
333 static unsigned long long usedHeapSize(v8::Isolate* isolate)
334 {
335     v8::HeapStatistics heapStatistics;
336     isolate->GetHeapStatistics(&heapStatistics);
337     return heapStatistics.used_heap_size();
338 }
339
340 void V8GCController::gcPrologue(v8::GCType type, v8::GCCallbackFlags flags)
341 {
342     // FIXME: It would be nice if the GC callbacks passed the Isolate directly....
343     v8::Isolate* isolate = v8::Isolate::GetCurrent();
344     TRACE_EVENT_BEGIN1(TRACE_DISABLED_BY_DEFAULT("devtools.timeline"), "GCEvent", "usedHeapSizeBefore", usedHeapSize(isolate));
345     if (type == v8::kGCTypeScavenge)
346         minorGCPrologue(isolate);
347     else if (type == v8::kGCTypeMarkSweepCompact)
348         majorGCPrologue(isolate, flags & v8::kGCCallbackFlagConstructRetainedObjectInfos);
349 }
350
351 void V8GCController::minorGCPrologue(v8::Isolate* isolate)
352 {
353     TRACE_EVENT_BEGIN0("v8", "minorGC");
354     if (isMainThread()) {
355         ScriptForbiddenScope::enter();
356         {
357             TRACE_EVENT_SCOPED_SAMPLING_STATE("blink", "DOMMinorGC");
358             v8::HandleScope scope(isolate);
359             MinorGCWrapperVisitor visitor(isolate);
360             v8::V8::VisitHandlesForPartialDependence(isolate, &visitor);
361             visitor.notifyFinished();
362         }
363         V8PerIsolateData::from(isolate)->setPreviousSamplingState(TRACE_EVENT_GET_SAMPLING_STATE());
364         TRACE_EVENT_SET_SAMPLING_STATE("v8", "V8MinorGC");
365     }
366 }
367
368 // Create object groups for DOM tree nodes.
369 void V8GCController::majorGCPrologue(v8::Isolate* isolate, bool constructRetainedObjectInfos)
370 {
371     v8::HandleScope scope(isolate);
372     TRACE_EVENT_BEGIN0("v8", "majorGC");
373     if (isMainThread()) {
374         ScriptForbiddenScope::enter();
375         {
376             TRACE_EVENT_SCOPED_SAMPLING_STATE("blink", "DOMMajorGC");
377             MajorGCWrapperVisitor visitor(isolate, constructRetainedObjectInfos);
378             v8::V8::VisitHandlesWithClassIds(isolate, &visitor);
379             visitor.notifyFinished();
380         }
381         V8PerIsolateData::from(isolate)->setPreviousSamplingState(TRACE_EVENT_GET_SAMPLING_STATE());
382         TRACE_EVENT_SET_SAMPLING_STATE("v8", "V8MajorGC");
383     } else {
384         MajorGCWrapperVisitor visitor(isolate, constructRetainedObjectInfos);
385         v8::V8::VisitHandlesWithClassIds(isolate, &visitor);
386         visitor.notifyFinished();
387     }
388 }
389
390 void V8GCController::gcEpilogue(v8::GCType type, v8::GCCallbackFlags flags)
391 {
392     // FIXME: It would be nice if the GC callbacks passed the Isolate directly....
393     v8::Isolate* isolate = v8::Isolate::GetCurrent();
394     if (type == v8::kGCTypeScavenge)
395         minorGCEpilogue(isolate);
396     else if (type == v8::kGCTypeMarkSweepCompact)
397         majorGCEpilogue(isolate);
398
399     // Forces a Blink heap garbage collection when a garbage collection
400     // was forced from V8. This is used for tests that force GCs from
401     // JavaScript to verify that objects die when expected.
402     if (flags & v8::kGCCallbackFlagForced) {
403         // This single GC is not enough for two reasons:
404         //   (1) The GC is not precise because the GC scans on-stack pointers conservatively.
405         //   (2) One GC is not enough to break a chain of persistent handles. It's possible that
406         //       some heap allocated objects own objects that contain persistent handles
407         //       pointing to other heap allocated objects. To break the chain, we need multiple GCs.
408         //
409         // Regarding (1), we force a precise GC at the end of the current event loop. So if you want
410         // to collect all garbage, you need to wait until the next event loop.
411         // Regarding (2), it would be OK in practice to trigger only one GC per gcEpilogue, because
412         // GCController.collectAll() forces 7 V8's GC.
413         Heap::collectGarbage(ThreadState::HeapPointersOnStack, ThreadState::ForcedGC);
414
415         // Forces a precise GC at the end of the current event loop.
416         Heap::setForcePreciseGCForTesting();
417     }
418
419     TRACE_EVENT_END1(TRACE_DISABLED_BY_DEFAULT("devtools.timeline"), "GCEvent", "usedHeapSizeAfter", usedHeapSize(isolate));
420     TRACE_EVENT_INSTANT1(TRACE_DISABLED_BY_DEFAULT("devtools.timeline"), "UpdateCounters", "data", InspectorUpdateCountersEvent::data());
421 }
422
423 void V8GCController::minorGCEpilogue(v8::Isolate* isolate)
424 {
425     TRACE_EVENT_END0("v8", "minorGC");
426     if (isMainThread()) {
427         TRACE_EVENT_SET_NONCONST_SAMPLING_STATE(V8PerIsolateData::from(isolate)->previousSamplingState());
428         ScriptForbiddenScope::exit();
429     }
430 }
431
432 void V8GCController::majorGCEpilogue(v8::Isolate* isolate)
433 {
434     TRACE_EVENT_END0("v8", "majorGC");
435     if (isMainThread()) {
436         TRACE_EVENT_SET_NONCONST_SAMPLING_STATE(V8PerIsolateData::from(isolate)->previousSamplingState());
437         ScriptForbiddenScope::exit();
438
439         // Schedule a precise GC to avoid the following scenario:
440         // (1) A DOM object X holds a v8::Persistent to a V8 object.
441         //     Assume that X is small but the V8 object is huge.
442         //     The v8::Persistent is released when X is destructed.
443         // (2) X's DOM wrapper is created.
444         // (3) The DOM wrapper becomes unreachable.
445         // (4) V8 triggers a GC. The V8's GC collects the DOM wrapper.
446         //     However, X is not collected until a next Oilpan's GC is
447         //     triggered.
448         // (5) If a lot of such DOM objects are created, we end up with
449         //     a situation where V8's GC collects the DOM wrappers but
450         //     the DOM objects are not collected forever. (Note that
451         //     Oilpan's GC is not triggered unless Oilpan's heap gets full.)
452         // (6) V8 hits OOM.
453         ThreadState::current()->setGCRequested();
454     }
455 }
456
457 void V8GCController::collectGarbage(v8::Isolate* isolate)
458 {
459     v8::HandleScope handleScope(isolate);
460     RefPtr<ScriptState> scriptState = ScriptState::create(v8::Context::New(isolate), DOMWrapperWorld::create());
461     ScriptState::Scope scope(scriptState.get());
462     V8ScriptRunner::compileAndRunInternalScript(v8String(isolate, "if (gc) gc();"), isolate);
463     scriptState->disposePerContextData();
464 }
465
466 void V8GCController::reportDOMMemoryUsageToV8(v8::Isolate* isolate)
467 {
468     if (!isMainThread())
469         return;
470
471     static size_t lastUsageReportedToV8 = 0;
472
473     size_t currentUsage = Partitions::currentDOMMemoryUsage();
474     int64_t diff = static_cast<int64_t>(currentUsage) - static_cast<int64_t>(lastUsageReportedToV8);
475     isolate->AdjustAmountOfExternalAllocatedMemory(diff);
476
477     lastUsageReportedToV8 = currentUsage;
478 }
479
480 class DOMWrapperTracer : public v8::PersistentHandleVisitor {
481 public:
482     explicit DOMWrapperTracer(Visitor* visitor)
483         : m_visitor(visitor)
484     {
485     }
486
487     virtual void VisitPersistentHandle(v8::Persistent<v8::Value>* value, uint16_t classId) override
488     {
489         if (classId != WrapperTypeInfo::NodeClassId && classId != WrapperTypeInfo::ObjectClassId)
490             return;
491
492         // Casting to a Handle is safe here, since the Persistent doesn't get GCd
493         // during tracing.
494         ASSERT((*reinterpret_cast<v8::Handle<v8::Value>*>(value))->IsObject());
495         v8::Handle<v8::Object>* wrapper = reinterpret_cast<v8::Handle<v8::Object>*>(value);
496         ASSERT(V8DOMWrapper::isDOMWrapper(*wrapper));
497         if (m_visitor)
498             toWrapperTypeInfo(*wrapper)->trace(m_visitor, toScriptWrappableBase(*wrapper));
499     }
500
501 private:
502     Visitor* m_visitor;
503 };
504
505 void V8GCController::traceDOMWrappers(v8::Isolate* isolate, Visitor* visitor)
506 {
507     DOMWrapperTracer tracer(visitor);
508     v8::V8::VisitHandlesWithClassIds(isolate, &tracer);
509 }
510
511 } // namespace blink