Bug 1629761 - Should not invoke attributeChangedCallback for the attribute that is changed during upgrading; r=smaug

In https://dom.spec.whatwg.org/#handle-attribute-changes, the attributeChangedCallback
reaction is enqueued only if the custom-element-state is customized.

And the assumption of "custom-element-definition is only available on the element whose
custom-element-state is customized" is no longer true after bug 1610054 along with the
spec changings in https://github.com/whatwg/html/pull/5126.

Differential Revision: https://phabricator.services.mozilla.com/D71027

--HG--
extra : moz-landing-system : lando
This commit is contained in:
Edgar Chen 2020-04-15 13:36:09 +00:00
parent 1e4fcf5b1a
commit 444a911d92
4 changed files with 93 additions and 51 deletions

View File

@ -182,7 +182,7 @@ void CustomElementData::AttachedInternals() {
mIsAttachedInternals = true;
}
CustomElementDefinition* CustomElementData::GetCustomElementDefinition() {
CustomElementDefinition* CustomElementData::GetCustomElementDefinition() const {
// Per spec, if there is a definition, the custom element state should be
// either "failed" (during upgrade) or "customized".
MOZ_ASSERT_IF(mCustomElementDefinition, mState != State::eUndefined);
@ -492,6 +492,7 @@ CustomElementRegistry::CreateCustomElementCallback(
return callback;
}
// https://html.spec.whatwg.org/commit-snapshots/65f39c6fc0efa92b0b2b23b93197016af6ac0de6/#enqueue-a-custom-element-callback-reaction
/* static */
void CustomElementRegistry::EnqueueLifecycleCallback(
Document::ElementCallbackType aType, Element* aCustomElement,

View File

@ -103,7 +103,7 @@ struct CustomElementData {
AutoTArray<UniquePtr<CustomElementReaction>, 3> mReactionQueue;
void SetCustomElementDefinition(CustomElementDefinition* aDefinition);
CustomElementDefinition* GetCustomElementDefinition();
CustomElementDefinition* GetCustomElementDefinition() const;
nsAtom* GetCustomElementType() const { return mType; }
void AttachedInternals();
bool HasAttachedInternals() const { return mIsAttachedInternals; }

View File

@ -375,8 +375,7 @@ void Element::Focus(const FocusOptions& aOptions, CallerType aCallerType,
fm->NeedsFlushBeforeEventHandling(this);
return;
}
uint32_t fmFlags =
nsFocusManager::FocusOptionsToFocusManagerFlags(aOptions);
uint32_t fmFlags = nsFocusManager::FocusOptionsToFocusManagerFlags(aOptions);
if (aCallerType == CallerType::NonSystem) {
fmFlags |= nsIFocusManager::FLAG_NONSYSTEMCALLER;
}
@ -2331,31 +2330,34 @@ nsresult Element::SetAttrAndNotify(
}
}
CustomElementDefinition* definition = GetCustomElementDefinition();
// Only custom element which is in `custom` state could get the
// CustomElementDefinition.
if (definition && definition->IsInObservedAttributeList(aName)) {
RefPtr<nsAtom> oldValueAtom;
if (oldValue) {
oldValueAtom = oldValue->GetAsAtom();
} else {
// If there is no old value, get the value of the uninitialized
// attribute that was swapped with aParsedValue.
oldValueAtom = aParsedValue.GetAsAtom();
const CustomElementData* data = GetCustomElementData();
if (data && data->mState == CustomElementData::State::eCustom) {
CustomElementDefinition* definition = data->GetCustomElementDefinition();
MOZ_ASSERT(definition, "Should have a valid CustomElementDefinition");
if (definition->IsInObservedAttributeList(aName)) {
RefPtr<nsAtom> oldValueAtom;
if (oldValue) {
oldValueAtom = oldValue->GetAsAtom();
} else {
// If there is no old value, get the value of the uninitialized
// attribute that was swapped with aParsedValue.
oldValueAtom = aParsedValue.GetAsAtom();
}
RefPtr<nsAtom> newValueAtom = valueForAfterSetAttr.GetAsAtom();
nsAutoString ns;
nsContentUtils::NameSpaceManager()->GetNameSpaceURI(aNamespaceID, ns);
LifecycleCallbackArgs args = {nsDependentAtomString(aName),
aModType == MutationEvent_Binding::ADDITION
? VoidString()
: nsDependentAtomString(oldValueAtom),
nsDependentAtomString(newValueAtom),
(ns.IsEmpty() ? VoidString() : ns)};
nsContentUtils::EnqueueLifecycleCallback(
Document::eAttributeChanged, this, &args, nullptr, definition);
}
RefPtr<nsAtom> newValueAtom = valueForAfterSetAttr.GetAsAtom();
nsAutoString ns;
nsContentUtils::NameSpaceManager()->GetNameSpaceURI(aNamespaceID, ns);
LifecycleCallbackArgs args = {nsDependentAtomString(aName),
aModType == MutationEvent_Binding::ADDITION
? VoidString()
: nsDependentAtomString(oldValueAtom),
nsDependentAtomString(newValueAtom),
(ns.IsEmpty() ? VoidString() : ns)};
nsContentUtils::EnqueueLifecycleCallback(Document::eAttributeChanged, this,
&args, nullptr, definition);
}
if (aCallAfterSetAttr) {
@ -2523,19 +2525,22 @@ void Element::PostIdMaybeChange(int32_t aNamespaceID, nsAtom* aName,
nsresult Element::OnAttrSetButNotChanged(int32_t aNamespaceID, nsAtom* aName,
const nsAttrValueOrString& aValue,
bool aNotify) {
// Only custom element which is in `custom` state could get the
// CustomElementDefinition.
CustomElementDefinition* definition = GetCustomElementDefinition();
if (definition && definition->IsInObservedAttributeList(aName)) {
nsAutoString ns;
nsContentUtils::NameSpaceManager()->GetNameSpaceURI(aNamespaceID, ns);
const CustomElementData* data = GetCustomElementData();
if (data && data->mState == CustomElementData::State::eCustom) {
CustomElementDefinition* definition = data->GetCustomElementDefinition();
MOZ_ASSERT(definition, "Should have a valid CustomElementDefinition");
nsAutoString value(aValue.String());
LifecycleCallbackArgs args = {nsDependentAtomString(aName), value, value,
(ns.IsEmpty() ? VoidString() : ns)};
if (definition->IsInObservedAttributeList(aName)) {
nsAutoString ns;
nsContentUtils::NameSpaceManager()->GetNameSpaceURI(aNamespaceID, ns);
nsContentUtils::EnqueueLifecycleCallback(Document::eAttributeChanged, this,
&args, nullptr, definition);
nsAutoString value(aValue.String());
LifecycleCallbackArgs args = {nsDependentAtomString(aName), value, value,
(ns.IsEmpty() ? VoidString() : ns)};
nsContentUtils::EnqueueLifecycleCallback(
Document::eAttributeChanged, this, &args, nullptr, definition);
}
}
return NS_OK;
@ -2631,20 +2636,23 @@ nsresult Element::UnsetAttr(int32_t aNameSpaceID, nsAtom* aName, bool aNotify) {
PostIdMaybeChange(aNameSpaceID, aName, nullptr);
CustomElementDefinition* definition = GetCustomElementDefinition();
// Only custom element which is in `custom` state could get the
// CustomElementDefinition.
if (definition && definition->IsInObservedAttributeList(aName)) {
nsAutoString ns;
nsContentUtils::NameSpaceManager()->GetNameSpaceURI(aNameSpaceID, ns);
const CustomElementData* data = GetCustomElementData();
if (data && data->mState == CustomElementData::State::eCustom) {
CustomElementDefinition* definition = data->GetCustomElementDefinition();
MOZ_ASSERT(definition, "Should have a valid CustomElementDefinition");
RefPtr<nsAtom> oldValueAtom = oldValue.GetAsAtom();
LifecycleCallbackArgs args = {
nsDependentAtomString(aName), nsDependentAtomString(oldValueAtom),
VoidString(), (ns.IsEmpty() ? VoidString() : ns)};
if (definition->IsInObservedAttributeList(aName)) {
nsAutoString ns;
nsContentUtils::NameSpaceManager()->GetNameSpaceURI(aNameSpaceID, ns);
nsContentUtils::EnqueueLifecycleCallback(Document::eAttributeChanged, this,
&args, nullptr, definition);
RefPtr<nsAtom> oldValueAtom = oldValue.GetAsAtom();
LifecycleCallbackArgs args = {
nsDependentAtomString(aName), nsDependentAtomString(oldValueAtom),
VoidString(), (ns.IsEmpty() ? VoidString() : ns)};
nsContentUtils::EnqueueLifecycleCallback(
Document::eAttributeChanged, this, &args, nullptr, definition);
}
}
rv = AfterSetAttr(aNameSpaceID, aName, nullptr, &oldValue, nullptr, aNotify);

View File

@ -50,6 +50,39 @@ test_with_window(function (contentWindow) {
assert_connected_log_entry(log[5], element2);
}, 'Upgrading a custom element must invoke attributeChangedCallback and connectedCallback before start upgrading another element');
test_with_window(function (contentWindow) {
const contentDocument = contentWindow.document;
contentDocument.write('<test-element>');
const element = contentDocument.querySelector('test-element');
assert_equals(Object.getPrototypeOf(element), contentWindow.HTMLElement.prototype);
let log = [];
class TestElement extends contentWindow.HTMLElement {
constructor() {
super();
this.id = "foo";
this.setAttribute('id', 'foo');
this.removeAttribute('id');
this.style.fontSize = '10px';
log.push(create_constructor_log(this));
}
connectedCallback(...args) {
log.push(create_connected_callback_log(this, ...args));
}
attributeChangedCallback(...args) {
log.push(create_attribute_changed_callback_log(this, ...args));
}
static get observedAttributes() { return ['id', 'style']; }
}
contentWindow.customElements.define('test-element', TestElement);
assert_equals(Object.getPrototypeOf(element), TestElement.prototype);
assert_equals(log.length, 2);
assert_constructor_log_entry(log[0], element);
assert_connected_log_entry(log[1], element);
}, 'Upgrading a custom element must not invoke attributeChangedCallback for the attribute that is changed during upgrading');
test_with_window(function (contentWindow) {
const contentDocument = contentWindow.document;
contentDocument.write('<test-element id="first-element">');