mirror of
https://github.com/lexmount/moli.git
synced 2026-10-07 00:01:00 +00:00
fix(html): preserve document scripting mode
This commit is contained in:
@@ -51,6 +51,10 @@ pub struct Document {
|
||||
ready_state: DocumentReadyState,
|
||||
quirks_mode: QuirksMode,
|
||||
kind: DocumentKind,
|
||||
// The document's HTML scripting flag. Browsing-context policy can still
|
||||
// override execution, but detached/template documents must retain `false`
|
||||
// for fragment parsing and serialization even without a window.
|
||||
scripting_enabled: bool,
|
||||
css_target: Option<NativeNodeId>,
|
||||
default_language: Option<Box<str>>,
|
||||
source_last_modified_ms: Option<f64>,
|
||||
@@ -63,6 +67,10 @@ impl Document {
|
||||
}
|
||||
|
||||
pub fn new_html(url: Url) -> Self {
|
||||
Self::new_html_with_scripting(url, true)
|
||||
}
|
||||
|
||||
pub fn new_html_with_scripting(url: Url, scripting_enabled: bool) -> Self {
|
||||
Self {
|
||||
base_url_state: DocumentBaseUrlState::new(&url),
|
||||
url,
|
||||
@@ -70,6 +78,7 @@ impl Document {
|
||||
ready_state: DocumentReadyState::Complete,
|
||||
quirks_mode: QuirksMode::NoQuirks,
|
||||
kind: DocumentKind::Html,
|
||||
scripting_enabled,
|
||||
css_target: None,
|
||||
default_language: None,
|
||||
source_last_modified_ms: None,
|
||||
@@ -84,6 +93,7 @@ impl Document {
|
||||
ready_state: DocumentReadyState::Complete,
|
||||
quirks_mode: QuirksMode::NoQuirks,
|
||||
kind: DocumentKind::Xml,
|
||||
scripting_enabled: true,
|
||||
css_target: None,
|
||||
default_language: None,
|
||||
source_last_modified_ms: None,
|
||||
@@ -126,6 +136,10 @@ impl Document {
|
||||
self.kind == DocumentKind::Html
|
||||
}
|
||||
|
||||
pub fn scripting_enabled(&self) -> bool {
|
||||
self.scripting_enabled
|
||||
}
|
||||
|
||||
pub fn fallback_base_url(&self) -> &Url {
|
||||
self.base_url_state.fallback_base_url()
|
||||
}
|
||||
@@ -159,6 +173,10 @@ impl Document {
|
||||
self.content_type = content_type.into().into_boxed_str();
|
||||
}
|
||||
|
||||
pub fn set_scripting_enabled(&mut self, scripting_enabled: bool) {
|
||||
self.scripting_enabled = scripting_enabled;
|
||||
}
|
||||
|
||||
pub fn set_css_target(&mut self, target: Option<NativeNodeId>) -> bool {
|
||||
if self.css_target == target {
|
||||
return false;
|
||||
|
||||
@@ -22,8 +22,19 @@ impl DomHost {
|
||||
}
|
||||
|
||||
pub fn create_detached_html_document_with_url(&mut self, url: Url) -> DomHandle {
|
||||
self.create_detached_html_document_with_url_and_scripting(url, true)
|
||||
}
|
||||
|
||||
pub fn create_detached_html_document_with_url_and_scripting(
|
||||
&mut self,
|
||||
url: Url,
|
||||
scripting_enabled: bool,
|
||||
) -> DomHandle {
|
||||
self.dom.create_node(
|
||||
NodeData::Document(Box::new(Document::new_html(url))),
|
||||
NodeData::Document(Box::new(Document::new_html_with_scripting(
|
||||
url,
|
||||
scripting_enabled,
|
||||
))),
|
||||
None,
|
||||
false,
|
||||
false,
|
||||
@@ -973,6 +984,15 @@ impl DomHost {
|
||||
.map(Document::quirks_mode)
|
||||
}
|
||||
|
||||
pub fn document_scripting_enabled_for_handle(
|
||||
&self,
|
||||
document_handle: DomHandle,
|
||||
) -> Option<bool> {
|
||||
self.node(document_handle)
|
||||
.and_then(Node::as_document)
|
||||
.map(Document::scripting_enabled)
|
||||
}
|
||||
|
||||
pub fn document_base_url(&self) -> Option<Url> {
|
||||
self.document_base_url_for_handle(self.document_handle())
|
||||
}
|
||||
@@ -1134,6 +1154,25 @@ impl DomHost {
|
||||
true
|
||||
}
|
||||
|
||||
pub fn set_document_scripting_enabled_for_handle(
|
||||
&mut self,
|
||||
document_handle: DomHandle,
|
||||
scripting_enabled: bool,
|
||||
) -> bool {
|
||||
let Some(document) = self
|
||||
.node_mut(document_handle)
|
||||
.and_then(|node| node.data_mut().as_document_mut())
|
||||
else {
|
||||
return false;
|
||||
};
|
||||
if document.scripting_enabled() == scripting_enabled {
|
||||
return false;
|
||||
}
|
||||
document.set_scripting_enabled(scripting_enabled);
|
||||
self.record_mutation(MutationScope::LocalState);
|
||||
true
|
||||
}
|
||||
|
||||
pub fn set_document_quirks_mode_for_handle(
|
||||
&mut self,
|
||||
document_handle: DomHandle,
|
||||
|
||||
@@ -458,6 +458,23 @@ pub(super) fn serialize_html(
|
||||
.then_some(html)
|
||||
}
|
||||
|
||||
pub(super) fn serialize_html_with_stored_scripting_state(
|
||||
dom: &NativeDom,
|
||||
node_id: NativeNodeId,
|
||||
target: HtmlSerializationTarget,
|
||||
) -> Option<String> {
|
||||
let scripting_enabled_for_node =
|
||||
|node| dom.node_document_scripting_enabled(node).unwrap_or(false);
|
||||
let mut html = String::new();
|
||||
serialize_html_into_sink(
|
||||
dom,
|
||||
node_id,
|
||||
HtmlSerializationOptions::new(target, &scripting_enabled_for_node),
|
||||
&mut html,
|
||||
)
|
||||
.then_some(html)
|
||||
}
|
||||
|
||||
pub(in crate::native) fn serialize_html_with_shadow_root_provider<F>(
|
||||
dom: &NativeDom,
|
||||
node_id: NativeNodeId,
|
||||
@@ -483,7 +500,8 @@ pub(super) fn serialize_html_with_limit(
|
||||
node_id: NativeNodeId,
|
||||
max_bytes: usize,
|
||||
) -> Result<Option<String>, HtmlSerializationLimitExceeded> {
|
||||
let scripting_enabled_for_node = |_: NativeNodeId| true;
|
||||
let scripting_enabled_for_node =
|
||||
|node| dom.node_document_scripting_enabled(node).unwrap_or(false);
|
||||
let mut out = BoundedHtmlSerialization::new(max_bytes);
|
||||
if !serialize_html_into_sink(
|
||||
dom,
|
||||
|
||||
@@ -8,7 +8,9 @@ pub(super) use engine::{
|
||||
HtmlSerializationTarget, HtmlSerializedShadowRoot, escape_html_attribute_into_string,
|
||||
serialize_html_with_shadow_root_provider,
|
||||
};
|
||||
use engine::{serialize_html, serialize_html_with_limit};
|
||||
use engine::{
|
||||
serialize_html, serialize_html_with_limit, serialize_html_with_stored_scripting_state,
|
||||
};
|
||||
|
||||
/// A bounded serialization stopped before appending bytes past its limit.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
@@ -30,7 +32,12 @@ impl std::error::Error for HtmlSerializationLimitExceeded {}
|
||||
|
||||
impl NativeDom {
|
||||
pub fn serialize_document(&self) -> String {
|
||||
self.serialize_document_with_scripting_enabled(true)
|
||||
serialize_html_with_stored_scripting_state(
|
||||
self,
|
||||
self.document_node_id,
|
||||
HtmlSerializationTarget::ChildrenOnly,
|
||||
)
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
pub fn serialize_document_with_scripting_enabled(&self, scripting_enabled: bool) -> String {
|
||||
@@ -44,7 +51,11 @@ impl NativeDom {
|
||||
}
|
||||
|
||||
pub fn outer_html(&self, node_id: NativeNodeId) -> Option<String> {
|
||||
self.outer_html_with_scripting_enabled(node_id, true)
|
||||
serialize_html_with_stored_scripting_state(
|
||||
self,
|
||||
node_id,
|
||||
HtmlSerializationTarget::IncludeNode,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn outer_html_with_scripting_enabled(
|
||||
@@ -74,7 +85,11 @@ impl NativeDom {
|
||||
}
|
||||
|
||||
pub fn inner_html(&self, node_id: NativeNodeId) -> Option<String> {
|
||||
self.inner_html_with_scripting_enabled(node_id, true)
|
||||
serialize_html_with_stored_scripting_state(
|
||||
self,
|
||||
node_id,
|
||||
HtmlSerializationTarget::ChildrenOnly,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn inner_html_with_scripting_enabled(
|
||||
|
||||
@@ -159,13 +159,20 @@ impl NativeDom {
|
||||
}
|
||||
|
||||
pub fn new_html(final_url: url::Url) -> Self {
|
||||
Self::new_html_with_scripting(final_url, true)
|
||||
}
|
||||
|
||||
pub fn new_html_with_scripting(final_url: url::Url, scripting_enabled: bool) -> Self {
|
||||
let document_node_id = NativeNodeId::new(0);
|
||||
let document_node = Node::new(
|
||||
document_node_id,
|
||||
None,
|
||||
None,
|
||||
NodeFlags::new(true),
|
||||
NodeData::Document(Box::new(Document::new_html(final_url))),
|
||||
NodeData::Document(Box::new(Document::new_html_with_scripting(
|
||||
final_url,
|
||||
scripting_enabled,
|
||||
))),
|
||||
);
|
||||
Self {
|
||||
nodes: NativeNodeStorage::from_node(document_node),
|
||||
@@ -507,7 +514,7 @@ impl NativeDom {
|
||||
.map(|document| document.url().clone())
|
||||
.unwrap_or_else(|| url::Url::parse("about:blank").expect("about:blank is valid"));
|
||||
let owner_document = self.create_node(
|
||||
NodeData::Document(Box::new(Document::new_html(url))),
|
||||
NodeData::Document(Box::new(Document::new_html_with_scripting(url, false))),
|
||||
None,
|
||||
false,
|
||||
false,
|
||||
@@ -2584,6 +2591,12 @@ mod tests {
|
||||
.and_then(Node::owner_document)
|
||||
.expect("template content owner document");
|
||||
assert_ne!(content_owner, dom.document_node_id());
|
||||
assert_eq!(
|
||||
dom.node(content_owner)
|
||||
.and_then(Node::as_document)
|
||||
.map(Document::scripting_enabled),
|
||||
Some(false)
|
||||
);
|
||||
|
||||
let child = dom.create_element("span");
|
||||
assert_eq!(
|
||||
|
||||
@@ -285,6 +285,17 @@ impl NativeDom {
|
||||
.map(|document| document.is_html_document())
|
||||
}
|
||||
|
||||
pub fn node_document_scripting_enabled(&self, node_id: NativeNodeId) -> Option<bool> {
|
||||
let node = self.node(node_id)?;
|
||||
if let Some(document) = node.as_document() {
|
||||
return Some(document.scripting_enabled());
|
||||
}
|
||||
let owner_document = node.owner_document()?;
|
||||
self.node(owner_document)
|
||||
.and_then(Node::as_document)
|
||||
.map(|document| document.scripting_enabled())
|
||||
}
|
||||
|
||||
fn collect_matching_elements(
|
||||
&self,
|
||||
root: NativeNodeId,
|
||||
|
||||
+40
-7
@@ -417,7 +417,14 @@ impl HtmlParser {
|
||||
}
|
||||
|
||||
pub fn parse_dom_host(&self, final_url: Url, html: String) -> DomHost {
|
||||
let stream = self.start_document(final_url);
|
||||
let target =
|
||||
ParserStreamHtmlTreeSinkTarget::new_with_declarative_shadow_roots_and_scripting(
|
||||
final_url,
|
||||
true,
|
||||
self.scripting_enabled,
|
||||
);
|
||||
let stream =
|
||||
HtmlTreeSinkStream::from_target_with_scripting(target, self.scripting_enabled);
|
||||
for chunk in html_chunks(&html) {
|
||||
stream.feed(chunk);
|
||||
}
|
||||
@@ -430,8 +437,13 @@ impl HtmlParser {
|
||||
html: String,
|
||||
) -> NativeDom {
|
||||
let target =
|
||||
ParserStreamHtmlTreeSinkTarget::new_with_declarative_shadow_roots(final_url, false);
|
||||
let stream = HtmlTreeSinkStream::from_target_with_scripting(target, self.scripting_enabled);
|
||||
ParserStreamHtmlTreeSinkTarget::new_with_declarative_shadow_roots_and_scripting(
|
||||
final_url,
|
||||
false,
|
||||
self.scripting_enabled,
|
||||
);
|
||||
let stream =
|
||||
HtmlTreeSinkStream::from_target_with_scripting(target, self.scripting_enabled);
|
||||
for chunk in html_chunks(&html) {
|
||||
stream.feed(chunk);
|
||||
}
|
||||
@@ -476,10 +488,12 @@ impl HtmlParser {
|
||||
allow_declarative_shadow_roots: bool,
|
||||
scripting_enabled: bool,
|
||||
) -> NativeDom {
|
||||
let target = ParserStreamHtmlTreeSinkTarget::new_with_declarative_shadow_roots(
|
||||
final_url,
|
||||
allow_declarative_shadow_roots,
|
||||
);
|
||||
let target =
|
||||
ParserStreamHtmlTreeSinkTarget::new_with_declarative_shadow_roots_and_scripting(
|
||||
final_url,
|
||||
allow_declarative_shadow_roots,
|
||||
scripting_enabled,
|
||||
);
|
||||
let context = QualName::new(
|
||||
None,
|
||||
Namespace::from(context_namespace),
|
||||
@@ -2087,6 +2101,25 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn standalone_html_parser_preserves_the_requested_scripting_mode() {
|
||||
let document = HtmlParser::SCRIPTING_DISABLED.parse_without_declarative_shadow_roots(
|
||||
Url::parse("https://example.test/").expect("test url"),
|
||||
"<body><noscript>& <></noscript></body>".to_owned(),
|
||||
);
|
||||
assert_eq!(
|
||||
document
|
||||
.document()
|
||||
.map(|document| document.scripting_enabled()),
|
||||
Some(false)
|
||||
);
|
||||
let noscript = first_element_by_ns(&document, HTML_NS, "noscript");
|
||||
assert_eq!(
|
||||
document.inner_html(noscript).as_deref(),
|
||||
Some("& <>")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mathml_annotation_xml_text_html_is_html_integration_point() {
|
||||
let document = parse_test_document(concat!(
|
||||
|
||||
@@ -2032,15 +2032,15 @@ impl ParserStreamHtmlTreeSinkTarget {
|
||||
Some(move || sink.finish())
|
||||
}
|
||||
|
||||
fn new(final_url: Url) -> Self {
|
||||
Self::new_with_declarative_shadow_roots(final_url, true)
|
||||
}
|
||||
|
||||
pub(super) fn new_with_declarative_shadow_roots(
|
||||
pub(super) fn new_with_declarative_shadow_roots_and_scripting(
|
||||
final_url: Url,
|
||||
allow_declarative_shadow_roots: bool,
|
||||
scripting_enabled: bool,
|
||||
) -> Self {
|
||||
let dom_host = DomHost::from_dom(NativeDom::new(final_url.clone()));
|
||||
let dom_host = DomHost::from_dom(NativeDom::new_html_with_scripting(
|
||||
final_url.clone(),
|
||||
scripting_enabled,
|
||||
));
|
||||
let document_handle = dom_host.document_handle();
|
||||
Self {
|
||||
owned_dom_host: Some(dom_host),
|
||||
@@ -3557,8 +3557,13 @@ impl ParserStreamHtmlTreeSinkTarget {
|
||||
|
||||
pub(super) fn new_parser_stream_html_tree_sink_target(
|
||||
final_url: Url,
|
||||
scripting_enabled: bool,
|
||||
) -> ParserStreamHtmlTreeSinkTarget {
|
||||
ParserStreamHtmlTreeSinkTarget::new(final_url)
|
||||
ParserStreamHtmlTreeSinkTarget::new_with_declarative_shadow_roots_and_scripting(
|
||||
final_url,
|
||||
true,
|
||||
scripting_enabled,
|
||||
)
|
||||
}
|
||||
|
||||
pub(super) fn new_parser_stream_html_tree_sink_stream(
|
||||
@@ -3566,7 +3571,7 @@ pub(super) fn new_parser_stream_html_tree_sink_stream(
|
||||
scripting_enabled: bool,
|
||||
) -> HtmlTreeSinkStream {
|
||||
HtmlTreeSinkStream::from_target_with_scripting(
|
||||
new_parser_stream_html_tree_sink_target(final_url),
|
||||
new_parser_stream_html_tree_sink_target(final_url, scripting_enabled),
|
||||
scripting_enabled,
|
||||
)
|
||||
}
|
||||
@@ -4314,6 +4319,55 @@ fn parser_stream_live_fragment_root_writes_fragment() {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn live_fragment_parser_preserves_noscript_when_scripting_is_disabled() {
|
||||
let url = Url::parse("https://example.test/").expect("test url");
|
||||
let mut dom_host = DomHost::from_dom(NativeDom::new_html_with_scripting(url.clone(), false));
|
||||
let document = dom_host.document_handle();
|
||||
let html = dom_host.create_element("html");
|
||||
let body = dom_host.create_element("body");
|
||||
assert!(dom_host.append_child(document, html));
|
||||
assert!(dom_host.append_child(html, body));
|
||||
let existing_noscript = dom_host.create_element("noscript");
|
||||
let existing_text = dom_host.create_text_node("<em>existing&</em>");
|
||||
assert!(dom_host.append_child(existing_noscript, existing_text));
|
||||
assert!(dom_host.append_child(body, existing_noscript));
|
||||
let fragment = dom_host.create_document_fragment_for_document(document);
|
||||
let ptr = &mut dom_host as *mut DomHost;
|
||||
let mut effects = DomMutationEffects::default();
|
||||
|
||||
{
|
||||
let mut collector = TestMutationEffectCollector {
|
||||
host: ptr,
|
||||
effects: &mut effects,
|
||||
panic_on_mutation: false,
|
||||
};
|
||||
crate::HtmlParser::with_scripting_enabled(false).parse_fragment_into_live_dom(
|
||||
url,
|
||||
fragment,
|
||||
document,
|
||||
body,
|
||||
"http://www.w3.org/1999/xhtml",
|
||||
"body",
|
||||
"<noscript><em>fallback&</em></noscript><span>tail</span>",
|
||||
&mut collector,
|
||||
false,
|
||||
);
|
||||
}
|
||||
|
||||
assert_eq!(
|
||||
dom_host
|
||||
.elements_by_tag_name(fragment, "noscript", false)
|
||||
.len(),
|
||||
1,
|
||||
"a scripting-disabled live fragment must preserve its noscript root"
|
||||
);
|
||||
assert_eq!(
|
||||
dom_host.elements_by_tag_name(fragment, "span", false).len(),
|
||||
1
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn live_fragment_parser_uses_template_contents_owner_document() {
|
||||
let url = Url::parse("https://example.test/").expect("test url");
|
||||
|
||||
@@ -585,8 +585,13 @@ impl DocumentRuntime {
|
||||
self.dom_host.create_detached_xml_document()
|
||||
}
|
||||
|
||||
pub(crate) fn create_detached_html_document_with_url(&mut self, url: url::Url) -> DomHandle {
|
||||
self.dom_host.create_detached_html_document_with_url(url)
|
||||
pub(crate) fn create_detached_html_document_with_url_and_scripting(
|
||||
&mut self,
|
||||
url: url::Url,
|
||||
scripting_enabled: bool,
|
||||
) -> DomHandle {
|
||||
self.dom_host
|
||||
.create_detached_html_document_with_url_and_scripting(url, scripting_enabled)
|
||||
}
|
||||
|
||||
pub(crate) fn create_detached_xml_document_with_url(&mut self, url: url::Url) -> DomHandle {
|
||||
|
||||
@@ -772,6 +772,10 @@ impl JsContextHost {
|
||||
}
|
||||
self.child_browsing_context_host_for_document_handle(document_handle)
|
||||
.map(|handle| self.child_browsing_context_scripting_enabled(handle))
|
||||
.or_else(|| {
|
||||
self.dom_host()
|
||||
.document_scripting_enabled_for_handle(document_handle)
|
||||
})
|
||||
// DOMParser and document.implementation documents have no browsing
|
||||
// context, so scripting is disabled for their fragment parsers too.
|
||||
.unwrap_or(false)
|
||||
|
||||
@@ -60,14 +60,19 @@ fn create_native_detached_document_handle_with_url(
|
||||
scope: &mut v8::PinScope<'_, '_>,
|
||||
kind: &str,
|
||||
url: Url,
|
||||
scripting_enabled: bool,
|
||||
) -> Option<DomHandle> {
|
||||
let runtime_ptr = context_host_ptr_from_global_bridge(scope)?;
|
||||
let runtime = unsafe { &mut *runtime_ptr };
|
||||
Some(if kind == "html" {
|
||||
runtime.create_detached_html_document_with_url(url)
|
||||
let handle = if kind == "html" {
|
||||
runtime.create_detached_html_document_with_url_and_scripting(url, scripting_enabled)
|
||||
} else {
|
||||
runtime.create_detached_xml_document_with_url(url)
|
||||
})
|
||||
};
|
||||
let _ = runtime
|
||||
.dom_host_mut()
|
||||
.set_document_scripting_enabled_for_handle(handle, scripting_enabled);
|
||||
Some(handle)
|
||||
}
|
||||
|
||||
fn detached_document_url(parsed: &DomHost) -> Url {
|
||||
@@ -94,6 +99,7 @@ fn new_detached_document_shell<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
kind: &str,
|
||||
url: Url,
|
||||
scripting_enabled: bool,
|
||||
) -> Option<v8::Local<'s, v8::Object>> {
|
||||
let to_string_tag = if kind == "html" {
|
||||
Some("HTMLDocument")
|
||||
@@ -116,7 +122,9 @@ fn new_detached_document_shell<'s>(
|
||||
);
|
||||
set_detached_document_url_state(scope, state, &url)?;
|
||||
define_detached_state(scope, document, state);
|
||||
if let Some(handle) = create_native_detached_document_handle_with_url(scope, kind, url) {
|
||||
if let Some(handle) =
|
||||
create_native_detached_document_handle_with_url(scope, kind, url, scripting_enabled)
|
||||
{
|
||||
define_detached_native_handle(scope, document, handle);
|
||||
}
|
||||
install_detached_document_instance_properties(scope, document, kind);
|
||||
@@ -201,12 +209,13 @@ pub(crate) fn build_detached_document_object_from_dom_host_with_content_type<'s>
|
||||
) -> Option<v8::Local<'s, v8::Object>> {
|
||||
let url = detached_document_url(&parsed);
|
||||
let quirks_mode = parsed.dom().document()?.quirks_mode();
|
||||
let scripting_enabled = parsed.dom().document()?.scripting_enabled();
|
||||
let compat_mode = if quirks_mode == selectors::matching::QuirksMode::Quirks {
|
||||
"BackCompat"
|
||||
} else {
|
||||
"CSS1Compat"
|
||||
};
|
||||
let document = new_detached_document_shell(scope, kind, url)?;
|
||||
let document = new_detached_document_shell(scope, kind, url, scripting_enabled)?;
|
||||
if let Some(state) = detached_state_object(scope, document) {
|
||||
let _ = state.set(
|
||||
scope,
|
||||
@@ -328,6 +337,7 @@ pub(in crate::native_bridge::document) fn build_detached_html_document_object<'s
|
||||
scope,
|
||||
"html",
|
||||
Url::parse("about:blank").expect("static about:blank parses"),
|
||||
false,
|
||||
)?;
|
||||
populate_native_html_document_shell(scope, document, title)?;
|
||||
Some(document)
|
||||
@@ -347,6 +357,7 @@ pub(in crate::native_bridge::document) fn build_detached_document_object<'s>(
|
||||
scope,
|
||||
kind,
|
||||
Url::parse("about:blank").expect("static about:blank parses"),
|
||||
false,
|
||||
)?;
|
||||
if kind == "xml"
|
||||
&& let Some(namespace_uri) = namespace_uri.as_deref()
|
||||
|
||||
@@ -805,13 +805,15 @@ fn exec_command_insert_html_target(runtime: &JsContextHost) -> Option<DomHandle>
|
||||
}
|
||||
|
||||
fn input_text_from_html_fragment(runtime: &JsContextHost, value: &str) -> String {
|
||||
let parsed = HtmlParser::with_scripting_enabled(true)
|
||||
.parse_fragment_without_declarative_shadow_roots(
|
||||
runtime.host_document().url().clone(),
|
||||
"http://www.w3.org/1999/xhtml",
|
||||
"body",
|
||||
value.to_owned(),
|
||||
);
|
||||
let document_handle = runtime.dom_host().document_handle();
|
||||
let parsed =
|
||||
HtmlParser::with_scripting_enabled(runtime.document_scripting_enabled(document_handle))
|
||||
.parse_fragment_without_declarative_shadow_roots(
|
||||
runtime.host_document().url().clone(),
|
||||
"http://www.w3.org/1999/xhtml",
|
||||
"body",
|
||||
value.to_owned(),
|
||||
);
|
||||
let root = parsed
|
||||
.body_node_id()
|
||||
.unwrap_or_else(|| parsed.document_node_id());
|
||||
|
||||
@@ -298,6 +298,58 @@ fn domparser_html_preserves_quirks_mode_and_parses_with_scripting_disabled() {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn offline_html_documents_parse_and_serialize_noscript_with_scripting_disabled() {
|
||||
let mut vm = new_storage_test_vm("https://offline-noscript.test/");
|
||||
|
||||
let result = vm
|
||||
.eval(
|
||||
r#"
|
||||
(() => {
|
||||
const encoded = '& <>';
|
||||
const decoded = '&\u00a0<>';
|
||||
const markup = value => `<noscript>${value}</noscript>`;
|
||||
const matches = (node, serialization) =>
|
||||
node.textContent === decoded && node.innerHTML === serialization;
|
||||
|
||||
const live = document.createElement('div');
|
||||
live.innerHTML = markup(decoded);
|
||||
|
||||
const parsed = new DOMParser().parseFromString(
|
||||
`<body>${markup(encoded)}</body>`,
|
||||
'text/html'
|
||||
);
|
||||
|
||||
const template = document.createElement('template');
|
||||
template.innerHTML = markup(encoded);
|
||||
|
||||
const created = document.implementation.createHTMLDocument('');
|
||||
created.body.innerHTML = `<pre>${markup(encoded)}</pre>`;
|
||||
|
||||
const contextualDocument = document.implementation.createHTMLDocument('');
|
||||
const range = contextualDocument.createRange();
|
||||
range.selectNode(contextualDocument.body);
|
||||
const contextual = range.createContextualFragment(markup(encoded));
|
||||
|
||||
const written = document.implementation.createHTMLDocument('');
|
||||
written.write(`<div>${markup(decoded)}</div>`);
|
||||
|
||||
return [
|
||||
matches(live.firstChild, decoded),
|
||||
matches(parsed.body.firstChild, encoded),
|
||||
matches(template.content.firstChild, encoded),
|
||||
matches(created.body.firstChild.firstChild, encoded),
|
||||
matches(contextual.firstChild, encoded),
|
||||
matches(written.body.firstChild.firstChild, encoded)
|
||||
].join('|');
|
||||
})()
|
||||
"#,
|
||||
)
|
||||
.expect("offline noscript parsing and serialization probe should evaluate");
|
||||
|
||||
assert_eq!(result, "true|true|true|true|true|true");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn domparser_xml_preserves_requested_content_type_for_success_and_error_documents() {
|
||||
let mut vm = new_storage_test_vm("https://domparser-xml-content-type.test/");
|
||||
|
||||
Reference in New Issue
Block a user