blitz-pdf 0.1.2 → 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +26 -0
- package/THIRD_PARTY_NOTICES.md +5 -2
- package/docs/layout-regressions.md +65 -0
- package/docs/pagination-regressions.md +89 -0
- package/package.json +2 -1
- package/vendor/README.md +35 -0
- package/vendor/blitz-dom/Cargo.toml +113 -0
- package/vendor/blitz-dom/LICENSE-APACHE +176 -0
- package/vendor/blitz-dom/LICENSE-MIT +23 -0
- package/vendor/blitz-dom/assets/default.css +1142 -0
- package/vendor/blitz-dom/assets/moz-bullet-font.otf +0 -0
- package/vendor/blitz-dom/src/accessibility.rs +255 -0
- package/vendor/blitz-dom/src/config.rs +97 -0
- package/vendor/blitz-dom/src/cssom.rs +971 -0
- package/vendor/blitz-dom/src/debug.rs +206 -0
- package/vendor/blitz-dom/src/document.rs +3395 -0
- package/vendor/blitz-dom/src/events/driver.rs +427 -0
- package/vendor/blitz-dom/src/events/focus.rs +40 -0
- package/vendor/blitz-dom/src/events/ime.rs +31 -0
- package/vendor/blitz-dom/src/events/keyboard.rs +174 -0
- package/vendor/blitz-dom/src/events/mod.rs +315 -0
- package/vendor/blitz-dom/src/events/pointer.rs +849 -0
- package/vendor/blitz-dom/src/font_metrics.rs +177 -0
- package/vendor/blitz-dom/src/form.rs +463 -0
- package/vendor/blitz-dom/src/html.rs +38 -0
- package/vendor/blitz-dom/src/iframe.rs +186 -0
- package/vendor/blitz-dom/src/layout/construct.rs +1328 -0
- package/vendor/blitz-dom/src/layout/damage.rs +642 -0
- package/vendor/blitz-dom/src/layout/inline.rs +1160 -0
- package/vendor/blitz-dom/src/layout/list.rs +237 -0
- package/vendor/blitz-dom/src/layout/mod.rs +969 -0
- package/vendor/blitz-dom/src/layout/paint_tree.rs +462 -0
- package/vendor/blitz-dom/src/layout/replaced.rs +439 -0
- package/vendor/blitz-dom/src/layout/table.rs +967 -0
- package/vendor/blitz-dom/src/layout/text_transform.rs +979 -0
- package/vendor/blitz-dom/src/layout/writing_mode.rs +437 -0
- package/vendor/blitz-dom/src/lib.rs +160 -0
- package/vendor/blitz-dom/src/mutator.rs +1818 -0
- package/vendor/blitz-dom/src/net.rs +649 -0
- package/vendor/blitz-dom/src/node/attributes.rs +60 -0
- package/vendor/blitz-dom/src/node/custom_widget.rs +178 -0
- package/vendor/blitz-dom/src/node/element.rs +1106 -0
- package/vendor/blitz-dom/src/node/mod.rs +30 -0
- package/vendor/blitz-dom/src/node/node.rs +2036 -0
- package/vendor/blitz-dom/src/node/scrollbar.rs +237 -0
- package/vendor/blitz-dom/src/node/serialize.rs +225 -0
- package/vendor/blitz-dom/src/node/stylo_data.rs +142 -0
- package/vendor/blitz-dom/src/node/svg.rs +265 -0
- package/vendor/blitz-dom/src/node/text.rs +804 -0
- package/vendor/blitz-dom/src/query_selector.rs +275 -0
- package/vendor/blitz-dom/src/resolve.rs +474 -0
- package/vendor/blitz-dom/src/resolved_style.rs +797 -0
- package/vendor/blitz-dom/src/scrolling.rs +780 -0
- package/vendor/blitz-dom/src/selection.rs +122 -0
- package/vendor/blitz-dom/src/stylo.rs +1518 -0
- package/vendor/blitz-dom/src/stylo_device.rs +88 -0
- package/vendor/blitz-dom/src/stylo_to_cursor_icon.rs +49 -0
- package/vendor/blitz-dom/src/stylo_to_kurbo.rs +129 -0
- package/vendor/blitz-dom/src/stylo_to_parley.rs +655 -0
- package/vendor/blitz-dom/src/traversal.rs +600 -0
- package/vendor/blitz-dom/src/tree.rs +136 -0
- package/vendor/blitz-dom/src/url.rs +63 -0
- package/vendor/blitz-dom/src/util.rs +256 -0
- package/wasm/blitz_pdf_bg.wasm +0 -0
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
//! Overlay scrollbar geometry and css-scrollbars-1 style accessors for
|
|
2
|
+
//! [`Node`]. Geometry is shared between painting (blitz-paint) and thumb
|
|
3
|
+
//! hit-testing so the two cannot drift.
|
|
4
|
+
|
|
5
|
+
use blitz_traits::node_id::NodeId;
|
|
6
|
+
use kurbo::Rect as KurboRect;
|
|
7
|
+
use taffy::AbsoluteAxis;
|
|
8
|
+
use web_time::Duration;
|
|
9
|
+
|
|
10
|
+
use super::Node;
|
|
11
|
+
|
|
12
|
+
/// How long overlay scrollbars stay fully opaque after their last activity
|
|
13
|
+
/// (a scroll, or the pointer leaving the thumb), and how long the fade-out
|
|
14
|
+
/// takes. Chromium's overlay values.
|
|
15
|
+
pub(crate) const FADE_DELAY: Duration = Duration::from_millis(500);
|
|
16
|
+
pub(crate) const FADE_DURATION: Duration = Duration::from_millis(200);
|
|
17
|
+
|
|
18
|
+
/// Overlay scrollbar opacity as a function of time since the scroll
|
|
19
|
+
/// container's last scrollbar activity: fully opaque through the fade
|
|
20
|
+
/// delay, then fading linearly to hidden.
|
|
21
|
+
pub(crate) fn opacity_at(elapsed: Duration) -> f32 {
|
|
22
|
+
match elapsed.checked_sub(FADE_DELAY) {
|
|
23
|
+
None => 1.0,
|
|
24
|
+
Some(fading) => 1.0 - (fading.as_secs_f32() / FADE_DURATION.as_secs_f32()).min(1.0),
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/// A specific scrollbar: one axis of one scroll container.
|
|
29
|
+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
30
|
+
pub struct ScrollbarRef {
|
|
31
|
+
pub node_id: NodeId,
|
|
32
|
+
pub axis: AbsoluteAxis,
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/// The computed value of `scrollbar-width` (css-scrollbars-1).
|
|
36
|
+
pub use style::properties::generated::longhands::scrollbar_width::computed_value::T as ScrollbarWidth;
|
|
37
|
+
|
|
38
|
+
/// The computed value of `scrollbar-color` (css-scrollbars-1), with colors
|
|
39
|
+
/// fully resolved (no `currentColor`).
|
|
40
|
+
#[derive(Clone, Debug, Default, PartialEq)]
|
|
41
|
+
pub enum ScrollbarColor {
|
|
42
|
+
#[default]
|
|
43
|
+
Auto,
|
|
44
|
+
Colors {
|
|
45
|
+
thumb: style::color::AbsoluteColor,
|
|
46
|
+
track: style::color::AbsoluteColor,
|
|
47
|
+
},
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
impl Node {
|
|
51
|
+
/// The node's computed `scrollbar-width`.
|
|
52
|
+
pub fn scrollbar_width(&self) -> ScrollbarWidth {
|
|
53
|
+
self.primary_styles()
|
|
54
|
+
.map(|style| style.clone_scrollbar_width())
|
|
55
|
+
.unwrap_or(ScrollbarWidth::Auto)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/// The node's computed `scrollbar-color`, with colors resolved against
|
|
59
|
+
/// the element's computed `color`.
|
|
60
|
+
pub fn scrollbar_color(&self) -> ScrollbarColor {
|
|
61
|
+
let Some(style) = self.primary_styles() else {
|
|
62
|
+
return ScrollbarColor::Auto;
|
|
63
|
+
};
|
|
64
|
+
let current_color = style.clone_color();
|
|
65
|
+
match style.clone_scrollbar_color() {
|
|
66
|
+
style::values::computed::ScrollbarColor::Auto => ScrollbarColor::Auto,
|
|
67
|
+
style::values::computed::ScrollbarColor::Colors { thumb, track } => {
|
|
68
|
+
ScrollbarColor::Colors {
|
|
69
|
+
thumb: thumb.resolve_to_absolute(¤t_color),
|
|
70
|
+
track: track.resolve_to_absolute(¤t_color),
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/// Whether the node shows an overlay scrollbar in the given axis:
|
|
77
|
+
/// always for `overflow: scroll`, only when the content overflows for
|
|
78
|
+
/// `overflow: auto`, never otherwise — and never when
|
|
79
|
+
/// `scrollbar-width: none`.
|
|
80
|
+
pub fn wants_scrollbar(&self, axis: AbsoluteAxis) -> bool {
|
|
81
|
+
use style::values::computed::Overflow;
|
|
82
|
+
let Some(style) = self.primary_styles() else {
|
|
83
|
+
return false;
|
|
84
|
+
};
|
|
85
|
+
if self.scrollbar_width() == ScrollbarWidth::None {
|
|
86
|
+
return false;
|
|
87
|
+
}
|
|
88
|
+
let (overflow, scroll_extent) = match axis {
|
|
89
|
+
AbsoluteAxis::Horizontal => (
|
|
90
|
+
style.clone_overflow_x(),
|
|
91
|
+
self.final_layout().scroll_width() as f64,
|
|
92
|
+
),
|
|
93
|
+
AbsoluteAxis::Vertical => (
|
|
94
|
+
style.clone_overflow_y(),
|
|
95
|
+
self.final_layout().scroll_height() as f64,
|
|
96
|
+
),
|
|
97
|
+
};
|
|
98
|
+
match overflow {
|
|
99
|
+
Overflow::Scroll => true,
|
|
100
|
+
Overflow::Auto => scroll_extent > 0.5,
|
|
101
|
+
_ => false,
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/// The scrollport (padding box) in (unscaled) CSS px relative to the
|
|
106
|
+
/// node's border-box origin. Taffy has content-box helpers but none for
|
|
107
|
+
/// the padding box.
|
|
108
|
+
fn scrollport(&self) -> KurboRect {
|
|
109
|
+
let layout = self.final_layout();
|
|
110
|
+
KurboRect::new(
|
|
111
|
+
layout.border.left as f64,
|
|
112
|
+
layout.border.top as f64,
|
|
113
|
+
layout.size.width as f64 - layout.border.right as f64,
|
|
114
|
+
layout.size.height as f64 - layout.border.bottom as f64,
|
|
115
|
+
)
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/// Geometry of the overlay scrollbar thumb for the given axis, in
|
|
119
|
+
/// (unscaled) CSS px relative to the node's border-box origin. `None`
|
|
120
|
+
/// if there is no scrollable overflow in that axis.
|
|
121
|
+
pub fn scrollbar_thumb(&self, axis: AbsoluteAxis) -> Option<KurboRect> {
|
|
122
|
+
// Matches Chromium's overlay thumb in its interactive state.
|
|
123
|
+
const THUMB_THICKNESS: f64 = 10.0;
|
|
124
|
+
const THIN_THUMB_THICKNESS: f64 = 6.0;
|
|
125
|
+
const THUMB_MARGIN: f64 = 2.0;
|
|
126
|
+
const MIN_THUMB_LENGTH: f64 = 32.0;
|
|
127
|
+
|
|
128
|
+
let layout = self.final_layout();
|
|
129
|
+
let scroll_extent = match axis {
|
|
130
|
+
AbsoluteAxis::Horizontal => layout.scroll_width() as f64,
|
|
131
|
+
AbsoluteAxis::Vertical => layout.scroll_height() as f64,
|
|
132
|
+
};
|
|
133
|
+
if scroll_extent <= 0.5 {
|
|
134
|
+
return None;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
let thickness = match self.scrollbar_width() {
|
|
138
|
+
ScrollbarWidth::Thin => THIN_THUMB_THICKNESS,
|
|
139
|
+
_ => THUMB_THICKNESS,
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
let port = self.scrollport();
|
|
143
|
+
let (viewport_len, scroll_offset) = match axis {
|
|
144
|
+
AbsoluteAxis::Horizontal => (port.width(), self.scroll_offset().x),
|
|
145
|
+
AbsoluteAxis::Vertical => (port.height(), self.scroll_offset().y),
|
|
146
|
+
};
|
|
147
|
+
let thumb_len = (viewport_len * viewport_len / (viewport_len + scroll_extent))
|
|
148
|
+
.max(MIN_THUMB_LENGTH)
|
|
149
|
+
.min(viewport_len);
|
|
150
|
+
let progress = (scroll_offset / scroll_extent).clamp(0.0, 1.0);
|
|
151
|
+
// Round a sub-pixel displacement up to a whole pixel so any nonzero
|
|
152
|
+
// scroll visibly moves the thumb off the origin.
|
|
153
|
+
let thumb_start = match progress * (viewport_len - thumb_len) {
|
|
154
|
+
start if start > 0.0 && start < 1.0 => 1.0,
|
|
155
|
+
start => start,
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
Some(match axis {
|
|
159
|
+
AbsoluteAxis::Horizontal => KurboRect::new(
|
|
160
|
+
port.x0 + thumb_start,
|
|
161
|
+
port.y1 - THUMB_MARGIN - thickness,
|
|
162
|
+
port.x0 + thumb_start + thumb_len,
|
|
163
|
+
port.y1 - THUMB_MARGIN,
|
|
164
|
+
),
|
|
165
|
+
AbsoluteAxis::Vertical => KurboRect::new(
|
|
166
|
+
port.x1 - THUMB_MARGIN - thickness,
|
|
167
|
+
port.y0 + thumb_start,
|
|
168
|
+
port.x1 - THUMB_MARGIN,
|
|
169
|
+
port.y0 + thumb_start + thumb_len,
|
|
170
|
+
),
|
|
171
|
+
})
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/// Content px scrolled per thumb px dragged, for the given axis.
|
|
175
|
+
pub fn scrollbar_drag_ratio(&self, axis: AbsoluteAxis) -> f64 {
|
|
176
|
+
let Some(thumb) = self.scrollbar_thumb(axis) else {
|
|
177
|
+
return 0.0;
|
|
178
|
+
};
|
|
179
|
+
let port = self.scrollport();
|
|
180
|
+
let (scroll_extent, viewport_len, thumb_len) = match axis {
|
|
181
|
+
AbsoluteAxis::Horizontal => (
|
|
182
|
+
self.final_layout().scroll_width() as f64,
|
|
183
|
+
port.width(),
|
|
184
|
+
thumb.width(),
|
|
185
|
+
),
|
|
186
|
+
AbsoluteAxis::Vertical => (
|
|
187
|
+
self.final_layout().scroll_height() as f64,
|
|
188
|
+
port.height(),
|
|
189
|
+
thumb.height(),
|
|
190
|
+
),
|
|
191
|
+
};
|
|
192
|
+
let track_play = viewport_len - thumb_len;
|
|
193
|
+
if track_play <= 0.0 {
|
|
194
|
+
return 0.0;
|
|
195
|
+
}
|
|
196
|
+
scroll_extent / track_play
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/// The scrollbar thumb containing the given point (in this node's
|
|
200
|
+
/// border-box coordinates), if any. The `scrollbars` feature's single
|
|
201
|
+
/// behavioral gate: returning `None` keeps unpainted thumbs from ever
|
|
202
|
+
/// claiming pointer events.
|
|
203
|
+
pub(crate) fn scrollbar_at_local(&self, x: f64, y: f64) -> Option<ScrollbarRef> {
|
|
204
|
+
if !cfg!(feature = "scrollbars") {
|
|
205
|
+
return None;
|
|
206
|
+
}
|
|
207
|
+
for axis in [AbsoluteAxis::Vertical, AbsoluteAxis::Horizontal] {
|
|
208
|
+
if !self.wants_scrollbar(axis) {
|
|
209
|
+
continue;
|
|
210
|
+
}
|
|
211
|
+
if let Some(thumb) = self.scrollbar_thumb(axis)
|
|
212
|
+
&& thumb.contains(kurbo::Point::new(x, y))
|
|
213
|
+
{
|
|
214
|
+
return Some(ScrollbarRef {
|
|
215
|
+
node_id: self.id,
|
|
216
|
+
axis,
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
None
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
#[cfg(test)]
|
|
225
|
+
mod tests {
|
|
226
|
+
use super::*;
|
|
227
|
+
|
|
228
|
+
#[test]
|
|
229
|
+
fn opacity_holds_through_the_fade_delay_then_fades_out() {
|
|
230
|
+
assert_eq!(opacity_at(Duration::ZERO), 1.0);
|
|
231
|
+
assert_eq!(opacity_at(FADE_DELAY), 1.0);
|
|
232
|
+
let mid_fade = opacity_at(FADE_DELAY + FADE_DURATION / 2);
|
|
233
|
+
assert!((mid_fade - 0.5).abs() < 0.01, "got {mid_fade}");
|
|
234
|
+
assert_eq!(opacity_at(FADE_DELAY + FADE_DURATION), 0.0);
|
|
235
|
+
assert_eq!(opacity_at(Duration::from_secs(3600)), 0.0);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
//! Serialization of nodes to HTML, and of inline `<svg>` subtrees to SVG source for usvg.
|
|
2
|
+
|
|
3
|
+
use html_escape::{encode_quoted_attribute_to_string, encode_text_to_string};
|
|
4
|
+
use markup5ever::{QualName, ns};
|
|
5
|
+
use style_traits::values::ToCss;
|
|
6
|
+
|
|
7
|
+
use super::{Node, NodeData};
|
|
8
|
+
|
|
9
|
+
#[derive(Clone, Copy)]
|
|
10
|
+
enum OutputStyle {
|
|
11
|
+
Normal,
|
|
12
|
+
Pretty,
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
#[derive(Clone, Copy)]
|
|
16
|
+
struct SerializeOpts {
|
|
17
|
+
style: OutputStyle,
|
|
18
|
+
/// Replace `currentColor` in attribute values with the element's computed `color`
|
|
19
|
+
resolve_current_color: bool,
|
|
20
|
+
/// Declare the `xlink` prefix on the root element so that the output is well-formed XML
|
|
21
|
+
declare_xlink_ns: bool,
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
impl SerializeOpts {
|
|
25
|
+
const fn html(style: OutputStyle) -> Self {
|
|
26
|
+
Self {
|
|
27
|
+
style,
|
|
28
|
+
resolve_current_color: false,
|
|
29
|
+
declare_xlink_ns: false,
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const XLINK_NS: &str = "http://www.w3.org/1999/xlink";
|
|
35
|
+
|
|
36
|
+
/// Writes an attribute's serialized name, per the HTML fragment serialization algorithm
|
|
37
|
+
fn write_attr_name(name: &QualName, writer: &mut String) {
|
|
38
|
+
match name.ns {
|
|
39
|
+
ns!() | ns!(html) => {}
|
|
40
|
+
ns!(xml) => writer.push_str("xml:"),
|
|
41
|
+
ns!(xmlns) if name.local.as_str() == "xmlns" => {}
|
|
42
|
+
ns!(xmlns) => writer.push_str("xmlns:"),
|
|
43
|
+
ns!(xlink) => writer.push_str("xlink:"),
|
|
44
|
+
_ => {
|
|
45
|
+
if let Some(prefix) = &name.prefix {
|
|
46
|
+
writer.push_str(prefix);
|
|
47
|
+
writer.push(':');
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
writer.push_str(&name.local);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
fn is_xlink_ns_decl(name: &QualName) -> bool {
|
|
55
|
+
(name.ns == ns!(xmlns) && name.local.as_str() == "xlink")
|
|
56
|
+
|| name.local.as_str() == "xmlns:xlink"
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
impl Node {
|
|
60
|
+
/// Renders the HTML of this node and all its children as a `String` without extra whitespace.
|
|
61
|
+
///
|
|
62
|
+
/// Example output:
|
|
63
|
+
///
|
|
64
|
+
/// ```text
|
|
65
|
+
/// <html><head /><body><main id="main"><div class="arbitrary-class" /></main></body></html>
|
|
66
|
+
/// ```
|
|
67
|
+
pub fn outer_html(&self) -> String {
|
|
68
|
+
let mut output = String::new();
|
|
69
|
+
self.write_outer_html(&mut output);
|
|
70
|
+
output
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/// Renders the HTML of this node and all its children as a `String` with whitespace for human
|
|
74
|
+
/// readability.
|
|
75
|
+
///
|
|
76
|
+
/// Example output:
|
|
77
|
+
///
|
|
78
|
+
/// ```text
|
|
79
|
+
/// <html>
|
|
80
|
+
/// <head />
|
|
81
|
+
/// <body>
|
|
82
|
+
/// <main id="main">
|
|
83
|
+
/// <div class="arbitrary-class" />
|
|
84
|
+
/// </main>
|
|
85
|
+
/// </body>
|
|
86
|
+
/// </html>
|
|
87
|
+
/// ```
|
|
88
|
+
pub fn outer_html_pretty(&self) -> String {
|
|
89
|
+
let mut output = String::new();
|
|
90
|
+
self.write_outer_html_pretty(&mut output);
|
|
91
|
+
output
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
pub fn write_outer_html(&self, writer: &mut String) {
|
|
95
|
+
self.write_outer_html_in_style(writer, SerializeOpts::html(OutputStyle::Normal), 0);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
pub fn write_outer_html_pretty(&self, writer: &mut String) {
|
|
99
|
+
self.write_outer_html_in_style(writer, SerializeOpts::html(OutputStyle::Pretty), 0);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/// Serializes this `<svg>` subtree as a standalone SVG document for usvg, with
|
|
103
|
+
/// `currentColor` resolved against each element's computed `color`.
|
|
104
|
+
#[cfg(feature = "svg")]
|
|
105
|
+
pub(crate) fn svg_source(&self) -> String {
|
|
106
|
+
let mut output = String::new();
|
|
107
|
+
let opts = SerializeOpts {
|
|
108
|
+
style: OutputStyle::Normal,
|
|
109
|
+
resolve_current_color: true,
|
|
110
|
+
declare_xlink_ns: true,
|
|
111
|
+
};
|
|
112
|
+
self.write_outer_html_in_style(&mut output, opts, 0);
|
|
113
|
+
output
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
fn write_outer_html_in_style(&self, writer: &mut String, opts: SerializeOpts, nesting: usize) {
|
|
117
|
+
const INDENT: &str = " ";
|
|
118
|
+
let style = opts.style;
|
|
119
|
+
let has_children = !self.children.is_empty();
|
|
120
|
+
let current_color = opts
|
|
121
|
+
.resolve_current_color
|
|
122
|
+
.then(|| self.primary_styles())
|
|
123
|
+
.flatten()
|
|
124
|
+
.map(|style| style.clone_color())
|
|
125
|
+
.map(|color| color.to_css_string());
|
|
126
|
+
|
|
127
|
+
match &self.data {
|
|
128
|
+
NodeData::Document(_) => {}
|
|
129
|
+
NodeData::Comment { .. } => {}
|
|
130
|
+
NodeData::AnonymousBlock(_) => {}
|
|
131
|
+
// NodeData::Doctype { name, .. } => write!(s, "DOCTYPE {name}"),
|
|
132
|
+
NodeData::Text(data) => {
|
|
133
|
+
if matches!(style, OutputStyle::Pretty) {
|
|
134
|
+
for _ in 0..nesting {
|
|
135
|
+
writer.push_str(INDENT);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
let in_raw_text_element = self
|
|
139
|
+
.parent
|
|
140
|
+
.and_then(|id| self.tree()[id].data.downcast_element())
|
|
141
|
+
.is_some_and(|el| {
|
|
142
|
+
// Documents are parsed with scripting disabled, so `<noscript>` is not raw text
|
|
143
|
+
el.name.ns == ns!(html)
|
|
144
|
+
&& matches!(
|
|
145
|
+
el.name.local.as_ref(),
|
|
146
|
+
"style"
|
|
147
|
+
| "script"
|
|
148
|
+
| "xmp"
|
|
149
|
+
| "iframe"
|
|
150
|
+
| "noembed"
|
|
151
|
+
| "noframes"
|
|
152
|
+
| "plaintext"
|
|
153
|
+
)
|
|
154
|
+
});
|
|
155
|
+
if in_raw_text_element {
|
|
156
|
+
writer.push_str(data.content.as_str());
|
|
157
|
+
} else {
|
|
158
|
+
encode_text_to_string(data.content.as_str(), writer);
|
|
159
|
+
}
|
|
160
|
+
if matches!(style, OutputStyle::Pretty) {
|
|
161
|
+
writer.push('\n');
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
NodeData::Element(data) => {
|
|
165
|
+
if matches!(style, OutputStyle::Pretty) {
|
|
166
|
+
for _ in 0..nesting {
|
|
167
|
+
writer.push_str(INDENT);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
writer.push('<');
|
|
171
|
+
writer.push_str(&data.name.local);
|
|
172
|
+
|
|
173
|
+
if opts.declare_xlink_ns
|
|
174
|
+
&& nesting == 0
|
|
175
|
+
&& !data.attrs().iter().any(|attr| is_xlink_ns_decl(&attr.name))
|
|
176
|
+
{
|
|
177
|
+
writer.push_str(" xmlns:xlink=\"");
|
|
178
|
+
writer.push_str(XLINK_NS);
|
|
179
|
+
writer.push('"');
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
for attr in data.attrs() {
|
|
183
|
+
writer.push(' ');
|
|
184
|
+
write_attr_name(&attr.name, writer);
|
|
185
|
+
writer.push_str("=\"");
|
|
186
|
+
#[allow(clippy::unnecessary_unwrap)] // Convert to if-let chain once stabilised
|
|
187
|
+
if current_color.is_some() && attr.value.contains("currentColor") {
|
|
188
|
+
let value = attr
|
|
189
|
+
.value
|
|
190
|
+
.replace("currentColor", current_color.as_ref().unwrap());
|
|
191
|
+
encode_quoted_attribute_to_string(&value, writer);
|
|
192
|
+
} else {
|
|
193
|
+
encode_quoted_attribute_to_string(&attr.value, writer);
|
|
194
|
+
}
|
|
195
|
+
writer.push('"');
|
|
196
|
+
}
|
|
197
|
+
if !has_children {
|
|
198
|
+
writer.push_str(" /");
|
|
199
|
+
}
|
|
200
|
+
writer.push('>');
|
|
201
|
+
if matches!(style, OutputStyle::Pretty) {
|
|
202
|
+
writer.push('\n');
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
if has_children {
|
|
206
|
+
for &child_id in &self.children {
|
|
207
|
+
self.tree()[child_id].write_outer_html_in_style(writer, opts, nesting + 1);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
if matches!(style, OutputStyle::Pretty) {
|
|
211
|
+
for _ in 0..nesting {
|
|
212
|
+
writer.push_str(INDENT);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
writer.push_str("</");
|
|
216
|
+
writer.push_str(&data.name.local);
|
|
217
|
+
writer.push('>');
|
|
218
|
+
if matches!(style, OutputStyle::Pretty) {
|
|
219
|
+
writer.push('\n');
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
use std::fmt;
|
|
2
|
+
use std::ops::Deref;
|
|
3
|
+
use std::{cell::UnsafeCell, ops::DerefMut};
|
|
4
|
+
use style::data::{ElementDataMut, ElementDataRef, ElementDataWrapper};
|
|
5
|
+
use style::servo_arc::Arc;
|
|
6
|
+
|
|
7
|
+
use crate::layout::damage::ALL_DAMAGE;
|
|
8
|
+
|
|
9
|
+
/// Interior-mutable wrapper around `Option<ElementDataWrapper>`.
|
|
10
|
+
///
|
|
11
|
+
/// Encapsulates the `UnsafeCell` so that access sites don't need raw `unsafe` blocks.
|
|
12
|
+
///
|
|
13
|
+
/// Safety relies on:
|
|
14
|
+
/// - Regular static-borrow checking for regular access, and on:
|
|
15
|
+
/// - Stylo having exclusive access to nodes during style traversals
|
|
16
|
+
/// - Stylo's thread-safe traversal model: `init`/`clear` only happen during exclusive-access phases
|
|
17
|
+
pub struct StyloData {
|
|
18
|
+
inner: UnsafeCell<Option<ElementDataWrapper>>,
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
impl Default for StyloData {
|
|
22
|
+
fn default() -> Self {
|
|
23
|
+
Self {
|
|
24
|
+
inner: UnsafeCell::new(None),
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
impl fmt::Debug for StyloData {
|
|
30
|
+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
31
|
+
f.debug_struct("StyloData").finish_non_exhaustive()
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
impl Deref for StyloData {
|
|
36
|
+
type Target = Option<ElementDataWrapper>;
|
|
37
|
+
fn deref(&self) -> &Self::Target {
|
|
38
|
+
unsafe { &*self.inner.get() }
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
impl DerefMut for StyloData {
|
|
43
|
+
fn deref_mut(&mut self) -> &mut Self::Target {
|
|
44
|
+
self.inner.get_mut()
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
impl StyloData {
|
|
49
|
+
/// Whether element data has been initialized.
|
|
50
|
+
pub fn has_data(&self) -> bool {
|
|
51
|
+
unsafe { &*self.inner.get() }.is_some()
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/// Borrow the element data immutably, if present.
|
|
55
|
+
pub fn get(&self) -> Option<ElementDataRef<'_>> {
|
|
56
|
+
self.as_ref().map(|w| w.borrow())
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/// Borrow the element data mutably, if present.
|
|
60
|
+
pub fn get_mut(&mut self) -> Option<ElementDataMut<'_>> {
|
|
61
|
+
self.as_mut().map(|w| w.borrow_mut())
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/// Initialize the element data ready for use (if it is not already initialized)
|
|
65
|
+
pub fn ensure_init_mut(&mut self) -> ElementDataMut<'_> {
|
|
66
|
+
// SAFETY:
|
|
67
|
+
// If we have exclusive access to self (implied by &mut self) then it safe to mutate self.
|
|
68
|
+
unsafe { self.ensure_init() }
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
pub fn primary_styles(&self) -> Option<StyleDataRef<'_>> {
|
|
72
|
+
let stylo_element_data = self.get();
|
|
73
|
+
if stylo_element_data
|
|
74
|
+
.as_ref()
|
|
75
|
+
.and_then(|d| d.styles.get_primary())
|
|
76
|
+
.is_some()
|
|
77
|
+
{
|
|
78
|
+
Some(StyleDataRef(self.get().unwrap()))
|
|
79
|
+
} else {
|
|
80
|
+
None
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/// Borrow the primary computed style directly (rather than the `Arc` containing it).
|
|
85
|
+
pub fn computed_styles(&self) -> Option<ComputedStyleRef<'_>> {
|
|
86
|
+
self.primary_styles().map(ComputedStyleRef)
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/// Get a mutable reference to the data
|
|
90
|
+
pub unsafe fn unsafe_stylo_only_mut(&self) -> Option<ElementDataMut<'_>> {
|
|
91
|
+
let opt = unsafe { &mut *self.inner.get() };
|
|
92
|
+
opt.as_mut().map(|w| w.borrow_mut())
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/// Initialize the element data ready for use (if it is not already initialized)
|
|
96
|
+
///
|
|
97
|
+
/// SAFETY:
|
|
98
|
+
/// There must be no outstanding borrows to this container or anything contained within it
|
|
99
|
+
/// when this method is called
|
|
100
|
+
pub unsafe fn ensure_init(&self) -> ElementDataMut<'_> {
|
|
101
|
+
if !self.has_data() {
|
|
102
|
+
unsafe { *self.inner.get() = Some(ElementDataWrapper::default()) };
|
|
103
|
+
let mut data_mut = unsafe { self.unsafe_stylo_only_mut() }.unwrap();
|
|
104
|
+
data_mut.damage = ALL_DAMAGE;
|
|
105
|
+
data_mut
|
|
106
|
+
} else {
|
|
107
|
+
unsafe { self.unsafe_stylo_only_mut() }.unwrap()
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/// Clear the element data, returning to the uninitialized state.
|
|
112
|
+
///
|
|
113
|
+
/// SAFETY:
|
|
114
|
+
/// There must be no outstanding borrows to this container or anything contained within it
|
|
115
|
+
/// when this method is called
|
|
116
|
+
pub unsafe fn clear(&self) {
|
|
117
|
+
unsafe { *self.inner.get() = None };
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
pub struct StyleDataRef<'a>(ElementDataRef<'a>);
|
|
122
|
+
|
|
123
|
+
impl Deref for StyleDataRef<'_> {
|
|
124
|
+
type Target = Arc<style::properties::ComputedValues>;
|
|
125
|
+
|
|
126
|
+
fn deref(&self) -> &Self::Target {
|
|
127
|
+
self.0.styles.get_primary().unwrap()
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/// Borrow of a node's stylo element data that `Deref`s directly to the primary
|
|
132
|
+
/// [`ComputedValues`](style::properties::ComputedValues), suitable for wrapping
|
|
133
|
+
/// in [`stylo_taffy::TaffyStyloStyle`].
|
|
134
|
+
pub struct ComputedStyleRef<'a>(StyleDataRef<'a>);
|
|
135
|
+
|
|
136
|
+
impl Deref for ComputedStyleRef<'_> {
|
|
137
|
+
type Target = style::properties::ComputedValues;
|
|
138
|
+
|
|
139
|
+
fn deref(&self) -> &Self::Target {
|
|
140
|
+
self.0.0.styles.get_primary().unwrap()
|
|
141
|
+
}
|
|
142
|
+
}
|