Title: [98659] trunk
Revision
98659
Author
commit-qu...@webkit.org
Date
2011-10-27 16:38:09 -0700 (Thu, 27 Oct 2011)

Log Message

[MutationObservers] Implement subtree observation of transiently disconnected nodes
https://bugs.webkit.org/show_bug.cgi?id=70788

Patch by Rafael Weinstein <rafa...@chromium.org> on 2011-10-27
Reviewed by Ryosuke Niwa.

Source/WebCore:

This patch adds support for observing all descendant nodes reachable from a subtree
observation until delivery of mutations -- even if they become detached. We do this by
introducing a "transient registration" which can exist for a short time along side
normal registrations on Node. Transient registrations have a reference to the node
which "owns" the subtree observation registration (the "registrationNode"). Transient
registrations are cleared immediately before mutations are delivered to an observer,
or when the observer re-observes at the registrationNode, in-effect resetting the
observation.

New tests added to fast/mutation/observe-subtree.html.

* dom/CharacterData.cpp:
(WebCore::CharacterData::dispatchModifiedEvent):
* dom/ChildListMutationScope.cpp:
(WebCore::MutationAccumulationRouter::ChildListMutationAccumulator::ChildListMutationAccumulator):
(WebCore::MutationAccumulationRouter::ChildListMutationAccumulator::enqueueMutationRecord):
(WebCore::MutationAccumulationRouter::MutationAccumulationRouter::incrementScopingLevel):
* dom/ContainerNode.cpp:
(WebCore::dispatchChildRemovalEvents):
* dom/Element.cpp:
(WebCore::enqueueAttributesMutationRecord):
* dom/Node.cpp:
(WebCore::addMatchingObservers):
(WebCore::Node::getRegisteredMutationObserversOfType):
(WebCore::Node::registerMutationObserver):
(WebCore::Node::unregisterMutationObserver):
(WebCore::Node::notifySubtreeObserversOfDisconnection):
* dom/Node.h:
* dom/NodeRareData.h:
(WebCore::MutationObserverEntry::MutationObserverEntry):
(WebCore::MutationObserverEntry::operator==):
* dom/WebKitMutationObserver.cpp:
(WebCore::WebKitMutationObserver::observe):
(WebCore::unregisterTransientEntries):
(WebCore::WebKitMutationObserver::disconnect):
(WebCore::WebKitMutationObserver::observedNodeDestructed):
(WebCore::WebKitMutationObserver::observedSubtreeWillDisconnect):
(WebCore::WebKitMutationObserver::clearTransientEntries):
(WebCore::WebKitMutationObserver::deliver):
* dom/WebKitMutationObserver.h:

LayoutTests:

Added tests which assert that nodes can be detached from a subtree where observation is registered
and still be observed -- until the end of the current "microtask".

* fast/mutation/observe-subtree-expected.txt:
* fast/mutation/observe-subtree.html:

Modified Paths

Diff

Modified: trunk/LayoutTests/ChangeLog (98658 => 98659)


--- trunk/LayoutTests/ChangeLog	2011-10-27 23:35:43 UTC (rev 98658)
+++ trunk/LayoutTests/ChangeLog	2011-10-27 23:38:09 UTC (rev 98659)
@@ -1,3 +1,16 @@
+2011-10-27  Rafael Weinstein  <rafa...@chromium.org>
+
+        [MutationObservers] Implement subtree observation of transiently disconnected nodes
+        https://bugs.webkit.org/show_bug.cgi?id=70788
+
+        Reviewed by Ryosuke Niwa.
+
+        Added tests which assert that nodes can be detached from a subtree where observation is registered
+        and still be observed -- until the end of the current "microtask".
+
+        * fast/mutation/observe-subtree-expected.txt:
+        * fast/mutation/observe-subtree.html:
+
 2011-10-27  Filip Pizlo  <fpi...@apple.com>
 
         If the bytecode generator emits code after the return in the first basic block,

Modified: trunk/LayoutTests/fast/mutation/observe-subtree-expected.txt (98658 => 98659)


--- trunk/LayoutTests/fast/mutation/observe-subtree-expected.txt	2011-10-27 23:35:43 UTC (rev 98658)
+++ trunk/LayoutTests/fast/mutation/observe-subtree-expected.txt	2011-10-27 23:38:09 UTC (rev 98659)
@@ -33,6 +33,48 @@
 PASS mutations[0].attributeName is "foo"
 PASS mutations[0].attributeNamespace is null
 
