Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
6033a74
test: add more unit-tests for `html` module
thomaseizinger Feb 17, 2026
65ffbe2
refactor: remove html dependencies from `tauri-cli`
thomaseizinger Feb 17, 2026
ff39ab7
feat: introduce `html-manipulation-2` feature
thomaseizinger Feb 23, 2026
d38e3a4
Remove deprecation
thomaseizinger Feb 23, 2026
0a16744
Use new feature flag
thomaseizinger Feb 23, 2026
01d3b1e
Unroll `build` feature
thomaseizinger Feb 23, 2026
b34aa50
Introduce `build-2` feature
thomaseizinger Feb 24, 2026
36790ad
Reduce diff
thomaseizinger Feb 24, 2026
8de5238
Use `build-2` in more places
thomaseizinger Feb 24, 2026
25499ab
Add docs
thomaseizinger Mar 1, 2026
2f12590
Refactor `inject_script_hashes`
thomaseizinger Mar 1, 2026
3cfdf39
Refactor `with_head`
thomaseizinger Mar 1, 2026
395048c
Rename serialize and parse functions
thomaseizinger Mar 1, 2026
b7ebae8
Add changes file
thomaseizinger Mar 1, 2026
f86f874
Remove unused function
thomaseizinger Mar 10, 2026
6f2ce5c
Update changelog
thomaseizinger Mar 10, 2026
445394a
Remove test
thomaseizinger Mar 10, 2026
c25c767
Update wry
Legend-Master Mar 14, 2026
9412b16
Merge branch 'dev' into chore/replace-kuchikiki-dom_query
Legend-Master Mar 14, 2026
12403c4
Merge branch 'chore/replace-kuchikiki-dom_query' of https://github.co…
Legend-Master Mar 14, 2026
abd555a
Add todo comments
Legend-Master Mar 14, 2026
d512732
refactor `with_head` to `ensure_head`
Legend-Master Mar 14, 2026
999de1e
Remove unused casts
Legend-Master Mar 14, 2026
16d4808
Avoid using format to construct html elements
Legend-Master Mar 14, 2026
55c56cb
Feature gate `inline_isolation`
Legend-Master Mar 14, 2026
f49131c
Keep old prepends appends
Legend-Master Mar 14, 2026
70ad666
Fix `inline_isolation_replaces_src_with_content` test
Legend-Master Mar 14, 2026
7c96a7f
End meta tag
Legend-Master Mar 14, 2026
a7742bf
Mirror test to old html module
Legend-Master Mar 14, 2026
8bd11a8
Use back to `append_html` for csp and link issue
Legend-Master Mar 14, 2026
80ee230
Try out dom query main branch
Legend-Master Mar 15, 2026
3190747
Use nodes instead to avoid an extra clone
Legend-Master Mar 17, 2026
6f1f9ca
Use wry 0.54.4 and dom_query 0.27
Legend-Master Mar 17, 2026
2ec1a3d
Merge branch 'dev' into chore/replace-kuchikiki-dom_query
Legend-Master Mar 17, 2026
456a429
Merge branch 'dev' into chore/replace-kuchikiki-dom_query
Legend-Master Mar 25, 2026
19afa09
Mark stability
Legend-Master Mar 25, 2026
91888fb
Remove `PatternObject`
Legend-Master Mar 25, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changes/supersede-kuchikiki.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"tauri-utils": minor:deps
---

Add new `html-manipulation-2` and `build-2` feature flags that use `dom_query` instead of `kuchikiki` for HTML parsing / manipulation.
This allows downstream users to remove `kuchikiki` and its dependencies from their dependency tree.
24 changes: 12 additions & 12 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion crates/tauri-build/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ anyhow = "1"
quote = { version = "1", optional = true }
tauri-codegen = { version = "2.5.5", path = "../tauri-codegen", optional = true }
tauri-utils = { version = "2.8.3", path = "../tauri-utils", features = [
"build",
"build-2",
"resources",
] }
cargo_toml = "0.22"
Expand Down
4 changes: 1 addition & 3 deletions crates/tauri-cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ tauri-utils = { version = "2.8.3", path = "../tauri-utils", features = [
"schema",
"config-json5",
"config-toml",
"html-manipulation",
"html-manipulation-2",
] }
toml = "1"
jsonschema = { version = "0.33", default-features = false }
Expand All @@ -89,8 +89,6 @@ env_logger = "0.11"
icns = { package = "tauri-icns", version = "0.1" }
image = { version = "0.25", default-features = false, features = ["ico"] }
axum = { version = "0.8", features = ["ws"] }
html5ever = "0.29"
kuchiki = { package = "kuchikiki", version = "=0.8.8-speedreader" }
tokio = { version = "1", features = ["macros", "sync"] }
common-path = "1"
serde-value = "0.7"
Expand Down
30 changes: 6 additions & 24 deletions crates/tauri-cli/src/dev/builtin_dev_server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,6 @@ use axum::{
http::{header, StatusCode, Uri},
response::{IntoResponse, Response},
};
use html5ever::{namespace_url, ns, LocalName, QualName};
use kuchiki::{traits::TendrilSink, NodeRef};
use std::{
net::{IpAddr, SocketAddr},
path::{Path, PathBuf},
Expand Down Expand Up @@ -128,30 +126,14 @@ async fn ws_handler(ws: WebSocketUpgrade, state: State<ServerState>) -> Response
}

