Home | History | Annotate | Download | only in v8
      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.
     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 
     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:
     99     explicit MinorGCWrapperVisitor(v8::Isolate* isolate)
    100         : m_isolate(isolate)
    101     { }
    102 
    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 
    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:
    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 
    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:
    232     explicit MajorGCWrapperVisitor(v8::Isolate* isolate, bool constructRetainedObjectInfos)
    233         : m_isolate(isolate)
    234         , m_liveRootGroupIdSet(false)
    235         , m_constructRetainedObjectInfos(constructRetainedObjectInfos)
    236     {
    237     }
    238 
    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 
    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:
    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 
    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 
    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.
    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 
    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 
    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 
    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 
    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 
    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