Implement Trusted types document write sinks (#36824)

Implements the Document.write algorithm covering
Trusted HTML.

Part of #36258

Signed-off-by: Tim van der Lippe <tvanderlippe@gmail.com>

Signed-off-by: Tim van der Lippe <tvanderlippe@gmail.com>
This commit is contained in:
Tim van der Lippe 2025-05-04 13:50:33 +02:00 committed by GitHub
parent 43edab336a
commit c00e0aae61
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 142 additions and 107 deletions

View file

@ -103,7 +103,9 @@ use crate::dom::bindings::codegen::Bindings::WindowBinding::{
};
use crate::dom::bindings::codegen::Bindings::XPathEvaluatorBinding::XPathEvaluatorMethods;
use crate::dom::bindings::codegen::Bindings::XPathNSResolverBinding::XPathNSResolver;
use crate::dom::bindings::codegen::UnionTypes::{NodeOrString, StringOrElementCreationOptions};
use crate::dom::bindings::codegen::UnionTypes::{
NodeOrString, StringOrElementCreationOptions, TrustedHTMLOrString,
};
use crate::dom::bindings::error::{Error, ErrorInfo, ErrorResult, Fallible};
use crate::dom::bindings::inheritance::{Castable, ElementTypeId, HTMLElementTypeId, NodeTypeId};
use crate::dom::bindings::num::Finite;
@ -184,6 +186,7 @@ use crate::dom::touch::Touch;
use crate::dom::touchevent::TouchEvent as DomTouchEvent;
use crate::dom::touchlist::TouchList;
use crate::dom::treewalker::TreeWalker;
use crate::dom::trustedhtml::TrustedHTML;
use crate::dom::types::VisibilityStateEntry;
use crate::dom::uievent::UIEvent;
use crate::dom::virtualmethods::vtable_for;
@ -3818,6 +3821,90 @@ impl Document {
.Performance()
.queue_entry(entry.upcast::<PerformanceEntry>(), can_gc);
}
/// <https://html.spec.whatwg.org/multipage/#document-write-steps>
fn write(
&self,
text: Vec<TrustedHTMLOrString>,
line_feed: bool,
containing_class: &str,
field: &str,
can_gc: CanGc,
) -> ErrorResult {
// Step 1: Let string be the empty string.
let mut strings: Vec<String> = Vec::with_capacity(text.len());
// Step 2: Let isTrusted be false if text contains a string; otherwise true.
let mut is_trusted = true;
// Step 3: For each value of text:
for value in text {
match value {
// Step 3.1: If value is a TrustedHTML object, then append value's associated data to string.
TrustedHTMLOrString::TrustedHTML(trusted_html) => {
strings.push(trusted_html.to_string().to_owned());
},
TrustedHTMLOrString::String(str_) => {
// Step 2: Let isTrusted be false if text contains a string; otherwise true.
is_trusted = false;
// Step 3.2: Otherwise, append value to string.
strings.push(str_.into());
},
};
}
let mut string = itertools::join(strings, "");
// Step 4: If isTrusted is false, set string to the result of invoking the
// Get Trusted Type compliant string algorithm with TrustedHTML,
// this's relevant global object, string, sink, and "script".
if !is_trusted {
string = TrustedHTML::get_trusted_script_compliant_string(
&self.global(),
TrustedHTMLOrString::String(string.into()),
containing_class,
field,
can_gc,
)?;
}
// Step 5: If lineFeed is true, append U+000A LINE FEED to string.
if line_feed {
string.push('\n');
}
// Step 6: If document is an XML document, then throw an "InvalidStateError" DOMException.
if !self.is_html_document() {
return Err(Error::InvalidState);
}
// Step 7: If document's throw-on-dynamic-markup-insertion counter is greater than 0,
// then throw an "InvalidStateError" DOMException.
if self.throw_on_dynamic_markup_insertion_counter.get() > 0 {
return Err(Error::InvalidState);
}
// Step 8: If document's active parser was aborted is true, then return.
if !self.is_active() || self.active_parser_was_aborted.get() {
return Ok(());
}
let parser = match self.get_current_parser() {
Some(ref parser) if parser.can_write() => DomRoot::from_ref(&**parser),
// Step 9: If the insertion point is undefined, then:
_ => {
// Step 9.1: If document's unload counter is greater than 0 or
// document's ignore-destructive-writes counter is greater than 0, then return.
if self.is_prompting_or_unloading() ||
self.ignore_destructive_writes_counter.get() > 0
{
return Ok(());
}
// Step 9.2: Run the document open steps with document.
self.Open(None, None, can_gc)?;
self.get_current_parser().unwrap()
},
};
// Steps 10-11.
parser.write(string.into(), can_gc);
Ok(())
}
}
fn is_character_value_key(key: &Key) -> bool {
@ -6408,54 +6495,17 @@ impl DocumentMethods<crate::DomTypeHolder> for Document {
}
// https://html.spec.whatwg.org/multipage/#dom-document-write
fn Write(&self, text: Vec<DOMString>, can_gc: CanGc) -> ErrorResult {
if !self.is_html_document() {
// Step 1.
return Err(Error::InvalidState);
}
// Step 2.
if self.throw_on_dynamic_markup_insertion_counter.get() > 0 {
return Err(Error::InvalidState);
}
// Step 3 - what specifies the is_active() part here?
if !self.is_active() || self.active_parser_was_aborted.get() {
return Ok(());
}
let parser = match self.get_current_parser() {
Some(ref parser) if parser.can_write() => DomRoot::from_ref(&**parser),
_ => {
// Either there is no parser, which means the parsing ended;
// or script nesting level is 0, which means the method was
// called from outside a parser-executed script.
if self.is_prompting_or_unloading() ||
self.ignore_destructive_writes_counter.get() > 0
{
// Step 4.
return Ok(());
}
// Step 5.
self.Open(None, None, can_gc)?;
self.get_current_parser().unwrap()
},
};
// Step 7.
// TODO: handle reload override buffer.
// Steps 6-8.
parser.write(text, can_gc);
// Step 9.
Ok(())
fn Write(&self, text: Vec<TrustedHTMLOrString>, can_gc: CanGc) -> ErrorResult {
// The document.write(...text) method steps are to run the document write steps
// with this, text, false, and "Document write".
self.write(text, false, "Document", "write", can_gc)
}
// https://html.spec.whatwg.org/multipage/#dom-document-writeln
fn Writeln(&self, mut text: Vec<DOMString>, can_gc: CanGc) -> ErrorResult {
text.push("\n".into());
self.Write(text, can_gc)
fn Writeln(&self, text: Vec<TrustedHTMLOrString>, can_gc: CanGc) -> ErrorResult {
// The document.writeln(...text) method steps are to run the document write steps
// with this, text, true, and "Document writeln".
self.write(text, true, "Document", "writeln", can_gc)
}
// https://html.spec.whatwg.org/multipage/#dom-document-close

View file

@ -357,16 +357,14 @@ impl ServoParser {
}
/// Steps 6-8 of <https://html.spec.whatwg.org/multipage/#document.write()>
pub(crate) fn write(&self, text: Vec<DOMString>, can_gc: CanGc) {
pub(crate) fn write(&self, text: DOMString, can_gc: CanGc) {
assert!(self.can_write());
if self.document.has_pending_parsing_blocking_script() {
// There is already a pending parsing blocking script so the
// parser is suspended, we just append everything to the
// script input and abort these steps.
for chunk in text {
self.script_input.push_back(String::from(chunk).into());
}
self.script_input.push_back(String::from(text).into());
return;
}
@ -376,9 +374,7 @@ impl ServoParser {
assert!(self.script_input.is_empty());
let input = BufferQueue::default();
for chunk in text {
input.push_back(String::from(chunk).into());
}
input.push_back(String::from(text).into());
let profiler_chan = self
.document

View file

@ -2,13 +2,19 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use std::fmt;
use dom_struct::dom_struct;
use crate::dom::bindings::codegen::Bindings::TrustedHTMLBinding::TrustedHTMLMethods;
use crate::dom::bindings::codegen::UnionTypes::TrustedHTMLOrString;
use crate::dom::bindings::error::Fallible;
use crate::dom::bindings::reflector::{Reflector, reflect_dom_object};
use crate::dom::bindings::root::DomRoot;
use crate::dom::bindings::str::DOMString;
use crate::dom::globalscope::GlobalScope;
use crate::dom::trustedtypepolicy::TrustedType;
use crate::dom::trustedtypepolicyfactory::TrustedTypePolicyFactory;
use crate::script_runtime::CanGc;
#[dom_struct]
@ -30,6 +36,37 @@ impl TrustedHTML {
pub(crate) fn new(data: String, global: &GlobalScope, can_gc: CanGc) -> DomRoot<Self> {
reflect_dom_object(Box::new(Self::new_inherited(data)), global, can_gc)
}
pub(crate) fn get_trusted_script_compliant_string(
global: &GlobalScope,
value: TrustedHTMLOrString,
containing_class: &str,
field: &str,
can_gc: CanGc,
) -> Fallible<String> {
match value {
TrustedHTMLOrString::String(value) => {
let sink = format!("{} {}", containing_class, field);
TrustedTypePolicyFactory::get_trusted_type_compliant_string(
TrustedType::TrustedHTML,
global,
value.as_ref().to_owned(),
&sink,
"'script'",
can_gc,
)
},
TrustedHTMLOrString::TrustedHTML(trusted_html) => Ok(trusted_html.to_string()),
}
}
}
impl fmt::Display for TrustedHTML {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str(&self.data)
}
}
impl TrustedHTMLMethods<crate::DomTypeHolder> for TrustedHTML {