+Testing that transiently detached nodes are still observed via subtree.
+...both changes should be received. Change detached subDiv again.
+PASS mutations.length is 2
+PASS mutations[0].type is "attributes"
+PASS mutations[0].target is subDiv
+PASS mutations[0].attributeName is "foo"
+PASS mutations[1].type is "attributes"
+PASS mutations[1].target is subDiv
+PASS mutations[1].attributeName is "test"
+...transient subtree observation was stopped after delivery, so subDiv change should not be received. Reattach and change again.
+PASS mutations is null
+...reattached subtree should now be observable. Try detaching and re-observing.
+PASS mutations.length is 1
+PASS mutations[0].type is "attributes"
+PASS mutations[0].target is subDiv
+PASS mutations[0].attributeName is "foo"
+...The change made before re-observing should be received, but not the one after.
+PASS mutations.length is 1
+PASS mutations[0].type is "characterData"
+PASS mutations[0].target is subDiv.firstChild
+
+Testing correct behavior of transient observation with complex movement .
+...All changes should be received except for setting the "d" attribute on subDiv3 before it was reachable from div.
+PASS mutations.length is 6
+PASS mutations[0].type is "attributes"
+PASS mutations[0].target is subDiv
+PASS mutations[0].attributeName is "a"
+PASS mutations[1].type is "attributes"
+PASS mutations[1].target is subDiv2
+PASS mutations[1].attributeName is "b"
+PASS mutations[2].type is "characterData"
+PASS mutations[2].target is text
+PASS mutations[3].type is "attributes"
+PASS mutations[3].target is subDiv2
+PASS mutations[3].attributeName is "e"
+PASS mutations[4].type is "attributes"
+PASS mutations[4].target is subDiv3
+PASS mutations[4].attributeName is "f"
+PASS mutations[5].type is "attributes"
+PASS mutations[5].target is subDiv2
+PASS mutations[5].attributeName is "g"
+
 PASS successfullyParsed is true
 
 TEST COMPLETE

Modified: trunk/LayoutTests/fast/mutation/observe-subtree.html (98658 => 98659)


--- trunk/LayoutTests/fast/mutation/observe-subtree.html	2011-10-27 23:35:43 UTC (rev 98658)
+++ trunk/LayoutTests/fast/mutation/observe-subtree.html	2011-10-27 23:38:09 UTC (rev 98659)
@@ -15,7 +15,7 @@
 var mutations;
 var mutations2;
 var div;
-var subDiv;
+var subDiv, subDiv2, subDiv3, text;
 var calls;
 
 function testBasic() {
@@ -135,7 +135,159 @@
 
     start();
 }
