blitz-pdf 0.1.3 → 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 +6 -6
  2. package/THIRD_PARTY_NOTICES.md +5 -2
  3. package/docs/layout-regressions.md +65 -0
  4. package/docs/pagination-regressions.md +7 -6
  5. package/package.json +1 -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,642 @@
1
+ use blitz_traits::node_id::NodeId;
2
+ #[cfg(feature = "svg")]
3
+ use markup5ever::local_name;
4
+
5
+ use crate::net::ResourceHandler;
6
+ use crate::node::{Node, NodeFlags};
7
+ use crate::tree::NodeTree;
8
+ use crate::{
9
+ BaseDocument, net::ImageHandler, node::ImageResourceData, node::Status, util::ImageLayerKind,
10
+ };
11
+ use style::matching::StyleChange;
12
+ use style::properties::ComputedValues;
13
+ use style::properties::generated::longhands::position::computed_value::T as Position;
14
+ use style::selector_parser::{PseudoElement, RestyleDamage};
15
+ use style::shared_lock::StylesheetGuards;
16
+ use style::url::ComputedUrl;
17
+ use style::values::generics::image::Image as StyloImage;
18
+ use style::values::specified::align::AlignFlags;
19
+ use style::values::specified::box_::DisplayInside;
20
+ use style::values::specified::box_::DisplayOutside;
21
+ use thin_vec::ThinVec;
22
+
23
+ // Blitz-specific damage bits, in increasing order of severity above Servo's
24
+ // `RestyleDamage::RELAYOUT` (0b1111).
25
+
26
+ pub(crate) const ONLY_RELAYOUT: RestyleDamage =
27
+ RestyleDamage::from_bits_retain(0b_0000_0000_0000_1000);
28
+
29
+ /// The node's `order` changed: its flex/grid container must re-collect its
30
+ /// `layout_children` (which sorts them). Consumed by the container in
31
+ /// `propagate_damage_flags`, which marks itself `CONSTRUCT_BOX` without
32
+ /// forwarding that further up. Deliberately not part of `ALL_DAMAGE`.
33
+ pub(crate) const REORDER_CHILDREN: RestyleDamage =
34
+ RestyleDamage::from_bits_retain(0b_0000_0000_0001_0000);
35
+
36
+ pub(crate) const CONSTRUCT_BOX: RestyleDamage =
37
+ RestyleDamage::from_bits_retain(0b_0000_0000_0010_0000);
38
+ pub(crate) const CONSTRUCT_FC: RestyleDamage =
39
+ RestyleDamage::from_bits_retain(0b_0000_0000_0100_0000);
40
+ pub(crate) const CONSTRUCT_DESCENDENT: RestyleDamage =
41
+ RestyleDamage::from_bits_retain(0b_0000_0000_1000_0000);
42
+
43
+ pub(crate) const ALL_DAMAGE: RestyleDamage =
44
+ RestyleDamage::from_bits_retain(0b_0000_0000_1110_1111);
45
+
46
+ impl BaseDocument {
47
+ /// Union `RestyleDamage` up the tree and clear the layout caches of every
48
+ /// node whose subtree needs relayout.
49
+ ///
50
+ /// Damage is stored on (styled) DOM nodes, so this pass walks the DOM tree
51
+ /// (`children` + `::before`/`::after`): `display:contents` nodes and the
52
+ /// inline descendants of inline roots carry damage but are not layout
53
+ /// children. Owned anonymous boxes are refreshed through `anonymous_blocks`,
54
+ /// without revisiting their DOM children. Damage inside an anonymous box
55
+ /// also invalidates its cache by walking `layout_parent` upward.
56
+ pub(crate) fn propagate_damage_flags(&mut self, node_id: NodeId) -> RestyleDamage {
57
+ let own_damage = if let Some(data) = self.nodes[node_id]
58
+ .try_stylo_element_data_mut()
59
+ .and_then(|s| s.get_mut())
60
+ {
61
+ data.damage
62
+ } else {
63
+ return RestyleDamage::empty();
64
+ };
65
+ let mut damage = own_damage;
66
+
67
+ // Skip subtrees which contain no damage. Anonymous nodes are never
68
+ // skipped themselves because damage marking walks the DOM parent
69
+ // chain, which bypasses anonymous boxes: a damaged node's flagged
70
+ // ancestors may reach it only through an unflagged anonymous wrapper.
71
+ {
72
+ let node = &self.nodes[node_id];
73
+ if damage.is_empty() && !node.has_damaged_descendants() && !node.is_anonymous() {
74
+ return RestyleDamage::empty();
75
+ }
76
+ }
77
+
78
+ let mut damage_from_children = RestyleDamage::empty();
79
+ let children = std::mem::take(&mut self.nodes[node_id].children);
80
+ for child in children.iter() {
81
+ damage_from_children |= self.propagate_damage_flags(*child);
82
+ }
83
+ if let Some(before_id) = self.nodes[node_id].before() {
84
+ damage_from_children |= self.propagate_damage_flags(before_id);
85
+ }
86
+ if let Some(after_id) = self.nodes[node_id].after() {
87
+ damage_from_children |= self.propagate_damage_flags(after_id);
88
+ }
89
+
90
+ // A child's `order` changed: this container re-collects (and so re-sorts)
91
+ // its layout children. Only the direct parent consumes the flag, and the
92
+ // resulting `CONSTRUCT_BOX` is not forwarded further up: the container's
93
+ // own box is unchanged, so ancestors need only `RELAYOUT` (always set
94
+ // alongside `REORDER_CHILDREN`) plus `CONSTRUCT_DESCENDENT` so that a
95
+ // traversal gated on it still reaches this container.
96
+ let reorder = damage_from_children.contains(REORDER_CHILDREN)
97
+ && matches!(
98
+ self.nodes[node_id].display_constructed_as().inside(),
99
+ DisplayInside::Flex | DisplayInside::Grid
100
+ );
101
+ damage_from_children.remove(REORDER_CHILDREN);
102
+ damage |= damage_from_children;
103
+
104
+ // Put children back
105
+ self.nodes[node_id].children = children;
106
+
107
+ if damage.contains(CONSTRUCT_BOX) {
108
+ damage.insert(RestyleDamage::RELAYOUT);
109
+ }
110
+
111
+ // Compute damage to propagate to parent
112
+ let mut damage_for_parent = damage; // & RestyleDamage::RELAYOUT;
113
+
114
+ if reorder {
115
+ damage.insert(CONSTRUCT_BOX);
116
+ damage_for_parent.insert(CONSTRUCT_DESCENDENT);
117
+ }
118
+
119
+ // An inline `<svg>` is rendered from a snapshot of its subtree (with
120
+ // `currentColor` resolved) taken during box construction, so it must be
121
+ // rebuilt whenever the style of the `<svg>` or any descendant changes.
122
+ #[cfg(feature = "svg")]
123
+ if !damage.is_empty()
124
+ && self.nodes[node_id]
125
+ .data
126
+ .is_element_with_tag_name(&local_name!("svg"))
127
+ {
128
+ damage.insert(CONSTRUCT_BOX);
129
+ }
130
+
131
+ // Reconstruction replaces the anonymous boxes with freshly styled ones.
132
+ if !own_damage.is_empty() && !damage.intersects(CONSTRUCT_BOX | CONSTRUCT_FC) {
133
+ let anonymous_damage = self.refresh_anonymous_block_styles(node_id);
134
+ damage |= anonymous_damage;
135
+ damage_for_parent |= anonymous_damage;
136
+ }
137
+
138
+ let node = &mut self.nodes[node_id];
139
+
140
+ // If the node or any of it's children have been mutated or their layout styles
141
+ // have changed, then we should clear it's layout cache.
142
+ let mut invalidate_anonymous_ancestors = false;
143
+ if damage.intersects(ONLY_RELAYOUT | CONSTRUCT_BOX) {
144
+ node.invalidate_layout_cache();
145
+ invalidate_anonymous_ancestors = true;
146
+ damage.remove(ONLY_RELAYOUT);
147
+ }
148
+
149
+ // Store damage for current node
150
+ node.set_damage(damage);
151
+
152
+ // Anonymous boxes between this node and its nearest element ancestor
153
+ // are not visited by the DOM walk, so invalidate them here by walking
154
+ // `layout_parent`. Stop at the first non-anonymous node: elements are
155
+ // DOM ancestors and clear themselves when RELAYOUT reaches them.
156
+ //
157
+ // The chain is always usable: anonymous blocks are only freed by the
158
+ // reconstruct branch of `resolve_layout_children` (which immediately
159
+ // re-collects and resets `layout_parent` for every child it visits,
160
+ // recursing through the fresh anonymous blocks which are born with
161
+ // `ALL_DAMAGE`) and by node removal (the subtree leaves the document).
162
+ // So when this pass runs, a node's `layout_parent` chain is either
163
+ // current or owned by a container which reconstructs this frame. A
164
+ // stale key for a freed block yields `None` (slab keys are versioned).
165
+ if invalidate_anonymous_ancestors {
166
+ let mut current = self.nodes[node_id].layout_parent.get();
167
+ while let Some(ancestor) = current.and_then(|id| self.nodes.get_mut(id)) {
168
+ if !ancestor.is_anonymous() {
169
+ break;
170
+ }
171
+ ancestor.invalidate_layout_cache();
172
+ current = ancestor.layout_parent.get();
173
+ }
174
+ }
175
+
176
+ // let _is_fc_root = node
177
+ // .primary_styles()
178
+ // .map(|s| is_fc_root(&s))
179
+ // .unwrap_or(false);
180
+
181
+ // if damage.contains(CONSTRUCT_BOX) {
182
+ // // damage_for_parent.insert(CONSTRUCT_FC | CONSTRUCT_DESCENDENT);
183
+ // damage_for_parent.insert(CONSTRUCT_BOX);
184
+ // }
185
+
186
+ // if damage.contains(CONSTRUCT_FC) {
187
+ // damage_for_parent.insert(CONSTRUCT_DESCENDENT);
188
+ // // if !is_fc_root {
189
+ // damage_for_parent.insert(CONSTRUCT_FC);
190
+ // // }
191
+ // }
192
+
193
+ // Propagate damage to parent
194
+ damage_for_parent
195
+ }
196
+
197
+ /// Refresh owned anonymous boxes without revisiting their DOM children.
198
+ fn refresh_anonymous_block_styles(&mut self, node_id: NodeId) -> RestyleDamage {
199
+ // Skip owners with no anonymous boxes to refresh.
200
+ if self.nodes[node_id].anonymous_blocks.is_empty() {
201
+ return RestyleDamage::empty();
202
+ }
203
+
204
+ // Cascade once from the owner, sharing the result across its anonymous boxes.
205
+ let style = {
206
+ let Some(parent_style) = self.nodes[node_id].primary_styles() else {
207
+ return RestyleDamage::empty();
208
+ };
209
+ let read_guard = self.guard.read();
210
+ let guards = StylesheetGuards::same(&read_guard);
211
+ self.stylist.style_for_anonymous::<&Node>(
212
+ &guards,
213
+ &PseudoElement::ServoAnonymousBox,
214
+ &parent_style,
215
+ )
216
+ };
217
+
218
+ // Take the ownership list so we can mutate nodes without copying it.
219
+ let anonymous_blocks = std::mem::take(&mut self.nodes[node_id].anonymous_blocks);
220
+ let mut damage_for_parent = RestyleDamage::empty();
221
+ for &anonymous_id in &anonymous_blocks {
222
+ // Table fixup boxes retain their row/cell role and inherit from
223
+ // their actual table-part parent, which may be a row group or row.
224
+ let table_parent = self.nodes.get(anonymous_id).and_then(|node| {
225
+ let display = node.display_style()?;
226
+ matches!(
227
+ display.inside(),
228
+ DisplayInside::TableRow | DisplayInside::TableCell
229
+ )
230
+ .then_some((node.parent?, display))
231
+ });
232
+ let style = table_parent.map_or_else(
233
+ || style.clone(),
234
+ |(parent, display)| super::construct::anonymous_box_style(self, parent, display),
235
+ );
236
+ let (mut damage, changed) = {
237
+ // Skip boxes whose node, style data, or previous style is unavailable.
238
+ let Some(node) = self.nodes.get_mut(anonymous_id) else {
239
+ continue;
240
+ };
241
+ let Some(mut data) = node.try_stylo_element_data_mut().and_then(|s| s.get_mut())
242
+ else {
243
+ continue;
244
+ };
245
+ let Some(old_style) = data.styles.primary.as_ref() else {
246
+ continue;
247
+ };
248
+
249
+ // Compute precise damage, retaining the old style allocation if unchanged.
250
+ let diff = RestyleDamage::compute_style_difference::<&Node>(old_style, &style);
251
+ let changed = matches!(diff.change, StyleChange::Changed { .. });
252
+ if changed {
253
+ data.styles.primary = Some(style.clone());
254
+ data.set_restyled();
255
+ }
256
+ (data.damage | diff.damage, changed)
257
+ };
258
+
259
+ // Refresh nested owned boxes unless reconstruction will replace them.
260
+ if changed && !damage.intersects(CONSTRUCT_BOX | CONSTRUCT_FC) {
261
+ damage |= self.refresh_anonymous_block_styles(anonymous_id);
262
+ }
263
+
264
+ // Reconstruction also requires relayout; propagate all damage to the owner.
265
+ if damage.contains(CONSTRUCT_BOX) {
266
+ damage.insert(RestyleDamage::RELAYOUT);
267
+ }
268
+ damage_for_parent |= damage;
269
+
270
+ // Invalidate cached layout and consume only the relayout bit.
271
+ let node = &mut self.nodes[anonymous_id];
272
+ if damage.intersects(ONLY_RELAYOUT | CONSTRUCT_BOX) {
273
+ node.invalidate_layout_cache();
274
+ damage.remove(ONLY_RELAYOUT);
275
+ }
276
+
277
+ // Store remaining damage and mark the ancestor path when needed.
278
+ node.set_damage(damage);
279
+ if !damage.is_empty() {
280
+ node.mark_damaged();
281
+ }
282
+ }
283
+
284
+ // Restore the ownership list and return the combined damage.
285
+ self.nodes[node_id].anonymous_blocks = anonymous_blocks;
286
+ damage_for_parent
287
+ }
288
+
289
+ /// Mark every node in the document with `ALL_DAMAGE` (non-incremental
290
+ /// mode). Iterates the node slotmap directly rather than walking the tree,
291
+ /// which is cheaper and also reaches pseudo-elements and anonymous boxes
292
+ /// without special-casing them. Detached nodes get marked too, which is
293
+ /// harmless: they are reconstructed on re-attachment anyway.
294
+ pub(crate) fn mark_all_damaged(&mut self) {
295
+ for (_, node) in self.nodes.iter_mut() {
296
+ node.insert_damage(ALL_DAMAGE);
297
+ }
298
+ }
299
+
300
+ /// Clear damage and the `damaged_descendants`/`dirty_descendants` flags
301
+ /// on all nodes which may carry them, using the `damaged_descendants`
302
+ /// flags to skip clean subtrees (mirroring `propagate_damage_flags`).
303
+ ///
304
+ /// Every node is visited exactly once: DOM nodes through their parent's
305
+ /// `children`, pseudo-elements through their owner's `before`/`after`,
306
+ /// anonymous boxes through their owner's `anonymous_blocks`. An anonymous
307
+ /// box's `children` are DOM nodes already reached through its owner, so
308
+ /// they are not recursed into.
309
+ pub(crate) fn clear_damage_and_dirty_flags(&mut self, node_id: NodeId) {
310
+ // Anonymous boxes can be freed during construction while still
311
+ // listed in their owner's `anonymous_blocks`.
312
+ let Some(node) = self.nodes.get_mut(node_id) else {
313
+ return;
314
+ };
315
+ let is_anonymous = node.is_anonymous();
316
+ let has_damage = node.damage().is_some_and(|d| !d.is_empty());
317
+ if !has_damage && !node.has_damaged_descendants() && !is_anonymous {
318
+ return;
319
+ }
320
+ let anonymous_blocks = std::mem::take(&mut node.anonymous_blocks);
321
+ let children = std::mem::take(&mut node.children);
322
+ let (before, after) = (node.before(), node.after());
323
+
324
+ for anon_id in anonymous_blocks.iter() {
325
+ self.clear_damage_and_dirty_flags(*anon_id);
326
+ }
327
+ if !is_anonymous {
328
+ for child in children.iter() {
329
+ self.clear_damage_and_dirty_flags(*child);
330
+ }
331
+ }
332
+ if let Some(before_id) = before {
333
+ self.clear_damage_and_dirty_flags(before_id);
334
+ }
335
+ if let Some(after_id) = after {
336
+ self.clear_damage_and_dirty_flags(after_id);
337
+ }
338
+
339
+ let node = &mut self.nodes[node_id];
340
+ node.anonymous_blocks = anonymous_blocks;
341
+ node.children = children;
342
+ node.clear_damage_mut();
343
+ node.unset_damaged_descendants();
344
+ node.unset_dirty_descendants();
345
+ }
346
+ }
347
+
348
+ // fn is_fc_root(style: &ComputedValues) -> bool {
349
+ // let display = style.clone_display();
350
+ // let display_inside = display.inside();
351
+
352
+ // match display_inside {
353
+ // DisplayInside::Flow => {
354
+ // // Depends on parent context
355
+ // false
356
+ // }
357
+
358
+ // DisplayInside::None => true,
359
+ // DisplayInside::FlowRoot => true,
360
+ // DisplayInside::Flex => true,
361
+ // DisplayInside::Grid => true,
362
+ // DisplayInside::Table => true,
363
+ // DisplayInside::TableCell => true,
364
+
365
+ // DisplayInside::Contents => false,
366
+ // DisplayInside::TableRowGroup => false,
367
+ // DisplayInside::TableColumn => false,
368
+ // DisplayInside::TableColumnGroup => false,
369
+ // DisplayInside::TableHeaderGroup => false,
370
+ // DisplayInside::TableFooterGroup => false,
371
+ // DisplayInside::TableRow => false,
372
+ // }
373
+ // }
374
+
375
+ pub(crate) fn compute_layout_damage(old: &ComputedValues, new: &ComputedValues) -> RestyleDamage {
376
+ let box_tree_needs_rebuild = || {
377
+ let old_box = old.get_box();
378
+ let new_box = new.get_box();
379
+
380
+ if old_box.display != new_box.display
381
+ || old_box.float != new_box.float
382
+ || old_box.position != new_box.position
383
+ || old_box.contain != new_box.contain
384
+ || old.clone_visibility() != new.clone_visibility()
385
+ {
386
+ return true;
387
+ }
388
+
389
+ if old.get_font() != new.get_font() {
390
+ return true;
391
+ }
392
+
393
+ if new_box.display.outside() == DisplayOutside::Block
394
+ && new_box.display.inside() == DisplayInside::Flow
395
+ {
396
+ let alignment_establishes_new_block_formatting_context = |style: &ComputedValues| {
397
+ style.get_position().align_content.primary() != AlignFlags::NORMAL
398
+ };
399
+
400
+ let old_column = old.get_column();
401
+ let new_column = new.get_column();
402
+ if old_box.overflow_x.is_scrollable() != new_box.overflow_x.is_scrollable()
403
+ || old_column.is_multicol() != new_column.is_multicol()
404
+ || old_column.column_span != new_column.column_span
405
+ || alignment_establishes_new_block_formatting_context(old)
406
+ != alignment_establishes_new_block_formatting_context(new)
407
+ {
408
+ return true;
409
+ }
410
+ }
411
+
412
+ if old_box.display.is_list_item() {
413
+ let old_list = old.get_list();
414
+ let new_list = new.get_list();
415
+ if old_list.list_style_position != new_list.list_style_position
416
+ || old_list.list_style_image != new_list.list_style_image
417
+ || (new_list.list_style_image == StyloImage::None
418
+ && old_list.list_style_type != new_list.list_style_type)
419
+ {
420
+ return true;
421
+ }
422
+ }
423
+
424
+ if new.is_pseudo_style() && old.get_counters().content != new.get_counters().content {
425
+ return true;
426
+ }
427
+
428
+ false
429
+ };
430
+
431
+ let text_shaping_needs_recollect = || {
432
+ if old.clone_direction() != new.clone_direction()
433
+ || old.clone_unicode_bidi() != new.clone_unicode_bidi()
434
+ {
435
+ return true;
436
+ }
437
+
438
+ let old_text = old.get_inherited_text();
439
+ let new_text = new.get_inherited_text();
440
+ if !std::ptr::eq(old_text, new_text)
441
+ && (old_text.white_space_collapse != new_text.white_space_collapse
442
+ || old_text.text_transform != new_text.text_transform
443
+ || old_text.word_break != new_text.word_break
444
+ || old_text.line_break != new_text.line_break
445
+ || old_text.overflow_wrap != new_text.overflow_wrap
446
+ || old_text.letter_spacing != new_text.letter_spacing
447
+ || old_text.word_spacing != new_text.word_spacing
448
+ || old_text.text_rendering != new_text.text_rendering)
449
+ {
450
+ return true;
451
+ }
452
+
453
+ false
454
+ };
455
+
456
+ #[allow(
457
+ clippy::if_same_then_else,
458
+ reason = "these branches will soon be different"
459
+ )]
460
+ if box_tree_needs_rebuild() {
461
+ ALL_DAMAGE
462
+ } else if text_shaping_needs_recollect() {
463
+ ALL_DAMAGE
464
+ } else if old.get_position().order != new.get_position().order
465
+ // `position` is unchanged here (else the box tree would be rebuilt), so
466
+ // checking the new style suffices. Out-of-flow children are not flex/grid
467
+ // items: `Node::order()` keys them as 0 regardless of `order`.
468
+ && !matches!(
469
+ new.get_box().position,
470
+ Position::Absolute | Position::Fixed
471
+ )
472
+ {
473
+ RestyleDamage::RELAYOUT | REORDER_CHILDREN
474
+ } else {
475
+ // This element needs to be laid out again, but does not have any damage to
476
+ // its box. In the future, we will distinguish between types of damage to the
477
+ // fragment as well.
478
+ RestyleDamage::RELAYOUT
479
+ }
480
+ }
481
+
482
+ /// Stable-sort a flex/grid container's freshly constructed `layout_children`
483
+ /// (in document order) by `order`. Skips the sort when every child has the
484
+ /// default `order` (0).
485
+ pub(crate) fn sort_layout_children_by_order(
486
+ nodes: &NodeTree,
487
+ layout_children: &mut ThinVec<NodeId>,
488
+ ) {
489
+ if layout_children.iter().all(|id| nodes[*id].order() == 0) {
490
+ return;
491
+ }
492
+ layout_children.sort_by_cached_key(|id| nodes[*id].order());
493
+ }
494
+
495
+ impl BaseDocument {
496
+ pub(crate) fn invalidate_inline_contexts(&mut self) {
497
+ let scale = self.viewport.scale();
498
+
499
+ let font_ctx = &self.font_ctx;
500
+ let layout_ctx = &mut self.layout_ctx;
501
+
502
+ let mut anon_nodes = Vec::new();
503
+
504
+ for (_, node) in self.nodes.iter_mut() {
505
+ if !(node.flags.contains(NodeFlags::IS_IN_DOCUMENT)) {
506
+ continue;
507
+ }
508
+
509
+ let Some(element) = node.data.downcast_element_mut() else {
510
+ continue;
511
+ };
512
+
513
+ if element.inline_layout_data.is_some() {
514
+ if node.is_anonymous() {
515
+ anon_nodes.push(node.id);
516
+ } else {
517
+ node.insert_damage(ALL_DAMAGE);
518
+ }
519
+ } else if let Some(input) = element.text_input_data_mut() {
520
+ input.editor.set_scale(scale);
521
+ let mut font_ctx = font_ctx.lock().unwrap();
522
+ input.editor.refresh_layout(&mut font_ctx, layout_ctx);
523
+ node.insert_damage(ONLY_RELAYOUT);
524
+ }
525
+ }
526
+
527
+ for node_id in anon_nodes {
528
+ if let Some(parent_id) = *(self.nodes[node_id].layout_parent.get_mut()) {
529
+ self.nodes[parent_id].insert_damage(ALL_DAMAGE);
530
+ }
531
+ }
532
+ }
533
+
534
+ /// Flush the image layers of nodes whose style changed during the last
535
+ /// style traversal (or whose pseudo-element boxes were (re)constructed).
536
+ pub(crate) fn flush_pending_style_images(&mut self) {
537
+ let mut pending = std::mem::take(&mut self.pending_style_image_nodes);
538
+ pending.sort_unstable();
539
+ pending.dedup();
540
+ for node_id in pending {
541
+ // Anonymous boxes (including pseudo-elements) can be removed from
542
+ // the slab between queueing and flushing; skip stale IDs.
543
+ if !self.nodes.contains_key(node_id) {
544
+ continue;
545
+ }
546
+ self.flush_image_layers_from_style(node_id, ImageLayerKind::Background);
547
+ self.flush_image_layers_from_style(node_id, ImageLayerKind::Mask);
548
+ }
549
+ }
550
+
551
+ /// Flush a CSS image layer list (`background-image` or `mask-image`) from style
552
+ /// to dedicated storage on the node, fetching any images which are not yet loaded.
553
+ fn flush_image_layers_from_style(&mut self, node_id: NodeId, kind: ImageLayerKind) {
554
+ let doc_id = self.id();
555
+ let node = self.nodes.get_mut(node_id).unwrap();
556
+ // Clone the primary style `Arc` into an owned value so the immutable
557
+ // borrow of `node` (held by the stylo element data guard) is released
558
+ // before we take a mutable borrow of `node.data` below.
559
+ let style = {
560
+ let stylo_element_data = node.try_stylo_element_data().and_then(|s| s.get());
561
+ let primary_styles = stylo_element_data
562
+ .as_ref()
563
+ .and_then(|data| data.styles.get_primary());
564
+ let Some(style) = primary_styles else {
565
+ return;
566
+ };
567
+ style.clone()
568
+ };
569
+ let Some(elem) = node.data.downcast_element_mut() else {
570
+ return;
571
+ };
572
+
573
+ let (style_images, elem_images) = match kind {
574
+ ImageLayerKind::Background => (
575
+ &style.get_background().background_image.0,
576
+ &mut elem.background_images,
577
+ ),
578
+ ImageLayerKind::Mask => (&style.get_svg().mask_image.0, &mut elem.mask_images),
579
+ };
580
+
581
+ let len = style_images.len();
582
+ elem_images.resize(len, None);
583
+
584
+ for idx in 0..len {
585
+ let style_image = &style_images[idx];
586
+ let new_image = match style_image {
587
+ StyloImage::Url(ComputedUrl::Valid(new_url)) => {
588
+ let old_image = elem_images[idx].as_ref();
589
+ let old_image_url = old_image.map(|data| &data.url);
590
+ if old_image_url.is_some_and(|old_url| **new_url == **old_url) {
591
+ continue;
592
+ }
593
+
594
+ // Check cache first
595
+ let url_str = new_url.as_str();
596
+ if let Some(cached_image) = self.image_cache.get(url_str) {
597
+ #[cfg(feature = "tracing")]
598
+ tracing::info!("Loading image {url_str} from cache");
599
+ Some(ImageResourceData {
600
+ url: new_url.clone(),
601
+ status: Status::Ok,
602
+ image: cached_image.clone(),
603
+ })
604
+ } else if let Some(waiting_list) = self.pending_images.get_mut(url_str) {
605
+ // Image is already being fetched, queue this node
606
+ #[cfg(feature = "tracing")]
607
+ tracing::info!("Image {url_str} already pending, queueing node {node_id}");
608
+ waiting_list.push((node_id, kind.image_type(idx)));
609
+ Some(ImageResourceData::new(new_url.clone()))
610
+ } else {
611
+ // Start fetch and track as pending
612
+ #[cfg(feature = "tracing")]
613
+ tracing::info!("Fetching image {url_str}");
614
+ self.pending_images
615
+ .insert(url_str.to_string(), vec![(node_id, kind.image_type(idx))]);
616
+
617
+ self.net_provider.fetch(
618
+ doc_id,
619
+ crate::net::stamped_request(
620
+ (**new_url).clone(),
621
+ self.abort_signal.as_ref(),
622
+ ),
623
+ ResourceHandler::boxed(
624
+ self.tx.clone(),
625
+ doc_id,
626
+ None, // Don't pass node_id, we'll handle via pending_images
627
+ self.shell_provider.clone(),
628
+ ImageHandler::new(kind.image_type(idx)),
629
+ ),
630
+ );
631
+
632
+ Some(ImageResourceData::new(new_url.clone()))
633
+ }
634
+ }
635
+ _ => None,
636
+ };
637
+
638
+ // Element will always exist due to resize_with above
639
+ elem_images[idx] = new_image;
640
+ }
641
+ }
642
+ }