Skip to content

Commit 1ee6f5f

Browse files
committed
fix(hpx-browser): resolve Blitz DOM migration build/lint/test failures
Complete the Blitz DOM migration with build, lint, and test fixes: Build fixes: - Fix 4 borrow checker errors in js_runtime/extensions/dom_ext.rs (op_dom_get_tag_name, op_dom_get_attribute, op_dom_has_attribute, op_dom_get_attribute_names) - replaced .and_then(|n| n.as_element()) with match on state.dom.get(id) to avoid capturing borrowed references inside closures - Suppress unused crate dependency warnings for openssl and tokio_openssl in hpx/src/lib.rs via #[cfg(feature = "openssl-tls")] use-as-underscore Lint fixes: - Remove unused optional serde dependency from crates/yawc/Cargo.toml (triggering cargo-shear warning) - Add #[allow(deprecated)] to test module in crates/hpx-browser/src/net/mod.rs to suppress get()/post()/get_follow() deprecation warnings Test fixes: - Fix extract_assets_basic test failure by setting config.base_url = Some("http://localhost") in html_parser.rs - blitz-dom defaults to data: URL which cannot resolve relative URLs like /style.css, causing panic at document::resolve_url Other: - Remove custom CSS parser, cascade, selectors, values (~4000 lines) replaced by blitz-dom via inner.resolve() - Rewrite dom.rs to thin wrapper over BlitzDocument - Add specs/2026-07-05-01-openssl-tls-backend design documentation 54 files changed, 1985 insertions(+), 7457 deletions(-)
1 parent 3148b19 commit 1ee6f5f

54 files changed

Lines changed: 1947 additions & 7419 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

Cargo.toml

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -183,14 +183,6 @@ unused_must_use = "deny"
183183
all = "warn"
184184

185185
[workspace.dependencies]
186-
# local crates
187-
hpx = { path = "crates/hpx", version = "2.4.33" }
188-
189-
hpx-browser = { path = "crates/hpx-browser", version = "2.4.33" }
190-
191-
hpx-dl = { path = "crates/hpx-dl", version = "2.4.33" }
192-
193-
hpx-yawc = { path = "crates/yawc", version = "2.4.33" }
194186

195187
# external crates
196188
ahash = "0.8.12"
@@ -199,6 +191,11 @@ async-trait = "0.1.80"
199191
axum = "0.8.9"
200192
axum-core = "0.5.6"
201193
base64 = "0.22.1"
194+
195+
# Blitz browser engine crates (git dep to match stylo 0.19.0)
196+
blitz-dom = { git = "https://github.com/DioxusLabs/blitz.git", version = "=0.3.0-alpha.6", features = ["floats"] }
197+
blitz-html = { git = "https://github.com/DioxusLabs/blitz.git", version = "=0.3.0-alpha.6" }
198+
blitz-traits = { git = "https://github.com/DioxusLabs/blitz.git", version = "=0.3.0-alpha.6" }
202199
boring = "5.1.0"
203200
brotli = "8.0.4"
204201
bytes = "1.12.0"
@@ -226,15 +223,19 @@ hex = "0.4.3"
226223
hickory-resolver = "0.26.0"
227224
hmac = "0.13.0"
228225
hotpath = "0.19.1"
229-
html5ever = "0.39.0"
226+
# local crates
227+
hpx = { path = "crates/hpx", version = "2.4.33" }
228+
hpx-browser = { path = "crates/hpx-browser", version = "2.4.33" }
229+
hpx-dl = { path = "crates/hpx-dl", version = "2.4.33" }
230+
hpx-yawc = { path = "crates/yawc", version = "2.4.33" }
230231
http = "1.4.2"
231232
http-body = "1.0.1"
232233
http-body-util = "0.1.3"
233234
http2 = "0.5.19"
234235
httparse = "1.10.1"
235236
hyper = "1.10.1"
236237
hyper-util = "0.1.20"
237-
image = "0.25.10"
238+
image = "=0.25.6"
238239
ipnet = "2.12.0"
239240
js-sys = "0.3.103"
240241
libc = "0.2.186"
@@ -244,6 +245,7 @@ mime = "0.3.17"
244245
mime_guess = "2.0.5"
245246
openssl = "0.10"
246247
parking_lot = "0.12.3"
248+
parley = "0.10"
247249
percent-encoding = "2.3.2"
248250
pin-project = "1.1.11"
249251
pin-project-lite = "0.2.17"
@@ -264,7 +266,7 @@ rustls-pki-types = "1.15.0"
264266
rustybuzz = "0.20.1"
265267
scc = "3.8.3"
266268
schnellru = "0.2.4"
267-
selectors = "0.38.0"
269+
selectors = "0.39.0"
268270
serde = "1.0.228"
269271
serde_json = "1.0.150"
270272
serde_urlencoded = "0.7.1"
@@ -279,11 +281,11 @@ socket2 = "0.6.4"
279281
sqlx = "0.9.0"
280282
strum = "0.28.0"
281283
strum_macros = "0.28.0"
282-
stylo = "0.18.0"
284+
stylo = "0.19.0"
283285
swash = "0.2.9"
284286
sync_wrapper = "1.0.2"
285287
system-configuration = "0.7.0"
286-
taffy = "0.12.0"
288+
taffy = { version = "0.12.1", default-features = false, features = ["std", "flexbox", "grid", "block_layout", "content_size", "calc", "detailed_layout_info", "float_layout", "taffy_tree"] }
287289
tempfile = "3.27.0"
288290
thiserror = "2.0.18"
289291
tokio = "1.52.1"

