Skip to content

Commit aec5537

Browse files
authored
fix(codegen): serialize template tags, closes #4410 (#5247)
1 parent 4c5a303 commit aec5537

3 files changed

Lines changed: 105 additions & 7 deletions

File tree

‎.changes/fix-html-template.md‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
"tauri-utils": patch
3+
"tauri-codegen": patch
4+
---
5+
6+
Properly serialize HTML template tags.

‎core/tauri-codegen/src/context.rs‎

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,9 @@ use sha2::{Digest, Sha256};
1111

1212
use tauri_utils::assets::AssetKey;
1313
use tauri_utils::config::{AppUrl, Config, PatternKind, WindowUrl};
14-
use tauri_utils::html::{inject_nonce_token, parse as parse_html};
14+
use tauri_utils::html::{
15+
inject_nonce_token, parse as parse_html, serialize_node as serialize_html_node,
16+
};
1517

1618
#[cfg(feature = "shell-scope")]
1719
use tauri_utils::config::{ShellAllowedArg, ShellAllowedArgs, ShellAllowlistScope};
@@ -37,10 +39,10 @@ fn map_core_assets(
3739
options.dangerous_disable_asset_csp_modification.clone();
3840
move |key, path, input, csp_hashes| {
3941
if path.extension() == Some(OsStr::new("html")) {
40-
let mut document = parse_html(String::from_utf8_lossy(input).into_owned());
41-
4242
#[allow(clippy::collapsible_if)]
4343
if csp {
44+
let mut document = parse_html(String::from_utf8_lossy(input).into_owned());
45+
4446
if target == Target::Linux {
4547
::tauri_utils::html::inject_csp_token(&mut document);
4648
}
@@ -77,9 +79,9 @@ fn map_core_assets(
7779
.push(format!("'sha256-{}'", base64::encode(&hash)));
7880
}
7981
}
80-
}
8182

82-
*input = document.to_string().as_bytes().to_vec();
83+
*input = serialize_html_node(&document);
84+
}
8385
}
8486
Ok(())
8587
}

‎core/tauri-utils/src/html.rs‎

Lines changed: 92 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,15 @@
66
77
use std::path::{Path, PathBuf};
88

9-
use html5ever::{interface::QualName, namespace_url, ns, tendril::TendrilSink, LocalName};
9+
use html5ever::{
10+
interface::QualName,
11+
namespace_url, ns,
12+
serialize::{HtmlSerializer, SerializeOpts, Serializer, TraversalScope},
13+
tendril::TendrilSink,
14+
LocalName,
15+
};
1016
pub use kuchiki::NodeRef;
11-
use kuchiki::{Attribute, ExpandedName};
17+
use kuchiki::{Attribute, ExpandedName, NodeData};
1218
use serde::Serialize;
1319
#[cfg(feature = "isolation")]
1420
use serialize_to_javascript::DefaultTemplate;
@@ -24,6 +30,90 @@ pub const SCRIPT_NONCE_TOKEN: &str = "__TAURI_SCRIPT_NONCE__";
2430
/// The token used for style nonces.
2531
pub const STYLE_NONCE_TOKEN: &str = "__TAURI_STYLE_NONCE__";
2632

33+
// taken from https://github.com/kuchiki-rs/kuchiki/blob/57ee6920d835315a498e748ba4b07a851ae5e498/src/serializer.rs#L12
34+
fn serialize_node_ref_internal<S: Serializer>(
35+
node: &NodeRef,
36+
serializer: &mut S,
37+
traversal_scope: TraversalScope,
38+
) -> crate::Result<()> {
39+
match (traversal_scope, node.data()) {
40+
(ref scope, &NodeData::Element(ref element)) => {
41+
if *scope == TraversalScope::IncludeNode {
42+
let attrs = element.attributes.borrow();
43+
44+
// Unfortunately we need to allocate something to hold these &'a QualName
45+
let attrs = attrs
46+
.map
47+
.iter()
48+
.map(|(name, attr)| {
49+
(
50+
QualName::new(attr.prefix.clone(), name.ns.clone(), name.local.clone()),
51+
&attr.value,
52+
)
53+
})
54+
.collect::<Vec<_>>();
55+
56+
serializer.start_elem(
57+
element.name.clone(),
58+
attrs.iter().map(|&(ref name, value)| (name, &**value)),
59+
)?
60+
}
61+
62+
let children = match element.template_contents.as_ref() {
63+
Some(template_root) => template_root.children(),
64+
None => node.children(),
65+
};
66+
for child in children {
67+
serialize_node_ref_internal(&child, serializer, TraversalScope::IncludeNode)?
68+
}
69+
70+
if *scope == TraversalScope::IncludeNode {
71+
serializer.end_elem(element.name.clone())?
72+
}
73+
Ok(())
74+
}
75+
76+
(_, &NodeData::DocumentFragment) | (_, &NodeData::Document(_)) => {
77+
for child in node.children() {
78+
serialize_node_ref_internal(&child, serializer, TraversalScope::IncludeNode)?
79+
}
80+
Ok(())
81+
}
82+
83+
(TraversalScope::ChildrenOnly(_), _) => Ok(()),
84+
85+
(TraversalScope::IncludeNode, &NodeData::Doctype(ref doctype)) => {
86+
serializer.write_doctype(&doctype.name).map_err(Into::into)
87+
}
88+
(TraversalScope::IncludeNode, &NodeData::Text(ref text)) => {
89+
serializer.write_text(&text.borrow()).map_err(Into::into)
90+
}
91+
(TraversalScope::IncludeNode, &NodeData::Comment(ref text)) => {
92+
serializer.write_comment(&text.borrow()).map_err(Into::into)
93+
}
94+
(TraversalScope::IncludeNode, &NodeData::ProcessingInstruction(ref contents)) => {
95+
let contents = contents.borrow();
96+
serializer
97+
.write_processing_instruction(&contents.0, &contents.1)
98+
.map_err(Into::into)
99+
}
100+
}
101+
}
102+
103+
/// Serializes the node to HTML.
104+
pub fn serialize_node(node: &NodeRef) -> Vec<u8> {
105+
let mut u8_vec = Vec::new();
106+
let mut ser = HtmlSerializer::new(
107+
&mut u8_vec,
108+
SerializeOpts {
109+
traversal_scope: TraversalScope::IncludeNode,
110+
..Default::default()
111+
},
112+
);
113+
serialize_node_ref_internal(node, &mut ser, TraversalScope::IncludeNode).unwrap();
114+
u8_vec
115+
}
116+
27117
/// Parses the given HTML string.
28118
pub fn parse(html: String) -> NodeRef {
29119
kuchiki::parse_html().one(html)

0 commit comments

Comments
 (0)