-var tests = [testBasic, testMultipleObservers, testMultipleObservations];
+
+function testTransientDetachedBasic() {
+    var observer;
+
+    function start() {
+        debug('Testing that transiently detached nodes are still observed via subtree.');
+
+        mutations = null;
+        div = document.createElement('div');
+        subDiv = div.appendChild(document.createElement('div'));
+        subDiv.innerHTML = 'hello, world';
+        observer = new WebKitMutationObserver(function(mutations) {
+            window.mutations = mutations;
+        });
+
+        observer.observe(div, {attributes: true, characterData: true, subtree: true});
+        subDiv.setAttribute('foo', 'bar');
+        div.removeChild(subDiv);
+        subDiv.setAttribute('test', 'test');
+        setTimeout(checkDeliveredAndChangeAgain, 0);
+    }
+
+    function checkDeliveredAndChangeAgain() {
+        debug('...both changes should be received. Change detached subDiv again.');
+
+        shouldBe('mutations.length', '2');
+        shouldBe('mutations[0].type', '"attributes"');
+        shouldBe('mutations[0].target', 'subDiv');
+        shouldBe('mutations[0].attributeName', '"foo"');
+        shouldBe('mutations[1].type', '"attributes"');
+        shouldBe('mutations[1].target', 'subDiv');
+        shouldBe('mutations[1].attributeName', '"test"');
+
+        mutations = null;
+        subDiv.setAttribute('foo', 'baz');
+
+        setTimeout(checkNotDeliveredAndReattach);
+    }
+
+    function checkNotDeliveredAndReattach() {
+        debug('...transient subtree observation was stopped after delivery, so subDiv change should not be received. Reattach and change again.');
+
+        shouldBe('mutations', 'null');
+
+        mutations = null
+        div.appendChild(subDiv);
+        subDiv.setAttribute('foo', 'bat');
+
+        setTimeout(checkDeliveredAndReobserve);
+    }
+
+    function checkDeliveredAndReobserve() {
+        debug('...reattached subtree should now be observable. Try detaching and re-observing.');
+
+        shouldBe('mutations.length', '1');
+        shouldBe('mutations[0].type', '"attributes"');
+        shouldBe('mutations[0].target', 'subDiv');
+        shouldBe('mutations[0].attributeName', '"foo"');
+
+        mutations = null;
+        div.removeChild(subDiv);
+        subDiv.firstChild.textContent = 'badbye';
+        observer.observe(div, {attributes: true, characterData: true, subtree: true});
+        subDiv.setAttribute('foo', 'boo');
+
+        setTimeout(finish);
+    }
+
+
+    function finish() {
+        debug('...The change made before re-observing should be received, but not the one after.');
+
+        shouldBe('mutations.length', '1');
+        shouldBe('mutations[0].type', '"characterData"');
+        shouldBe('mutations[0].target', 'subDiv.firstChild');
+
+        observer.disconnect();
+        debug('');
+        runNextTest();
+    }
+    start();
+}
+
+function testTransientDetachedDetailed() {
+    var observer;
+
+    function start() {
+        debug('Testing correct behavior of transient observation with complex movement .');
+
+        mutations = null;
+        div = document.createElement('div');
+        subDiv = div.appendChild(document.createElement('div'));
+        subDiv2 = subDiv.appendChild(document.createElement('div'));
+        subDiv2.innerHTML = 'hello, world';
+        subDiv3 = document.createElement('div');
+
+        observer = new WebKitMutationObserver(function(mutations) {
+            window.mutations = mutations;
+        });
+
+        observer.observe(div, {attributes: true, characterData: true, subtree: true});
+        div.removeChild(subDiv);
+        subDiv.removeChild(subDiv2);
+        text = subDiv2.removeChild(subDiv2.firstChild);
+
+        subDiv.setAttribute('a', 'a');
+        subDiv2.setAttribute('b', 'b');
+        text.textContent = 'c';
+        subDiv3.appendChild(subDiv2);
+        subDiv3.setAttribute('d', 'd');
+        subDiv2.setAttribute('e', 'e');
+        div.appendChild(subDiv3);
+        subDiv3.setAttribute('f', 'f');
+        subDiv2.setAttribute('g', 'g');
+
+        setTimeout(finish, 0);
+    }
+
+    function finish() {
+        debug('...All changes should be received except for setting the "d" attribute on subDiv3 before it was reachable from div.');
+
+        shouldBe('mutations.length', '6');
+        shouldBe('mutations[0].type', '"attributes"');
+        shouldBe('mutations[0].target', 'subDiv');
+        shouldBe('mutations[0].attributeName', '"a"');
+
+        shouldBe('mutations[1].type', '"attributes"');
+        shouldBe('mutations[1].target', 'subDiv2');
+        shouldBe('mutations[1].attributeName', '"b"');
+
+        shouldBe('mutations[2].type', '"characterData"');
+        shouldBe('mutations[2].target', 'text');
+
+        shouldBe('mutations[3].type', '"attributes"');
+        shouldBe('mutations[3].target', 'subDiv2');
+        shouldBe('mutations[3].attributeName', '"e"');
+
+        shouldBe('mutations[4].type', '"attributes"');
+        shouldBe('mutations[4].target', 'subDiv3');
+        shouldBe('mutations[4].attributeName', '"f"');
+
+        shouldBe('mutations[5].type', '"attributes"');
+        shouldBe('mutations[5].target', 'subDiv2');
+        shouldBe('mutations[5].attributeName', '"g"');
+
+        observer.disconnect();
+        debug('');
+        runNextTest();
+    }
+    start();
+}
+
+var tests = [testBasic, testMultipleObservers, testMultipleObservations, testTransientDetachedBasic, testTransientDetachedDetailed];
 var testIndex = 0;
 
 function runNextTest() {

Modified: trunk/Source/WebCore/ChangeLog (98658 => 98659)


--- trunk/Source/WebCore/ChangeLog	2011-10-27 23:35:43 UTC (rev 98658)
+++ trunk/Source/WebCore/ChangeLog	2011-10-27 23:38:09 UTC (rev 98659)
@@ -1,3 +1,51 @@
+2011-10-27  Rafael Weinstein  <rafa...@chromium.org>
+
+        [MutationObservers] Implement subtree observation of transiently disconnected nodes
+        https://bugs.webkit.org/show_bug.cgi?id=70788
+
+        Reviewed by Ryosuke Niwa.
+
+        This patch adds support for observing all descendant nodes reachable from a subtree
+        observation until delivery of mutations -- even if they become detached. We do this by
+        introducing a "transient registration" which can exist for a short time along side
+        normal registrations on Node. Transient registrations have a reference to the node
+        which "owns" the subtree observation registration (the "registrationNode"). Transient
+        registrations are cleared immediately before mutations are delivered to an observer,
+        or when the observer re-observes at the registrationNode, in-effect resetting the
+        observation.
+
+        New tests added to fast/mutation/observe-subtree.html.
+
+        * dom/CharacterData.cpp:
+        (WebCore::CharacterData::dispatchModifiedEvent):
+        * dom/ChildListMutationScope.cpp:
+        (WebCore::MutationAccumulationRouter::ChildListMutationAccumulator::ChildListMutationAccumulator):
+        (WebCore::MutationAccumulationRouter::ChildListMutationAccumulator::enqueueMutationRecord):
+        (WebCore::MutationAccumulationRouter::MutationAccumulationRouter::incrementScopingLevel):
+        * dom/ContainerNode.cpp:
+        (WebCore::dispatchChildRemovalEvents):
+        * dom/Element.cpp:
+        (WebCore::enqueueAttributesMutationRecord):
+        * dom/Node.cpp:
+        (WebCore::addMatchingObservers):
+        (WebCore::Node::getRegisteredMutationObserversOfType):
+        (WebCore::Node::registerMutationObserver):
+        (WebCore::Node::unregisterMutationObserver):
+        (WebCore::Node::notifySubtreeObserversOfDisconnection):
+        * dom/Node.h:
+        * dom/NodeRareData.h:
+        (WebCore::MutationObserverEntry::MutationObserverEntry):
+        (WebCore::MutationObserverEntry::operator==):
+        * dom/WebKitMutationObserver.cpp:
+        (WebCore::WebKitMutationObserver::observe):
+        (WebCore::unregisterTransientEntries):
+        (WebCore::WebKitMutationObserver::disconnect):
+        (WebCore::WebKitMutationObserver::observedNodeDestructed):
+        (WebCore::WebKitMutationObserver::observedSubtreeWillDisconnect):
+        (WebCore::WebKitMutationObserver::clearTransientEntries):
+        (WebCore::WebKitMutationObserver::deliver):
+        * dom/WebKitMutationObserver.h:
+
 2011-10-27  Pratik Solanki  <psola...@apple.com>
 
         Ask CG to not parse image metadata

Modified: trunk/Source/WebCore/dom/CharacterData.cpp (98658 => 98659)


--- trunk/Source/WebCore/dom/CharacterData.cpp	2011-10-27 23:35:43 UTC (rev 98658)
+++ trunk/Source/WebCore/dom/CharacterData.cpp	2011-10-27 23:38:09 UTC (rev 98659)
@@ -193,12 +193,12 @@
 void CharacterData::dispatchModifiedEvent(StringImpl* oldData)
 {
 #if ENABLE(MUTATION_OBSERVERS)
-    Vector<WebKitMutationObserver*> observers;
+    HashMap<WebKitMutationObserver*, MutationObserverOptions> observers;
     getRegisteredMutationObserversOfType(observers, WebKitMutationObserver::CharacterData);
     if (!observers.isEmpty()) {
         RefPtr<MutationRecord> mutation = MutationRecord::createCharacterData(this);
-        for (size_t i = 0; i < observers.size(); ++i)
-            observers[i]->enqueueMutationRecord(mutation);
+        for (HashMap<WebKitMutationObserver*, MutationObserverOptions>::iterator iter = observers.begin(); iter != observers.end(); ++iter)
+                iter->first->enqueueMutationRecord(mutation);
     }
 #endif
     if (parentNode())

Modified: trunk/Source/WebCore/dom/ChildListMutationScope.cpp (98658 => 98659)


--- trunk/Source/WebCore/dom/ChildListMutationScope.cpp	2011-10-27 23:35:43 UTC (rev 98658)
+++ trunk/Source/WebCore/dom/ChildListMutationScope.cpp	2011-10-27 23:38:09 UTC (rev 98659)
@@ -53,7 +53,7 @@
 class ChildListMutationAccumulator : public RefCounted<ChildListMutationAccumulator> {
     WTF_MAKE_NONCOPYABLE(ChildListMutationAccumulator);
 public:
-    ChildListMutationAccumulator(PassRefPtr<Node>, Vector<WebKitMutationObserver*>&);
+    ChildListMutationAccumulator(PassRefPtr<Node>, HashMap<WebKitMutationObserver*, MutationObserverOptions>&);
     ~ChildListMutationAccumulator();
 
     void childAdded(PassRefPtr<Node>);
@@ -73,7 +73,7 @@
     RefPtr<Node> m_nextSibling;
     RefPtr<Node> m_lastAdded;
 
-    Vector<WebKitMutationObserver*> m_observers;
+    HashMap<WebKitMutationObserver*, MutationObserverOptions> m_observers;
 };
 
 class MutationAccumulationRouter {
@@ -100,7 +100,7 @@
     static MutationAccumulationRouter* s_instance;
 };
 
-ChildListMutationAccumulator::ChildListMutationAccumulator(PassRefPtr<Node> target, Vector<WebKitMutationObserver*>& observers)
+ChildListMutationAccumulator::ChildListMutationAccumulator(PassRefPtr<Node> target, HashMap<WebKitMutationObserver*, MutationObserverOptions>& observers)
     : m_target(target)
 {
     m_observers.swap(observers);
@@ -169,8 +169,8 @@
     RefPtr<MutationRecord> mutation = MutationRecord::createChildList(
         m_target, StaticNodeList::adopt(m_addedNodes), StaticNodeList::adopt(m_removedNodes), m_previousSibling, m_nextSibling);
 
-    for (size_t i = 0; i < m_observers.size(); ++i)
-        m_observers[i]->enqueueMutationRecord(mutation);
+    for (HashMap<WebKitMutationObserver*, MutationObserverOptions>::iterator iter = m_observers.begin(); iter != m_observers.end(); ++iter)
+        iter->first->enqueueMutationRecord(mutation);
 
     clear();
 }
@@ -244,7 +244,7 @@
         return;
     }
 
-    Vector<WebKitMutationObserver*> observers;
+    HashMap<WebKitMutationObserver*, MutationObserverOptions> observers;
     target->getRegisteredMutationObserversOfType(observers, WebKitMutationObserver::ChildList);
     if (observers.isEmpty())
         m_accumulations.set(target, 0);

Modified: trunk/Source/WebCore/dom/ContainerNode.cpp (98658 => 98659)


--- trunk/Source/WebCore/dom/ContainerNode.cpp	2011-10-27 23:35:43 UTC (rev 98658)
+++ trunk/Source/WebCore/dom/ContainerNode.cpp	2011-10-27 23:38:09 UTC (rev 98659)
@@ -1144,6 +1144,7 @@
     if (c->parentNode()) {
         ChildListMutationScope mutation(c->parentNode());
         mutation.willRemoveChild(c);
+        c->notifyMutationObserversNodeWillDetach();
     }
 #endif
 

Modified: trunk/Source/WebCore/dom/Element.cpp (98658 => 98659)


--- trunk/Source/WebCore/dom/Element.cpp	2011-10-27 23:35:43 UTC (rev 98658)
+++ trunk/Source/WebCore/dom/Element.cpp	2011-10-27 23:38:09 UTC (rev 98659)
@@ -619,14 +619,14 @@
 #if ENABLE(MUTATION_OBSERVERS)
 static void enqueueAttributesMutationRecord(Element* element, const QualifiedName& name)
 {
-    Vector<WebKitMutationObserver*> observers;
+    HashMap<WebKitMutationObserver*, MutationObserverOptions> observers;
     element->getRegisteredMutationObserversOfType(observers, WebKitMutationObserver::Attributes);
     if (observers.isEmpty())
         return;
 
     RefPtr<MutationRecord> mutation = MutationRecord::createAttributes(element, name);
-    for (size_t i = 0; i < observers.size(); ++i)
-        observers[i]->enqueueMutationRecord(mutation);
+    for (HashMap<WebKitMutationObserver*, MutationObserverOptions>::iterator iter = observers.begin(); iter != observers.end(); ++iter)
+        iter->first->enqueueMutationRecord(mutation);
 }
 #endif
 

Modified: trunk/Source/WebCore/dom/Node.cpp (98658 => 98659)


--- trunk/Source/WebCore/dom/Node.cpp	2011-10-27 23:35:43 UTC (rev 98658)
+++ trunk/Source/WebCore/dom/Node.cpp	2011-10-27 23:38:09 UTC (rev 98659)
@@ -549,10 +549,9 @@
         treeScope()->removeNodeListCache();
 
 #if ENABLE(MUTATION_OBSERVERS)
-    Vector<MutationObserverEntry>* observerEntries = mutationObserverEntries();
-    if (observerEntries) {
-        for (size_t i = 0; i < observerEntries->size(); ++i)
-            observerEntries->at(i).observer->observedNodeDestructed(this);
+    if (Vector<MutationObserverRegistration>* registry = mutationObserverRegistry()) {
+        for (size_t i = 0; i < registry->size(); ++i)
+            registry->at(i).observer->observedNodeDestructed(this);
     }
 #endif
 
@@ -2698,66 +2697,87 @@
 }
 
 #if ENABLE(MUTATION_OBSERVERS)
-Vector<MutationObserverEntry>* Node::mutationObserverEntries()
+Vector<MutationObserverRegistration>* Node::mutationObserverRegistry()
 {
-    return hasRareData() ? rareData()->mutationObserverEntries() : 0;
+    return hasRareData() ? rareData()->mutationObserverRegistry() : 0;
 }
 
-static void addMatchingObservers(HashSet<WebKitMutationObserver*>& observerSet, Vector<MutationObserverEntry>* observerEntries, MutationObserverOptions options)
+static void addMatchingObservers(HashMap<WebKitMutationObserver*, MutationObserverOptions>& observers, Vector<MutationObserverRegistration>* registry, MutationObserverOptions options)
 {
-    if (!observerEntries)
+    if (!registry)
         return;
 
-    const size_t size = observerEntries->size();
+    const size_t size = registry->size();
     for (size_t i = 0; i < size; ++i) {
-        MutationObserverEntry& entry = observerEntries->at(i);
-        if (entry.hasAllOptions(options))
-            observerSet.add(entry.observer.get());
+        MutationObserverRegistration& entry = registry->at(i);
+
+        if (!entry.hasAllOptions(options))
+            continue;
+
+        pair<HashMap<WebKitMutationObserver*, MutationObserverOptions>::iterator, bool> result = observers.add(entry.observer.get(), entry.options);
+        if (!result.second)
+            result.first->second |= entry.options;
     }
 }
 
-void Node::getRegisteredMutationObserversOfType(Vector<WebKitMutationObserver*>& observers, WebKitMutationObserver::MutationType type)
+void Node::getRegisteredMutationObserversOfType(HashMap<WebKitMutationObserver*, MutationObserverOptions>& observers, WebKitMutationObserver::MutationType type)
 {
-    HashSet<WebKitMutationObserver*> observerSet;
-    addMatchingObservers(observerSet, mutationObserverEntries(), type);
+    addMatchingObservers(observers, mutationObserverRegistry(), type);
     for (Node* node = parentNode(); node; node = node->parentNode())
-        addMatchingObservers(observerSet, node->mutationObserverEntries(), type | WebKitMutationObserver::Subtree);
-
-    // FIXME: this method should output a HashSet instead of a Vector.
-    if (!observerSet.isEmpty())
-        copyToVector(observerSet, observers);
+        addMatchingObservers(observers, node->mutationObserverRegistry(), type | WebKitMutationObserver::Subtree);
 }
 
-Node::MutationRegistrationResult Node::registerMutationObserver(PassRefPtr<WebKitMutationObserver> observer, MutationObserverOptions options)
+Node::MutationRegistrationResult Node::registerMutationObserver(PassRefPtr<WebKitMutationObserver> observer, MutationObserverOptions options, Node* registrationNode)
 {
-    Vector<MutationObserverEntry>* observerEntries = ensureRareData()->ensureMutationObserverEntries();
-    MutationObserverEntry entry(observer, options);
+    Vector<MutationObserverRegistration>* registry = ensureRareData()->ensureMutationObserverRegistry();
+    MutationObserverRegistration registration(observer, options, registrationNode);
 
-    size_t index = observerEntries->find(entry);
+    size_t index = registry->find(registration);
     if (index == notFound) {
-        observerEntries->append(entry);
+        registry->append(registration);
         return MutationObserverRegistered;
     }
 
-    (*observerEntries)[index].options = entry.options;
+    registry->at(index).options = registration.options;
     return MutationRegistrationOptionsReset;
 }
 
-void Node::unregisterMutationObserver(PassRefPtr<WebKitMutationObserver> observer)
+void Node::unregisterMutationObserver(PassRefPtr<WebKitMutationObserver> observer, Node* registrationNode)
 {
-    Vector<MutationObserverEntry>* observerEntries = mutationObserverEntries();
-    ASSERT(observerEntries);
-    if (!observerEntries)
+    Vector<MutationObserverRegistration>* registry = mutationObserverRegistry();
+    ASSERT(registry);
+    if (!registry)
         return;
 
-    MutationObserverEntry entry(observer, 0);
-    size_t index = observerEntries->find(entry);
+    MutationObserverRegistration registration(observer, 0, registrationNode);
+    size_t index = registry->find(registration);
     ASSERT(index != notFound);
     if (index == notFound)
         return;
 
-    observerEntries->remove(index);
+    registry->remove(index);
 }
+
+void Node::notifyMutationObserversNodeWillDetach()
+{
+    for (Node* node = parentNode(); node; node = node->parentNode()) {
+        Vector<MutationObserverRegistration>* registry = node->mutationObserverRegistry();
+        if (!registry)
+            continue;
+
+        const size_t size = registry->size();
+        for (size_t i = 0; i < size; ++i) {
+            MutationObserverRegistration& registration = registry->at(i);
+            if (!registration.hasAllOptions(WebKitMutationObserver::Subtree))
+                continue;
+
+            Node* registrationNode = registration.registrationNode;
+            if (!registrationNode)
+                registrationNode = node;
+            registration.observer->willDetachNodeInObservedSubtree(registrationNode, registration.options, this);
+        }
+    }
+}
 #endif // ENABLE(MUTATION_OBSERVERS)
 
 

Modified: trunk/Source/WebCore/dom/Node.h (98658 => 98659)


--- trunk/Source/WebCore/dom/Node.h	2011-10-27 23:35:43 UTC (rev 98658)
+++ trunk/Source/WebCore/dom/Node.h	2011-10-27 23:38:09 UTC (rev 98659)
@@ -81,7 +81,7 @@
 class TagNodeList;
 class TreeScope;
 
-struct MutationObserverEntry;
+struct MutationObserverRegistration;
 
 typedef int ExceptionCode;
 
@@ -590,15 +590,17 @@
 #endif
 
 #if ENABLE(MUTATION_OBSERVERS)
-    void getRegisteredMutationObserversOfType(Vector<WebKitMutationObserver*>&, WebKitMutationObserver::MutationType);
+    void getRegisteredMutationObserversOfType(HashMap<WebKitMutationObserver*, MutationObserverOptions>&, WebKitMutationObserver::MutationType);
 
     enum MutationRegistrationResult {
         MutationObserverRegistered,
         MutationRegistrationOptionsReset
     };
-    MutationRegistrationResult registerMutationObserver(PassRefPtr<WebKitMutationObserver>, MutationObserverOptions);
+    MutationRegistrationResult registerMutationObserver(PassRefPtr<WebKitMutationObserver>, MutationObserverOptions, Node* registrationNode = 0);
 
-    void unregisterMutationObserver(PassRefPtr<WebKitMutationObserver>);
+    void unregisterMutationObserver(PassRefPtr<WebKitMutationObserver>, Node* registrationNode = 0);
+
+    void notifyMutationObserversNodeWillDetach();
 #endif // ENABLE(MUTATION_OBSERVERS)
 
 private:
@@ -725,7 +727,7 @@
     void trackForDebugging();
 
 #if ENABLE(MUTATION_OBSERVERS)
-    Vector<MutationObserverEntry>* mutationObserverEntries();
+    Vector<MutationObserverRegistration>* mutationObserverRegistry();
 #endif
 
     mutable uint32_t m_nodeFlags;

Modified: trunk/Source/WebCore/dom/NodeRareData.h (98658 => 98659)


--- trunk/Source/WebCore/dom/NodeRareData.h	2011-10-27 23:35:43 UTC (rev 98658)
+++ trunk/Source/WebCore/dom/NodeRareData.h	2011-10-27 23:38:09 UTC (rev 98659)
@@ -89,16 +89,17 @@
 };
 
 #if ENABLE(MUTATION_OBSERVERS)