crates/hpx-browser/Cargo.toml

Lines changed: 22 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -19,60 +19,52 @@ canvas = [
1919
"dep:rustfft",
2020
]
2121
cdp = ["dep:hpx-yawc", "dep:hyper", "dep:hyper-util", "hpx-yawc/axum"]
22-
css_engine = ["dep:stylo", "dep:selectors"]
2322
proptest = ["dep:proptest"]
2423
v8 = ["dep:deno_core"]
2524
workers = ["v8"]
2625

2726
[dependencies]
28-
# always-on
2927
async-trait = { workspace = true }
3028
base64 = { workspace = true }
29+
30+
# Blitz browser engine crates
31+
blitz-dom = { workspace = true }
32+
blitz-html = { workspace = true }
33+
blitz-traits = { workspace = true }
3134
chrono = { workspace = true }
35+
36+
deno_core = { workspace = true, optional = true }
37+
fontdb = { workspace = true, optional = true }
3238
futures-util = { workspace = true }
3339
hpx = { workspace = true }
34-
html5ever = { workspace = true }
40+
hpx-yawc = { workspace = true, optional = true }
41+
hyper = { workspace = true, features = ["server", "http1"], optional = true }
42+
hyper-util = { workspace = true, features = [
43+
"tokio",
44+
"server-auto",
45+
], optional = true }
46+
image = { workspace = true, optional = true }
47+
parley = { workspace = true }
48+
png = { workspace = true, optional = true }
49+
proptest = { workspace = true, optional = true }
3550
rand = { workspace = true }
3651
rand_chacha = { workspace = true }
3752
rand_distr = { workspace = true }
53+
rustfft = { workspace = true, optional = true }
54+
rustybuzz = { workspace = true, optional = true }
3855
scc = { workspace = true }
3956
serde = { workspace = true, features = ["derive"] }
4057
serde_json = { workspace = true }
4158
sha2 = { workspace = true }
59+
skia-safe = { workspace = true, optional = true }
60+
swash = { workspace = true, optional = true }
4261
taffy = { workspace = true }
4362
thiserror = { workspace = true }
4463
tokio = { workspace = true, features = ["full"] }
4564
tracing = { workspace = true }
4665
url = { workspace = true }
4766
winnow = { workspace = true }
4867

49-
# v8 feature
50-
deno_core = { workspace = true, optional = true }
51-
52-
# canvas feature
53-
fontdb = { workspace = true, optional = true }
54-
image = { workspace = true, optional = true }
55-
png = { workspace = true, optional = true }
56-
rustfft = { workspace = true, optional = true }
57-
rustybuzz = { workspace = true, optional = true }
58-
skia-safe = { workspace = true, optional = true }
59-
swash = { workspace = true, optional = true }
60-
61-
# cdp feature
62-
hpx-yawc = { workspace = true, optional = true }
63-
hyper = { workspace = true, features = ["server", "http1"], optional = true }
64-
hyper-util = { workspace = true, features = [
65-
"tokio",
66-
"server-auto",
67-
], optional = true }
68-
69-
# css_engine feature
70-
selectors = { workspace = true, optional = true }
71-
stylo = { workspace = true, optional = true }
72-
73-
# property testing
74-
proptest = { workspace = true, optional = true }
75-
7668
[dev-dependencies]
7769
criterion = { workspace = true, features = ["html_reports"] }
7870

