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