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,1518 @@
1
+ //! Enable the dom to participate in styling by servo
2
+ //!
3
+
4
+ use blitz_traits::node_id::NodeId;
5
+ use std::ptr::NonNull;
6
+ use std::sync::Mutex;
7
+ use std::sync::atomic::Ordering;
8
+
9
+ use crate::StyleThreading;
10
+ use crate::layout::damage::compute_layout_damage;
11
+ use crate::node::Node;
12
+ use crate::node::NodeData;
13
+ use markup5ever::{LocalName, LocalNameStaticSet, Namespace, NamespaceStaticSet, local_name, ns};
14
+ use selectors::bloom::BLOOM_HASH_MASK;
15
+ use selectors::{
16
+ Element, OpaqueElement,
17
+ attr::{AttrSelectorOperation, NamespaceConstraint},
18
+ matching::{ElementSelectorFlags, MatchingContext, VisitedHandlingMode},
19
+ sink::Push,
20
+ };
21
+ use style::CaseSensitivityExt;
22
+ use style::animation::AnimationSetKey;
23
+ use style::animation::AnimationState;
24
+ use style::applicable_declarations::ApplicableDeclarationBlock;
25
+ use style::bloom::each_relevant_element_hash;
26
+ use style::color::AbsoluteColor;
27
+ use style::data::{ElementDataMut, ElementDataRef};
28
+ use style::global_style_data::STYLE_THREAD_POOL;
29
+ use style::invalidation::element::restyle_hints::RestyleHint;
30
+ use style::properties::ComputedValues;
31
+ use style::properties::{Importance, PropertyDeclaration};
32
+ use style::rule_tree::CascadeLevel;
33
+ use style::rule_tree::CascadeOrigin;
34
+ use style::selector_parser::PseudoElement;
35
+ use style::selector_parser::RestyleDamage;
36
+ use style::stylesheets::layer_rule::LayerOrder;
37
+ use style::stylesheets::scope_rule::ImplicitScopeRoot;
38
+ use style::values::AtomString;
39
+ use style::values::specified::{NoCalcPercentage, XLang};
40
+ use style::{
41
+ Atom,
42
+ context::{
43
+ QuirksMode, RegisteredSpeculativePainter, RegisteredSpeculativePainters,
44
+ SharedStyleContext, StyleContext, ThreadLocalStyleContext,
45
+ },
46
+ dom::{LayoutIterator, NodeInfo, OpaqueNode, TDocument, TElement, TNode, TShadowRoot},
47
+ global_style_data::GLOBAL_STYLE_DATA,
48
+ properties::PropertyDeclarationBlock,
49
+ selector_parser::{NonTSPseudoClass, SelectorImpl},
50
+ servo_arc::{Arc, ArcBorrow},
51
+ shared_lock::{Locked, SharedRwLock, StylesheetGuards},
52
+ stylist::RuleInclusion,
53
+ thread_state::ThreadState,
54
+ traversal::{DomTraversal, resolve_style},
55
+ traversal_flags::TraversalFlags,
56
+ values::{AtomIdent, GenericAtomIdent},
57
+ };
58
+ use style_dom::ElementState;
59
+
60
+ use style::values::computed::text::TextAlign as StyloTextAlign;
61
+
62
+ impl crate::document::BaseDocument {
63
+ pub fn resolve_stylist(&mut self, now: f64) {
64
+ style::thread_state::enter(ThreadState::LAYOUT);
65
+
66
+ let guard = &self.guard;
67
+ let guards = StylesheetGuards {
68
+ author: &guard.read(),
69
+ ua_or_user: &guard.read(),
70
+ };
71
+
72
+ let root = TDocument::as_node(&&self.nodes[self.root_node_id])
73
+ .first_element_child()
74
+ .unwrap()
75
+ .as_element()
76
+ .unwrap();
77
+
78
+ self.stylist
79
+ .flush(&guards)
80
+ .process_style(root, Some(&self.snapshots));
81
+
82
+ // Mark actively animating nodes as dirty
83
+ let mut sets = self.animations.sets.write();
84
+ for (key, set) in sets.iter_mut() {
85
+ let node_id = NodeId::from_u64(key.node.id() as u64);
86
+
87
+ // Drop animations belonging to nodes that are no longer in the
88
+ // document. A removed element is never restyled, so it would never
89
+ // get a chance to cancel its own animations; an infinite animation
90
+ // would then keep `has_active_animations` set forever and force a
91
+ // redraw every frame. Emptying the set here lets the `retain` below
92
+ // discard it so the flag can clear on this same pass.
93
+ let in_document = self
94
+ .nodes
95
+ .get(node_id)
96
+ .is_some_and(|node| node.flags.is_in_document());
97
+ if !in_document {
98
+ set.animations.clear();
99
+ set.transitions.clear();
100
+ continue;
101
+ }
102
+
103
+ self.nodes[node_id].set_restyle_hint(RestyleHint::RESTYLE_SELF);
104
+
105
+ for animation in set.animations.iter_mut() {
106
+ if animation.state == AnimationState::Pending && animation.started_at <= now {
107
+ animation.state = AnimationState::Running;
108
+ }
109
+ animation.iterate_if_necessary(now);
110
+
111
+ if animation.state == AnimationState::Running && animation.has_ended(now) {
112
+ animation.state = AnimationState::Finished;
113
+ }
114
+ }
115
+
116
+ for transition in set.transitions.iter_mut() {
117
+ if transition.state == AnimationState::Pending && transition.start_time <= now {
118
+ transition.state = AnimationState::Running;
119
+ }
120
+ if transition.state == AnimationState::Running && transition.has_ended(now) {
121
+ transition.state = AnimationState::Finished;
122
+ }
123
+ }
124
+ }
125
+ drop(sets);
126
+
127
+ // Build the style context used by the style traversal
128
+ let context = SharedStyleContext {
129
+ traversal_flags: TraversalFlags::empty(),
130
+ stylist: &self.stylist,
131
+ options: GLOBAL_STYLE_DATA.options.clone(),
132
+ guards,
133
+ visited_styles_enabled: false,
134
+ animations: self.animations.clone(),
135
+ current_time_for_animations: now,
136
+ snapshot_map: &self.snapshots,
137
+ registered_speculative_painters: &RegisteredPaintersImpl,
138
+ };
139
+
140
+ // components/layout_2020/lib.rs:983
141
+ let root = self.root_element();
142
+ // dbg!(root);
143
+ let token = RecalcStyle::pre_traverse(root, &context);
144
+
145
+ let mut nodes_needing_style_image_flush = Vec::new();
146
+ if token.should_traverse() {
147
+ // Style the elements, resolving their data
148
+ let mut traverser = RecalcStyle::new(context);
149
+ // `Sequential` bypasses Stylo's global pool. See `StyleThreading`.
150
+ let pool_guard = matches!(self.style_threading, StyleThreading::Parallel)
151
+ .then(|| STYLE_THREAD_POOL.pool());
152
+ let rayon_pool = pool_guard.as_ref().and_then(|g| g.as_ref());
153
+ style::driver::traverse_dom(&traverser, token, rayon_pool);
154
+ nodes_needing_style_image_flush =
155
+ std::mem::take(traverser.nodes_needing_style_image_flush.get_mut().unwrap());
156
+ }
157
+ self.pending_style_image_nodes
158
+ .extend(nodes_needing_style_image_flush);
159
+
160
+ for opaque in self.snapshots.keys() {
161
+ let id = NodeId::from_u64(opaque.id() as u64);
162
+ if let Some(node) = self.nodes.get_mut(id) {
163
+ node.set_has_snapshot(false);
164
+ }
165
+ }
166
+ self.snapshots.clear();
167
+
168
+ let mut sets = self.animations.sets.write();
169
+ for set in sets.values_mut() {
170
+ set.clear_canceled_animations();
171
+ for animation in set.animations.iter_mut() {
172
+ animation.is_new = false;
173
+ }
174
+ for transition in set.transitions.iter_mut() {
175
+ transition.is_new = false;
176
+ }
177
+ }
178
+ sets.retain(|_, state| !state.is_empty());
179
+ self.has_active_animations = sets.values().any(|state| state.needs_animation_ticks());
180
+
181
+ // Maybe run garbage collection. Stylo has internal to determine whether to run or not.
182
+ self.stylist.rule_tree().maybe_gc();
183
+
184
+ style::thread_state::exit(ThreadState::LAYOUT);
185
+ }
186
+
187
+ /// Compute the style of an element which the regular style traversal
188
+ /// skipped because it lives inside a `display: none` subtree.
189
+ ///
190
+ /// The style traversal culls `display: none` subtrees, so such elements
191
+ /// never have styles stored on them, but `getComputedStyle()` must still
192
+ /// return their computed values. This resolves the style on demand
193
+ /// (styling any unstyled ancestors along the way) without storing it on the
194
+ /// node. Returns `None` for non-elements and elements outside the document.
195
+ pub fn resolve_undisplayed_style(&self, node_id: NodeId) -> Option<Arc<ComputedValues>> {
196
+ let node = self.nodes.get(node_id)?;
197
+ if !node.is_element() || !node.flags.is_in_document() {
198
+ return None;
199
+ }
200
+
201
+ style::thread_state::enter(ThreadState::LAYOUT);
202
+
203
+ let guard = &self.guard;
204
+ let guards = StylesheetGuards {
205
+ author: &guard.read(),
206
+ ua_or_user: &guard.read(),
207
+ };
208
+ let shared = SharedStyleContext {
209
+ traversal_flags: TraversalFlags::empty(),
210
+ stylist: &self.stylist,
211
+ options: GLOBAL_STYLE_DATA.options.clone(),
212
+ guards,
213
+ visited_styles_enabled: false,
214
+ animations: self.animations.clone(),
215
+ current_time_for_animations: 0.0,
216
+ snapshot_map: &self.snapshots,
217
+ registered_speculative_painters: &RegisteredPaintersImpl,
218
+ };
219
+ let mut thread_local = ThreadLocalStyleContext::new();
220
+ let mut context = StyleContext {
221
+ shared: &shared,
222
+ thread_local: &mut thread_local,
223
+ };
224
+ let styles = resolve_style(&mut context, node, RuleInclusion::All, None, None);
225
+
226
+ style::thread_state::exit(ThreadState::LAYOUT);
227
+
228
+ Some(styles.primary().clone())
229
+ }
230
+ }
231
+
232
+ /// A handle to a node that Servo's style traits are implemented against
233
+ ///
234
+ /// Since BlitzNodes are not persistent (IE we don't keep the pointers around between frames), we choose to just implement
235
+ /// the tree structure in the nodes themselves, and temporarily give out pointers during the layout phase.
236
+ type BlitzNode<'a> = &'a Node;
237
+
238
+ impl<'a> TDocument for BlitzNode<'a> {
239
+ type ConcreteNode = BlitzNode<'a>;
240
+
241
+ fn as_node(&self) -> Self::ConcreteNode {
242
+ self
243
+ }
244
+
245
+ fn is_html_document(&self) -> bool {
246
+ true
247
+ }
248
+
249
+ fn quirks_mode(&self) -> QuirksMode {
250
+ QuirksMode::NoQuirks
251
+ }
252
+
253
+ fn shared_lock(&self) -> &SharedRwLock {
254
+ self.guard()
255
+ }
256
+ }
257
+
258
+ impl NodeInfo for BlitzNode<'_> {
259
+ fn is_element(&self) -> bool {
260
+ Node::is_element(self)
261
+ }
262
+
263
+ fn is_text_node(&self) -> bool {
264
+ Node::is_text_node(self)
265
+ }
266
+ }
267
+
268
+ impl<'a> TShadowRoot for BlitzNode<'a> {
269
+ type ConcreteNode = BlitzNode<'a>;
270
+
271
+ fn as_node(&self) -> Self::ConcreteNode {
272
+ self
273
+ }
274
+
275
+ fn host(&self) -> <Self::ConcreteNode as TNode>::ConcreteElement {
276
+ todo!("Shadow roots not implemented")
277
+ }
278
+
279
+ fn style_data<'b>(&self) -> Option<&'b style::stylist::CascadeData>
280
+ where
281
+ Self: 'b,
282
+ {
283
+ todo!("Shadow roots not implemented")
284
+ }
285
+ }
286
+
287
+ // components/styleaapper.rs:
288
+ impl<'a> TNode for BlitzNode<'a> {
289
+ type ConcreteElement = BlitzNode<'a>;
290
+ type ConcreteDocument = BlitzNode<'a>;
291
+ type ConcreteShadowRoot = BlitzNode<'a>;
292
+
293
+ fn parent_node(&self) -> Option<Self> {
294
+ self.parent.map(|id| self.with(id))
295
+ }
296
+
297
+ fn first_child(&self) -> Option<Self> {
298
+ self.children.first().map(|id| self.with(*id))
299
+ }
300
+
301
+ fn last_child(&self) -> Option<Self> {
302
+ self.children.last().map(|id| self.with(*id))
303
+ }
304
+
305
+ fn prev_sibling(&self) -> Option<Self> {
306
+ self.backward(1)
307
+ }
308
+
309
+ fn next_sibling(&self) -> Option<Self> {
310
+ self.forward(1)
311
+ }
312
+
313
+ fn owner_doc(&self) -> Self::ConcreteDocument {
314
+ // Walk up the (layout-)parent chain to the root Document node.
315
+ let mut node = *self;
316
+ while let Some(parent_id) = node.parent {
317
+ node = node.with(parent_id);
318
+ }
319
+ node
320
+ }
321
+
322
+ fn is_in_document(&self) -> bool {
323
+ true
324
+ }
325
+
326
+ // I think this is the same as parent_node only in the cases when the direct parent is not a real element, forcing us
327
+ // to travel upwards
328
+ //
329
+ // For the sake of this demo, we're just going to return the parent node ann
330
+ fn traversal_parent(&self) -> Option<Self::ConcreteElement> {
331
+ self.parent_node().and_then(|node| node.as_element())
332
+ }
333
+
334
+ fn opaque(&self) -> OpaqueNode {
335
+ OpaqueNode(self.id.as_u64() as usize)
336
+ }
337
+
338
+ fn debug_id(self) -> usize {
339
+ self.id.as_u64() as usize
340
+ }
341
+
342
+ fn as_element(&self) -> Option<Self::ConcreteElement> {
343
+ match self.data {
344
+ NodeData::Element { .. } => Some(self),
345
+ _ => None,
346
+ }
347
+ }
348
+
349
+ fn as_document(&self) -> Option<Self::ConcreteDocument> {
350
+ match self.data {
351
+ NodeData::Document(_) => Some(self),
352
+ _ => None,
353
+ }
354
+ }
355
+
356
+ fn as_shadow_root(&self) -> Option<Self::ConcreteShadowRoot> {
357
+ // TODO: implement shadow DOM
358
+ None
359
+ }
360
+ }
361
+
362
+ impl selectors::Element for BlitzNode<'_> {
363
+ type Impl = SelectorImpl;
364
+
365
+ fn opaque(&self) -> selectors::OpaqueElement {
366
+ // This correctly uses a unique id for the OpaqueElement (unlike using a pointer to the "slot")
367
+ // However, it makes it impossible for us to "rehydrate" the OpaqueElement back into an actual Element
368
+ // which is required to implement the `implicit_scope_for_sheet_in_shadow_root` method below
369
+ //
370
+ // We should see if selectors will accept a PR that allows us to use 128bits for the OpaqueElement. Or
371
+ // find some other solution that will enable "rehydration". This is required to enable and use the
372
+ // Shadow DOM functionality in Stylo.
373
+ let non_null =
374
+ NonNull::new((self.id.as_u64() as usize).wrapping_add(1) as *mut ()).unwrap();
375
+ OpaqueElement::from_non_null_ptr(non_null)
376
+ }
377
+
378
+ fn parent_element(&self) -> Option<Self> {
379
+ TElement::traversal_parent(self)
380
+ }
381
+
382
+ fn parent_node_is_shadow_root(&self) -> bool {
383
+ false
384
+ }
385
+
386
+ fn containing_shadow_host(&self) -> Option<Self> {
387
+ None
388
+ }
389
+
390
+ fn is_pseudo_element(&self) -> bool {
391
+ matches!(self.data, NodeData::AnonymousBlock(_))
392
+ }
393
+
394
+ // These methods are implemented naively since we only threaded real nodes and not fake nodes
395
+ // we should try and use `find` instead of this foward/backward stuff since its ugly and slow
396
+ fn prev_sibling_element(&self) -> Option<Self> {
397
+ let mut n = 1;
398
+ while let Some(node) = self.backward(n) {
399
+ if node.is_element() {
400
+ return Some(node);
401
+ }
402
+ n += 1;
403
+ }
404
+
405
+ None
406
+ }
407
+
408
+ fn next_sibling_element(&self) -> Option<Self> {
409
+ let mut n = 1;
410
+ while let Some(node) = self.forward(n) {
411
+ if node.is_element() {
412
+ return Some(node);
413
+ }
414
+ n += 1;
415
+ }
416
+
417
+ None
418
+ }
419
+
420
+ fn first_element_child(&self) -> Option<Self> {
421
+ let mut children = self.dom_children();
422
+ children.find(|child| child.is_element())
423
+ }
424
+
425
+ fn is_html_element_in_html_document(&self) -> bool {
426
+ true // self.has_namespace(ns!(html))
427
+ }
428
+
429
+ fn has_local_name(&self, local_name: &LocalName) -> bool {
430
+ self.data.is_element_with_tag_name(local_name)
431
+ }
432
+
433
+ fn has_namespace(&self, ns: &Namespace) -> bool {
434
+ self.element_data().expect("Not an element").name.ns == *ns
435
+ }
436
+
437
+ fn is_same_type(&self, other: &Self) -> bool {
438
+ self.local_name() == other.local_name() && self.namespace() == other.namespace()
439
+ }
440
+
441
+ fn attr_matches(
442
+ &self,
443
+ ns: &NamespaceConstraint<&GenericAtomIdent<NamespaceStaticSet>>,
444
+ local_name: &GenericAtomIdent<LocalNameStaticSet>,
445
+ operation: &AttrSelectorOperation<&AtomString>,
446
+ ) -> bool {
447
+ let Some(attrs) = self.data.attrs() else {
448
+ return false;
449
+ };
450
+ attrs.iter().any(|attr| {
451
+ attr.name.local == local_name.0
452
+ && match ns {
453
+ NamespaceConstraint::Any => true,
454
+ NamespaceConstraint::Specific(ns) => attr.name.ns == ns.0,
455
+ }
456
+ && operation.eval_str(&attr.value)
457
+ })
458
+ }
459
+
460
+ fn match_non_ts_pseudo_class(
461
+ &self,
462
+ pseudo_class: &<Self::Impl as selectors::SelectorImpl>::NonTSPseudoClass,
463
+ _context: &mut MatchingContext<Self::Impl>,
464
+ ) -> bool {
465
+ match *pseudo_class {
466
+ NonTSPseudoClass::Active => self.element_state().contains(ElementState::ACTIVE),
467
+ NonTSPseudoClass::AnyLink => self
468
+ .element_state()
469
+ .intersects(ElementState::VISITED_OR_UNVISITED),
470
+ NonTSPseudoClass::Checked => self.element_state().contains(ElementState::CHECKED),
471
+ NonTSPseudoClass::Valid => false,
472
+ NonTSPseudoClass::Invalid => false,
473
+ NonTSPseudoClass::Defined => false,
474
+ NonTSPseudoClass::Disabled => self.element_state().contains(ElementState::DISABLED),
475
+ NonTSPseudoClass::Enabled => self.element_state().contains(ElementState::ENABLED),
476
+ NonTSPseudoClass::Focus => self.element_state().contains(ElementState::FOCUS),
477
+ NonTSPseudoClass::FocusWithin => false,
478
+ NonTSPseudoClass::FocusVisible => false,
479
+ NonTSPseudoClass::Fullscreen => false,
480
+ NonTSPseudoClass::Hover => self.element_state().contains(ElementState::HOVER),
481
+ NonTSPseudoClass::Indeterminate => false,
482
+ NonTSPseudoClass::Lang(_) => false,
483
+ NonTSPseudoClass::CustomState(_) => false,
484
+ NonTSPseudoClass::Link => self.element_state().contains(ElementState::UNVISITED),
485
+ NonTSPseudoClass::PlaceholderShown => false,
486
+ NonTSPseudoClass::ReadWrite => false,
487
+ NonTSPseudoClass::ReadOnly => false,
488
+ NonTSPseudoClass::ServoNonZeroBorder => false,
489
+ NonTSPseudoClass::Target => false,
490
+ NonTSPseudoClass::Visited => false,
491
+ NonTSPseudoClass::Autofill => false,
492
+ NonTSPseudoClass::Default => false,
493
+
494
+ NonTSPseudoClass::InRange => false,
495
+ NonTSPseudoClass::Modal => false,
496
+ NonTSPseudoClass::Open => false,
497
+ NonTSPseudoClass::Optional => false,
498
+ NonTSPseudoClass::OutOfRange => false,
499
+ NonTSPseudoClass::PopoverOpen => false,
500
+ NonTSPseudoClass::Required => false,
501
+ NonTSPseudoClass::UserInvalid => false,
502
+ NonTSPseudoClass::UserValid => false,
503
+ NonTSPseudoClass::MozMeterOptimum => false,
504
+ NonTSPseudoClass::MozMeterSubOptimum => false,
505
+ NonTSPseudoClass::MozMeterSubSubOptimum => false,
506
+ }
507
+ }
508
+
509
+ fn match_pseudo_element(
510
+ &self,
511
+ pe: &PseudoElement,
512
+ _context: &mut MatchingContext<Self::Impl>,
513
+ ) -> bool {
514
+ let pseudo = match self.try_stylo_element_data().and_then(|s| s.get()) {
515
+ Some(el) => el
516
+ .styles
517
+ .get_primary()
518
+ .and_then(|s| s.pseudo())
519
+ .or(match &self.data {
520
+ NodeData::AnonymousBlock(_) => Some(PseudoElement::ServoAnonymousBox),
521
+ _ => None,
522
+ }),
523
+ None => None,
524
+ };
525
+
526
+ pseudo.is_some_and(|psuedo| psuedo == *pe)
527
+ }
528
+
529
+ fn apply_selector_flags(&self, flags: ElementSelectorFlags) {
530
+ // Handle flags that apply to the element.
531
+ let self_flags = flags.for_self();
532
+ if !self_flags.is_empty() {
533
+ self.selector_flags()
534
+ .set(self.selector_flags().get() | self_flags);
535
+ }
536
+
537
+ // Handle flags that apply to the parent.
538
+ let parent_flags = flags.for_parent();
539
+ if !parent_flags.is_empty() {
540
+ if let Some(parent) = self.parent_node() {
541
+ parent
542
+ .selector_flags()
543
+ .set(parent.selector_flags().get() | parent_flags);
544
+ }
545
+ }
546
+ }
547
+
548
+ fn is_link(&self) -> bool {
549
+ self.data.is_element_with_tag_name(&local_name!("a"))
550
+ }
551
+
552
+ fn is_html_slot_element(&self) -> bool {
553
+ false
554
+ }
555
+
556
+ fn has_id(
557
+ &self,
558
+ id: &<Self::Impl as selectors::SelectorImpl>::Identifier,
559
+ case_sensitivity: selectors::attr::CaseSensitivity,
560
+ ) -> bool {
561
+ self.element_data()
562
+ .and_then(|data| data.id.as_ref())
563
+ .map(|id_attr| case_sensitivity.eq_atom(id_attr, id))
564
+ .unwrap_or(false)
565
+ }
566
+
567
+ fn has_class(
568
+ &self,
569
+ search_name: &<Self::Impl as selectors::SelectorImpl>::Identifier,
570
+ case_sensitivity: selectors::attr::CaseSensitivity,
571
+ ) -> bool {
572
+ let class_attr = self.data.attr(local_name!("class"));
573
+ if let Some(class_attr) = class_attr {
574
+ // split the class attribute
575
+ for pheme in class_attr.split_ascii_whitespace() {
576
+ let atom = Atom::from(pheme);
577
+ if case_sensitivity.eq_atom(&atom, search_name) {
578
+ return true;
579
+ }
580
+ }
581
+ }
582
+
583
+ false
584
+ }
585
+
586
+ fn imported_part(
587
+ &self,
588
+ _name: &<Self::Impl as selectors::SelectorImpl>::Identifier,
589
+ ) -> Option<<Self::Impl as selectors::SelectorImpl>::Identifier> {
590
+ None
591
+ }
592
+
593
+ fn is_part(&self, _name: &<Self::Impl as selectors::SelectorImpl>::Identifier) -> bool {
594
+ false
595
+ }
596
+
597
+ fn is_empty(&self) -> bool {
598
+ self.dom_children().next().is_none()
599
+ }
600
+
601
+ fn is_root(&self) -> bool {
602
+ self.parent_node()
603
+ .and_then(|parent| parent.parent_node())
604
+ .is_none()
605
+ }
606
+
607
+ fn has_custom_state(
608
+ &self,
609
+ _name: &<Self::Impl as selectors::SelectorImpl>::Identifier,
610
+ ) -> bool {
611
+ false
612
+ }
613
+
614
+ fn add_element_unique_hashes(&self, filter: &mut selectors::bloom::BloomFilter) -> bool {
615
+ each_relevant_element_hash(*self, |hash| filter.insert_hash(hash & BLOOM_HASH_MASK));
616
+ true
617
+ }
618
+ }
619
+
620
+ impl<'a> TElement for BlitzNode<'a> {
621
+ type ConcreteNode = BlitzNode<'a>;
622
+
623
+ type TraversalChildrenIterator = Traverser<'a>;
624
+
625
+ fn as_node(&self) -> Self::ConcreteNode {
626
+ self
627
+ }
628
+
629
+ fn implicit_scope_for_sheet_in_shadow_root(
630
+ _opaque_host: OpaqueElement,
631
+ _sheet_index: usize,
632
+ ) -> Option<ImplicitScopeRoot> {
633
+ // We cannot currently implement this as we are using the NodeId as the OpaqueElement,
634
+ // and need a reference to the Slab to convert it back into an Element
635
+ //
636
+ // Luckily it is only needed for shadow dom.
637
+ todo!();
638
+ }
639
+
640
+ fn traversal_children(&self) -> style::dom::LayoutIterator<Self::TraversalChildrenIterator> {
641
+ LayoutIterator(Traverser {
642
+ // dom: self.tree(),
643
+ parent: self,
644
+ child_index: 0,
645
+ })
646
+ }
647
+
648
+ fn is_html_element(&self) -> bool {
649
+ self.is_element()
650
+ }
651
+
652
+ // not implemented.....
653
+ fn is_mathml_element(&self) -> bool {
654
+ false
655
+ }
656
+
657
+ // need to check the namespace
658
+ fn is_svg_element(&self) -> bool {
659
+ false
660
+ }
661
+
662
+ fn style_attribute(&self) -> Option<ArcBorrow<'_, Locked<PropertyDeclarationBlock>>> {
663
+ self.element_data()
664
+ .expect("Not an element")
665
+ .style_attribute
666
+ .as_ref()
667
+ .map(|f| f.borrow_arc())
668
+ }
669
+
670
+ fn state(&self) -> ElementState {
671
+ *self.element_state()
672
+ }
673
+
674
+ fn has_part_attr(&self) -> bool {
675
+ false
676
+ }
677
+
678
+ fn exports_any_part(&self) -> bool {
679
+ false
680
+ }
681
+
682
+ fn id(&self) -> Option<&style::Atom> {
683
+ self.element_data().and_then(|data| data.id.as_ref())
684
+ }
685
+
686
+ fn each_class<F>(&self, mut callback: F)
687
+ where
688
+ F: FnMut(&style::values::AtomIdent),
689
+ {
690
+ let class_attr = self.data.attr(local_name!("class"));
691
+ if let Some(class_attr) = class_attr {
692
+ // split the class attribute
693
+ for pheme in class_attr.split_ascii_whitespace() {
694
+ let atom = Atom::from(pheme); // interns the string
695
+ callback(AtomIdent::cast(&atom));
696
+ }
697
+ }
698
+ }
699
+
700
+ fn each_attr_name<F>(&self, mut callback: F)
701
+ where
702
+ F: FnMut(&style::LocalName),
703
+ {
704
+ if let Some(attrs) = self.data.attrs() {
705
+ for attr in attrs.iter() {
706
+ callback(&GenericAtomIdent(attr.name.local.clone()));
707
+ }
708
+ }
709
+ }
710
+
711
+ fn has_dirty_descendants(&self) -> bool {
712
+ Node::has_dirty_descendants(self)
713
+ }
714
+
715
+ fn has_snapshot(&self) -> bool {
716
+ Node::has_snapshot(self)
717
+ }
718
+
719
+ fn handled_snapshot(&self) -> bool {
720
+ self.snapshot_handled().load(Ordering::SeqCst)
721
+ }
722
+
723
+ unsafe fn set_handled_snapshot(&self) {
724
+ self.snapshot_handled().store(true, Ordering::SeqCst);
725
+ }
726
+
727
+ // Stylo calls this on elements it is already traversing (or has already
728
+ // reached via invalidation), so the ancestor chain must not be re-marked:
729
+ // ancestors visited earlier in the preorder traversal have had their bits
730
+ // cleared, and re-flagging them leaves stale `dirty_descendants` bits that
731
+ // break the early-out invariant of `Node::mark_ancestors_dirty` (a set bit
732
+ // implies all ancestors are set).
733
+ unsafe fn set_dirty_descendants(&self) {
734
+ Node::set_dirty_descendants(self);
735
+ }
736
+
737
+ unsafe fn unset_dirty_descendants(&self) {
738
+ Node::unset_dirty_descendants(self);
739
+ }
740
+
741
+ fn store_children_to_process(&self, _n: isize) {
742
+ unimplemented!()
743
+ }
744
+
745
+ fn did_process_child(&self) -> isize {
746
+ unimplemented!()
747
+ }
748
+
749
+ unsafe fn ensure_data(&self) -> ElementDataMut<'_> {
750
+ // SAFETY: stylo traversal has exclusive access to nodes
751
+ unsafe { self.stylo_element_data().ensure_init() }
752
+ }
753
+
754
+ unsafe fn clear_data(&self) {
755
+ // SAFETY: stylo traversal has exclusive access to nodes
756
+ unsafe { self.stylo_element_data().clear() }
757
+ }
758
+
759
+ fn has_data(&self) -> bool {
760
+ self.try_stylo_element_data().is_some_and(|s| s.has_data())
761
+ }
762
+
763
+ fn borrow_data(&self) -> Option<ElementDataRef<'_>> {
764
+ self.try_stylo_element_data().and_then(|s| s.get())
765
+ }
766
+
767
+ fn mutate_data(&self) -> Option<ElementDataMut<'_>> {
768
+ unsafe { self.stylo_element_data().unsafe_stylo_only_mut() }
769
+ }
770
+
771
+ fn skip_item_display_fixup(&self) -> bool {
772
+ false
773
+ }
774
+
775
+ fn may_have_animations(&self) -> bool {
776
+ true
777
+ }
778
+
779
+ fn has_animations(&self, context: &SharedStyleContext) -> bool {
780
+ self.has_css_animations(context, None) || self.has_css_transitions(context, None)
781
+ }
782
+
783
+ fn has_css_animations(
784
+ &self,
785
+ context: &SharedStyleContext,
786
+ pseudo_element: Option<PseudoElement>,
787
+ ) -> bool {
788
+ let key = AnimationSetKey::new(TNode::opaque(&TElement::as_node(self)), pseudo_element);
789
+ context.animations.has_active_animations(&key)
790
+ }
791
+
792
+ fn has_css_transitions(
793
+ &self,
794
+ context: &SharedStyleContext,
795
+ pseudo_element: Option<PseudoElement>,
796
+ ) -> bool {
797
+ let key = AnimationSetKey::new(TNode::opaque(&TElement::as_node(self)), pseudo_element);
798
+ context.animations.has_active_transitions(&key)
799
+ }
800
+
801
+ fn animation_rule(
802
+ &self,
803
+ context: &SharedStyleContext,
804
+ ) -> Option<Arc<Locked<PropertyDeclarationBlock>>> {
805
+ let opaque = TNode::opaque(&TElement::as_node(self));
806
+ context.animations.get_animation_declarations(
807
+ &AnimationSetKey::new_for_non_pseudo(opaque),
808
+ context.current_time_for_animations,
809
+ self.guard(),
810
+ )
811
+ }
812
+
813
+ fn transition_rule(
814
+ &self,
815
+ context: &SharedStyleContext,
816
+ ) -> Option<Arc<Locked<PropertyDeclarationBlock>>> {
817
+ let opaque = TNode::opaque(&TElement::as_node(self));
818
+ context.animations.get_transition_declarations(
819
+ &AnimationSetKey::new_for_non_pseudo(opaque),
820
+ context.current_time_for_animations,
821
+ self.guard(),
822
+ )
823
+ }
824
+
825
+ fn shadow_root(&self) -> Option<<Self::ConcreteNode as TNode>::ConcreteShadowRoot> {
826
+ None
827
+ }
828
+
829
+ fn containing_shadow(&self) -> Option<<Self::ConcreteNode as TNode>::ConcreteShadowRoot> {
830
+ None
831
+ }
832
+
833
+ fn get_attr(&self, attr: &style::LocalName, ns: &style::Namespace) -> Option<String> {
834
+ // TODO: case-insensitive matching for HTML-ns attrs
835
+ self.attrs()?
836
+ .iter()
837
+ .find(|a| a.name.local == attr.0 && a.name.ns == ns.0)
838
+ .map(|a| a.value.clone())
839
+ }
840
+
841
+ fn lang_attr(&self) -> Option<style::selector_parser::AttrValue> {
842
+ None
843
+ }
844
+
845
+ fn match_element_lang(
846
+ &self,
847
+ _override_lang: Option<Option<style::selector_parser::AttrValue>>,
848
+ _value: &style::selector_parser::Lang,
849
+ ) -> bool {
850
+ false
851
+ }
852
+
853
+ fn is_html_document_body_element(&self) -> bool {
854
+ // Check node is a <body> element
855
+ let is_body_element = self.data.is_element_with_tag_name(&local_name!("body"));
856
+
857
+ // If it isn't then return early
858
+ if !is_body_element {
859
+ return false;
860
+ }
861
+
862
+ // If it is then check if it is a child of the root (<html>) element
863
+ let root_node = TNode::owner_doc(self);
864
+ let root_element = TDocument::as_node(&root_node)
865
+ .first_element_child()
866
+ .unwrap();
867
+ root_element.children.contains(&self.id)
868
+ }
869
+
870
+ fn synthesize_presentational_hints_for_legacy_attributes<V>(
871
+ &self,
872
+ _visited_handling: VisitedHandlingMode,
873
+ hints: &mut V,
874
+ ) where
875
+ V: Push<style::applicable_declarations::ApplicableDeclarationBlock>,
876
+ {
877
+ let Some(elem) = self.data.downcast_element() else {
878
+ return;
879
+ };
880
+
881
+ let tag = &elem.name.local;
882
+
883
+ let mut push_style = |decl: PropertyDeclaration| {
884
+ hints.push(ApplicableDeclarationBlock::from_declarations(
885
+ Arc::new(
886
+ self.guard()
887
+ .wrap(PropertyDeclarationBlock::with_one(decl, Importance::Normal)),
888
+ ),
889
+ CascadeLevel::new(CascadeOrigin::PresHints),
890
+ LayerOrder::root(),
891
+ ));
892
+ };
893
+
894
+ fn parse_color_attr(value: &str) -> Option<(u8, u8, u8, f32)> {
895
+ if !value.starts_with('#') {
896
+ return None;
897
+ }
898
+
899
+ let value = &value[1..];
900
+ if value.len() == 3 {
901
+ let r = u8::from_str_radix(&value[0..1], 16).ok()?;
902
+ let g = u8::from_str_radix(&value[1..2], 16).ok()?;
903
+ let b = u8::from_str_radix(&value[2..3], 16).ok()?;
904
+ return Some((r, g, b, 1.0));
905
+ }
906
+
907
+ if value.len() == 6 {
908
+ let r = u8::from_str_radix(&value[0..2], 16).ok()?;
909
+ let g = u8::from_str_radix(&value[2..4], 16).ok()?;
910
+ let b = u8::from_str_radix(&value[4..6], 16).ok()?;
911
+ return Some((r, g, b, 1.0));
912
+ }
913
+
914
+ None
915
+ }
916
+
917
+ /// The HTML "rules for parsing dimension values" -- Stylo's
918
+ /// implementation of them -- packaged as a specified
919
+ /// `<length-percentage>`. `ignoring_zero` selects the spec's separate
920
+ /// "maps to the dimension property (ignoring zero)" mapping, where a
921
+ /// zero value is dropped rather than honoured.
922
+ ///
923
+ /// https://html.spec.whatwg.org/multipage/common-microsyntaxes.html#rules-for-parsing-dimension-values
924
+ fn parse_size_attr(
925
+ value: &str,
926
+ ignoring_zero: bool,
927
+ ) -> Option<style::values::specified::LengthPercentage> {
928
+ use style::servo::attr::{
929
+ LengthOrPercentageOrAuto, parse_length, parse_nonzero_length,
930
+ };
931
+ use style::values::specified::{LengthPercentage, NoCalcLength};
932
+ let parsed = if ignoring_zero {
933
+ parse_nonzero_length(value)
934
+ } else {
935
+ parse_length(value)
936
+ };
937
+ match parsed {
938
+ LengthOrPercentageOrAuto::Length(length) => Some(LengthPercentage::Length(
939
+ NoCalcLength::from_px(length.to_f32_px()),
940
+ )),
941
+ LengthOrPercentageOrAuto::Percentage(fraction) => Some(
942
+ LengthPercentage::Percentage(NoCalcPercentage::new(fraction)),
943
+ ),
944
+ LengthOrPercentageOrAuto::Auto => None,
945
+ }
946
+ }
947
+
948
+ /// Parse the value of an SVG `width`/`height` presentation attribute.
949
+ /// Unlike the legacy HTML dimension attributes, these accept any CSS
950
+ /// <length-percentage> (e.g. `1em`), and a unitless number means user
951
+ /// units, which map to CSS px.
952
+ fn parse_svg_size_attr(value: &str) -> Option<style::values::specified::LengthPercentage> {
953
+ use style::values::specified::{LengthPercentage, NoCalcLength};
954
+ use style_traits::ParsingMode;
955
+
956
+ let value = value.trim();
957
+ if let Some(number) = value.strip_suffix('%') {
958
+ let val: f32 = number.trim().parse().ok()?;
959
+ return (val >= 0.0)
960
+ .then(|| LengthPercentage::Percentage(NoCalcPercentage::new(val / 100.0)));
961
+ }
962
+
963
+ // Split into number and unit: the unit is the trailing run of
964
+ // ASCII alphabetic characters (this never eats into a scientific
965
+ // exponent such as `1e3`, which ends in a digit).
966
+ let number_len = value
967
+ .trim_end_matches(|c: char| c.is_ascii_alphabetic())
968
+ .len();
969
+ let (number, unit) = value.split_at(number_len);
970
+ let val: f32 = number.trim().parse().ok().filter(|v| *v >= 0.0)?;
971
+ let length = if unit.is_empty() {
972
+ NoCalcLength::from_px(val)
973
+ } else {
974
+ NoCalcLength::parse_dimension_with_flags(ParsingMode::DEFAULT, false, val, unit)
975
+ .ok()?
976
+ };
977
+ Some(LengthPercentage::Length(length))
978
+ }
979
+
980
+ // `<input type=image>` is the only input type that is replaced
981
+ // content, and it is the only one that takes the embedded-content
982
+ // presentational attributes. The type attribute is matched ASCII
983
+ // case-insensitively, as attribute keywords always are.
984
+ let is_image_input = *tag == local_name!("input")
985
+ && elem.attrs().iter().any(|attr| {
986
+ attr.name.local == local_name!("type") && attr.value.eq_ignore_ascii_case("image")
987
+ });
988
+
989
+ // https://html.spec.whatwg.org/multipage/#the-lang-and-xml:lang-attributes
990
+ let accepts_lang = elem.name.ns == ns!(html) || elem.name.ns == ns!(svg);
991
+ let mut lang = None;
992
+
993
+ for attr in elem.attrs() {
994
+ let name = &attr.name.local;
995
+ let value = attr.value.as_str();
996
+
997
+ if *name == local_name!("lang") {
998
+ if attr.name.ns == ns!(xml) {
999
+ lang = Some(value);
1000
+ } else if attr.name.ns == ns!() && accepts_lang {
1001
+ lang = lang.or(Some(value));
1002
+ }
1003
+ }
1004
+
1005
+ if *name == local_name!("align") {
1006
+ use style::values::specified::TextAlign;
1007
+ let keyword = match value {
1008
+ "left" => Some(StyloTextAlign::MozLeft),
1009
+ "right" => Some(StyloTextAlign::MozRight),
1010
+ "center" => Some(StyloTextAlign::MozCenter),
1011
+ _ => None,
1012
+ };
1013
+
1014
+ if let Some(keyword) = keyword {
1015
+ push_style(PropertyDeclaration::TextAlign(TextAlign::Keyword(keyword)));
1016
+ }
1017
+ }
1018
+
1019
+ // The width/height attributes on these elements map to the
1020
+ // corresponding dimension properties (percentages allowed):
1021
+ // https://html.spec.whatwg.org/multipage/rendering.html#dimRendering
1022
+ // https://html.spec.whatwg.org/multipage/rendering.html#attributes-for-embedded-content-and-images
1023
+ let is_width = *name == local_name!("width");
1024
+ let is_height = *name == local_name!("height");
1025
+ // The elements whose width/height attributes map to the dimension
1026
+ // properties. `input` only joins them as type=image, which is the
1027
+ // one replaced input type.
1028
+ let is_embedded = *tag == local_name!("iframe")
1029
+ || *tag == local_name!("embed")
1030
+ || *tag == local_name!("video")
1031
+ || *tag == local_name!("object")
1032
+ || *tag == local_name!("img")
1033
+ || *tag == local_name!("marquee")
1034
+ || is_image_input;
1035
+ let maps_to_dimension = if is_width {
1036
+ is_embedded
1037
+ || *tag == local_name!("table")
1038
+ || *tag == local_name!("col")
1039
+ || *tag == local_name!("colgroup")
1040
+ || *tag == local_name!("tr")
1041
+ || *tag == local_name!("td")
1042
+ || *tag == local_name!("th")
1043
+ || *tag == local_name!("hr")
1044
+ } else if is_height {
1045
+ is_embedded
1046
+ || *tag == local_name!("table")
1047
+ || *tag == local_name!("thead")
1048
+ || *tag == local_name!("tbody")
1049
+ || *tag == local_name!("tfoot")
1050
+ || *tag == local_name!("tr")
1051
+ || *tag == local_name!("td")
1052
+ || *tag == local_name!("th")
1053
+ } else {
1054
+ false
1055
+ };
1056
+ if maps_to_dimension {
1057
+ // Three of these use the "(ignoring zero)" variant of the
1058
+ // mapping, where a zero is dropped instead of honoured:
1059
+ // `table width`, and `td`/`th` in both axes.
1060
+ let is_cell = *tag == local_name!("td") || *tag == local_name!("th");
1061
+ let ignoring_zero = is_cell || (is_width && *tag == local_name!("table"));
1062
+ if let Some(size) = parse_size_attr(value, ignoring_zero) {
1063
+ use style::values::generics::{NonNegative, length::Size};
1064
+ let size = Size::LengthPercentage(NonNegative(size));
1065
+ push_style(if is_width {
1066
+ PropertyDeclaration::Width(size)
1067
+ } else {
1068
+ PropertyDeclaration::Height(size)
1069
+ });
1070
+ }
1071
+ }
1072
+
1073
+ // hspace/vspace map to the horizontal and vertical margins as
1074
+ // dimension properties. This is a *smaller* set than the one that
1075
+ // takes width/height: `iframe` and `video` take width and height
1076
+ // but not hspace/vspace, and html/rendering/unmapped-attributes
1077
+ // checks exactly that -- browsers have got it wrong before.
1078
+ let takes_spacing = *tag == local_name!("embed")
1079
+ || *tag == local_name!("img")
1080
+ || *tag == local_name!("object")
1081
+ || *tag == local_name!("marquee")
1082
+ || is_image_input;
1083
+ if takes_spacing {
1084
+ let is_hspace = *name == local_name!("hspace");
1085
+ let is_vspace = *name == local_name!("vspace");
1086
+ if is_hspace || is_vspace {
1087
+ if let Some(size) = parse_size_attr(value, false) {
1088
+ use style::values::generics::length::GenericMargin;
1089
+ let margin = GenericMargin::LengthPercentage(size);
1090
+ if is_hspace {
1091
+ push_style(PropertyDeclaration::MarginLeft(margin.clone()));
1092
+ push_style(PropertyDeclaration::MarginRight(margin));
1093
+ } else {
1094
+ push_style(PropertyDeclaration::MarginTop(margin.clone()));
1095
+ push_style(PropertyDeclaration::MarginBottom(margin));
1096
+ }
1097
+ }
1098
+ }
1099
+ }
1100
+
1101
+ // https://svgwg.org/svg2-draft/geometry.html#Sizing
1102
+ // The `width` and `height` attributes on an `<svg>` element are
1103
+ // presentation attributes that map to the CSS `width`/`height`
1104
+ // properties, so e.g. `width="1em"` must resolve against the
1105
+ // element's font-size like any other CSS length.
1106
+ if *tag == local_name!("svg")
1107
+ && (*name == local_name!("width") || *name == local_name!("height"))
1108
+ {
1109
+ if let Some(size) = parse_svg_size_attr(value) {
1110
+ use style::values::generics::{NonNegative, length::Size};
1111
+ let size = Size::LengthPercentage(NonNegative(size));
1112
+ push_style(if *name == local_name!("width") {
1113
+ PropertyDeclaration::Width(size)
1114
+ } else {
1115
+ PropertyDeclaration::Height(size)
1116
+ });
1117
+ }
1118
+ }
1119
+
1120
+ // The `border` attribute maps to the four border widths as a
1121
+ // pixel length, plus the four border styles as `solid` -- width
1122
+ // alone would compute back to zero against the default
1123
+ // border-style of `none`. It is only these three elements:
1124
+ // `embed`, `iframe`, `marquee` and non-image `input` all have a
1125
+ // `border` attribute that must stay unmapped.
1126
+ if *name == local_name!("border")
1127
+ && (*tag == local_name!("img") || *tag == local_name!("object") || is_image_input)
1128
+ {
1129
+ if let Ok(px) = style::servo::attr::parse_unsigned_integer(value.chars()) {
1130
+ use style::values::specified::{BorderSideWidth, BorderStyle};
1131
+ let width = BorderSideWidth::from_px(px as f32);
1132
+ push_style(PropertyDeclaration::BorderTopWidth(width.clone()));
1133
+ push_style(PropertyDeclaration::BorderRightWidth(width.clone()));
1134
+ push_style(PropertyDeclaration::BorderBottomWidth(width.clone()));
1135
+ push_style(PropertyDeclaration::BorderLeftWidth(width));
1136
+ push_style(PropertyDeclaration::BorderTopStyle(BorderStyle::Solid));
1137
+ push_style(PropertyDeclaration::BorderRightStyle(BorderStyle::Solid));
1138
+ push_style(PropertyDeclaration::BorderBottomStyle(BorderStyle::Solid));
1139
+ push_style(PropertyDeclaration::BorderLeftStyle(BorderStyle::Solid));
1140
+ }
1141
+ }
1142
+
1143
+ // `body` carries four legacy margin attributes, as pixel lengths:
1144
+ // marginwidth and marginheight set both sides of an axis, and
1145
+ // leftmargin and topmargin set one side each.
1146
+ //
1147
+ // There is deliberately no `rightmargin` or `bottommargin`. They
1148
+ // look like the obvious counterparts to the two that exist, but
1149
+ // the spec does not define them and browsers ignore them in both
1150
+ // standards and quirks mode -- body-margin-3a/3b assert exactly
1151
+ // that.
1152
+ if *tag == local_name!("body") {
1153
+ // Matched as strings: these are not in the static atom set,
1154
+ // so `local_name!` will not compile for them.
1155
+ let sides: &[u8] = match &**name {
1156
+ "marginwidth" => b"lr",
1157
+ "marginheight" => b"tb",
1158
+ "leftmargin" => b"l",
1159
+ "topmargin" => b"t",
1160
+ _ => b"",
1161
+ };
1162
+ if !sides.is_empty() {
1163
+ if let Ok(px) = style::servo::attr::parse_unsigned_integer(value.chars()) {
1164
+ use style::values::generics::length::GenericMargin;
1165
+ use style::values::specified::{LengthPercentage, NoCalcLength};
1166
+ let margin = GenericMargin::LengthPercentage(LengthPercentage::Length(
1167
+ NoCalcLength::from_px(px as f32),
1168
+ ));
1169
+ for side in sides {
1170
+ push_style(match side {
1171
+ b'l' => PropertyDeclaration::MarginLeft(margin.clone()),
1172
+ b'r' => PropertyDeclaration::MarginRight(margin.clone()),
1173
+ b't' => PropertyDeclaration::MarginTop(margin.clone()),
1174
+ b'b' => PropertyDeclaration::MarginBottom(margin.clone()),
1175
+ _ => unreachable!("side table above only yields lrtb"),
1176
+ });
1177
+ }
1178
+ }
1179
+ }
1180
+ }
1181
+
1182
+ if *name == local_name!("bgcolor") {
1183
+ use style::values::specified::Color;
1184
+ if let Some((r, g, b, a)) = parse_color_attr(value) {
1185
+ push_style(PropertyDeclaration::BackgroundColor(
1186
+ Color::from_absolute_color(AbsoluteColor::srgb_legacy(r, g, b, a)),
1187
+ ));
1188
+ }
1189
+ }
1190
+
1191
+ if *name == local_name!("hidden") {
1192
+ use style::values::specified::Display;
1193
+ push_style(PropertyDeclaration::Display(Display::None));
1194
+ }
1195
+ }
1196
+
1197
+ if let Some(lang) = lang {
1198
+ push_style(PropertyDeclaration::XLang(XLang(Atom::from(lang))));
1199
+ }
1200
+ }
1201
+
1202
+ fn local_name(&self) -> &LocalName {
1203
+ &self.element_data().expect("Not an element").name.local
1204
+ }
1205
+
1206
+ fn namespace(&self) -> &Namespace {
1207
+ &self.element_data().expect("Not an element").name.ns
1208
+ }
1209
+
1210
+ fn query_container_size(
1211
+ &self,
1212
+ _display: &style::values::specified::Display,
1213
+ ) -> euclid::default::Size2D<Option<app_units::Au>> {
1214
+ // FIXME: Implement container queries. For now this effectively disables them without panicking.
1215
+ Default::default()
1216
+ }
1217
+
1218
+ fn each_custom_state<F>(&self, _callback: F)
1219
+ where
1220
+ F: FnMut(&AtomIdent),
1221
+ {
1222
+ todo!()
1223
+ }
1224
+
1225
+ fn has_selector_flags(&self, flags: ElementSelectorFlags) -> bool {
1226
+ self.selector_flags().get().contains(flags)
1227
+ }
1228
+
1229
+ fn relative_selector_search_direction(&self) -> ElementSelectorFlags {
1230
+ let flags = self.selector_flags().get();
1231
+ if flags.contains(ElementSelectorFlags::RELATIVE_SELECTOR_SEARCH_DIRECTION_ANCESTOR_SIBLING)
1232
+ {
1233
+ ElementSelectorFlags::RELATIVE_SELECTOR_SEARCH_DIRECTION_ANCESTOR_SIBLING
1234
+ } else if flags.contains(ElementSelectorFlags::RELATIVE_SELECTOR_SEARCH_DIRECTION_ANCESTOR)
1235
+ {
1236
+ ElementSelectorFlags::RELATIVE_SELECTOR_SEARCH_DIRECTION_ANCESTOR
1237
+ } else if flags.contains(ElementSelectorFlags::RELATIVE_SELECTOR_SEARCH_DIRECTION_SIBLING) {
1238
+ ElementSelectorFlags::RELATIVE_SELECTOR_SEARCH_DIRECTION_SIBLING
1239
+ } else {
1240
+ ElementSelectorFlags::empty()
1241
+ }
1242
+ }
1243
+
1244
+ fn compute_layout_damage(old: &ComputedValues, new: &ComputedValues) -> RestyleDamage {
1245
+ compute_layout_damage(old, new)
1246
+ // ALL_DAMAGE
1247
+ }
1248
+
1249
+ // fn update_animations(
1250
+ // &self,
1251
+ // before_change_style: Option<Arc<ComputedValues>>,
1252
+ // tasks: style::context::UpdateAnimationsTasks,
1253
+ // ) {
1254
+ // todo!()
1255
+ // }
1256
+
1257
+ // fn process_post_animation(&self, tasks: style::context::PostAnimationTasks) {
1258
+ // todo!()
1259
+ // }
1260
+
1261
+ // fn needs_transitions_update(
1262
+ // &self,
1263
+ // before_change_style: &ComputedValues,
1264
+ // after_change_style: &ComputedValues,
1265
+ // ) -> bool {
1266
+ // todo!()
1267
+ // }
1268
+ }
1269
+
1270
+ pub struct Traverser<'a> {
1271
+ // dom: &'a Slab<Node>,
1272
+ parent: BlitzNode<'a>,
1273
+ child_index: usize,
1274
+ }
1275
+
1276
+ impl<'a> Iterator for Traverser<'a> {
1277
+ type Item = BlitzNode<'a>;
1278
+
1279
+ fn next(&mut self) -> Option<Self::Item> {
1280
+ let node_id = self.parent.children.get(self.child_index)?;
1281
+ let node = self.parent.with(*node_id);
1282
+
1283
+ self.child_index += 1;
1284
+
1285
+ Some(node)
1286
+ }
1287
+ }
1288
+
1289
+ impl std::hash::Hash for BlitzNode<'_> {
1290
+ fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
1291
+ state.write_u64(self.id.as_u64())
1292
+ }
1293
+ }
1294
+
1295
+ /// Handle custom painters like images for layouting
1296
+ ///
1297
+ /// todo: actually implement this
1298
+ pub struct RegisteredPaintersImpl;
1299
+ impl RegisteredSpeculativePainters for RegisteredPaintersImpl {
1300
+ fn get(&self, _name: &Atom) -> Option<&dyn RegisteredSpeculativePainter> {
1301
+ None
1302
+ }
1303
+ }
1304
+
1305
+ use style::traversal::recalc_style_at;
1306
+
1307
+ pub struct RecalcStyle<'a> {
1308
+ context: SharedStyleContext<'a>,
1309
+ /// Nodes whose `background-image`/`mask-image` layers need flushing to
1310
+ /// dedicated storage on the node (see `flush_image_layers_from_style`)
1311
+ /// because their style changed during this traversal.
1312
+ nodes_needing_style_image_flush: Mutex<Vec<NodeId>>,
1313
+ }
1314
+
1315
+ impl<'a> RecalcStyle<'a> {
1316
+ pub fn new(context: SharedStyleContext<'a>) -> Self {
1317
+ RecalcStyle {
1318
+ context,
1319
+ nodes_needing_style_image_flush: Mutex::new(Vec::new()),
1320
+ }
1321
+ }
1322
+ }
1323
+
1324
+ #[allow(unsafe_code)]
1325
+ impl<'dom> DomTraversal<BlitzNode<'dom>> for RecalcStyle<'_> {
1326
+ fn process_preorder<F: FnMut(BlitzNode<'dom>)>(
1327
+ &self,
1328
+ context: &mut StyleContext<BlitzNode<'dom>>,
1329
+ node: BlitzNode<'dom>,
1330
+ note_child: F,
1331
+ ) {
1332
+ if let Some(el) = node.as_element() {
1333
+ // let mut data = el.mutate_data().unwrap();
1334
+ let mut data = unsafe { el.ensure_data() };
1335
+ recalc_style_at(self, context, el, &mut data, note_child);
1336
+
1337
+ sync_pseudo_element_styles(el, &data, &self.nodes_needing_style_image_flush);
1338
+
1339
+ if !data.damage.is_empty() {
1340
+ // Mark the ancestor chain so that the damage propagation pass
1341
+ // visits this element.
1342
+ el.mark_damaged();
1343
+
1344
+ if data
1345
+ .styles
1346
+ .get_primary()
1347
+ .is_some_and(|style| needs_style_image_flush(el, style))
1348
+ {
1349
+ self.nodes_needing_style_image_flush
1350
+ .lock()
1351
+ .unwrap()
1352
+ .push(el.id);
1353
+ }
1354
+ }
1355
+
1356
+ // Gets set later on
1357
+ el.unset_dirty_descendants();
1358
+ }
1359
+ }
1360
+
1361
+ #[inline]
1362
+ fn needs_postorder_traversal() -> bool {
1363
+ false
1364
+ }
1365
+
1366
+ fn process_postorder(
1367
+ &self,
1368
+ _style_context: &mut StyleContext<BlitzNode<'dom>>,
1369
+ _node: BlitzNode<'dom>,
1370
+ ) {
1371
+ panic!("this should never be called")
1372
+ }
1373
+
1374
+ #[inline]
1375
+ fn shared_context(&self) -> &SharedStyleContext<'_> {
1376
+ &self.context
1377
+ }
1378
+ }
1379
+
1380
+ /// Flush updated pseudo-element (`::before`/`::after`) styles from the owning
1381
+ /// element's freshly-computed stylo data to the pseudo-element's anonymous node.
1382
+ ///
1383
+ /// Pseudo-element styles are normally flushed to the pseudo-element's node
1384
+ /// during box construction (see `flush_pseudo_elements`), but in incremental
1385
+ /// mode box construction only runs for nodes with construction damage.
1386
+ /// Pseudo-element style changes which don't require reconstruction (e.g.
1387
+ /// animations/transitions of repaint- or relayout-only properties) must still
1388
+ /// be flushed to the pseudo-element's node - along with the damage they imply -
1389
+ /// so that layout and paint see the new style.
1390
+ ///
1391
+ /// This runs during the style traversal, immediately after the owning element
1392
+ /// has been restyled, because that is where the old and new pseudo styles can
1393
+ /// be diffed precisely (the pseudo node's stored primary style is the "old"
1394
+ /// style; the owner's just-computed pseudo styles are the "new" ones).
1395
+ ///
1396
+ /// SAFETY: the style traversal has exclusive access to the tree, and each
1397
+ /// pseudo-element node is only ever accessed from its owning element's
1398
+ /// `process_preorder` (pseudo nodes are not part of the DOM child list and are
1399
+ /// not themselves visited by the traversal), so mutating the pseudo node's
1400
+ /// stylo data here cannot race with any other traversal thread.
1401
+ #[allow(unsafe_code)]
1402
+ fn sync_pseudo_element_styles(
1403
+ el: &Node,
1404
+ data: &style::data::ElementData,
1405
+ nodes_needing_style_image_flush: &Mutex<Vec<NodeId>>,
1406
+ ) {
1407
+ let before_node_id = el.before();
1408
+ let after_node_id = el.after();
1409
+ if before_node_id.is_none() && after_node_id.is_none() {
1410
+ return;
1411
+ }
1412
+
1413
+ // Note: yes these are kinda backwards (see `flush_pseudo_elements`)
1414
+ let pseudos = data.styles.pseudos.as_array();
1415
+ let before_style = pseudos[1].clone();
1416
+ let after_style = pseudos[0].clone();
1417
+
1418
+ // Creation and removal of pseudo-elements is handled during box construction
1419
+ // (Stylo generates construction damage for those cases), so only the case
1420
+ // where the pseudo-element both was and remains present is handled here.
1421
+ for (pe_node_id, pe_style) in [(before_node_id, before_style), (after_node_id, after_style)] {
1422
+ let (Some(pe_node_id), Some(pe_style)) = (pe_node_id, pe_style) else {
1423
+ continue;
1424
+ };
1425
+ let pe_node = el.with(pe_node_id);
1426
+ let Some(stylo_data) = pe_node.try_stylo_element_data() else {
1427
+ continue;
1428
+ };
1429
+ let mut pe_data = match unsafe { stylo_data.unsafe_stylo_only_mut() } {
1430
+ Some(data) => data,
1431
+ None => continue,
1432
+ };
1433
+ let Some(old_style) = pe_data.styles.primary.clone() else {
1434
+ continue;
1435
+ };
1436
+ if std::ptr::eq(&*old_style, &*pe_style) {
1437
+ continue;
1438
+ }
1439
+
1440
+ let diff = RestyleDamage::compute_style_difference::<&Node>(&old_style, &pe_style);
1441
+ if !diff.damage.is_empty() {
1442
+ pe_data.damage.insert(diff.damage);
1443
+ pe_node.mark_damaged();
1444
+
1445
+ if needs_style_image_flush(pe_node, &pe_style) {
1446
+ nodes_needing_style_image_flush
1447
+ .lock()
1448
+ .unwrap()
1449
+ .push(pe_node_id);
1450
+ }
1451
+ }
1452
+ pe_data.styles.primary = Some(pe_style);
1453
+ pe_data.set_restyled();
1454
+ }
1455
+ }
1456
+
1457
+ /// Whether a node's `background-image`/`mask-image` layers need flushing to
1458
+ /// dedicated storage on the node: the URLs referenced by the new style differ
1459
+ /// from the image data currently stored on the node.
1460
+ fn needs_style_image_flush(node: &Node, style: &ComputedValues) -> bool {
1461
+ use crate::node::ImageResourceData;
1462
+ use style::url::ComputedUrl;
1463
+ use style::values::computed::image::Image;
1464
+
1465
+ fn out_of_sync(style_images: &[Image], stored: &[Option<ImageResourceData>]) -> bool {
1466
+ if style_images.len() != stored.len() {
1467
+ // Differing lengths only matter if either side references an image
1468
+ // (a style list of `none` layers and empty storage are in sync).
1469
+ return style_images
1470
+ .iter()
1471
+ .any(|image| matches!(image, Image::Url(_)))
1472
+ || stored.iter().any(Option::is_some);
1473
+ }
1474
+ std::iter::zip(style_images, stored).any(|(style_image, stored)| {
1475
+ match (style_image, stored) {
1476
+ (Image::Url(ComputedUrl::Valid(url)), Some(data)) => **url != *data.url,
1477
+ (Image::Url(ComputedUrl::Valid(_)), None) => true,
1478
+ (_, Some(_)) => true,
1479
+ (_, None) => false,
1480
+ }
1481
+ })
1482
+ }
1483
+
1484
+ node.data.downcast_element().is_some_and(|el| {
1485
+ out_of_sync(
1486
+ &style.get_background().background_image.0,
1487
+ &el.background_images,
1488
+ ) || out_of_sync(&style.get_svg().mask_image.0, &el.mask_images)
1489
+ })
1490
+ }
1491
+
1492
+ #[test]
1493
+ fn assert_size_of_equals() {
1494
+ // use std::mem;
1495
+
1496
+ // fn assert_layout<E>() {
1497
+ // assert_eq!(
1498
+ // mem::size_of::<SharingCache<E>>(),
1499
+ // mem::size_of::<TypelessSharingCache>()
1500
+ // );
1501
+ // assert_eq!(
1502
+ // mem::align_of::<SharingCache<E>>(),
1503
+ // mem::align_of::<TypelessSharingCache>()
1504
+ // );
1505
+ // }
1506
+
1507
+ // let size = mem::size_of::<StyleSharingCandidate<BlitzNode>>();
1508
+ // dbg!(size);
1509
+ }
1510
+
1511
+ #[test]
1512
+ fn parse_inline() {
1513
+ // let attrs = style::attr::AttrValue::from_serialized_tokenlist(
1514
+ // r#"visibility: hidden; left: 1306.5px; top: 50px; display: none;"#.to_string(),
1515
+ // );
1516
+
1517
+ // let val = CSSInlineStyleDeclaration();
1518
+ }