mirror of
https://github.com/lexmount/moli.git
synced 2026-11-13 08:08:10 +00:00
fix(dom): expose inert template documents through Document
Track the inert template-document role separately from HTML/XML semantics. Choose the Document wrapper for that role while preserving the source kind for HTML name normalization and XML element creation. Cover parser/createElement/innerHTML paths in main and child documents, DOMParser and createHTMLDocument sources, and XHTML contents. Ordinary Document interfaces retain their existing identity. Validation: cargo fmt --all; strict workspace/all-targets/all-features Clippy; 86 template-related tests across three stress iterations; full Nextest 21,167 passed, 19 skipped, without retries.
This commit is contained in:
@@ -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<NativeNodeId>,
|
||||
default_language: Option<Box<str>>,
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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>,
|
||||
) -> 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(
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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#"<!doctype html><body>
|
||||
<template id="parsed"><div>main</div></template>
|
||||
<iframe></iframe>
|
||||
</body>"#,
|
||||
);
|
||||
|
||||
let result = vm
|
||||
.eval(
|
||||
r#"(() => {
|
||||
const check = (ok, label) => { if (!ok) throw Error(label); };
|
||||
const child = document.querySelector('iframe').contentDocument;
|
||||
child.open();
|
||||
child.write('<!doctype html><body><template id="parsed"><div>child</div></template>');
|
||||
child.close();
|
||||
const documents = [
|
||||
['main', document],
|
||||
['child', child],
|
||||
['created', document.implementation.createHTMLDocument('')],
|
||||
['parsed', new DOMParser().parseFromString('<body></body>', '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 = '<template><div>content</div></template>';
|
||||
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(
|
||||
'<template xmlns="http://www.w3.org/1999/xhtml"><title>xml</title></template>',
|
||||
'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/");
|
||||
|
||||
Reference in New Issue
Block a user