Title: [102814] trunk
Revision
102814
Author
ad...@chromium.org
Date
2011-12-14 13:14:15 -0800 (Wed, 14 Dec 2011)

Log Message

Broaden support for mutation observation of attributes
https://bugs.webkit.org/show_bug.cgi?id=74448

Reviewed by Ryosuke Niwa.

Source/WebCore:

The previously-landed MutationObserver support for attributes was incomplete:
it didn't support mutations related to Attr nodes (methods on Attrs,
setAttributeNode/removeAttributeNode on Element, or methods on NamedNodeMap).

This patch adds full support of mutation observation for all these cases,
and adds test cases for all these situations.

* dom/Attr.cpp:
(WebCore::Attr::setValue): Enqueue a mutation record when Attr.value is set from JS.
(WebCore::Attr::childrenChanged): Enqueue a mutation record when an Attr's value
changes to due additions/removals of Text children.
* dom/Element.cpp:
(WebCore::Element::enqueueAttributesMutationRecordIfRequested): Previously a static,
expose as part of Element's interface to allow it to be re-used by NamedNodeMap and Attr.
(WebCore::Element::removeAttribute): Remove enqueue call now handled by NamedNodeMap.
(WebCore::Element::setAttributeInternal): Fixup call of enqueueAttributesMutationRecordIfRequested.
* dom/Element.h:
* dom/NamedNodeMap.cpp:
(WebCore::NamedNodeMap::setNamedItem): Enqueue a mutation record when an attribute
is changed via Element.attributes.setNamedItem from JS.
(WebCore::NamedNodeMap::removeNamedItem): Enqueue a mutation record when an
attribute is removed, either via Element.attributes.removeNamedItem or Element.removeAttribute.

LayoutTests:

Add tests covering attribute mutation via Attr nodes.

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

Modified Paths

Diff

Modified: trunk/LayoutTests/ChangeLog (102813 => 102814)


--- trunk/LayoutTests/ChangeLog	2011-12-14 20:59:53 UTC (rev 102813)
+++ trunk/LayoutTests/ChangeLog	2011-12-14 21:14:15 UTC (rev 102814)
@@ -1,3 +1,15 @@
+2011-12-14  Adam Klein  <ad...@chromium.org>
+
+        Broaden support for mutation observation of attributes
+        https://bugs.webkit.org/show_bug.cgi?id=74448
+
+        Reviewed by Ryosuke Niwa.
+
+        Add tests covering attribute mutation via Attr nodes.
+
+        * fast/mutation/observe-attributes-expected.txt:
+        * fast/mutation/observe-attributes.html:
+
 2011-12-14  Eric Carlson  <eric.carl...@apple.com>
 
         Media url with fragment may not load

Modified: trunk/LayoutTests/fast/mutation/observe-attributes-expected.txt (102813 => 102814)


--- trunk/LayoutTests/fast/mutation/observe-attributes-expected.txt	2011-12-14 20:59:53 UTC (rev 102813)
+++ trunk/LayoutTests/fast/mutation/observe-attributes-expected.txt	2011-12-14 21:14:15 UTC (rev 102814)
@@ -90,13 +90,16 @@
 PASS mutations[0].oldValue is "bar"
 
 Testing setting an attribute via reflected IDL attribute.
-PASS mutations.length is 2
+PASS mutations.length is 3
 PASS mutations[0].type is "attributes"
 PASS mutations[0].attributeName is "id"
 PASS mutations[0].oldValue is null
 PASS mutations[1].type is "attributes"
 PASS mutations[1].attributeName is "id"
 PASS mutations[1].oldValue is "foo"
+PASS mutations[2].type is "attributes"
+PASS mutations[2].attributeName is "id"
+PASS mutations[2].oldValue is "bar"
 
 Testing that attributeFilter works as expected and ignores case with HTML elements.
 ...only foo, bar & boom should be received.
