Propagate Trusted Types errors for Node.textContent (#38871)

Part of #36258

Signed-off-by: Tim van der Lippe <tvanderlippe@gmail.com>
This commit is contained in:
Tim van der Lippe 2025-08-22 21:41:50 +02:00 committed by GitHub
parent f1a5da6836
commit 10ac177aa5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
14 changed files with 52 additions and 85 deletions

View file

@ -4982,7 +4982,7 @@ impl DocumentMethods<crate::DomTypeHolder> for Document {
None => return, None => return,
}; };
let elem = if root.namespace() == &ns!(svg) && root.local_name() == &local_name!("svg") { let node = if root.namespace() == &ns!(svg) && root.local_name() == &local_name!("svg") {
let elem = root.upcast::<Node>().child_elements().find(|node| { let elem = root.upcast::<Node>().child_elements().find(|node| {
node.namespace() == &ns!(svg) && node.local_name() == &local_name!("title") node.namespace() == &ns!(svg) && node.local_name() == &local_name!("title")
}); });
@ -5036,7 +5036,7 @@ impl DocumentMethods<crate::DomTypeHolder> for Document {
return; return;
}; };
elem.SetTextContent(Some(title), can_gc); node.set_text_content_for_element(Some(title), can_gc);
} }
// https://html.spec.whatwg.org/multipage/#dom-document-head // https://html.spec.whatwg.org/multipage/#dom-document-head

View file

