blitz-pdf 0.1.2 → 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +26 -0
- package/THIRD_PARTY_NOTICES.md +5 -2
- package/docs/layout-regressions.md +65 -0
- package/docs/pagination-regressions.md +89 -0
- package/package.json +2 -1
- package/vendor/README.md +35 -0
- package/vendor/blitz-dom/Cargo.toml +113 -0
- package/vendor/blitz-dom/LICENSE-APACHE +176 -0
- package/vendor/blitz-dom/LICENSE-MIT +23 -0
- package/vendor/blitz-dom/assets/default.css +1142 -0
- package/vendor/blitz-dom/assets/moz-bullet-font.otf +0 -0
- package/vendor/blitz-dom/src/accessibility.rs +255 -0
- package/vendor/blitz-dom/src/config.rs +97 -0
- package/vendor/blitz-dom/src/cssom.rs +971 -0
- package/vendor/blitz-dom/src/debug.rs +206 -0
- package/vendor/blitz-dom/src/document.rs +3395 -0
- package/vendor/blitz-dom/src/events/driver.rs +427 -0
- package/vendor/blitz-dom/src/events/focus.rs +40 -0
- package/vendor/blitz-dom/src/events/ime.rs +31 -0
- package/vendor/blitz-dom/src/events/keyboard.rs +174 -0
- package/vendor/blitz-dom/src/events/mod.rs +315 -0
- package/vendor/blitz-dom/src/events/pointer.rs +849 -0
- package/vendor/blitz-dom/src/font_metrics.rs +177 -0
- package/vendor/blitz-dom/src/form.rs +463 -0
- package/vendor/blitz-dom/src/html.rs +38 -0
- package/vendor/blitz-dom/src/iframe.rs +186 -0
- package/vendor/blitz-dom/src/layout/construct.rs +1328 -0
- package/vendor/blitz-dom/src/layout/damage.rs +642 -0
- package/vendor/blitz-dom/src/layout/inline.rs +1160 -0
- package/vendor/blitz-dom/src/layout/list.rs +237 -0
- package/vendor/blitz-dom/src/layout/mod.rs +969 -0
- package/vendor/blitz-dom/src/layout/paint_tree.rs +462 -0
- package/vendor/blitz-dom/src/layout/replaced.rs +439 -0
- package/vendor/blitz-dom/src/layout/table.rs +967 -0
- package/vendor/blitz-dom/src/layout/text_transform.rs +979 -0
- package/vendor/blitz-dom/src/layout/writing_mode.rs +437 -0
- package/vendor/blitz-dom/src/lib.rs +160 -0
- package/vendor/blitz-dom/src/mutator.rs +1818 -0
- package/vendor/blitz-dom/src/net.rs +649 -0
- package/vendor/blitz-dom/src/node/attributes.rs +60 -0
- package/vendor/blitz-dom/src/node/custom_widget.rs +178 -0
- package/vendor/blitz-dom/src/node/element.rs +1106 -0
- package/vendor/blitz-dom/src/node/mod.rs +30 -0
- package/vendor/blitz-dom/src/node/node.rs +2036 -0
- package/vendor/blitz-dom/src/node/scrollbar.rs +237 -0
- package/vendor/blitz-dom/src/node/serialize.rs +225 -0
- package/vendor/blitz-dom/src/node/stylo_data.rs +142 -0
- package/vendor/blitz-dom/src/node/svg.rs +265 -0
- package/vendor/blitz-dom/src/node/text.rs +804 -0
- package/vendor/blitz-dom/src/query_selector.rs +275 -0
- package/vendor/blitz-dom/src/resolve.rs +474 -0
- package/vendor/blitz-dom/src/resolved_style.rs +797 -0
- package/vendor/blitz-dom/src/scrolling.rs +780 -0
- package/vendor/blitz-dom/src/selection.rs +122 -0
- package/vendor/blitz-dom/src/stylo.rs +1518 -0
- package/vendor/blitz-dom/src/stylo_device.rs +88 -0
- package/vendor/blitz-dom/src/stylo_to_cursor_icon.rs +49 -0
- package/vendor/blitz-dom/src/stylo_to_kurbo.rs +129 -0
- package/vendor/blitz-dom/src/stylo_to_parley.rs +655 -0
- package/vendor/blitz-dom/src/traversal.rs +600 -0
- package/vendor/blitz-dom/src/tree.rs +136 -0
- package/vendor/blitz-dom/src/url.rs +63 -0
- package/vendor/blitz-dom/src/util.rs +256 -0
- package/wasm/blitz_pdf_bg.wasm +0 -0
|
@@ -0,0 +1,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
|
+
}
|