Changeset 102814 in webkit


Ignore:
Timestamp:
Dec 14, 2011 1:14:15 PM (12 years ago)
Author:
adamk@chromium.org
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:
Location:
trunk
Files:
8 edited

Legend:

Unmodified
Added
Removed
  • trunk/LayoutTests/ChangeLog

    r102800 r102814  
     12011-12-14  Adam Klein  <adamk@chromium.org>
     2
     3        Broaden support for mutation observation of attributes
     4        https://bugs.webkit.org/show_bug.cgi?id=74448
     5
     6        Reviewed by Ryosuke Niwa.
     7
     8        Add tests covering attribute mutation via Attr nodes.
     9
     10        * fast/mutation/observe-attributes-expected.txt:
     11        * fast/mutation/observe-attributes.html:
     12
    1132011-12-14  Eric Carlson  <eric.carlson@apple.com>
    214
  • trunk/LayoutTests/fast/mutation/observe-attributes-expected.txt

    r102264 r102814  
    9191
    9292Testing setting an attribute via reflected IDL attribute.
    93 PASS mutations.length is 2
     93PASS mutations.length is 3
    9494PASS mutations[0].type is "attributes"
    9595PASS mutations[0].attributeName is "id"
     
    9898PASS mutations[1].attributeName is "id"
    9999PASS mutations[1].oldValue is "foo"
     100PASS mutations[2].type is "attributes"
     101PASS mutations[2].attributeName is "id"
     102PASS mutations[2].oldValue is "bar"
    100103
    101104Testing that attributeFilter works as expected and ignores case with HTML elements.
     
    190193PASS mutations is null
    191194
     195Test that mutating an attribute through an attr node delivers mutation records
     196PASS mutations.length is 1
     197PASS mutations[0].target is div
     198PASS mutations[0].type is "attributes"
     199PASS mutations[0].attributeName is "data-test"
     200PASS mutations[0].oldValue is "foo"
     201
     202Test that mutating an attribute by attaching a child to an attr node delivers mutation records
     203PASS mutations.length is 1
     204PASS mutations[0].target is div
     205PASS mutations[0].type is "attributes"
     206PASS mutations[0].attributeName is "data-test"
     207PASS mutations[0].oldValue is "foo"
     208
     209Test that mutating via setAttributeNode delivers mutation records
     210PASS mutations.length is 3
     211PASS mutations[0].target is div
     212PASS mutations[0].type is "attributes"
     213PASS mutations[0].attributeName is "data-test"
     214PASS mutations[0].oldValue is "foo"
     215PASS mutations[1].target is div
     216PASS mutations[1].type is "attributes"
     217PASS mutations[1].attributeName is "data-other"
     218PASS mutations[1].oldValue is null
     219PASS mutations[2].target is div
     220PASS mutations[2].type is "attributes"
     221PASS mutations[2].attributeName is "id"
     222PASS mutations[2].oldValue is "myId"
     223
     224Test that setAttribute on an attribute with an existing Attr delivers mutation records
     225PASS mutations.length is 1
     226PASS mutations[0].target is div
     227PASS mutations[0].type is "attributes"
     228PASS mutations[0].attributeName is "data-test"
     229PASS mutations[0].oldValue is "foo"
     230
     231Test that setNamedItem and removeNamedItem deliver mutation records
     232PASS mutations.length is 2
     233PASS mutations[0].target is div
     234PASS mutations[0].type is "attributes"
     235PASS mutations[0].attributeName is "data-test"
     236PASS mutations[0].oldValue is "foo"
     237PASS mutations[1].target is div
     238PASS mutations[1].type is "attributes"
     239PASS mutations[1].attributeName is "data-test"
     240PASS mutations[1].oldValue is "bar"
     241
    192242PASS successfullyParsed is true
    193243
  • trunk/LayoutTests/fast/mutation/observe-attributes.html

    r102264 r102814  
    11<!DOCTYPE html>
    2 <html>
    3 <head>
    4 <meta charset="utf-8">
    52<script src="../js/resources/js-test-pre.js"></script>
    6 </head>
    7 <body>
    8 <p id=description></p>
    9 <div id="console"></div>
    103<script>
    114
     
    136var mutations, mutations2, mutationsWithOldValue;
    147var calls;
     8var div;
    159
    1610function testBasic() {
     
    430424        div.id = 'foo';
    431425        div.id = 'bar';
    432         setTimeout(finish, 0);
    433     }
    434 
    435     function finish() {
    436         shouldBe('mutations.length', '2');
     426        div.id = null;
     427        setTimeout(finish, 0);
     428    }
     429
     430    function finish() {
     431        shouldBe('mutations.length', '3');
    437432        shouldBe('mutations[0].type', '"attributes"');
    438433        shouldBe('mutations[0].attributeName', '"id"');
     
    441436        shouldBe('mutations[1].attributeName', '"id"');
    442437        shouldBe('mutations[1].oldValue', '"foo"');
     438        shouldBe('mutations[2].type', '"attributes"');
     439        shouldBe('mutations[2].attributeName', '"id"');
     440        shouldBe('mutations[2].oldValue', '"bar"');
    443441        observer.disconnect();
    444442        debug('');
     
    778776    function finish() {
    779777        shouldBe('mutations', 'null');
     778
     779        observer.disconnect();
     780        debug('');
     781        runNextTest();
     782    }
     783
     784    start();
     785}
     786
     787function testMutateThroughAttrNodeValue() {
     788    var observer;
     789
     790    function start() {
     791        debug('Test that mutating an attribute through an attr node delivers mutation records');
     792
     793        mutations = null;
     794        observer = new WebKitMutationObserver(function(mutations) {
     795            window.mutations = mutations;
     796        });
     797
     798        div = document.createElement('div');
     799        div.setAttribute('data-test', 'foo');
     800        observer.observe(div, { attributes: true, attributeOldValue: true });
     801        div.attributes['data-test'].value = 'bar';
     802
     803        setTimeout(finish, 0);
     804    }
     805
     806    function finish() {
     807        shouldBe('mutations.length', '1');
     808        shouldBe('mutations[0].target', 'div');
     809        shouldBe('mutations[0].type', '"attributes"');
     810        shouldBe('mutations[0].attributeName', '"data-test"');
     811        shouldBe('mutations[0].oldValue', '"foo"');
     812
     813        observer.disconnect();
     814        debug('');
     815        runNextTest();
     816    }
     817
     818    start();
     819}
     820
     821function testMutateThroughAttrNodeChild() {
     822    var observer;
     823
     824    function start() {
     825        debug('Test that mutating an attribute by attaching a child to an attr node delivers mutation records');
     826
     827        mutations = null;
     828        observer = new WebKitMutationObserver(function(mutations) {
     829            window.mutations = mutations;
     830        });
     831
     832        div = document.createElement('div');
     833        div.setAttribute('data-test', 'foo');
     834        observer.observe(div, { attributes: true, attributeOldValue: true });
     835        div.attributes['data-test'].appendChild(document.createTextNode('bar'));
     836
     837        setTimeout(finish, 0);
     838    }
     839
     840    function finish() {
     841        shouldBe('mutations.length', '1');
     842        shouldBe('mutations[0].target', 'div');
     843        shouldBe('mutations[0].type', '"attributes"');
     844        shouldBe('mutations[0].attributeName', '"data-test"');
     845        shouldBe('mutations[0].oldValue', '"foo"');
     846
     847        observer.disconnect();
     848        debug('');
     849        runNextTest();
     850    }
     851
     852    start();
     853}
     854
     855function testSetAndRemoveAttributeNode() {
     856    var observer;
     857
     858    function start() {
     859        debug('Test that mutating via setAttributeNode delivers mutation records');
     860
     861        mutations = null;
     862        observer = new WebKitMutationObserver(function(mutations) {
     863            window.mutations = mutations;
     864        });
     865
     866        div = document.createElement('div');
     867        div.id = 'myId';
     868        div.setAttribute('data-test', 'foo');
     869        observer.observe(div, { attributes: true, attributeOldValue: true });
     870        var attr = document.createAttribute('data-test');
     871        attr.value = 'bar';
     872        div.setAttributeNode(attr);
     873        attr = document.createAttribute('data-other');
     874        attr.value = 'baz';
     875        div.setAttributeNode(attr);
     876        div.removeAttributeNode(div.attributes['id']);
     877
     878        setTimeout(finish, 0);
     879    }
     880
     881    function finish() {
     882        shouldBe('mutations.length', '3');
     883        shouldBe('mutations[0].target', 'div');
     884        shouldBe('mutations[0].type', '"attributes"');
     885        shouldBe('mutations[0].attributeName', '"data-test"');
     886        shouldBe('mutations[0].oldValue', '"foo"');
     887        shouldBe('mutations[1].target', 'div');
     888        shouldBe('mutations[1].type', '"attributes"');
     889        shouldBe('mutations[1].attributeName', '"data-other"');
     890        shouldBe('mutations[1].oldValue', 'null');
     891        shouldBe('mutations[2].target', 'div');
     892        shouldBe('mutations[2].type', '"attributes"');
     893        shouldBe('mutations[2].attributeName', '"id"');
     894        shouldBe('mutations[2].oldValue', '"myId"');
     895
     896        observer.disconnect();
     897        debug('');
     898        runNextTest();
     899    }
     900
     901    start();
     902}
     903
     904function testMixedNodeAndElementOperations() {
     905    var observer;
     906
     907    function start() {
     908        debug('Test that setAttribute on an attribute with an existing Attr delivers mutation records');
     909
     910        mutations = null;
     911        observer = new WebKitMutationObserver(function(mutations) {
     912            window.mutations = mutations;
     913        });
     914
     915        div = document.createElement('div');
     916        var attr = document.createAttribute('data-test');
     917        attr.value = 'foo';
     918        div.setAttributeNode(attr);
     919        observer.observe(div, { attributes: true, attributeOldValue: true });
     920        div.setAttribute('data-test', 'bar');
     921
     922        setTimeout(finish, 0);
     923    }
     924
     925    function finish() {
     926        shouldBe('mutations.length', '1');
     927        shouldBe('mutations[0].target', 'div');
     928        shouldBe('mutations[0].type', '"attributes"');
     929        shouldBe('mutations[0].attributeName', '"data-test"');
     930        shouldBe('mutations[0].oldValue', '"foo"');
     931
     932        observer.disconnect();
     933        debug('');
     934        runNextTest();
     935    }
     936
     937    start();
     938}
     939
     940function testNamedNodeMapOperations() {
     941    var observer;
     942
     943    function start() {
     944        debug('Test that setNamedItem and removeNamedItem deliver mutation records');
     945
     946        mutations = null;
     947        observer = new WebKitMutationObserver(function(mutations) {
     948            window.mutations = mutations;
     949        });
     950
     951        div = document.createElement('div');
     952        div.setAttribute('data-test', 'foo');
     953        observer.observe(div, { attributes: true, attributeOldValue: true });
     954        var attr = document.createAttribute('data-test');
     955        attr.value = 'bar';
     956        div.attributes.setNamedItem(attr);
     957        div.attributes.removeNamedItem('data-test');
     958
     959        setTimeout(finish, 0);
     960    }
     961
     962    function finish() {
     963        shouldBe('mutations.length', '2');
     964        shouldBe('mutations[0].target', 'div');
     965        shouldBe('mutations[0].type', '"attributes"');
     966        shouldBe('mutations[0].attributeName', '"data-test"');
     967        shouldBe('mutations[0].oldValue', '"foo"');
     968        shouldBe('mutations[1].target', 'div');
     969        shouldBe('mutations[1].type', '"attributes"');
     970        shouldBe('mutations[1].attributeName', '"data-test"');
     971        shouldBe('mutations[1].oldValue', '"bar"');
    780972
    781973        observer.disconnect();
     
    805997    testStyleAttributePropertyAccess,
    806998    testStyleAttributePropertyAccessOldValue,
    807     testStyleAttributePropertyAccessIgnoreNoop
     999    testStyleAttributePropertyAccessIgnoreNoop,
     1000    testMutateThroughAttrNodeValue,
     1001    testMutateThroughAttrNodeChild,
     1002    testSetAndRemoveAttributeNode,
     1003    testMixedNodeAndElementOperations,
     1004    testNamedNodeMapOperations
    8081005];
    8091006var testIndex = 0;
     
    8251022</script>
    8261023<script src="../js/resources/js-test-post.js"></script>
    827 </body>
    828 </html>
  • trunk/Source/WebCore/ChangeLog

    r102810 r102814  
     12011-12-14  Adam Klein  <adamk@chromium.org>
     2
     3        Broaden support for mutation observation of attributes
     4        https://bugs.webkit.org/show_bug.cgi?id=74448
     5
     6        Reviewed by Ryosuke Niwa.
     7
     8        The previously-landed MutationObserver support for attributes was incomplete:
     9        it didn't support mutations related to Attr nodes (methods on Attrs,
     10        setAttributeNode/removeAttributeNode on Element, or methods on NamedNodeMap).
     11
     12        This patch adds full support of mutation observation for all these cases,
     13        and adds test cases for all these situations.
     14
     15        * dom/Attr.cpp:
     16        (WebCore::Attr::setValue): Enqueue a mutation record when Attr.value is set from JS.
     17        (WebCore::Attr::childrenChanged): Enqueue a mutation record when an Attr's value
     18        changes to due additions/removals of Text children.
     19        * dom/Element.cpp:
     20        (WebCore::Element::enqueueAttributesMutationRecordIfRequested): Previously a static,
     21        expose as part of Element's interface to allow it to be re-used by NamedNodeMap and Attr.
     22        (WebCore::Element::removeAttribute): Remove enqueue call now handled by NamedNodeMap.
     23        (WebCore::Element::setAttributeInternal): Fixup call of enqueueAttributesMutationRecordIfRequested.
     24        * dom/Element.h:
     25        * dom/NamedNodeMap.cpp:
     26        (WebCore::NamedNodeMap::setNamedItem): Enqueue a mutation record when an attribute
     27        is changed via Element.attributes.setNamedItem from JS.
     28        (WebCore::NamedNodeMap::removeNamedItem): Enqueue a mutation record when an
     29        attribute is removed, either via Element.attributes.removeNamedItem or Element.removeAttribute.
     30
    1312011-12-14  Raymond Toy  <rtoy@google.com>
    232
  • trunk/Source/WebCore/dom/Attr.cpp

    r102431 r102814  
    131131void Attr::setValue(const AtomicString& value, ExceptionCode&)
    132132{
     133#if ENABLE(MUTATION_OBSERVERS)
     134    if (m_element)
     135        m_element->enqueueAttributesMutationRecordIfRequested(m_attribute->name(), m_attribute->value());
     136#endif
     137
    133138    if (m_element && m_element->isIdAttributeName(m_attribute->name()))
    134139        m_element->updateId(m_element->getIdAttribute(), value);
     
    168173    if (m_ignoreChildrenChanged > 0)
    169174        return;
    170  
     175
     176#if ENABLE(MUTATION_OBSERVERS)
     177    if (m_element)
     178        m_element->enqueueAttributesMutationRecordIfRequested(m_attribute->name(), m_attribute->value());
     179#endif
     180
    171181    Node::childrenChanged(changedByParser, beforeChange, afterChange, childCountDelta);
    172182
  • trunk/Source/WebCore/dom/Element.cpp

    r102721 r102814  
    182182
    183183#if ENABLE(MUTATION_OBSERVERS)
    184 static void enqueueAttributesMutationRecord(Element* target, const QualifiedName& attributeName, const AtomicString& oldValue)
    185 {
    186     if (OwnPtr<MutationObserverInterestGroup> mutationRecipients = MutationObserverInterestGroup::createForAttributesMutation(target, attributeName))
    187         mutationRecipients->enqueueMutationRecord(MutationRecord::createAttributes(target, attributeName, oldValue));
     184void Element::enqueueAttributesMutationRecordIfRequested(const QualifiedName& attributeName, const AtomicString& oldValue)
     185{
     186    if (isSynchronizingStyleAttribute())
     187        return;
     188    if (OwnPtr<MutationObserverInterestGroup> mutationRecipients = MutationObserverInterestGroup::createForAttributesMutation(this, attributeName))
     189        mutationRecipients->enqueueMutationRecord(MutationRecord::createAttributes(this, attributeName, oldValue));
    188190}
    189191#endif
     
    196198        if (ec == NOT_FOUND_ERR)
    197199            ec = 0;
    198 #if ENABLE(MUTATION_OBSERVERS)
    199         else
    200             enqueueAttributesMutationRecord(this, name, attrNode->nodeValue());
    201 #endif
    202200    }
    203201}
     
    657655#endif
    658656
     657#if ENABLE(MUTATION_OBSERVERS)
     658    enqueueAttributesMutationRecordIfRequested(name, old ? old->value() : nullAtom);
     659#endif
     660
    659661    document()->incDOMTreeVersion();
    660 
    661 #if ENABLE(MUTATION_OBSERVERS)
    662     // The call to attributeChanged below may dispatch DOMSubtreeModified, so it's important to enqueue a MutationRecord now.
    663     if (!isSynchronizingStyleAttribute())
    664         enqueueAttributesMutationRecord(this, name, old ? old->value() : nullAtom);
    665 #endif
    666662
    667663    if (isIdAttributeName(name))
     
    15241520        if (ec == NOT_FOUND_ERR)
    15251521            ec = 0;
    1526 #if ENABLE(MUTATION_OBSERVERS)
    1527         else
    1528             enqueueAttributesMutationRecord(this, QualifiedName(nullAtom, localName, nullAtom), attrNode->nodeValue());
    1529 #endif
    15301522    }
    15311523   
  • trunk/Source/WebCore/dom/Element.h

    r102695 r102814  
    365365    PassRefPtr<RenderStyle> styleForRenderer();
    366366
     367#if ENABLE(MUTATION_OBSERVERS)
     368    void enqueueAttributesMutationRecordIfRequested(const QualifiedName&, const AtomicString& oldValue);
     369#endif
     370
    367371protected:
    368372    Element(const QualifiedName& tagName, Document* document, ConstructionType type)
  • trunk/Source/WebCore/dom/NamedNodeMap.cpp

    r102705 r102814  
    120120    }
    121121
     122#if ENABLE(MUTATION_OBSERVERS)
     123    m_element->enqueueAttributesMutationRecordIfRequested(attribute->name(), oldAttribute ? oldAttribute->value() : nullAtom);
     124#endif
     125
    122126    if (attr->isId())
    123127        m_element->updateId(oldAttribute ? oldAttribute->value() : nullAtom, attribute->value());
     
    149153        return 0;
    150154    }
     155
     156#if ENABLE(MUTATION_OBSERVERS)
     157    if (m_element)
     158        m_element->enqueueAttributesMutationRecordIfRequested(attribute->name(), attribute->value());
     159#endif
    151160
    152161    RefPtr<Attr> attr = attribute->createAttrIfNeeded(m_element);
Note: See TracChangeset for help on using the changeset viewer.