-struct MutationObserverEntry {
-    MutationObserverEntry(PassRefPtr<WebKitMutationObserver> observer, MutationObserverOptions options)
+struct MutationObserverRegistration {
+    MutationObserverRegistration(PassRefPtr<WebKitMutationObserver> observer, MutationObserverOptions options, Node* node)
         : observer(observer)
         , options(options)
+        , registrationNode(node)
     {
     }
 
-    bool operator==(const MutationObserverEntry& other) const
+    bool operator==(const MutationObserverRegistration& other) const
     {
-        return observer == other.observer;
+        return observer == other.observer && registrationNode == other.registrationNode;
     }
 
     bool hasAllOptions(MutationObserverOptions options) const
@@ -108,6 +109,12 @@
 
     RefPtr<WebKitMutationObserver> observer;
     MutationObserverOptions options;
+
+    // registrationNode will be 0 if the registration is non-transient. I.e. The registrationNode is the Node in whose
+    // registry it exists.
+    // Note that this doesn't need to be a RefPtr because the observer will be holding a RefPtr to the same node at
+    // least for the lifetime of this Registration in its m_transientObservedNodes map.
+    Node* registrationNode;
 };
 #endif // ENABLE(MUTATION_OBSERVERS)
 
@@ -161,12 +168,12 @@
     }
 
 #if ENABLE(MUTATION_OBSERVERS)
