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())