Check whether an element is custom in the spec-compliant way (#35960)

* Check whether element is custom in spec-compliant way

Signed-off-by: Xiaocheng Hu <xiaochengh.work@gmail.com>

* Update tests

Signed-off-by: Xiaocheng Hu <xiaochengh.work@gmail.com>

---------

Signed-off-by: Xiaocheng Hu <xiaochengh.work@gmail.com>
This commit is contained in:
Xiaocheng Hu 2025-03-14 02:03:57 +08:00 committed by GitHub
parent ea35353e9a
commit 62d6759106
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 71 additions and 13 deletions

View file

@ -166,7 +166,7 @@ impl Attr {
MutationObserver::queue_a_mutation_record(owner.upcast::<Node>(), mutation);
if owner.get_custom_element_definition().is_some() {
if owner.is_custom() {
let reaction = CallbackReaction::AttributeChanged(
name,
Some(old_value),

View file

@ -371,6 +371,11 @@ impl Element {
CustomElementState::Uncustomized
}
/// <https://dom.spec.whatwg.org/#concept-element-custom>
pub(crate) fn is_custom(&self) -> bool {
self.get_custom_element_state() == CustomElementState::Custom
}
pub(crate) fn set_custom_element_definition(&self, definition: Rc<CustomElementDefinition>) {
self.ensure_rare_data().custom_element_definition = Some(definition);
}
@ -1580,7 +1585,7 @@ impl Element {
MutationObserver::queue_a_mutation_record(&self.node, mutation);
if self.get_custom_element_definition().is_some() {
if self.is_custom() {
let value = DOMString::from(&**attr.value());
let reaction = CallbackReaction::AttributeChanged(name, None, Some(value), namespace);
ScriptThread::enqueue_callback_reaction(self, reaction, None);
@ -1772,9 +1777,11 @@ impl Element {
MutationObserver::queue_a_mutation_record(&self.node, mutation);
let reaction =
CallbackReaction::AttributeChanged(name, Some(old_value), None, namespace);
ScriptThread::enqueue_callback_reaction(self, reaction, None);
if self.is_custom() {
let reaction =
CallbackReaction::AttributeChanged(name, Some(old_value), None, namespace);
ScriptThread::enqueue_callback_reaction(self, reaction, None);
}
self.attrs.borrow_mut().remove(idx);
attr.set_owner(None);
@ -2453,7 +2460,7 @@ impl ElementMethods<crate::DomTypeHolder> for Element {
}
// Step 4.
if self.get_custom_element_definition().is_some() {
if self.is_custom() {
let old_name = old_attr.local_name().clone();
let old_value = DOMString::from(&**old_attr.value());
let new_value = DOMString::from(&**attr.value());

View file

@ -422,7 +422,8 @@ impl Node {
pub(crate) fn as_custom_element(&self) -> Option<DomRoot<Element>> {
self.downcast::<Element>().and_then(|element| {
if element.get_custom_element_definition().is_some() {
if element.is_custom() {
assert!(element.get_custom_element_definition().is_some());
Some(DomRoot::from_ref(element))
} else {
None
@ -2334,7 +2335,7 @@ impl Node {
.filter_map(DomRoot::downcast::<Element>)
{
// Step 7.7.2, whatwg/dom#833
if descendant.get_custom_element_definition().is_some() {
if descendant.is_custom() {
if descendant.is_connected() {
ScriptThread::enqueue_callback_reaction(
&descendant,

View file

@ -612804,7 +612804,7 @@
]
],
"custom-element-reaction-queue.html": [
"246b15a0af36cffa0b64f1d57e4208538b92bdd7",
"eb8366f2b415894f396da613987982ae41ffe0b6",
[
null,
{}

View file

@ -1,4 +0,0 @@
[custom-element-reaction-queue.html]
[Upgrading a custom element must not invoke attributeChangedCallback for the attribute that is changed during upgrading]
expected: FAIL

View file

@ -83,6 +83,60 @@ test_with_window(function (contentWindow) {
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>');
const element = contentDocument.querySelector('test-element');
assert_equals(Object.getPrototypeOf(element), contentWindow.HTMLElement.prototype);
let log = [];
class TestElement extends contentWindow.HTMLElement {
constructor() {
super();
this.remove();
log.push(create_constructor_log(this));
}
connectedCallback(...args) {
log.push(create_connected_callback_log(this, ...args));
}
disconnectedCallback(...args) {
log.push(create_disconnected_callback_log(this, ...args));
}
}
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 disconnectedCallback if the element is disconnected during upgrading');
test_with_window(function (contentWindow) {
const contentDocument = contentWindow.document;
const element = contentDocument.createElement('test-element');
assert_equals(Object.getPrototypeOf(element), contentWindow.HTMLElement.prototype);
let log = [];
class TestElement extends contentWindow.HTMLElement {
constructor() {
super();
contentDocument.documentElement.appendChild(this);
log.push(create_constructor_log(this));
}
connectedCallback(...args) {
log.push(create_connected_callback_log(this, ...args));
}
}
contentWindow.customElements.define('test-element', TestElement);
contentWindow.customElements.upgrade(element);
assert_equals(Object.getPrototypeOf(element), TestElement.prototype);
assert_equals(log.length, 1);
assert_constructor_log_entry(log[0], element);
}, 'Upgrading a disconnected custom element must not invoke connectedCallback if the element is connected during upgrading');
test_with_window(function (contentWindow) {
const contentDocument = contentWindow.document;
contentDocument.write('<test-element id="first-element">');