-    Vector<MutationObserverEntry>* mutationObserverEntries() { return m_mutationObservers.get(); }
-    Vector<MutationObserverEntry>* ensureMutationObserverEntries()
+    Vector<MutationObserverRegistration>* mutationObserverRegistry() { return m_mutationObserverRegistry.get(); }
+    Vector<MutationObserverRegistration>* ensureMutationObserverRegistry()
     {
-        if (!m_mutationObservers)
-            m_mutationObservers = adoptPtr(new Vector<MutationObserverEntry>);
-        return m_mutationObservers.get();
+        if (!m_mutationObserverRegistry)
+            m_mutationObserverRegistry = adoptPtr(new Vector<MutationObserverRegistration>);
+        return m_mutationObserverRegistry.get();
     }
 #endif
 
@@ -188,7 +195,7 @@
     bool m_needsFocusAppearanceUpdateSoonAfterAttach : 1;
 
 #if ENABLE(MUTATION_OBSERVERS)
-    OwnPtr<Vector<MutationObserverEntry> > m_mutationObservers;
+    OwnPtr<Vector<MutationObserverRegistration> > m_mutationObserverRegistry;
 #endif
 };
 

Modified: trunk/Source/WebCore/dom/WebKitMutationObserver.cpp (98658 => 98659)


