blob: 14779ed5953c2c0ee2f87ea2e907b989f0b888b9 [file] [log] [blame]
/*
* Copyright (C) 2009 Google Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "config.h"
#include "bindings/v8/V8GCController.h"
#include <algorithm>
#include "V8MessagePort.h"
#include "V8MutationObserver.h"
#include "V8Node.h"
#include "V8ScriptRunner.h"
#include "bindings/v8/RetainedDOMInfo.h"
#include "bindings/v8/V8AbstractEventListener.h"
#include "bindings/v8/V8Binding.h"
#include "bindings/v8/WrapperTypeInfo.h"
#include "core/dom/Attr.h"
#include "core/dom/NodeTraversal.h"
#include "core/dom/shadow/ElementShadow.h"
#include "core/dom/shadow/ShadowRoot.h"
#include "core/html/HTMLImageElement.h"
#include "platform/TraceEvent.h"
namespace WebCore {
// FIXME: This should use opaque GC roots.
static void addReferencesForNodeWithEventListeners(v8::Isolate* isolate, Node* node, const v8::Persistent<v8::Object>& wrapper)
{
ASSERT(node->hasEventListeners());
EventListenerIterator iterator(node);
while (EventListener* listener = iterator.nextListener()) {
if (listener->type() != EventListener::JSEventListenerType)
continue;
V8AbstractEventListener* v8listener = static_cast<V8AbstractEventListener*>(listener);
if (!v8listener->hasExistingListenerObject())
continue;
// FIXME: update this to use the upcasting function which v8 will provide.
v8::Persistent<v8::Value>* value = reinterpret_cast<v8::Persistent<v8::Value>*>(&(v8listener->existingListenerObjectPersistentHandle()));
isolate->SetReference(wrapper, *value);
}
}
Node* V8GCController::opaqueRootForGC(Node* node, v8::Isolate*)
{
// FIXME: Remove the special handling for image elements.
// The same special handling is in V8GCController::gcTree().
// Maybe should image elements be active DOM nodes?
// See https://code.google.com/p/chromium/issues/detail?id=164882
if (node->inDocument() || (node->hasTagName(HTMLNames::imgTag) && toHTMLImageElement(node)->hasPendingActivity()))
return &node->document();
if (node->isAttributeNode()) {
Node* ownerElement = toAttr(node)->ownerElement();
if (!ownerElement)
return node;
node = ownerElement;
}
while (Node* parent = node->parentOrShadowHostNode())
node = parent;
return node;
}
// Regarding a minor GC algorithm for DOM nodes, see this document:
// https://docs.google.com/a/google.com/presentation/d/1uifwVYGNYTZDoGLyCb7sXa7g49mWNMW2gaWvMN5NLk8/edit#slide=id.p
class MinorGCWrapperVisitor : public v8::PersistentHandleVisitor {
public:
explicit MinorGCWrapperVisitor(v8::Isolate* isolate)
: m_isolate(isolate)
{
UNUSED_PARAM(m_isolate);
}
virtual void VisitPersistentHandle(v8::Persistent<v8::Value>* value, uint16_t classId) OVERRIDE
{
// A minor DOM GC can collect only Nodes.
if (classId != v8DOMNodeClassId)
return;
// To make minor GC cycle time bounded, we limit the number of wrappers handled
// by each minor GC cycle to 10000. This value was selected so that the minor
// GC cycle time is bounded to 20 ms in a case where the new space size
// is 16 MB and it is full of wrappers (which is almost the worst case).
// Practically speaking, as far as I crawled real web applications,
// the number of wrappers handled by each minor GC cycle is at most 3000.
// So this limit is mainly for pathological micro benchmarks.
const unsigned wrappersHandledByEachMinorGC = 10000;
if (m_nodesInNewSpace.size() >= wrappersHandledByEachMinorGC)
return;
// Casting to a Handle is safe here, since the Persistent cannot get GCd
// during the GC prologue.
ASSERT((*reinterpret_cast<v8::Handle<v8::Value>*>(value))->IsObject());
v8::Handle<v8::Object>* wrapper = reinterpret_cast<v8::Handle<v8::Object>*>(value);
ASSERT(V8DOMWrapper::maybeDOMWrapper(*wrapper));
ASSERT(V8Node::HasInstanceInAnyWorld(*wrapper, m_isolate));
Node* node = V8Node::toNative(*wrapper);
// A minor DOM GC can handle only node wrappers in the main world.
// Note that node->wrapper().IsEmpty() returns true for nodes that
// do not have wrappers in the main world.
if (node->containsWrapper()) {
const WrapperTypeInfo* type = toWrapperTypeInfo(*wrapper);
ActiveDOMObject* activeDOMObject = type->toActiveDOMObject(*wrapper);
if (activeDOMObject && activeDOMObject->hasPendingActivity())
return;
m_nodesInNewSpace.append(node);
node->setV8CollectableDuringMinorGC(true);
}
}
void notifyFinished()
{
Node** nodeIterator = m_nodesInNewSpace.begin();
Node** nodeIteratorEnd = m_nodesInNewSpace.end();
for (; nodeIterator < nodeIteratorEnd; ++nodeIterator) {
Node* node = *nodeIterator;
ASSERT(node->containsWrapper());
if (node->isV8CollectableDuringMinorGC()) // This branch is just for performance.
gcTree(m_isolate, node);
}
}
private:
bool traverseTree(Node* rootNode, Vector<Node*, initialNodeVectorSize>* newSpaceNodes)
{
// To make each minor GC time bounded, we might need to give up
// traversing at some point for a large DOM tree. That being said,
// I could not observe the need even in pathological test cases.
for (Node* node = rootNode; node; node = NodeTraversal::next(node)) {
if (node->containsWrapper()) {
// FIXME: Remove the special handling for image elements.
// The same special handling is in V8GCController::opaqueRootForGC().
// Maybe should image elements be active DOM nodes?
// See https://code.google.com/p/chromium/issues/detail?id=164882
if (!node->isV8CollectableDuringMinorGC() || (node->hasTagName(HTMLNames::imgTag) && toHTMLImageElement(node)->hasPendingActivity())) {
// This node is not in the new space of V8. This indicates that
// the minor GC cannot anyway judge reachability of this DOM tree.
// Thus we give up traversing the DOM tree.
return false;
}
node->setV8CollectableDuringMinorGC(false);
newSpaceNodes->append(node);
}
if (ShadowRoot* shadowRoot = node->youngestShadowRoot()) {
if (!traverseTree(shadowRoot, newSpaceNodes))
return false;
} else if (node->isShadowRoot()) {
if (ShadowRoot* shadowRoot = toShadowRoot(node)->olderShadowRoot()) {
if (!traverseTree(shadowRoot, newSpaceNodes))
return false;
}
}
}
return true;
}
void gcTree(v8::Isolate* isolate, Node* startNode)
{
Vector<Node*, initialNodeVectorSize> newSpaceNodes;
Node* node = startNode;
while (node->parentOrShadowHostNode())
node = node->parentOrShadowHostNode();
if (!traverseTree(node, &newSpaceNodes))
return;
// We completed the DOM tree traversal. All wrappers in the DOM tree are
// stored in newSpaceNodes and are expected to exist in the new space of V8.
// We report those wrappers to V8 as an object group.
Node** nodeIterator = newSpaceNodes.begin();
Node** const nodeIteratorEnd = newSpaceNodes.end();
if (nodeIterator == nodeIteratorEnd)
return;
v8::UniqueId id(reinterpret_cast<intptr_t>((*nodeIterator)->unsafePersistent().value()));
for (; nodeIterator != nodeIteratorEnd; ++nodeIterator) {
// This is safe because we know that GC won't happen before we
// dispose the UnsafePersistent (we're just preparing a GC). Though,
// we need to keep the UnsafePersistent alive until we're done with
// v8::Persistent.
UnsafePersistent<v8::Object> unsafeWrapper = (*nodeIterator)->unsafePersistent();
v8::Persistent<v8::Object>* wrapper = unsafeWrapper.persistent();
wrapper->MarkPartiallyDependent();
// FIXME: update this to use the upcasting function which v8 will provide
v8::Persistent<v8::Value>* value = reinterpret_cast<v8::Persistent<v8::Value>*>(wrapper);
isolate->SetObjectGroupId(*value, id);
}
}
Vector<Node*> m_nodesInNewSpace;
v8::Isolate* m_isolate;
};
class MajorGCWrapperVisitor : public v8::PersistentHandleVisitor {
public:
explicit MajorGCWrapperVisitor(v8::Isolate* isolate, bool constructRetainedObjectInfos)
: m_isolate(isolate)
, m_liveRootGroupIdSet(false)
, m_constructRetainedObjectInfos(constructRetainedObjectInfos)
{
}
virtual void VisitPersistentHandle(v8::Persistent<v8::Value>* value, uint16_t classId) OVERRIDE
{
// Casting to a Handle is safe here, since the Persistent cannot get GCd
// during the GC prologue.
ASSERT((*reinterpret_cast<v8::Handle<v8::Value>*>(value))->IsObject());
if (classId != v8DOMNodeClassId && classId != v8DOMObjectClassId)
return;
v8::Handle<v8::Object>* wrapper = reinterpret_cast<v8::Handle<v8::Object>*>(value);
ASSERT(V8DOMWrapper::maybeDOMWrapper(*wrapper));
if (value->IsIndependent())
return;
const WrapperTypeInfo* type = toWrapperTypeInfo(*wrapper);
void* object = toNative(*wrapper);
if (V8MessagePort::wrapperTypeInfo.equals(type)) {
// Mark each port as in-use if it's entangled. For simplicity's sake,
// we assume all ports are remotely entangled, since the Chromium port
// implementation can't tell the difference.
MessagePort* port = static_cast<MessagePort*>(object);
if (port->isEntangled() || port->hasPendingActivity())
m_isolate->SetObjectGroupId(*value, liveRootId());
} else if (V8MutationObserver::wrapperTypeInfo.equals(type)) {
// FIXME: Allow opaqueRootForGC to operate on multiple roots and move this logic into V8MutationObserverCustom.
MutationObserver* observer = static_cast<MutationObserver*>(object);
HashSet<Node*> observedNodes = observer->getObservedNodes();
for (HashSet<Node*>::iterator it = observedNodes.begin(); it != observedNodes.end(); ++it) {
v8::UniqueId id(reinterpret_cast<intptr_t>(V8GCController::opaqueRootForGC(*it, m_isolate)));
m_isolate->SetReferenceFromGroup(id, *value);
}
} else {
ActiveDOMObject* activeDOMObject = type->toActiveDOMObject(*wrapper);
if (activeDOMObject && activeDOMObject->hasPendingActivity())
m_isolate->SetObjectGroupId(*value, liveRootId());
}
if (classId == v8DOMNodeClassId) {
UNUSED_PARAM(m_isolate);
ASSERT(V8Node::HasInstanceInAnyWorld(*wrapper, m_isolate));
ASSERT(!value->IsIndependent());
Node* node = static_cast<Node*>(object);
if (node->hasEventListeners())
addReferencesForNodeWithEventListeners(m_isolate, node, v8::Persistent<v8::Object>::Cast(*value));
Node* root = V8GCController::opaqueRootForGC(node, m_isolate);
m_isolate->SetObjectGroupId(*value, v8::UniqueId(reinterpret_cast<intptr_t>(root)));
if (m_constructRetainedObjectInfos)
m_groupsWhichNeedRetainerInfo.append(root);
} else if (classId == v8DOMObjectClassId) {
ASSERT(!value->IsIndependent());
void* root = type->opaqueRootForGC(object, m_isolate);
m_isolate->SetObjectGroupId(*value, v8::UniqueId(reinterpret_cast<intptr_t>(root)));
} else {
ASSERT_NOT_REACHED();
}
}
void notifyFinished()
{
if (!m_constructRetainedObjectInfos)
return;
std::sort(m_groupsWhichNeedRetainerInfo.begin(), m_groupsWhichNeedRetainerInfo.end());
Node* alreadyAdded = 0;
v8::HeapProfiler* profiler = m_isolate->GetHeapProfiler();
for (size_t i = 0; i < m_groupsWhichNeedRetainerInfo.size(); ++i) {
Node* root = m_groupsWhichNeedRetainerInfo[i];
if (root != alreadyAdded) {
profiler->SetRetainedObjectInfo(v8::UniqueId(reinterpret_cast<intptr_t>(root)), new RetainedDOMInfo(root));
alreadyAdded = root;
}
}
}
private:
v8::UniqueId liveRootId()
{
const v8::Persistent<v8::Value>& liveRoot = V8PerIsolateData::from(m_isolate)->ensureLiveRoot();
const intptr_t* idPointer = reinterpret_cast<const intptr_t*>(&liveRoot);
v8::UniqueId id(*idPointer);
if (!m_liveRootGroupIdSet) {
m_isolate->SetObjectGroupId(liveRoot, id);
m_liveRootGroupIdSet = true;
}
return id;
}
v8::Isolate* m_isolate;
Vector<Node*> m_groupsWhichNeedRetainerInfo;
bool m_liveRootGroupIdSet;
bool m_constructRetainedObjectInfos;
};
void V8GCController::gcPrologue(v8::GCType type, v8::GCCallbackFlags flags)
{
// FIXME: It would be nice if the GC callbacks passed the Isolate directly....
v8::Isolate* isolate = v8::Isolate::GetCurrent();
if (type == v8::kGCTypeScavenge)
minorGCPrologue(isolate);
else if (type == v8::kGCTypeMarkSweepCompact)
majorGCPrologue(flags & v8::kGCCallbackFlagConstructRetainedObjectInfos, isolate);
}
void V8GCController::minorGCPrologue(v8::Isolate* isolate)
{
TRACE_EVENT_BEGIN0("v8", "minorGC");
if (isMainThread()) {
{
TRACE_EVENT_SCOPED_SAMPLING_STATE("Blink", "MinorGC");
v8::HandleScope scope(isolate);
MinorGCWrapperVisitor visitor(isolate);
v8::V8::VisitHandlesForPartialDependence(isolate, &visitor);
visitor.notifyFinished();
}
V8PerIsolateData::from(isolate)->setPreviousSamplingState(TRACE_EVENT_GET_SAMPLING_STATE());
TRACE_EVENT_SET_SAMPLING_STATE("V8", "MinorGC");
}
}
// Create object groups for DOM tree nodes.
void V8GCController::majorGCPrologue(bool constructRetainedObjectInfos, v8::Isolate* isolate)
{
v8::HandleScope scope(isolate);
TRACE_EVENT_BEGIN0("v8", "majorGC");
if (isMainThread()) {
{
TRACE_EVENT_SCOPED_SAMPLING_STATE("Blink", "MajorGC");
MajorGCWrapperVisitor visitor(isolate, constructRetainedObjectInfos);
v8::V8::VisitHandlesWithClassIds(&visitor);
visitor.notifyFinished();
}
V8PerIsolateData::from(isolate)->setPreviousSamplingState(TRACE_EVENT_GET_SAMPLING_STATE());
TRACE_EVENT_SET_SAMPLING_STATE("V8", "MajorGC");
} else {
MajorGCWrapperVisitor visitor(isolate, constructRetainedObjectInfos);
v8::V8::VisitHandlesWithClassIds(&visitor);
visitor.notifyFinished();
}
}
void V8GCController::gcEpilogue(v8::GCType type, v8::GCCallbackFlags flags)
{
// FIXME: It would be nice if the GC callbacks passed the Isolate directly....
v8::Isolate* isolate = v8::Isolate::GetCurrent();
if (type == v8::kGCTypeScavenge)
minorGCEpilogue(isolate);
else if (type == v8::kGCTypeMarkSweepCompact)
majorGCEpilogue(isolate);
}
void V8GCController::minorGCEpilogue(v8::Isolate* isolate)
{
TRACE_EVENT_END0("v8", "minorGC");
if (isMainThread())
TRACE_EVENT_SET_NONCONST_SAMPLING_STATE(V8PerIsolateData::from(isolate)->previousSamplingState());
}
void V8GCController::majorGCEpilogue(v8::Isolate* isolate)
{
v8::HandleScope scope(isolate);
TRACE_EVENT_END0("v8", "majorGC");
if (isMainThread())
TRACE_EVENT_SET_NONCONST_SAMPLING_STATE(V8PerIsolateData::from(isolate)->previousSamplingState());
}
void V8GCController::hintForCollectGarbage()
{
V8PerIsolateData* data = V8PerIsolateData::current();
if (!data->shouldCollectGarbageSoon())
return;
const int longIdlePauseInMS = 1000;
data->clearShouldCollectGarbageSoon();
v8::V8::ContextDisposedNotification();
v8::V8::IdleNotification(longIdlePauseInMS);
}
void V8GCController::collectGarbage(v8::Isolate* isolate)
{
v8::HandleScope handleScope(isolate);
v8::Local<v8::Context> context = v8::Context::New(isolate);
if (context.IsEmpty())
return;
v8::Context::Scope contextScope(context);
V8ScriptRunner::compileAndRunInternalScript(v8String("if (gc) gc();", isolate), isolate);
}
} // namespace WebCore