@@ -189,6 +192,53 @@
 Testing that a no-op style property mutation does not create Mutation Records.
 PASS mutations is null
 
+Test that mutating an attribute through an attr node delivers mutation records
+PASS mutations.length is 1
+PASS mutations[0].target is div
+PASS mutations[0].type is "attributes"
+PASS mutations[0].attributeName is "data-test"
+PASS mutations[0].oldValue is "foo"
+
+Test that mutating an attribute by attaching a child to an attr node delivers mutation records
+PASS mutations.length is 1
+PASS mutations[0].target is div
+PASS mutations[0].type is "attributes"
+PASS mutations[0].attributeName is "data-test"
+PASS mutations[0].oldValue is "foo"
+
+Test that mutating via setAttributeNode delivers mutation records
+PASS mutations.length is 3
+PASS mutations[0].target is div
+PASS mutations[0].type is "attributes"
+PASS mutations[0].attributeName is "data-test"
+PASS mutations[0].oldValue is "foo"
+PASS mutations[1].target is div
+PASS mutations[1].type is "attributes"
+PASS mutations[1].attributeName is "data-other"
+PASS mutations[1].oldValue is null
+PASS mutations[2].target is div
+PASS mutations[2].type is "attributes"
+PASS mutations[2].attributeName is "id"
+PASS mutations[2].oldValue is "myId"
+
+Test that setAttribute on an attribute with an existing Attr delivers mutation records
+PASS mutations.length is 1
+PASS mutations[0].target is div
+PASS mutations[0].type is "attributes"
+PASS mutations[0].attributeName is "data-test"
+PASS mutations[0].oldValue is "foo"
+
+Test that setNamedItem and removeNamedItem deliver mutation records
+PASS mutations.length is 2
+PASS mutations[0].target is div
+PASS mutations[0].type is "attributes"
+PASS mutations[0].attributeName is "data-test"
+PASS mutations[0].oldValue is "foo"
+PASS mutations[1].target is div
+PASS mutations[1].type is "attributes"
+PASS mutations[1].attributeName is "data-test"
+PASS mutations[1].oldValue is "bar"
+
 PASS successfullyParsed is true
 
 TEST COMPLETE

Modified: trunk/LayoutTests/fast/mutation/observe-attributes.html (102813 => 102814)


--- trunk/LayoutTests/fast/mutation/observe-attributes.html	2011-12-14 20:59:53 UTC (rev 102813)
+++ trunk/LayoutTests/fast/mutation/observe-attributes.html	2011-12-14 21:14:15 UTC (rev 102814)
@@ -1,17 +1,11 @@
 <!DOCTYPE html>
-<html>
-<head>
-<meta charset="utf-8">
 <script src=""
-</head>
-<body>
-<p id=description></p>
-<div id="console"></div>
 <script>
 
 window.jsTestIsAsync = true;
 var mutations, mutations2, mutationsWithOldValue;
 var calls;
+var div;
 
 function testBasic() {
     var div;
@@ -429,17 +423,21 @@
         observer.observe(div, { attributes: true, attributeOldValue: true });
         div.id = 'foo';
         div.id = 'bar';
+        div.id = null;
         setTimeout(finish, 0);
     }
 
     function finish() {
-        shouldBe('mutations.length', '2');
+        shouldBe('mutations.length', '3');
         shouldBe('mutations[0].type', '"attributes"');
         shouldBe('mutations[0].attributeName', '"id"');
         shouldBe('mutations[0].oldValue', 'null');
         shouldBe('mutations[1].type', '"attributes"');
         shouldBe('mutations[1].attributeName', '"id"');
         shouldBe('mutations[1].oldValue', '"foo"');
+        shouldBe('mutations[2].type', '"attributes"');
+        shouldBe('mutations[2].attributeName', '"id"');
+        shouldBe('mutations[2].oldValue', '"bar"');
         observer.disconnect();
         debug('');
         runNextTest();
@@ -786,6 +784,200 @@
     start();
 }
 
