Source/WebCore/ChangeLog

 12011-11-23 Rafael Weinstein <rafaelw@chromium.org>
 2
 3 [MutationObservers] Modifications to the style property don't dispatch "attributes" Mutation Records
 4 https://bugs.webkit.org/show_bug.cgi?id=70137
 5
 6 Reviewed by NOBODY (OOPS!).
 7
 8 This patch adds a private AttributesMutationScope mechanism to CSSMutableStyleDeclaration (which uses
 9 the RAII pattern similar to the public ChildListMutationScope). This manages the (sometimes conditional)
 10 pre-change serialization of the style attribute (if an observer has requested oldValue), creation of
 11 the mutation record, and dispatch if the declaration was actual affected.
 12
 13 * css/CSSMutableStyleDeclaration.cpp:
 14 (WebCore::CSSMutableStyleDeclaration::removeProperty):
 15 (WebCore::CSSMutableStyleDeclaration::setProperty):
 16 (WebCore::CSSMutableStyleDeclaration::setPropertyInternal):
 17 (WebCore::CSSMutableStyleDeclaration::parseDeclaration):
 18 (WebCore::CSSMutableStyleDeclaration::addParsedProperties):
 19 (WebCore::CSSMutableStyleDeclaration::addParsedProperty):
 20 (WebCore::CSSMutableStyleDeclaration::setCssText):
 21 (WebCore::CSSMutableStyleDeclaration::merge):
 22 (WebCore::CSSMutableStyleDeclaration::removePropertiesInSet):
 23 * dom/Element.cpp:
 24 (WebCore::Element::setAttribute):
 25
1262011-11-23 Yury Semikhatsky <yurys@chromium.org>
227
328 Unreviewed. Build fix. Added missing ENABLE(WORKERS) guards.

Source/WebCore/css/CSSMutableStyleDeclaration.cpp

3232#include "CSSValueList.h"
3333#include "Document.h"
3434#include "ExceptionCode.h"
 35#include "HTMLNames.h"
3536#include "InspectorInstrumentation.h"
 37#include "MutationRecord.h"
3638#include "StyledElement.h"
 39#include "WebKitMutationObserver.h"
3740#include <wtf/text/StringBuilder.h>
3841#include <wtf/text/WTFString.h>
3942