--- trunk/Source/WebCore/dom/WebKitMutationObserver.cpp	2011-10-27 23:35:43 UTC (rev 98658)
+++ trunk/Source/WebCore/dom/WebKitMutationObserver.cpp	2011-10-27 23:38:09 UTC (rev 98659)
@@ -53,14 +53,30 @@
 
 WebKitMutationObserver::~WebKitMutationObserver()
 {
+    clearAllTransientObservations();
 }
 
+static inline void clearTransientObservationsForRegistration(WebKitMutationObserver* observer, Node* registrationNode, PassOwnPtr<NodeHashSet> transientNodes)
+{
+    for (NodeHashSet::iterator iter = transientNodes->begin(); iter != transientNodes->end(); ++iter)
+        (*iter)->unregisterMutationObserver(observer, registrationNode);
+}
+
 void WebKitMutationObserver::observe(Node* node, MutationObserverOptions options)
 {
     // FIXME: More options composition work needs to be done here, e.g., validation.
 
-    if (node->registerMutationObserver(this, options) == Node::MutationObserverRegistered)
+    if (node->registerMutationObserver(this, options) == Node::MutationObserverRegistered) {
         m_observedNodes.append(node);
+        return;
+    }
+
+    HashMap<RefPtr<Node>, NodeHashSet*>::iterator iter = m_transientObservedNodes.find(node);
+    if (iter == m_transientObservedNodes.end())
+        return;
+
+    clearTransientObservationsForRegistration(this, node, adoptPtr(iter->second));
+    m_transientObservedNodes.remove(iter);
 }
 
 void WebKitMutationObserver::disconnect()