+function testMutateThroughAttrNodeValue() {
+    var observer;
+
+    function start() {
+        debug('Test that mutating an attribute through an attr node delivers mutation records');
+
+        mutations = null;
+        observer = new WebKitMutationObserver(function(mutations) {
+            window.mutations = mutations;
+        });
+
+        div = document.createElement('div');
+        div.setAttribute('data-test', 'foo');
+        observer.observe(div, { attributes: true, attributeOldValue: true });
+        div.attributes['data-test'].value = 'bar';
+
+        setTimeout(finish, 0);
+    }
+
+    function finish() {
+        shouldBe('mutations.length', '1');
+        shouldBe('mutations[0].target', 'div');
+        shouldBe('mutations[0].type', '"attributes"');
+        shouldBe('mutations[0].attributeName', '"data-test"');
+        shouldBe('mutations[0].oldValue', '"foo"');
+
+        observer.disconnect();
+        debug('');
+        runNextTest();
+    }
+
+    start();
+}
+
+function testMutateThroughAttrNodeChild() {
+    var observer;
+
+    function start() {
+        debug('Test that mutating an attribute by attaching a child to an attr node delivers mutation records');
+
+        mutations = null;
+        observer = new WebKitMutationObserver(function(mutations) {
+            window.mutations = mutations;
+        });
+
+        div = document.createElement('div');
+        div.setAttribute('data-test', 'foo');
+        observer.observe(div, { attributes: true, attributeOldValue: true });
+        div.attributes['data-test'].appendChild(document.createTextNode('bar'));
+
+        setTimeout(finish, 0);
+    }
+
+    function finish() {
+        shouldBe('mutations.length', '1');
+        shouldBe('mutations[0].target', 'div');
+        shouldBe('mutations[0].type', '"attributes"');
+        shouldBe('mutations[0].attributeName', '"data-test"');
+        shouldBe('mutations[0].oldValue', '"foo"');
+
+        observer.disconnect();
+        debug('');
+        runNextTest();
+    }
+
+    start();
+}
+
+function testSetAndRemoveAttributeNode() {
+    var observer;
+
+    function start() {
+        debug('Test that mutating via setAttributeNode delivers mutation records');
+
+        mutations = null;
+        observer = new WebKitMutationObserver(function(mutations) {
+            window.mutations = mutations;
+        });
+
+        div = document.createElement('div');
+        div.id = 'myId';
+        div.setAttribute('data-test', 'foo');
+        observer.observe(div, { attributes: true, attributeOldValue: true });
+        var attr = document.createAttribute('data-test');
+        attr.value = 'bar';
+        div.setAttributeNode(attr);
+        attr = document.createAttribute('data-other');
+        attr.value = 'baz';
+        div.setAttributeNode(attr);
+        div.removeAttributeNode(div.attributes['id']);
+
+        setTimeout(finish, 0);
+    }
+
+    function finish() {
+        shouldBe('mutations.length', '3');
+        shouldBe('mutations[0].target', 'div');
+        shouldBe('mutations[0].type', '"attributes"');
+        shouldBe('mutations[0].attributeName', '"data-test"');
+        shouldBe('mutations[0].oldValue', '"foo"');
+        shouldBe('mutations[1].target', 'div');
+        shouldBe('mutations[1].type', '"attributes"');
+        shouldBe('mutations[1].attributeName', '"data-other"');
+        shouldBe('mutations[1].oldValue', 'null');
+        shouldBe('mutations[2].target', 'div');
+        shouldBe('mutations[2].type', '"attributes"');
+        shouldBe('mutations[2].attributeName', '"id"');
+        shouldBe('mutations[2].oldValue', '"myId"');
+
+        observer.disconnect();
+        debug('');
+        runNextTest();
+    }
+
+    start();
+}
+
+function testMixedNodeAndElementOperations() {
+    var observer;
+
+    function start() {
+        debug('Test that setAttribute on an attribute with an existing Attr delivers mutation records');
+
+        mutations = null;
+        observer = new WebKitMutationObserver(function(mutations) {
+            window.mutations = mutations;
+        });
+
+        div = document.createElement('div');
+        var attr = document.createAttribute('data-test');
+        attr.value = 'foo';
+        div.setAttributeNode(attr);
+        observer.observe(div, { attributes: true, attributeOldValue: true });
+        div.setAttribute('data-test', 'bar');
+
+        setTimeout(finish, 0);
+    }
+
+    function finish() {
+        shouldBe('mutations.length', '1');
+        shouldBe('mutations[0].target', 'div');
+        shouldBe('mutations[0].type', '"attributes"');
+        shouldBe('mutations[0].attributeName', '"data-test"');
+        shouldBe('mutations[0].oldValue', '"foo"');
+
+        observer.disconnect();
+        debug('');
+        runNextTest();
+    }
+
+    start();
+}
+
+function testNamedNodeMapOperations() {
+    var observer;
+
+    function start() {
+        debug('Test that setNamedItem and removeNamedItem deliver mutation records');
+
+        mutations = null;
+        observer = new WebKitMutationObserver(function(mutations) {
+            window.mutations = mutations;
+        });
+
+        div = document.createElement('div');
+        div.setAttribute('data-test', 'foo');
+        observer.observe(div, { attributes: true, attributeOldValue: true });
+        var attr = document.createAttribute('data-test');
+        attr.value = 'bar';
+        div.attributes.setNamedItem(attr);
+        div.attributes.removeNamedItem('data-test');
+
+        setTimeout(finish, 0);
+    }
+
+    function finish() {
+        shouldBe('mutations.length', '2');
+        shouldBe('mutations[0].target', 'div');
+        shouldBe('mutations[0].type', '"attributes"');
+        shouldBe('mutations[0].attributeName', '"data-test"');
+        shouldBe('mutations[0].oldValue', '"foo"');
+        shouldBe('mutations[1].target', 'div');
+        shouldBe('mutations[1].type', '"attributes"');
+        shouldBe('mutations[1].attributeName', '"data-test"');
+        shouldBe('mutations[1].oldValue', '"bar"');
+
+        observer.disconnect();
+        debug('');
+        runNextTest();
+    }
+
+    start();
+}
+
 var tests = [
     testBasic,
     testWrongType,
@@ -804,7 +996,12 @@
     testAttributeFilterNonHTMLDocument,
     testStyleAttributePropertyAccess,
     testStyleAttributePropertyAccessOldValue,
-    testStyleAttributePropertyAccessIgnoreNoop
+    testStyleAttributePropertyAccessIgnoreNoop,
+    testMutateThroughAttrNodeValue,
+    testMutateThroughAttrNodeChild,
+    testSetAndRemoveAttributeNode,
+    testMixedNodeAndElementOperations,
+    testNamedNodeMapOperations
 ];
 var testIndex = 0;
 
