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 @@
assert_equals(logs.length, 2);
}, '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 => {
let document = w.document;
let element = document.createElement('a-a');
......
......@@ -38,6 +38,7 @@
#include "core/css/CSSStyleDeclaration.h"
#include "core/css/CSSValue.h"
#include "core/css/parser/CSSParser.h"
#include "core/dom/custom/CEReactionsScope.h"
#include "core/events/EventTarget.h"
#include "wtf/ASCIICType.h"
#include "wtf/PassRefPtr.h"
......@@ -212,6 +213,8 @@ void V8CSSStyleDeclaration::namedPropertySetterCustom(v8::Local<v8::Name> name,
if (!unresolvedProperty)
return;
CEReactionsScope ceReactionsScope;
TOSTRING_VOID(V8StringResource<TreatNullAsNullString>, propertyValue, value);
ExceptionState exceptionState(ExceptionState::SetterContext, getPropertyName(resolveCSSPropertyID(unresolvedProperty)), "CSSStyleDeclaration", info.Holder(), info.GetIsolate());
// TODO(leviw): This API doesn't support custom properties.
......
......@@ -24,19 +24,19 @@
DependentLifetime,
SetWrapperReferenceTo(CSSRule parentRule),
] interface CSSStyleDeclaration {
[RaisesException=Setter] attribute DOMString cssText;
[CEReactions, RaisesException=Setter] attribute DOMString cssText;
readonly attribute unsigned long length;
getter DOMString item(unsigned long index);
DOMString getPropertyValue(DOMString property);
DOMString getPropertyPriority(DOMString property);
// TODO(foolip): The value and priority arguments should have
// [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 setPropertyPriority(DOMString property, [TreatNullAs=EmptyString] DOMString priority);
[RaisesException] DOMString removeProperty(DOMString property);
[CEReactions, RaisesException] DOMString removeProperty(DOMString property);
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.
// http://dev.w3.org/csswg/cssom/#dom-cssstyledeclaration-camel-cased-attribute
......
......@@ -34,6 +34,8 @@
#include "core/dom/MutationRecord.h"
#include "core/dom/StyleChangeReason.h"
#include "core/dom/StyleEngine.h"
#include "core/dom/custom/CustomElement.h"
#include "core/dom/custom/CustomElementDefinition.h"
#include "core/inspector/InspectorInstrumentation.h"
#include "platform/RuntimeEnabledFeatures.h"
......@@ -41,6 +43,13 @@ namespace blink {
namespace {
static CustomElementDefinition* definitionIfStyleChangedCallback(Element* element)
{
CustomElementDefinition* definition = CustomElement::definitionForElement(element);
return definition && definition->hasStyleAttributeChangedCallback()
? definition : nullptr;
}
class StyleAttributeMutationScope {
WTF_MAKE_NONCOPYABLE(StyleAttributeMutationScope);
STACK_ALLOCATED();
......@@ -60,18 +69,16 @@ public:
if (!s_currentDecl->parentElement())
return;
bool shouldReadOldValue = false;
m_mutationRecipients = MutationObserverInterestGroup::createForAttributesMutation(*s_currentDecl->parentElement(), HTMLNames::styleAttr);
if (m_mutationRecipients && m_mutationRecipients->isOldValueRequested())
shouldReadOldValue = true;
bool shouldReadOldValue =
(m_mutationRecipients && m_mutationRecipients->isOldValueRequested())
|| definitionIfStyleChangedCallback(s_currentDecl->parentElement());
AtomicString oldValue;
if (shouldReadOldValue)
oldValue = s_currentDecl->parentElement()->getAttribute(HTMLNames::styleAttr);
m_oldValue = s_currentDecl->parentElement()->getAttribute(HTMLNames::styleAttr);
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);
}
}
......@@ -82,10 +89,19 @@ public:
if (s_scopeCount)
return;
if (m_mutation && s_shouldDeliver)
m_mutationRecipients->enqueueMutationRecord(m_mutation);
if (s_shouldDeliver) {
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.
AbstractPropertySetCSSStyleDeclaration* localCopyStyleDecl = s_currentDecl;
......@@ -117,6 +133,7 @@ private:
Member<MutationObserverInterestGroup> m_mutationRecipients;
Member<MutationRecord> m_mutation;
AtomicString m_oldValue;
};
unsigned StyleAttributeMutationScope::s_scopeCount = 0;
......
......@@ -32,13 +32,21 @@ CustomElementsRegistry* CustomElement::registry(const Document& document)
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))
return registry->definitionForName(element.localName());
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)
{
if (!name.length() || name[0] < 'a' || name[0] > 'z')
......@@ -180,16 +188,14 @@ void CustomElement::enqueue(Element* element, CustomElementReaction* reaction)
void CustomElement::enqueueConnectedCallback(Element* element)
{
DCHECK_EQ(element->getCustomElementState(), CustomElementState::Custom);
CustomElementDefinition* definition = definitionForElement(*element);
CustomElementDefinition* definition = definitionForElementWithoutCheck(*element);
if (definition->hasConnectedCallback())
definition->enqueueConnectedCallback(element);
}
void CustomElement::enqueueDisconnectedCallback(Element* element)
{
DCHECK_EQ(element->getCustomElementState(), CustomElementState::Custom);
CustomElementDefinition* definition = definitionForElement(*element);
CustomElementDefinition* definition = definitionForElementWithoutCheck(*element);
if (definition->hasDisconnectedCallback())
definition->enqueueDisconnectedCallback(element);
}
......@@ -199,8 +205,7 @@ void CustomElement::enqueueAttributeChangedCallback(Element* element,
const QualifiedName& name,
const AtomicString& oldValue, const AtomicString& newValue)
{
DCHECK_EQ(element->getCustomElementState(), CustomElementState::Custom);
CustomElementDefinition* definition = definitionForElement(*element);
CustomElementDefinition* definition = definitionForElementWithoutCheck(*element);
if (definition->hasAttributeChangedCallback(name))
definition->enqueueAttributeChangedCallback(element, name, oldValue, newValue);
}
......
......@@ -30,7 +30,7 @@ public:
static CustomElementsRegistry* registry(const Element&);
static CustomElementsRegistry* registry(const Document&);
static CustomElementDefinition* definitionForElement(const Element&);
static CustomElementDefinition* definitionForElement(const Element*);
static bool isValidName(const AtomicString& name);
......
......@@ -24,8 +24,10 @@ CustomElementDefinition::CustomElementDefinition(
CustomElementDefinition::CustomElementDefinition(
const CustomElementDescriptor& descriptor,
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)
}
bool CustomElementDefinition::hasAttributeChangedCallback(
const QualifiedName& name)
const QualifiedName& name) const
{
return m_observedAttributes.contains(name.localName());
}
bool CustomElementDefinition::hasStyleAttributeChangedCallback() const
{
return m_hasStyleAttributeChangedCallback;
}
void CustomElementDefinition::enqueueUpgradeReaction(Element* element)
{
CustomElement::enqueue(element,
......
......@@ -56,7 +56,8 @@ public:
virtual bool hasConnectedCallback() 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 runDisconnectedCallback(Element*) = 0;
......@@ -74,11 +75,11 @@ protected:
static void checkConstructorResult(Element*, Document&, const QualifiedName&, ExceptionState&);
HashSet<AtomicString> m_observedAttributes;
private:
const CustomElementDescriptor m_descriptor;
ConstructionStack m_constructionStack;
HashSet<AtomicString> m_observedAttributes;
bool m_hasStyleAttributeChangedCallback;
void enqueueAttributeChangedCallbackForAllAttributes(Element*);
};
......
......@@ -231,6 +231,12 @@ public:
{
}
TestCustomElementDefinition(const CustomElementDescriptor& descriptor,
const HashSet<AtomicString>& observedAttributes)
: CustomElementDefinition(descriptor, observedAttributes)
{
}
~TestCustomElementDefinition() override = default;
ScriptValue getConstructorForScript() override
......@@ -265,11 +271,12 @@ class LogUpgradeDefinition : public TestCustomElementDefinition {
WTF_MAKE_NONCOPYABLE(LogUpgradeDefinition);
public:
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()
......
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