From 62d6759106c987ed438d6a8e281fea28859300d3 Mon Sep 17 00:00:00 2001 From: Xiaocheng Hu Date: Fri, 14 Mar 2025 02:03:57 +0800 Subject: [PATCH] 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 * Update tests Signed-off-by: Xiaocheng Hu --------- Signed-off-by: Xiaocheng Hu --- components/script/dom/attr.rs | 2 +- components/script/dom/element.rs | 17 ++++-- components/script/dom/node.rs | 5 +- tests/wpt/meta/MANIFEST.json | 2 +- .../custom-element-reaction-queue.html.ini | 4 -- .../custom-element-reaction-queue.html | 54 +++++++++++++++++++ 6 files changed, 71 insertions(+), 13 deletions(-) delete mode 100644 tests/wpt/meta/custom-elements/custom-element-reaction-queue.html.ini diff --git a/components/script/dom/attr.rs b/components/script/dom/attr.rs index fff594cead0..428f02cece3 100644 --- a/components/script/dom/attr.rs +++ b/components/script/dom/attr.rs @@ -166,7 +166,7 @@ impl Attr { MutationObserver::queue_a_mutation_record(owner.upcast::(), mutation); - if owner.get_custom_element_definition().is_some() { + if owner.is_custom() { let reaction = CallbackReaction::AttributeChanged( name, Some(old_value), diff --git a/components/script/dom/element.rs b/components/script/dom/element.rs index 43f09bc60cf..2b06da5b6ce 100644 --- a/components/script/dom/element.rs +++ b/components/script/dom/element.rs @@ -371,6 +371,11 @@ impl Element { CustomElementState::Uncustomized } + /// + pub(crate) fn is_custom(&self) -> bool { + self.get_custom_element_state() == CustomElementState::Custom + } + pub(crate) fn set_custom_element_definition(&self, definition: Rc) { 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 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()); diff --git a/components/script/dom/node.rs b/components/script/dom/node.rs index 6ae2c5b646f..6326b59f62d 100644 --- a/components/script/dom/node.rs +++ b/components/script/dom/node.rs @@ -422,7 +422,8 @@ impl Node { pub(crate) fn as_custom_element(&self) -> Option> { self.downcast::().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::) { // 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, diff --git a/tests/wpt/meta/MANIFEST.json b/tests/wpt/meta/MANIFEST.json index e54ddc40d22..de9221bf3ac 100644 --- a/tests/wpt/meta/MANIFEST.json +++ b/tests/wpt/meta/MANIFEST.json @@ -612804,7 +612804,7 @@ ] ], "custom-element-reaction-queue.html": [ - "246b15a0af36cffa0b64f1d57e4208538b92bdd7", + "eb8366f2b415894f396da613987982ae41ffe0b6", [ null, {} diff --git a/tests/wpt/meta/custom-elements/custom-element-reaction-queue.html.ini b/tests/wpt/meta/custom-elements/custom-element-reaction-queue.html.ini deleted file mode 100644 index bd5d04c5256..00000000000 --- a/tests/wpt/meta/custom-elements/custom-element-reaction-queue.html.ini +++ /dev/null @@ -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 - diff --git a/tests/wpt/tests/custom-elements/custom-element-reaction-queue.html b/tests/wpt/tests/custom-elements/custom-element-reaction-queue.html index 246b15a0af3..eb8366f2b41 100644 --- a/tests/wpt/tests/custom-elements/custom-element-reaction-queue.html +++ b/tests/wpt/tests/custom-elements/custom-element-reaction-queue.html @@ -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(''); + + 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('');