mirror of
https://github.com/servo/servo.git
synced 2025-07-23 15:23:42 +01:00
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:
parent
43edab336a
commit
c00e0aae61
9 changed files with 142 additions and 107 deletions
|
@ -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::XPathEvaluatorBinding::XPathEvaluatorMethods;
|
||||||
use crate::dom::bindings::codegen::Bindings::XPathNSResolverBinding::XPathNSResolver;
|
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::error::{Error, ErrorInfo, ErrorResult, Fallible};
|
||||||
use crate::dom::bindings::inheritance::{Castable, ElementTypeId, HTMLElementTypeId, NodeTypeId};
|
use crate::dom::bindings::inheritance::{Castable, ElementTypeId, HTMLElementTypeId, NodeTypeId};
|
||||||
use crate::dom::bindings::num::Finite;
|
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::touchevent::TouchEvent as DomTouchEvent;
|
||||||
use crate::dom::touchlist::TouchList;
|
use crate::dom::touchlist::TouchList;
|
||||||
use crate::dom::treewalker::TreeWalker;
|
use crate::dom::treewalker::TreeWalker;
|
||||||
|
use crate::dom::trustedhtml::TrustedHTML;
|
||||||
use crate::dom::types::VisibilityStateEntry;
|
use crate::dom::types::VisibilityStateEntry;
|
||||||
use crate::dom::uievent::UIEvent;
|
use crate::dom::uievent::UIEvent;
|
||||||
use crate::dom::virtualmethods::vtable_for;
|
use crate::dom::virtualmethods::vtable_for;
|
||||||
|
@ -3818,6 +3821,90 @@ impl Document {
|
||||||
.Performance()
|
.Performance()
|
||||||
.queue_entry(entry.upcast::<PerformanceEntry>(), can_gc);
|
.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 {
|
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
|
// https://html.spec.whatwg.org/multipage/#dom-document-write
|
||||||
fn Write(&self, text: Vec<DOMString>, can_gc: CanGc) -> ErrorResult {
|
fn Write(&self, text: Vec<TrustedHTMLOrString>, can_gc: CanGc) -> ErrorResult {
|
||||||
if !self.is_html_document() {
|
// The document.write(...text) method steps are to run the document write steps
|
||||||
// Step 1.
|
// with this, text, false, and "Document write".
|
||||||
return Err(Error::InvalidState);
|
self.write(text, false, "Document", "write", can_gc)
|
||||||
}
|
|
||||||
|
|
||||||
// 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(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://html.spec.whatwg.org/multipage/#dom-document-writeln
|
// https://html.spec.whatwg.org/multipage/#dom-document-writeln
|
||||||
fn Writeln(&self, mut text: Vec<DOMString>, can_gc: CanGc) -> ErrorResult {
|
fn Writeln(&self, text: Vec<TrustedHTMLOrString>, can_gc: CanGc) -> ErrorResult {
|
||||||
text.push("\n".into());
|
// The document.writeln(...text) method steps are to run the document write steps
|
||||||
self.Write(text, can_gc)
|
// with this, text, true, and "Document writeln".
|
||||||
|
self.write(text, true, "Document", "writeln", can_gc)
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://html.spec.whatwg.org/multipage/#dom-document-close
|
// https://html.spec.whatwg.org/multipage/#dom-document-close
|
||||||
|
|
|
@ -357,16 +357,14 @@ impl ServoParser {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Steps 6-8 of <https://html.spec.whatwg.org/multipage/#document.write()>
|
/// 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());
|
assert!(self.can_write());
|
||||||
|
|
||||||
if self.document.has_pending_parsing_blocking_script() {
|
if self.document.has_pending_parsing_blocking_script() {
|
||||||
// There is already a pending parsing blocking script so the
|
// There is already a pending parsing blocking script so the
|
||||||
// parser is suspended, we just append everything to the
|
// parser is suspended, we just append everything to the
|
||||||
// script input and abort these steps.
|
// script input and abort these steps.
|
||||||
for chunk in text {
|
self.script_input.push_back(String::from(text).into());
|
||||||
self.script_input.push_back(String::from(chunk).into());
|
|
||||||
}
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -376,9 +374,7 @@ impl ServoParser {
|
||||||
assert!(self.script_input.is_empty());
|
assert!(self.script_input.is_empty());
|
||||||
|
|
||||||
let input = BufferQueue::default();
|
let input = BufferQueue::default();
|
||||||
for chunk in text {
|
input.push_back(String::from(text).into());
|
||||||
input.push_back(String::from(chunk).into());
|
|
||||||
}
|
|
||||||
|
|
||||||
let profiler_chan = self
|
let profiler_chan = self
|
||||||
.document
|
.document
|
||||||
|
|
|
@ -2,13 +2,19 @@
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
* 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/. */
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
use std::fmt;
|
||||||
|
|
||||||
use dom_struct::dom_struct;
|
use dom_struct::dom_struct;
|
||||||
|
|
||||||
use crate::dom::bindings::codegen::Bindings::TrustedHTMLBinding::TrustedHTMLMethods;
|
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::reflector::{Reflector, reflect_dom_object};
|
||||||
use crate::dom::bindings::root::DomRoot;
|
use crate::dom::bindings::root::DomRoot;
|
||||||
use crate::dom::bindings::str::DOMString;
|
use crate::dom::bindings::str::DOMString;
|
||||||
use crate::dom::globalscope::GlobalScope;
|
use crate::dom::globalscope::GlobalScope;
|
||||||
|
use crate::dom::trustedtypepolicy::TrustedType;
|
||||||
|
use crate::dom::trustedtypepolicyfactory::TrustedTypePolicyFactory;
|
||||||
use crate::script_runtime::CanGc;
|
use crate::script_runtime::CanGc;
|
||||||
|
|
||||||
#[dom_struct]
|
#[dom_struct]
|
||||||
|
@ -30,6 +36,37 @@ impl TrustedHTML {
|
||||||
pub(crate) fn new(data: String, global: &GlobalScope, can_gc: CanGc) -> DomRoot<Self> {
|
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)
|
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 {
|
impl TrustedHTMLMethods<crate::DomTypeHolder> for TrustedHTML {
|
||||||
|
|
|
@ -129,9 +129,9 @@ partial /*sealed*/ interface Document {
|
||||||
[CEReactions, Throws]
|
[CEReactions, Throws]
|
||||||
undefined close();
|
undefined close();
|
||||||
[CEReactions, Throws]
|
[CEReactions, Throws]
|
||||||
undefined write(DOMString... text);
|
undefined write((TrustedHTML or DOMString)... text);
|
||||||
[CEReactions, Throws]
|
[CEReactions, Throws]
|
||||||
undefined writeln(DOMString... text);
|
undefined writeln((TrustedHTML or DOMString)... text);
|
||||||
|
|
||||||
// user interaction
|
// user interaction
|
||||||
readonly attribute Window?/*Proxy?*/ defaultView;
|
readonly attribute Window?/*Proxy?*/ defaultView;
|
||||||
|
|
2
tests/wpt/meta/MANIFEST.json
vendored
2
tests/wpt/meta/MANIFEST.json
vendored
|
@ -818553,7 +818553,7 @@
|
||||||
]
|
]
|
||||||
],
|
],
|
||||||
"block-string-assignment-to-Document-write.html": [
|
"block-string-assignment-to-Document-write.html": [
|
||||||
"0b16a9c4910070b2bcf829d15b1aba8b7be9d060",
|
"c774dca4390955b3b91f1648adb18ad22bd6e355",
|
||||||
[
|
[
|
||||||
null,
|
null,
|
||||||
{}
|
{}
|
||||||
|
|
|
@ -1,3 +0,0 @@
|
||||||
[Document-write-exception-order.xhtml]
|
|
||||||
[`document.write(string)` throws TypeError]
|
|
||||||
expected: FAIL
|
|
|
@ -1,42 +0,0 @@
|
||||||
[block-string-assignment-to-Document-write.html]
|
|
||||||
[`document.write(string)` throws]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[`document.write(string, string)` throws]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[`document.write(string, TrustedHTML)` throws]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[`document.writeln(string)` throws]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[`document.writeln(string, string)` throws]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[`document.writeln(string, TrustedHTML)` throws]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[`document.write(null)` throws]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[`document.writeln(null)` throws]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[`document.write(string)` observes default policy]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[`document.write(string, string)` observes default policy]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[`document.write(string, TrustedHTML)` observes default policy]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[`document.writeln(string)` observes default policy]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[`document.writeln(string, string)` observes default policy]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[`document.writeln(string, TrustedHTML)` observes default policy]
|
|
||||||
expected: FAIL
|
|
|
@ -1,6 +0,0 @@
|
||||||
[trusted-types-reporting-for-Document-write.html]
|
|
||||||
[Violation report for plain string for write() with at least one plain string argument.]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[Violation report for plain string for writeln() with at least one plain string argument.]
|
|
||||||
expected: FAIL
|
|
|
@ -124,6 +124,9 @@
|
||||||
assert_equals(sink, "Document write");
|
assert_equals(sink, "Document write");
|
||||||
} else if (html === "assertSinkEqualsDocumentWriteLn") {
|
} else if (html === "assertSinkEqualsDocumentWriteLn") {
|
||||||
assert_equals(sink, "Document writeln");
|
assert_equals(sink, "Document writeln");
|
||||||
|
} else if (html === "assertSinkEqualsDocumentWriteLn\n") {
|
||||||
|
// Ensure that new line characters aren't incorrectly added prior to processing
|
||||||
|
assert_unreached(`Should not process any other HTML, but got "${html}"`);
|
||||||
}
|
}
|
||||||
|
|
||||||
return html.replace("Hi", "Quack")
|
return html.replace("Hi", "Quack")
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue