Source/WebCore/ChangeLog

 12011-10-26 Adam Klein <adamk@chromium.org>
 2
 3 [MutationObservers] Support attributeOldValue for attribute mutations
 4 https://bugs.webkit.org/show_bug.cgi?id=70861
 5
 6 Reviewed by NOBODY (OOPS!).
 7
 8 Respect 'attributeOldValue' when passed to WebKitMutationObserver.observe().
 9
 10 If multiple observers have different attributeOldValue settings in
 11 their registrations, two different MutationRecords are created (one is
 12 a wrapper around the other).
 13
 14 If a single observer has multiple registrations that apply to a single
 15 mutation, and those registrations have different values for
 16 attributeOldValue, the observer is passed the oldValue.
 17
 18 * dom/Element.cpp:
 19 (WebCore::hasOldValue):
 20 (WebCore::enqueueAttributesMutationRecord):
 21 (WebCore::Element::setAttribute):
 22 * dom/MutationRecord.cpp:
 23 (WebCore::MutationRecord::createAttributes):
 24 (WebCore::MutationRecord::createWithNullOldValue):
 25 * dom/MutationRecord.h:
 26 (WebCore::MutationRecord::oldValue):
 27
1282011-10-27 Rafael Weinstein <rafaelw@chromium.org>
229
330 [MutationObservers] Implement subtree observation of transiently disconnected nodes

Source/WebCore/dom/Element.cpp

@@const AtomicString& Element::getAttributeNS(const String& namespaceURI, const St
617617}
618618
619619#if ENABLE(MUTATION_OBSERVERS)
620 static void enqueueAttributesMutationRecord(Element* element, const QualifiedName& name)
 620static inline bool hasOldValue(MutationObserverOptions options)
 621{
 622 return options & WebKitMutationObserver::AttributeOldValue;
 623}
 624
 625static inline bool isOldValueRequested(const HashMap<WebKitMutationObserver*, MutationObserverOptions>& observers)
 626{
 627 for (HashMap<WebKitMutationObserver*, MutationObserverOptions>::const_iterator iter = observers.begin(); iter != observers.end(); ++iter) {
 628 if (hasOldValue(iter->second))
 629 return true;
 630 }
 631 return false;
 632}
 633
 634static void enqueueAttributesMutationRecord(Element* element, const QualifiedName& name, const AtomicString& oldValue)
621635{
622636 HashMap<WebKitMutationObserver*, MutationObserverOptions> observers;
623637 element->getRegisteredMutationObserversOfType(observers, WebKitMutationObserver::Attributes);
624638 if (observers.isEmpty())
625639 return;
626640
627  RefPtr<MutationRecord> mutation = MutationRecord::createAttributes(element, name);
 641 RefPtr<MutationRecord> mutation = MutationRecord::createAttributes(element, name, isOldValueRequested(observers) ? oldValue : nullAtom);
 642 RefPtr<MutationRecord> mutationWithNullOldValue = MutationRecord::createWithNullOldValue(mutation);
628643 for (HashMap<WebKitMutationObserver*, MutationObserverOptions>::iterator iter = observers.begin(); iter != observers.end(); ++iter)
629  iter->first->enqueueMutationRecord(mutation);
 644 iter->first->enqueueMutationRecord(hasOldValue(iter->second) ? mutation : mutationWithNullOldValue);
630645}
631646#endif
632647