@@ -69,10 +85,13 @@
         m_observedNodes[i]->unregisterMutationObserver(this);
 
     m_observedNodes.clear();
+    clearAllTransientObservations();
 }
 
 void WebKitMutationObserver::observedNodeDestructed(Node* node)
 {
+    ASSERT(!m_transientObservedNodes.contains(node));
+
     size_t index = m_observedNodes.find(node);
     ASSERT(index != notFound);
     if (index == notFound)
@@ -95,10 +114,36 @@
     activeMutationObservers().add(this);
 }
 
+void WebKitMutationObserver::willDetachNodeInObservedSubtree(PassRefPtr<Node> prpRegistrationNode, MutationObserverOptions options, PassRefPtr<Node> prpDetachingNode)
+{
+    RefPtr<Node> registrationNode = prpRegistrationNode;
+    RefPtr<Node> detachingNode = prpDetachingNode;
+
+    detachingNode->registerMutationObserver(this, options, registrationNode.get());
+    HashMap<RefPtr<Node>, NodeHashSet*>::iterator iter = m_transientObservedNodes.find(registrationNode);
+    if (iter != m_transientObservedNodes.end()) {
+        iter->second->add(detachingNode);
+        return;
+    }
+
+    OwnPtr<NodeHashSet> set = adoptPtr(new NodeHashSet());
+    set->add(detachingNode);
+    m_transientObservedNodes.set(registrationNode, set.leakPtr());
+}
+
+void WebKitMutationObserver::clearAllTransientObservations()
+{
+    for (HashMap<RefPtr<Node>, NodeHashSet*>::iterator iter = m_transientObservedNodes.begin(); iter != m_transientObservedNodes.end(); ++iter)
+        clearTransientObservationsForRegistration(this, iter->first.get(), adoptPtr(iter->second));
+
+    m_transientObservedNodes.clear();
+}
+
 void WebKitMutationObserver::deliver()
 {
     MutationRecordArray records;
     records.swap(m_records);
+    clearAllTransientObservations();
     m_callback->handleEvent(&records, this);
 }
 