@ -3876,8 +3876,7 @@ impl ElementMethods<crate::DomTypeHolder> for Element {
.iter() .iter()
.any(|c| matches!(*c, b'&' | b'\0' | b'<' | b'\r')) .any(|c| matches!(*c, b'&' | b'\0' | b'<' | b'\r'))
{ {
Node::SetTextContent(&target, Some(value), can_gc); return Node::SetTextContent(&target, Some(value), can_gc);
return Ok(());
} }
// Step 3: Let fragment be the result of invoking the fragment parsing algorithm steps // Step 3: Let fragment be the result of invoking the fragment parsing algorithm steps

View file

@ -132,7 +132,8 @@ impl HTMLAnchorElementMethods<crate::DomTypeHolder> for HTMLAnchorElement {
// https://html.spec.whatwg.org/multipage/#dom-a-text // https://html.spec.whatwg.org/multipage/#dom-a-text
fn SetText(&self, value: DOMString, can_gc: CanGc) { fn SetText(&self, value: DOMString, can_gc: CanGc) {
self.upcast::<Node>().SetTextContent(Some(value), can_gc) self.upcast::<Node>()
.set_text_content_for_element(Some(value), can_gc)
} }
// https://html.spec.whatwg.org/multipage/#dom-a-rel // https://html.spec.whatwg.org/multipage/#dom-a-rel

View file

@ -114,7 +114,7 @@ impl HTMLDetailsElement {
HTMLElement::new(local_name!("summary"), None, &document, None, can_gc); HTMLElement::new(local_name!("summary"), None, &document, None, can_gc);
fallback_summary fallback_summary
.upcast::<Node>() .upcast::<Node>()
.SetTextContent(Some(DEFAULT_SUMMARY.into()), can_gc); .set_text_content_for_element(Some(DEFAULT_SUMMARY.into()), can_gc);
summary summary
.upcast::<Node>() .upcast::<Node>()
.AppendChild(fallback_summary.upcast::<Node>(), can_gc) .AppendChild(fallback_summary.upcast::<Node>(), can_gc)

View file

@ -2055,7 +2055,7 @@ impl HTMLMediaElement {
*self.media_controls_id.borrow_mut() = Some(id); *self.media_controls_id.borrow_mut() = Some(id);
script script
.upcast::<Node>() .upcast::<Node>()
.SetTextContent(Some(DOMString::from(media_controls_script)), can_gc); .set_text_content_for_element(Some(DOMString::from(media_controls_script)), can_gc);
if let Err(e) = shadow_root if let Err(e) = shadow_root
.upcast::<Node>() .upcast::<Node>()
.AppendChild(script.upcast::<Node>(), can_gc) .AppendChild(script.upcast::<Node>(), can_gc)
@ -2074,7 +2074,7 @@ impl HTMLMediaElement {
); );
style style
.upcast::<Node>() .upcast::<Node>()
.SetTextContent(Some(DOMString::from(MEDIA_CONTROL_CSS)), can_gc); .set_text_content_for_element(Some(DOMString::from(MEDIA_CONTROL_CSS)), can_gc);
if let Err(e) = shadow_root if let Err(e) = shadow_root
.upcast::<Node>() .upcast::<Node>()

View file

@ -176,7 +176,9 @@ impl HTMLOptionElementMethods<crate::DomTypeHolder> for HTMLOptionElement {
let option = DomRoot::downcast::<HTMLOptionElement>(element).unwrap(); let option = DomRoot::downcast::<HTMLOptionElement>(element).unwrap();
if !text.is_empty() { if !text.is_empty() {
option.upcast::<Node>().SetTextContent(Some(text), can_gc) option
.upcast::<Node>()
.set_text_content_for_element(Some(text), can_gc)
} }
if let Some(val) = value { if let Some(val) = value {
@ -224,7 +226,8 @@ impl HTMLOptionElementMethods<crate::DomTypeHolder> for HTMLOptionElement {
/// <https://html.spec.whatwg.org/multipage/#dom-option-text> /// <https://html.spec.whatwg.org/multipage/#dom-option-text>
fn SetText(&self, value: DOMString, can_gc: CanGc) { fn SetText(&self, value: DOMString, can_gc: CanGc) {
self.upcast::<Node>().SetTextContent(Some(value), can_gc) self.upcast::<Node>()
.set_text_content_for_element(Some(value), can_gc)
} }
/// <https://html.spec.whatwg.org/multipage/#dom-option-form> /// <https://html.spec.whatwg.org/multipage/#dom-option-form>

View file

@ -1523,7 +1523,8 @@ impl HTMLScriptElementMethods<crate::DomTypeHolder> for HTMLScriptElement {
// Step 2: Set this's script text value to value. // Step 2: Set this's script text value to value.
*self.script_text.borrow_mut() = value.clone(); *self.script_text.borrow_mut() = value.clone();
// Step 3: Run set text content with this and value. // Step 3: Run set text content with this and value.
self.upcast::<Node>().SetTextContent(Some(value), can_gc); self.upcast::<Node>()
.set_text_content_for_element(Some(value), can_gc);
Ok(()) Ok(())
} }

View file

@ -294,7 +294,7 @@ impl HTMLSelectElement {
); );
chevron_container chevron_container
.upcast::<Node>() .upcast::<Node>()
.SetTextContent(Some("".into()), can_gc); .set_text_content_for_element(Some("".into()), can_gc);
select_box select_box
.upcast::<Node>() .upcast::<Node>()
.AppendChild(chevron_container.upcast::<Node>(), can_gc) .AppendChild(chevron_container.upcast::<Node>(), can_gc)

View file

@ -311,7 +311,8 @@ impl HTMLTextAreaElementMethods<crate::DomTypeHolder> for HTMLTextAreaElement {
// https://html.spec.whatwg.org/multipage/#dom-textarea-defaultvalue // https://html.spec.whatwg.org/multipage/#dom-textarea-defaultvalue
fn SetDefaultValue(&self, value: DOMString, can_gc: CanGc) { fn SetDefaultValue(&self, value: DOMString, can_gc: CanGc) {
self.upcast::<Node>().SetTextContent(Some(value), can_gc); self.upcast::<Node>()
.set_text_content_for_element(Some(value), can_gc);
// if the element's dirty value flag is false, then the element's // if the element's dirty value flag is false, then the element's
// raw value must be set to the value of the element's textContent IDL attribute // raw value must be set to the value of the element's textContent IDL attribute

View file

@ -9,7 +9,6 @@ use html5ever::{LocalName, Prefix};
use js::rust::HandleObject; use js::rust::HandleObject;
use crate::dom::bindings::codegen::Bindings::HTMLTitleElementBinding::HTMLTitleElementMethods; use crate::dom::bindings::codegen::Bindings::HTMLTitleElementBinding::HTMLTitleElementMethods;
use crate::dom::bindings::codegen::Bindings::NodeBinding::NodeMethods;
use crate::dom::bindings::inheritance::Castable; use crate::dom::bindings::inheritance::Castable;
use crate::dom::bindings::root::DomRoot; use crate::dom::bindings::root::DomRoot;
use crate::dom::bindings::str::DOMString; use crate::dom::bindings::str::DOMString;
@ -71,7 +70,8 @@ impl HTMLTitleElementMethods<crate::DomTypeHolder> for HTMLTitleElement {
// https://html.spec.whatwg.org/multipage/#dom-title-text // https://html.spec.whatwg.org/multipage/#dom-title-text
fn SetText(&self, value: DOMString, can_gc: CanGc) { fn SetText(&self, value: DOMString, can_gc: CanGc) {
self.upcast::<Node>().SetTextContent(Some(value), can_gc) self.upcast::<Node>()
.set_text_content_for_element(Some(value), can_gc)
} }
} }

View file

@ -3067,6 +3067,30 @@ impl Node {
DOMString::from(content) DOMString::from(content)
} }
/// <https://dom.spec.whatwg.org/#string-replace-all>
pub(crate) fn set_text_content_for_element(&self, value: Option<DOMString>, can_gc: CanGc) {
// This should only be called for elements and document fragments when setting the
// text content: https://dom.spec.whatwg.org/#set-text-content
assert!(matches!(
self.type_id(),
NodeTypeId::DocumentFragment(_) | NodeTypeId::Element(..)
));
let value = value.unwrap_or_default();
let node = if value.is_empty() {
// Step 1. Let node be null.
None
} else {
// Step 2. If string is not the empty string, then set node to
// a new Text node whose data is string and node document is parents node document.
Some(DomRoot::upcast(
self.owner_doc().CreateTextNode(value, can_gc),
))
};
// Step 3. Replace all with node within parent.
Self::replace_all(node.as_deref(), self, can_gc);
}
pub(crate) fn namespace_to_string(namespace: Namespace) -> Option<DOMString> { pub(crate) fn namespace_to_string(namespace: Namespace) -> Option<DOMString> {
match namespace { match namespace {
ns!() => None, ns!() => None,
@ -3372,34 +3396,23 @@ impl NodeMethods<crate::DomTypeHolder> for Node {
} }
} }
/// <https://dom.spec.whatwg.org/#dom-node-textcontent> /// <https://dom.spec.whatwg.org/#set-text-content>
fn SetTextContent(&self, value: Option<DOMString>, can_gc: CanGc) { fn SetTextContent(&self, value: Option<DOMString>, can_gc: CanGc) -> Fallible<()> {
let value = value.unwrap_or_default();
match self.type_id() { match self.type_id() {
NodeTypeId::DocumentFragment(_) | NodeTypeId::Element(..) => { NodeTypeId::DocumentFragment(_) | NodeTypeId::Element(..) => {
// Step 1-2. self.set_text_content_for_element(value, can_gc);
let node = if value.is_empty() {
None
} else {
Some(DomRoot::upcast(
self.owner_doc().CreateTextNode(value, can_gc),
))
};
// Step 3.
Node::replace_all(node.as_deref(), self, can_gc);
}, },
NodeTypeId::Attr => { NodeTypeId::Attr => {
let attr = self.downcast::<Attr>().unwrap(); let attr = self.downcast::<Attr>().unwrap();
// TODO(#36258): Propagate failure to callers attr.SetValue(value.unwrap_or_default(), can_gc)?;
let _ = attr.SetValue(value, can_gc);
}, },
NodeTypeId::CharacterData(..) => { NodeTypeId::CharacterData(..) => {
let characterdata = self.downcast::<CharacterData>().unwrap(); let characterdata = self.downcast::<CharacterData>().unwrap();
characterdata.SetData(value); characterdata.SetData(value.unwrap_or_default());
}, },
NodeTypeId::DocumentType | NodeTypeId::Document(_) => {}, NodeTypeId::DocumentType | NodeTypeId::Document(_) => {},
} };
Ok(())
} }
/// <https://dom.spec.whatwg.org/#dom-node-insertbefore> /// <https://dom.spec.whatwg.org/#dom-node-insertbefore>

View file

@ -56,7 +56,7 @@ interface Node : EventTarget {
[CEReactions, Pure, SetterThrows] [CEReactions, Pure, SetterThrows]
attribute DOMString? nodeValue; attribute DOMString? nodeValue;
[CEReactions, Pure] [CEReactions, Pure, SetterThrows]
attribute DOMString? textContent; attribute DOMString? textContent;
[CEReactions] [CEReactions]
undefined normalize(); undefined normalize();

View file

@ -1,9 +0,0 @@
[block-string-assignment-to-attribute-via-attribute-node.html]
[Set script.src via textContent]
expected: FAIL
[Set iframe.srcdoc via textContent]
expected: FAIL
[Set div.onclick via textContent]
expected: FAIL

View file

@ -1,42 +0,0 @@
[set-attributes-require-trusted-types-no-default-policy.html]
[Node.textContent throws for elementNS=http://www.w3.org/1999/xhtml, element=DIV, attrName=onclick with a plain string]
expected: FAIL
[Node.textContent throws for elementNS=http://www.w3.org/2000/svg, element=g, attrName=ondblclick with a plain string]
expected: FAIL
[Node.textContent throws for elementNS=http://www.w3.org/1998/Math/MathML, element=mrow, attrName=onmousedown with a plain string]
expected: FAIL
[Node.textContent throws for elementNS=http://www.w3.org/1999/xhtml, element=IFRAME, attrName=srcdoc with a plain string]
expected: FAIL
[Node.textContent throws for elementNS=http://www.w3.org/1999/xhtml, element=SCRIPT, attrName=src with a plain string]
expected: FAIL
[Node.textContent throws for elementNS=http://www.w3.org/2000/svg, element=script, attrName=href with a plain string]
expected: FAIL
[Node.textContent throws for elementNS=http://www.w3.org/2000/svg, element=script, attrNS=http://www.w3.org/1999/xlink, attrName=href with a plain string]
expected: FAIL
[Node.textContent throws for elementNS=http://www.w3.org/1999/xhtml, element=DIV, attrName=onclick with a TrustedScript input.]
expected: FAIL
[Node.textContent throws for elementNS=http://www.w3.org/2000/svg, element=g, attrName=ondblclick with a TrustedScript input.]
expected: FAIL
[Node.textContent throws for elementNS=http://www.w3.org/1998/Math/MathML, element=mrow, attrName=onmousedown with a TrustedScript input.]
expected: FAIL
[Node.textContent throws for elementNS=http://www.w3.org/1999/xhtml, element=IFRAME, attrName=srcdoc with a TrustedHTML input.]
expected: FAIL
[Node.textContent throws for elementNS=http://www.w3.org/1999/xhtml, element=SCRIPT, attrName=src with a TrustedScriptURL input.]
expected: FAIL
[Node.textContent throws for elementNS=http://www.w3.org/2000/svg, element=script, attrName=href with a TrustedScriptURL input.]
expected: FAIL
[Node.textContent throws for elementNS=http://www.w3.org/2000/svg, element=script, attrNS=http://www.w3.org/1999/xlink, attrName=href with a TrustedScriptURL input.]
expected: FAIL