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 "V8GCController.h" 33 34 #include "ActiveDOMObject.h" 35 #include "Attr.h" 36 #include "DOMDataStore.h" 37 #include "DOMImplementation.h" 38 #include "HTMLImageElement.h" 39 #include "HTMLNames.h" 40 #include "MessagePort.h" 41 #include "PlatformBridge.h" 42 #include "RetainedDOMInfo.h" 43 #include "RetainedObjectInfo.h" 44 #include "V8Binding.h" 45 #include "V8CSSRule.h" 46 #include "V8CSSRuleList.h" 47 #include "V8CSSStyleDeclaration.h" 48 #include "V8DOMImplementation.h" 49 #include "V8MessagePort.h" 50 #include "V8StyleSheet.h" 51 #include "V8StyleSheetList.h" 52 #include "WrapperTypeInfo.h" 53 54 #include <algorithm> 55 #include <utility> 56 #include <v8-debug.h> 57 #include <wtf/HashMap.h> 58 #include <wtf/StdLibExtras.h> 59 #include <wtf/UnusedParam.h> 60 61 namespace WebCore { 62 63 #ifndef NDEBUG 64 // Keeps track of global handles created (not JS wrappers 65 // of DOM objects). Often these global handles are source 66 // of leaks. 67 // 68 // If you want to let a C++ object hold a persistent handle 69 // to a JS object, you should register the handle here to 70 // keep track of leaks. 71 // 72 // When creating a persistent handle, call: 73 // 74 // #ifndef NDEBUG 75 // V8GCController::registerGlobalHandle(type, host, handle); 76 // #endif 77 // 78 // When releasing the handle, call: 79 // 80 // #ifndef NDEBUG 81 // V8GCController::unregisterGlobalHandle(type, host, handle); 82 // #endif 83 // 84 typedef HashMap<v8::Value*, GlobalHandleInfo*> GlobalHandleMap; 85 86 static GlobalHandleMap& globalHandleMap() 87 { 88 DEFINE_STATIC_LOCAL(GlobalHandleMap, staticGlobalHandleMap, ()); 89 return staticGlobalHandleMap; 90 } 91 92 // The function is the place to set the break point to inspect 93 // live global handles. Leaks are often come from leaked global handles. 94 static void enumerateGlobalHandles() 95 { 96 for (GlobalHandleMap::iterator it = globalHandleMap().begin(), end = globalHandleMap().end(); it != end; ++it) { 97 GlobalHandleInfo* info = it->second; 98 UNUSED_PARAM(info); 99 v8::Value* handle = it->first; 100 UNUSED_PARAM(handle); 101 } 102 } 103 104 void V8GCController::registerGlobalHandle(GlobalHandleType type, void* host, v8::Persistent<v8::Value> handle) 105 { 106 ASSERT(!globalHandleMap().contains(*handle)); 107 globalHandleMap().set(*handle, new GlobalHandleInfo(host, type)); 108 } 109 110 void V8GCController::unregisterGlobalHandle(void* host, v8::Persistent<v8::Value> handle) 111 { 112 ASSERT(globalHandleMap().contains(*handle)); 113 GlobalHandleInfo* info = globalHandleMap().take(*handle); 114 ASSERT(info->m_host == host); 115 delete info; 116 } 117 #endif // ifndef NDEBUG 118 119 typedef HashMap<Node*, v8::Object*> DOMNodeMap; 120 typedef HashMap<void*, v8::Object*> DOMObjectMap; 121 122 #ifndef NDEBUG 123 124 class DOMObjectVisitor : public DOMWrapperMap<void>::Visitor { 125 public: 126 void visitDOMWrapper(DOMDataStore* store, void* object, v8::Persistent<v8::Object> wrapper) 127 { 128 WrapperTypeInfo* type = V8DOMWrapper::domWrapperType(wrapper); 129 UNUSED_PARAM(type); 130 UNUSED_PARAM(object); 131 } 132 }; 133 134 class EnsureWeakDOMNodeVisitor : public DOMWrapperMap<Node>::Visitor { 135 public: 136 void visitDOMWrapper(DOMDataStore* store, Node* object, v8::Persistent<v8::Object> wrapper) 137 { 138 UNUSED_PARAM(object); 139 ASSERT(wrapper.IsWeak()); 140 } 141 }; 142 143 #endif // NDEBUG 144 145 class GCPrologueVisitor : public DOMWrapperMap<void>::Visitor { 146 public: 147 void visitDOMWrapper(DOMDataStore* store, void* object, v8::Persistent<v8::Object> wrapper) 148 { 149 WrapperTypeInfo* typeInfo = V8DOMWrapper::domWrapperType(wrapper); 150 151 // Additional handling of message port ensuring that entangled ports also 152 // have their wrappers entangled. This should ideally be handled when the 153 // ports are actually entangled in MessagePort::entangle, but to avoid 154 // forking MessagePort.* this is postponed to GC time. Having this postponed 155 // has the drawback that the wrappers are "entangled/unentangled" for each 156 // GC even though their entaglement most likely is still the same. 157 if (V8MessagePort::info.equals(typeInfo)) { 158 // Mark each port as in-use if it's entangled. For simplicity's sake, we assume all ports are remotely entangled, 159 // since the Chromium port implementation can't tell the difference. 160 MessagePort* port1 = static_cast<MessagePort*>(object); 161 if (port1->isEntangled() || port1->hasPendingActivity()) 162 wrapper.ClearWeak(); 163 } else { 164 ActiveDOMObject* activeDOMObject = typeInfo->toActiveDOMObject(wrapper); 165 if (activeDOMObject && activeDOMObject->hasPendingActivity()) 166 wrapper.ClearWeak(); 167 } 168 } 169 }; 170 171 // Implements v8::RetainedObjectInfo. 172 class UnspecifiedGroup : public RetainedObjectInfo { 173 public: 174 explicit UnspecifiedGroup(void* object) 175 : m_object(object) 176 { 177 ASSERT(m_object); 178 } 179 180 virtual void Dispose() { delete this; } 181 182 virtual bool IsEquivalent(v8::RetainedObjectInfo* other) 183 { 184 ASSERT(other); 185 return other == this || static_cast<WebCore::RetainedObjectInfo*>(other)->GetEquivalenceClass() == this->GetEquivalenceClass(); 186 } 187 188 virtual intptr_t GetHash() 189 { 190 return reinterpret_cast<intptr_t>(m_object); 191 } 192 193 virtual const char* GetLabel() 194 { 195 return "Object group"; 196 } 197 198 virtual intptr_t GetEquivalenceClass() 199 { 200 return reinterpret_cast<intptr_t>(m_object); 201 } 202 203 private: 204 void* m_object; 205 }; 206 207 class GroupId { 208 public: 209 GroupId() : m_type(NullType), m_groupId(0) {} 210 GroupId(Node* node) : m_type(NodeType), m_node(node) {} 211 GroupId(void* other) : m_type(OtherType), m_other(other) {} 212 bool operator!() const { return m_type == NullType; } 213 uintptr_t groupId() const { return m_groupId; } 214 RetainedObjectInfo* createRetainedObjectInfo() const 215 { 216 switch (m_type) { 217 case NullType: 218 return 0; 219 case NodeType: 220 return new RetainedDOMInfo(m_node); 221 case OtherType: 222 return new UnspecifiedGroup(m_other); 223 default: 224 return 0; 225 } 226 } 227 228 private: 229 enum Type { 230 NullType, 231 NodeType, 232 OtherType 233 }; 234 Type m_type; 235 union { 236 uintptr_t m_groupId; 237 Node* m_node; 238 void* m_other; 239 }; 240 }; 241 242 class GrouperItem { 243 public: 244 GrouperItem(GroupId groupId, v8::Persistent<v8::Object> wrapper) : m_groupId(groupId), m_wrapper(wrapper) {} 245 uintptr_t groupId() const { return m_groupId.groupId(); } 246 RetainedObjectInfo* createRetainedObjectInfo() const { return m_groupId.createRetainedObjectInfo(); } 247 v8::Persistent<v8::Object> wrapper() const { return m_wrapper; } 248 249 private: 250 GroupId m_groupId; 251 v8::Persistent<v8::Object> m_wrapper; 252 }; 253 254 bool operator<(const GrouperItem& a, const GrouperItem& b) 255 { 256 return a.groupId() < b.groupId(); 257 } 258 259 typedef Vector<GrouperItem> GrouperList; 260 261 // If the node is in document, put it in the ownerDocument's object group. 262 // 263 // If an image element was created by JavaScript "new Image", 264 // it is not in a document. However, if the load event has not 265 // been fired (still onloading), it is treated as in the document. 266 // 267 // Otherwise, the node is put in an object group identified by the root 268 // element of the tree to which it belongs. 269 static GroupId calculateGroupId(Node* node) 270 { 271 if (node->inDocument() || (node->hasTagName(HTMLNames::imgTag) && !static_cast<HTMLImageElement*>(node)->haveFiredLoadEvent())) 272 return GroupId(node->document()); 273 274 Node* root = node; 275 if (node->isAttributeNode()) { 276 root = static_cast<Attr*>(node)->ownerElement(); 277 // If the attribute has no element, no need to put it in the group, 278 // because it'll always be a group of 1. 279 if (!root) 280 return GroupId(); 281 } else { 282 while (Node* parent = root->parentNode()) 283 root = parent; 284 } 285 286 return GroupId(root); 287 } 288 289 static GroupId calculateGroupId(StyleBase* styleBase) 290 { 291 ASSERT(styleBase); 292 StyleBase* current = styleBase; 293 StyleSheet* styleSheet = 0; 294 while (true) { 295 // Special case: CSSStyleDeclarations might be either inline and in this case 296 // we need to group them with their node or regular ones. 297 if (current->isMutableStyleDeclaration()) { 298 CSSMutableStyleDeclaration* cssMutableStyleDeclaration = static_cast<CSSMutableStyleDeclaration*>(current); 299 if (cssMutableStyleDeclaration->isInlineStyleDeclaration()) { 300 ASSERT(cssMutableStyleDeclaration->parent()->isStyleSheet()); 301 return calculateGroupId(cssMutableStyleDeclaration->node()); 302 } 303 // Either we have no parent, or this parent is a CSSRule. 304 ASSERT(cssMutableStyleDeclaration->parent() == cssMutableStyleDeclaration->parentRule()); 305 } 306 307 if (current->isStyleSheet()) 308 styleSheet = static_cast<StyleSheet*>(current); 309 310 StyleBase* parent = current->parent(); 311 if (!parent) 312 break; 313 current = parent; 314 } 315 316 if (styleSheet) { 317 if (Node* ownerNode = styleSheet->ownerNode()) 318 return calculateGroupId(ownerNode); 319 return GroupId(styleSheet); 320 } 321 322 return GroupId(current); 323 } 324 325 class GrouperVisitor : public DOMWrapperMap<Node>::Visitor, public DOMWrapperMap<void>::Visitor { 326 public: 327 void visitDOMWrapper(DOMDataStore* store, Node* node, v8::Persistent<v8::Object> wrapper) 328 { 329 GroupId groupId = calculateGroupId(node); 330 if (!groupId) 331 return; 332 m_grouper.append(GrouperItem(groupId, wrapper)); 333 } 334 335 void visitDOMWrapper(DOMDataStore* store, void* object, v8::Persistent<v8::Object> wrapper) 336 { 337 WrapperTypeInfo* typeInfo = V8DOMWrapper::domWrapperType(wrapper); 338 339 if (typeInfo->isSubclass(&V8StyleSheetList::info)) { 340 StyleSheetList* styleSheetList = static_cast<StyleSheetList*>(object); 341 GroupId groupId(styleSheetList); 342 if (Document* document = styleSheetList->document()) 343 groupId = GroupId(document); 344 m_grouper.append(GrouperItem(groupId, wrapper)); 345 346 } else if (typeInfo->isSubclass(&V8DOMImplementation::info)) { 347 DOMImplementation* domImplementation = static_cast<DOMImplementation*>(object); 348 GroupId groupId(domImplementation); 349 if (Document* document = domImplementation->ownerDocument()) 350 groupId = GroupId(document); 351 m_grouper.append(GrouperItem(groupId, wrapper)); 352 353 } else if (typeInfo->isSubclass(&V8StyleSheet::info) || typeInfo->isSubclass(&V8CSSRule::info)) { 354 m_grouper.append(GrouperItem(calculateGroupId(static_cast<StyleBase*>(object)), wrapper)); 355 356 } else if (typeInfo->isSubclass(&V8CSSStyleDeclaration::info)) { 357 CSSStyleDeclaration* cssStyleDeclaration = static_cast<CSSStyleDeclaration*>(object); 358 359 GroupId groupId = calculateGroupId(cssStyleDeclaration); 360 m_grouper.append(GrouperItem(groupId, wrapper)); 361 362 // Keep alive "dirty" primitive values (i.e. the ones that 363 // have user-added properties) by creating implicit 364 // references between the style declaration and the values 365 // in it. 366 if (cssStyleDeclaration->isMutableStyleDeclaration()) { 367 CSSMutableStyleDeclaration* cssMutableStyleDeclaration = static_cast<CSSMutableStyleDeclaration*>(cssStyleDeclaration); 368 Vector<v8::Persistent<v8::Value> > values; 369 values.reserveCapacity(cssMutableStyleDeclaration->length()); 370 CSSMutableStyleDeclaration::const_iterator end = cssMutableStyleDeclaration->end(); 371 for (CSSMutableStyleDeclaration::const_iterator it = cssMutableStyleDeclaration->begin(); it != end; ++it) { 372 v8::Persistent<v8::Object> value = store->domObjectMap().get(it->value()); 373 if (!value.IsEmpty() && value->IsDirty()) 374 values.append(value); 375 } 376 if (!values.isEmpty()) 377 v8::V8::AddImplicitReferences(wrapper, values.data(), values.size()); 378 } 379 380 } else if (typeInfo->isSubclass(&V8CSSRuleList::info)) { 381 CSSRuleList* cssRuleList = static_cast<CSSRuleList*>(object); 382 GroupId groupId(cssRuleList); 383 StyleList* styleList = cssRuleList->styleList(); 384 if (styleList) 385 groupId = calculateGroupId(styleList); 386 m_grouper.append(GrouperItem(groupId, wrapper)); 387 } 388 } 389 390 void applyGrouping() 391 { 392 // Group by sorting by the group id. 393 std::sort(m_grouper.begin(), m_grouper.end()); 394 395 for (size_t i = 0; i < m_grouper.size(); ) { 396 // Seek to the next key (or the end of the list). 397 size_t nextKeyIndex = m_grouper.size(); 398 for (size_t j = i; j < m_grouper.size(); ++j) { 399 if (m_grouper[i].groupId() != m_grouper[j].groupId()) { 400 nextKeyIndex = j; 401 break; 402 } 403 } 404 405 ASSERT(nextKeyIndex > i); 406 407 // We only care about a group if it has more than one object. If it only 408 // has one object, it has nothing else that needs to be kept alive. 409 if (nextKeyIndex - i <= 1) { 410 i = nextKeyIndex; 411 continue; 412 } 413 414 size_t rootIndex = i; 415 416 Vector<v8::Persistent<v8::Value> > group; 417 group.reserveCapacity(nextKeyIndex - i); 418 for (; i < nextKeyIndex; ++i) { 419 v8::Persistent<v8::Value> wrapper = m_grouper[i].wrapper(); 420 if (!wrapper.IsEmpty()) 421 group.append(wrapper); 422 } 423 424 if (group.size() > 1) 425 v8::V8::AddObjectGroup(&group[0], group.size(), m_grouper[rootIndex].createRetainedObjectInfo()); 426 427 ASSERT(i == nextKeyIndex); 428 } 429 } 430 431 private: 432 GrouperList m_grouper; 433 }; 434 435 // Create object groups for DOM tree nodes. 436 void V8GCController::gcPrologue() 437 { 438 v8::HandleScope scope; 439 440 #ifndef NDEBUG 441 DOMObjectVisitor domObjectVisitor; 442 visitDOMObjectsInCurrentThread(&domObjectVisitor); 443 #endif 444 445 // Run through all objects with possible pending activity making their 446 // wrappers non weak if there is pending activity. 447 GCPrologueVisitor prologueVisitor; 448 visitActiveDOMObjectsInCurrentThread(&prologueVisitor); 449 450 // Create object groups. 451 GrouperVisitor grouperVisitor; 452 visitDOMNodesInCurrentThread(&grouperVisitor); 453 visitDOMObjectsInCurrentThread(&grouperVisitor); 454 grouperVisitor.applyGrouping(); 455 456 // Clean single element cache for string conversions. 457 lastStringImpl = 0; 458 lastV8String.Clear(); 459 } 460 461 class GCEpilogueVisitor : public DOMWrapperMap<void>::Visitor { 462 public: 463 void visitDOMWrapper(DOMDataStore* store, void* object, v8::Persistent<v8::Object> wrapper) 464 { 465 WrapperTypeInfo* typeInfo = V8DOMWrapper::domWrapperType(wrapper); 466 if (V8MessagePort::info.equals(typeInfo)) { 467 MessagePort* port1 = static_cast<MessagePort*>(object); 468 // We marked this port as reachable in GCPrologueVisitor. Undo this now since the 469 // port could be not reachable in the future if it gets disentangled (and also 470 // GCPrologueVisitor expects to see all handles marked as weak). 471 if ((!wrapper.IsWeak() && !wrapper.IsNearDeath()) || port1->hasPendingActivity()) 472 wrapper.MakeWeak(port1, &DOMDataStore::weakActiveDOMObjectCallback); 473 } else { 474 ActiveDOMObject* activeDOMObject = typeInfo->toActiveDOMObject(wrapper); 475 if (activeDOMObject && activeDOMObject->hasPendingActivity()) { 476 ASSERT(!wrapper.IsWeak()); 477 // NOTE: To re-enable weak status of the active object we use 478 // |object| from the map and not |activeDOMObject|. The latter 479 // may be a different pointer (in case ActiveDOMObject is not 480 // the main base class of the object's class) and pointer 481 // identity is required by DOM map functions. 482 wrapper.MakeWeak(object, &DOMDataStore::weakActiveDOMObjectCallback); 483 } 484 } 485 } 486 }; 487 488 int V8GCController::workingSetEstimateMB = 0; 489 490 namespace { 491 492 int getMemoryUsageInMB() 493 { 494 #if PLATFORM(CHROMIUM) || PLATFORM(ANDROID) 495 return PlatformBridge::memoryUsageMB(); 496 #else 497 return 0; 498 #endif 499 } 500 501 int getActualMemoryUsageInMB() 502 { 503 #if PLATFORM(CHROMIUM) || PLATFORM(ANDROID) 504 return PlatformBridge::actualMemoryUsageMB(); 505 #else 506 return 0; 507 #endif 508 } 509 510 } // anonymous namespace 511 512 void V8GCController::gcEpilogue() 513 { 514 v8::HandleScope scope; 515 516 // Run through all objects with pending activity making their wrappers weak 517 // again. 518 GCEpilogueVisitor epilogueVisitor; 519 visitActiveDOMObjectsInCurrentThread(&epilogueVisitor); 520 521 workingSetEstimateMB = getActualMemoryUsageInMB(); 522 523 #ifndef NDEBUG 524 // Check all survivals are weak. 525 DOMObjectVisitor domObjectVisitor; 526 visitDOMObjectsInCurrentThread(&domObjectVisitor); 527 528 EnsureWeakDOMNodeVisitor weakDOMNodeVisitor; 529 visitDOMNodesInCurrentThread(&weakDOMNodeVisitor); 530 531 enumerateGlobalHandles(); 532 #endif 533 } 534 535 void V8GCController::checkMemoryUsage() 536 { 537 #if PLATFORM(CHROMIUM) || PLATFORM(QT) && !OS(SYMBIAN) 538 // These values are appropriate for Chromium only. 539 const int lowUsageMB = 256; // If memory usage is below this threshold, do not bother forcing GC. 540 const int highUsageMB = 1024; // If memory usage is above this threshold, force GC more aggresively. 541 const int highUsageDeltaMB = 128; // Delta of memory usage growth (vs. last workingSetEstimateMB) to force GC when memory usage is high. 542 #elif PLATFORM(ANDROID) 543 // Query the PlatformBridge for memory thresholds as these vary device to device. 544 static const int lowUsageMB = PlatformBridge::lowMemoryUsageMB(); 545 static const int highUsageMB = PlatformBridge::highMemoryUsageMB(); 546 static const int highUsageDeltaMB = PlatformBridge::highUsageDeltaMB(); 547 #else 548 return; 549 #endif 550 551 int memoryUsageMB = getMemoryUsageInMB(); 552 if ((memoryUsageMB > lowUsageMB && memoryUsageMB > 2 * workingSetEstimateMB) || (memoryUsageMB > highUsageMB && memoryUsageMB > workingSetEstimateMB + highUsageDeltaMB)) 553 v8::V8::LowMemoryNotification(); 554 } 555 556 557 } // namespace WebCore 558