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 "V8MessagePort.h" 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/shadow/ElementShadow.h" 45 #include "core/dom/shadow/ShadowRoot.h" 46 #include "core/html/HTMLImageElement.h" 47 #include "core/platform/chromium/TraceEvent.h" 48 #include <algorithm> 49 50 namespace WebCore { 51 52 // FIXME: This should use opaque GC roots. 53 static void addReferencesForNodeWithEventListeners(v8::Isolate* isolate, Node* node, const v8::Persistent<v8::Object>& wrapper) 54 { 55 ASSERT(node->hasEventListeners()); 56 57 EventListenerIterator iterator(node); 58 while (EventListener* listener = iterator.nextListener()) { 59 if (listener->type() != EventListener::JSEventListenerType) 60 continue; 61 V8AbstractEventListener* v8listener = static_cast<V8AbstractEventListener*>(listener); 62 if (!v8listener->hasExistingListenerObject()) 63 continue; 64 65 // FIXME: update this to use the upcasting function which v8 will provide. 66 v8::Persistent<v8::Value>* value = reinterpret_cast<v8::Persistent<v8::Value>*>(&(v8listener->existingListenerObjectPersistentHandle())); 67 isolate->SetReference(wrapper, *value); 68 } 69 } 70 71 Node* V8GCController::opaqueRootForGC(Node* node, v8::Isolate*) 72 { 73 // FIXME: Remove the special handling for image elements. 74 // The same special handling is in V8GCController::gcTree(). 75 // Maybe should image elements be active DOM nodes? 76 // See https://code.google.com/p/chromium/issues/detail?id=164882 77 if (node->inDocument() || (node->hasTagName(HTMLNames::imgTag) && toHTMLImageElement(node)->hasPendingActivity())) 78 return node->document(); 79 80 if (node->isAttributeNode()) { 81 Node* ownerElement = toAttr(node)->ownerElement(); 82 if (!ownerElement) 83 return node; 84 node = ownerElement; 85 } 86 87 while (Node* parent = node->parentOrShadowHostNode()) 88 node = parent; 89 90 return node; 91 } 92 93 // Regarding a minor GC algorithm for DOM nodes, see this document: 94 // https://docs.google.com/a/google.com/presentation/d/1uifwVYGNYTZDoGLyCb7sXa7g49mWNMW2gaWvMN5NLk8/edit#slide=id.p 95 class MinorGCWrapperVisitor : public v8::PersistentHandleVisitor { 96 public: 97 explicit MinorGCWrapperVisitor(v8::Isolate* isolate) 98 : m_isolate(isolate) 99 { 100 UNUSED_PARAM(m_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 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 // The same special handling is in V8GCController::opaqueRootForGC(). 162 // Maybe should image elements be active DOM nodes? 163 // See https://code.google.com/p/chromium/issues/detail?id=164882 164 if (!node->isV8CollectableDuringMinorGC() || (node->hasTagName(HTMLNames::imgTag) && toHTMLImageElement(node)->hasPendingActivity())) { 165 // This node is not in the new space of V8. This indicates that 166 // the minor GC cannot anyway judge reachability of this DOM tree. 167 // Thus we give up traversing the DOM tree. 168 return false; 169 } 170 node->setV8CollectableDuringMinorGC(false); 171 newSpaceNodes->append(node); 172 } 173 if (ShadowRoot* shadowRoot = node->youngestShadowRoot()) { 174 if (!traverseTree(shadowRoot, newSpaceNodes)) 175 return false; 176 } else if (node->isShadowRoot()) { 177 if (ShadowRoot* shadowRoot = toShadowRoot(node)->olderShadowRoot()) { 178 if (!traverseTree(shadowRoot, newSpaceNodes)) 179 return false; 180 } 181 } 182 } 183 return true; 184 } 185 186 void gcTree(v8::Isolate* isolate, Node* startNode) 187 { 188 Vector<Node*, initialNodeVectorSize> newSpaceNodes; 189 190 Node* node = startNode; 191 while (node->parentOrShadowHostNode()) 192 node = node->parentOrShadowHostNode(); 193 194 if (!traverseTree(node, &newSpaceNodes)) 195 return; 196 197 // We completed the DOM tree traversal. All wrappers in the DOM tree are 198 // stored in newSpaceNodes and are expected to exist in the new space of V8. 199 // We report those wrappers to V8 as an object group. 200 Node** nodeIterator = newSpaceNodes.begin(); 201 Node** const nodeIteratorEnd = newSpaceNodes.end(); 202 if (nodeIterator == nodeIteratorEnd) 203 return; 204 v8::UniqueId id(reinterpret_cast<intptr_t>((*nodeIterator)->unsafePersistent().value())); 205 for (; nodeIterator != nodeIteratorEnd; ++nodeIterator) { 206 // This is safe because we know that GC won't happen before we 207 // dispose the UnsafePersistent (we're just preparing a GC). Though, 208 // we need to keep the UnsafePersistent alive until we're done with 209 // v8::Persistent. 210 UnsafePersistent<v8::Object> unsafeWrapper = (*nodeIterator)->unsafePersistent(); 211 v8::Persistent<v8::Object>* wrapper = unsafeWrapper.persistent(); 212 wrapper->MarkPartiallyDependent(isolate); 213 // FIXME: update this to use the upcasting function which v8 will provide 214 v8::Persistent<v8::Value>* value = reinterpret_cast<v8::Persistent<v8::Value>*>(wrapper); 215 isolate->SetObjectGroupId(*value, id); 216 } 217 } 218 219 Vector<Node*> m_nodesInNewSpace; 220 v8::Isolate* m_isolate; 221 }; 222 223 class MajorGCWrapperVisitor : public v8::PersistentHandleVisitor { 224 public: 225 explicit MajorGCWrapperVisitor(v8::Isolate* isolate, bool constructRetainedObjectInfos) 226 : m_isolate(isolate) 227 , m_liveRootGroupIdSet(false) 228 , m_constructRetainedObjectInfos(constructRetainedObjectInfos) 229 { 230 } 231 232 virtual void VisitPersistentHandle(v8::Persistent<v8::Value>* value, uint16_t classId) OVERRIDE 233 { 234 // Casting to a Handle is safe here, since the Persistent cannot get GCd 235 // during the GC prologue. 236 ASSERT((*reinterpret_cast<v8::Handle<v8::Value>*>(value))->IsObject()); 237 238 if (classId != v8DOMNodeClassId && classId != v8DOMObjectClassId) 239 return; 240 241 v8::Handle<v8::Object>* wrapper = reinterpret_cast<v8::Handle<v8::Object>*>(value); 242 243 ASSERT(V8DOMWrapper::maybeDOMWrapper(*wrapper)); 244 245 if (value->IsIndependent(m_isolate)) 246 return; 247 248 WrapperTypeInfo* type = toWrapperTypeInfo(*wrapper); 249 void* object = toNative(*wrapper); 250 251 if (V8MessagePort::info.equals(type)) { 252 // Mark each port as in-use if it's entangled. For simplicity's sake, 253 // we assume all ports are remotely entangled, since the Chromium port 254 // implementation can't tell the difference. 255 MessagePort* port = static_cast<MessagePort*>(object); 256 if (port->isEntangled() || port->hasPendingActivity()) 257 m_isolate->SetObjectGroupId(*value, liveRootId()); 258 } else if (V8MutationObserver::info.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 UNUSED_PARAM(m_isolate); 274 ASSERT(V8Node::HasInstanceInAnyWorld(*wrapper, m_isolate)); 275 ASSERT(!value->IsIndependent(m_isolate)); 276 277 Node* node = static_cast<Node*>(object); 278 279 if (node->hasEventListeners()) 280 addReferencesForNodeWithEventListeners(m_isolate, node, v8::Persistent<v8::Object>::Cast(*value)); 281 Node* root = V8GCController::opaqueRootForGC(node, m_isolate); 282 m_isolate->SetObjectGroupId(*value, v8::UniqueId(reinterpret_cast<intptr_t>(root))); 283 if (m_constructRetainedObjectInfos) 284 m_groupsWhichNeedRetainerInfo.append(root); 285 } else if (classId == v8DOMObjectClassId) { 286 ASSERT(!value->IsIndependent(m_isolate)); 287 void* root = type->opaqueRootForGC(object, m_isolate); 288 m_isolate->SetObjectGroupId(*value, v8::UniqueId(reinterpret_cast<intptr_t>(root))); 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 } 309 310 private: 311 v8::UniqueId liveRootId() 312 { 313 const v8::Persistent<v8::Value>& liveRoot = V8PerIsolateData::from(m_isolate)->ensureLiveRoot(); 314 const intptr_t* idPointer = reinterpret_cast<const intptr_t*>(&liveRoot); 315 v8::UniqueId id(*idPointer); 316 if (!m_liveRootGroupIdSet) { 317 m_isolate->SetObjectGroupId(liveRoot, id); 318 m_liveRootGroupIdSet = true; 319 } 320 return id; 321 } 322 323 v8::Isolate* m_isolate; 324 Vector<Node*> m_groupsWhichNeedRetainerInfo; 325 bool m_liveRootGroupIdSet; 326 bool m_constructRetainedObjectInfos; 327 }; 328 329 void V8GCController::gcPrologue(v8::GCType type, v8::GCCallbackFlags flags) 330 { 331 // FIXME: It would be nice if the GC callbacks passed the Isolate directly.... 332 v8::Isolate* isolate = v8::Isolate::GetCurrent(); 333 if (type == v8::kGCTypeScavenge) 334 minorGCPrologue(isolate); 335 else if (type == v8::kGCTypeMarkSweepCompact) 336 majorGCPrologue(flags & v8::kGCCallbackFlagConstructRetainedObjectInfos, isolate); 337 } 338 339 void V8GCController::minorGCPrologue(v8::Isolate* isolate) 340 { 341 TRACE_EVENT_BEGIN0("v8", "minorGC"); 342 if (isMainThread()) { 343 { 344 TRACE_EVENT_SCOPED_SAMPLING_STATE("Blink", "MinorGC"); 345 v8::HandleScope scope(isolate); 346 MinorGCWrapperVisitor visitor(isolate); 347 v8::V8::VisitHandlesForPartialDependence(isolate, &visitor); 348 visitor.notifyFinished(); 349 } 350 V8PerIsolateData::from(isolate)->setPreviousSamplingState(TRACE_EVENT_GET_SAMPLING_STATE()); 351 TRACE_EVENT_SET_SAMPLING_STATE("V8", "MinorGC"); 352 } 353 } 354 355 // Create object groups for DOM tree nodes. 356 void V8GCController::majorGCPrologue(bool constructRetainedObjectInfos, v8::Isolate* isolate) 357 { 358 v8::HandleScope scope(isolate); 359 TRACE_EVENT_BEGIN0("v8", "majorGC"); 360 if (isMainThread()) { 361 { 362 TRACE_EVENT_SCOPED_SAMPLING_STATE("Blink", "MajorGC"); 363 MajorGCWrapperVisitor visitor(isolate, constructRetainedObjectInfos); 364 v8::V8::VisitHandlesWithClassIds(&visitor); 365 visitor.notifyFinished(); 366 } 367 V8PerIsolateData::from(isolate)->setPreviousSamplingState(TRACE_EVENT_GET_SAMPLING_STATE()); 368 TRACE_EVENT_SET_SAMPLING_STATE("V8", "MajorGC"); 369 } else { 370 MajorGCWrapperVisitor visitor(isolate, constructRetainedObjectInfos); 371 v8::V8::VisitHandlesWithClassIds(&visitor); 372 visitor.notifyFinished(); 373 } 374 } 375 376 void V8GCController::gcEpilogue(v8::GCType type, v8::GCCallbackFlags flags) 377 { 378 // FIXME: It would be nice if the GC callbacks passed the Isolate directly.... 379 v8::Isolate* isolate = v8::Isolate::GetCurrent(); 380 if (type == v8::kGCTypeScavenge) 381 minorGCEpilogue(isolate); 382 else if (type == v8::kGCTypeMarkSweepCompact) 383 majorGCEpilogue(isolate); 384 } 385 386 void V8GCController::minorGCEpilogue(v8::Isolate* isolate) 387 { 388 TRACE_EVENT_END0("v8", "minorGC"); 389 if (isMainThread()) 390 TRACE_EVENT_SET_NONCONST_SAMPLING_STATE(V8PerIsolateData::from(isolate)->previousSamplingState()); 391 } 392 393 void V8GCController::majorGCEpilogue(v8::Isolate* isolate) 394 { 395 v8::HandleScope scope(isolate); 396 397 TRACE_EVENT_END0("v8", "majorGC"); 398 if (isMainThread()) 399 TRACE_EVENT_SET_NONCONST_SAMPLING_STATE(V8PerIsolateData::from(isolate)->previousSamplingState()); 400 } 401 402 void V8GCController::hintForCollectGarbage() 403 { 404 V8PerIsolateData* data = V8PerIsolateData::current(); 405 if (!data->shouldCollectGarbageSoon()) 406 return; 407 const int longIdlePauseInMS = 1000; 408 data->clearShouldCollectGarbageSoon(); 409 v8::V8::ContextDisposedNotification(); 410 v8::V8::IdleNotification(longIdlePauseInMS); 411 } 412 413 void V8GCController::collectGarbage(v8::Isolate* isolate) 414 { 415 v8::HandleScope handleScope(isolate); 416 v8::Local<v8::Context> context = v8::Context::New(isolate); 417 if (context.IsEmpty()) 418 return; 419 v8::Context::Scope contextScope(context); 420 V8ScriptRunner::compileAndRunInternalScript(v8String("if (gc) gc();", isolate), isolate); 421 } 422 423 } // namespace WebCore 424