@@ -824,5 +1021,3 @@
 
 </script>
 <script src=""
-</body>
-</html>

Modified: trunk/Source/WebCore/ChangeLog (102813 => 102814)


--- trunk/Source/WebCore/ChangeLog	2011-12-14 20:59:53 UTC (rev 102813)
+++ trunk/Source/WebCore/ChangeLog	2011-12-14 21:14:15 UTC (rev 102814)
@@ -1,3 +1,33 @@
+2011-12-14  Adam Klein  <ad...@chromium.org>
+
+        Broaden support for mutation observation of attributes
+        https://bugs.webkit.org/show_bug.cgi?id=74448
+
+        Reviewed by Ryosuke Niwa.
+
+        The previously-landed MutationObserver support for attributes was incomplete:
+        it didn't support mutations related to Attr nodes (methods on Attrs,
+        setAttributeNode/removeAttributeNode on Element, or methods on NamedNodeMap).
+
+        This patch adds full support of mutation observation for all these cases,
+        and adds test cases for all these situations.
+
+        * dom/Attr.cpp:
+        (WebCore::Attr::setValue): Enqueue a mutation record when Attr.value is set from JS.
+        (WebCore::Attr::childrenChanged): Enqueue a mutation record when an Attr's value
+        changes to due additions/removals of Text children.
+        * dom/Element.cpp:
+        (WebCore::Element::enqueueAttributesMutationRecordIfRequested): Previously a static,
+        expose as part of Element's interface to allow it to be re-used by NamedNodeMap and Attr.
+        (WebCore::Element::removeAttribute): Remove enqueue call now handled by NamedNodeMap.
+        (WebCore::Element::setAttributeInternal): Fixup call of enqueueAttributesMutationRecordIfRequested.
+        * dom/Element.h:
+        * dom/NamedNodeMap.cpp:
+        (WebCore::NamedNodeMap::setNamedItem): Enqueue a mutation record when an attribute
+        is changed via Element.attributes.setNamedItem from JS.
+        (WebCore::NamedNodeMap::removeNamedItem): Enqueue a mutation record when an
+        attribute is removed, either via Element.attributes.removeNamedItem or Element.removeAttribute.
+
 2011-12-14  Raymond Toy  <r...@google.com>
 
         * platform/audio/Distance.h (WebCore): 