@@using namespace std;
4144
4245namespace WebCore {
4346
 47#if ENABLE(MUTATION_OBSERVERS)
 48namespace {
 49
 50class StyleAttributeMutationScope {
 51 WTF_MAKE_NONCOPYABLE(StyleAttributeMutationScope);
 52public:
 53 StyleAttributeMutationScope(CSSMutableStyleDeclaration* decl)
 54 {
 55 ++s_scopeCount;
 56
 57 if (s_scopeCount != 1) {
 58 ASSERT(s_currentDecl == decl);
 59 return;
 60 }
 61
 62 ASSERT(!s_currentDecl);
 63 s_currentDecl = decl;
 64
 65 if (!s_currentDecl->isInlineStyleDeclaration())
 66 return;
 67
 68 s_mutationRecipients = MutationObserverInterestGroup::createForAttributesMutation(s_currentDecl->node(), HTMLNames::styleAttr);
 69 if (s_mutationRecipients->isEmpty()) {
 70 s_mutationRecipients.clear();
 71 return;
 72 }
 73
 74 Element* element = toElement(s_currentDecl->node());
 75 AtomicString oldValue = s_mutationRecipients->isOldValueRequested() ? element->getAttribute(HTMLNames::styleAttr) : nullAtom;
 76 s_mutation = MutationRecord::createAttributes(element, HTMLNames::styleAttr, oldValue);
 77 }
 78
 79 ~StyleAttributeMutationScope()
 80 {
 81 --s_scopeCount;
 82 if (!s_scopeCount)
 83 s_currentDecl = 0;
 84 }
 85
 86 void enqueueMutationRecord()
 87 {
 88 if (!s_mutation)
 89 return;
 90 s_mutationRecipients->enqueueMutationRecord(s_mutation);
 91 s_mutation.clear();
 92 s_mutationRecipients.clear();
 93 }
 94
 95private:
 96 static unsigned s_scopeCount;
 97 static OwnPtr<MutationObserverInterestGroup> s_mutationRecipients;
 98 static RefPtr<MutationRecord> s_mutation;
 99 static CSSMutableStyleDeclaration* s_currentDecl;
 100};
 101
 102unsigned StyleAttributeMutationScope::s_scopeCount = 0;
 103OwnPtr<MutationObserverInterestGroup> StyleAttributeMutationScope::s_mutationRecipients;
 104RefPtr<MutationRecord> StyleAttributeMutationScope::s_mutation;
 105CSSMutableStyleDeclaration* StyleAttributeMutationScope::s_currentDecl = 0;
 106
 107} // namespace
 108#endif // ENABLE(MUTATION_OBSERVERS)
 109
44110CSSMutableStyleDeclaration::CSSMutableStyleDeclaration()
45111 : CSSStyleDeclaration(0, /* isMutable */ true)
46112 , m_node(0)

@@String CSSMutableStyleDeclaration::removeProperty(int propertyID, bool notifyCha
520586{
521587 ASSERT(!m_iteratorCount);
522588
 589#if ENABLE(MUTATION_OBSERVERS)
 590 StyleAttributeMutationScope mutationScope(this);
 591#endif
 592
523593 if (removeShorthandProperty(propertyID, notifyChanged)) {
524594 // FIXME: Return an equivalent shorthand when possible.
525595 return String();

@@String CSSMutableStyleDeclaration::removeProperty(int propertyID, bool notifyCha
535605 // and sweeping them when the vector grows too big.
536606 m_properties.remove(foundProperty - m_properties.data());
537607
 608#if ENABLE(MUTATION_OBSERVERS)
 609 mutationScope.enqueueMutationRecord();
 610#endif
 611
538612 if (notifyChanged)
539613 setNeedsStyleRecalc();
540614

@@bool CSSMutableStyleDeclaration::setProperty(int propertyID, const String& value
602676{
603677 ASSERT(!m_iteratorCount);
604678
 679#if ENABLE(MUTATION_OBSERVERS)
 680 StyleAttributeMutationScope mutationScope(this);
 681#endif
 682
605683 // Setting the value to an empty string just removes the property in both IE and Gecko.
606684 // Setting it to null seems to produce less consistent results, but we treat it just the same.
607685 if (value.isEmpty()) {

@@bool CSSMutableStyleDeclaration::setProperty(int propertyID, const String& value
615693 if (!success) {
616694 // CSS DOM requires raising SYNTAX_ERR here, but this is too dangerous for compatibility,
617695 // see <http://bugs.webkit.org/show_bug.cgi?id=7296>.
618  } else if (notifyChanged)
 696 return false;
 697 }
 698
 699#if ENABLE(MUTATION_OBSERVERS)
 700 mutationScope.enqueueMutationRecord();
 701#endif
 702
 703 if (notifyChanged)
619704 setNeedsStyleRecalc();
620705
621  return success;
 706 return true;
622707}
623708
624709void CSSMutableStyleDeclaration::setPropertyInternal(const CSSProperty& property, CSSProperty* slot)
625710{
626711 ASSERT(!m_iteratorCount);
627712
 713#if ENABLE(MUTATION_OBSERVERS)
 714 StyleAttributeMutationScope mutationScope(this);
 715#endif
 716
628717 if (!removeShorthandProperty(property.id(), false)) {
629718 CSSProperty* toReplace = slot ? slot : findPropertyWithId(property.id());
630719 if (toReplace) {

@@void CSSMutableStyleDeclaration::setPropertyInternal(const CSSProperty& property
633722 }
634723 }
635724 m_properties.append(property);
 725
 726#if ENABLE(MUTATION_OBSERVERS)
 727 mutationScope.enqueueMutationRecord();
 728#endif
636729}
637730
638731bool CSSMutableStyleDeclaration::setProperty(int propertyID, int value, bool important, bool notifyChanged)

@@void CSSMutableStyleDeclaration::parseDeclaration(const String& styleDeclaration
673766{
674767 ASSERT(!m_iteratorCount);
675768
 769#if ENABLE(MUTATION_OBSERVERS)
 770 StyleAttributeMutationScope mutationScope(this);
 771#endif
 772
676773 m_properties.clear();
677774 CSSParser parser(useStrictParsing());
678775 parser.parseDeclaration(this, styleDeclaration);
 776
 777#if ENABLE(MUTATION_OBSERVERS)
 778 mutationScope.enqueueMutationRecord();
 779#endif
 780
679781 setNeedsStyleRecalc();
680782}
681783

@@void CSSMutableStyleDeclaration::addParsedProperties(const CSSProperty* const* p
683785{
684786 ASSERT(!m_iteratorCount);
685787
 788#if ENABLE(MUTATION_OBSERVERS)
 789 StyleAttributeMutationScope mutationScope(this);
 790#endif
 791
686792 m_properties.reserveCapacity(numProperties);
687793 for (int i = 0; i < numProperties; ++i)
688794 addParsedProperty(*properties[i]);
689795
 796#if ENABLE(MUTATION_OBSERVERS)
 797 mutationScope.enqueueMutationRecord();
 798#endif
 799
690800 // FIXME: This probably should have a call to setNeedsStyleRecalc() if something changed. We may also wish to add
691801 // a notifyChanged argument to this function to follow the model of other functions in this class.
692802}

@@void CSSMutableStyleDeclaration::addParsedProperty(const CSSProperty& property)
695805{
696806 ASSERT(!m_iteratorCount);
697807
 808#if ENABLE(MUTATION_OBSERVERS)
 809 StyleAttributeMutationScope mutationScope(this);
 810#endif
 811
698812 // Only add properties that have no !important counterpart present
699813 if (!getPropertyPriority(property.id()) || property.isImportant()) {
700814 removeProperty(property.id(), false, false);
701815 m_properties.append(property);
702816 }
 817
 818#if ENABLE(MUTATION_OBSERVERS)
 819 mutationScope.enqueueMutationRecord();
 820#endif
703821}
704822
705823void CSSMutableStyleDeclaration::setLengthProperty(int propertyId, const String& value, bool important, bool /*multiLength*/)

@@void CSSMutableStyleDeclaration::setCssText(const String& text, ExceptionCode& e
790908{
791909 ASSERT(!m_iteratorCount);
792910
 911#if ENABLE(MUTATION_OBSERVERS)
 912 StyleAttributeMutationScope mutationScope(this);
 913#endif
 914
793915 ec = 0;
794916 m_properties.clear();
795917 CSSParser parser(useStrictParsing());
796918 parser.parseDeclaration(this, text);
 919
 920#if ENABLE(MUTATION_OBSERVERS)
 921 mutationScope.enqueueMutationRecord();
 922#endif
 923
797924 // FIXME: Detect syntax errors and set ec.
798925 setNeedsStyleRecalc();
799926}

@@void CSSMutableStyleDeclaration::merge(const CSSMutableStyleDeclaration* other,
802929{
803930 ASSERT(!m_iteratorCount);
804931
 932#if ENABLE(MUTATION_OBSERVERS)
 933 StyleAttributeMutationScope mutationScope(this);
 934#endif
 935
805936 unsigned size = other->m_properties.size();
806937 for (unsigned n = 0; n < size; ++n) {
807938 const CSSProperty& toMerge = other->m_properties[n];

@@void CSSMutableStyleDeclaration::merge(const CSSMutableStyleDeclaration* other,
813944 } else
814945 m_properties.append(toMerge);
815946 }
 947
 948#if ENABLE(MUTATION_OBSERVERS)
 949 mutationScope.enqueueMutationRecord();
 950#endif
 951
816952 // FIXME: This probably should have a call to setNeedsStyleRecalc() if something changed. We may also wish to add
817953 // a notifyChanged argument to this function to follow the model of other functions in this class.
818954}

@@void CSSMutableStyleDeclaration::removePropertiesInSet(const int* set, unsigned
8701006 if (m_properties.isEmpty())
8711007 return;
8721008
 1009#if ENABLE(MUTATION_OBSERVERS)
 1010 StyleAttributeMutationScope mutationScope(this);
 1011#endif
 1012
8731013 // FIXME: This is always used with static sets and in that case constructing the hash repeatedly is pretty pointless.
8741014 HashSet<int> toRemove;
8751015 for (unsigned i = 0; i < length; ++i)

@@void CSSMutableStyleDeclaration::removePropertiesInSet(const int* set, unsigned
8921032 bool changed = newProperties.size() != m_properties.size();
8931033 m_properties = newProperties;
8941034
895  if (changed && notifyChanged)
896  setNeedsStyleRecalc();
 1035 if (!changed || !notifyChanged)
 1036 return;
 1037
 1038#if ENABLE(MUTATION_OBSERVERS)
 1039 mutationScope.enqueueMutationRecord();
 1040#endif
 1041
 1042 setNeedsStyleRecalc();
8971043}
8981044
8991045PassRefPtr<CSSMutableStyleDeclaration> CSSMutableStyleDeclaration::makeMutable()

Source/WebCore/dom/Element.cpp

@@const AtomicString& Element::getAttributeNS(const String& namespaceURI, const St
619619#if ENABLE(MUTATION_OBSERVERS)
620620static void enqueueAttributesMutationRecord(Element* target, const QualifiedName& attributeName, const AtomicString& oldValue)
621621{
622  OwnPtr<MutationObserverInterestGroup> mutationRecipients = MutationObserverInterestGroup::createForAttributesMutation(target, attributeName.localName());
 622 OwnPtr<MutationObserverInterestGroup> mutationRecipients = MutationObserverInterestGroup::createForAttributesMutation(target, attributeName);
623623 mutationRecipients->enqueueMutationRecord(MutationRecord::createAttributes(target, attributeName, oldValue));
624624}
625625#endif

@@void Element::setAttribute(const AtomicString& name, const AtomicString& value,
646646
647647#if ENABLE(MUTATION_OBSERVERS)
648648 // The call to attributeChanged below may dispatch DOMSubtreeModified, so it's important to enqueue a MutationRecord now.
649  enqueueAttributesMutationRecord(this, attributeName, old ? old->value() : nullAtom);
 649 if (!isSynchronizingStyleAttribute())
 650 enqueueAttributesMutationRecord(this, attributeName, old ? old->value() : nullAtom);
650651#endif
651652
652653 if (isIdAttributeName(old ? old->name() : attributeName))

@@void Element::setAttribute(const QualifiedName& name, const AtomicString& value,
684685
685686#if ENABLE(MUTATION_OBSERVERS)
686687 // The call to attributeChanged below may dispatch DOMSubtreeModified, so it's important to enqueue a MutationRecord now.
687  enqueueAttributesMutationRecord(this, name, old ? old->value() : nullAtom);
 688 if (!isSynchronizingStyleAttribute())
 689 enqueueAttributesMutationRecord(this, name, old ? old->value() : nullAtom);
688690#endif
689691
690692 if (isIdAttributeName(name))

Source/WebCore/dom/WebKitMutationObserver.cpp

4040#include "MutationObserverRegistration.h"
4141#include "MutationRecord.h"
4242#include "Node.h"
 43#include "QualifiedName.h"
4344#include <wtf/ListHashSet.h>
4445
4546namespace WebCore {

@@PassOwnPtr<MutationObserverInterestGroup> MutationObserverInterestGroup::createF
151152 return adoptPtr(new MutationObserverInterestGroup(target, WebKitMutationObserver::CharacterData));
152153}
153154
154 PassOwnPtr<MutationObserverInterestGroup> MutationObserverInterestGroup::createForAttributesMutation(Node* target, const AtomicString& attributeName)
 155PassOwnPtr<MutationObserverInterestGroup> MutationObserverInterestGroup::createForAttributesMutation(Node* target, const QualifiedName& attributeName)
155156{
156  return adoptPtr(new MutationObserverInterestGroup(target, WebKitMutationObserver::Attributes, attributeName));
 157 return adoptPtr(new MutationObserverInterestGroup(target, WebKitMutationObserver::Attributes, attributeName.localName()));
157158}
158159
159160MutationObserverInterestGroup::MutationObserverInterestGroup(Node* target, WebKitMutationObserver::MutationType type, const AtomicString& attributeName)

Source/WebCore/dom/WebKitMutationObserver.h

@@class MutationCallback;
4747class MutationObserverRegistration;
4848class MutationRecord;
4949class Node;
 50class QualifiedName;
5051
5152typedef int ExceptionCode;
5253

@@class MutationObserverInterestGroup {
99100public:
100101 static PassOwnPtr<MutationObserverInterestGroup> createForChildListMutation(Node* target);
101102 static PassOwnPtr<MutationObserverInterestGroup> createForCharacterDataMutation(Node* target);
102  static PassOwnPtr<MutationObserverInterestGroup> createForAttributesMutation(Node* target, const AtomicString& attributeName);
 103 static PassOwnPtr<MutationObserverInterestGroup> createForAttributesMutation(Node* target, const QualifiedName& attributeName);
103104
104105 bool isOldValueRequested();
105106 bool isEmpty() { return m_observers.isEmpty(); }

LayoutTests/ChangeLog

 12011-11-23 Rafael Weinstein <rafaelw@chromium.org>
 2
 3 [MutationObservers] Modifications to the style property don't dispatch "attributes" Mutation Records
 4 https://bugs.webkit.org/show_bug.cgi?id=70137
 5
 6 Reviewed by NOBODY (OOPS!).
 7
 8 Added tests asserting that changes to the style property are observable and work correctly if oldValue is requested.
 9
 10 * fast/mutation/observe-attributes-expected.txt:
 11 * fast/mutation/observe-attributes.html:
 12
1132011-11-23 Philippe Normand <pnormand@igalia.com>
214
315 Unreviewed, skip another svg test on GTK showing signed zero issues.

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

@@PASS mutations[3].type is "attributes"
152152PASS mutations[3].attributeName is "pathLength"
153153PASS mutations[3].attributeNamespace is "http://www.w3.org/2000/svg"
154154
 155Testing that modifying an elements style property dispatches Mutation Records.
 156PASS mutations.length is 3
 157PASS mutations[0].type is "attributes"
 158PASS mutations[0].attributeName is "style"
 159PASS mutations[0].oldValue is null
 160PASS mutations[1].type is "attributes"
 161PASS mutations[1].attributeName is "style"
 162PASS mutations[1].oldValue is null
 163PASS mutations[2].type is "attributes"
 164PASS mutations[2].attributeName is "style"
 165PASS mutations[2].oldValue is null
 166...mutation record created.
 167PASS mutations is null
 168
 169Testing that modifying an elements style property dispatches Mutation Records with correct oldValues.
 170PASS mutations.length is 3
 171PASS mutations[0].type is "attributes"
 172PASS mutations[0].attributeName is "style"
 173PASS mutations[0].oldValue is "color: yellow; width: 100px; "
 174PASS mutations[1].type is "attributes"
 175PASS mutations[1].attributeName is "style"
 176PASS mutations[1].oldValue is "width: 100px; color: red; "
 177PASS mutations[2].type is "attributes"
 178PASS mutations[2].attributeName is "style"
 179PASS mutations[2].oldValue is "color: red; width: 200px; "
 180...mutation record created.
 181PASS mutations is null
 182
155183PASS successfullyParsed is true
156184
157185TEST COMPLETE

LayoutTests/fast/mutation/observe-attributes.html

@@function testAttributeFilterNonHTMLDocument() {
638638 start();
639639}
640640
 641function testStyleAttributePropertyAccess() {
 642 var svgDoc, div, path;
 643 var observer;
 644
 645 function start() {
 646 debug('Testing that modifying an elements style property dispatches Mutation Records.');
 647
 648 mutations = null;
 649 observer = new WebKitMutationObserver(function(m) {
 650 mutations = m;
 651 });
 652
 653 div = document.createElement('div');
 654 div.setAttribute('style', 'color: yellow; width: 100px; ');
 655 observer.observe(div, { attributes: true });
 656 div.style.color = 'red';
 657 div.style.width = '200px';
 658 div.style.color = 'blue';
 659
 660 setTimeout(checkAndContinue, 0);
 661 }
 662
 663 function checkAndContinue() {
 664 shouldBe('mutations.length', '3');
 665 shouldBe('mutations[0].type', '"attributes"');
 666 shouldBe('mutations[0].attributeName', '"style"');
 667 shouldBe('mutations[0].oldValue', 'null');
 668 shouldBe('mutations[1].type', '"attributes"');
 669 shouldBe('mutations[1].attributeName', '"style"');
 670 shouldBe('mutations[1].oldValue', 'null');
 671 shouldBe('mutations[2].type', '"attributes"');
 672 shouldBe('mutations[2].attributeName', '"style"');
 673 shouldBe('mutations[2].oldValue', 'null');
 674
 675 mutations = null;
 676 div.getAttribute('style');
 677 setTimeout(finish, 0);
 678 }
 679
 680 function finish() {
 681 debug('...mutation record created.');
 682
 683 shouldBe('mutations', 'null');
 684
 685 observer.disconnect();
 686 debug('');
 687 runNextTest();
 688 }
 689
 690 start();
 691}
 692
 693function testStyleAttributePropertyAccessOldValue() {
 694 var svgDoc, div, path;
 695 var observer;
 696
 697 function start() {
 698 debug('Testing that modifying an elements style property dispatches Mutation Records with correct oldValues.');
 699
 700 mutations = null;
 701 observer = new WebKitMutationObserver(function(m) {
 702 mutations = m;
 703 });
 704
 705 div = document.createElement('div');
 706 div.setAttribute('style', 'color: yellow; width: 100px; ');
 707 observer.observe(div, { attributes: true, attributeOldValue: true });
 708 div.style.color = 'red';
 709 div.style.width = '200px';
 710 div.style.color = 'blue';
 711
 712 setTimeout(checkAndContinue, 0);
 713 }
 714
 715 function checkAndContinue() {
 716 shouldBe('mutations.length', '3');
 717 shouldBe('mutations[0].type', '"attributes"');
 718 shouldBe('mutations[0].attributeName', '"style"');
 719 shouldBe('mutations[0].oldValue', '"color: yellow; width: 100px; "');
 720 shouldBe('mutations[1].type', '"attributes"');
 721 shouldBe('mutations[1].attributeName', '"style"');
 722 shouldBe('mutations[1].oldValue', '"width: 100px; color: red; "');
 723 shouldBe('mutations[2].type', '"attributes"');
 724 shouldBe('mutations[2].attributeName', '"style"');
 725 shouldBe('mutations[2].oldValue', '"color: red; width: 200px; "');
 726
 727 mutations = null;
 728 div.getAttribute('style');
 729 setTimeout(finish, 0);
 730 }
 731
 732 function finish() {
 733 debug('...mutation record created.');
 734
 735 shouldBe('mutations', 'null');
 736
 737 observer.disconnect();
 738 debug('');
 739 runNextTest();
 740 }
 741
 742 start();
 743}
 744
641745var tests = [
642746 testBasic,
643747 testWrongType,

@@var tests = [
653757 testAttributeFilter,
654758 testAttributeFilterSubtree,
655759 testAttributeFilterNonHTMLElement,
656  testAttributeFilterNonHTMLDocument
 760 testAttributeFilterNonHTMLDocument,
 761 testStyleAttributePropertyAccess,
 762 testStyleAttributePropertyAccessOldValue
657763];
658764var testIndex = 0;
659765