@@void Element::setAttribute(const AtomicString& name, const AtomicString& value,
652667
653668#if ENABLE(MUTATION_OBSERVERS)
654669 // The call to attributeChanged below may dispatch DOMSubtreeModified, so it's important to enqueue a MutationRecord now.
655  enqueueAttributesMutationRecord(this, attributeName);
 670 enqueueAttributesMutationRecord(this, attributeName, old ? old->value() : nullAtom);
656671#endif
657672
658673 if (isIdAttributeName(old ? old->name() : attributeName))

@@void Element::setAttribute(const QualifiedName& name, const AtomicString& value,
690705
691706#if ENABLE(MUTATION_OBSERVERS)
692707 // The call to attributeChanged below may dispatch DOMSubtreeModified, so it's important to enqueue a MutationRecord now.
693  enqueueAttributesMutationRecord(this, name);
 708 enqueueAttributesMutationRecord(this, name, old ? old->value() : nullAtom);
694709#endif
695710
696711 if (isIdAttributeName(name))

Source/WebCore/dom/MutationRecord.cpp

@@namespace {
4747class ChildListRecord : public MutationRecord {
4848public:
4949 ChildListRecord(PassRefPtr<Node> target, PassRefPtr<NodeList> added, PassRefPtr<NodeList> removed, PassRefPtr<Node> previousSibling, PassRefPtr<Node> nextSibling)
50  : MutationRecord(target)
 50 : m_target(target)
5151 , m_addedNodes(added)
5252 , m_removedNodes(removed)
5353 , m_previousSibling(previousSibling)

@@public:
5656 }
5757
5858private:
59  virtual const AtomicString& type();
60  virtual NodeList* addedNodes() { return m_addedNodes.get(); }
61  virtual NodeList* removedNodes() { return m_removedNodes.get(); }
62  virtual Node* previousSibling() { return m_previousSibling.get(); }
63  virtual Node* nextSibling() { return m_nextSibling.get(); }
64 
 59 virtual const AtomicString& type() OVERRIDE;
 60 virtual Node* target() OVERRIDE { return m_target.get(); }
 61 virtual NodeList* addedNodes() OVERRIDE { return m_addedNodes.get(); }
 62 virtual NodeList* removedNodes() OVERRIDE { return m_removedNodes.get(); }
 63 virtual Node* previousSibling() OVERRIDE { return m_previousSibling.get(); }
 64 virtual Node* nextSibling() OVERRIDE { return m_nextSibling.get(); }
 65
 66 RefPtr<Node> m_target;
6567 RefPtr<NodeList> m_addedNodes;
6668 RefPtr<NodeList> m_removedNodes;
6769 RefPtr<Node> m_previousSibling;

@@private:
7072
7173class AttributesRecord : public MutationRecord {
7274public:
73  AttributesRecord(PassRefPtr<Node> target, const QualifiedName& name)
74  : MutationRecord(target)
 75 AttributesRecord(PassRefPtr<Node> target, const QualifiedName& name, const AtomicString& oldValue)
 76 : m_target(target)
7577 , m_attributeName(name.localName())
7678 , m_attributeNamespace(name.namespaceURI())
 79 , m_oldValue(oldValue)
7780 {
7881 }
7982
8083private:
81  virtual const AtomicString& type();
82  virtual const AtomicString& attributeName() { return m_attributeName; }
83  virtual const AtomicString& attributeNamespace() { return m_attributeNamespace; }
84  virtual String oldValue() { return m_oldValue; }
85  virtual void setOldValue(const String& value) { m_oldValue = value; }
 84 virtual const AtomicString& type() OVERRIDE;
 85 virtual Node* target() OVERRIDE { return m_target.get(); }
 86 virtual const AtomicString& attributeName() OVERRIDE { return m_attributeName; }
 87 virtual const AtomicString& attributeNamespace() OVERRIDE { return m_attributeNamespace; }
 88 virtual String oldValue() OVERRIDE { return m_oldValue; }
8689
 90 RefPtr<Node> m_target;
8791 AtomicString m_attributeName;
8892 AtomicString m_attributeNamespace;
89  String m_oldValue;
 93 AtomicString m_oldValue;
9094};
9195
9296class CharacterDataRecord : public MutationRecord {
9397public:
9498 CharacterDataRecord(PassRefPtr<Node> target)
95  : MutationRecord(target)
 99 : m_target(target)
96100 {
97101 }
98102
99103private:
100  virtual const AtomicString& type();
101  virtual String oldValue() { return m_oldValue; }
102  virtual void setOldValue(const String& value) { m_oldValue = value; }
 104 virtual const AtomicString& type() OVERRIDE;
 105 virtual Node* target() OVERRIDE { return m_target.get(); }
 106
 107 RefPtr<Node> m_target;
 108};
 109
 110class MutationRecordWithNullOldValue : public MutationRecord {
 111public:
 112 MutationRecordWithNullOldValue(PassRefPtr<MutationRecord> record)
 113 : m_record(record)
 114 {
 115 }
103116
104  String m_oldValue;
 117private:
 118 virtual const AtomicString& type() OVERRIDE { return m_record->type(); }
 119 virtual Node* target() OVERRIDE { return m_record->target(); }
 120 virtual NodeList* addedNodes() OVERRIDE { return m_record->addedNodes(); }
 121 virtual NodeList* removedNodes() OVERRIDE { return m_record->removedNodes(); }
 122 virtual Node* previousSibling() OVERRIDE { return m_record->previousSibling(); }
 123 virtual Node* nextSibling() OVERRIDE { return m_record->nextSibling(); }
 124 virtual const AtomicString& attributeName() OVERRIDE { return m_record->attributeName(); }
 125 virtual const AtomicString& attributeNamespace() OVERRIDE { return m_record->attributeNamespace(); }
 126
 127 virtual String oldValue() OVERRIDE { return String(); }
 128
 129 RefPtr<MutationRecord> m_record;
105130};
106131
107132const AtomicString& ChildListRecord::type()

@@PassRefPtr<MutationRecord> MutationRecord::createChildList(PassRefPtr<Node> targ
129154 return adoptRef(static_cast<MutationRecord*>(new ChildListRecord(target, added, removed, previousSibling, nextSibling)));
130155}
131156
132 PassRefPtr<MutationRecord> MutationRecord::createAttributes(PassRefPtr<Node> target, const QualifiedName& name)
 157PassRefPtr<MutationRecord> MutationRecord::createAttributes(PassRefPtr<Node> target, const QualifiedName& name, const AtomicString& oldValue)
133158{
134  return adoptRef(static_cast<MutationRecord*>(new AttributesRecord(target, name)));
 159 return adoptRef(static_cast<MutationRecord*>(new AttributesRecord(target, name, oldValue)));
135160}
136161
137162PassRefPtr<MutationRecord> MutationRecord::createCharacterData(PassRefPtr<Node> target)

@@PassRefPtr<MutationRecord> MutationRecord::createCharacterData(PassRefPtr<Node>
139164 return adoptRef(static_cast<MutationRecord*>(new CharacterDataRecord(target)));
140165}
141166
142 MutationRecord::MutationRecord(PassRefPtr<Node> target)
143  : m_target(target)
 167PassRefPtr<MutationRecord> MutationRecord::createWithNullOldValue(PassRefPtr<MutationRecord> record)
144168{
 169 if (record->oldValue().isNull())
 170 return record;
 171 return adoptRef(static_cast<MutationRecord*>(new MutationRecordWithNullOldValue(record)));
145172}
146173
147174MutationRecord::~MutationRecord()

Source/WebCore/dom/MutationRecord.h

@@class QualifiedName;
4747class MutationRecord : public RefCounted<MutationRecord> {
4848public:
4949 static PassRefPtr<MutationRecord> createChildList(PassRefPtr<Node> target, PassRefPtr<NodeList> added, PassRefPtr<NodeList> removed, PassRefPtr<Node> previousSibling, PassRefPtr<Node> nextSibling);
50  static PassRefPtr<MutationRecord> createAttributes(PassRefPtr<Node> target, const QualifiedName&);
 50 static PassRefPtr<MutationRecord> createAttributes(PassRefPtr<Node> target, const QualifiedName&, const AtomicString& oldValue);
5151 static PassRefPtr<MutationRecord> createCharacterData(PassRefPtr<Node> target);
5252
 53 static PassRefPtr<MutationRecord> createWithNullOldValue(PassRefPtr<MutationRecord>);
 54
5355 virtual ~MutationRecord();
5456
5557 virtual const AtomicString& type() = 0;
56  Node* target() { return m_target.get(); }
 58 virtual Node* target() = 0;
5759
5860 virtual NodeList* addedNodes() { return 0; }
5961 virtual NodeList* removedNodes() { return 0; }

@@public:
6466 virtual const AtomicString& attributeNamespace() { return nullAtom; }
6567
6668 virtual String oldValue() { return String(); }
67  virtual void setOldValue(const String&) { }
68 
69 protected:
70  explicit MutationRecord(PassRefPtr<Node> target);
71 
72 private:
73  RefPtr<Node> m_target;
7469};
7570
76 
7771} // namespace WebCore
7872
7973#endif // ENABLE(MUTATION_OBSERVERS)

LayoutTests/ChangeLog

 12011-10-26 Adam Klein <adamk@chromium.org>
 2
 3 [MutationObservers] Support attributeOldValue for attribute mutations
 4 https://bugs.webkit.org/show_bug.cgi?id=70861
 5
 6 Reviewed by NOBODY (OOPS!).
 7
 8 Added test cases for attributeOldValue to existing tests.
 9
 10 * fast/mutation/observe-attributes-expected.txt:
 11 * fast/mutation/observe-attributes.html:
 12
1132011-10-27 Rafael Weinstein <rafaelw@chromium.org>
214
315 [MutationObservers] Implement subtree observation of transiently disconnected nodes

LayoutTests/fast/mutation/observe-attributes-expected.txt

@@PASS mutations[0].attributeName is "foo"
5858PASS mutations[1].type is "attributes"
5959PASS mutations[1].attributeName is "baz"
6060
 61Testing basic oldValue delivery.
 62PASS mutations.length is 2
 63PASS mutations[0].type is "attributes"
 64PASS mutations[0].attributeName is "foo"
 65PASS mutations[0].oldValue is null
 66PASS mutations[1].type is "attributes"
 67PASS mutations[1].attributeName is "foo"
 68PASS mutations[1].oldValue is "bar"
 69
 70Testing that oldValue is delivered as requested (or not).
 71PASS mutations.length is 1
 72PASS mutations[0].type is "attributes"
 73PASS mutations[0].attributeName is "foo"
 74PASS mutations[0].oldValue is "bar"
 75PASS mutations2.length is 1
 76PASS mutations2[0].type is "attributes"
 77PASS mutations2[0].attributeName is "foo"
 78PASS mutations2[0].oldValue is null
 79
 80An observer with multiple observations will get attributeOldValue if any entries request it.
 81PASS mutations.length is 1
 82PASS mutations[0].type is "attributes"
 83PASS mutations[0].attributeName is "foo"
 84PASS mutations[0].oldValue is "bar"
 85
 86Testing setting an attribute via reflected IDL attribute.
 87PASS mutations.length is 2
 88PASS mutations[0].type is "attributes"
 89PASS mutations[0].attributeName is "id"
 90PASS mutations[0].oldValue is null
 91PASS mutations[1].type is "attributes"
 92PASS mutations[1].attributeName is "id"
 93PASS mutations[1].oldValue is "foo"
 94
6195PASS successfullyParsed is true
6296
6397TEST COMPLETE

LayoutTests/fast/mutation/observe-attributes.html

@@function testOrderingWrtDOMSubtreeModified() {
294294
295295 start();
296296}
297 var tests = [testBasic, testWrongType, testMultipleRegistration, testMultipleObservers, testNamespaceURI, testPropertyAccess, testOrderingWrtDOMSubtreeModified];
 297
 298function testOldValue() {
 299 var div;
 300 var observer;
 301
 302 function start() {
 303 debug('Testing basic oldValue delivery.');
 304 mutations = null;
 305 div = document.createElement('div');
 306 observer = new WebKitMutationObserver(function(mutations) {
 307 window.mutations = mutations;
 308 });
 309 observer.observe(div, { attributes: true, attributeOldValue: true });
 310 div.setAttribute('foo', 'bar');
 311 div.setAttribute('foo', 'baz');
 312 setTimeout(finish, 0);
 313 }
 314
 315 function finish() {
 316 shouldBe('mutations.length', '2');
 317 shouldBe('mutations[0].type', '"attributes"');
 318 shouldBe('mutations[0].attributeName', '"foo"');
 319 shouldBe('mutations[0].oldValue', 'null');
 320 shouldBe('mutations[1].type', '"attributes"');
 321 shouldBe('mutations[1].attributeName', '"foo"');
 322 shouldBe('mutations[1].oldValue', '"bar"');
 323 observer.disconnect();
 324 debug('');
 325 runNextTest();
 326 }
 327
 328 start();
 329}
 330
 331function testOldValueAsRequested() {
 332 var div;
 333 var observer;
 334 var observer2;
 335
 336 function start() {
 337 debug('Testing that oldValue is delivered as requested (or not).');
 338 mutations = null;
 339 div = document.createElement('div');
 340 div.setAttribute('foo', 'bar');
 341 observer = new WebKitMutationObserver(function(mutations) {
 342 window.mutations = mutations;
 343 });
 344 observer2 = new WebKitMutationObserver(function(mutations) {
 345 window.mutations2 = mutations;
 346 });
 347 observer.observe(div, { attributes: true, attributeOldValue: true });
 348 observer2.observe(div, { attributes: true });
 349 div.setAttribute('foo', 'baz');
 350 setTimeout(finish, 0);
 351 }
 352
 353 function finish() {
 354 shouldBe('mutations.length', '1');
 355 shouldBe('mutations[0].type', '"attributes"');
 356 shouldBe('mutations[0].attributeName', '"foo"');
 357 shouldBe('mutations[0].oldValue', '"bar"');
 358 shouldBe('mutations2.length', '1');
 359 shouldBe('mutations2[0].type', '"attributes"');
 360 shouldBe('mutations2[0].attributeName', '"foo"');
 361 shouldBe('mutations2[0].oldValue', 'null');
 362 observer.disconnect();
 363 observer2.disconnect();
 364 debug('');
 365 runNextTest();
 366 }
 367
 368 start();
 369}
 370
 371function testOldValueUnionMultipleObservations() {
 372 var div;
 373 var span;
 374 var observer;
 375
 376 function start() {
 377 debug('An observer with multiple observations will get attributeOldValue if any entries request it.');
 378 mutations = null;
 379 div = document.createElement('div');
 380 span = div.appendChild(document.createElement('span'));
 381 span.setAttribute('foo', 'bar');
 382 observer = new WebKitMutationObserver(function(mutations) {
 383 window.mutations = mutations;
 384 });
 385 observer.observe(div, { attributes: true, attributeOldValue: true, subtree: true });
 386 observer.observe(span, { attributes: true });
 387 span.setAttribute('foo', 'baz');
 388 setTimeout(finish, 0);
 389 }
 390
 391 function finish() {
 392 shouldBe('mutations.length', '1');
 393 shouldBe('mutations[0].type', '"attributes"');
 394 shouldBe('mutations[0].attributeName', '"foo"');
 395 shouldBe('mutations[0].oldValue', '"bar"');
 396 observer.disconnect();
 397 debug('');
 398 runNextTest();
 399 }
 400
 401 start();
 402}
 403
 404function testIDLAttribute() {
 405 var div;
 406 var observer;
 407
 408 function start() {
 409 debug('Testing setting an attribute via reflected IDL attribute.');
 410 mutations = null;
 411 div = document.createElement('div');
 412 observer = new WebKitMutationObserver(function(mutations) {
 413 window.mutations = mutations;
 414 });
 415 observer.observe(div, { attributes: true, attributeOldValue: true });
 416 div.id = 'foo';
 417 div.id = 'bar';
 418 setTimeout(finish, 0);
 419 }
 420
 421 function finish() {
 422 shouldBe('mutations.length', '2');
 423 shouldBe('mutations[0].type', '"attributes"');
 424 shouldBe('mutations[0].attributeName', '"id"');
 425 shouldBe('mutations[0].oldValue', 'null');
 426 shouldBe('mutations[1].type', '"attributes"');
 427 shouldBe('mutations[1].attributeName', '"id"');
 428 shouldBe('mutations[1].oldValue', '"foo"');
 429 observer.disconnect();
 430 debug('');
 431 runNextTest();
 432 }
 433
 434 start();
 435}
 436
 437var tests = [
 438 testBasic,
 439 testWrongType,
 440 testMultipleRegistration,
 441 testMultipleObservers,
 442 testNamespaceURI,
 443 testPropertyAccess,
 444 testOrderingWrtDOMSubtreeModified,
 445 testOldValue,
 446 testOldValueAsRequested,
 447 testOldValueUnionMultipleObservations,
 448 testIDLAttribute
 449];
298450var testIndex = 0;
299 
 451
300452function runNextTest() {
301453 if (testIndex < tests.length)
302454 tests[testIndex++]();