Modified: trunk/Source/WebCore/dom/Attr.cpp (102813 => 102814)


--- trunk/Source/WebCore/dom/Attr.cpp	2011-12-14 20:59:53 UTC (rev 102813)
+++ trunk/Source/WebCore/dom/Attr.cpp	2011-12-14 21:14:15 UTC (rev 102814)
@@ -130,6 +130,11 @@
 
 void Attr::setValue(const AtomicString& value, ExceptionCode&)
 {
+#if ENABLE(MUTATION_OBSERVERS)
+    if (m_element)
+        m_element->enqueueAttributesMutationRecordIfRequested(m_attribute->name(), m_attribute->value());
+#endif
+
     if (m_element && m_element->isIdAttributeName(m_attribute->name()))
         m_element->updateId(m_element->getIdAttribute(), value);
 
@@ -167,7 +172,12 @@
 {
     if (m_ignoreChildrenChanged > 0)
         return;
- 
+
+#if ENABLE(MUTATION_OBSERVERS)
+    if (m_element)
+        m_element->enqueueAttributesMutationRecordIfRequested(m_attribute->name(), m_attribute->value());
+#endif
+
     Node::childrenChanged(changedByParser, beforeChange, afterChange, childCountDelta);
 
     invalidateNodeListsCacheAfterAttributeChanged();

Modified: trunk/Source/WebCore/dom/Element.cpp (102813 => 102814)


--- trunk/Source/WebCore/dom/Element.cpp	2011-12-14 20:59:53 UTC (rev 102813)
+++ trunk/Source/WebCore/dom/Element.cpp	2011-12-14 21:14:15 UTC (rev 102814)
@@ -181,10 +181,12 @@
 }
 
 #if ENABLE(MUTATION_OBSERVERS)
-static void enqueueAttributesMutationRecord(Element* target, const QualifiedName& attributeName, const AtomicString& oldValue)
+void Element::enqueueAttributesMutationRecordIfRequested(const QualifiedName& attributeName, const AtomicString& oldValue)
 {
-    if (OwnPtr<MutationObserverInterestGroup> mutationRecipients = MutationObserverInterestGroup::createForAttributesMutation(target, attributeName))
-        mutationRecipients->enqueueMutationRecord(MutationRecord::createAttributes(target, attributeName, oldValue));
+    if (isSynchronizingStyleAttribute())
+        return;
+    if (OwnPtr<MutationObserverInterestGroup> mutationRecipients = MutationObserverInterestGroup::createForAttributesMutation(this, attributeName))
+        mutationRecipients->enqueueMutationRecord(MutationRecord::createAttributes(this, attributeName, oldValue));
 }
 #endif
 
@@ -195,10 +197,6 @@
         RefPtr<Node> attrNode = m_attributeMap->removeNamedItem(name, ec);
         if (ec == NOT_FOUND_ERR)
             ec = 0;
-#if ENABLE(MUTATION_OBSERVERS)
-        else
-            enqueueAttributesMutationRecord(this, name, attrNode->nodeValue());
-#endif
     }
 }
 
