| OLD | NEW |
| (Empty) |
| 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 "sky/engine/config.h" | |
| 32 #include "sky/engine/bindings/core/v8/V8GCController.h" | |
| 33 | |
| 34 #include <algorithm> | |
| 35 #include "bindings/core/v8/V8MutationObserver.h" | |
| 36 #include "bindings/core/v8/V8Node.h" | |
| 37 #include "sky/engine/bindings/core/v8/RetainedDOMInfo.h" | |
| 38 #include "sky/engine/bindings/core/v8/V8AbstractEventListener.h" | |
| 39 #include "sky/engine/bindings/core/v8/V8Binding.h" | |
| 40 #include "sky/engine/bindings/core/v8/V8ScriptRunner.h" | |
| 41 #include "sky/engine/bindings/core/v8/WrapperTypeInfo.h" | |
| 42 #include "sky/engine/core/dom/Attr.h" | |
| 43 #include "sky/engine/core/dom/Document.h" | |
| 44 #include "sky/engine/core/dom/NodeTraversal.h" | |
| 45 #include "sky/engine/core/dom/TemplateContentDocumentFragment.h" | |
| 46 #include "sky/engine/core/dom/shadow/ElementShadow.h" | |
| 47 #include "sky/engine/core/dom/shadow/ShadowRoot.h" | |
| 48 #include "sky/engine/core/html/HTMLImageElement.h" | |
| 49 #include "sky/engine/core/html/HTMLTemplateElement.h" | |
| 50 #include "sky/engine/core/html/imports/HTMLImportsController.h" | |
| 51 #include "sky/engine/platform/Partitions.h" | |
| 52 #include "sky/engine/platform/TraceEvent.h" | |
| 53 #include "sky/engine/wtf/Vector.h" | |
| 54 | |
| 55 namespace blink { | |
| 56 | |
| 57 // FIXME: This should use opaque GC roots. | |
| 58 static void addReferencesForNodeWithEventListeners(v8::Isolate* isolate, Node* n
ode, const v8::Persistent<v8::Object>& wrapper) | |
| 59 { | |
| 60 ASSERT(node->hasEventListeners()); | |
| 61 | |
| 62 EventListenerIterator iterator(node); | |
| 63 while (EventListener* listener = iterator.nextListener()) { | |
| 64 V8AbstractEventListener* v8listener = static_cast<V8AbstractEventListene
r*>(listener); | |
| 65 if (!v8listener->hasExistingListenerObject()) | |
| 66 continue; | |
| 67 | |
| 68 isolate->SetReference(wrapper, v8::Persistent<v8::Value>::Cast(v8listene
r->existingListenerObjectPersistentHandle())); | |
| 69 } | |
| 70 } | |
| 71 | |
| 72 Node* V8GCController::opaqueRootForGC(Node* node, v8::Isolate*) | |
| 73 { | |
| 74 ASSERT(node); | |
| 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() || (isHTMLImageElement(*node) && toHTMLImageElement(*
node).hasPendingActivity())) { | |
| 80 Document& document = node->document(); | |
| 81 if (HTMLImportsController* controller = document.importsController()) | |
| 82 return controller->master(); | |
| 83 return &document; | |
| 84 } | |
| 85 | |
| 86 while (Node* parent = node->parentOrShadowHostOrTemplateHostNode()) | |
| 87 node = parent; | |
| 88 | |
| 89 return node; | |
| 90 } | |
| 91 | |
| 92 // Regarding a minor GC algorithm for DOM nodes, see this document: | |
| 93 // https://docs.google.com/a/google.com/presentation/d/1uifwVYGNYTZDoGLyCb7sXa7g
49mWNMW2gaWvMN5NLk8/edit#slide=id.p | |
| 94 class MinorGCWrapperVisitor : public v8::PersistentHandleVisitor { | |
| 95 public: | |
| 96 explicit MinorGCWrapperVisitor(v8::Isolate* isolate) | |
| 97 : m_isolate(isolate) | |
| 98 { } | |
| 99 | |
| 100 virtual void VisitPersistentHandle(v8::Persistent<v8::Value>* value, uint16_
t classId) override | |
| 101 { | |
| 102 // A minor DOM GC can collect only Nodes. | |
| 103 if (classId != WrapperTypeInfo::NodeClassId) | |
| 104 return; | |
| 105 | |
| 106 // To make minor GC cycle time bounded, we limit the number of wrappers
handled | |
| 107 // by each minor GC cycle to 10000. This value was selected so that the
minor | |
| 108 // GC cycle time is bounded to 20 ms in a case where the new space size | |
| 109 // is 16 MB and it is full of wrappers (which is almost the worst case). | |
| 110 // Practically speaking, as far as I crawled real web applications, | |
| 111 // the number of wrappers handled by each minor GC cycle is at most 3000
. | |
| 112 // So this limit is mainly for pathological micro benchmarks. | |
| 113 const unsigned wrappersHandledByEachMinorGC = 10000; | |
| 114 if (m_nodesInNewSpace.size() >= wrappersHandledByEachMinorGC) | |
| 115 return; | |
| 116 | |
| 117 // Casting to a Handle is safe here, since the Persistent doesn't get GC
d | |
| 118 // during the GC prologue. | |
| 119 ASSERT((*reinterpret_cast<v8::Handle<v8::Value>*>(value))->IsObject()); | |
| 120 v8::Handle<v8::Object>* wrapper = reinterpret_cast<v8::Handle<v8::Object
>*>(value); | |
| 121 ASSERT(V8DOMWrapper::isDOMWrapper(*wrapper)); | |
| 122 ASSERT(V8Node::hasInstance(*wrapper, m_isolate)); | |
| 123 Node* node = V8Node::toNative(*wrapper); | |
| 124 // A minor DOM GC can handle only node wrappers in the main world. | |
| 125 // Note that node->wrapper().IsEmpty() returns true for nodes that | |
| 126 // do not have wrappers in the main world. | |
| 127 if (node->containsWrapper()) { | |
| 128 const WrapperTypeInfo* type = toWrapperTypeInfo(*wrapper); | |
| 129 ActiveDOMObject* activeDOMObject = type->toActiveDOMObject(*wrapper)
; | |
| 130 if (activeDOMObject && activeDOMObject->hasPendingActivity()) | |
| 131 return; | |
| 132 // FIXME: Remove the special handling for image elements. | |
| 133 // The same special handling is in V8GCController::opaqueRootForGC()
. | |
| 134 // Maybe should image elements be active DOM nodes? | |
| 135 // See https://code.google.com/p/chromium/issues/detail?id=164882 | |
| 136 if (isHTMLImageElement(*node) && toHTMLImageElement(*node).hasPendin
gActivity()) | |
| 137 return; | |
| 138 | |
| 139 m_nodesInNewSpace.append(node); | |
| 140 node->markV8CollectableDuringMinorGC(); | |
| 141 } | |
| 142 } | |
| 143 | |
| 144 void notifyFinished() | |
| 145 { | |
| 146 for (size_t i = 0; i < m_nodesInNewSpace.size(); i++) { | |
| 147 Node* node = m_nodesInNewSpace[i]; | |
| 148 ASSERT(node->containsWrapper()); | |
| 149 if (node->isV8CollectableDuringMinorGC()) { // This branch is just f
or performance. | |
| 150 gcTree(m_isolate, node); | |
| 151 node->clearV8CollectableDuringMinorGC(); | |
| 152 } | |
| 153 } | |
| 154 } | |
| 155 | |
| 156 private: | |
| 157 bool traverseTree(Node* rootNode, Vector<RawPtr<Node>, initialNodeVectorSize
>* partiallyDependentNodes) | |
| 158 { | |
| 159 // To make each minor GC time bounded, we might need to give up | |
| 160 // traversing at some point for a large DOM tree. That being said, | |
| 161 // I could not observe the need even in pathological test cases. | |
| 162 for (Node* node = rootNode; node; node = NodeTraversal::next(*node)) { | |
| 163 if (node->containsWrapper()) { | |
| 164 if (!node->isV8CollectableDuringMinorGC()) { | |
| 165 // This node is not in the new space of V8. This indicates t
hat | |
| 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->clearV8CollectableDuringMinorGC(); | |
| 171 partiallyDependentNodes->append(node); | |
| 172 } | |
| 173 if (ShadowRoot* shadowRoot = node->shadowRoot()) { | |
| 174 if (!traverseTree(shadowRoot, partiallyDependentNodes)) | |
| 175 return false; | |
| 176 } | |
| 177 // <template> has a |content| property holding a DOM fragment which
we must traverse, | |
| 178 // just like we do for the shadow trees above. | |
| 179 if (isHTMLTemplateElement(*node)) { | |
| 180 if (!traverseTree(toHTMLTemplateElement(*node).content(), partia
llyDependentNodes)) | |
| 181 return false; | |
| 182 } | |
| 183 | |
| 184 // Document maintains the list of imported documents through HTMLImp
ortsController. | |
| 185 if (node->isDocumentNode()) { | |
| 186 Document* document = toDocument(node); | |
| 187 HTMLImportsController* controller = document->importsController(
); | |
| 188 if (controller && document == controller->master()) { | |
| 189 for (unsigned i = 0; i < controller->loaderCount(); ++i) { | |
| 190 if (!traverseTree(controller->loaderDocumentAt(i), parti
allyDependentNodes)) | |
| 191 return false; | |
| 192 } | |
| 193 } | |
| 194 } | |
| 195 } | |
| 196 return true; | |
| 197 } | |
| 198 | |
| 199 void gcTree(v8::Isolate* isolate, Node* startNode) | |
| 200 { | |
| 201 Vector<RawPtr<Node>, initialNodeVectorSize> partiallyDependentNodes; | |
| 202 | |
| 203 Node* node = startNode; | |
| 204 while (Node* parent = node->parentOrShadowHostOrTemplateHostNode()) | |
| 205 node = parent; | |
| 206 | |
| 207 if (!traverseTree(node, &partiallyDependentNodes)) | |
| 208 return; | |
| 209 | |
| 210 // We completed the DOM tree traversal. All wrappers in the DOM tree are | |
| 211 // stored in partiallyDependentNodes and are expected to exist in the ne
w space of V8. | |
| 212 // We report those wrappers to V8 as an object group. | |
| 213 if (!partiallyDependentNodes.size()) | |
| 214 return; | |
| 215 Node* groupRoot = partiallyDependentNodes[0]; | |
| 216 for (size_t i = 0; i < partiallyDependentNodes.size(); i++) { | |
| 217 partiallyDependentNodes[i]->markAsDependentGroup(groupRoot, isolate)
; | |
| 218 } | |
| 219 } | |
| 220 | |
| 221 Vector<RawPtr<Node> > m_nodesInNewSpace; | |
| 222 v8::Isolate* m_isolate; | |
| 223 }; | |
| 224 | |
| 225 class MajorGCWrapperVisitor : public v8::PersistentHandleVisitor { | |
| 226 public: | |
| 227 explicit MajorGCWrapperVisitor(v8::Isolate* isolate, bool constructRetainedO
bjectInfos) | |
| 228 : m_isolate(isolate) | |
| 229 , m_liveRootGroupIdSet(false) | |
| 230 , m_constructRetainedObjectInfos(constructRetainedObjectInfos) | |
| 231 { | |
| 232 } | |
| 233 | |
| 234 virtual void VisitPersistentHandle(v8::Persistent<v8::Value>* value, uint16_
t classId) override | |
| 235 { | |
| 236 if (classId != WrapperTypeInfo::NodeClassId && classId != WrapperTypeInf
o::ObjectClassId) | |
| 237 return; | |
| 238 | |
| 239 // Casting to a Handle is safe here, since the Persistent doesn't get GC
d | |
| 240 // during the GC prologue. | |
| 241 ASSERT((*reinterpret_cast<v8::Handle<v8::Value>*>(value))->IsObject()); | |
| 242 v8::Handle<v8::Object>* wrapper = reinterpret_cast<v8::Handle<v8::Object
>*>(value); | |
| 243 ASSERT(V8DOMWrapper::isDOMWrapper(*wrapper)); | |
| 244 | |
| 245 if (value->IsIndependent()) | |
| 246 return; | |
| 247 | |
| 248 const WrapperTypeInfo* type = toWrapperTypeInfo(*wrapper); | |
| 249 | |
| 250 ActiveDOMObject* activeDOMObject = type->toActiveDOMObject(*wrapper); | |
| 251 if (activeDOMObject && activeDOMObject->hasPendingActivity()) | |
| 252 m_isolate->SetObjectGroupId(*value, liveRootId()); | |
| 253 | |
| 254 if (classId == WrapperTypeInfo::NodeClassId) { | |
| 255 ASSERT(V8Node::hasInstance(*wrapper, m_isolate)); | |
| 256 Node* node = V8Node::toNative(*wrapper); | |
| 257 if (node->hasEventListeners()) | |
| 258 addReferencesForNodeWithEventListeners(m_isolate, node, v8::Pers
istent<v8::Object>::Cast(*value)); | |
| 259 Node* root = V8GCController::opaqueRootForGC(node, m_isolate); | |
| 260 m_isolate->SetObjectGroupId(*value, v8::UniqueId(reinterpret_cast<in
tptr_t>(root))); | |
| 261 if (m_constructRetainedObjectInfos) | |
| 262 m_groupsWhichNeedRetainerInfo.append(root); | |
| 263 } else if (classId == WrapperTypeInfo::ObjectClassId) { | |
| 264 type->visitDOMWrapper(toScriptWrappableBase(*wrapper), v8::Persisten
t<v8::Object>::Cast(*value), m_isolate); | |
| 265 } else { | |
| 266 ASSERT_NOT_REACHED(); | |
| 267 } | |
| 268 } | |
| 269 | |
| 270 void notifyFinished() | |
| 271 { | |
| 272 if (!m_constructRetainedObjectInfos) | |
| 273 return; | |
| 274 std::sort(m_groupsWhichNeedRetainerInfo.begin(), m_groupsWhichNeedRetain
erInfo.end()); | |
| 275 Node* alreadyAdded = 0; | |
| 276 v8::HeapProfiler* profiler = m_isolate->GetHeapProfiler(); | |
| 277 for (size_t i = 0; i < m_groupsWhichNeedRetainerInfo.size(); ++i) { | |
| 278 Node* root = m_groupsWhichNeedRetainerInfo[i]; | |
| 279 if (root != alreadyAdded) { | |
| 280 profiler->SetRetainedObjectInfo(v8::UniqueId(reinterpret_cast<in
tptr_t>(root)), new RetainedDOMInfo(root)); | |
| 281 alreadyAdded = root; | |
| 282 } | |
| 283 } | |
| 284 } | |
| 285 | |
| 286 private: | |
| 287 v8::UniqueId liveRootId() | |
| 288 { | |
| 289 const v8::Persistent<v8::Value>& liveRoot = V8PerIsolateData::from(m_iso
late)->ensureLiveRoot(); | |
| 290 const intptr_t* idPointer = reinterpret_cast<const intptr_t*>(&liveRoot)
; | |
| 291 v8::UniqueId id(*idPointer); | |
| 292 if (!m_liveRootGroupIdSet) { | |
| 293 m_isolate->SetObjectGroupId(liveRoot, id); | |
| 294 m_liveRootGroupIdSet = true; | |
| 295 } | |
| 296 return id; | |
| 297 } | |
| 298 | |
| 299 v8::Isolate* m_isolate; | |
| 300 Vector<RawPtr<Node> > m_groupsWhichNeedRetainerInfo; | |
| 301 bool m_liveRootGroupIdSet; | |
| 302 bool m_constructRetainedObjectInfos; | |
| 303 }; | |
| 304 | |
| 305 void V8GCController::gcPrologue(v8::GCType type, v8::GCCallbackFlags flags) | |
| 306 { | |
| 307 // FIXME: It would be nice if the GC callbacks passed the Isolate directly..
.. | |
| 308 v8::Isolate* isolate = v8::Isolate::GetCurrent(); | |
| 309 if (type == v8::kGCTypeScavenge) | |
| 310 minorGCPrologue(isolate); | |
| 311 else if (type == v8::kGCTypeMarkSweepCompact) | |
| 312 majorGCPrologue(flags & v8::kGCCallbackFlagConstructRetainedObjectInfos,
isolate); | |
| 313 } | |
| 314 | |
| 315 void V8GCController::minorGCPrologue(v8::Isolate* isolate) | |
| 316 { | |
| 317 TRACE_EVENT_BEGIN0("v8", "minorGC"); | |
| 318 if (isMainThread()) { | |
| 319 ScriptForbiddenScope::enter(); | |
| 320 { | |
| 321 TRACE_EVENT_SCOPED_SAMPLING_STATE("blink", "DOMMinorGC"); | |
| 322 v8::HandleScope scope(isolate); | |
| 323 MinorGCWrapperVisitor visitor(isolate); | |
| 324 v8::V8::VisitHandlesForPartialDependence(isolate, &visitor); | |
| 325 visitor.notifyFinished(); | |
| 326 } | |
| 327 V8PerIsolateData::from(isolate)->setPreviousSamplingState(TRACE_EVENT_GE
T_SAMPLING_STATE()); | |
| 328 TRACE_EVENT_SET_SAMPLING_STATE("v8", "V8MinorGC"); | |
| 329 } | |
| 330 } | |
| 331 | |
| 332 // Create object groups for DOM tree nodes. | |
| 333 void V8GCController::majorGCPrologue(bool constructRetainedObjectInfos, v8::Isol
ate* isolate) | |
| 334 { | |
| 335 v8::HandleScope scope(isolate); | |
| 336 TRACE_EVENT_BEGIN0("v8", "majorGC"); | |
| 337 if (isMainThread()) { | |
| 338 ScriptForbiddenScope::enter(); | |
| 339 { | |
| 340 TRACE_EVENT_SCOPED_SAMPLING_STATE("blink", "DOMMajorGC"); | |
| 341 MajorGCWrapperVisitor visitor(isolate, constructRetainedObjectInfos)
; | |
| 342 v8::V8::VisitHandlesWithClassIds(&visitor); | |
| 343 visitor.notifyFinished(); | |
| 344 } | |
| 345 V8PerIsolateData::from(isolate)->setPreviousSamplingState(TRACE_EVENT_GE
T_SAMPLING_STATE()); | |
| 346 TRACE_EVENT_SET_SAMPLING_STATE("v8", "V8MajorGC"); | |
| 347 } else { | |
| 348 MajorGCWrapperVisitor visitor(isolate, constructRetainedObjectInfos); | |
| 349 v8::V8::VisitHandlesWithClassIds(&visitor); | |
| 350 visitor.notifyFinished(); | |
| 351 } | |
| 352 } | |
| 353 | |
| 354 void V8GCController::gcEpilogue(v8::GCType type, v8::GCCallbackFlags flags) | |
| 355 { | |
| 356 // FIXME: It would be nice if the GC callbacks passed the Isolate directly..
.. | |
| 357 v8::Isolate* isolate = v8::Isolate::GetCurrent(); | |
| 358 if (type == v8::kGCTypeScavenge) | |
| 359 minorGCEpilogue(isolate); | |
| 360 else if (type == v8::kGCTypeMarkSweepCompact) | |
| 361 majorGCEpilogue(isolate); | |
| 362 } | |
| 363 | |
| 364 void V8GCController::minorGCEpilogue(v8::Isolate* isolate) | |
| 365 { | |
| 366 TRACE_EVENT_END0("v8", "minorGC"); | |
| 367 if (isMainThread()) | |
| 368 ScriptForbiddenScope::exit(); | |
| 369 } | |
| 370 | |
| 371 void V8GCController::majorGCEpilogue(v8::Isolate* isolate) | |
| 372 { | |
| 373 v8::HandleScope scope(isolate); | |
| 374 | |
| 375 TRACE_EVENT_END0("v8", "majorGC"); | |
| 376 if (isMainThread()) | |
| 377 ScriptForbiddenScope::exit(); | |
| 378 } | |
| 379 | |
| 380 void V8GCController::collectGarbage(v8::Isolate* isolate) | |
| 381 { | |
| 382 v8::HandleScope handleScope(isolate); | |
| 383 RefPtr<ScriptState> scriptState = ScriptState::create(v8::Context::New(isola
te), DOMWrapperWorld::create(FakeWorld)); | |
| 384 ScriptState::Scope scope(scriptState.get()); | |
| 385 V8ScriptRunner::compileAndRunInternalScript(v8String(isolate, "if (gc) gc();
"), isolate); | |
| 386 scriptState->disposePerContextData(); | |
| 387 } | |
| 388 | |
| 389 void V8GCController::reportDOMMemoryUsageToV8(v8::Isolate* isolate) | |
| 390 { | |
| 391 if (!isMainThread()) | |
| 392 return; | |
| 393 | |
| 394 static size_t lastUsageReportedToV8 = 0; | |
| 395 | |
| 396 size_t currentUsage = Partitions::currentDOMMemoryUsage(); | |
| 397 int64_t diff = static_cast<int64_t>(currentUsage) - static_cast<int64_t>(las
tUsageReportedToV8); | |
| 398 isolate->AdjustAmountOfExternalAllocatedMemory(diff); | |
| 399 | |
| 400 lastUsageReportedToV8 = currentUsage; | |
| 401 } | |
| 402 | |
| 403 } // namespace blink | |
| OLD | NEW |