Modified: trunk/Source/WebCore/dom/WebKitMutationObserver.h (98658 => 98659)


--- trunk/Source/WebCore/dom/WebKitMutationObserver.h	2011-10-27 23:35:43 UTC (rev 98658)
+++ trunk/Source/WebCore/dom/WebKitMutationObserver.h	2011-10-27 23:38:09 UTC (rev 98659)
@@ -33,6 +33,8 @@
 
 #if ENABLE(MUTATION_OBSERVERS)
 
+#include <wtf/HashMap.h>
+#include <wtf/HashSet.h>
 #include <wtf/PassOwnPtr.h>
 #include <wtf/RefCounted.h>
 #include <wtf/RefPtr.h>
@@ -45,6 +47,7 @@
 class Node;
 
 typedef unsigned char MutationObserverOptions;
+typedef HashSet<RefPtr<Node> > NodeHashSet;
 
 class WebKitMutationObserver : public RefCounted<WebKitMutationObserver> {
 public:
@@ -68,17 +71,22 @@
 
     void observe(Node*, MutationObserverOptions);
     void disconnect();
+    void willDetachNodeInObservedSubtree(PassRefPtr<Node> registrationNode, MutationObserverOptions, PassRefPtr<Node> detachingNode);
     void observedNodeDestructed(Node*);
     void enqueueMutationRecord(PassRefPtr<MutationRecord>);
 
 private:
     WebKitMutationObserver(PassRefPtr<MutationCallback>);
 
+    void clearAllTransientObservations();
     void deliver();
 
     RefPtr<MutationCallback> m_callback;
     Vector<RefPtr<MutationRecord> > m_records;
     Vector<Node*> m_observedNodes; // NodeRareData has a RefPtr to this, so use a weak pointer to avoid a cycle.
+
+    // FIXME: Change this to be OwnPtr<NodeHashSet> when OwnPtr supports being contained as map values.
+    HashMap<RefPtr<Node>, NodeHashSet*> m_transientObservedNodes;
 };
 
 }
_______________________________________________
webkit-changes mailing list
webkit-changes@lists.webkit.org
http://lists.webkit.org/mailman/listinfo.cgi/webkit-changes

Reply via email to