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.
Files changed (64) hide show
  1. package/README.md +26 -0
  2. package/THIRD_PARTY_NOTICES.md +5 -2
  3. package/docs/layout-regressions.md +65 -0
  4. package/docs/pagination-regressions.md +89 -0
  5. package/package.json +2 -1
  6. package/vendor/README.md +35 -0
  7. package/vendor/blitz-dom/Cargo.toml +113 -0
  8. package/vendor/blitz-dom/LICENSE-APACHE +176 -0
  9. package/vendor/blitz-dom/LICENSE-MIT +23 -0
  10. package/vendor/blitz-dom/assets/default.css +1142 -0
  11. package/vendor/blitz-dom/assets/moz-bullet-font.otf +0 -0
  12. package/vendor/blitz-dom/src/accessibility.rs +255 -0
  13. package/vendor/blitz-dom/src/config.rs +97 -0
  14. package/vendor/blitz-dom/src/cssom.rs +971 -0
  15. package/vendor/blitz-dom/src/debug.rs +206 -0
  16. package/vendor/blitz-dom/src/document.rs +3395 -0
  17. package/vendor/blitz-dom/src/events/driver.rs +427 -0
  18. package/vendor/blitz-dom/src/events/focus.rs +40 -0
  19. package/vendor/blitz-dom/src/events/ime.rs +31 -0
  20. package/vendor/blitz-dom/src/events/keyboard.rs +174 -0
  21. package/vendor/blitz-dom/src/events/mod.rs +315 -0
  22. package/vendor/blitz-dom/src/events/pointer.rs +849 -0
  23. package/vendor/blitz-dom/src/font_metrics.rs +177 -0
  24. package/vendor/blitz-dom/src/form.rs +463 -0
  25. package/vendor/blitz-dom/src/html.rs +38 -0
  26. package/vendor/blitz-dom/src/iframe.rs +186 -0
  27. package/vendor/blitz-dom/src/layout/construct.rs +1328 -0
  28. package/vendor/blitz-dom/src/layout/damage.rs +642 -0
  29. package/vendor/blitz-dom/src/layout/inline.rs +1160 -0
  30. package/vendor/blitz-dom/src/layout/list.rs +237 -0
  31. package/vendor/blitz-dom/src/layout/mod.rs +969 -0
  32. package/vendor/blitz-dom/src/layout/paint_tree.rs +462 -0
  33. package/vendor/blitz-dom/src/layout/replaced.rs +439 -0
  34. package/vendor/blitz-dom/src/layout/table.rs +967 -0
  35. package/vendor/blitz-dom/src/layout/text_transform.rs +979 -0
  36. package/vendor/blitz-dom/src/layout/writing_mode.rs +437 -0
  37. package/vendor/blitz-dom/src/lib.rs +160 -0
  38. package/vendor/blitz-dom/src/mutator.rs +1818 -0
  39. package/vendor/blitz-dom/src/net.rs +649 -0
  40. package/vendor/blitz-dom/src/node/attributes.rs +60 -0
  41. package/vendor/blitz-dom/src/node/custom_widget.rs +178 -0
  42. package/vendor/blitz-dom/src/node/element.rs +1106 -0
  43. package/vendor/blitz-dom/src/node/mod.rs +30 -0
  44. package/vendor/blitz-dom/src/node/node.rs +2036 -0
  45. package/vendor/blitz-dom/src/node/scrollbar.rs +237 -0
  46. package/vendor/blitz-dom/src/node/serialize.rs +225 -0
  47. package/vendor/blitz-dom/src/node/stylo_data.rs +142 -0
  48. package/vendor/blitz-dom/src/node/svg.rs +265 -0
  49. package/vendor/blitz-dom/src/node/text.rs +804 -0
  50. package/vendor/blitz-dom/src/query_selector.rs +275 -0
  51. package/vendor/blitz-dom/src/resolve.rs +474 -0
  52. package/vendor/blitz-dom/src/resolved_style.rs +797 -0
  53. package/vendor/blitz-dom/src/scrolling.rs +780 -0
  54. package/vendor/blitz-dom/src/selection.rs +122 -0
  55. package/vendor/blitz-dom/src/stylo.rs +1518 -0
  56. package/vendor/blitz-dom/src/stylo_device.rs +88 -0
  57. package/vendor/blitz-dom/src/stylo_to_cursor_icon.rs +49 -0
  58. package/vendor/blitz-dom/src/stylo_to_kurbo.rs +129 -0
  59. package/vendor/blitz-dom/src/stylo_to_parley.rs +655 -0
  60. package/vendor/blitz-dom/src/traversal.rs +600 -0
  61. package/vendor/blitz-dom/src/tree.rs +136 -0
  62. package/vendor/blitz-dom/src/url.rs +63 -0
  63. package/vendor/blitz-dom/src/util.rs +256 -0
  64. 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(&current_color),
70
+ track: track.resolve_to_absolute(&current_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
+ }