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.
- package/README.md +6 -6
- package/THIRD_PARTY_NOTICES.md +5 -2
- package/docs/layout-regressions.md +65 -0
- package/docs/pagination-regressions.md +7 -6
- package/package.json +1 -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,462 @@
|
|
|
1
|
+
//! Construction of the paint tree (`Node::paint_children` and
|
|
2
|
+
//! `Node::stacking_context`) from the laid-out box tree.
|
|
3
|
+
//!
|
|
4
|
+
//! The pass runs once per frame after layout and builds *topology* only: which
|
|
5
|
+
//! node ids each node paints itself and which z-indexed descendants are hoisted
|
|
6
|
+
//! to the nearest stacking-context root. Geometry (a hoisted box's offset from
|
|
7
|
+
//! its stacking-context root) is derived at use time from `final_layout()` and
|
|
8
|
+
//! memoised per geometry generation, so replaying a cached list for a clean
|
|
9
|
+
//! subtree can never paint or hit-test a box where layout no longer puts it.
|
|
10
|
+
|
|
11
|
+
use blitz_traits::node_id::NodeId;
|
|
12
|
+
use std::cell::Cell;
|
|
13
|
+
use std::ops::Range;
|
|
14
|
+
use thin_vec::ThinVec;
|
|
15
|
+
|
|
16
|
+
use crate::layout::damage::ALL_DAMAGE;
|
|
17
|
+
use crate::tree::NodeTree;
|
|
18
|
+
use crate::{BaseDocument, Node};
|
|
19
|
+
use style::properties::generated::longhands::position::computed_value::T as Position;
|
|
20
|
+
use style::selector_parser::RestyleDamage;
|
|
21
|
+
use style::values::computed::Float;
|
|
22
|
+
use style::values::specified::box_::DisplayInside;
|
|
23
|
+
use taffy::{Point, Rect};
|
|
24
|
+
|
|
25
|
+
/// Offset of the hoisted child `child_id` from the border box of its
|
|
26
|
+
/// stacking-context root `sc_root_id`, excluding the child's own
|
|
27
|
+
/// `final_layout().location`: the sum of `location - scroll_offset` of every
|
|
28
|
+
/// box strictly between the two on the child's positioning chain.
|
|
29
|
+
///
|
|
30
|
+
/// Walks `Node::containing_block` (the out-of-flow containing block for boxes
|
|
31
|
+
/// hoisted by Taffy, otherwise `layout_parent`, which is set for every layout
|
|
32
|
+
/// child by `resolve_layout_children`, anonymous blocks included).
|
|
33
|
+
pub fn hoisted_child_position(tree: &NodeTree, sc_root_id: NodeId, child_id: NodeId) -> Point<f32> {
|
|
34
|
+
let child = &tree[child_id];
|
|
35
|
+
let mut position = Point::ZERO;
|
|
36
|
+
let mut current = child.containing_block();
|
|
37
|
+
|
|
38
|
+
// A fixed-position box whose containing block is the root element is
|
|
39
|
+
// positioned against the viewport and does not scroll with it: cancel the
|
|
40
|
+
// scroll the walk below (or the stacking-context root itself) applies.
|
|
41
|
+
// A fixed box contained by a transformed ancestor scrolls with it.
|
|
42
|
+
if child.taffy_position() == taffy::Position::Fixed
|
|
43
|
+
&& let Some(cb) = current.and_then(|id| tree.get(id))
|
|
44
|
+
&& cb.containing_block().is_none()
|
|
45
|
+
{
|
|
46
|
+
let scroll_offset = *cb.scroll_offset();
|
|
47
|
+
let viewport_scroll = tree.viewport_scroll();
|
|
48
|
+
position.x += scroll_offset.x as f32 + viewport_scroll.x as f32;
|
|
49
|
+
position.y += scroll_offset.y as f32 + viewport_scroll.y as f32;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
while let Some(id) = current {
|
|
53
|
+
if id == sc_root_id {
|
|
54
|
+
break;
|
|
55
|
+
}
|
|
56
|
+
let Some(node) = tree.get(id) else {
|
|
57
|
+
break;
|
|
58
|
+
};
|
|
59
|
+
let location = node.final_layout().location;
|
|
60
|
+
let scroll_offset = *node.scroll_offset();
|
|
61
|
+
position.x += location.x - scroll_offset.x as f32;
|
|
62
|
+
position.y += location.y - scroll_offset.y as f32;
|
|
63
|
+
current = node.containing_block();
|
|
64
|
+
}
|
|
65
|
+
position
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/// A child with a z_index that is hoisted up to it's containing Stacking Context for paint purposes
|
|
69
|
+
#[derive(Debug, Clone)]
|
|
70
|
+
pub struct HoistedPaintChild {
|
|
71
|
+
pub node_id: NodeId,
|
|
72
|
+
pub z_index: i32,
|
|
73
|
+
/// `(geometry generation, offset from the stacking-context root)`, see
|
|
74
|
+
/// [`Self::position`]. Generation `0` is never current.
|
|
75
|
+
position: Cell<(u64, Point<f32>)>,
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
impl HoistedPaintChild {
|
|
79
|
+
fn new(node_id: NodeId, z_index: i32) -> Self {
|
|
80
|
+
Self {
|
|
81
|
+
node_id,
|
|
82
|
+
z_index,
|
|
83
|
+
position: Cell::new((0, Point::ZERO)),
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/// The child's offset from the border box of its stacking-context root
|
|
88
|
+
/// (`sc_root_id`), excluding the child's own `final_layout().location`.
|
|
89
|
+
/// Computed at most once per geometry generation.
|
|
90
|
+
pub fn position(&self, tree: &NodeTree, sc_root_id: NodeId) -> Point<f32> {
|
|
91
|
+
let generation = tree.geometry_generation();
|
|
92
|
+
let (stamp, cached) = self.position.get();
|
|
93
|
+
if stamp == generation {
|
|
94
|
+
return cached;
|
|
95
|
+
}
|
|
96
|
+
let position = hoisted_child_position(tree, sc_root_id, self.node_id);
|
|
97
|
+
self.position.set((generation, position));
|
|
98
|
+
position
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/// Whether [`Self::position`] is memoised for the current geometry generation.
|
|
102
|
+
pub fn position_is_cached(&self, tree: &NodeTree) -> bool {
|
|
103
|
+
self.position.get().0 == tree.geometry_generation()
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
#[derive(Debug)]
|
|
108
|
+
pub struct StackingContext {
|
|
109
|
+
pub children: ThinVec<HoistedPaintChild>,
|
|
110
|
+
/// The number of hoisted point children with negative z_index
|
|
111
|
+
pub negative_z_count: u32,
|
|
112
|
+
/// `(geometry generation, bounding box of the hoisted children)`, see
|
|
113
|
+
/// [`Self::hoisted_content_bbox`].
|
|
114
|
+
hoisted_content_bbox: Cell<(u64, Rect<f32>)>,
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
impl StackingContext {
|
|
118
|
+
/// Build a sorted stacking context from the hoisted entries collected for it.
|
|
119
|
+
fn from_children(children: ThinVec<HoistedPaintChild>) -> Self {
|
|
120
|
+
let mut sc = Self {
|
|
121
|
+
children,
|
|
122
|
+
negative_z_count: 0,
|
|
123
|
+
hoisted_content_bbox: Cell::new((0, Rect::ZERO)),
|
|
124
|
+
};
|
|
125
|
+
sc.sort();
|
|
126
|
+
sc
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/// Bounding box (in CSS pixels relative to the stacking-context root
|
|
130
|
+
/// `sc_root_id`, unscrolled) of the scrollable overflow of all hoisted
|
|
131
|
+
/// children, including their transforms. `scale` is the device pixel
|
|
132
|
+
/// ratio `scrollable_overflow` is stored in. Computed at most once per
|
|
133
|
+
/// geometry generation.
|
|
134
|
+
pub fn hoisted_content_bbox(
|
|
135
|
+
&self,
|
|
136
|
+
tree: &NodeTree,
|
|
137
|
+
sc_root_id: NodeId,
|
|
138
|
+
scale: f64,
|
|
139
|
+
) -> Rect<f32> {
|
|
140
|
+
let generation = tree.geometry_generation();
|
|
141
|
+
let (stamp, cached) = self.hoisted_content_bbox.get();
|
|
142
|
+
if stamp == generation {
|
|
143
|
+
return cached;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
let child_rect = |child: &HoistedPaintChild| -> Rect<f32> {
|
|
147
|
+
let node = &tree[child.node_id];
|
|
148
|
+
let position = child.position(tree, sc_root_id);
|
|
149
|
+
let location = node.final_layout().location;
|
|
150
|
+
let mut overflow = *node.scrollable_overflow();
|
|
151
|
+
if let Some(transform) = node.transform().as_deref() {
|
|
152
|
+
overflow = transform.transform_rect_bbox(overflow);
|
|
153
|
+
}
|
|
154
|
+
let left = position.x + location.x;
|
|
155
|
+
let top = position.y + location.y;
|
|
156
|
+
Rect {
|
|
157
|
+
left: left + (overflow.x0 / scale) as f32,
|
|
158
|
+
top: top + (overflow.y0 / scale) as f32,
|
|
159
|
+
right: left + (overflow.x1 / scale) as f32,
|
|
160
|
+
bottom: top + (overflow.y1 / scale) as f32,
|
|
161
|
+
}
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
let mut area = Rect::ZERO;
|
|
165
|
+
if let Some((first, rest)) = self.children.split_first() {
|
|
166
|
+
area = child_rect(first);
|
|
167
|
+
for child in rest {
|
|
168
|
+
let pos = child_rect(child);
|
|
169
|
+
area.left = area.left.min(pos.left);
|
|
170
|
+
area.top = area.top.min(pos.top);
|
|
171
|
+
area.right = area.right.max(pos.right);
|
|
172
|
+
area.bottom = area.bottom.max(pos.bottom);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
self.hoisted_content_bbox.set((generation, area));
|
|
176
|
+
area
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
pub fn sort(&mut self) {
|
|
180
|
+
self.children.sort_by_key(|c| c.z_index);
|
|
181
|
+
self.negative_z_count = self.children.iter().take_while(|c| c.z_index < 0).count() as u32;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
pub fn neg_z_range(&self) -> Range<usize> {
|
|
185
|
+
0..(self.negative_z_count as usize)
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
pub fn pos_z_range(&self) -> Range<usize> {
|
|
189
|
+
(self.negative_z_count as usize)..self.children.len()
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
pub fn neg_z_hoisted_children(
|
|
193
|
+
&self,
|
|
194
|
+
) -> impl ExactSizeIterator<Item = &HoistedPaintChild> + DoubleEndedIterator {
|
|
195
|
+
self.children[self.neg_z_range()].iter()
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
pub fn pos_z_hoisted_children(
|
|
199
|
+
&self,
|
|
200
|
+
) -> impl ExactSizeIterator<Item = &HoistedPaintChild> + DoubleEndedIterator {
|
|
201
|
+
self.children[self.pos_z_range()].iter()
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
impl BaseDocument {
|
|
206
|
+
/// Rebuild `paint_children` and `stacking_context` for the tree rooted at
|
|
207
|
+
/// `root_id` from the laid-out box tree.
|
|
208
|
+
///
|
|
209
|
+
/// Each node's layout children are placed either in its `paint_children`
|
|
210
|
+
/// (sorted by paint order) or, when they have a non-zero `z-index` and are
|
|
211
|
+
/// positioned or flex/grid items, in the nearest ancestor stacking context.
|
|
212
|
+
///
|
|
213
|
+
/// Subtrees whose stored damage lacks `REBUILD_STACKING_CONTEXT` are skipped: their
|
|
214
|
+
/// own lists are still valid and the entries they contributed to the
|
|
215
|
+
/// enclosing stacking context are replayed from `Node::sc_contribution_cache`.
|
|
216
|
+
pub(crate) fn build_paint_tree(&mut self, root_id: NodeId) {
|
|
217
|
+
self.build_paint_tree_impl(root_id, None);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/// `stacking_context` is the entry list of the nearest ancestor stacking
|
|
221
|
+
/// context, or `None` when `node_id` is itself a stacking-context root.
|
|
222
|
+
fn build_paint_tree_impl(
|
|
223
|
+
&mut self,
|
|
224
|
+
node_id: NodeId,
|
|
225
|
+
stacking_context: Option<&mut ThinVec<HoistedPaintChild>>,
|
|
226
|
+
) {
|
|
227
|
+
{
|
|
228
|
+
let node = &self.nodes[node_id];
|
|
229
|
+
// `propagate_damage_flags` stores the union of a node's own and
|
|
230
|
+
// its descendants' damage on the node, so the absence of
|
|
231
|
+
// `REBUILD_STACKING_CONTEXT` (which every heavier level implies)
|
|
232
|
+
// means nothing in the subtree can have changed a paint list.
|
|
233
|
+
// Anonymous boxes are never skipped: damage marking walks the DOM
|
|
234
|
+
// parent chain, which bypasses them.
|
|
235
|
+
//
|
|
236
|
+
// Whether a node is a stacking-context root also depends on its
|
|
237
|
+
// parent (z-indexed flex/grid items), which does not damage the
|
|
238
|
+
// node itself, so the role the caller resolved must match the one
|
|
239
|
+
// this node was last built with.
|
|
240
|
+
let role_unchanged = node.stacking_context.is_some() == stacking_context.is_none();
|
|
241
|
+
let clean = role_unchanged
|
|
242
|
+
&& !node
|
|
243
|
+
.damage()
|
|
244
|
+
.unwrap_or(ALL_DAMAGE)
|
|
245
|
+
.contains(RestyleDamage::REBUILD_STACKING_CONTEXT)
|
|
246
|
+
&& !node.is_anonymous();
|
|
247
|
+
if clean {
|
|
248
|
+
if let Some(stacking_context) = stacking_context {
|
|
249
|
+
stacking_context.extend(node.sc_contribution_cache.borrow().iter().cloned());
|
|
250
|
+
}
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
let Some(display) = self.nodes[node_id].display_style() else {
|
|
256
|
+
return;
|
|
257
|
+
};
|
|
258
|
+
|
|
259
|
+
// Entries hoisted out of this subtree are collected directly into the
|
|
260
|
+
// storage that will hold them afterwards: the node's own stacking
|
|
261
|
+
// context for a root, otherwise its contribution cache (from which
|
|
262
|
+
// they are copied into the enclosing stacking context below).
|
|
263
|
+
let mut entries = {
|
|
264
|
+
let node = &mut self.nodes[node_id];
|
|
265
|
+
let mut entries = match stacking_context {
|
|
266
|
+
None => node
|
|
267
|
+
.stacking_context
|
|
268
|
+
.take()
|
|
269
|
+
.map(|sc| sc.children)
|
|
270
|
+
.unwrap_or_default(),
|
|
271
|
+
Some(_) => std::mem::take(&mut *node.sc_contribution_cache.borrow_mut()),
|
|
272
|
+
};
|
|
273
|
+
entries.clear();
|
|
274
|
+
entries
|
|
275
|
+
};
|
|
276
|
+
|
|
277
|
+
let children = self.nodes[node_id].layout_children.borrow_mut().take();
|
|
278
|
+
if let Some(children) = children {
|
|
279
|
+
let is_flex_or_grid =
|
|
280
|
+
matches!(display.inside(), DisplayInside::Flex | DisplayInside::Grid);
|
|
281
|
+
|
|
282
|
+
// Out-of-flow boxes are laid out (and painted) relative to their
|
|
283
|
+
// containing block, as recorded by Taffy's out-of-flow pass in the
|
|
284
|
+
// containing block's `hoisted_children`. A box whose containing
|
|
285
|
+
// block is not its layout parent is owned by the containing block;
|
|
286
|
+
// its layout parent neither visits nor lists it.
|
|
287
|
+
let owned_here = |doc: &Self, child_id: NodeId| -> bool {
|
|
288
|
+
doc.nodes[child_id]
|
|
289
|
+
.oof_containing_block
|
|
290
|
+
.get()
|
|
291
|
+
.is_none_or(|cb| cb == node_id)
|
|
292
|
+
};
|
|
293
|
+
// Boxes hoisted to this node from further down the tree, keyed by
|
|
294
|
+
// the index of the layout child they descend from: they are visited
|
|
295
|
+
// and listed in tree order, directly after that child (CSS 2.1
|
|
296
|
+
// Appendix E). Boxes whose layout-parent chain does not lead through
|
|
297
|
+
// a layout child of this node come after all of them.
|
|
298
|
+
let mut hoisted_past_parent: Vec<(usize, NodeId)> = self.nodes[node_id]
|
|
299
|
+
.hoisted_children
|
|
300
|
+
.borrow()
|
|
301
|
+
.iter()
|
|
302
|
+
.copied()
|
|
303
|
+
.filter(|&id| {
|
|
304
|
+
self.nodes
|
|
305
|
+
.get(id)
|
|
306
|
+
.is_some_and(|n| n.layout_parent.get() != Some(node_id))
|
|
307
|
+
})
|
|
308
|
+
.map(|id| {
|
|
309
|
+
let anchor = self
|
|
310
|
+
.layout_child_containing(node_id, id)
|
|
311
|
+
.and_then(|anchor| children.iter().position(|&c| c == anchor))
|
|
312
|
+
.unwrap_or(children.len());
|
|
313
|
+
(anchor, id)
|
|
314
|
+
})
|
|
315
|
+
.collect();
|
|
316
|
+
hoisted_past_parent.sort_by_key(|&(anchor, _)| anchor);
|
|
317
|
+
let mut next_hoisted = 0;
|
|
318
|
+
|
|
319
|
+
let mut paint_children = self.nodes[node_id]
|
|
320
|
+
.paint_children
|
|
321
|
+
.borrow_mut()
|
|
322
|
+
.take()
|
|
323
|
+
.unwrap_or_default();
|
|
324
|
+
paint_children.clear();
|
|
325
|
+
paint_children.reserve(children.len() + hoisted_past_parent.len());
|
|
326
|
+
|
|
327
|
+
for (index, &child_id) in children.iter().enumerate() {
|
|
328
|
+
if owned_here(self, child_id) {
|
|
329
|
+
let is_sc_root = self.nodes[child_id].is_stacking_context_root(is_flex_or_grid);
|
|
330
|
+
self.build_paint_tree_impl(
|
|
331
|
+
child_id,
|
|
332
|
+
match is_sc_root {
|
|
333
|
+
true => None,
|
|
334
|
+
false => Some(&mut entries),
|
|
335
|
+
},
|
|
336
|
+
);
|
|
337
|
+
|
|
338
|
+
// Only stacking contexts with a non-zero z-index are lifted
|
|
339
|
+
// out of tree order into the enclosing stacking context's
|
|
340
|
+
// z-sorted list (CSS 2.1 Appendix E).
|
|
341
|
+
let z_index = self.nodes[child_id].z_index();
|
|
342
|
+
if is_sc_root && z_index != 0 {
|
|
343
|
+
entries.push(HoistedPaintChild::new(child_id, z_index))
|
|
344
|
+
} else {
|
|
345
|
+
paint_children.push(child_id);
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
while let Some(&(anchor, hoisted_id)) = hoisted_past_parent.get(next_hoisted)
|
|
350
|
+
&& anchor == index
|
|
351
|
+
{
|
|
352
|
+
self.attach_hoisted_past_parent(hoisted_id, &mut paint_children, &mut entries);
|
|
353
|
+
next_hoisted += 1;
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
for &(_, hoisted_id) in &hoisted_past_parent[next_hoisted..] {
|
|
357
|
+
self.attach_hoisted_past_parent(hoisted_id, &mut paint_children, &mut entries);
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
paint_children
|
|
361
|
+
.sort_by_cached_key(|id| node_to_paint_order(&self.nodes[*id], is_flex_or_grid));
|
|
362
|
+
|
|
363
|
+
let node = &self.nodes[node_id];
|
|
364
|
+
*node.paint_children.borrow_mut() = Some(paint_children);
|
|
365
|
+
*node.layout_children.borrow_mut() = Some(children);
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
let node = &mut self.nodes[node_id];
|
|
369
|
+
if let Some(stacking_context) = stacking_context {
|
|
370
|
+
node.stacking_context = None;
|
|
371
|
+
stacking_context.extend(entries.iter().cloned());
|
|
372
|
+
*node.sc_contribution_cache.borrow_mut() = entries;
|
|
373
|
+
} else {
|
|
374
|
+
node.sc_contribution_cache.borrow_mut().clear();
|
|
375
|
+
node.stacking_context = Some(Box::new(StackingContext::from_children(entries)));
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
/// The layout child of `node_id` whose subtree contains `id`, following
|
|
380
|
+
/// `layout_parent`.
|
|
381
|
+
fn layout_child_containing(&self, node_id: NodeId, id: NodeId) -> Option<NodeId> {
|
|
382
|
+
let mut current = id;
|
|
383
|
+
loop {
|
|
384
|
+
let parent = self.nodes.get(current)?.layout_parent.get()?;
|
|
385
|
+
if parent == node_id {
|
|
386
|
+
return Some(current);
|
|
387
|
+
}
|
|
388
|
+
current = parent;
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
/// Visit an out-of-flow box hoisted to its containing block past its
|
|
393
|
+
/// layout parent and place it in the paint lists: z-indexed boxes go to
|
|
394
|
+
/// the enclosing stacking context like any other z-indexed positioned
|
|
395
|
+
/// child, the rest join `paint_children` at the same paint level as other
|
|
396
|
+
/// positioned boxes (the caller's stable sort keeps tree order).
|
|
397
|
+
fn attach_hoisted_past_parent(
|
|
398
|
+
&mut self,
|
|
399
|
+
child_id: NodeId,
|
|
400
|
+
paint_children: &mut ThinVec<NodeId>,
|
|
401
|
+
entries: &mut ThinVec<HoistedPaintChild>,
|
|
402
|
+
) {
|
|
403
|
+
self.build_paint_tree_impl(
|
|
404
|
+
child_id,
|
|
405
|
+
match self.nodes[child_id].is_stacking_context_root(false) {
|
|
406
|
+
true => None,
|
|
407
|
+
false => Some(entries),
|
|
408
|
+
},
|
|
409
|
+
);
|
|
410
|
+
|
|
411
|
+
let z_index = self.nodes[child_id].z_index();
|
|
412
|
+
if z_index != 0 {
|
|
413
|
+
entries.push(HoistedPaintChild::new(child_id, z_index));
|
|
414
|
+
} else {
|
|
415
|
+
paint_children.push(child_id);
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
#[inline(always)]
|
|
421
|
+
fn position_to_order(pos: Position) -> i32 {
|
|
422
|
+
match pos {
|
|
423
|
+
Position::Static => 0,
|
|
424
|
+
// All positioned descendants with z-index: auto share one paint
|
|
425
|
+
// level (CSS 2.1 Appendix E step 8); the stable sort keeps them in
|
|
426
|
+
// tree order among themselves, above in-flow content and floats.
|
|
427
|
+
Position::Relative | Position::Sticky | Position::Absolute | Position::Fixed => 2,
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
#[inline(always)]
|
|
431
|
+
fn float_to_order(pos: Float) -> i32 {
|
|
432
|
+
match pos {
|
|
433
|
+
Float::None => 0,
|
|
434
|
+
_ => 1,
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
/// Paint sort key: (paint level, order-modified position). Positioned
|
|
439
|
+
/// (z-index: auto) descendants paint above in-flow content (CSS 2.1
|
|
440
|
+
/// Appendix E step 8); within a level the stable sort preserves
|
|
441
|
+
/// (order-modified) document order.
|
|
442
|
+
#[inline(always)]
|
|
443
|
+
fn node_to_paint_order(node: &Node, is_flex_or_grid: bool) -> (i32, i32) {
|
|
444
|
+
let Some(style) = node.primary_styles() else {
|
|
445
|
+
return (0, 0);
|
|
446
|
+
};
|
|
447
|
+
let position = style.clone_position();
|
|
448
|
+
if is_flex_or_grid {
|
|
449
|
+
match position {
|
|
450
|
+
Position::Static => (0, style.clone_order()),
|
|
451
|
+
Position::Relative | Position::Sticky => (2, style.clone_order()),
|
|
452
|
+
// Out-of-flow children are not flex/grid items: `order` does
|
|
453
|
+
// not apply; tree order does.
|
|
454
|
+
Position::Absolute | Position::Fixed => (2, 0),
|
|
455
|
+
}
|
|
456
|
+
} else {
|
|
457
|
+
(
|
|
458
|
+
position_to_order(position) + float_to_order(style.clone_float()),
|
|
459
|
+
0,
|
|
460
|
+
)
|
|
461
|
+
}
|
|
462
|
+
}
|