diff --git a/.changes/fix-html-template.md b/.changes/fix-html-template.md
new file mode 100644
index 000000000..203f98f9f
--- /dev/null
+++ b/.changes/fix-html-template.md
@@ -0,0 +1,6 @@
+---
+"tauri-utils": patch
+"tauri-codegen": patch
+---
+
+Properly serialize HTML template tags.
diff --git a/core/tauri-codegen/src/context.rs b/core/tauri-codegen/src/context.rs
index 00db7b754..ea330c3ca 100644
--- a/core/tauri-codegen/src/context.rs
+++ b/core/tauri-codegen/src/context.rs
@@ -11,7 +11,9 @@ use sha2::{Digest, Sha256};
use tauri_utils::assets::AssetKey;
use tauri_utils::config::{AppUrl, Config, PatternKind, WindowUrl};
-use tauri_utils::html::{inject_nonce_token, parse as parse_html};
+use tauri_utils::html::{
+ inject_nonce_token, parse as parse_html, serialize_node as serialize_html_node,
+};
#[cfg(feature = "shell-scope")]
use tauri_utils::config::{ShellAllowedArg, ShellAllowedArgs, ShellAllowlistScope};
@@ -37,10 +39,10 @@ fn map_core_assets(
options.dangerous_disable_asset_csp_modification.clone();
move |key, path, input, csp_hashes| {
if path.extension() == Some(OsStr::new("html")) {
- let mut document = parse_html(String::from_utf8_lossy(input).into_owned());
-
#[allow(clippy::collapsible_if)]
if csp {
+ let mut document = parse_html(String::from_utf8_lossy(input).into_owned());
+
if target == Target::Linux {
::tauri_utils::html::inject_csp_token(&mut document);
}
@@ -77,9 +79,9 @@ fn map_core_assets(
.push(format!("'sha256-{}'", base64::encode(&hash)));
}
}
- }
- *input = document.to_string().as_bytes().to_vec();
+ *input = serialize_html_node(&document);
+ }
}
Ok(())
}
diff --git a/core/tauri-utils/src/html.rs b/core/tauri-utils/src/html.rs
index 31985df9e..acd8e7f82 100644
--- a/core/tauri-utils/src/html.rs
+++ b/core/tauri-utils/src/html.rs
@@ -6,9 +6,15 @@
use std::path::{Path, PathBuf};
-use html5ever::{interface::QualName, namespace_url, ns, tendril::TendrilSink, LocalName};
+use html5ever::{
+ interface::QualName,
+ namespace_url, ns,
+ serialize::{HtmlSerializer, SerializeOpts, Serializer, TraversalScope},
+ tendril::TendrilSink,
+ LocalName,
+};
pub use kuchiki::NodeRef;
-use kuchiki::{Attribute, ExpandedName};
+use kuchiki::{Attribute, ExpandedName, NodeData};
use serde::Serialize;
#[cfg(feature = "isolation")]
use serialize_to_javascript::DefaultTemplate;
@@ -24,6 +30,90 @@ pub const SCRIPT_NONCE_TOKEN: &str = "__TAURI_SCRIPT_NONCE__";
/// The token used for style nonces.
pub const STYLE_NONCE_TOKEN: &str = "__TAURI_STYLE_NONCE__";
+// taken from https://github.com/kuchiki-rs/kuchiki/blob/57ee6920d835315a498e748ba4b07a851ae5e498/src/serializer.rs#L12
+fn serialize_node_ref_internal(
+ node: &NodeRef,
+ serializer: &mut S,
+ traversal_scope: TraversalScope,
+) -> crate::Result<()> {
+ match (traversal_scope, node.data()) {
+ (ref scope, &NodeData::Element(ref element)) => {
+ if *scope == TraversalScope::IncludeNode {
+ let attrs = element.attributes.borrow();
+
+ // Unfortunately we need to allocate something to hold these &'a QualName
+ let attrs = attrs
+ .map
+ .iter()
+ .map(|(name, attr)| {
+ (
+ QualName::new(attr.prefix.clone(), name.ns.clone(), name.local.clone()),
+ &attr.value,
+ )
+ })
+ .collect::>();
+
+ serializer.start_elem(
+ element.name.clone(),
+ attrs.iter().map(|&(ref name, value)| (name, &**value)),
+ )?
+ }
+
+ let children = match element.template_contents.as_ref() {
+ Some(template_root) => template_root.children(),
+ None => node.children(),
+ };
+ for child in children {
+ serialize_node_ref_internal(&child, serializer, TraversalScope::IncludeNode)?
+ }
+
+ if *scope == TraversalScope::IncludeNode {
+ serializer.end_elem(element.name.clone())?
+ }
+ Ok(())
+ }
+
+ (_, &NodeData::DocumentFragment) | (_, &NodeData::Document(_)) => {
+ for child in node.children() {
+ serialize_node_ref_internal(&child, serializer, TraversalScope::IncludeNode)?
+ }
+ Ok(())
+ }
+
+ (TraversalScope::ChildrenOnly(_), _) => Ok(()),
+
+ (TraversalScope::IncludeNode, &NodeData::Doctype(ref doctype)) => {
+ serializer.write_doctype(&doctype.name).map_err(Into::into)
+ }
+ (TraversalScope::IncludeNode, &NodeData::Text(ref text)) => {
+ serializer.write_text(&text.borrow()).map_err(Into::into)
+ }
+ (TraversalScope::IncludeNode, &NodeData::Comment(ref text)) => {
+ serializer.write_comment(&text.borrow()).map_err(Into::into)
+ }
+ (TraversalScope::IncludeNode, &NodeData::ProcessingInstruction(ref contents)) => {
+ let contents = contents.borrow();
+ serializer
+ .write_processing_instruction(&contents.0, &contents.1)
+ .map_err(Into::into)
+ }
+ }
+}
+
+/// Serializes the node to HTML.
+pub fn serialize_node(node: &NodeRef) -> Vec {
+ let mut u8_vec = Vec::new();
+ let mut ser = HtmlSerializer::new(
+ &mut u8_vec,
+ SerializeOpts {
+ traversal_scope: TraversalScope::IncludeNode,
+ ..Default::default()
+ },
+ );
+ serialize_node_ref_internal(node, &mut ser, TraversalScope::IncludeNode).unwrap();
+ u8_vec
+}
+
/// Parses the given HTML string.
pub fn parse(html: String) -> NodeRef {
kuchiki::parse_html().one(html)