crates/hpx-browser/src/canvas/text/shaper.rs

Lines changed: 115 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,15 @@
1-
//! Text shaping via `rustybuzz`.
1+
use std::{
2+
collections::HashMap,
3+
sync::{Mutex, OnceLock},
4+
};
5+
6+
use parley::{
7+
Alignment, AlignmentOptions, FontContext, LayoutContext,
8+
fontique::Blob,
9+
layout::PositionedLayoutItem,
10+
style::{FontFamily, StyleProperty},
11+
};
212

3-
use rustybuzz::{Face as RustybuzzFace, UnicodeBuffer};
4-
5-
/// A single glyph emitted by the shaper.
613
#[derive(Debug, Clone)]
714
pub struct Glyph {
815
pub glyph_id: u32,
@@ -13,7 +20,6 @@ pub struct Glyph {
1320
pub y_offset: f32,
1421
}
1522

16-
/// A shaped run of glyphs.
1723
#[derive(Debug, Clone)]
1824
pub struct ShapedRun {
1925
pub glyphs: Vec<Glyph>,
@@ -41,92 +47,131 @@ impl ShapedRun {
4147
}
4248
}
4349

44-
/// Shape `text` using the given font face at `size_px`.
45-
pub fn shape(text: &str, face_data: &[u8], face_index: u32, size_px: f32) -> ShapedRun {
46-
if text.is_empty() {
47-
return ShapedRun::empty();
50+
struct ParleyState {
51+
font_cx: Mutex<FontContext>,
52+
layout_cx: Mutex<LayoutContext>,
53+
family_cache: Mutex<HashMap<usize, String>>,
54+
}
55+
56+
static PARLEY: OnceLock<ParleyState> = OnceLock::new();
57+
58+
fn parley_state() -> &'static ParleyState {
59+
PARLEY.get_or_init(|| ParleyState {
60+
font_cx: Mutex::new(FontContext::new()),
61+
layout_cx: Mutex::new(LayoutContext::new()),
62+
family_cache: Mutex::new(HashMap::new()),
63+
})
64+
}
65+
66+
fn ensure_font_registered(state: &ParleyState, face_data: &[u8]) -> String {
67+
let key = face_data.as_ptr() as usize;
68+
{
69+
let cache = state.family_cache.lock().unwrap();
70+
if let Some(name) = cache.get(&key) {
71+
return name.clone();
72+
}
4873
}
49-
let Some(face) = RustybuzzFace::from_slice(face_data, face_index) else {
50-
return ShapedRun::empty();
51-
};
52-
let upem = face.units_per_em() as f32;
53-
if upem <= 0.0 {
74+
let mut font_cx = state.font_cx.lock().unwrap();
75+
let blob = Blob::from(face_data.to_vec());
76+
let results = font_cx.collection.register_fonts(blob, None);
77+
let name = results
78+
.first()
79+
.and_then(|(fid, _)| font_cx.collection.family_name(*fid))
80+
.unwrap_or("sans-serif")
81+
.to_string();
82+
drop(font_cx);
83+
let mut cache = state.family_cache.lock().unwrap();
84+
cache.insert(key, name.clone());
85+
name
86+
}
87+
88+
pub fn shape(text: &str, face_data: &[u8], _face_index: u32, size_px: f32) -> ShapedRun {
89+
if text.is_empty() {
5490
return ShapedRun::empty();
5591
}
56-
let scale = size_px / upem;
5792

58-
let mut buffer = UnicodeBuffer::new();
59-
buffer.push_str(text);
60-
buffer.guess_segment_properties();
93+
let state = parley_state();
94+
let family_name = ensure_font_registered(state, face_data);
95+
96+
let mut font_cx = state.font_cx.lock().unwrap();
97+
let mut layout_cx = state.layout_cx.lock().unwrap();
98+
99+
let mut builder = layout_cx.ranged_builder(&mut font_cx, text, 1.0, true);
100+
builder.push_default(StyleProperty::FontFamily(FontFamily::named(&family_name)));
101+
builder.push_default(StyleProperty::FontSize(size_px));
61102

62-
let glyph_buffer = rustybuzz::shape(&face, &[], buffer);
63-
let infos = glyph_buffer.glyph_infos();
64-
let positions = glyph_buffer.glyph_positions();
103+
let mut layout = builder.build(text);
104+
layout.break_all_lines(Some(f32::INFINITY));
105+
layout.align(Alignment::Start, AlignmentOptions::default());
65106

66-
let mut glyphs = Vec::with_capacity(infos.len());
107+
let mut glyphs = Vec::new();
67108
let mut cursor_x = 0.0_f32;
68-
let mut total_advance = 0.0_f32;
69109
let mut bbox_left = f32::INFINITY;
70110
let mut bbox_right = f32::NEG_INFINITY;
71111
let mut bbox_ascent = f32::NEG_INFINITY;
72112
let mut bbox_descent = f32::NEG_INFINITY;
73-
let mut any_glyph_bbox = false;
74-
75-
for (info, pos) in infos.iter().zip(positions.iter()) {
76-
let x_advance = pos.x_advance as f32 * scale;
77-
let y_advance = pos.y_advance as f32 * scale;
78-
let x_offset = pos.x_offset as f32 * scale;
79-
let y_offset = pos.y_offset as f32 * scale;
80-
81-
if let Some(em_bbox) =
82-
face.glyph_bounding_box(rustybuzz::ttf_parser::GlyphId(info.glyph_id as u16))
83-
{
84-
any_glyph_bbox = true;
85-
let glyph_left = cursor_x + x_offset + em_bbox.x_min as f32 * scale;
86-
let glyph_right = cursor_x + x_offset + em_bbox.x_max as f32 * scale;
87-
let glyph_top = (em_bbox.y_max as f32 * scale) - y_offset;
88-
let glyph_bot = (em_bbox.y_min as f32 * scale) - y_offset;
89-
if glyph_left < bbox_left {
90-
bbox_left = glyph_left;
91-
}
92-
if glyph_right > bbox_right {
93-
bbox_right = glyph_right;
94-
}
95-
if glyph_top > bbox_ascent {
96-
bbox_ascent = glyph_top;
97-
}
98-
if -glyph_bot > bbox_descent {
99-
bbox_descent = -glyph_bot;
100-
}
113+
let mut ascent = 0.0_f32;
114+
let mut descent = 0.0_f32;
115+
116+
for line in layout.lines() {
117+
let line_metrics = line.metrics();
118+
if line_metrics.ascent > ascent {
119+
ascent = line_metrics.ascent;
120+
}
121+
if line_metrics.descent > descent {
122+
descent = line_metrics.descent;
101123
}
102124

103-
glyphs.push(Glyph {
104-
glyph_id: info.glyph_id,
105-
cluster: info.cluster,
106-
x_advance,
107-
y_advance,
108-
x_offset,
109-
y_offset,
110-
});
111-
cursor_x += x_advance;
112-
total_advance += x_advance;
125+
for item in line.items() {
126+
if let PositionedLayoutItem::GlyphRun(glyph_run) = item {
127+
for g in glyph_run.positioned_glyphs() {
128+
let x_advance = g.advance;
129+
let x_offset = g.x - cursor_x;
130+
let y_offset = g.y;
131+
132+
let glyph_left = cursor_x + x_offset;
133+
let glyph_right = glyph_left + x_advance;
134+
if glyph_left < bbox_left {
135+
bbox_left = glyph_left;
136+
}
137+
if glyph_right > bbox_right {
138+
bbox_right = glyph_right;
139+
}
140+
if -y_offset > bbox_ascent {
141+
bbox_ascent = -y_offset;
142+
}
143+
if y_offset > bbox_descent {
144+
bbox_descent = y_offset;
145+
}
146+
147+
glyphs.push(Glyph {
148+
glyph_id: g.id,
149+
cluster: 0,
150+
x_advance,
151+
y_advance: 0.0,
152+
x_offset,
153+
y_offset,
154+
});
155+
cursor_x += x_advance;
156+
}
157+
}
158+
}
113159
}
114160

115-
let ascender = face.ascender() as f32 * scale;
116-
let descender = face.descender() as f32 * scale;
161+
let width = cursor_x;
117162

118-
if !any_glyph_bbox {
163+
if glyphs.is_empty() {
119164
bbox_left = 0.0;
120-
bbox_right = total_advance;
121-
bbox_ascent = ascender;
122-
bbox_descent = -descender;
165+
bbox_right = 0.0;
166+
bbox_ascent = 0.0;
167+
bbox_descent = 0.0;
123168
}
124169

125170
ShapedRun {
126171
glyphs,
127-
width: total_advance,
128-
ascent: ascender,
129-
descent: -descender,
172+
width,
173+
ascent,
174+
descent,
130175
bbox_left,
131176
bbox_right,
132177
bbox_ascent,

0 commit comments

Comments
 (0)