fn inject_address(html_bytes: Vec<u8>, address: &SocketAddr) -> Vec<u8> {
fn with_html_head<F: FnOnce(&NodeRef)>(document: &mut NodeRef, f: F) {
if let Ok(ref node) = document.select_first("head") {
f(node.as_node())
} else {
let node = NodeRef::new_element(
QualName::new(None, ns!(html), LocalName::from("head")),
None,
);
f(&node);
document.prepend(node)
}
}
let document = tauri_utils::html2::parse_doc(String::from_utf8_lossy(&html_bytes).into_owned());

let mut document = kuchiki::parse_html()
.one(String::from_utf8_lossy(&html_bytes).into_owned())
.document_node;
with_html_head(&mut document, |head| {
let script = RELOAD_SCRIPT.replace("{{reload_url}}", &format!("ws://{address}/__tauri_cli"));
let script_el = NodeRef::new_element(QualName::new(None, ns!(html), "script".into()), None);
script_el.append(NodeRef::new_text(script));
head.prepend(script_el);
});
tauri_utils::html2::append_script_to_head(
&document,
&RELOAD_SCRIPT.replace("{{reload_url}}", &format!("ws://{address}/__tauri_cli")),
);

tauri_utils::html::serialize_node(&document)
tauri_utils::html2::serialize_doc(&document)
}

fn fs_read_scoped(path: PathBuf, scope: &Path) -> crate::Result<Vec<u8>> {
Expand Down
2 changes: 1 addition & 1 deletion crates/tauri-codegen/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ syn = "2"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
tauri-utils = { version = "2.8.3", path = "../tauri-utils", features = [
"build",
"build-2",
] }
thiserror = "2"
walkdir = "2"
Expand Down
54 changes: 26 additions & 28 deletions crates/tauri-codegen/src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ use tauri_utils::{
},
assets::AssetKey,
config::{Config, FrontendDist, PatternKind},
html::{inject_nonce_token, parse as parse_html, serialize_node as serialize_html_node, NodeRef},
html2::{inject_nonce_token, parse_doc, serialize_doc, Document},
platform::Target,
tokens::{map_lit, str_lit},
};
Expand All @@ -44,27 +44,25 @@ pub struct ContextData {
pub test: bool,
}

