Source/WebCore/ChangeLog

 12011-11-10 Eugene Nalimov <enal@google.com>
 2
 3 Event listener for active DOM object that is also DOM node can be garbage collected prematurely.
 4 https://bugs.webkit.org/show_bug.cgi?id=70421
 5
 6 Reviewed by NOBODY (OOPS!).
 7
 8 Problem demonstrated itself when HTMLAudioElement was changed to become active DOM object.
 9 Before that there were no DOM objects that simultaneously were nodes and active objects.
 10 DOM object could be held in one of 3 maps -- node map, active objects map, and all other
 11 objects map, and HTMLAudioElement should be in 2 maps simultaneously. When it was in the
 12 active DOM objects map only, its event listener could be garbage collected, because special
 13 code that groups listeners with wrappers could handle only wrappers for objects in the node
 14 map. If we put HTMLAudioElement into nodes map, it would not be active DOM node, and can be
 15 garbage collected prematurely itself (see https://bugs.webkit.org/show_bug.cgi?id=66878).
 16 Fix is to introduce 4th map -- active nodes map, and change the code accordingly.
 17
 18 Test: media/audio-garbage-collect.html
 19
 20 * bindings/scripts/CodeGeneratorV8.pm:
 21 (GenerateNamedConstructorCallback):
 22 (GetDomMapFunction):
 23 * bindings/v8/DOMDataStore.cpp:
 24 (WebCore::DOMDataStore::DOMDataStore):
 25 (WebCore::DOMDataStore::getDOMWrapperMap):
 26 (WebCore::DOMDataStore::weakNodeCallback):
 27 * bindings/v8/DOMDataStore.h:
 28 (WebCore::DOMDataStore::activeDomNodeMap):
 29 * bindings/v8/ScopedDOMDataStore.cpp:
 30 (WebCore::ScopedDOMDataStore::ScopedDOMDataStore):
 31 (WebCore::ScopedDOMDataStore::~ScopedDOMDataStore):
 32 * bindings/v8/StaticDOMDataStore.cpp:
 33 (WebCore::StaticDOMDataStore::StaticDOMDataStore):
 34 * bindings/v8/StaticDOMDataStore.h:
 35 * bindings/v8/V8DOMMap.cpp:
 36 (WebCore::getActiveDOMNodeMap):
 37 (WebCore::removeAllDOMObjects):
 38 (WebCore::visitActiveDOMNodes):
 39 * bindings/v8/V8DOMMap.h:
 40 * bindings/v8/V8DOMWrapper.cpp:
 41 (WebCore::V8DOMWrapper::setJSWrapperForDOMNode):
 42 (WebCore::V8DOMWrapper::getWrapperSlow):
 43 * bindings/v8/V8GCController.cpp:
 44 (WebCore::GCPrologueSpecialCase):
 45 (WebCore::void):
 46 (WebCore::Node):
 47 (WebCore::GCPrologueVisitor::visitDOMWrapper):
 48 (WebCore::V8GCController::gcPrologue):
 49 (WebCore::GCEpilogueHelper::GCEpilogueSpecialCase):
 50 (WebCore::GCEpilogueVisitor::visitDOMWrapper):
 51 (WebCore::V8GCController::gcEpilogue):
 52 * dom/Node.h:
 53 (WebCore::Node::isActiveNode):
 54 * html/HTMLAudioElement.h:
 55 (WebCore::HTMLAudioElement::isActiveNode):
 56
1572011-11-14 Julien Chaffraix <jchaffraix@webkit.org>
258
359 Crash in RenderTableSection::splitColumn
100183

Source/WebCore/bindings/scripts/CodeGeneratorV8.pm

@@END
17771777 }
17781778
17791779 my $DOMObject = "DOMObject";
1780  # A DOMObject that is an ActiveDOMObject and also a DOMNode should be treated as an ActiveDOMObject.
1781  if ($dataNode->extendedAttributes->{"ActiveDOMObject"}) {
1782  $DOMObject = "ActiveDOMObject";
1783  } elsif (IsNodeSubType($dataNode)) {
 1780 # A DOMObject that is an ActiveDOMObject and also a DOMNode should be treated as an DOMNode here.
 1781 # setJSWrapperForDOMNode() will look if node is active and choose correct map to add node to.
 1782 if (IsNodeSubType($dataNode)) {
17841783 $DOMObject = "DOMNode";
 1784 } elsif ($dataNode->extendedAttributes->{"ActiveDOMObject"}) {
 1785 $DOMObject = "ActiveDOMObject";
17851786 }
17861787 push(@implContent, <<END);
17871788

@@sub GetDomMapFunction
30823083 my $dataNode = shift;
30833084 my $type = shift;
30843085 return "getDOMSVGElementInstanceMap()" if $type eq "SVGElementInstance";
 3086 return "getActiveDOMNodeMap()" if (IsNodeSubType($dataNode) && $dataNode->extendedAttributes->{"ActiveDOMObject"});
30853087 return "getDOMNodeMap()" if (IsNodeSubType($dataNode));
30863088 return "getActiveDOMObjectMap()" if $dataNode->extendedAttributes->{"ActiveDOMObject"};
30873089 return "getDOMObjectMap()";
100183

Source/WebCore/bindings/v8/DOMDataStore.cpp

@@namespace WebCore {
8686
8787DOMDataStore::DOMDataStore()
8888 : m_domNodeMap(0)
 89 , m_activeDomNodeMap(0)
8990 , m_domObjectMap(0)
9091 , m_activeDomObjectMap(0)
9192#if ENABLE(SVG)

@@void* DOMDataStore::getDOMWrapperMap(DOM
108109 switch (type) {
109110 case DOMNodeMap:
110111 return m_domNodeMap;
 112 case ActiveDOMNodeMap:
 113 return m_activeDomNodeMap;
111114 case DOMObjectMap:
112115 return m_domObjectMap;
113116 case ActiveDOMObjectMap:

@@void DOMDataStore::weakNodeCallback(v8::
149152 DOMDataList& list = DOMDataStore::allStores();
150153 for (size_t i = 0; i < list.size(); ++i) {
151154 DOMDataStore* store = list[i];
152  if (store->domNodeMap().removeIfPresent(node, v8Object)) {
 155 DOMNodeMapping& nodeMap = node->isActiveNode() ? store->activeDomNodeMap() : store->domNodeMap();
 156 if (nodeMap.removeIfPresent(node, v8Object)) {
153157 node->deref(); // Nobody overrides Node::deref so it's safe
154158 return; // There might be at most one wrapper for the node in world's maps
155159 }
100183

Source/WebCore/bindings/v8/DOMDataStore.h

@@namespace WebCore {
6565 public:
6666 enum DOMWrapperMapType {
6767 DOMNodeMap,
 68 ActiveDOMNodeMap,
6869 DOMObjectMap,
6970 ActiveDOMObjectMap,
7071#if ENABLE(SVG)

@@namespace WebCore {
8182 void* getDOMWrapperMap(DOMWrapperMapType);
8283
8384 DOMNodeMapping& domNodeMap() { return *m_domNodeMap; }
 85 DOMNodeMapping& activeDomNodeMap() { return *m_activeDomNodeMap; }
8486 DOMWrapperMap<void>& domObjectMap() { return *m_domObjectMap; }
8587 DOMWrapperMap<void>& activeDomObjectMap() { return *m_activeDomObjectMap; }
8688#if ENABLE(SVG)

@@namespace WebCore {
8991
9092 // Need by V8GCController.
9193 static void weakActiveDOMObjectCallback(v8::Persistent<v8::Value> v8Object, void* domObject);
 94 static void weakNodeCallback(v8::Persistent<v8::Value> v8Object, void* domObject);
9295
9396 protected:
94  static void weakNodeCallback(v8::Persistent<v8::Value> v8Object, void* domObject);
9597 static void weakDOMObjectCallback(v8::Persistent<v8::Value> v8Object, void* domObject);
9698#if ENABLE(SVG)
9799 static void weakSVGElementInstanceCallback(v8::Persistent<v8::Value> v8Object, void* domObject);
98100#endif
99101
100102 DOMNodeMapping* m_domNodeMap;
 103 DOMNodeMapping* m_activeDomNodeMap;
101104 DOMWrapperMap<void>* m_domObjectMap;
102105 DOMWrapperMap<void>* m_activeDomObjectMap;
103106#if ENABLE(SVG)
100183

Source/WebCore/bindings/v8/ScopedDOMDataStore.cpp

@@ScopedDOMDataStore::ScopedDOMDataStore()
3737 : DOMDataStore()
3838{
3939 m_domNodeMap = new DOMWrapperMap<Node>(&DOMDataStore::weakNodeCallback);
 40 m_activeDomNodeMap = new DOMWrapperMap<Node>(&DOMDataStore::weakNodeCallback);
4041 m_domObjectMap = new DOMWrapperMap<void>(&DOMDataStore::weakDOMObjectCallback);
4142 m_activeDomObjectMap = new DOMWrapperMap<void>(&DOMDataStore::weakActiveDOMObjectCallback);
4243#if ENABLE(SVG)

@@ScopedDOMDataStore::ScopedDOMDataStore()
4748ScopedDOMDataStore::~ScopedDOMDataStore()
4849{
4950 delete m_domNodeMap;
 51 delete m_activeDomNodeMap;
5052 delete m_domObjectMap;
5153 delete m_activeDomObjectMap;
5254#if ENABLE(SVG)
100183

Source/WebCore/bindings/v8/StaticDOMDataStore.cpp

@@namespace WebCore {
3737StaticDOMDataStore::StaticDOMDataStore()
3838 : DOMDataStore()
3939 , m_staticDomNodeMap(&DOMDataStore::weakNodeCallback)
 40 , m_staticActiveDomNodeMap(&DOMDataStore::weakNodeCallback)
4041 , m_staticDomObjectMap(&DOMDataStore::weakDOMObjectCallback)
4142 , m_staticActiveDomObjectMap(&DOMDataStore::weakActiveDOMObjectCallback)
4243#if ENABLE(SVG)

@@StaticDOMDataStore::StaticDOMDataStore()
4445#endif
4546{
4647 m_domNodeMap = &m_staticDomNodeMap;
 48 m_activeDomNodeMap = &m_staticActiveDomNodeMap;
4749 m_domObjectMap = &m_staticDomObjectMap;
4850 m_activeDomObjectMap = &m_staticActiveDomObjectMap;
4951#if ENABLE(SVG)
100183

Source/WebCore/bindings/v8/StaticDOMDataStore.h

@@public:
5151
5252private:
5353 IntrusiveDOMWrapperMap m_staticDomNodeMap;
 54 IntrusiveDOMWrapperMap m_staticActiveDomNodeMap;
5455 DOMWrapperMap<void> m_staticDomObjectMap;
5556 DOMWrapperMap<void> m_staticActiveDomObjectMap;
5657#if ENABLE(SVG)
100183

Source/WebCore/bindings/v8/V8DOMMap.cpp

@@DOMNodeMapping& getDOMNodeMap()
6464 return getDOMDataStore().domNodeMap();
6565}
6666
 67DOMNodeMapping& getActiveDOMNodeMap()
 68{
 69 return getDOMDataStore().activeDomNodeMap();
 70}
 71
6772DOMWrapperMap<void>& getDOMObjectMap()
6873{
6974 return getDOMDataStore().domObjectMap();

@@void removeAllDOMObjects()
9499 // Remove all DOM nodes.
95100 DOMData::removeObjectsFromWrapperMap<Node>(&store, store.domNodeMap());
96101
 102 // Remove all active DOM nodes.
 103 DOMData::removeObjectsFromWrapperMap<Node>(&store, store.activeDomNodeMap());
 104
97105#if ENABLE(SVG)
98106 // Remove all SVG element instances in the wrapper map.
99107 DOMData::removeObjectsFromWrapperMap<SVGElementInstance>(&store, store.domSvgElementInstanceMap());

@@void visitDOMNodes(DOMWrapperMap<Node>::
119127 }
120128}
121129
 130void visitActiveDOMNodes(DOMWrapperMap<Node>::Visitor* visitor)
 131{
 132 v8::HandleScope scope;
 133
 134 DOMDataList& list = DOMDataStore::allStores();
 135 for (size_t i = 0; i < list.size(); ++i) {
 136 DOMDataStore* store = list[i];
 137
 138 store->activeDomNodeMap().visit(store, visitor);
 139 }
 140}
 141
122142void visitDOMObjects(DOMWrapperMap<void>::Visitor* visitor)
123143{
124144 v8::HandleScope scope;
100183

Source/WebCore/bindings/v8/V8DOMMap.h

@@namespace WebCore {
158158
159159 // A map from DOM node to its JS wrapper.
160160 DOMNodeMapping& getDOMNodeMap();
 161 DOMNodeMapping& getActiveDOMNodeMap();
161162 void visitDOMNodes(DOMWrapperMap<Node>::Visitor*);
 163 void visitActiveDOMNodes(DOMWrapperMap<Node>::Visitor*);
162164
163165 // A map from a DOM object (non-node) to its JS wrapper. This map does not contain the DOM objects which can have pending activity (active dom objects).
164166 DOMWrapperMap<void>& getDOMObjectMap();
100183

Source/WebCore/bindings/v8/V8DOMWrapper.cpp

@@void V8DOMWrapper::setJSWrapperForActive
9292void V8DOMWrapper::setJSWrapperForDOMNode(Node* node, v8::Persistent<v8::Object> wrapper)
9393{
9494 ASSERT(V8DOMWrapper::maybeDOMWrapper(wrapper));
95  getDOMNodeMap().set(node, wrapper);
 95 if (node->isActiveNode())
 96 getActiveDOMNodeMap().set(node, wrapper);
 97 else
 98 getDOMNodeMap().set(node, wrapper);
9699}
97100
98101v8::Local<v8::Function> V8DOMWrapper::getConstructor(WrapperTypeInfo* type, v8::Handle<v8::Value> objectPrototype)

@@v8::Handle<v8::Object> V8DOMWrapper::get
295298 return v8::Handle<v8::Object>();
296299 return *wrapper;
297300 }
298  DOMNodeMapping& domNodeMap = context->world()->domDataStore()->domNodeMap();
 301 DOMDataStore* store = context->world()->domDataStore();
 302 DOMNodeMapping& domNodeMap = node->isActiveNode() ? store->activeDomNodeMap() : store->domNodeMap();
299303 return domNodeMap.get(node);
300304}
301305
100183

Source/WebCore/bindings/v8/V8GCController.cpp

@@public:
143143
144144#endif // NDEBUG
145145
146 class GCPrologueVisitor : public DOMWrapperMap<void>::Visitor {
147 public:
148  void visitDOMWrapper(DOMDataStore* store, void* object, v8::Persistent<v8::Object> wrapper)
 146struct SpecialCasePrologueObjectHandler {
 147 static bool process(void* object, v8::Persistent<v8::Object> wrapper, WrapperTypeInfo* typeInfo)
149148 {
150  WrapperTypeInfo* typeInfo = V8DOMWrapper::domWrapperType(wrapper);
151 
152149 // Additional handling of message port ensuring that entangled ports also
153150 // have their wrappers entangled. This should ideally be handled when the
154151 // ports are actually entangled in MessagePort::entangle, but to avoid

@@public:
161158 MessagePort* port1 = static_cast<MessagePort*>(object);
162159 if (port1->isEntangled() || port1->hasPendingActivity())
163160 wrapper.ClearWeak();
164  } else {
 161 return true;
 162 }
 163 return false;
 164 }
 165};
 166
 167struct SpecialCasePrologueNodeHandler {
 168 static bool process(Node* object, v8::Persistent<v8::Object> wrapper, WrapperTypeInfo* typeInfo)
 169 {
 170 return false;
 171 }
 172};
 173
 174template<typename T, typename S> class GCPrologueVisitor : public DOMWrapperMap<T>::Visitor {
 175public:
 176 void visitDOMWrapper(DOMDataStore* store, T* object, v8::Persistent<v8::Object> wrapper)
 177 {
 178 WrapperTypeInfo* typeInfo = V8DOMWrapper::domWrapperType(wrapper);
 179
 180 if (!S::process(object, wrapper, typeInfo)) {
165181 ActiveDOMObject* activeDOMObject = typeInfo->toActiveDOMObject(wrapper);
166182 if (activeDOMObject && activeDOMObject->hasPendingActivity())
167183 wrapper.ClearWeak();

@@void V8GCController::gcPrologue()
373389
374390 // Run through all objects with possible pending activity making their
375391 // wrappers non weak if there is pending activity.
376  GCPrologueVisitor prologueVisitor;
377  visitActiveDOMObjects(&prologueVisitor);
 392 GCPrologueVisitor<void, SpecialCasePrologueObjectHandler> prologueObjectVisitor;
 393 visitActiveDOMObjects(&prologueObjectVisitor);
 394 GCPrologueVisitor<Node, SpecialCasePrologueNodeHandler> prologueNodeVisitor;
 395 visitActiveDOMNodes(&prologueNodeVisitor);
378396
379397 // Create object groups.
380398 GrouperVisitor grouperVisitor;
381399 visitDOMNodes(&grouperVisitor);
 400 visitActiveDOMNodes(&grouperVisitor);
382401 visitDOMObjects(&grouperVisitor);
383402 grouperVisitor.applyGrouping();
384403

@@void V8GCController::gcPrologue()
387406 data->stringCache()->clearOnGC();
388407}
389408
390 class GCEpilogueVisitor : public DOMWrapperMap<void>::Visitor {
391 public:
392  void visitDOMWrapper(DOMDataStore* store, void* object, v8::Persistent<v8::Object> wrapper)
 409struct SpecialCaseEpilogueObjectHandler {
 410 static bool process(void* object, v8::Persistent<v8::Object> wrapper, WrapperTypeInfo* typeInfo)
393411 {
394  WrapperTypeInfo* typeInfo = V8DOMWrapper::domWrapperType(wrapper);
395412 if (V8MessagePort::info.equals(typeInfo)) {
396413 MessagePort* port1 = static_cast<MessagePort*>(object);
397414 // We marked this port as reachable in GCPrologueVisitor. Undo this now since the

@@public:
399416 // GCPrologueVisitor expects to see all handles marked as weak).
400417 if ((!wrapper.IsWeak() && !wrapper.IsNearDeath()) || port1->hasPendingActivity())
401418 wrapper.MakeWeak(port1, &DOMDataStore::weakActiveDOMObjectCallback);
402  } else {
 419 return true;
 420 }
 421 return false;
 422 }
 423};
 424
 425struct SpecialCaseEpilogueNodeHandler {
 426 static bool process(Node* object, v8::Persistent<v8::Object> wrapper, WrapperTypeInfo* typeInfo)
 427 {
 428 return false;
 429 }
 430};
 431
 432template<typename T, typename S, v8::WeakReferenceCallback callback> class GCEpilogueVisitor : public DOMWrapperMap<T>::Visitor {
 433public:
 434 void visitDOMWrapper(DOMDataStore* store, T* object, v8::Persistent<v8::Object> wrapper)
 435 {
 436 WrapperTypeInfo* typeInfo = V8DOMWrapper::domWrapperType(wrapper);
 437 if (!S::process(object, wrapper, typeInfo)) {
403438 ActiveDOMObject* activeDOMObject = typeInfo->toActiveDOMObject(wrapper);
404439 if (activeDOMObject && activeDOMObject->hasPendingActivity()) {
405440 ASSERT(!wrapper.IsWeak());

@@public:
408443 // may be a different pointer (in case ActiveDOMObject is not
409444 // the main base class of the object's class) and pointer
410445 // identity is required by DOM map functions.
411  wrapper.MakeWeak(object, &DOMDataStore::weakActiveDOMObjectCallback);
 446 wrapper.MakeWeak(object, callback);
412447 }
413448 }
414449 }

@@void V8GCController::gcEpilogue()
444479
445480 // Run through all objects with pending activity making their wrappers weak
446481 // again.
447  GCEpilogueVisitor epilogueVisitor;
448  visitActiveDOMObjects(&epilogueVisitor);
 482 GCEpilogueVisitor<void, SpecialCaseEpilogueObjectHandler, &DOMDataStore::weakActiveDOMObjectCallback> epilogueObjectVisitor;
 483 visitActiveDOMObjects(&epilogueObjectVisitor);
 484 GCEpilogueVisitor<Node, SpecialCaseEpilogueNodeHandler, &DOMDataStore::weakNodeCallback> epilogueNodeVisitor;
 485 visitActiveDOMNodes(&epilogueNodeVisitor);
449486
450487 workingSetEstimateMB = getActualMemoryUsageInMB();
451488
100183

Source/WebCore/dom/Node.h

@@public:
190190
191191 Node* lastDescendant() const;
192192 Node* firstDescendant() const;
 193
 194 virtual bool isActiveNode() const { return false; }
193195
194196 // Other methods (not part of DOM)
195197
100183

Source/WebCore/html/HTMLAudioElement.h

@@public:
4242
4343 virtual bool hasPendingActivity() const { return isPlaying() || HTMLMediaElement::hasPendingActivity(); }
4444
 45 virtual bool isActiveNode() const { return true; }
 46
4547private:
4648 HTMLAudioElement(const QualifiedName&, Document*);
4749
100183

LayoutTests/ChangeLog

 12011-11-10 Eugene Nalimov <enal@google.com>
 2
 3 Event listener for active DOM object that is also DOM node can be garbage collected prematurely.
 4 https://bugs.webkit.org/show_bug.cgi?id=70421 and https://bugs.webkit.org/show_bug.cgi?id=66878
 5
 6 Reviewed by NOBODY (OOPS!).
 7
 8 * media/audio-garbage-collect-expected.txt: Added.
 9 * media/audio-garbage-collect.html: Added.
 10
1112011-11-14 Julien Chaffraix <jchaffraix@webkit.org>
212
313 Crash in RenderTableSection::splitColumn
100183

LayoutTests/media/audio-garbage-collect-expected.txt

 1Tests that we don't garbage collect playing audio object or event listener.
 2
 3According to http://www.whatwg.org/specs/web-apps/current-work/multipage/the-video-element.html,
 4"4.8.10.8 Playing the media resource",
 5"Media elements must not stop playing just because all references to them have been removed; only once a media element is in a state where no further audio could ever be played by that element may the element be garbage collected."
 6
 7(see https://bugs.webkit.org/show_bug.cgi?id=66878, https://bugs.webkit.org/show_bug.cgi?id=70421, and http://crbug.com/62604 for more details).
 8
 9PASS
 10
 11
0

LayoutTests/media/audio-garbage-collect.html

 1<!DOCTYPE HTML>
 2
 3<html>
 4<body>
 5
 6<p>Tests that we don't garbage collect playing audio object or event listener.</p>
 7<p>According to http://www.whatwg.org/specs/web-apps/current-work/multipage/the-video-element.html,<br />
 8"4.8.10.8 Playing the media resource",<br />
 9"Media elements must not stop playing just because all references to them have
 10been removed; only once a media element is in a state where no further audio
 11could ever be played by that element may the element be garbage collected."<br /><br />
 12(see https://bugs.webkit.org/show_bug.cgi?id=66878, https://bugs.webkit.org/show_bug.cgi?id=70421, and http://crbug.com/62604 for more details).</p>
 13<p id="result">
 14FAIL: Test either still running or stopped prematurely.
 15</p>
 16
 17<script src=media-file.js></script>
 18<script src=video-test.js></script>
 19<script type="text/javascript">
 20
 21var num_players = 4;
 22var play_times = 5;
 23
 24function forceGC() {
 25 if (window.GCController)
 26 return GCController.collect();
 27
 28 // Force garbage collection
 29 for (var ndx = 0; ndx < 99000; ndx ++)
 30 var str = new String("1234");
 31}
 32
 33function finish() {
 34 document.getElementById("result").innerText = "PASS";
 35 if (window.layoutTestController) {
 36 layoutTestController.notifyDone();
 37 }
 38}
 39
 40function start() {
 41 var num_played = 0;
 42 var audioFile = findMediaFile("audio", "content/silence");
 43 var a = new Audio(audioFile);
 44 a.addEventListener('ended', function() {
 45 num_played ++;
 46 if (num_played < play_times) {
 47 a.currentTime = a.duration - 0.5;
 48 a.play();
 49 if (num_played == play_times - 1) {
 50 a = null;
 51 forceGC();
 52 }
 53 } else {
 54 num_players --;
 55 if (num_players == 0)
 56 start();
 57 else
 58 finish();
 59 }
 60 });
 61 a.addEventListener('canplaythrough', function() {
 62 a.currentTime = a.duration - 0.5;
 63 a.play();
 64 });
 65}
 66
 67start();
 68
 69</script>
 70</body>
 71</html>
0