cascade.rs (55927B)
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::color::AbsoluteColor; 9 use crate::computed_value_flags::ComputedValueFlags; 10 use crate::custom_properties::{ 11 CustomPropertiesBuilder, DeferFontRelativeCustomPropertyResolution, 12 }; 13 use crate::dom::{AttributeProvider, DummyAttributeProvider, TElement}; 14 #[cfg(feature = "gecko")] 15 use crate::font_metrics::FontMetricsOrientation; 16 use crate::logical_geometry::WritingMode; 17 use crate::properties::{ 18 property_counts, CSSWideKeyword, ComputedValues, DeclarationImportanceIterator, Importance, 19 LonghandId, LonghandIdSet, PrioritaryPropertyId, PropertyDeclaration, PropertyDeclarationId, 20 PropertyFlags, ShorthandsWithPropertyReferencesCache, StyleBuilder, CASCADE_PROPERTY, 21 }; 22 use crate::rule_cache::{RuleCache, RuleCacheConditions}; 23 use crate::rule_tree::{CascadeLevel, StrongRuleNode}; 24 use crate::selector_parser::PseudoElement; 25 use crate::shared_lock::StylesheetGuards; 26 use crate::style_adjuster::StyleAdjuster; 27 use crate::stylesheets::container_rule::ContainerSizeQuery; 28 use crate::stylesheets::{layer_rule::LayerOrder, Origin}; 29 use crate::stylist::Stylist; 30 #[cfg(feature = "gecko")] 31 use crate::values::specified::length::FontBaseSize; 32 use crate::values::specified::position::PositionTryFallbacksTryTactic; 33 use crate::values::{computed, specified}; 34 use rustc_hash::FxHashMap; 35 use servo_arc::Arc; 36 use smallvec::SmallVec; 37 use std::borrow::Cow; 38 39 /// Whether we're resolving a style with the purposes of reparenting for ::first-line. 40 #[derive(Copy, Clone)] 41 #[allow(missing_docs)] 42 pub enum FirstLineReparenting<'a> { 43 No, 44 Yes { 45 /// The style we're re-parenting for ::first-line. ::first-line only affects inherited 46 /// properties so we use this to avoid some work and also ensure correctness by copying the 47 /// reset structs from this style. 48 style_to_reparent: &'a ComputedValues, 49 }, 50 } 51 52 /// Performs the CSS cascade, computing new styles for an element from its parent style. 53 /// 54 /// The arguments are: 55 /// 56 /// * `device`: Used to get the initial viewport and other external state. 57 /// 58 /// * `rule_node`: The rule node in the tree that represent the CSS rules that 59 /// matched. 60 /// 61 /// * `parent_style`: The parent style, if applicable; if `None`, this is the root node. 62 /// 63 /// Returns the computed values. 64 /// * `flags`: Various flags. 65 /// 66 pub fn cascade<E>( 67 stylist: &Stylist, 68 pseudo: Option<&PseudoElement>, 69 rule_node: &StrongRuleNode, 70 guards: &StylesheetGuards, 71 parent_style: Option<&ComputedValues>, 72 layout_parent_style: Option<&ComputedValues>, 73 first_line_reparenting: FirstLineReparenting, 74 try_tactic: &PositionTryFallbacksTryTactic, 75 visited_rules: Option<&StrongRuleNode>, 76 cascade_input_flags: ComputedValueFlags, 77 rule_cache: Option<&RuleCache>, 78 rule_cache_conditions: &mut RuleCacheConditions, 79 element: Option<E>, 80 ) -> Arc<ComputedValues> 81 where 82 E: TElement, 83 { 84 cascade_rules( 85 stylist, 86 pseudo, 87 rule_node, 88 guards, 89 parent_style, 90 layout_parent_style, 91 first_line_reparenting, 92 try_tactic, 93 CascadeMode::Unvisited { visited_rules }, 94 cascade_input_flags, 95 rule_cache, 96 rule_cache_conditions, 97 element, 98 ) 99 } 100 101 struct DeclarationIterator<'a> { 102 // Global to the iteration. 103 guards: &'a StylesheetGuards<'a>, 104 restriction: Option<PropertyFlags>, 105 // The rule we're iterating over. 106 current_rule_node: Option<&'a StrongRuleNode>, 107 // Per rule state. 108 declarations: DeclarationImportanceIterator<'a>, 109 origin: Origin, 110 importance: Importance, 111 priority: CascadePriority, 112 } 113 114 impl<'a> DeclarationIterator<'a> { 115 #[inline] 116 fn new( 117 rule_node: &'a StrongRuleNode, 118 guards: &'a StylesheetGuards, 119 pseudo: Option<&PseudoElement>, 120 ) -> Self { 121 let restriction = pseudo.and_then(|p| p.property_restriction()); 122 let mut iter = Self { 123 guards, 124 current_rule_node: Some(rule_node), 125 origin: Origin::UserAgent, 126 importance: Importance::Normal, 127 priority: CascadePriority::new(CascadeLevel::UANormal, LayerOrder::root()), 128 declarations: DeclarationImportanceIterator::default(), 129 restriction, 130 }; 131 iter.update_for_node(rule_node); 132 iter 133 } 134 135 fn update_for_node(&mut self, node: &'a StrongRuleNode) { 136 self.priority = node.cascade_priority(); 137 let level = self.priority.cascade_level(); 138 self.origin = level.origin(); 139 self.importance = level.importance(); 140 let guard = match self.origin { 141 Origin::Author => self.guards.author, 142 Origin::User | Origin::UserAgent => self.guards.ua_or_user, 143 }; 144 self.declarations = match node.style_source() { 145 Some(source) => source.read(guard).declaration_importance_iter(), 146 None => DeclarationImportanceIterator::default(), 147 }; 148 } 149 } 150 151 impl<'a> Iterator for DeclarationIterator<'a> { 152 type Item = (&'a PropertyDeclaration, CascadePriority); 153 154 #[inline] 155 fn next(&mut self) -> Option<Self::Item> { 156 loop { 157 if let Some((decl, importance)) = self.declarations.next_back() { 158 if self.importance != importance { 159 continue; 160 } 161 162 if let Some(restriction) = self.restriction { 163 // decl.id() is either a longhand or a custom 164 // property. Custom properties are always allowed, but 165 // longhands are only allowed if they have our 166 // restriction flag set. 167 if let PropertyDeclarationId::Longhand(id) = decl.id() { 168 if !id.flags().contains(restriction) && self.origin != Origin::UserAgent { 169 continue; 170 } 171 } 172 } 173 174 return Some((decl, self.priority)); 175 } 176 177 let next_node = self.current_rule_node.take()?.parent()?; 178 self.current_rule_node = Some(next_node); 179 self.update_for_node(next_node); 180 } 181 } 182 } 183 184 fn cascade_rules<E>( 185 stylist: &Stylist, 186 pseudo: Option<&PseudoElement>, 187 rule_node: &StrongRuleNode, 188 guards: &StylesheetGuards, 189 parent_style: Option<&ComputedValues>, 190 layout_parent_style: Option<&ComputedValues>, 191 first_line_reparenting: FirstLineReparenting, 192 try_tactic: &PositionTryFallbacksTryTactic, 193 cascade_mode: CascadeMode, 194 cascade_input_flags: ComputedValueFlags, 195 rule_cache: Option<&RuleCache>, 196 rule_cache_conditions: &mut RuleCacheConditions, 197 element: Option<E>, 198 ) -> Arc<ComputedValues> 199 where 200 E: TElement, 201 { 202 apply_declarations( 203 stylist, 204 pseudo, 205 rule_node, 206 guards, 207 DeclarationIterator::new(rule_node, guards, pseudo), 208 parent_style, 209 layout_parent_style, 210 first_line_reparenting, 211 try_tactic, 212 cascade_mode, 213 cascade_input_flags, 214 rule_cache, 215 rule_cache_conditions, 216 element, 217 ) 218 } 219 220 /// Whether we're cascading for visited or unvisited styles. 221 #[derive(Clone, Copy)] 222 pub enum CascadeMode<'a, 'b> { 223 /// We're cascading for unvisited styles. 224 Unvisited { 225 /// The visited rules that should match the visited style. 226 visited_rules: Option<&'a StrongRuleNode>, 227 }, 228 /// We're cascading for visited styles. 229 Visited { 230 /// The cascade for our unvisited style. 231 unvisited_context: &'a computed::Context<'b>, 232 }, 233 } 234 235 fn iter_declarations<'builder, 'decls: 'builder>( 236 iter: impl Iterator<Item = (&'decls PropertyDeclaration, CascadePriority)>, 237 declarations: &mut Declarations<'decls>, 238 mut custom_builder: Option<&mut CustomPropertiesBuilder<'builder, 'decls>>, 239 attr_provider: &dyn AttributeProvider, 240 ) { 241 for (declaration, priority) in iter { 242 if let PropertyDeclaration::Custom(ref declaration) = *declaration { 243 if let Some(ref mut builder) = custom_builder { 244 builder.cascade(declaration, priority, attr_provider); 245 } 246 } else { 247 let id = declaration.id().as_longhand().unwrap(); 248 declarations.note_declaration(declaration, priority, id); 249 if CustomPropertiesBuilder::might_have_non_custom_dependency(id, declaration) { 250 if let Some(ref mut builder) = custom_builder { 251 builder.maybe_note_non_custom_dependency(id, declaration); 252 } 253 } 254 } 255 } 256 } 257 258 /// NOTE: This function expects the declaration with more priority to appear 259 /// first. 260 pub fn apply_declarations<'a, E, I>( 261 stylist: &'a Stylist, 262 pseudo: Option<&'a PseudoElement>, 263 rules: &StrongRuleNode, 264 guards: &StylesheetGuards, 265 iter: I, 266 parent_style: Option<&'a ComputedValues>, 267 layout_parent_style: Option<&ComputedValues>, 268 first_line_reparenting: FirstLineReparenting<'a>, 269 try_tactic: &'a PositionTryFallbacksTryTactic, 270 cascade_mode: CascadeMode, 271 cascade_input_flags: ComputedValueFlags, 272 rule_cache: Option<&'a RuleCache>, 273 rule_cache_conditions: &'a mut RuleCacheConditions, 274 element: Option<E>, 275 ) -> Arc<ComputedValues> 276 where 277 E: TElement + 'a, 278 I: Iterator<Item = (&'a PropertyDeclaration, CascadePriority)>, 279 { 280 debug_assert!(layout_parent_style.is_none() || parent_style.is_some()); 281 let device = stylist.device(); 282 let inherited_style = parent_style.unwrap_or(device.default_computed_values()); 283 let is_root_element = pseudo.is_none() && element.map_or(false, |e| e.is_root()); 284 285 let container_size_query = 286 ContainerSizeQuery::for_option_element(element, Some(inherited_style), pseudo.is_some()); 287 288 let mut context = computed::Context::new( 289 // We'd really like to own the rules here to avoid refcount traffic, but 290 // animation's usage of `apply_declarations` make this tricky. See bug 291 // 1375525. 292 StyleBuilder::new( 293 device, 294 Some(stylist), 295 parent_style, 296 pseudo, 297 Some(rules.clone()), 298 is_root_element, 299 ), 300 stylist.quirks_mode(), 301 rule_cache_conditions, 302 container_size_query, 303 ); 304 305 context.style().add_flags(cascade_input_flags); 306 307 let using_cached_reset_properties; 308 let ignore_colors = context.builder.device.forced_colors().is_active(); 309 let mut cascade = Cascade::new(first_line_reparenting, try_tactic, ignore_colors); 310 let mut declarations = Default::default(); 311 let mut shorthand_cache = ShorthandsWithPropertyReferencesCache::default(); 312 let attr_provider: &dyn AttributeProvider = match element { 313 Some(ref attr_provider) => attr_provider, 314 None => &DummyAttributeProvider {}, 315 }; 316 let properties_to_apply = match cascade_mode { 317 CascadeMode::Visited { unvisited_context } => { 318 context.builder.custom_properties = unvisited_context.builder.custom_properties.clone(); 319 context.builder.writing_mode = unvisited_context.builder.writing_mode; 320 context.builder.color_scheme = unvisited_context.builder.color_scheme; 321 // We never insert visited styles into the cache so we don't need to try looking it up. 322 // It also wouldn't be super-profitable, only a handful :visited properties are 323 // non-inherited. 324 using_cached_reset_properties = false; 325 // TODO(bug 1859385): If we match the same rules when visited and unvisited, we could 326 // try to avoid gathering the declarations. That'd be: 327 // unvisited_context.builder.rules.as_ref() == Some(rules) 328 iter_declarations(iter, &mut declarations, None, attr_provider); 329 330 LonghandIdSet::visited_dependent() 331 }, 332 CascadeMode::Unvisited { visited_rules } => { 333 let deferred_custom_properties = { 334 let mut builder = CustomPropertiesBuilder::new(stylist, &mut context); 335 iter_declarations(iter, &mut declarations, Some(&mut builder), attr_provider); 336 // Detect cycles, remove properties participating in them, and resolve properties, except: 337 // * Registered custom properties that depend on font-relative properties (Resolved) 338 // when prioritary properties are resolved), and 339 // * Any property that, in turn, depend on properties like above. 340 builder.build( 341 DeferFontRelativeCustomPropertyResolution::Yes, 342 attr_provider, 343 ) 344 }; 345 346 // Resolve prioritary properties - Guaranteed to not fall into a cycle with existing custom 347 // properties. 348 cascade.apply_prioritary_properties( 349 &mut context, 350 &declarations, 351 &mut shorthand_cache, 352 attr_provider, 353 ); 354 355 // Resolve the deferred custom properties. 356 if let Some(deferred) = deferred_custom_properties { 357 CustomPropertiesBuilder::build_deferred( 358 deferred, 359 stylist, 360 &mut context, 361 attr_provider, 362 ); 363 } 364 365 if let Some(visited_rules) = visited_rules { 366 cascade.compute_visited_style_if_needed( 367 &mut context, 368 element, 369 parent_style, 370 layout_parent_style, 371 visited_rules, 372 guards, 373 ); 374 } 375 376 using_cached_reset_properties = cascade.try_to_use_cached_reset_properties( 377 &mut context.builder, 378 rule_cache, 379 guards, 380 ); 381 382 if using_cached_reset_properties { 383 LonghandIdSet::late_group_only_inherited() 384 } else { 385 LonghandIdSet::late_group() 386 } 387 }, 388 }; 389 390 cascade.apply_non_prioritary_properties( 391 &mut context, 392 &declarations.longhand_declarations, 393 &mut shorthand_cache, 394 &properties_to_apply, 395 attr_provider, 396 ); 397 398 cascade.finished_applying_properties(&mut context.builder); 399 400 std::mem::drop(cascade); 401 402 context.builder.clear_modified_reset(); 403 404 if matches!(cascade_mode, CascadeMode::Unvisited { .. }) { 405 StyleAdjuster::new(&mut context.builder).adjust( 406 layout_parent_style.unwrap_or(inherited_style), 407 element, 408 try_tactic, 409 ); 410 } 411 412 if context.builder.modified_reset() || using_cached_reset_properties { 413 // If we adjusted any reset structs, we can't cache this ComputedValues. 414 // 415 // Also, if we re-used existing reset structs, don't bother caching it back again. (Aside 416 // from being wasted effort, it will be wrong, since context.rule_cache_conditions won't be 417 // set appropriately if we didn't compute those reset properties.) 418 context.rule_cache_conditions.borrow_mut().set_uncacheable(); 419 } 420 421 context.builder.build() 422 } 423 424 /// For ignored colors mode, we sometimes want to do something equivalent to 425 /// "revert-or-initial", where we `revert` for a given origin, but then apply a 426 /// given initial value if nothing in other origins did override it. 427 /// 428 /// This is a bit of a clunky way of achieving this. 429 type DeclarationsToApplyUnlessOverriden = SmallVec<[PropertyDeclaration; 2]>; 430 431 fn tweak_when_ignoring_colors( 432 context: &computed::Context, 433 longhand_id: LonghandId, 434 origin: Origin, 435 declaration: &mut Cow<PropertyDeclaration>, 436 declarations_to_apply_unless_overridden: &mut DeclarationsToApplyUnlessOverriden, 437 ) { 438 use crate::values::computed::ToComputedValue; 439 use crate::values::specified::Color; 440 441 if !longhand_id.ignored_when_document_colors_disabled() { 442 return; 443 } 444 445 let is_ua_or_user_rule = matches!(origin, Origin::User | Origin::UserAgent); 446 if is_ua_or_user_rule { 447 return; 448 } 449 450 // Always honor colors if forced-color-adjust is set to none. 451 #[cfg(feature = "gecko")] 452 { 453 let forced = context 454 .builder 455 .get_inherited_text() 456 .clone_forced_color_adjust(); 457 if forced == computed::ForcedColorAdjust::None { 458 return; 459 } 460 } 461 462 // Don't override background-color on ::-moz-color-swatch. It is set as an 463 // author style (via the style attribute), but it's pretty important for it 464 // to show up for obvious reasons :) 465 if context 466 .builder 467 .pseudo 468 .map_or(false, |p| p.is_color_swatch()) 469 && longhand_id == LonghandId::BackgroundColor 470 { 471 return; 472 } 473 474 fn alpha_channel(color: &Color, context: &computed::Context) -> f32 { 475 // We assume here currentColor is opaque. 476 color 477 .to_computed_value(context) 478 .resolve_to_absolute(&AbsoluteColor::BLACK) 479 .alpha 480 } 481 482 // A few special-cases ahead. 483 match **declaration { 484 // Honor CSS-wide keywords like unset / revert / initial... 485 PropertyDeclaration::CSSWideKeyword(..) => return, 486 PropertyDeclaration::BackgroundColor(ref color) => { 487 // We honor system colors and transparent colors unconditionally. 488 // 489 // NOTE(emilio): We honor transparent unconditionally, like we do 490 // for color, even though it causes issues like bug 1625036. The 491 // reasoning is that the conditions that trigger that (having 492 // mismatched widget and default backgrounds) are both uncommon, and 493 // broken in other applications as well, and not honoring 494 // transparent makes stuff uglier or break unconditionally 495 // (bug 1666059, bug 1755713). 496 if color.honored_in_forced_colors_mode(/* allow_transparent = */ true) { 497 return; 498 } 499 // For background-color, we revert or initial-with-preserved-alpha 500 // otherwise, this is needed to preserve semi-transparent 501 // backgrounds. 502 let alpha = alpha_channel(color, context); 503 if alpha == 0.0 { 504 return; 505 } 506 let mut color = context.builder.device.default_background_color(); 507 color.alpha = alpha; 508 declarations_to_apply_unless_overridden 509 .push(PropertyDeclaration::BackgroundColor(color.into())) 510 }, 511 PropertyDeclaration::Color(ref color) => { 512 // We honor color: transparent and system colors. 513 if color 514 .0 515 .honored_in_forced_colors_mode(/* allow_transparent = */ true) 516 { 517 return; 518 } 519 // If the inherited color would be transparent, but we would 520 // override this with a non-transparent color, then override it with 521 // the default color. Otherwise just let it inherit through. 522 if context 523 .builder 524 .get_parent_inherited_text() 525 .clone_color() 526 .alpha 527 == 0.0 528 { 529 let color = context.builder.device.default_color(); 530 declarations_to_apply_unless_overridden.push(PropertyDeclaration::Color( 531 specified::ColorPropertyValue(color.into()), 532 )) 533 } 534 }, 535 // We honor url background-images if backplating. 536 #[cfg(feature = "gecko")] 537 PropertyDeclaration::BackgroundImage(ref bkg) => { 538 use crate::values::generics::image::Image; 539 if static_prefs::pref!("browser.display.permit_backplate") { 540 if bkg 541 .0 542 .iter() 543 .all(|image| matches!(*image, Image::Url(..) | Image::None)) 544 { 545 return; 546 } 547 } 548 }, 549 _ => { 550 // We honor system colors more generally for all colors. 551 // 552 // We used to honor transparent but that causes accessibility 553 // regressions like bug 1740924. 554 // 555 // NOTE(emilio): This doesn't handle caret-color and accent-color 556 // because those use a slightly different syntax (<color> | auto for 557 // example). 558 // 559 // That's probably fine though, as using a system color for 560 // caret-color doesn't make sense (using currentColor is fine), and 561 // we ignore accent-color in high-contrast-mode anyways. 562 if let Some(color) = declaration.color_value() { 563 if color.honored_in_forced_colors_mode(/* allow_transparent = */ false) { 564 return; 565 } 566 } 567 }, 568 } 569 570 *declaration.to_mut() = 571 PropertyDeclaration::css_wide_keyword(longhand_id, CSSWideKeyword::Revert); 572 } 573 574 /// We track the index only for prioritary properties. For other properties we can just iterate. 575 type DeclarationIndex = u16; 576 577 /// "Prioritary" properties are properties that other properties depend on in one way or another. 578 /// 579 /// We keep track of their position in the declaration vector, in order to be able to cascade them 580 /// separately in precise order. 581 #[derive(Copy, Clone)] 582 struct PrioritaryDeclarationPosition { 583 // DeclarationIndex::MAX signals no index. 584 most_important: DeclarationIndex, 585 least_important: DeclarationIndex, 586 } 587 588 impl Default for PrioritaryDeclarationPosition { 589 fn default() -> Self { 590 Self { 591 most_important: DeclarationIndex::MAX, 592 least_important: DeclarationIndex::MAX, 593 } 594 } 595 } 596 597 #[derive(Copy, Clone)] 598 struct Declaration<'a> { 599 decl: &'a PropertyDeclaration, 600 priority: CascadePriority, 601 next_index: DeclarationIndex, 602 } 603 604 /// The set of property declarations from our rules. 605 #[derive(Default)] 606 struct Declarations<'a> { 607 /// Whether we have any prioritary property. This is just a minor optimization. 608 has_prioritary_properties: bool, 609 /// A list of all the applicable longhand declarations. 610 longhand_declarations: SmallVec<[Declaration<'a>; 64]>, 611 /// The prioritary property position data. 612 prioritary_positions: [PrioritaryDeclarationPosition; property_counts::PRIORITARY], 613 } 614 615 impl<'a> Declarations<'a> { 616 fn note_prioritary_property(&mut self, id: PrioritaryPropertyId) { 617 let new_index = self.longhand_declarations.len(); 618 if new_index >= DeclarationIndex::MAX as usize { 619 // This prioritary property is past the amount of declarations we can track. Let's give 620 // up applying it to prevent getting confused. 621 return; 622 } 623 624 self.has_prioritary_properties = true; 625 let new_index = new_index as DeclarationIndex; 626 let position = &mut self.prioritary_positions[id as usize]; 627 if position.most_important == DeclarationIndex::MAX { 628 // We still haven't seen this property, record the current position as the most 629 // prioritary index. 630 position.most_important = new_index; 631 } else { 632 // Let the previous item in the list know about us. 633 self.longhand_declarations[position.least_important as usize].next_index = new_index; 634 } 635 position.least_important = new_index; 636 } 637 638 fn note_declaration( 639 &mut self, 640 decl: &'a PropertyDeclaration, 641 priority: CascadePriority, 642 id: LonghandId, 643 ) { 644 if let Some(id) = PrioritaryPropertyId::from_longhand(id) { 645 self.note_prioritary_property(id); 646 } 647 self.longhand_declarations.push(Declaration { 648 decl, 649 priority, 650 next_index: 0, 651 }); 652 } 653 } 654 655 struct Cascade<'b> { 656 first_line_reparenting: FirstLineReparenting<'b>, 657 try_tactic: &'b PositionTryFallbacksTryTactic, 658 ignore_colors: bool, 659 seen: LonghandIdSet, 660 author_specified: LonghandIdSet, 661 reverted_set: LonghandIdSet, 662 reverted: FxHashMap<LonghandId, (CascadePriority, bool)>, 663 declarations_to_apply_unless_overridden: DeclarationsToApplyUnlessOverriden, 664 } 665 666 impl<'b> Cascade<'b> { 667 fn new( 668 first_line_reparenting: FirstLineReparenting<'b>, 669 try_tactic: &'b PositionTryFallbacksTryTactic, 670 ignore_colors: bool, 671 ) -> Self { 672 Self { 673 first_line_reparenting, 674 try_tactic, 675 ignore_colors, 676 seen: LonghandIdSet::default(), 677 author_specified: LonghandIdSet::default(), 678 reverted_set: Default::default(), 679 reverted: Default::default(), 680 declarations_to_apply_unless_overridden: Default::default(), 681 } 682 } 683 684 fn substitute_variables_if_needed<'cache, 'decl>( 685 &self, 686 context: &mut computed::Context, 687 shorthand_cache: &'cache mut ShorthandsWithPropertyReferencesCache, 688 declaration: &'decl PropertyDeclaration, 689 attr_provider: &dyn AttributeProvider, 690 ) -> Cow<'decl, PropertyDeclaration> 691 where 692 'cache: 'decl, 693 { 694 let declaration = match *declaration { 695 PropertyDeclaration::WithVariables(ref declaration) => declaration, 696 ref d => return Cow::Borrowed(d), 697 }; 698 699 if !declaration.id.inherited() { 700 context.rule_cache_conditions.borrow_mut().set_uncacheable(); 701 702 // NOTE(emilio): We only really need to add the `display` / 703 // `content` flag if the CSS variable has not been specified on our 704 // declarations, but we don't have that information at this point, 705 // and it doesn't seem like an important enough optimization to 706 // warrant it. 707 match declaration.id { 708 LonghandId::Display => { 709 context 710 .builder 711 .add_flags(ComputedValueFlags::DISPLAY_DEPENDS_ON_INHERITED_STYLE); 712 }, 713 LonghandId::Content => { 714 context 715 .builder 716 .add_flags(ComputedValueFlags::CONTENT_DEPENDS_ON_INHERITED_STYLE); 717 }, 718 _ => {}, 719 } 720 } 721 722 debug_assert!( 723 context.builder.stylist.is_some(), 724 "Need a Stylist to substitute variables!" 725 ); 726 declaration.value.substitute_variables( 727 declaration.id, 728 context.builder.custom_properties(), 729 context.builder.stylist.unwrap(), 730 context, 731 shorthand_cache, 732 attr_provider, 733 ) 734 } 735 736 fn apply_one_prioritary_property( 737 &mut self, 738 context: &mut computed::Context, 739 decls: &Declarations, 740 cache: &mut ShorthandsWithPropertyReferencesCache, 741 id: PrioritaryPropertyId, 742 attr_provider: &dyn AttributeProvider, 743 ) -> bool { 744 let mut index = decls.prioritary_positions[id as usize].most_important; 745 if index == DeclarationIndex::MAX { 746 return false; 747 } 748 749 let longhand_id = id.to_longhand(); 750 debug_assert!( 751 !longhand_id.is_logical(), 752 "That could require more book-keeping" 753 ); 754 loop { 755 let decl = decls.longhand_declarations[index as usize]; 756 self.apply_one_longhand( 757 context, 758 longhand_id, 759 decl.decl, 760 decl.priority, 761 cache, 762 attr_provider, 763 ); 764 if self.seen.contains(longhand_id) { 765 return true; // Common case, we're done. 766 } 767 debug_assert!( 768 self.reverted_set.contains(longhand_id), 769 "How else can we fail to apply a prioritary property?" 770 ); 771 debug_assert!( 772 decl.next_index == 0 || decl.next_index > index, 773 "should make progress! {} -> {}", 774 index, 775 decl.next_index, 776 ); 777 index = decl.next_index; 778 if index == 0 { 779 break; 780 } 781 } 782 false 783 } 784 785 fn apply_prioritary_properties( 786 &mut self, 787 context: &mut computed::Context, 788 decls: &Declarations, 789 cache: &mut ShorthandsWithPropertyReferencesCache, 790 attr_provider: &dyn AttributeProvider, 791 ) { 792 // Keeps apply_one_prioritary_property calls readable, considering the repititious 793 // arguments. 794 macro_rules! apply { 795 ($prop:ident) => { 796 self.apply_one_prioritary_property( 797 context, 798 decls, 799 cache, 800 PrioritaryPropertyId::$prop, 801 attr_provider, 802 ) 803 }; 804 } 805 806 if !decls.has_prioritary_properties { 807 return; 808 } 809 810 let has_writing_mode = apply!(WritingMode) | apply!(Direction); 811 #[cfg(feature = "gecko")] 812 let has_writing_mode = has_writing_mode | apply!(TextOrientation); 813 814 if has_writing_mode { 815 context.builder.writing_mode = WritingMode::new(context.builder.get_inherited_box()) 816 } 817 818 if apply!(Zoom) { 819 context.builder.recompute_effective_zooms(); 820 if !context.builder.effective_zoom_for_inheritance.is_one() { 821 // NOTE(emilio): This is a bit of a hack, but matches the shipped WebKit and Blink 822 // behavior for now. Ideally, in the future, we have a pass over all 823 // implicitly-or-explicitly-inherited properties that can contain lengths and 824 // re-compute them properly, see https://github.com/w3c/csswg-drafts/issues/9397. 825 // TODO(emilio): we need to eagerly do this for line-height as well, probably. 826 self.recompute_font_size_for_zoom_change(&mut context.builder); 827 } 828 } 829 830 // Compute font-family. 831 let has_font_family = apply!(FontFamily); 832 let has_lang = apply!(XLang); 833 #[cfg(feature = "gecko")] 834 { 835 if has_lang { 836 self.recompute_initial_font_family_if_needed(&mut context.builder); 837 } 838 if has_font_family { 839 self.prioritize_user_fonts_if_needed(&mut context.builder); 840 } 841 842 // Compute font-size. 843 if apply!(XTextScale) { 844 self.unzoom_fonts_if_needed(&mut context.builder); 845 } 846 let has_font_size = apply!(FontSize); 847 let has_math_depth = apply!(MathDepth); 848 let has_min_font_size_ratio = apply!(MozMinFontSizeRatio); 849 850 if has_math_depth && has_font_size { 851 self.recompute_math_font_size_if_needed(context); 852 } 853 if has_lang || has_font_family { 854 self.recompute_keyword_font_size_if_needed(context); 855 } 856 if has_font_size || has_min_font_size_ratio || has_lang || has_font_family { 857 self.constrain_font_size_if_needed(&mut context.builder); 858 } 859 } 860 861 #[cfg(feature = "servo")] 862 { 863 apply!(FontSize); 864 if has_lang || has_font_family { 865 self.recompute_keyword_font_size_if_needed(context); 866 } 867 } 868 869 // Compute the rest of the first-available-font-affecting properties. 870 apply!(FontWeight); 871 apply!(FontStretch); 872 apply!(FontStyle); 873 #[cfg(feature = "gecko")] 874 apply!(FontSizeAdjust); 875 876 #[cfg(feature = "gecko")] 877 apply!(ForcedColorAdjust); 878 // color-scheme needs to be after forced-color-adjust, since it's one of the "skipped in 879 // forced-colors-mode" properties. 880 if apply!(ColorScheme) { 881 context.builder.color_scheme = context.builder.get_inherited_ui().color_scheme_bits(); 882 } 883 apply!(LineHeight); 884 } 885 886 fn apply_non_prioritary_properties( 887 &mut self, 888 context: &mut computed::Context, 889 longhand_declarations: &[Declaration], 890 shorthand_cache: &mut ShorthandsWithPropertyReferencesCache, 891 properties_to_apply: &LonghandIdSet, 892 attr_provider: &dyn AttributeProvider, 893 ) { 894 debug_assert!(!properties_to_apply.contains_any(LonghandIdSet::prioritary_properties())); 895 debug_assert!(self.declarations_to_apply_unless_overridden.is_empty()); 896 for declaration in &*longhand_declarations { 897 let mut longhand_id = declaration.decl.id().as_longhand().unwrap(); 898 if !properties_to_apply.contains(longhand_id) { 899 continue; 900 } 901 debug_assert!(PrioritaryPropertyId::from_longhand(longhand_id).is_none()); 902 let is_logical = longhand_id.is_logical(); 903 if is_logical { 904 let wm = context.builder.writing_mode; 905 context 906 .rule_cache_conditions 907 .borrow_mut() 908 .set_writing_mode_dependency(wm); 909 longhand_id = longhand_id.to_physical(wm); 910 } 911 self.apply_one_longhand( 912 context, 913 longhand_id, 914 declaration.decl, 915 declaration.priority, 916 shorthand_cache, 917 attr_provider, 918 ); 919 } 920 if !self.declarations_to_apply_unless_overridden.is_empty() { 921 debug_assert!(self.ignore_colors); 922 for declaration in std::mem::take(&mut self.declarations_to_apply_unless_overridden) { 923 let longhand_id = declaration.id().as_longhand().unwrap(); 924 debug_assert!(!longhand_id.is_logical()); 925 if !self.seen.contains(longhand_id) { 926 unsafe { 927 self.do_apply_declaration(context, longhand_id, &declaration); 928 } 929 } 930 } 931 } 932 933 if !context.builder.effective_zoom_for_inheritance.is_one() { 934 self.recompute_zoom_dependent_inherited_lengths(context); 935 } 936 } 937 938 #[cold] 939 fn recompute_zoom_dependent_inherited_lengths(&self, context: &mut computed::Context) { 940 debug_assert!(self.seen.contains(LonghandId::Zoom)); 941 for prop in LonghandIdSet::zoom_dependent_inherited_properties().iter() { 942 if self.seen.contains(prop) { 943 continue; 944 } 945 let declaration = PropertyDeclaration::css_wide_keyword(prop, CSSWideKeyword::Inherit); 946 unsafe { 947 self.do_apply_declaration(context, prop, &declaration); 948 } 949 } 950 } 951 952 fn apply_one_longhand( 953 &mut self, 954 context: &mut computed::Context, 955 longhand_id: LonghandId, 956 declaration: &PropertyDeclaration, 957 priority: CascadePriority, 958 cache: &mut ShorthandsWithPropertyReferencesCache, 959 attr_provider: &dyn AttributeProvider, 960 ) { 961 debug_assert!(!longhand_id.is_logical()); 962 let origin = priority.cascade_level().origin(); 963 if self.seen.contains(longhand_id) { 964 return; 965 } 966 967 if self.reverted_set.contains(longhand_id) { 968 if let Some(&(reverted_priority, is_origin_revert)) = self.reverted.get(&longhand_id) { 969 if !reverted_priority.allows_when_reverted(&priority, is_origin_revert) { 970 return; 971 } 972 } 973 } 974 975 let mut declaration = 976 self.substitute_variables_if_needed(context, cache, declaration, attr_provider); 977 978 // When document colors are disabled, do special handling of 979 // properties that are marked as ignored in that mode. 980 if self.ignore_colors { 981 tweak_when_ignoring_colors( 982 context, 983 longhand_id, 984 origin, 985 &mut declaration, 986 &mut self.declarations_to_apply_unless_overridden, 987 ); 988 } 989 let can_skip_apply = match declaration.get_css_wide_keyword() { 990 Some(keyword) => { 991 if matches!( 992 keyword, 993 CSSWideKeyword::RevertLayer | CSSWideKeyword::Revert 994 ) { 995 let origin_revert = keyword == CSSWideKeyword::Revert; 996 // We intentionally don't want to insert it into `self.seen`, `reverted` takes 997 // care of rejecting other declarations as needed. 998 self.reverted_set.insert(longhand_id); 999 self.reverted.insert(longhand_id, (priority, origin_revert)); 1000 return; 1001 } 1002 1003 let inherited = longhand_id.inherited(); 1004 let zoomed = !context.builder.effective_zoom_for_inheritance.is_one() 1005 && longhand_id.zoom_dependent(); 1006 match keyword { 1007 CSSWideKeyword::Revert | CSSWideKeyword::RevertLayer => unreachable!(), 1008 CSSWideKeyword::Unset => !zoomed || !inherited, 1009 CSSWideKeyword::Inherit => inherited && !zoomed, 1010 CSSWideKeyword::Initial => !inherited, 1011 } 1012 }, 1013 None => false, 1014 }; 1015 1016 self.seen.insert(longhand_id); 1017 if origin == Origin::Author { 1018 self.author_specified.insert(longhand_id); 1019 } 1020 1021 if !can_skip_apply { 1022 unsafe { self.do_apply_declaration(context, longhand_id, &declaration) } 1023 } 1024 } 1025 1026 #[inline] 1027 unsafe fn do_apply_declaration( 1028 &self, 1029 context: &mut computed::Context, 1030 longhand_id: LonghandId, 1031 declaration: &PropertyDeclaration, 1032 ) { 1033 debug_assert!(!longhand_id.is_logical()); 1034 // We could (and used to) use a pattern match here, but that bloats this 1035 // function to over 100K of compiled code! 1036 // 1037 // To improve i-cache behavior, we outline the individual functions and 1038 // use virtual dispatch instead. 1039 (CASCADE_PROPERTY[longhand_id as usize])(&declaration, context); 1040 } 1041 1042 fn compute_visited_style_if_needed<E>( 1043 &self, 1044 context: &mut computed::Context, 1045 element: Option<E>, 1046 parent_style: Option<&ComputedValues>, 1047 layout_parent_style: Option<&ComputedValues>, 1048 visited_rules: &StrongRuleNode, 1049 guards: &StylesheetGuards, 1050 ) where 1051 E: TElement, 1052 { 1053 let is_link = context.builder.pseudo.is_none() && element.unwrap().is_link(); 1054 1055 macro_rules! visited_parent { 1056 ($parent:expr) => { 1057 if is_link { 1058 $parent 1059 } else { 1060 $parent.map(|p| p.visited_style().unwrap_or(p)) 1061 } 1062 }; 1063 } 1064 1065 // We could call apply_declarations directly, but that'd cause 1066 // another instantiation of this function which is not great. 1067 let style = cascade_rules( 1068 context.builder.stylist.unwrap(), 1069 context.builder.pseudo, 1070 visited_rules, 1071 guards, 1072 visited_parent!(parent_style), 1073 visited_parent!(layout_parent_style), 1074 self.first_line_reparenting, 1075 self.try_tactic, 1076 CascadeMode::Visited { 1077 unvisited_context: &*context, 1078 }, 1079 // Cascade input flags don't matter for the visited style, they are 1080 // in the main (unvisited) style. 1081 Default::default(), 1082 // The rule cache doesn't care about caching :visited 1083 // styles, we cache the unvisited style instead. We still do 1084 // need to set the caching dependencies properly if present 1085 // though, so the cache conditions need to match. 1086 None, // rule_cache 1087 &mut *context.rule_cache_conditions.borrow_mut(), 1088 element, 1089 ); 1090 context.builder.visited_style = Some(style); 1091 } 1092 1093 fn finished_applying_properties(&self, builder: &mut StyleBuilder) { 1094 #[cfg(feature = "gecko")] 1095 { 1096 if let Some(bg) = builder.get_background_if_mutated() { 1097 bg.fill_arrays(); 1098 } 1099 1100 if let Some(svg) = builder.get_svg_if_mutated() { 1101 svg.fill_arrays(); 1102 } 1103 } 1104 1105 if self 1106 .author_specified 1107 .contains_any(LonghandIdSet::border_background_properties()) 1108 { 1109 builder.add_flags(ComputedValueFlags::HAS_AUTHOR_SPECIFIED_BORDER_BACKGROUND); 1110 } 1111 1112 if self.author_specified.contains(LonghandId::FontFamily) { 1113 builder.add_flags(ComputedValueFlags::HAS_AUTHOR_SPECIFIED_FONT_FAMILY); 1114 } 1115 1116 if self.author_specified.contains(LonghandId::Color) { 1117 builder.add_flags(ComputedValueFlags::HAS_AUTHOR_SPECIFIED_TEXT_COLOR); 1118 } 1119 1120 if self.author_specified.contains(LonghandId::LetterSpacing) { 1121 builder.add_flags(ComputedValueFlags::HAS_AUTHOR_SPECIFIED_LETTER_SPACING); 1122 } 1123 1124 if self.author_specified.contains(LonghandId::WordSpacing) { 1125 builder.add_flags(ComputedValueFlags::HAS_AUTHOR_SPECIFIED_WORD_SPACING); 1126 } 1127 1128 if self 1129 .author_specified 1130 .contains(LonghandId::FontSynthesisWeight) 1131 { 1132 builder.add_flags(ComputedValueFlags::HAS_AUTHOR_SPECIFIED_FONT_SYNTHESIS_WEIGHT); 1133 } 1134 1135 #[cfg(feature = "gecko")] 1136 if self 1137 .author_specified 1138 .contains(LonghandId::FontSynthesisStyle) 1139 { 1140 builder.add_flags(ComputedValueFlags::HAS_AUTHOR_SPECIFIED_FONT_SYNTHESIS_STYLE); 1141 } 1142 1143 #[cfg(feature = "servo")] 1144 { 1145 if let Some(font) = builder.get_font_if_mutated() { 1146 font.compute_font_hash(); 1147 } 1148 } 1149 } 1150 1151 fn try_to_use_cached_reset_properties( 1152 &self, 1153 builder: &mut StyleBuilder<'b>, 1154 cache: Option<&'b RuleCache>, 1155 guards: &StylesheetGuards, 1156 ) -> bool { 1157 let style = match self.first_line_reparenting { 1158 FirstLineReparenting::Yes { style_to_reparent } => style_to_reparent, 1159 FirstLineReparenting::No => { 1160 let Some(cache) = cache else { return false }; 1161 let Some(style) = cache.find(guards, builder) else { 1162 return false; 1163 }; 1164 style 1165 }, 1166 }; 1167 1168 builder.copy_reset_from(style); 1169 1170 // We're using the same reset style as another element, and we'll skip 1171 // applying the relevant properties. So we need to do the relevant 1172 // bookkeeping here to keep these bits correct. 1173 // 1174 // Note that the border/background properties are non-inherited, so we 1175 // don't need to do anything else other than just copying the bits over. 1176 // 1177 // When using this optimization, we also need to copy whether the old 1178 // style specified viewport units / used font-relative lengths, this one 1179 // would as well. It matches the same rules, so it is the right thing 1180 // to do anyways, even if it's only used on inherited properties. 1181 let bits_to_copy = ComputedValueFlags::HAS_AUTHOR_SPECIFIED_BORDER_BACKGROUND 1182 | ComputedValueFlags::DEPENDS_ON_SELF_FONT_METRICS 1183 | ComputedValueFlags::DEPENDS_ON_INHERITED_FONT_METRICS 1184 | ComputedValueFlags::USES_CONTAINER_UNITS 1185 | ComputedValueFlags::USES_VIEWPORT_UNITS; 1186 builder.add_flags(style.flags & bits_to_copy); 1187 1188 true 1189 } 1190 1191 /// The initial font depends on the current lang group so we may need to 1192 /// recompute it if the language changed. 1193 #[inline] 1194 #[cfg(feature = "gecko")] 1195 fn recompute_initial_font_family_if_needed(&self, builder: &mut StyleBuilder) { 1196 use crate::gecko_bindings::bindings; 1197 use crate::values::computed::font::FontFamily; 1198 1199 let default_font_type = { 1200 let font = builder.get_font(); 1201 1202 if !font.mFont.family.is_initial { 1203 return; 1204 } 1205 1206 let default_font_type = unsafe { 1207 bindings::Gecko_nsStyleFont_ComputeFallbackFontTypeForLanguage( 1208 builder.device.document(), 1209 font.mLanguage.mRawPtr, 1210 ) 1211 }; 1212 1213 let initial_generic = font.mFont.family.families.single_generic(); 1214 debug_assert!( 1215 initial_generic.is_some(), 1216 "Initial font should be just one generic font" 1217 ); 1218 if initial_generic == Some(default_font_type) { 1219 return; 1220 } 1221 1222 default_font_type 1223 }; 1224 1225 // NOTE: Leaves is_initial untouched. 1226 builder.mutate_font().mFont.family.families = 1227 FontFamily::generic(default_font_type).families.clone(); 1228 } 1229 1230 /// Prioritize user fonts if needed by pref. 1231 #[inline] 1232 #[cfg(feature = "gecko")] 1233 fn prioritize_user_fonts_if_needed(&self, builder: &mut StyleBuilder) { 1234 use crate::gecko_bindings::bindings; 1235 1236 // Check the use_document_fonts setting for content, but for chrome 1237 // documents they're treated as always enabled. 1238 if static_prefs::pref!("browser.display.use_document_fonts") != 0 1239 || builder.device.chrome_rules_enabled_for_document() 1240 { 1241 return; 1242 } 1243 1244 let default_font_type = { 1245 let font = builder.get_font(); 1246 1247 if font.mFont.family.is_system_font { 1248 return; 1249 } 1250 1251 if !font.mFont.family.families.needs_user_font_prioritization() { 1252 return; 1253 } 1254 1255 unsafe { 1256 bindings::Gecko_nsStyleFont_ComputeFallbackFontTypeForLanguage( 1257 builder.device.document(), 1258 font.mLanguage.mRawPtr, 1259 ) 1260 } 1261 }; 1262 1263 let font = builder.mutate_font(); 1264 font.mFont 1265 .family 1266 .families 1267 .prioritize_first_generic_or_prepend(default_font_type); 1268 } 1269 1270 /// Some keyword sizes depend on the font family and language. 1271 fn recompute_keyword_font_size_if_needed(&self, context: &mut computed::Context) { 1272 use crate::values::computed::ToComputedValue; 1273 1274 if !self.seen.contains(LonghandId::XLang) && !self.seen.contains(LonghandId::FontFamily) { 1275 return; 1276 } 1277 1278 let new_size = { 1279 let font = context.builder.get_font(); 1280 let info = font.clone_font_size().keyword_info; 1281 let new_size = match info.kw { 1282 specified::FontSizeKeyword::None => return, 1283 _ => { 1284 context.for_non_inherited_property = false; 1285 specified::FontSize::Keyword(info).to_computed_value(context) 1286 }, 1287 }; 1288 1289 #[cfg(feature = "gecko")] 1290 if font.mScriptUnconstrainedSize == new_size.computed_size { 1291 return; 1292 } 1293 1294 new_size 1295 }; 1296 1297 context.builder.mutate_font().set_font_size(new_size); 1298 } 1299 1300 /// Some properties, plus setting font-size itself, may make us go out of 1301 /// our minimum font-size range. 1302 #[cfg(feature = "gecko")] 1303 fn constrain_font_size_if_needed(&self, builder: &mut StyleBuilder) { 1304 use crate::gecko_bindings::bindings; 1305 use crate::values::generics::NonNegative; 1306 1307 let min_font_size = { 1308 let font = builder.get_font(); 1309 let min_font_size = unsafe { 1310 bindings::Gecko_nsStyleFont_ComputeMinSize(&**font, builder.device.document()) 1311 }; 1312 1313 if font.mFont.size.0 >= min_font_size { 1314 return; 1315 } 1316 1317 NonNegative(min_font_size) 1318 }; 1319 1320 builder.mutate_font().mFont.size = min_font_size; 1321 } 1322 1323 /// <svg:text> is not affected by text zoom, and it uses a preshint to disable it. We fix up 1324 /// the struct when this happens by unzooming its contained font values, which will have been 1325 /// zoomed in the parent. 1326 #[cfg(feature = "gecko")] 1327 fn unzoom_fonts_if_needed(&self, builder: &mut StyleBuilder) { 1328 debug_assert!(self.seen.contains(LonghandId::XTextScale)); 1329 1330 let parent_text_scale = builder.get_parent_font().clone__x_text_scale(); 1331 let text_scale = builder.get_font().clone__x_text_scale(); 1332 if parent_text_scale == text_scale { 1333 return; 1334 } 1335 debug_assert_ne!( 1336 parent_text_scale.text_zoom_enabled(), 1337 text_scale.text_zoom_enabled(), 1338 "There's only one value that disables it" 1339 ); 1340 debug_assert!( 1341 !text_scale.text_zoom_enabled(), 1342 "We only ever disable text zoom never enable it" 1343 ); 1344 let device = builder.device; 1345 builder.mutate_font().unzoom_fonts(device); 1346 } 1347 1348 fn recompute_font_size_for_zoom_change(&self, builder: &mut StyleBuilder) { 1349 debug_assert!(self.seen.contains(LonghandId::Zoom)); 1350 // NOTE(emilio): Intentionally not using the effective zoom here, since all the inherited 1351 // zooms are already applied. 1352 let old_size = builder.get_font().clone_font_size(); 1353 let new_size = old_size.zoom(builder.effective_zoom_for_inheritance); 1354 if old_size == new_size { 1355 return; 1356 } 1357 builder.mutate_font().set_font_size(new_size); 1358 } 1359 1360 /// Special handling of font-size: math (used for MathML). 1361 /// https://w3c.github.io/mathml-core/#the-math-script-level-property 1362 /// TODO: Bug: 1548471: MathML Core also does not specify a script min size 1363 /// should we unship that feature or standardize it? 1364 #[cfg(feature = "gecko")] 1365 fn recompute_math_font_size_if_needed(&self, context: &mut computed::Context) { 1366 use crate::values::generics::NonNegative; 1367 1368 // Do not do anything if font-size: math or math-depth is not set. 1369 if context.builder.get_font().clone_font_size().keyword_info.kw 1370 != specified::FontSizeKeyword::Math 1371 { 1372 return; 1373 } 1374 1375 const SCALE_FACTOR_WHEN_INCREMENTING_MATH_DEPTH_BY_ONE: f32 = 0.71; 1376 1377 // Helper function that calculates the scale factor applied to font-size 1378 // when math-depth goes from parent_math_depth to computed_math_depth. 1379 // This function is essentially a modification of the MathML3's formula 1380 // 0.71^(parent_math_depth - computed_math_depth) so that a scale factor 1381 // of parent_script_percent_scale_down is applied when math-depth goes 1382 // from 0 to 1 and parent_script_script_percent_scale_down is applied 1383 // when math-depth goes from 0 to 2. This is also a straightforward 1384 // implementation of the specification's algorithm: 1385 // https://w3c.github.io/mathml-core/#the-math-script-level-property 1386 fn scale_factor_for_math_depth_change( 1387 parent_math_depth: i32, 1388 computed_math_depth: i32, 1389 parent_script_percent_scale_down: Option<f32>, 1390 parent_script_script_percent_scale_down: Option<f32>, 1391 ) -> f32 { 1392 let mut a = parent_math_depth; 1393 let mut b = computed_math_depth; 1394 let c = SCALE_FACTOR_WHEN_INCREMENTING_MATH_DEPTH_BY_ONE; 1395 let scale_between_0_and_1 = parent_script_percent_scale_down.unwrap_or_else(|| c); 1396 let scale_between_0_and_2 = 1397 parent_script_script_percent_scale_down.unwrap_or_else(|| c * c); 1398 let mut s = 1.0; 1399 let mut invert_scale_factor = false; 1400 if a == b { 1401 return s; 1402 } 1403 if b < a { 1404 std::mem::swap(&mut a, &mut b); 1405 invert_scale_factor = true; 1406 } 1407 let mut e = b - a; 1408 if a <= 0 && b >= 2 { 1409 s *= scale_between_0_and_2; 1410 e -= 2; 1411 } else if a == 1 { 1412 s *= scale_between_0_and_2 / scale_between_0_and_1; 1413 e -= 1; 1414 } else if b == 1 { 1415 s *= scale_between_0_and_1; 1416 e -= 1; 1417 } 1418 s *= (c as f32).powi(e); 1419 if invert_scale_factor { 1420 1.0 / s.max(f32::MIN_POSITIVE) 1421 } else { 1422 s 1423 } 1424 } 1425 1426 let (new_size, new_unconstrained_size) = { 1427 use crate::values::specified::font::QueryFontMetricsFlags; 1428 1429 let builder = &context.builder; 1430 let font = builder.get_font(); 1431 let parent_font = builder.get_parent_font(); 1432 1433 let delta = font.mMathDepth.saturating_sub(parent_font.mMathDepth); 1434 1435 if delta == 0 { 1436 return; 1437 } 1438 1439 let mut min = parent_font.mScriptMinSize; 1440 if font.mXTextScale.text_zoom_enabled() { 1441 min = builder.device.zoom_text(min); 1442 } 1443 1444 // Calculate scale factor following MathML Core's algorithm. 1445 let scale = { 1446 // Script scale factors are independent of orientation. 1447 let font_metrics = context.query_font_metrics( 1448 FontBaseSize::InheritedStyle, 1449 FontMetricsOrientation::Horizontal, 1450 QueryFontMetricsFlags::NEEDS_MATH_SCALES, 1451 ); 1452 scale_factor_for_math_depth_change( 1453 parent_font.mMathDepth as i32, 1454 font.mMathDepth as i32, 1455 font_metrics.script_percent_scale_down, 1456 font_metrics.script_script_percent_scale_down, 1457 ) 1458 }; 1459 1460 let parent_size = parent_font.mSize.0; 1461 let parent_unconstrained_size = parent_font.mScriptUnconstrainedSize.0; 1462 let new_size = parent_size.scale_by(scale); 1463 let new_unconstrained_size = parent_unconstrained_size.scale_by(scale); 1464 1465 if scale <= 1. { 1466 // The parent size can be smaller than scriptminsize, e.g. if it 1467 // was specified explicitly. Don't scale in this case, but we 1468 // don't want to set it to scriptminsize either since that will 1469 // make it larger. 1470 if parent_size <= min { 1471 (parent_size, new_unconstrained_size) 1472 } else { 1473 (min.max(new_size), new_unconstrained_size) 1474 } 1475 } else { 1476 // If the new unconstrained size is larger than the min size, 1477 // this means we have escaped the grasp of scriptminsize and can 1478 // revert to using the unconstrained size. 1479 // However, if the new size is even larger (perhaps due to usage 1480 // of em units), use that instead. 1481 ( 1482 new_size.min(new_unconstrained_size.max(min)), 1483 new_unconstrained_size, 1484 ) 1485 } 1486 }; 1487 let font = context.builder.mutate_font(); 1488 font.mFont.size = NonNegative(new_size); 1489 font.mSize = NonNegative(new_size); 1490 font.mScriptUnconstrainedSize = NonNegative(new_unconstrained_size); 1491 } 1492 }