Keep declarative shadow templates inert
This commit is contained in:
1 parent
4eecbe870e
commit
6d4ce11aa1
2 files changed
+113
No files matched your search
@@ -224,6 +224,21 @@ impl TreeSink for DomTree {
|
|||||||
self.set_quirks(mode == QuirksMode::Quirks);
|
self.set_quirks(mode == QuirksMode::Quirks);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn allow_declarative_shadow_roots(&self, _intended_parent: &NodeId) -> bool {
|
||||||
|
// html5ever defaults this hook to `true`, but its default
|
||||||
|
// `attach_declarative_shadow` implementation returns an error. In
|
||||||
|
// that combination the tree builder consumes
|
||||||
|
// `<template shadowrootmode=...>` without inserting a template and
|
||||||
|
// then appends its contents directly to the host. That leaks shadow
|
||||||
|
// styles and markup into the light DOM.
|
||||||
|
//
|
||||||
|
// Keep declarative shadow roots as ordinary inert templates until
|
||||||
|
// DomTree has a real shadow-root node, tree-scoped selectors, and a
|
||||||
|
// scoped stylesheet cascade. Returning false makes html5ever take its
|
||||||
|
// fully implemented ordinary-template path.
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
fn is_mathml_annotation_xml_integration_point(&self, target: &NodeId) -> bool {
|
fn is_mathml_annotation_xml_integration_point(&self, target: &NodeId) -> bool {
|
||||||
self.with_node(*target, |n| match &n.data {
|
self.with_node(*target, |n| match &n.data {
|
||||||
NodeData::Element { mathml_annotation_xml_integration_point, .. } => {
|
NodeData::Element { mathml_annotation_xml_integration_point, .. } => {
|
||||||
@@ -343,4 +358,79 @@ mod tests {
|
|||||||
.expect("template context preserves the row");
|
.expect("template context preserves the row");
|
||||||
assert_eq!(tree.text_content(row), "cell");
|
assert_eq!(tree.text_content(row), "cell");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn declarative_shadow_markup_remains_an_inert_template() {
|
||||||
|
let tree = parse_html(
|
||||||
|
r#"<x-card id="host">
|
||||||
|
<template id="shadow" shadowrootmode="open">
|
||||||
|
<style id="shadow-style">.button { width:100% }</style>
|
||||||
|
<span id="shadow-content">shadow</span>
|
||||||
|
</template>
|
||||||
|
<span id="light-content">light</span>
|
||||||
|
</x-card>"#,
|
||||||
|
);
|
||||||
|
|
||||||
|
let host = tree.get_element_by_id("host").unwrap();
|
||||||
|
let template = tree.get_element_by_id("shadow").unwrap();
|
||||||
|
let contents = tree
|
||||||
|
.template_contents(template)
|
||||||
|
.expect("parsed template has a contents document");
|
||||||
|
let style = tree.get_element_by_id("shadow-style").unwrap();
|
||||||
|
let shadow_content = tree.get_element_by_id("shadow-content").unwrap();
|
||||||
|
let light_content = tree.get_element_by_id("light-content").unwrap();
|
||||||
|
let element_children = |parent| {
|
||||||
|
tree.children(parent)
|
||||||
|
.into_iter()
|
||||||
|
.filter(|child| tree.get_node(*child).is_some_and(|node| node.is_element()))
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
};
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
element_children(host),
|
||||||
|
vec![template, light_content],
|
||||||
|
"shadow markup must not be spliced into the host's light children"
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
tree.children(template).is_empty(),
|
||||||
|
"template nodes keep their markup in the separate contents document"
|
||||||
|
);
|
||||||
|
assert_eq!(element_children(contents), vec![style, shadow_content]);
|
||||||
|
assert_eq!(tree.get_node(style).unwrap().parent, Some(contents));
|
||||||
|
assert_eq!(
|
||||||
|
tree.get_node(shadow_content).unwrap().parent,
|
||||||
|
Some(contents)
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
!tree.descendants(tree.document()).contains(&style),
|
||||||
|
"inert shadow styles must not enter the document tree"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn ordinary_template_parsing_is_unchanged() {
|
||||||
|
let tree = parse_html(
|
||||||
|
r#"<div id="host">
|
||||||
|
<template id="ordinary"><span id="inside">content</span></template>
|
||||||
|
<span id="outside">light</span>
|
||||||
|
</div>"#,
|
||||||
|
);
|
||||||
|
|
||||||
|
let host = tree.get_element_by_id("host").unwrap();
|
||||||
|
let template = tree.get_element_by_id("ordinary").unwrap();
|
||||||
|
let contents = tree.template_contents(template).unwrap();
|
||||||
|
let inside = tree.get_element_by_id("inside").unwrap();
|
||||||
|
let outside = tree.get_element_by_id("outside").unwrap();
|
||||||
|
let element_children = |parent| {
|
||||||
|
tree.children(parent)
|
||||||
|
.into_iter()
|
||||||
|
.filter(|child| tree.get_node(*child).is_some_and(|node| node.is_element()))
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
};
|
||||||
|
|
||||||
|
assert_eq!(element_children(host), vec![template, outside]);
|
||||||
|
assert!(tree.children(template).is_empty());
|
||||||
|
assert_eq!(element_children(contents), vec![inside]);
|
||||||
|
assert_eq!(tree.get_node(inside).unwrap().parent, Some(contents));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -6748,6 +6748,29 @@ mod tests {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn declarative_shadow_style_does_not_leak_into_document_cascade() {
|
||||||
|
let tree = parse_html(
|
||||||
|
r#"<style>
|
||||||
|
html, body { margin:0 }
|
||||||
|
.button { width:123px; height:20px }
|
||||||
|
</style>
|
||||||
|
<x-button>
|
||||||
|
<template shadowrootmode="open">
|
||||||
|
<style>.button { width:100% }</style>
|
||||||
|
<span class="button">shadow control</span>
|
||||||
|
</template>
|
||||||
|
</x-button>
|
||||||
|
<div id="light-button" class="button"></div>"#,
|
||||||
|
);
|
||||||
|
let laid = layout_dom(&tree, (1280.0, 720.0));
|
||||||
|
let button = tree.get_element_by_id("light-button").unwrap();
|
||||||
|
let rect = laid.rects[&button];
|
||||||
|
|
||||||
|
assert!((rect.width - 123.0).abs() < 0.1, "{rect:?}");
|
||||||
|
assert!((rect.height - 20.0).abs() < 0.1, "{rect:?}");
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn multicol_balances_atomic_blocks_like_chromium() {
|
fn multicol_balances_atomic_blocks_like_chromium() {
|
||||||
// Chromium 140 geometry for this reduction is a 630x300 container,
|
// Chromium 140 geometry for this reduction is a 630x300 container,
|
||||||
|
|||||||
Reference in new issue
Block a user