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 //! The main cascading algorithm of the style system.
6
7 use crate::applicable_declarations::CascadePriority;
8 use crate::context::QuirksMode;
9 use crate::custom_properties::CustomPropertiesBuilder;
10 use crate::dom::TElement;
11 use crate::font_metrics::FontMetricsProvider;
12 use crate::logical_geometry::WritingMode;
13 use crate::media_queries::Device;
14 use crate::properties::{
15 CSSWideKeyword, ComputedValueFlags, ComputedValues, DeclarationImportanceIterator, Importance,
16 LonghandId, LonghandIdSet, PropertyDeclaration, PropertyDeclarationId, PropertyFlags,
17 ShorthandsWithPropertyReferencesCache, StyleBuilder, CASCADE_PROPERTY,
18 };
19 use crate::rule_cache::{RuleCache, RuleCacheConditions};
20 use crate::rule_tree::{StrongRuleNode, CascadeLevel};
21 use crate::selector_parser::PseudoElement;
22 use crate::shared_lock::StylesheetGuards;
23 use crate::style_adjuster::StyleAdjuster;
24 use crate::stylesheets::{Origin, layer_rule::LayerOrder};
25 use crate::values::{computed, specified};
26 use fxhash::FxHashMap;
27 use servo_arc::Arc;
28 use smallvec::SmallVec;
29 use std::borrow::Cow;
30 use std::cell::RefCell;
31
32 /// We split the cascade in two phases: 'early' properties, and 'late'
33 /// properties.
34 ///
35 /// Early properties are the ones that don't have dependencies _and_ other
36 /// properties depend on, for example, writing-mode related properties, color
37 /// (for currentColor), or font-size (for em, etc).
38 ///
39 /// Late properties are all the others.
40 trait CascadePhase {
is_early() -> bool41 fn is_early() -> bool;
42 }
43
44 struct EarlyProperties;
45 impl CascadePhase for EarlyProperties {
is_early() -> bool46 fn is_early() -> bool {
47 true
48 }
49 }
50
51 struct LateProperties;
52 impl CascadePhase for LateProperties {
is_early() -> bool53 fn is_early() -> bool {
54 false
55 }
56 }
57
58 #[derive(Clone, Copy, Debug, Eq, PartialEq)]
59 enum ApplyResetProperties {
60 No,
61 Yes,
62 }
63
64 /// Performs the CSS cascade, computing new styles for an element from its parent style.
65 ///
66 /// The arguments are:
67 ///
68 /// * `device`: Used to get the initial viewport and other external state.
69 ///
70 /// * `rule_node`: The rule node in the tree that represent the CSS rules that
71 /// matched.
72 ///
73 /// * `parent_style`: The parent style, if applicable; if `None`, this is the root node.
74 ///
75 /// Returns the computed values.
76 /// * `flags`: Various flags.
77 ///
cascade<E>( device: &Device, pseudo: Option<&PseudoElement>, rule_node: &StrongRuleNode, guards: &StylesheetGuards, parent_style: Option<&ComputedValues>, parent_style_ignoring_first_line: Option<&ComputedValues>, layout_parent_style: Option<&ComputedValues>, visited_rules: Option<&StrongRuleNode>, font_metrics_provider: &dyn FontMetricsProvider, quirks_mode: QuirksMode, rule_cache: Option<&RuleCache>, rule_cache_conditions: &mut RuleCacheConditions, element: Option<E>, ) -> Arc<ComputedValues> where E: TElement,78 pub fn cascade<E>(
79 device: &Device,
80 pseudo: Option<&PseudoElement>,
81 rule_node: &StrongRuleNode,
82 guards: &StylesheetGuards,
83 parent_style: Option<&ComputedValues>,
84 parent_style_ignoring_first_line: Option<&ComputedValues>,
85 layout_parent_style: Option<&ComputedValues>,
86 visited_rules: Option<&StrongRuleNode>,
87 font_metrics_provider: &dyn FontMetricsProvider,
88 quirks_mode: QuirksMode,
89 rule_cache: Option<&RuleCache>,
90 rule_cache_conditions: &mut RuleCacheConditions,
91 element: Option<E>,
92 ) -> Arc<ComputedValues>
93 where
94 E: TElement,
95 {
96 cascade_rules(
97 device,
98 pseudo,
99 rule_node,
100 guards,
101 parent_style,
102 parent_style_ignoring_first_line,
103 layout_parent_style,
104 font_metrics_provider,
105 CascadeMode::Unvisited { visited_rules },
106 quirks_mode,
107 rule_cache,
108 rule_cache_conditions,
109 element,
110 )
111 }
112
113 struct DeclarationIterator<'a> {
114 // Global to the iteration.
115 guards: &'a StylesheetGuards<'a>,
116 restriction: Option<PropertyFlags>,
117 // The rule we're iterating over.
118 current_rule_node: Option<&'a StrongRuleNode>,
119 // Per rule state.
120 declarations: DeclarationImportanceIterator<'a>,
121 origin: Origin,
122 importance: Importance,
123 priority: CascadePriority,
124 }
125
126 impl<'a> DeclarationIterator<'a> {
127 #[inline]
new( rule_node: &'a StrongRuleNode, guards: &'a StylesheetGuards, pseudo: Option<&PseudoElement>, ) -> Self128 fn new(
129 rule_node: &'a StrongRuleNode,
130 guards: &'a StylesheetGuards,
131 pseudo: Option<&PseudoElement>,
132 ) -> Self {
133 let restriction = pseudo.and_then(|p| p.property_restriction());
134 let mut iter = Self {
135 guards,
136 current_rule_node: Some(rule_node),
137 origin: Origin::UserAgent,
138 importance: Importance::Normal,
139 priority: CascadePriority::new(CascadeLevel::UANormal, LayerOrder::root()),
140 declarations: DeclarationImportanceIterator::default(),
141 restriction,
142 };
143 iter.update_for_node(rule_node);
144 iter
145 }
146
update_for_node(&mut self, node: &'a StrongRuleNode)147 fn update_for_node(&mut self, node: &'a StrongRuleNode) {
148 self.priority = node.cascade_priority();
149 let level = self.priority.cascade_level();
150 self.origin = level.origin();
151 self.importance = level.importance();
152 let guard = match self.origin {
153 Origin::Author => self.guards.author,
154 Origin::User | Origin::UserAgent => self.guards.ua_or_user,
155 };
156 self.declarations = match node.style_source() {
157 Some(source) => source.read(guard).declaration_importance_iter(),
158 None => DeclarationImportanceIterator::default(),
159 };
160 }
161 }
162
163 impl<'a> Iterator for DeclarationIterator<'a> {
164 type Item = (&'a PropertyDeclaration, CascadePriority);
165
166 #[inline]
next(&mut self) -> Option<Self::Item>167 fn next(&mut self) -> Option<Self::Item> {
168 loop {
169 if let Some((decl, importance)) = self.declarations.next_back() {
170 if self.importance != importance {
171 continue;
172 }
173
174 if let Some(restriction) = self.restriction {
175 // decl.id() is either a longhand or a custom
176 // property. Custom properties are always allowed, but
177 // longhands are only allowed if they have our
178 // restriction flag set.
179 if let PropertyDeclarationId::Longhand(id) = decl.id() {
180 if !id.flags().contains(restriction) && self.origin != Origin::UserAgent {
181 continue;
182 }
183 }
184 }
185
186 return Some((decl, self.priority));
187 }
188
189 let next_node = self.current_rule_node.take()?.parent()?;
190 self.current_rule_node = Some(next_node);
191 self.update_for_node(next_node);
192 }
193 }
194 }
195
cascade_rules<E>( device: &Device, pseudo: Option<&PseudoElement>, rule_node: &StrongRuleNode, guards: &StylesheetGuards, parent_style: Option<&ComputedValues>, parent_style_ignoring_first_line: Option<&ComputedValues>, layout_parent_style: Option<&ComputedValues>, font_metrics_provider: &dyn FontMetricsProvider, cascade_mode: CascadeMode, quirks_mode: QuirksMode, rule_cache: Option<&RuleCache>, rule_cache_conditions: &mut RuleCacheConditions, element: Option<E>, ) -> Arc<ComputedValues> where E: TElement,196 fn cascade_rules<E>(
197 device: &Device,
198 pseudo: Option<&PseudoElement>,
199 rule_node: &StrongRuleNode,
200 guards: &StylesheetGuards,
201 parent_style: Option<&ComputedValues>,
202 parent_style_ignoring_first_line: Option<&ComputedValues>,
203 layout_parent_style: Option<&ComputedValues>,
204 font_metrics_provider: &dyn FontMetricsProvider,
205 cascade_mode: CascadeMode,
206 quirks_mode: QuirksMode,
207 rule_cache: Option<&RuleCache>,
208 rule_cache_conditions: &mut RuleCacheConditions,
209 element: Option<E>,
210 ) -> Arc<ComputedValues>
211 where
212 E: TElement,
213 {
214 debug_assert_eq!(
215 parent_style.is_some(),
216 parent_style_ignoring_first_line.is_some()
217 );
218 apply_declarations(
219 device,
220 pseudo,
221 rule_node,
222 guards,
223 DeclarationIterator::new(rule_node, guards, pseudo),
224 parent_style,
225 parent_style_ignoring_first_line,
226 layout_parent_style,
227 font_metrics_provider,
228 cascade_mode,
229 quirks_mode,
230 rule_cache,
231 rule_cache_conditions,
232 element,
233 )
234 }
235
236 /// Whether we're cascading for visited or unvisited styles.
237 #[derive(Clone, Copy)]
238 pub enum CascadeMode<'a> {
239 /// We're cascading for unvisited styles.
240 Unvisited {
241 /// The visited rules that should match the visited style.
242 visited_rules: Option<&'a StrongRuleNode>,
243 },
244 /// We're cascading for visited styles.
245 Visited {
246 /// The writing mode of our unvisited style, needed to correctly resolve
247 /// logical properties..
248 writing_mode: WritingMode,
249 },
250 }
251
252 /// NOTE: This function expects the declaration with more priority to appear
253 /// first.
apply_declarations<'a, E, I>( device: &Device, pseudo: Option<&PseudoElement>, rules: &StrongRuleNode, guards: &StylesheetGuards, iter: I, parent_style: Option<&ComputedValues>, parent_style_ignoring_first_line: Option<&ComputedValues>, layout_parent_style: Option<&ComputedValues>, font_metrics_provider: &dyn FontMetricsProvider, cascade_mode: CascadeMode, quirks_mode: QuirksMode, rule_cache: Option<&RuleCache>, rule_cache_conditions: &mut RuleCacheConditions, element: Option<E>, ) -> Arc<ComputedValues> where E: TElement, I: Iterator<Item = (&'a PropertyDeclaration, CascadePriority)>,254 pub fn apply_declarations<'a, E, I>(
255 device: &Device,
256 pseudo: Option<&PseudoElement>,
257 rules: &StrongRuleNode,
258 guards: &StylesheetGuards,
259 iter: I,
260 parent_style: Option<&ComputedValues>,
261 parent_style_ignoring_first_line: Option<&ComputedValues>,
262 layout_parent_style: Option<&ComputedValues>,
263 font_metrics_provider: &dyn FontMetricsProvider,
264 cascade_mode: CascadeMode,
265 quirks_mode: QuirksMode,
266 rule_cache: Option<&RuleCache>,
267 rule_cache_conditions: &mut RuleCacheConditions,
268 element: Option<E>,
269 ) -> Arc<ComputedValues>
270 where
271 E: TElement,
272 I: Iterator<Item = (&'a PropertyDeclaration, CascadePriority)>,
273 {
274 debug_assert!(layout_parent_style.is_none() || parent_style.is_some());
275 debug_assert_eq!(
276 parent_style.is_some(),
277 parent_style_ignoring_first_line.is_some()
278 );
279 #[cfg(feature = "gecko")]
280 debug_assert!(
281 parent_style.is_none() ||
282 ::std::ptr::eq(
283 parent_style.unwrap(),
284 parent_style_ignoring_first_line.unwrap()
285 ) ||
286 parent_style.unwrap().is_first_line_style()
287 );
288
289 let inherited_style = parent_style.unwrap_or(device.default_computed_values());
290
291 let mut declarations = SmallVec::<[(&_, CascadePriority); 32]>::new();
292 let custom_properties = {
293 let mut builder = CustomPropertiesBuilder::new(inherited_style.custom_properties(), device);
294
295 for (declaration, priority) in iter {
296 declarations.push((declaration, priority));
297 if let PropertyDeclaration::Custom(ref declaration) = *declaration {
298 builder.cascade(declaration, priority);
299 }
300 }
301
302 builder.build()
303 };
304
305 let is_root_element = pseudo.is_none() && element.map_or(false, |e| e.is_root());
306
307 let mut context = computed::Context {
308 // We'd really like to own the rules here to avoid refcount traffic, but
309 // animation's usage of `apply_declarations` make this tricky. See bug
310 // 1375525.
311 builder: StyleBuilder::new(
312 device,
313 parent_style,
314 parent_style_ignoring_first_line,
315 pseudo,
316 Some(rules.clone()),
317 custom_properties,
318 is_root_element,
319 ),
320 cached_system_font: None,
321 in_media_query: false,
322 for_smil_animation: false,
323 for_non_inherited_property: None,
324 font_metrics_provider,
325 quirks_mode,
326 rule_cache_conditions: RefCell::new(rule_cache_conditions),
327 };
328
329 let using_cached_reset_properties = {
330 let mut cascade = Cascade::new(&mut context, cascade_mode);
331 let mut shorthand_cache = ShorthandsWithPropertyReferencesCache::default();
332
333 cascade.apply_properties::<EarlyProperties, _>(
334 ApplyResetProperties::Yes,
335 declarations.iter().cloned(),
336 &mut shorthand_cache,
337 );
338
339 cascade.compute_visited_style_if_needed(
340 element,
341 parent_style,
342 parent_style_ignoring_first_line,
343 layout_parent_style,
344 guards,
345 );
346
347 let using_cached_reset_properties =
348 cascade.try_to_use_cached_reset_properties(rule_cache, guards);
349
350 let apply_reset = if using_cached_reset_properties {
351 ApplyResetProperties::No
352 } else {
353 ApplyResetProperties::Yes
354 };
355
356 cascade.apply_properties::<LateProperties, _>(
357 apply_reset,
358 declarations.iter().cloned(),
359 &mut shorthand_cache,
360 );
361
362 using_cached_reset_properties
363 };
364
365 context.builder.clear_modified_reset();
366
367 if matches!(cascade_mode, CascadeMode::Unvisited { .. }) {
368 StyleAdjuster::new(&mut context.builder)
369 .adjust(layout_parent_style.unwrap_or(inherited_style), element);
370 }
371
372 if context.builder.modified_reset() || using_cached_reset_properties {
373 // If we adjusted any reset structs, we can't cache this ComputedValues.
374 //
375 // Also, if we re-used existing reset structs, don't bother caching it
376 // back again. (Aside from being wasted effort, it will be wrong, since
377 // context.rule_cache_conditions won't be set appropriately if we didn't
378 // compute those reset properties.)
379 context.rule_cache_conditions.borrow_mut().set_uncacheable();
380 }
381
382 context.builder.build()
383 }
384
385 /// For ignored colors mode, we sometimes want to do something equivalent to
386 /// "revert-or-initial", where we `revert` for a given origin, but then apply a
387 /// given initial value if nothing in other origins did override it.
388 ///
389 /// This is a bit of a clunky way of achieving this.
390 type DeclarationsToApplyUnlessOverriden = SmallVec<[PropertyDeclaration; 2]>;
391
tweak_when_ignoring_colors( context: &computed::Context, longhand_id: LonghandId, origin: Origin, declaration: &mut Cow<PropertyDeclaration>, declarations_to_apply_unless_overriden: &mut DeclarationsToApplyUnlessOverriden, )392 fn tweak_when_ignoring_colors(
393 context: &computed::Context,
394 longhand_id: LonghandId,
395 origin: Origin,
396 declaration: &mut Cow<PropertyDeclaration>,
397 declarations_to_apply_unless_overriden: &mut DeclarationsToApplyUnlessOverriden,
398 ) {
399 use crate::values::specified::Color;
400 use crate::values::computed::ToComputedValue;
401 use cssparser::RGBA;
402
403 if !longhand_id.ignored_when_document_colors_disabled() {
404 return;
405 }
406
407 let is_ua_or_user_rule = matches!(origin, Origin::User | Origin::UserAgent);
408 if is_ua_or_user_rule {
409 return;
410 }
411
412 // Don't override background-color on ::-moz-color-swatch. It is set as an
413 // author style (via the style attribute), but it's pretty important for it
414 // to show up for obvious reasons :)
415 if context.builder.pseudo.map_or(false, |p| p.is_color_swatch()) &&
416 longhand_id == LonghandId::BackgroundColor
417 {
418 return;
419 }
420
421 fn alpha_channel(color: &Color, context: &computed::Context) -> u8 {
422 // We assume here currentColor is opaque.
423 let color = color.to_computed_value(context).to_rgba(RGBA::new(0, 0, 0, 255));
424 color.alpha
425 }
426
427 // A few special-cases ahead.
428 match **declaration {
429 PropertyDeclaration::BackgroundColor(ref color) => {
430 // We honor system colors.
431 if color.is_system() {
432 return;
433 }
434 // For background-color, we revert or initial-with-preserved-alpha
435 // otherwise, this is needed to preserve semi-transparent
436 // backgrounds.
437 //
438 // FIXME(emilio, bug 1666059): We revert for alpha == 0, but maybe
439 // should consider not doing that even if it causes some issues like
440 // bug 1625036, or finding a performant way to preserve the original
441 // widget background color's rgb channels but not alpha...
442 let alpha = alpha_channel(color, context);
443 if alpha != 0 {
444 let mut color = context.builder.device.default_background_color();
445 color.alpha = alpha;
446 declarations_to_apply_unless_overriden
447 .push(PropertyDeclaration::BackgroundColor(color.into()))
448 }
449 },
450 PropertyDeclaration::Color(ref color) => {
451 // We honor color: transparent and system colors.
452 if color.0.is_system() {
453 return;
454 }
455 if alpha_channel(&color.0, context) == 0 {
456 return;
457 }
458 // If the inherited color would be transparent, but we would
459 // override this with a non-transparent color, then override it with
460 // the default color. Otherwise just let it inherit through.
461 if context.builder.get_parent_inherited_text().clone_color().alpha == 0 {
462 let color = context.builder.device.default_color();
463 declarations_to_apply_unless_overriden.push(PropertyDeclaration::Color(
464 specified::ColorPropertyValue(color.into()),
465 ))
466 }
467 },
468 // We honor url background-images if backplating.
469 #[cfg(feature = "gecko")]
470 PropertyDeclaration::BackgroundImage(ref bkg) => {
471 use crate::values::generics::image::Image;
472 if static_prefs::pref!("browser.display.permit_backplate") {
473 if bkg.0.iter().all(|image| matches!(*image, Image::Url(..))) {
474 return;
475 }
476 }
477 },
478 _ => {
479 // We honor system colors more generally for all colors.
480 //
481 // We used to honor transparent but that causes accessibility
482 // regressions like bug 1740924.
483 //
484 // NOTE(emilio): This doesn't handle caret-color and accent-color
485 // because those use a slightly different syntax (<color> | auto for
486 // example).
487 //
488 // That's probably fine though, as using a system color for
489 // caret-color doesn't make sense (using currentColor is fine), and
490 // we ignore accent-color in high-contrast-mode anyways.
491 if let Some(color) = declaration.color_value() {
492 if color.is_system() {
493 return;
494 }
495 }
496 },
497 }
498
499 *declaration.to_mut() =
500 PropertyDeclaration::css_wide_keyword(longhand_id, CSSWideKeyword::Revert);
501 }
502
503 struct Cascade<'a, 'b: 'a> {
504 context: &'a mut computed::Context<'b>,
505 cascade_mode: CascadeMode<'a>,
506 seen: LonghandIdSet,
507 author_specified: LonghandIdSet,
508 reverted_set: LonghandIdSet,
509 reverted: FxHashMap<LonghandId, (CascadePriority, bool)>,
510 }
511
512 impl<'a, 'b: 'a> Cascade<'a, 'b> {
new(context: &'a mut computed::Context<'b>, cascade_mode: CascadeMode<'a>) -> Self513 fn new(context: &'a mut computed::Context<'b>, cascade_mode: CascadeMode<'a>) -> Self {
514 Self {
515 context,
516 cascade_mode,
517 seen: LonghandIdSet::default(),
518 author_specified: LonghandIdSet::default(),
519 reverted_set: Default::default(),
520 reverted: Default::default(),
521 }
522 }
523
substitute_variables_if_needed<'decl, 'cache>( &mut self, declaration: &'decl PropertyDeclaration, cache: &'cache mut ShorthandsWithPropertyReferencesCache, ) -> Cow<'decl, PropertyDeclaration> where 'cache: 'decl,524 fn substitute_variables_if_needed<'decl, 'cache>(
525 &mut self,
526 declaration: &'decl PropertyDeclaration,
527 cache: &'cache mut ShorthandsWithPropertyReferencesCache,
528 ) -> Cow<'decl, PropertyDeclaration>
529 where
530 'cache: 'decl,
531 {
532 let declaration = match *declaration {
533 PropertyDeclaration::WithVariables(ref declaration) => declaration,
534 ref d => return Cow::Borrowed(d),
535 };
536
537 if !declaration.id.inherited() {
538 self.context
539 .rule_cache_conditions
540 .borrow_mut()
541 .set_uncacheable();
542
543 // NOTE(emilio): We only really need to add the `display` /
544 // `content` flag if the CSS variable has not been specified on our
545 // declarations, but we don't have that information at this point,
546 // and it doesn't seem like an important enough optimization to
547 // warrant it.
548 match declaration.id {
549 LonghandId::Display => {
550 self.context
551 .builder
552 .add_flags(ComputedValueFlags::DISPLAY_DEPENDS_ON_INHERITED_STYLE);
553 },
554 LonghandId::Content => {
555 self.context
556 .builder
557 .add_flags(ComputedValueFlags::CONTENT_DEPENDS_ON_INHERITED_STYLE);
558 },
559 _ => {},
560 }
561 }
562
563 declaration.value.substitute_variables(
564 declaration.id,
565 self.context.builder.writing_mode,
566 self.context.builder.custom_properties.as_ref(),
567 self.context.quirks_mode,
568 self.context.device(),
569 cache,
570 )
571 }
572
573 #[inline(always)]
apply_declaration(&mut self, longhand_id: LonghandId, declaration: &PropertyDeclaration)574 fn apply_declaration(&mut self, longhand_id: LonghandId, declaration: &PropertyDeclaration) {
575 // We could (and used to) use a pattern match here, but that bloats this
576 // function to over 100K of compiled code!
577 //
578 // To improve i-cache behavior, we outline the individual functions and
579 // use virtual dispatch instead.
580 let discriminant = longhand_id as usize;
581 (CASCADE_PROPERTY[discriminant])(declaration, &mut self.context);
582 }
583
apply_properties<'decls, Phase, I>( &mut self, apply_reset: ApplyResetProperties, declarations: I, mut shorthand_cache: &mut ShorthandsWithPropertyReferencesCache, ) where Phase: CascadePhase, I: Iterator<Item = (&'decls PropertyDeclaration, CascadePriority)>,584 fn apply_properties<'decls, Phase, I>(
585 &mut self,
586 apply_reset: ApplyResetProperties,
587 declarations: I,
588 mut shorthand_cache: &mut ShorthandsWithPropertyReferencesCache,
589 ) where
590 Phase: CascadePhase,
591 I: Iterator<Item = (&'decls PropertyDeclaration, CascadePriority)>,
592 {
593 let apply_reset = apply_reset == ApplyResetProperties::Yes;
594
595 debug_assert!(
596 !Phase::is_early() || apply_reset,
597 "Should always apply reset properties in the early phase, since we \
598 need to know font-size / writing-mode to decide whether to use the \
599 cached reset properties"
600 );
601
602 let ignore_colors = !self.context.builder.device.use_document_colors();
603 let mut declarations_to_apply_unless_overriden = DeclarationsToApplyUnlessOverriden::new();
604
605 for (declaration, priority) in declarations {
606 let origin = priority.cascade_level().origin();
607
608 let declaration_id = declaration.id();
609 let longhand_id = match declaration_id {
610 PropertyDeclarationId::Longhand(id) => id,
611 PropertyDeclarationId::Custom(..) => continue,
612 };
613
614 let inherited = longhand_id.inherited();
615 if !apply_reset && !inherited {
616 continue;
617 }
618
619 if Phase::is_early() != longhand_id.is_early_property() {
620 continue;
621 }
622
623 debug_assert!(!Phase::is_early() || !longhand_id.is_logical());
624 let physical_longhand_id = if Phase::is_early() {
625 longhand_id
626 } else {
627 longhand_id.to_physical(self.context.builder.writing_mode)
628 };
629
630 if self.seen.contains(physical_longhand_id) {
631 continue;
632 }
633
634 if self.reverted_set.contains(physical_longhand_id) {
635 if let Some(&(reverted_priority, is_origin_revert)) = self.reverted.get(&physical_longhand_id) {
636 if !reverted_priority.allows_when_reverted(&priority, is_origin_revert) {
637 continue;
638 }
639 }
640 }
641
642 // Only a few properties are allowed to depend on the visited state
643 // of links. When cascading visited styles, we can save time by
644 // only processing these properties.
645 if matches!(self.cascade_mode, CascadeMode::Visited { .. }) &&
646 !physical_longhand_id.is_visited_dependent()
647 {
648 continue;
649 }
650
651 let mut declaration =
652 self.substitute_variables_if_needed(declaration, &mut shorthand_cache);
653
654 // When document colors are disabled, do special handling of
655 // properties that are marked as ignored in that mode.
656 if ignore_colors {
657 tweak_when_ignoring_colors(
658 &self.context,
659 longhand_id,
660 origin,
661 &mut declaration,
662 &mut declarations_to_apply_unless_overriden,
663 );
664 debug_assert_eq!(
665 declaration.id(),
666 PropertyDeclarationId::Longhand(longhand_id),
667 "Shouldn't change the declaration id!",
668 );
669 }
670
671 let is_unset = match declaration.get_css_wide_keyword() {
672 Some(keyword) => match keyword {
673 CSSWideKeyword::RevertLayer |
674 CSSWideKeyword::Revert => {
675 let origin_revert = keyword == CSSWideKeyword::Revert;
676 // We intentionally don't want to insert it into
677 // `self.seen`, `reverted` takes care of rejecting other
678 // declarations as needed.
679 self.reverted_set.insert(physical_longhand_id);
680 self.reverted.insert(physical_longhand_id, (priority, origin_revert));
681 continue;
682 },
683 CSSWideKeyword::Unset => true,
684 CSSWideKeyword::Inherit => inherited,
685 CSSWideKeyword::Initial => !inherited,
686 },
687 None => false,
688 };
689
690 self.seen.insert(physical_longhand_id);
691 if origin == Origin::Author {
692 self.author_specified.insert(physical_longhand_id);
693 }
694
695 if is_unset {
696 continue;
697 }
698
699 // FIXME(emilio): We should avoid generating code for logical
700 // longhands and just use the physical ones, then rename
701 // physical_longhand_id to just longhand_id.
702 self.apply_declaration(longhand_id, &*declaration);
703 }
704
705 if ignore_colors {
706 for declaration in declarations_to_apply_unless_overriden.iter() {
707 let longhand_id = match declaration.id() {
708 PropertyDeclarationId::Longhand(id) => id,
709 PropertyDeclarationId::Custom(..) => unreachable!(),
710 };
711 debug_assert!(!longhand_id.is_logical());
712 if self.seen.contains(longhand_id) {
713 continue;
714 }
715 self.apply_declaration(longhand_id, declaration);
716 }
717 }
718
719 if Phase::is_early() {
720 self.fixup_font_stuff();
721 self.compute_writing_mode();
722 } else {
723 self.finished_applying_properties();
724 }
725 }
726
compute_writing_mode(&mut self)727 fn compute_writing_mode(&mut self) {
728 let writing_mode = match self.cascade_mode {
729 CascadeMode::Unvisited { .. } => {
730 WritingMode::new(self.context.builder.get_inherited_box())
731 },
732 CascadeMode::Visited { writing_mode } => writing_mode,
733 };
734 self.context.builder.writing_mode = writing_mode;
735 }
736
compute_visited_style_if_needed<E>( &mut self, element: Option<E>, parent_style: Option<&ComputedValues>, parent_style_ignoring_first_line: Option<&ComputedValues>, layout_parent_style: Option<&ComputedValues>, guards: &StylesheetGuards, ) where E: TElement,737 fn compute_visited_style_if_needed<E>(
738 &mut self,
739 element: Option<E>,
740 parent_style: Option<&ComputedValues>,
741 parent_style_ignoring_first_line: Option<&ComputedValues>,
742 layout_parent_style: Option<&ComputedValues>,
743 guards: &StylesheetGuards,
744 ) where
745 E: TElement,
746 {
747 let visited_rules = match self.cascade_mode {
748 CascadeMode::Unvisited { visited_rules } => visited_rules,
749 CascadeMode::Visited { .. } => return,
750 };
751
752 let visited_rules = match visited_rules {
753 Some(rules) => rules,
754 None => return,
755 };
756
757 let is_link = self.context.builder.pseudo.is_none() && element.unwrap().is_link();
758
759 macro_rules! visited_parent {
760 ($parent:expr) => {
761 if is_link {
762 $parent
763 } else {
764 $parent.map(|p| p.visited_style().unwrap_or(p))
765 }
766 };
767 }
768
769 let writing_mode = self.context.builder.writing_mode;
770
771 // We could call apply_declarations directly, but that'd cause
772 // another instantiation of this function which is not great.
773 let style = cascade_rules(
774 self.context.builder.device,
775 self.context.builder.pseudo,
776 visited_rules,
777 guards,
778 visited_parent!(parent_style),
779 visited_parent!(parent_style_ignoring_first_line),
780 visited_parent!(layout_parent_style),
781 self.context.font_metrics_provider,
782 CascadeMode::Visited { writing_mode },
783 self.context.quirks_mode,
784 // The rule cache doesn't care about caching :visited
785 // styles, we cache the unvisited style instead. We still do
786 // need to set the caching dependencies properly if present
787 // though, so the cache conditions need to match.
788 None, // rule_cache
789 &mut *self.context.rule_cache_conditions.borrow_mut(),
790 element,
791 );
792 self.context.builder.visited_style = Some(style);
793 }
794
finished_applying_properties(&mut self)795 fn finished_applying_properties(&mut self) {
796 let builder = &mut self.context.builder;
797
798 #[cfg(feature = "gecko")]
799 {
800 if let Some(bg) = builder.get_background_if_mutated() {
801 bg.fill_arrays();
802 }
803
804 if let Some(svg) = builder.get_svg_if_mutated() {
805 svg.fill_arrays();
806 }
807 }
808
809 if self
810 .author_specified
811 .contains_any(LonghandIdSet::border_background_properties())
812 {
813 builder.add_flags(ComputedValueFlags::HAS_AUTHOR_SPECIFIED_BORDER_BACKGROUND);
814 }
815
816 if self
817 .author_specified
818 .contains(LonghandId::FontFamily)
819 {
820 builder.add_flags(ComputedValueFlags::HAS_AUTHOR_SPECIFIED_FONT_FAMILY);
821 }
822
823 if self
824 .author_specified
825 .contains(LonghandId::LetterSpacing)
826 {
827 builder.add_flags(ComputedValueFlags::HAS_AUTHOR_SPECIFIED_LETTER_SPACING);
828 }
829
830 if self
831 .author_specified
832 .contains(LonghandId::WordSpacing)
833 {
834 builder.add_flags(ComputedValueFlags::HAS_AUTHOR_SPECIFIED_WORD_SPACING);
835 }
836
837 if self
838 .author_specified
839 .contains(LonghandId::FontSynthesis)
840 {
841 builder.add_flags(ComputedValueFlags::HAS_AUTHOR_SPECIFIED_FONT_SYNTHESIS);
842 }
843
844 #[cfg(feature = "servo")]
845 {
846 if let Some(font) = builder.get_font_if_mutated() {
847 font.compute_font_hash();
848 }
849 }
850 }
851
try_to_use_cached_reset_properties( &mut self, cache: Option<&'b RuleCache>, guards: &StylesheetGuards, ) -> bool852 fn try_to_use_cached_reset_properties(
853 &mut self,
854 cache: Option<&'b RuleCache>,
855 guards: &StylesheetGuards,
856 ) -> bool {
857 let cache = match cache {
858 Some(cache) => cache,
859 None => return false,
860 };
861
862 let builder = &mut self.context.builder;
863
864 let cached_style = match cache.find(guards, &builder) {
865 Some(style) => style,
866 None => return false,
867 };
868
869 builder.copy_reset_from(cached_style);
870
871 // We're using the same reset style as another element, and we'll skip
872 // applying the relevant properties. So we need to do the relevant
873 // bookkeeping here to keep these bits correct.
874 //
875 // Note that the border/background properties are non-inherited, so we
876 // don't need to do anything else other than just copying the bits over.
877 //
878 // When using this optimization, we also need to copy whether the old
879 // style specified viewport units / used font-relative lengths, this one
880 // would as well. It matches the same rules, so it is the right thing
881 // to do anyways, even if it's only used on inherited properties.
882 let bits_to_copy = ComputedValueFlags::HAS_AUTHOR_SPECIFIED_BORDER_BACKGROUND |
883 ComputedValueFlags::DEPENDS_ON_SELF_FONT_METRICS |
884 ComputedValueFlags::DEPENDS_ON_INHERITED_FONT_METRICS |
885 ComputedValueFlags::USES_VIEWPORT_UNITS;
886 builder.add_flags(cached_style.flags & bits_to_copy);
887
888 true
889 }
890
891 /// The initial font depends on the current lang group so we may need to
892 /// recompute it if the language changed.
893 #[inline]
894 #[cfg(feature = "gecko")]
recompute_initial_font_family_if_needed(&mut self)895 fn recompute_initial_font_family_if_needed(&mut self) {
896 use crate::gecko_bindings::bindings;
897 use crate::values::computed::font::FontFamily;
898
899 if !self.seen.contains(LonghandId::XLang) {
900 return;
901 }
902
903 let builder = &mut self.context.builder;
904 let default_font_type = {
905 let font = builder.get_font().gecko();
906
907 if !font.mFont.family.is_initial {
908 return;
909 }
910
911 let default_font_type = unsafe {
912 bindings::Gecko_nsStyleFont_ComputeFallbackFontTypeForLanguage(
913 builder.device.document(),
914 font.mLanguage.mRawPtr,
915 )
916 };
917
918 let initial_generic = font.mFont.family.families.single_generic();
919 debug_assert!(initial_generic.is_some(), "Initial font should be just one generic font");
920 if initial_generic == Some(default_font_type) {
921 return;
922 }
923
924 default_font_type
925 };
926
927 let font = builder.mutate_font().gecko_mut();
928 // NOTE: Leaves is_initial untouched.
929 font.mFont.family.families = FontFamily::generic(default_font_type).families.clone();
930 }
931
932 /// Prioritize user fonts if needed by pref.
933 #[inline]
934 #[cfg(feature = "gecko")]
prioritize_user_fonts_if_needed(&mut self)935 fn prioritize_user_fonts_if_needed(&mut self) {
936 use crate::gecko_bindings::bindings;
937
938 if !self.seen.contains(LonghandId::FontFamily) {
939 return;
940 }
941
942 if static_prefs::pref!("browser.display.use_document_fonts") != 0 {
943 return;
944 }
945
946 let builder = &mut self.context.builder;
947 let default_font_type = {
948 let font = builder.get_font().gecko();
949
950 if font.mFont.family.is_system_font {
951 return;
952 }
953
954 if !font.mFont.family.families.needs_user_font_prioritization() {
955 return;
956 }
957
958 unsafe {
959 bindings::Gecko_nsStyleFont_ComputeFallbackFontTypeForLanguage(
960 builder.device.document(),
961 font.mLanguage.mRawPtr,
962 )
963 }
964 };
965
966 let font = builder.mutate_font().gecko_mut();
967 font.mFont.family.families.prioritize_first_generic_or_prepend(default_font_type);
968 }
969
970 /// Some keyword sizes depend on the font family and language.
971 #[cfg(feature = "gecko")]
recompute_keyword_font_size_if_needed(&mut self)972 fn recompute_keyword_font_size_if_needed(&mut self) {
973 use crate::values::computed::ToComputedValue;
974 use crate::values::specified;
975
976 if !self.seen.contains(LonghandId::XLang) && !self.seen.contains(LonghandId::FontFamily) {
977 return;
978 }
979
980 let new_size = {
981 let font = self.context.builder.get_font();
982 let info = font.clone_font_size().keyword_info;
983 let new_size = match info.kw {
984 specified::FontSizeKeyword::None => return,
985 _ => {
986 self.context.for_non_inherited_property = None;
987 specified::FontSize::Keyword(info).to_computed_value(self.context)
988 },
989 };
990
991 if font.gecko().mScriptUnconstrainedSize == new_size.size {
992 return;
993 }
994
995 new_size
996 };
997
998 self.context.builder.mutate_font().set_font_size(new_size);
999 }
1000
1001 /// Some properties, plus setting font-size itself, may make us go out of
1002 /// our minimum font-size range.
1003 #[cfg(feature = "gecko")]
constrain_font_size_if_needed(&mut self)1004 fn constrain_font_size_if_needed(&mut self) {
1005 use crate::gecko_bindings::bindings;
1006 use crate::values::generics::NonNegative;
1007
1008 if !self.seen.contains(LonghandId::XLang) &&
1009 !self.seen.contains(LonghandId::FontFamily) &&
1010 !self.seen.contains(LonghandId::MozMinFontSizeRatio) &&
1011 !self.seen.contains(LonghandId::FontSize)
1012 {
1013 return;
1014 }
1015
1016 let builder = &mut self.context.builder;
1017 let min_font_size = {
1018 let font = builder.get_font().gecko();
1019 let min_font_size = unsafe {
1020 bindings::Gecko_nsStyleFont_ComputeMinSize(font, builder.device.document())
1021 };
1022
1023 if font.mFont.size.0 >= min_font_size {
1024 return;
1025 }
1026
1027 NonNegative(min_font_size)
1028 };
1029
1030 builder.mutate_font().gecko_mut().mFont.size = min_font_size;
1031 }
1032
1033 /// <svg:text> is not affected by text zoom, and it uses a preshint
1034 /// to disable it. We fix up the struct when this happens by
1035 /// unzooming its contained font values, which will have been zoomed
1036 /// in the parent.
1037 ///
1038 /// FIXME(emilio): Also, why doing this _before_ handling font-size? That
1039 /// sounds wrong.
1040 #[cfg(feature = "gecko")]
unzoom_fonts_if_needed(&mut self)1041 fn unzoom_fonts_if_needed(&mut self) {
1042 if !self.seen.contains(LonghandId::XTextZoom) {
1043 return;
1044 }
1045
1046 let builder = &mut self.context.builder;
1047
1048 let parent_zoom = builder.get_parent_font().gecko().mAllowZoomAndMinSize;
1049 let zoom = builder.get_font().gecko().mAllowZoomAndMinSize;
1050 if zoom == parent_zoom {
1051 return;
1052 }
1053 debug_assert!(
1054 !zoom,
1055 "We only ever disable text zoom (in svg:text), never enable it"
1056 );
1057 let device = builder.device;
1058 builder.mutate_font().unzoom_fonts(device);
1059 }
1060
1061 /// MathML script* attributes do some very weird shit with font-size.
1062 ///
1063 /// Handle them specially here, separate from other font-size stuff.
1064 ///
1065 /// How this should interact with lang="" and font-family-dependent sizes is
1066 /// not clear to me. For now just pretend those don't exist here.
1067 #[cfg(feature = "gecko")]
handle_mathml_scriptlevel_if_needed(&mut self)1068 fn handle_mathml_scriptlevel_if_needed(&mut self) {
1069 use crate::values::generics::NonNegative;
1070
1071 if !self.seen.contains(LonghandId::MathDepth) &&
1072 !self.seen.contains(LonghandId::MozScriptMinSize) &&
1073 !self.seen.contains(LonghandId::MozScriptSizeMultiplier)
1074 {
1075 return;
1076 }
1077
1078 // If the user specifies a font-size, just let it be.
1079 if self.seen.contains(LonghandId::FontSize) {
1080 return;
1081 }
1082
1083 let builder = &mut self.context.builder;
1084 let (new_size, new_unconstrained_size) = {
1085 let font = builder.get_font().gecko();
1086 let parent_font = builder.get_parent_font().gecko();
1087
1088 let delta = font.mMathDepth.saturating_sub(parent_font.mMathDepth);
1089
1090 if delta == 0 {
1091 return;
1092 }
1093
1094 let mut min = parent_font.mScriptMinSize;
1095 if font.mAllowZoomAndMinSize {
1096 min = builder.device.zoom_text(min);
1097 }
1098
1099 let scale = (parent_font.mScriptSizeMultiplier as f32).powi(delta as i32);
1100 let parent_size = parent_font.mSize.0;
1101 let parent_unconstrained_size = parent_font.mScriptUnconstrainedSize.0;
1102 let new_size = parent_size.scale_by(scale);
1103 let new_unconstrained_size = parent_unconstrained_size.scale_by(scale);
1104
1105 if scale <= 1. {
1106 // The parent size can be smaller than scriptminsize, e.g. if it
1107 // was specified explicitly. Don't scale in this case, but we
1108 // don't want to set it to scriptminsize either since that will
1109 // make it larger.
1110 if parent_size <= min {
1111 (parent_size, new_unconstrained_size)
1112 } else {
1113 (min.max(new_size), new_unconstrained_size)
1114 }
1115 } else {
1116 // If the new unconstrained size is larger than the min size,
1117 // this means we have escaped the grasp of scriptminsize and can
1118 // revert to using the unconstrained size.
1119 // However, if the new size is even larger (perhaps due to usage
1120 // of em units), use that instead.
1121 (
1122 new_size.min(new_unconstrained_size.max(min)),
1123 new_unconstrained_size,
1124 )
1125 }
1126 };
1127 let font = builder.mutate_font().gecko_mut();
1128 font.mFont.size = NonNegative(new_size);
1129 font.mSize = NonNegative(new_size);
1130 font.mScriptUnconstrainedSize = NonNegative(new_unconstrained_size);
1131 }
1132
1133 /// Various properties affect how font-size and font-family are computed.
1134 ///
1135 /// These need to be handled here, since relative lengths and ex / ch units
1136 /// for late properties depend on these.
fixup_font_stuff(&mut self)1137 fn fixup_font_stuff(&mut self) {
1138 #[cfg(feature = "gecko")]
1139 {
1140 self.unzoom_fonts_if_needed();
1141 self.recompute_initial_font_family_if_needed();
1142 self.prioritize_user_fonts_if_needed();
1143 self.recompute_keyword_font_size_if_needed();
1144 self.handle_mathml_scriptlevel_if_needed();
1145 self.constrain_font_size_if_needed()
1146 }
1147 }
1148 }
1149