diff --git a/moli-dom/src/native/document.rs b/moli-dom/src/native/document.rs index fdd07cc316..e87af0fa1e 100644 --- a/moli-dom/src/native/document.rs +++ b/moli-dom/src/native/document.rs @@ -52,6 +52,7 @@ pub struct Document { ready_state: DocumentReadyState, quirks_mode: QuirksMode, kind: DocumentKind, + is_inert_template_document: bool, design_mode_enabled: bool, css_target: Option, default_language: Option>, @@ -73,6 +74,7 @@ impl Document { ready_state: DocumentReadyState::Complete, quirks_mode: QuirksMode::NoQuirks, kind: DocumentKind::Html, + is_inert_template_document: false, design_mode_enabled: false, css_target: None, default_language: None, @@ -89,6 +91,7 @@ impl Document { ready_state: DocumentReadyState::Complete, quirks_mode: QuirksMode::NoQuirks, kind: DocumentKind::Xml, + is_inert_template_document: false, design_mode_enabled: false, css_target: None, default_language: None, @@ -96,6 +99,15 @@ impl Document { } } + pub(super) fn new_inert_template(url: Url, kind: DocumentKind) -> Self { + let mut document = match kind { + DocumentKind::Html => Self::new_html(url), + DocumentKind::Xml => Self::new_xml(url), + }; + document.is_inert_template_document = true; + document + } + pub fn url(&self) -> &Url { &self.url } @@ -144,6 +156,10 @@ impl Document { self.kind == DocumentKind::Html } + pub fn is_inert_template_document(&self) -> bool { + self.is_inert_template_document + } + pub fn design_mode_enabled(&self) -> bool { self.design_mode_enabled } diff --git a/moli-dom/src/native/mod.rs b/moli-dom/src/native/mod.rs index 796f6bb40f..db16752e01 100644 --- a/moli-dom/src/native/mod.rs +++ b/moli-dom/src/native/mod.rs @@ -13,6 +13,7 @@ mod scripts; use std::sync::Arc; +use document::DocumentKind; pub use document::{ Document, DocumentFragment, DocumentReadyState, DocumentTitleSetterTarget, DocumentType, }; @@ -518,21 +519,17 @@ impl NativeDom { document_handle: NativeNodeId, host: Option, ) -> NativeNodeId { - let (url, is_html_document) = self + let (url, kind) = self .node(document_handle) .and_then(Node::as_document) - .map(|document| (document.url().clone(), document.is_html_document())) + .map(|document| (document.url().clone(), document.kind())) .unwrap_or_else(|| { ( url::Url::parse("about:blank").expect("about:blank is valid"), - true, + DocumentKind::Html, ) }); - let document = if is_html_document { - Document::new_html(url) - } else { - Document::new_xml(url) - }; + let document = Document::new_inert_template(url, kind); let owner_document = self.create_node(NodeData::Document(Box::new(document)), None, false, false); self.create_node( diff --git a/moli-renderer-v8/src/native_bridge/bindings.rs b/moli-renderer-v8/src/native_bridge/bindings.rs index af00e48558..c5ffe8af4a 100644 --- a/moli-renderer-v8/src/native_bridge/bindings.rs +++ b/moli-renderer-v8/src/native_bridge/bindings.rs @@ -80,7 +80,9 @@ fn prototype_name_for_handle(host_ptr: *mut JsContextHost, handle: &BridgeHandle .node(*node_handle) .map(|node| match node.data() { crate::dom::native::NodeData::Document(document) => { - if document.is_html_document() { + if document.is_inert_template_document() { + "Document" + } else if document.is_html_document() { "HTMLDocument" } else { "XMLDocument" diff --git a/moli-renderer-v8/src/script_vm/tests/dom_xhr/dom/detached_documents.rs b/moli-renderer-v8/src/script_vm/tests/dom_xhr/dom/detached_documents.rs index b5eb868a4f..a88917f69d 100644 --- a/moli-renderer-v8/src/script_vm/tests/dom_xhr/dom/detached_documents.rs +++ b/moli-renderer-v8/src/script_vm/tests/dom_xhr/dom/detached_documents.rs @@ -907,6 +907,62 @@ fn detached_html_template_contents_preserve_html_element_names() { ); } +#[test] +fn template_contents_use_the_document_interface_with_source_document_semantics() { + let mut vm = new_parsed_test_vm( + "https://template-content-interface.test/", + r#" + + + "#, + ); + + let result = vm + .eval( + r#"(() => { + const check = (ok, label) => { if (!ok) throw Error(label); }; + const child = document.querySelector('iframe').contentDocument; + child.open(); + child.write(''); + child.close(); + const documents = [ + ['main', document], + ['child', child], + ['created', document.implementation.createHTMLDocument('')], + ['parsed', new DOMParser().parseFromString('', 'text/html')] + ]; + const checkTemplate = (template, label, html) => { + const owner = template.content.ownerDocument; + check(Object.prototype.toString.call(owner) === '[object Document]', label + ' interface'); + check(owner.defaultView === null, label + ' inert'); + check(owner.createElement('DIV').localName === (html ? 'div' : 'DIV'), label + ' kind'); + }; + for (const [label, doc] of documents) { + check(Object.prototype.toString.call(doc) === '[object HTMLDocument]', label + ' normal interface'); + checkTemplate(doc.createElement('template'), label + ' created', true); + const host = doc.createElement('div'); + host.innerHTML = ''; + checkTemplate(host.firstElementChild, label + ' fragment parsed', true); + const parsed = doc.querySelector('#parsed'); + if (label === 'main' || label === 'child') { + check(parsed !== null, label + ' parsed fixture'); + checkTemplate(parsed, label + ' document parsed', true); + } + } + const xml = new DOMParser().parseFromString( + '', + 'application/xhtml+xml' + ); + check(Object.prototype.toString.call(xml) === '[object Document]', 'normal XML interface'); + checkTemplate(xml.documentElement, 'XML template', false); + return 'ok'; +})()"#, + ) + .expect("inert document interfaces should be independent of HTML/XML semantics"); + + assert_eq!(result, "ok"); +} + #[test] fn template_content_host_rejects_insertion_cycles_after_adoption() { let mut vm = new_storage_test_vm("https://template-content-host.test/");