Source code

Revision control

Other Tools

1
/* This Source Code Form is subject to the terms of the Mozilla Public
2
* License, v. 2.0. If a copy of the MPL was not distributed with this
3
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5
//! Style resolution for a given element or pseudo-element.
6
7
use crate::applicable_declarations::ApplicableDeclarationList;
8
use crate::context::{CascadeInputs, ElementCascadeInputs, StyleContext};
9
use crate::data::{EagerPseudoStyles, ElementStyles};
10
use crate::dom::TElement;
11
use crate::matching::MatchMethods;
12
use crate::properties::longhands::display::computed_value::T as Display;
13
use crate::properties::{AnimationRules, ComputedValues};
14
use crate::rule_tree::StrongRuleNode;
15
use crate::selector_parser::{PseudoElement, SelectorImpl};
16
use crate::stylist::RuleInclusion;
17
use log::Level::Trace;
18
use selectors::matching::{ElementSelectorFlags, MatchingContext};
19
use selectors::matching::{MatchingMode, VisitedHandlingMode};
20
use servo_arc::Arc;
21
22
/// Whether pseudo-elements should be resolved or not.
23
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
24
pub enum PseudoElementResolution {
25
/// Only resolve pseudo-styles if possibly applicable.
26
IfApplicable,
27
/// Force pseudo-element resolution.
28
Force,
29
}
30
31
/// A struct that takes care of resolving the style of a given element.
32
pub struct StyleResolverForElement<'a, 'ctx, 'le, E>
33
where
34
'ctx: 'a,
35
'le: 'ctx,
36
E: TElement + MatchMethods + 'le,
37
{
38
element: E,
39
context: &'a mut StyleContext<'ctx, E>,
40
rule_inclusion: RuleInclusion,
41
pseudo_resolution: PseudoElementResolution,
42
_marker: ::std::marker::PhantomData<&'le E>,
43
}
44
45
struct MatchingResults {
46
rule_node: StrongRuleNode,
47
}
48
49
/// A style returned from the resolver machinery.
50
pub struct ResolvedStyle(pub Arc<ComputedValues>);
51
52
/// The primary style of an element or an element-backed pseudo-element.
53
pub struct PrimaryStyle {
54
/// The style itself.
55
pub style: ResolvedStyle,
56
/// Whether the style was reused from another element via the rule node (see
57
/// `StyleSharingCache::lookup_by_rules`).
58
pub reused_via_rule_node: bool,
59
}
60
61
/// A set of style returned from the resolver machinery.
62
pub struct ResolvedElementStyles {
63
/// Primary style.
64
pub primary: PrimaryStyle,
65
/// Pseudo styles.
66
pub pseudos: EagerPseudoStyles,
67
}
68
69
impl PrimaryStyle {
70
/// Convenience accessor for the style.
71
pub fn style(&self) -> &ComputedValues {
72
&*self.style.0
73
}
74
}
75
76
impl From<ResolvedElementStyles> for ElementStyles {
77
fn from(r: ResolvedElementStyles) -> ElementStyles {
78
ElementStyles {
79
primary: Some(r.primary.style.0),
80
pseudos: r.pseudos,
81
}
82
}
83
}
84
85
fn with_default_parent_styles<E, F, R>(element: E, f: F) -> R
86
where
87
E: TElement,
88
F: FnOnce(Option<&ComputedValues>, Option<&ComputedValues>) -> R,
89
{
90
let parent_el = element.inheritance_parent();
91
let parent_data = parent_el.as_ref().and_then(|e| e.borrow_data());
92
let parent_style = parent_data.as_ref().map(|d| d.styles.primary());
93
94
let mut layout_parent_el = parent_el.clone();
95
let layout_parent_data;
96
let mut layout_parent_style = parent_style;
97
if parent_style.map_or(false, |s| s.is_display_contents()) {
98
layout_parent_el = Some(layout_parent_el.unwrap().layout_parent());
99
layout_parent_data = layout_parent_el.as_ref().unwrap().borrow_data().unwrap();
100
layout_parent_style = Some(layout_parent_data.styles.primary());
101
}
102
103
f(
104
parent_style.map(|x| &**x),
105
layout_parent_style.map(|s| &**s),
106
)
107
}
108
109
fn eager_pseudo_is_definitely_not_generated(
110
pseudo: &PseudoElement,
111
style: &ComputedValues,
112
) -> bool {
113
use crate::properties::computed_value_flags::ComputedValueFlags;
114
115
if !pseudo.is_before_or_after() {
116
return false;
117
}
118
119
if !style.flags.intersects(ComputedValueFlags::INHERITS_DISPLAY) &&
120
style.get_box().clone_display() == Display::None
121
{
122
return true;
123
}
124
125
if !style.flags.intersects(ComputedValueFlags::INHERITS_CONTENT) &&
126
style.ineffective_content_property()
127
{
128
return true;
129
}
130
131
false
132
}
133
134
impl<'a, 'ctx, 'le, E> StyleResolverForElement<'a, 'ctx, 'le, E>
135
where
136
'ctx: 'a,
137
'le: 'ctx,
138
E: TElement + MatchMethods + 'le,
139
{
140
/// Trivially construct a new StyleResolverForElement.
141
pub fn new(
142
element: E,
143
context: &'a mut StyleContext<'ctx, E>,
144
rule_inclusion: RuleInclusion,
145
pseudo_resolution: PseudoElementResolution,
146
) -> Self {
147
Self {
148
element,
149
context,
150
rule_inclusion,
151
pseudo_resolution,
152
_marker: ::std::marker::PhantomData,
153
}
154
}
155
156
/// Resolve just the style of a given element.
157
pub fn resolve_primary_style(
158
&mut self,
159
parent_style: Option<&ComputedValues>,
160
layout_parent_style: Option<&ComputedValues>,
161
) -> PrimaryStyle {
162
let primary_results = self.match_primary(VisitedHandlingMode::AllLinksUnvisited);
163
164
let inside_link = parent_style.map_or(false, |s| s.visited_style().is_some());
165
166
let visited_rules = if self.context.shared.visited_styles_enabled &&
167
(inside_link || self.element.is_link())
168
{
169
let visited_matching_results =
170
self.match_primary(VisitedHandlingMode::RelevantLinkVisited);
171
Some(visited_matching_results.rule_node)
172
} else {
173
None
174
};
175
176
self.cascade_primary_style(
177
CascadeInputs {
178
rules: Some(primary_results.rule_node),
179
visited_rules,
180
},
181
parent_style,
182
layout_parent_style,
183
)
184
}
185
186
fn cascade_primary_style(
187
&mut self,
188
inputs: CascadeInputs,
189
parent_style: Option<&ComputedValues>,
190
layout_parent_style: Option<&ComputedValues>,
191
) -> PrimaryStyle {
192
// Before doing the cascade, check the sharing cache and see if we can
193
// reuse the style via rule node identity.
194
let may_reuse = !self.element.is_in_native_anonymous_subtree() &&
195
parent_style.is_some() &&
196
inputs.rules.is_some();
197
198
if may_reuse {
199
let cached = self.context.thread_local.sharing_cache.lookup_by_rules(
200
self.context.shared,
201
parent_style.unwrap(),
202
inputs.rules.as_ref().unwrap(),
203
inputs.visited_rules.as_ref(),
204
self.element,
205
);
206
if let Some(mut primary_style) = cached {
207
self.context.thread_local.statistics.styles_reused += 1;
208
primary_style.reused_via_rule_node |= true;
209
return primary_style;
210
}
211
}
212
213
// No style to reuse. Cascade the style, starting with visited style
214
// if necessary.
215
PrimaryStyle {
216
style: self.cascade_style_and_visited(
217
inputs,
218
parent_style,
219
layout_parent_style,
220
/* pseudo = */ None,
221
),
222
reused_via_rule_node: false,
223
}
224
}
225
226
/// Resolve the style of a given element, and all its eager pseudo-elements.
227
pub fn resolve_style(
228
&mut self,
229
parent_style: Option<&ComputedValues>,
230
layout_parent_style: Option<&ComputedValues>,
231
) -> ResolvedElementStyles {
232
let primary_style = self.resolve_primary_style(parent_style, layout_parent_style);
233
234
let mut pseudo_styles = EagerPseudoStyles::default();
235
236
if !self.element.is_pseudo_element() {
237
let layout_parent_style_for_pseudo = if primary_style.style().is_display_contents() {
238
layout_parent_style
239
} else {
240
Some(primary_style.style())
241
};
242
SelectorImpl::each_eagerly_cascaded_pseudo_element(|pseudo| {
243
let pseudo_style = self.resolve_pseudo_style(
244
&pseudo,
245
&primary_style,
246
layout_parent_style_for_pseudo,
247
);
248
249
if let Some(style) = pseudo_style {
250
if !matches!(self.pseudo_resolution, PseudoElementResolution::Force) &&
251
eager_pseudo_is_definitely_not_generated(&pseudo, &style.0)
252
{
253
return;
254
}
255
pseudo_styles.set(&pseudo, style.0);
256
}
257
})
258
}
259
260
ResolvedElementStyles {
261
primary: primary_style,
262
pseudos: pseudo_styles,
263
}
264
}
265
266
/// Resolve an element's styles with the default inheritance parent/layout
267
/// parents.
268
pub fn resolve_style_with_default_parents(&mut self) -> ResolvedElementStyles {
269
with_default_parent_styles(self.element, |parent_style, layout_parent_style| {
270
self.resolve_style(parent_style, layout_parent_style)
271
})
272
}
273
274
/// Cascade a set of rules, using the default parent for inheritance.
275
pub fn cascade_style_and_visited_with_default_parents(
276
&mut self,
277
inputs: CascadeInputs,
278
) -> ResolvedStyle {
279
with_default_parent_styles(self.element, |parent_style, layout_parent_style| {
280
self.cascade_style_and_visited(
281
inputs,
282
parent_style,
283
layout_parent_style,
284
/* pseudo = */ None,
285
)
286
})
287
}
288
289
fn cascade_style_and_visited(
290
&mut self,
291
inputs: CascadeInputs,
292
parent_style: Option<&ComputedValues>,
293
layout_parent_style: Option<&ComputedValues>,
294
pseudo: Option<&PseudoElement>,
295
) -> ResolvedStyle {
296
debug_assert!(pseudo.map_or(true, |p| p.is_eager()));
297
298
let implemented_pseudo = self.element.implemented_pseudo_element();
299
let pseudo = pseudo.or(implemented_pseudo.as_ref());
300
301
let mut conditions = Default::default();
302
let values = self.context.shared.stylist.cascade_style_and_visited(
303
Some(self.element),
304
pseudo,
305
inputs,
306
&self.context.shared.guards,
307
parent_style,
308
parent_style,
309
layout_parent_style,
310
&self.context.thread_local.font_metrics_provider,
311
Some(&self.context.thread_local.rule_cache),
312
&mut conditions,
313
);
314
315
self.context.thread_local.rule_cache.insert_if_possible(
316
&self.context.shared.guards,
317
&values,
318
pseudo,
319
&conditions,
320
);
321
322
ResolvedStyle(values)
323
}
324
325
/// Cascade the element and pseudo-element styles with the default parents.
326
pub fn cascade_styles_with_default_parents(
327
&mut self,
328
inputs: ElementCascadeInputs,
329
) -> ResolvedElementStyles {
330
with_default_parent_styles(self.element, move |parent_style, layout_parent_style| {
331
let primary_style =
332
self.cascade_primary_style(inputs.primary, parent_style, layout_parent_style);
333
334
let mut pseudo_styles = EagerPseudoStyles::default();
335
if let Some(mut pseudo_array) = inputs.pseudos.into_array() {
336
let layout_parent_style_for_pseudo = if primary_style.style().is_display_contents()
337
{
338
layout_parent_style
339
} else {
340
Some(primary_style.style())
341
};
342
343
for (i, inputs) in pseudo_array.iter_mut().enumerate() {
344
if let Some(inputs) = inputs.take() {
345
let pseudo = PseudoElement::from_eager_index(i);
346
347
let style = self.cascade_style_and_visited(
348
inputs,
349
Some(primary_style.style()),
350
layout_parent_style_for_pseudo,
351
Some(&pseudo),
352
);
353
354
if !matches!(self.pseudo_resolution, PseudoElementResolution::Force) &&
355
eager_pseudo_is_definitely_not_generated(&pseudo, &style.0)
356
{
357
continue;
358
}
359
360
pseudo_styles.set(&pseudo, style.0);
361
}
362
}
363
}
364
365
ResolvedElementStyles {
366
primary: primary_style,
367
pseudos: pseudo_styles,
368
}
369
})
370
}
371
372
fn resolve_pseudo_style(
373
&mut self,
374
pseudo: &PseudoElement,
375
originating_element_style: &PrimaryStyle,
376
layout_parent_style: Option<&ComputedValues>,
377
) -> Option<ResolvedStyle> {
378
let rules = self.match_pseudo(
379
originating_element_style.style(),
380
pseudo,
381
VisitedHandlingMode::AllLinksUnvisited,
382
)?;
383
384
let mut visited_rules = None;
385
if originating_element_style.style().visited_style().is_some() {
386
visited_rules = self.match_pseudo(
387
originating_element_style.style(),
388
pseudo,
389
VisitedHandlingMode::RelevantLinkVisited,
390
);
391
}
392
393
Some(self.cascade_style_and_visited(
394
CascadeInputs {
395
rules: Some(rules),
396
visited_rules,
397
},
398
Some(originating_element_style.style()),
399
layout_parent_style,
400
Some(pseudo),
401
))
402
}
403
404
fn match_primary(&mut self, visited_handling: VisitedHandlingMode) -> MatchingResults {
405
debug!(
406
"Match primary for {:?}, visited: {:?}",
407
self.element, visited_handling
408
);
409
let mut applicable_declarations = ApplicableDeclarationList::new();
410
411
let map = &mut self.context.thread_local.selector_flags;
412
let bloom_filter = self.context.thread_local.bloom_filter.filter();
413
let nth_index_cache = &mut self.context.thread_local.nth_index_cache;
414
let mut matching_context = MatchingContext::new_for_visited(
415
MatchingMode::Normal,
416
Some(bloom_filter),
417
Some(nth_index_cache),
418
visited_handling,
419
self.context.shared.quirks_mode(),
420
);
421
422
let stylist = &self.context.shared.stylist;
423
let implemented_pseudo = self.element.implemented_pseudo_element();
424
{
425
let resolving_element = self.element;
426
let mut set_selector_flags = |element: &E, flags: ElementSelectorFlags| {
427
resolving_element.apply_selector_flags(map, element, flags);
428
};
429
430
// Compute the primary rule node.
431
stylist.push_applicable_declarations(
432
self.element,
433
implemented_pseudo.as_ref(),
434
self.element.style_attribute(),
435
self.element.smil_override(),
436
self.element.animation_rules(),
437
self.rule_inclusion,
438
&mut applicable_declarations,
439
&mut matching_context,
440
&mut set_selector_flags,
441
);
442
}
443
444
// FIXME(emilio): This is a hack for animations, and should go away.
445
self.element.unset_dirty_style_attribute();
446
447
let rule_node = stylist
448
.rule_tree()
449
.compute_rule_node(&mut applicable_declarations, &self.context.shared.guards);
450
451
if log_enabled!(Trace) {
452
trace!("Matched rules for {:?}:", self.element);
453
for rn in rule_node.self_and_ancestors() {
454
let source = rn.style_source();
455
if source.is_some() {
456
trace!(" > {:?}", source);
457
}
458
}
459
}
460
461
MatchingResults { rule_node }
462
}
463
464
fn match_pseudo(
465
&mut self,
466
originating_element_style: &ComputedValues,
467
pseudo_element: &PseudoElement,
468
visited_handling: VisitedHandlingMode,
469
) -> Option<StrongRuleNode> {
470
debug!(
471
"Match pseudo {:?} for {:?}, visited: {:?}",
472
self.element, pseudo_element, visited_handling
473
);
474
debug_assert!(pseudo_element.is_eager());
475
debug_assert!(
476
!self.element.is_pseudo_element(),
477
"Element pseudos can't have any other eager pseudo."
478
);
479
480
let mut applicable_declarations = ApplicableDeclarationList::new();
481
482
let stylist = &self.context.shared.stylist;
483
484
if !self
485
.element
486
.may_generate_pseudo(pseudo_element, originating_element_style)
487
{
488
return None;
489
}
490
491
let bloom_filter = self.context.thread_local.bloom_filter.filter();
492
let nth_index_cache = &mut self.context.thread_local.nth_index_cache;
493
494
let mut matching_context = MatchingContext::new_for_visited(
495
MatchingMode::ForStatelessPseudoElement,
496
Some(bloom_filter),
497
Some(nth_index_cache),
498
visited_handling,
499
self.context.shared.quirks_mode(),
500
);
501
502
let map = &mut self.context.thread_local.selector_flags;
503
let resolving_element = self.element;
504
let mut set_selector_flags = |element: &E, flags: ElementSelectorFlags| {
505
resolving_element.apply_selector_flags(map, element, flags);
506
};
507
508
// NB: We handle animation rules for ::before and ::after when
509
// traversing them.
510
stylist.push_applicable_declarations(
511
self.element,
512
Some(pseudo_element),
513
None,
514
None,
515
AnimationRules(None, None),
516
self.rule_inclusion,
517
&mut applicable_declarations,
518
&mut matching_context,
519
&mut set_selector_flags,
520
);
521
522
if applicable_declarations.is_empty() {
523
return None;
524
}
525
526
let rule_node = stylist
527
.rule_tree()
528
.compute_rule_node(&mut applicable_declarations, &self.context.shared.guards);
529
530
Some(rule_node)
531
}
532
}