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