Commit 48bf4aae authored by kojii's avatar kojii Committed by Commit bot

Fire attributeChangedCallback on style changes for Custom Elements V1

W3C discussion[1] resolved to allow observing the style attribute.

CEReactions are added to CSSStyleDeclaration interface[2].

Ported from a canceled patch for Custom Elements V0[3].

[1] https://github.com/w3c/webcomponents/issues/521
[2] https://drafts.csswg.org/cssom/#cssstyledeclaration
[3] https://codereview.chromium.org/1446933002

BUG=594918

Review-Url: https://codereview.chromium.org/2166213002
Cr-Commit-Position: refs/heads/master@{#407112}
parent df7da06b
...@@ -88,6 +88,78 @@ ...@@ -88,6 +88,78 @@
assert_equals(logs.length, 2); assert_equals(logs.length, 2);
}, 'setAttribute should enqueue attributeChangedCallback'); }, 'setAttribute should enqueue attributeChangedCallback');
test_with_window(w => {
let document = w.document;
let element = document.createElement('a-a');
document.body.appendChild(element);
let logs = define_logger(w, ['style']);
logs.length = 0;
element.style.color = 'red';
assert_equals(logs.length, 1);
assert_log_is_type(logs, 0, attributeChanged, element, ['style', null, 'color: red;', '']);
element.style.color = 'green';
assert_equals(logs.length, 2);
assert_log_is_type(logs, 1, attributeChanged, element, ['style', 'color: red;', 'color: green;', '']);
element.style.color = '';
assert_equals(logs.length, 3);
assert_log_is_type(logs, 2, attributeChanged, element, ['style', 'color: green;', null, '']);
}, 'style.color should enqueue attributeChangedCallback for style attribute');
test_with_window(w => {
let document = w.document;
let element = document.createElement('a-a');
document.body.appendChild(element);
let logs = define_logger(w, ['style']);
logs.length = 0;
element.style.cssText = 'color: red';
assert_equals(logs.length, 1);
assert_log_is_type(logs, 0, attributeChanged, element, ['style', null, 'color: red;', '']);
}, 'style.cssText should enqueue attributeChangedCallback for style attribute');
test_with_window(w => {
let document = w.document;
let element = document.createElement('a-a');
document.body.appendChild(element);
let logs = define_logger(w, ['style']);
logs.length = 0;
element.style.setProperty('color', 'red');
assert_equals(logs.length, 1);
assert_log_is_type(logs, 0, attributeChanged, element, ['style', null, 'color: red;', '']);
element.style.removeProperty('color', 'red');
assert_equals(logs.length, 2);
assert_log_is_type(logs, 1, attributeChanged, element, ['style', 'color: red;', null, '']);
}, 'style.setProperty/removeProperty should enqueue attributeChangedCallback for style attribute');
test_with_window(w => {
let document = w.document;
let element = document.createElement('a-a');
document.body.appendChild(element);
let logs = define_logger(w, ['style']);
logs.length = 0;
element.style.cssFloat = 'left';
assert_equals(logs.length, 1);
assert_log_is_type(logs, 0, attributeChanged, element, ['style', null, 'float: left;', '']);
}, 'style.cssFloat should enqueue attributeChangedCallback for style attribute');
test_with_window(w => {
let document = w.document;
let element = document.createElement('a-a');
document.body.appendChild(element);
let logs = define_logger(w, ['lang']);
logs.length = 0;
element.lang = 'ja-jp';
assert_equals(logs.length, 1);
assert_log_is_type(logs, 0, attributeChanged, element, ['lang', null, 'ja-jp', '']);
}, 'lang property setter should enqueue attributeChangedCallback for lang attribute');
test_with_window(w => { test_with_window(w => {
let document = w.document; let document = w.document;
let element = document.createElement('a-a'); let element = document.createElement('a-a');
......
...@@ -38,6 +38,7 @@ ...@@ -38,6 +38,7 @@
#include "core/css/CSSStyleDeclaration.h" #include "core/css/CSSStyleDeclaration.h"
#include "core/css/CSSValue.h" #include "core/css/CSSValue.h"
#include "core/css/parser/CSSParser.h" #include "core/css/parser/CSSParser.h"
#include "core/dom/custom/CEReactionsScope.h"
#include "core/events/EventTarget.h" #include "core/events/EventTarget.h"
#include "wtf/ASCIICType.h" #include "wtf/ASCIICType.h"
#include "wtf/PassRefPtr.h" #include "wtf/PassRefPtr.h"
...@@ -212,6 +213,8 @@ void V8CSSStyleDeclaration::namedPropertySetterCustom(v8::Local<v8::Name> name, ...@@ -212,6 +213,8 @@ void V8CSSStyleDeclaration::namedPropertySetterCustom(v8::Local<v8::Name> name,
if (!unresolvedProperty) if (!unresolvedProperty)
return; return;
CEReactionsScope ceReactionsScope;
TOSTRING_VOID(V8StringResource<TreatNullAsNullString>, propertyValue, value); TOSTRING_VOID(V8StringResource<TreatNullAsNullString>, propertyValue, value);
ExceptionState exceptionState(ExceptionState::SetterContext, getPropertyName(resolveCSSPropertyID(unresolvedProperty)), "CSSStyleDeclaration", info.Holder(), info.GetIsolate()); ExceptionState exceptionState(ExceptionState::SetterContext, getPropertyName(resolveCSSPropertyID(unresolvedProperty)), "CSSStyleDeclaration", info.Holder(), info.GetIsolate());
// TODO(leviw): This API doesn't support custom properties. // TODO(leviw): This API doesn't support custom properties.
......
...@@ -24,19 +24,19 @@ ...@@ -24,19 +24,19 @@
DependentLifetime, DependentLifetime,
SetWrapperReferenceTo(CSSRule parentRule), SetWrapperReferenceTo(CSSRule parentRule),
] interface CSSStyleDeclaration { ] interface CSSStyleDeclaration {
[RaisesException=Setter] attribute DOMString cssText; [CEReactions, RaisesException=Setter] attribute DOMString cssText;
readonly attribute unsigned long length; readonly attribute unsigned long length;
getter DOMString item(unsigned long index); getter DOMString item(unsigned long index);
DOMString getPropertyValue(DOMString property); DOMString getPropertyValue(DOMString property);
DOMString getPropertyPriority(DOMString property); DOMString getPropertyPriority(DOMString property);
// TODO(foolip): The value and priority arguments should have // TODO(foolip): The value and priority arguments should have
// [TreatNullAs=EmptyString] and should not be nullable. // [TreatNullAs=EmptyString] and should not be nullable.
[RaisesException] void setProperty(DOMString property, DOMString? value, optional DOMString? priority = null); [CEReactions, RaisesException] void setProperty(DOMString property, DOMString? value, optional DOMString? priority = null);
// void setPropertyValue(DOMString property, [TreatNullAs=EmptyString] DOMString value); // void setPropertyValue(DOMString property, [TreatNullAs=EmptyString] DOMString value);
// void setPropertyPriority(DOMString property, [TreatNullAs=EmptyString] DOMString priority); // void setPropertyPriority(DOMString property, [TreatNullAs=EmptyString] DOMString priority);
[RaisesException] DOMString removeProperty(DOMString property); [CEReactions, RaisesException] DOMString removeProperty(DOMString property);
readonly attribute CSSRule? parentRule; readonly attribute CSSRule? parentRule;
[RaisesException=Setter, TreatNullAs=EmptyString] attribute DOMString cssFloat; [CEReactions, RaisesException=Setter, TreatNullAs=EmptyString] attribute DOMString cssFloat;
// The camel-cased and dashed attribute getters have custom bindings. // The camel-cased and dashed attribute getters have custom bindings.
// http://dev.w3.org/csswg/cssom/#dom-cssstyledeclaration-camel-cased-attribute // http://dev.w3.org/csswg/cssom/#dom-cssstyledeclaration-camel-cased-attribute
......
...@@ -34,6 +34,8 @@ ...@@ -34,6 +34,8 @@
#include "core/dom/MutationRecord.h" #include "core/dom/MutationRecord.h"
#include "core/dom/StyleChangeReason.h" #include "core/dom/StyleChangeReason.h"
#include "core/dom/StyleEngine.h" #include "core/dom/StyleEngine.h"
#include "core/dom/custom/CustomElement.h"
#include "core/dom/custom/CustomElementDefinition.h"
#include "core/inspector/InspectorInstrumentation.h" #include "core/inspector/InspectorInstrumentation.h"
#include "platform/RuntimeEnabledFeatures.h" #include "platform/RuntimeEnabledFeatures.h"
...@@ -41,6 +43,13 @@ namespace blink { ...@@ -41,6 +43,13 @@ namespace blink {
namespace { namespace {
static CustomElementDefinition* definitionIfStyleChangedCallback(Element* element)
{
CustomElementDefinition* definition = CustomElement::definitionForElement(element);
return definition && definition->hasStyleAttributeChangedCallback()
? definition : nullptr;
}
class StyleAttributeMutationScope { class StyleAttributeMutationScope {
WTF_MAKE_NONCOPYABLE(StyleAttributeMutationScope); WTF_MAKE_NONCOPYABLE(StyleAttributeMutationScope);
STACK_ALLOCATED(); STACK_ALLOCATED();
...@@ -60,18 +69,16 @@ public: ...@@ -60,18 +69,16 @@ public:
if (!s_currentDecl->parentElement()) if (!s_currentDecl->parentElement())
return; return;
bool shouldReadOldValue = false;
m_mutationRecipients = MutationObserverInterestGroup::createForAttributesMutation(*s_currentDecl->parentElement(), HTMLNames::styleAttr); m_mutationRecipients = MutationObserverInterestGroup::createForAttributesMutation(*s_currentDecl->parentElement(), HTMLNames::styleAttr);
if (m_mutationRecipients && m_mutationRecipients->isOldValueRequested()) bool shouldReadOldValue =
shouldReadOldValue = true; (m_mutationRecipients && m_mutationRecipients->isOldValueRequested())
|| definitionIfStyleChangedCallback(s_currentDecl->parentElement());
AtomicString oldValue;
if (shouldReadOldValue) if (shouldReadOldValue)
oldValue = s_currentDecl->parentElement()->getAttribute(HTMLNames::styleAttr); m_oldValue = s_currentDecl->parentElement()->getAttribute(HTMLNames::styleAttr);
if (m_mutationRecipients) { if (m_mutationRecipients) {
AtomicString requestedOldValue = m_mutationRecipients->isOldValueRequested() ? oldValue : nullAtom; AtomicString requestedOldValue = m_mutationRecipients->isOldValueRequested() ? m_oldValue : nullAtom;
m_mutation = MutationRecord::createAttributes(s_currentDecl->parentElement(), HTMLNames::styleAttr, requestedOldValue); m_mutation = MutationRecord::createAttributes(s_currentDecl->parentElement(), HTMLNames::styleAttr, requestedOldValue);
} }
} }
...@@ -82,10 +89,19 @@ public: ...@@ -82,10 +89,19 @@ public:
if (s_scopeCount) if (s_scopeCount)
return; return;
if (m_mutation && s_shouldDeliver) if (s_shouldDeliver) {
m_mutationRecipients->enqueueMutationRecord(m_mutation); if (m_mutation)
m_mutationRecipients->enqueueMutationRecord(m_mutation);
Element* element = s_currentDecl->parentElement();
if (CustomElementDefinition* definition = definitionIfStyleChangedCallback(element)) {
definition->enqueueAttributeChangedCallback(element,
HTMLNames::styleAttr, m_oldValue,
element->getAttribute(HTMLNames::styleAttr));
}
s_shouldDeliver = false; s_shouldDeliver = false;
}
// We have to clear internal state before calling Inspector's code. // We have to clear internal state before calling Inspector's code.
AbstractPropertySetCSSStyleDeclaration* localCopyStyleDecl = s_currentDecl; AbstractPropertySetCSSStyleDeclaration* localCopyStyleDecl = s_currentDecl;
...@@ -117,6 +133,7 @@ private: ...@@ -117,6 +133,7 @@ private:
Member<MutationObserverInterestGroup> m_mutationRecipients; Member<MutationObserverInterestGroup> m_mutationRecipients;
Member<MutationRecord> m_mutation; Member<MutationRecord> m_mutation;
AtomicString m_oldValue;
}; };
unsigned StyleAttributeMutationScope::s_scopeCount = 0; unsigned StyleAttributeMutationScope::s_scopeCount = 0;
......
...@@ -32,13 +32,21 @@ CustomElementsRegistry* CustomElement::registry(const Document& document) ...@@ -32,13 +32,21 @@ CustomElementsRegistry* CustomElement::registry(const Document& document)
return nullptr; return nullptr;
} }
CustomElementDefinition* CustomElement::definitionForElement(const Element& element) static CustomElementDefinition* definitionForElementWithoutCheck(const Element& element)
{ {
DCHECK_EQ(element.getCustomElementState(), CustomElementState::Custom);
if (CustomElementsRegistry* registry = CustomElement::registry(element)) if (CustomElementsRegistry* registry = CustomElement::registry(element))
return registry->definitionForName(element.localName()); return registry->definitionForName(element.localName());
return nullptr; return nullptr;
} }
CustomElementDefinition* CustomElement::definitionForElement(const Element* element)
{
if (!element || element->getCustomElementState() != CustomElementState::Custom)
return nullptr;
return definitionForElementWithoutCheck(*element);
}
bool CustomElement::isValidName(const AtomicString& name) bool CustomElement::isValidName(const AtomicString& name)
{ {
if (!name.length() || name[0] < 'a' || name[0] > 'z') if (!name.length() || name[0] < 'a' || name[0] > 'z')
...@@ -180,16 +188,14 @@ void CustomElement::enqueue(Element* element, CustomElementReaction* reaction) ...@@ -180,16 +188,14 @@ void CustomElement::enqueue(Element* element, CustomElementReaction* reaction)
void CustomElement::enqueueConnectedCallback(Element* element) void CustomElement::enqueueConnectedCallback(Element* element)
{ {
DCHECK_EQ(element->getCustomElementState(), CustomElementState::Custom); CustomElementDefinition* definition = definitionForElementWithoutCheck(*element);
CustomElementDefinition* definition = definitionForElement(*element);
if (definition->hasConnectedCallback()) if (definition->hasConnectedCallback())
definition->enqueueConnectedCallback(element); definition->enqueueConnectedCallback(element);
} }
void CustomElement::enqueueDisconnectedCallback(Element* element) void CustomElement::enqueueDisconnectedCallback(Element* element)
{ {
DCHECK_EQ(element->getCustomElementState(), CustomElementState::Custom); CustomElementDefinition* definition = definitionForElementWithoutCheck(*element);
CustomElementDefinition* definition = definitionForElement(*element);
if (definition->hasDisconnectedCallback()) if (definition->hasDisconnectedCallback())
definition->enqueueDisconnectedCallback(element); definition->enqueueDisconnectedCallback(element);
} }
...@@ -199,8 +205,7 @@ void CustomElement::enqueueAttributeChangedCallback(Element* element, ...@@ -199,8 +205,7 @@ void CustomElement::enqueueAttributeChangedCallback(Element* element,
const QualifiedName& name, const QualifiedName& name,
const AtomicString& oldValue, const AtomicString& newValue) const AtomicString& oldValue, const AtomicString& newValue)
{ {
DCHECK_EQ(element->getCustomElementState(), CustomElementState::Custom); CustomElementDefinition* definition = definitionForElementWithoutCheck(*element);
CustomElementDefinition* definition = definitionForElement(*element);
if (definition->hasAttributeChangedCallback(name)) if (definition->hasAttributeChangedCallback(name))
definition->enqueueAttributeChangedCallback(element, name, oldValue, newValue); definition->enqueueAttributeChangedCallback(element, name, oldValue, newValue);
} }
......
...@@ -30,7 +30,7 @@ public: ...@@ -30,7 +30,7 @@ public:
static CustomElementsRegistry* registry(const Element&); static CustomElementsRegistry* registry(const Element&);
static CustomElementsRegistry* registry(const Document&); static CustomElementsRegistry* registry(const Document&);
static CustomElementDefinition* definitionForElement(const Element&); static CustomElementDefinition* definitionForElement(const Element*);
static bool isValidName(const AtomicString& name); static bool isValidName(const AtomicString& name);
......
...@@ -24,8 +24,10 @@ CustomElementDefinition::CustomElementDefinition( ...@@ -24,8 +24,10 @@ CustomElementDefinition::CustomElementDefinition(
CustomElementDefinition::CustomElementDefinition( CustomElementDefinition::CustomElementDefinition(
const CustomElementDescriptor& descriptor, const CustomElementDescriptor& descriptor,
const HashSet<AtomicString>& observedAttributes) const HashSet<AtomicString>& observedAttributes)
: m_observedAttributes(observedAttributes) : m_descriptor(descriptor)
, m_descriptor(descriptor) , m_observedAttributes(observedAttributes)
, m_hasStyleAttributeChangedCallback(
observedAttributes.contains(HTMLNames::styleAttr.localName()))
{ {
} }
...@@ -127,11 +129,16 @@ void CustomElementDefinition::upgrade(Element* element) ...@@ -127,11 +129,16 @@ void CustomElementDefinition::upgrade(Element* element)
} }
bool CustomElementDefinition::hasAttributeChangedCallback( bool CustomElementDefinition::hasAttributeChangedCallback(
const QualifiedName& name) const QualifiedName& name) const
{ {
return m_observedAttributes.contains(name.localName()); return m_observedAttributes.contains(name.localName());
} }
bool CustomElementDefinition::hasStyleAttributeChangedCallback() const
{
return m_hasStyleAttributeChangedCallback;
}
void CustomElementDefinition::enqueueUpgradeReaction(Element* element) void CustomElementDefinition::enqueueUpgradeReaction(Element* element)
{ {
CustomElement::enqueue(element, CustomElement::enqueue(element,
......
...@@ -56,7 +56,8 @@ public: ...@@ -56,7 +56,8 @@ public:
virtual bool hasConnectedCallback() const = 0; virtual bool hasConnectedCallback() const = 0;
virtual bool hasDisconnectedCallback() const = 0; virtual bool hasDisconnectedCallback() const = 0;
bool hasAttributeChangedCallback(const QualifiedName&); bool hasAttributeChangedCallback(const QualifiedName&) const;
bool hasStyleAttributeChangedCallback() const;
virtual void runConnectedCallback(Element*) = 0; virtual void runConnectedCallback(Element*) = 0;
virtual void runDisconnectedCallback(Element*) = 0; virtual void runDisconnectedCallback(Element*) = 0;
...@@ -74,11 +75,11 @@ protected: ...@@ -74,11 +75,11 @@ protected:
static void checkConstructorResult(Element*, Document&, const QualifiedName&, ExceptionState&); static void checkConstructorResult(Element*, Document&, const QualifiedName&, ExceptionState&);
HashSet<AtomicString> m_observedAttributes;
private: private:
const CustomElementDescriptor m_descriptor; const CustomElementDescriptor m_descriptor;
ConstructionStack m_constructionStack; ConstructionStack m_constructionStack;
HashSet<AtomicString> m_observedAttributes;
bool m_hasStyleAttributeChangedCallback;
void enqueueAttributeChangedCallbackForAllAttributes(Element*); void enqueueAttributeChangedCallbackForAllAttributes(Element*);
}; };
......
...@@ -231,6 +231,12 @@ public: ...@@ -231,6 +231,12 @@ public:
{ {
} }
TestCustomElementDefinition(const CustomElementDescriptor& descriptor,
const HashSet<AtomicString>& observedAttributes)
: CustomElementDefinition(descriptor, observedAttributes)
{
}
~TestCustomElementDefinition() override = default; ~TestCustomElementDefinition() override = default;
ScriptValue getConstructorForScript() override ScriptValue getConstructorForScript() override
...@@ -265,11 +271,12 @@ class LogUpgradeDefinition : public TestCustomElementDefinition { ...@@ -265,11 +271,12 @@ class LogUpgradeDefinition : public TestCustomElementDefinition {
WTF_MAKE_NONCOPYABLE(LogUpgradeDefinition); WTF_MAKE_NONCOPYABLE(LogUpgradeDefinition);
public: public:
LogUpgradeDefinition(const CustomElementDescriptor& descriptor) LogUpgradeDefinition(const CustomElementDescriptor& descriptor)
: TestCustomElementDefinition(descriptor) : TestCustomElementDefinition(descriptor, {
"attr1",
"attr2",
HTMLNames::contenteditableAttr.localName(),
})
{ {
m_observedAttributes.add("attr1");
m_observedAttributes.add("attr2");
m_observedAttributes.add(HTMLNames::contenteditableAttr.localName());
} }
DEFINE_INLINE_VIRTUAL_TRACE() DEFINE_INLINE_VIRTUAL_TRACE()
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment