Skip to content

Commit e032c3b

Browse files
refactor: replace kuchikiki with dom_query (#14959)
* test: add more unit-tests for `html` module * refactor: remove html dependencies from `tauri-cli` * feat: introduce `html-manipulation-2` feature * Remove deprecation * Use new feature flag * Unroll `build` feature * Introduce `build-2` feature * Reduce diff * Use `build-2` in more places * Add docs * Refactor `inject_script_hashes` * Refactor `with_head` * Rename serialize and parse functions * Add changes file * Remove unused function * Update changelog * Remove test * Update wry * Add todo comments we don't have the git blame data in html2, better do it now or never find it again * refactor `with_head` to `ensure_head` * Remove unused casts * Avoid using format to construct html elements which has the potential to get injected * Feature gate `inline_isolation` * Keep old prepends appends * Fix `inline_isolation_replaces_src_with_content` test * End meta tag * Mirror test to old html module * Use back to `append_html` for csp and link issue * Try out dom query main branch * Use nodes instead to avoid an extra clone * Use wry 0.54.4 and dom_query 0.27 * Mark stability * Remove `PatternObject` --------- Co-authored-by: Tony <legendmastertony@gmail.com> Co-authored-by: Tony <68118705+Legend-Master@users.noreply.github.com>
1 parent 386312c commit e032c3b

25 files changed

Lines changed: 526 additions & 98 deletions

File tree

‎.changes/supersede-kuchikiki.md‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
"tauri-utils": minor:deps
3+
---
4+
5+
Add new `html-manipulation-2` and `build-2` feature flags that use `dom_query` instead of `kuchikiki` for HTML parsing / manipulation.
6+
This allows downstream users to remove `kuchikiki` and its dependencies from their dependency tree.

‎Cargo.lock‎

Lines changed: 12 additions & 12 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎crates/tauri-build/Cargo.toml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ anyhow = "1"
2828
quote = { version = "1", optional = true }
2929
tauri-codegen = { version = "2.5.5", path = "../tauri-codegen", optional = true }
3030
tauri-utils = { version = "2.8.3", path = "../tauri-utils", features = [
31-
"build",
31+
"build-2",
3232
"resources",
3333
] }
3434
cargo_toml = "0.22"

‎crates/tauri-cli/Cargo.toml‎

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ tauri-utils = { version = "2.8.3", path = "../tauri-utils", features = [
6363
"schema",
6464
"config-json5",
6565
"config-toml",
66-
"html-manipulation",
66+
"html-manipulation-2",
6767
] }
6868
toml = "1"
6969
jsonschema = { version = "0.33", default-features = false }
@@ -89,8 +89,6 @@ env_logger = "0.11"
8989
icns = { package = "tauri-icns", version = "0.1" }
9090
image = { version = "0.25", default-features = false, features = ["ico"] }
9191
axum = { version = "0.8", features = ["ws"] }
92-
html5ever = "0.29"
93-
kuchiki = { package = "kuchikiki", version = "=0.8.8-speedreader" }
9492
tokio = { version = "1", features = ["macros", "sync"] }
9593
common-path = "1"
9694
serde-value = "0.7"

‎crates/tauri-cli/src/dev/builtin_dev_server.rs‎

Lines changed: 6 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,6 @@ use axum::{
77
http::{header, StatusCode, Uri},
88
response::{IntoResponse, Response},
99
};
10-
use html5ever::{namespace_url, ns, LocalName, QualName};
11-
use kuchiki::{traits::TendrilSink, NodeRef};
1210
use std::{
1311
net::{IpAddr, SocketAddr},
1412
path::{Path, PathBuf},
@@ -128,30 +126,14 @@ async fn ws_handler(ws: WebSocketUpgrade, state: State<ServerState>) -> Response
128126
}
129127

130128
fn inject_address(html_bytes: Vec<u8>, address: &SocketAddr) -> Vec<u8> {
131-
fn with_html_head<F: FnOnce(&NodeRef)>(document: &mut NodeRef, f: F) {
132-
if let Ok(ref node) = document.select_first("head") {
133-
f(node.as_node())
134-
} else {
135-
let node = NodeRef::new_element(
136-
QualName::new(None, ns!(html), LocalName::from("head")),
137-
None,
138-
);
139-
f(&node);
140-
document.prepend(node)
141-
}
142-
}
129+
let document = tauri_utils::html2::parse_doc(String::from_utf8_lossy(&html_bytes).into_owned());
143130

144-
let mut document = kuchiki::parse_html()
145-
.one(String::from_utf8_lossy(&html_bytes).into_owned())
146-
.document_node;
147-
with_html_head(&mut document, |head| {
148-
let script = RELOAD_SCRIPT.replace("{{reload_url}}", &format!("ws://{address}/__tauri_cli"));
149-
let script_el = NodeRef::new_element(QualName::new(None, ns!(html), "script".into()), None);
150-
script_el.append(NodeRef::new_text(script));
151-
head.prepend(script_el);
152-
});
131+
tauri_utils::html2::append_script_to_head(
132+
&document,
133+
&RELOAD_SCRIPT.replace("{{reload_url}}", &format!("ws://{address}/__tauri_cli")),
134+
);
153135

154-
tauri_utils::html::serialize_node(&document)
136+
tauri_utils::html2::serialize_doc(&document)
155137
}
156138

157139
fn fs_read_scoped(path: PathBuf, scope: &Path) -> crate::Result<Vec<u8>> {

‎crates/tauri-codegen/Cargo.toml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ syn = "2"
2121
serde = { version = "1", features = ["derive"] }
2222
serde_json = "1"
2323
tauri-utils = { version = "2.8.3", path = "../tauri-utils", features = [
24-
"build",
24+
"build-2",
2525
] }
2626
thiserror = "2"
2727
walkdir = "2"

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

Lines changed: 26 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ use tauri_utils::{
2525
},
2626
assets::AssetKey,
2727
config::{Config, FrontendDist, PatternKind},
28-
html::{inject_nonce_token, parse as parse_html, serialize_node as serialize_html_node, NodeRef},
28+
html2::{inject_nonce_token, parse_doc, serialize_doc, Document},
2929
platform::Target,
3030
tokens::{map_lit, str_lit},
3131
};
@@ -44,27 +44,25 @@ pub struct ContextData {
4444
pub test: bool,
4545
}
4646

47-
fn inject_script_hashes(document: &NodeRef, key: &AssetKey, csp_hashes: &mut CspHashes) {
48-
if let Ok(inline_script_elements) = document.select("script:not(:empty)") {
49-
let mut scripts = Vec::new();
50-
for inline_script_el in inline_script_elements {
51-
let script = inline_script_el.as_node().text_contents();
52-
let mut hasher = Sha256::new();
53-
hasher.update(tauri_utils::html::normalize_script_for_csp(
54-
script.as_bytes(),
55-
));
56-
let hash = hasher.finalize();
57-
scripts.push(format!(
58-
"'sha256-{}'",
59-
base64::engine::general_purpose::STANDARD.encode(hash)
60-
));
61-
}
62-
csp_hashes
63-
.inline_scripts
64-
.entry(key.clone().into())
65-
.or_default()
66-
.append(&mut scripts);
67-
}
47+
fn inject_script_hashes(document: &Document, key: &AssetKey, csp_hashes: &mut CspHashes) {
48+
let script_elements = document.select("script:not(:empty)");
49+
50+
let scripts = script_elements
51+
.iter()
52+
.map(|element| {
53+
let script = tauri_utils::html2::normalize_script_for_csp(element.text().as_bytes());
54+
let script_hash = Sha256::digest(script);
55+
let hash_base64 = base64::engine::general_purpose::STANDARD.encode(script_hash);
56+
57+
format!("'sha256-{hash_base64}'")
58+
})
59+
.collect::<Vec<_>>();
60+
61+
csp_hashes
62+
.inline_scripts
63+
.entry(key.clone().into())
64+
.or_default()
65+
.extend(scripts);
6866
}
6967

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

8280
inject_nonce_token(&document, &dangerous_disable_asset_csp_modification);
8381

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

88-
*input = serialize_html_node(&document);
86+
*input = serialize_doc(&document);
8987
}
9088
}
9189
Ok(())
@@ -108,13 +106,13 @@ fn map_isolation(
108106

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

113111
// this is appended, so no need to reverse order it
114-
tauri_utils::html::inject_codegen_isolation_script(&isolation_html);
112+
tauri_utils::html2::inject_codegen_isolation_script(&isolation_html);
115113

116114
// temporary workaround for windows not loading assets
117-
tauri_utils::html::inline_isolation(&isolation_html, &dir);
115+
tauri_utils::html2::inline_isolation(&isolation_html, &dir);
118116

119117
inject_nonce_token(
120118
&isolation_html,
@@ -125,7 +123,7 @@ fn map_isolation(
125123

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

128-
*input = isolation_html.to_string().as_bytes().to_vec()
126+
*input = serialize_doc(&isolation_html)
129127
}
130128

131129
Ok(())

‎crates/tauri-codegen/src/embedded_assets.rs‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,7 @@ impl CspHashes {
181181
let mut hasher = Sha256::new();
182182
hasher.update(
183183
&std::fs::read(path)
184-
.map(|b| tauri_utils::html::normalize_script_for_csp(&b))
184+
.map(|b| tauri_utils::html2::normalize_script_for_csp(&b))
185185
.map_err(|error| EmbeddedAssetsError::AssetRead {
186186
path: path.to_path_buf(),
187187
error,

‎crates/tauri-plugin/Cargo.toml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ runtime = []
2828
anyhow = { version = "1", optional = true }
2929
serde = { version = "1", optional = true }
3030
tauri-utils = { version = "2.8.3", default-features = false, features = [
31-
"build",
31+
"build-2",
3232
], path = "../tauri-utils" }
3333
serde_json = { version = "1", optional = true }
3434
glob = { version = "0.3", optional = true }

‎crates/tauri-utils/Cargo.toml‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ brotli = { version = "8", optional = true, default-features = false, features =
2424
url = { version = "2", features = ["serde"] }
2525
html5ever = { version = "0.29", optional = true }
2626
kuchiki = { package = "kuchikiki", version = "0.8.8-speedreader", optional = true }
27+
dom_query = { version = "0.27", optional = true, default-features = false }
2728
proc-macro2 = { version = "1", optional = true }
2829
quote = { version = "1", optional = true }
2930
# Our code requires at least 0.8.21 so don't change this to 0.8
@@ -59,6 +60,7 @@ swift-rs = { version = "1", optional = true, features = ["build"] }
5960
getrandom = { version = "0.3", features = ["std"] }
6061
serial_test = "3"
6162
tauri = { path = "../tauri" }
63+
tempfile = "3.15.0"
6264

6365
[features]
6466
build = [
@@ -69,6 +71,15 @@ build = [
6971
"swift-rs",
7072
"html-manipulation",
7173
]
74+
# Same as `build` but uses `html-manipulation-2` to avoid the `kuchikiki` dependency.
75+
build-2 = [
76+
"proc-macro2",
77+
"quote",
78+
"cargo_metadata",
79+
"schema",
80+
"swift-rs",
81+
"html-manipulation-2",
82+
]
7283
compression = ["brotli"]
7384
schema = ["schemars"]
7485
isolation = ["aes-gcm", "getrandom", "serialize-to-javascript"]
@@ -77,3 +88,4 @@ config-json5 = ["json5"]
7788
config-toml = []
7889
resources = ["walkdir"]
7990
html-manipulation = ["dep:html5ever", "dep:kuchiki"]
91+
html-manipulation-2 = ["dep:dom_query"]

0 commit comments

Comments
 (0)