@@ -656,14 +654,12 @@
         InspectorInstrumentation::willModifyDOMAttr(document(), this);
 #endif
 
-    document()->incDOMTreeVersion();
-
 #if ENABLE(MUTATION_OBSERVERS)
-    // The call to attributeChanged below may dispatch DOMSubtreeModified, so it's important to enqueue a MutationRecord now.
-    if (!isSynchronizingStyleAttribute())
-        enqueueAttributesMutationRecord(this, name, old ? old->value() : nullAtom);
+    enqueueAttributesMutationRecordIfRequested(name, old ? old->value() : nullAtom);
 #endif
 
+    document()->incDOMTreeVersion();
+
     if (isIdAttributeName(name))
         updateId(old ? old->value() : nullAtom, value);
 
@@ -1523,10 +1519,6 @@
         RefPtr<Node> attrNode = m_attributeMap->removeNamedItem(localName, ec);
         if (ec == NOT_FOUND_ERR)
             ec = 0;
-#if ENABLE(MUTATION_OBSERVERS)
-        else
-            enqueueAttributesMutationRecord(this, QualifiedName(nullAtom, localName, nullAtom), attrNode->nodeValue());
-#endif
     }
     
     InspectorInstrumentation::didRemoveDOMAttr(document(), this, name);

Modified: trunk/Source/WebCore/dom/Element.h (102813 => 102814)


--- trunk/Source/WebCore/dom/Element.h	2011-12-14 20:59:53 UTC (rev 102813)
+++ trunk/Source/WebCore/dom/Element.h	2011-12-14 21:14:15 UTC (rev 102814)
@@ -364,6 +364,10 @@
     
     PassRefPtr<RenderStyle> styleForRenderer();
 
+#if ENABLE(MUTATION_OBSERVERS)
+    void enqueueAttributesMutationRecordIfRequested(const QualifiedName&, const AtomicString& oldValue);
+#endif
+
 protected:
     Element(const QualifiedName& tagName, Document* document, ConstructionType type)
         : ContainerNode(document, type)

Modified: trunk/Source/WebCore/dom/NamedNodeMap.cpp (102813 => 102814)


--- trunk/Source/WebCore/dom/NamedNodeMap.cpp	2011-12-14 20:59:53 UTC (rev 102813)
+++ trunk/Source/WebCore/dom/NamedNodeMap.cpp	2011-12-14 21:14:15 UTC (rev 102814)
@@ -119,6 +119,10 @@
         return 0;
     }
 
+#if ENABLE(MUTATION_OBSERVERS)
+    m_element->enqueueAttributesMutationRecordIfRequested(attribute->name(), oldAttribute ? oldAttribute->value() : nullAtom);
+#endif
+
     if (attr->isId())
         m_element->updateId(oldAttribute ? oldAttribute->value() : nullAtom, attribute->value());
 
@@ -149,6 +153,11 @@
         return 0;
     }
 
+#if ENABLE(MUTATION_OBSERVERS)
+    if (m_element)
+        m_element->enqueueAttributesMutationRecordIfRequested(attribute->name(), attribute->value());
+#endif
+
     RefPtr<Attr> attr = attribute->createAttrIfNeeded(m_element);
 
     if (attr->isId())
_______________________________________________
webkit-changes mailing list
webkit-changes@lists.webkit.org
http://lists.webkit.org/mailman/listinfo.cgi/webkit-changes

Reply via email to