fn inject_script_hashes(document: &NodeRef, key: &AssetKey, csp_hashes: &mut CspHashes) {
if let Ok(inline_script_elements) = document.select("script:not(:empty)") {
let mut scripts = Vec::new();
for inline_script_el in inline_script_elements {
let script = inline_script_el.as_node().text_contents();
let mut hasher = Sha256::new();
hasher.update(tauri_utils::html::normalize_script_for_csp(
script.as_bytes(),
));
let hash = hasher.finalize();
scripts.push(format!(
"'sha256-{}'",
base64::engine::general_purpose::STANDARD.encode(hash)
));
}
csp_hashes
.inline_scripts
.entry(key.clone().into())
.or_default()
.append(&mut scripts);
}
fn inject_script_hashes(document: &Document, key: &AssetKey, csp_hashes: &mut CspHashes) {
let script_elements = document.select("script:not(:empty)");

let scripts = script_elements
.iter()
.map(|element| {
let script = tauri_utils::html2::normalize_script_for_csp(element.text().as_bytes());
let script_hash = Sha256::digest(script);
let hash_base64 = base64::engine::general_purpose::STANDARD.encode(script_hash);

format!("'sha256-{hash_base64}'")
})
.collect::<Vec<_>>();

csp_hashes
.inline_scripts
.entry(key.clone().into())
.or_default()
.extend(scripts);
}

fn map_core_assets(
Expand All @@ -77,15 +75,15 @@ fn map_core_assets(
if path.extension() == Some(OsStr::new("html")) {
#[allow(clippy::collapsible_if)]
if csp {
let document = parse_html(String::from_utf8_lossy(input).into_owned());
let document = parse_doc(String::from_utf8_lossy(input).into_owned());

inject_nonce_token(&document, &dangerous_disable_asset_csp_modification);

if dangerous_disable_asset_csp_modification.can_modify("script-src") {
inject_script_hashes(&document, key, csp_hashes);
}

*input = serialize_html_node(&document);
*input = serialize_doc(&document);
}
}
Ok(())
Expand All @@ -108,13 +106,13 @@ fn map_isolation(

move |key, path, input, csp_hashes| {
if path.extension() == Some(OsStr::new("html")) {
let isolation_html = parse_html(String::from_utf8_lossy(input).into_owned());
let isolation_html = parse_doc(String::from_utf8_lossy(input).into_owned());

// this is appended, so no need to reverse order it
tauri_utils::html::inject_codegen_isolation_script(&isolation_html);
tauri_utils::html2::inject_codegen_isolation_script(&isolation_html);

// temporary workaround for windows not loading assets
tauri_utils::html::inline_isolation(&isolation_html, &dir);
tauri_utils::html2::inline_isolation(&isolation_html, &dir);

inject_nonce_token(
&isolation_html,
Expand All @@ -125,7 +123,7 @@ fn map_isolation(

csp_hashes.styles.push(iframe_style_csp_hash.clone());

*input = isolation_html.to_string().as_bytes().to_vec()
*input = serialize_doc(&isolation_html)
}

Ok(())
Expand Down
2 changes: 1 addition & 1 deletion crates/tauri-codegen/src/embedded_assets.rs
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,7 @@ impl CspHashes {
let mut hasher = Sha256::new();
hasher.update(
&std::fs::read(path)
.map(|b| tauri_utils::html::normalize_script_for_csp(&b))
.map(|b| tauri_utils::html2::normalize_script_for_csp(&b))
.map_err(|error| EmbeddedAssetsError::AssetRead {
path: path.to_path_buf(),
error,
Expand Down
2 changes: 1 addition & 1 deletion crates/tauri-plugin/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ runtime = []
anyhow = { version = "1", optional = true }
serde = { version = "1", optional = true }
tauri-utils = { version = "2.8.3", default-features = false, features = [
"build",
"build-2",
], path = "../tauri-utils" }
serde_json = { version = "1", optional = true }
glob = { version = "0.3", optional = true }
Expand Down
12 changes: 12 additions & 0 deletions crates/tauri-utils/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ brotli = { version = "8", optional = true, default-features = false, features =
url = { version = "2", features = ["serde"] }
html5ever = { version = "0.29", optional = true }
kuchiki = { package = "kuchikiki", version = "0.8.8-speedreader", optional = true }
dom_query = { version = "0.27", optional = true, default-features = false }
proc-macro2 = { version = "1", optional = true }
quote = { version = "1", optional = true }
# Our code requires at least 0.8.21 so don't change this to 0.8
Expand Down Expand Up @@ -59,6 +60,7 @@ swift-rs = { version = "1", optional = true, features = ["build"] }
getrandom = { version = "0.3", features = ["std"] }
serial_test = "3"
tauri = { path = "../tauri" }
tempfile = "3.15.0"

[features]
build = [
Expand All @@ -69,6 +71,15 @@ build = [
"swift-rs",
"html-manipulation",
]
# Same as `build` but uses `html-manipulation-2` to avoid the `kuchikiki` dependency.
build-2 = [
Comment thread
Legend-Master marked this conversation as resolved.
"proc-macro2",
"quote",
"cargo_metadata",
"schema",
"swift-rs",
"html-manipulation-2",
]
compression = ["brotli"]
schema = ["schemars"]
isolation = ["aes-gcm", "getrandom", "serialize-to-javascript"]
Expand All @@ -77,3 +88,4 @@ config-json5 = ["json5"]
config-toml = []
resources = ["walkdir"]
html-manipulation = ["dep:html5ever", "dep:kuchiki"]
html-manipulation-2 = ["dep:dom_query"]
2 changes: 1 addition & 1 deletion crates/tauri-utils/src/acl/capability.rs
Original file line number Diff line number Diff line change
Expand Up @@ -322,7 +322,7 @@ impl FromStr for CapabilityFile {
}
}

#[cfg(feature = "build")]
#[cfg(any(feature = "build", feature = "build-2"))]
mod build {
use std::convert::identity;

Expand Down
2 changes: 1 addition & 1 deletion crates/tauri-utils/src/acl/identifier.rs
Original file line number Diff line number Diff line change
Expand Up @@ -283,7 +283,7 @@ mod tests {
}
}

#[cfg(feature = "build")]
#[cfg(any(feature = "build", feature = "build-2"))]
mod build {
use proc_macro2::TokenStream;
use quote::{quote, ToTokens, TokenStreamExt};
Expand Down
2 changes: 1 addition & 1 deletion crates/tauri-utils/src/acl/manifest.rs
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ impl Manifest {
}
}

#[cfg(feature = "build")]
#[cfg(any(feature = "build", feature = "build-2"))]
mod build {
use proc_macro2::TokenStream;
use quote::{quote, ToTokens, TokenStreamExt};
Expand Down
Loading
Loading