context.rs (24447B)
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 context within which style is calculated. 6 7 #[cfg(feature = "servo")] 8 use crate::animation::DocumentAnimationSet; 9 use crate::bloom::StyleBloom; 10 use crate::computed_value_flags::ComputedValueFlags; 11 use crate::data::{EagerPseudoStyles, ElementData}; 12 use crate::derives::*; 13 use crate::dom::{SendElement, TElement}; 14 #[cfg(feature = "gecko")] 15 use crate::gecko_bindings::structs; 16 use crate::parallel::{STACK_SAFETY_MARGIN_KB, STYLE_THREAD_STACK_SIZE_KB}; 17 use crate::properties::ComputedValues; 18 #[cfg(feature = "servo")] 19 use crate::properties::PropertyId; 20 use crate::rule_cache::RuleCache; 21 use crate::rule_tree::StrongRuleNode; 22 use crate::selector_parser::{SnapshotMap, EAGER_PSEUDO_COUNT}; 23 use crate::shared_lock::StylesheetGuards; 24 use crate::sharing::StyleSharingCache; 25 use crate::stylist::Stylist; 26 use crate::thread_state::{self, ThreadState}; 27 use crate::traversal::DomTraversal; 28 use crate::traversal_flags::TraversalFlags; 29 use app_units::Au; 30 use euclid::default::Size2D; 31 use euclid::Scale; 32 #[cfg(feature = "servo")] 33 use rustc_hash::FxHashMap; 34 use selectors::context::SelectorCaches; 35 #[cfg(feature = "gecko")] 36 use servo_arc::Arc; 37 use std::fmt; 38 use std::ops; 39 use std::time::{Duration, Instant}; 40 use style_traits::CSSPixel; 41 use style_traits::DevicePixel; 42 #[cfg(feature = "servo")] 43 use style_traits::SpeculativePainter; 44 #[cfg(feature = "servo")] 45 use stylo_atoms::Atom; 46 47 pub use selectors::matching::QuirksMode; 48 49 /// A global options structure for the style system. We use this instead of 50 /// opts to abstract across Gecko and Servo. 51 #[derive(Clone)] 52 pub struct StyleSystemOptions { 53 /// Whether the style sharing cache is disabled. 54 pub disable_style_sharing_cache: bool, 55 /// Whether we should dump statistics about the style system. 56 pub dump_style_statistics: bool, 57 /// The minimum number of elements that must be traversed to trigger a dump 58 /// of style statistics. 59 pub style_statistics_threshold: usize, 60 } 61 62 #[cfg(feature = "gecko")] 63 fn get_env_bool(name: &str) -> bool { 64 use std::env; 65 match env::var(name) { 66 Ok(s) => !s.is_empty(), 67 Err(_) => false, 68 } 69 } 70 71 const DEFAULT_STATISTICS_THRESHOLD: usize = 50; 72 73 #[cfg(feature = "gecko")] 74 fn get_env_usize(name: &str) -> Option<usize> { 75 use std::env; 76 env::var(name).ok().map(|s| { 77 s.parse::<usize>() 78 .expect("Couldn't parse environmental variable as usize") 79 }) 80 } 81 82 /// A global variable holding the state of 83 /// `StyleSystemOptions::default().disable_style_sharing_cache`. 84 /// See [#22854](https://github.com/servo/servo/issues/22854). 85 #[cfg(feature = "servo")] 86 pub static DEFAULT_DISABLE_STYLE_SHARING_CACHE: std::sync::atomic::AtomicBool = 87 std::sync::atomic::AtomicBool::new(false); 88 89 /// A global variable holding the state of 90 /// `StyleSystemOptions::default().dump_style_statistics`. 91 /// See [#22854](https://github.com/servo/servo/issues/22854). 92 #[cfg(feature = "servo")] 93 pub static DEFAULT_DUMP_STYLE_STATISTICS: std::sync::atomic::AtomicBool = 94 std::sync::atomic::AtomicBool::new(false); 95 96 impl Default for StyleSystemOptions { 97 #[cfg(feature = "servo")] 98 fn default() -> Self { 99 use std::sync::atomic::Ordering; 100 101 StyleSystemOptions { 102 disable_style_sharing_cache: DEFAULT_DISABLE_STYLE_SHARING_CACHE 103 .load(Ordering::Relaxed), 104 dump_style_statistics: DEFAULT_DUMP_STYLE_STATISTICS.load(Ordering::Relaxed), 105 style_statistics_threshold: DEFAULT_STATISTICS_THRESHOLD, 106 } 107 } 108 109 #[cfg(feature = "gecko")] 110 fn default() -> Self { 111 StyleSystemOptions { 112 disable_style_sharing_cache: get_env_bool("DISABLE_STYLE_SHARING_CACHE"), 113 dump_style_statistics: get_env_bool("DUMP_STYLE_STATISTICS"), 114 style_statistics_threshold: get_env_usize("STYLE_STATISTICS_THRESHOLD") 115 .unwrap_or(DEFAULT_STATISTICS_THRESHOLD), 116 } 117 } 118 } 119 120 /// A shared style context. 121 /// 122 /// There's exactly one of these during a given restyle traversal, and it's 123 /// shared among the worker threads. 124 pub struct SharedStyleContext<'a> { 125 /// The CSS selector stylist. 126 pub stylist: &'a Stylist, 127 128 /// Whether visited styles are enabled. 129 /// 130 /// They may be disabled when Gecko's pref layout.css.visited_links_enabled 131 /// is false, or when in private browsing mode. 132 pub visited_styles_enabled: bool, 133 134 /// Configuration options. 135 pub options: StyleSystemOptions, 136 137 /// Guards for pre-acquired locks 138 pub guards: StylesheetGuards<'a>, 139 140 /// The current time for transitions and animations. This is needed to ensure 141 /// a consistent sampling time and also to adjust the time for testing. 142 pub current_time_for_animations: f64, 143 144 /// Flags controlling how we traverse the tree. 145 pub traversal_flags: TraversalFlags, 146 147 /// A map with our snapshots in order to handle restyle hints. 148 pub snapshot_map: &'a SnapshotMap, 149 150 /// The state of all animations for our styled elements. 151 #[cfg(feature = "servo")] 152 pub animations: DocumentAnimationSet, 153 154 /// Paint worklets 155 #[cfg(feature = "servo")] 156 pub registered_speculative_painters: &'a dyn RegisteredSpeculativePainters, 157 } 158 159 impl<'a> SharedStyleContext<'a> { 160 /// Return a suitable viewport size in order to be used for viewport units. 161 pub fn viewport_size(&self) -> Size2D<Au> { 162 self.stylist.device().au_viewport_size() 163 } 164 165 /// The device pixel ratio 166 pub fn device_pixel_ratio(&self) -> Scale<f32, CSSPixel, DevicePixel> { 167 self.stylist.device().device_pixel_ratio() 168 } 169 170 /// The quirks mode of the document. 171 pub fn quirks_mode(&self) -> QuirksMode { 172 self.stylist.quirks_mode() 173 } 174 } 175 176 /// The structure holds various intermediate inputs that are eventually used by 177 /// by the cascade. 178 /// 179 /// The matching and cascading process stores them in this format temporarily 180 /// within the `CurrentElementInfo`. At the end of the cascade, they are folded 181 /// down into the main `ComputedValues` to reduce memory usage per element while 182 /// still remaining accessible. 183 #[derive(Clone, Debug, Default)] 184 pub struct CascadeInputs { 185 /// The rule node representing the ordered list of rules matched for this 186 /// node. 187 pub rules: Option<StrongRuleNode>, 188 189 /// The rule node representing the ordered list of rules matched for this 190 /// node if visited, only computed if there's a relevant link for this 191 /// element. A element's "relevant link" is the element being matched if it 192 /// is a link or the nearest ancestor link. 193 pub visited_rules: Option<StrongRuleNode>, 194 195 /// The set of flags from container queries that we need for invalidation. 196 pub flags: ComputedValueFlags, 197 } 198 199 impl CascadeInputs { 200 /// Construct inputs from previous cascade results, if any. 201 pub fn new_from_style(style: &ComputedValues) -> Self { 202 Self { 203 rules: style.rules.clone(), 204 visited_rules: style.visited_style().and_then(|v| v.rules.clone()), 205 flags: style.flags.for_cascade_inputs(), 206 } 207 } 208 } 209 210 /// A list of cascade inputs for eagerly-cascaded pseudo-elements. 211 /// The list is stored inline. 212 #[derive(Debug)] 213 pub struct EagerPseudoCascadeInputs(Option<[Option<CascadeInputs>; EAGER_PSEUDO_COUNT]>); 214 215 // Manually implement `Clone` here because the derived impl of `Clone` for 216 // array types assumes the value inside is `Copy`. 217 impl Clone for EagerPseudoCascadeInputs { 218 fn clone(&self) -> Self { 219 if self.0.is_none() { 220 return EagerPseudoCascadeInputs(None); 221 } 222 let self_inputs = self.0.as_ref().unwrap(); 223 let mut inputs: [Option<CascadeInputs>; EAGER_PSEUDO_COUNT] = Default::default(); 224 for i in 0..EAGER_PSEUDO_COUNT { 225 inputs[i] = self_inputs[i].clone(); 226 } 227 EagerPseudoCascadeInputs(Some(inputs)) 228 } 229 } 230 231 impl EagerPseudoCascadeInputs { 232 /// Construct inputs from previous cascade results, if any. 233 fn new_from_style(styles: &EagerPseudoStyles) -> Self { 234 EagerPseudoCascadeInputs(styles.as_optional_array().map(|styles| { 235 let mut inputs: [Option<CascadeInputs>; EAGER_PSEUDO_COUNT] = Default::default(); 236 for i in 0..EAGER_PSEUDO_COUNT { 237 inputs[i] = styles[i].as_ref().map(|s| CascadeInputs::new_from_style(s)); 238 } 239 inputs 240 })) 241 } 242 243 /// Returns the list of rules, if they exist. 244 pub fn into_array(self) -> Option<[Option<CascadeInputs>; EAGER_PSEUDO_COUNT]> { 245 self.0 246 } 247 } 248 249 /// The cascade inputs associated with a node, including those for any 250 /// pseudo-elements. 251 /// 252 /// The matching and cascading process stores them in this format temporarily 253 /// within the `CurrentElementInfo`. At the end of the cascade, they are folded 254 /// down into the main `ComputedValues` to reduce memory usage per element while 255 /// still remaining accessible. 256 #[derive(Clone, Debug)] 257 pub struct ElementCascadeInputs { 258 /// The element's cascade inputs. 259 pub primary: CascadeInputs, 260 /// A list of the inputs for the element's eagerly-cascaded pseudo-elements. 261 pub pseudos: EagerPseudoCascadeInputs, 262 } 263 264 impl ElementCascadeInputs { 265 /// Construct inputs from previous cascade results, if any. 266 #[inline] 267 pub fn new_from_element_data(data: &ElementData) -> Self { 268 debug_assert!(data.has_styles()); 269 ElementCascadeInputs { 270 primary: CascadeInputs::new_from_style(data.styles.primary()), 271 pseudos: EagerPseudoCascadeInputs::new_from_style(&data.styles.pseudos), 272 } 273 } 274 } 275 276 /// Statistics gathered during the traversal. We gather statistics on each 277 /// thread and then combine them after the threads join via the Add 278 /// implementation below. 279 #[derive(AddAssign, Clone, Default)] 280 pub struct PerThreadTraversalStatistics { 281 /// The total number of elements traversed. 282 pub elements_traversed: u32, 283 /// The number of elements where has_styles() went from false to true. 284 pub elements_styled: u32, 285 /// The number of elements for which we performed selector matching. 286 pub elements_matched: u32, 287 /// The number of cache hits from the StyleSharingCache. 288 pub styles_shared: u32, 289 /// The number of styles reused via rule node comparison from the 290 /// StyleSharingCache. 291 pub styles_reused: u32, 292 } 293 294 /// Statistics gathered during the traversal plus some information from 295 /// other sources including stylist. 296 #[derive(Default)] 297 pub struct TraversalStatistics { 298 /// Aggregated statistics gathered during the traversal. 299 pub aggregated: PerThreadTraversalStatistics, 300 /// The number of selectors in the stylist. 301 pub selectors: u32, 302 /// The number of revalidation selectors. 303 pub revalidation_selectors: u32, 304 /// The number of state/attr dependencies in the dependency set. 305 pub dependency_selectors: u32, 306 /// The number of declarations in the stylist. 307 pub declarations: u32, 308 /// The number of times the stylist was rebuilt. 309 pub stylist_rebuilds: u32, 310 /// Time spent in the traversal, in milliseconds. 311 pub traversal_time: Duration, 312 /// Whether this was a parallel traversal. 313 pub is_parallel: bool, 314 /// Whether this is a "large" traversal. 315 pub is_large: bool, 316 } 317 318 /// Format the statistics in a way that the performance test harness understands. 319 /// See https://bugzilla.mozilla.org/show_bug.cgi?id=1331856#c2 320 impl fmt::Display for TraversalStatistics { 321 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 322 writeln!(f, "[PERF] perf block start")?; 323 writeln!( 324 f, 325 "[PERF],traversal,{}", 326 if self.is_parallel { 327 "parallel" 328 } else { 329 "sequential" 330 } 331 )?; 332 writeln!( 333 f, 334 "[PERF],elements_traversed,{}", 335 self.aggregated.elements_traversed 336 )?; 337 writeln!( 338 f, 339 "[PERF],elements_styled,{}", 340 self.aggregated.elements_styled 341 )?; 342 writeln!( 343 f, 344 "[PERF],elements_matched,{}", 345 self.aggregated.elements_matched 346 )?; 347 writeln!(f, "[PERF],styles_shared,{}", self.aggregated.styles_shared)?; 348 writeln!(f, "[PERF],styles_reused,{}", self.aggregated.styles_reused)?; 349 writeln!(f, "[PERF],selectors,{}", self.selectors)?; 350 writeln!( 351 f, 352 "[PERF],revalidation_selectors,{}", 353 self.revalidation_selectors 354 )?; 355 writeln!( 356 f, 357 "[PERF],dependency_selectors,{}", 358 self.dependency_selectors 359 )?; 360 writeln!(f, "[PERF],declarations,{}", self.declarations)?; 361 writeln!(f, "[PERF],stylist_rebuilds,{}", self.stylist_rebuilds)?; 362 writeln!( 363 f, 364 "[PERF],traversal_time_ms,{}", 365 self.traversal_time.as_secs_f64() * 1000. 366 )?; 367 writeln!(f, "[PERF] perf block end") 368 } 369 } 370 371 impl TraversalStatistics { 372 /// Generate complete traversal statistics. 373 /// 374 /// The traversal time is computed given the start time in seconds. 375 pub fn new<E, D>( 376 aggregated: PerThreadTraversalStatistics, 377 traversal: &D, 378 parallel: bool, 379 start: Instant, 380 ) -> TraversalStatistics 381 where 382 E: TElement, 383 D: DomTraversal<E>, 384 { 385 let threshold = traversal 386 .shared_context() 387 .options 388 .style_statistics_threshold; 389 let stylist = traversal.shared_context().stylist; 390 let is_large = aggregated.elements_traversed as usize >= threshold; 391 TraversalStatistics { 392 aggregated, 393 selectors: stylist.num_selectors() as u32, 394 revalidation_selectors: stylist.num_revalidation_selectors() as u32, 395 dependency_selectors: stylist.num_invalidations() as u32, 396 declarations: stylist.num_declarations() as u32, 397 stylist_rebuilds: stylist.num_rebuilds() as u32, 398 traversal_time: Instant::now() - start, 399 is_parallel: parallel, 400 is_large, 401 } 402 } 403 } 404 405 #[cfg(feature = "gecko")] 406 bitflags! { 407 /// Represents which tasks are performed in a SequentialTask of 408 /// UpdateAnimations which is a result of normal restyle. 409 pub struct UpdateAnimationsTasks: u8 { 410 /// Update CSS Animations. 411 const CSS_ANIMATIONS = structs::UpdateAnimationsTasks_CSSAnimations; 412 /// Update CSS Transitions. 413 const CSS_TRANSITIONS = structs::UpdateAnimationsTasks_CSSTransitions; 414 /// Update effect properties. 415 const EFFECT_PROPERTIES = structs::UpdateAnimationsTasks_EffectProperties; 416 /// Update animation cacade results for animations running on the compositor. 417 const CASCADE_RESULTS = structs::UpdateAnimationsTasks_CascadeResults; 418 /// Display property was changed from none. 419 /// Script animations keep alive on display:none elements, so we need to trigger 420 /// the second animation restyles for the script animations in the case where 421 /// the display property was changed from 'none' to others. 422 const DISPLAY_CHANGED_FROM_NONE = structs::UpdateAnimationsTasks_DisplayChangedFromNone; 423 /// Update CSS named scroll progress timelines. 424 const SCROLL_TIMELINES = structs::UpdateAnimationsTasks_ScrollTimelines; 425 /// Update CSS named view progress timelines. 426 const VIEW_TIMELINES = structs::UpdateAnimationsTasks_ViewTimelines; 427 } 428 } 429 430 /// A task to be run in sequential mode on the parent (non-worker) thread. This 431 /// is used by the style system to queue up work which is not safe to do during 432 /// the parallel traversal. 433 pub enum SequentialTask<E: TElement> { 434 /// Entry to avoid an unused type parameter error on servo. 435 Unused(SendElement<E>), 436 437 /// Performs one of a number of possible tasks related to updating 438 /// animations based on the |tasks| field. These include updating CSS 439 /// animations/transitions that changed as part of the non-animation style 440 /// traversal, and updating the computed effect properties. 441 #[cfg(feature = "gecko")] 442 UpdateAnimations { 443 /// The target element or pseudo-element. 444 el: SendElement<E>, 445 /// The before-change style for transitions. We use before-change style 446 /// as the initial value of its Keyframe. Required if |tasks| includes 447 /// CSSTransitions. 448 before_change_style: Option<Arc<ComputedValues>>, 449 /// The tasks which are performed in this SequentialTask. 450 tasks: UpdateAnimationsTasks, 451 }, 452 } 453 454 impl<E: TElement> SequentialTask<E> { 455 /// Executes this task. 456 pub fn execute(self) { 457 use self::SequentialTask::*; 458 debug_assert!(thread_state::get().contains(ThreadState::LAYOUT)); 459 match self { 460 Unused(_) => unreachable!(), 461 #[cfg(feature = "gecko")] 462 UpdateAnimations { 463 el, 464 before_change_style, 465 tasks, 466 } => { 467 el.update_animations(before_change_style, tasks); 468 }, 469 } 470 } 471 472 /// Creates a task to update various animation-related state on a given 473 /// (pseudo-)element. 474 #[cfg(feature = "gecko")] 475 pub fn update_animations( 476 el: E, 477 before_change_style: Option<Arc<ComputedValues>>, 478 tasks: UpdateAnimationsTasks, 479 ) -> Self { 480 use self::SequentialTask::*; 481 UpdateAnimations { 482 el: unsafe { SendElement::new(el) }, 483 before_change_style, 484 tasks, 485 } 486 } 487 } 488 489 /// A list of SequentialTasks that get executed on Drop. 490 pub struct SequentialTaskList<E>(Vec<SequentialTask<E>>) 491 where 492 E: TElement; 493 494 impl<E> ops::Deref for SequentialTaskList<E> 495 where 496 E: TElement, 497 { 498 type Target = Vec<SequentialTask<E>>; 499 500 fn deref(&self) -> &Self::Target { 501 &self.0 502 } 503 } 504 505 impl<E> ops::DerefMut for SequentialTaskList<E> 506 where 507 E: TElement, 508 { 509 fn deref_mut(&mut self) -> &mut Self::Target { 510 &mut self.0 511 } 512 } 513 514 impl<E> Drop for SequentialTaskList<E> 515 where 516 E: TElement, 517 { 518 fn drop(&mut self) { 519 debug_assert!(thread_state::get().contains(ThreadState::LAYOUT)); 520 for task in self.0.drain(..) { 521 task.execute() 522 } 523 } 524 } 525 526 /// A helper type for stack limit checking. This assumes that stacks grow 527 /// down, which is true for all non-ancient CPU architectures. 528 pub struct StackLimitChecker { 529 lower_limit: usize, 530 } 531 532 impl StackLimitChecker { 533 /// Create a new limit checker, for this thread, allowing further use 534 /// of up to |stack_size| bytes beyond (below) the current stack pointer. 535 #[inline(never)] 536 pub fn new(stack_size_limit: usize) -> Self { 537 StackLimitChecker { 538 lower_limit: StackLimitChecker::get_sp() - stack_size_limit, 539 } 540 } 541 542 /// Checks whether the previously stored stack limit has now been exceeded. 543 #[inline(never)] 544 pub fn limit_exceeded(&self) -> bool { 545 let curr_sp = StackLimitChecker::get_sp(); 546 547 // Do some sanity-checking to ensure that our invariants hold, even in 548 // the case where we've exceeded the soft limit. 549 // 550 // The correctness of depends on the assumption that no stack wraps 551 // around the end of the address space. 552 if cfg!(debug_assertions) { 553 // Compute the actual bottom of the stack by subtracting our safety 554 // margin from our soft limit. Note that this will be slightly below 555 // the actual bottom of the stack, because there are a few initial 556 // frames on the stack before we do the measurement that computes 557 // the limit. 558 let stack_bottom = self.lower_limit - STACK_SAFETY_MARGIN_KB * 1024; 559 560 // The bottom of the stack should be below the current sp. If it 561 // isn't, that means we've either waited too long to check the limit 562 // and burned through our safety margin (in which case we probably 563 // would have segfaulted by now), or we're using a limit computed for 564 // a different thread. 565 debug_assert!(stack_bottom < curr_sp); 566 567 // Compute the distance between the current sp and the bottom of 568 // the stack, and compare it against the current stack. It should be 569 // no further from us than the total stack size. We allow some slop 570 // to handle the fact that stack_bottom is a bit further than the 571 // bottom of the stack, as discussed above. 572 let distance_to_stack_bottom = curr_sp - stack_bottom; 573 let max_allowable_distance = (STYLE_THREAD_STACK_SIZE_KB + 10) * 1024; 574 debug_assert!(distance_to_stack_bottom <= max_allowable_distance); 575 } 576 577 // The actual bounds check. 578 curr_sp <= self.lower_limit 579 } 580 581 // Technically, rustc can optimize this away, but shouldn't for now. 582 // We should fix this once black_box is stable. 583 #[inline(always)] 584 fn get_sp() -> usize { 585 let mut foo: usize = 42; 586 (&mut foo as *mut usize) as usize 587 } 588 } 589 590 /// A thread-local style context. 591 /// 592 /// This context contains data that needs to be used during restyling, but is 593 /// not required to be unique among worker threads, so we create one per worker 594 /// thread in order to be able to mutate it without locking. 595 pub struct ThreadLocalStyleContext<E: TElement> { 596 /// A cache to share style among siblings. 597 pub sharing_cache: StyleSharingCache<E>, 598 /// A cache from matched properties to elements that match those. 599 pub rule_cache: RuleCache, 600 /// The bloom filter used to fast-reject selector-matching. 601 pub bloom_filter: StyleBloom<E>, 602 /// A set of tasks to be run (on the parent thread) in sequential mode after 603 /// the rest of the styling is complete. This is useful for 604 /// infrequently-needed non-threadsafe operations. 605 /// 606 /// It's important that goes after the style sharing cache and the bloom 607 /// filter, to ensure they're dropped before we execute the tasks, which 608 /// could create another ThreadLocalStyleContext for style computation. 609 pub tasks: SequentialTaskList<E>, 610 /// Statistics about the traversal. 611 pub statistics: PerThreadTraversalStatistics, 612 /// A checker used to ensure that parallel.rs does not recurse indefinitely 613 /// even on arbitrarily deep trees. See Gecko bug 1376883. 614 pub stack_limit_checker: StackLimitChecker, 615 /// Collection of caches (And cache-likes) for speeding up expensive selector matches. 616 pub selector_caches: SelectorCaches, 617 } 618 619 impl<E: TElement> ThreadLocalStyleContext<E> { 620 /// Creates a new `ThreadLocalStyleContext` 621 pub fn new() -> Self { 622 ThreadLocalStyleContext { 623 sharing_cache: StyleSharingCache::new(), 624 rule_cache: RuleCache::new(), 625 bloom_filter: StyleBloom::new(), 626 tasks: SequentialTaskList(Vec::new()), 627 statistics: PerThreadTraversalStatistics::default(), 628 stack_limit_checker: StackLimitChecker::new( 629 (STYLE_THREAD_STACK_SIZE_KB - STACK_SAFETY_MARGIN_KB) * 1024, 630 ), 631 selector_caches: SelectorCaches::default(), 632 } 633 } 634 } 635 636 /// A `StyleContext` is just a simple container for a immutable reference to a 637 /// shared style context, and a mutable reference to a local one. 638 pub struct StyleContext<'a, E: TElement + 'a> { 639 /// The shared style context reference. 640 pub shared: &'a SharedStyleContext<'a>, 641 /// The thread-local style context (mutable) reference. 642 pub thread_local: &'a mut ThreadLocalStyleContext<E>, 643 } 644 645 /// A registered painter 646 #[cfg(feature = "servo")] 647 pub trait RegisteredSpeculativePainter: SpeculativePainter { 648 /// The name it was registered with 649 fn name(&self) -> Atom; 650 /// The properties it was registered with 651 fn properties(&self) -> &FxHashMap<Atom, PropertyId>; 652 } 653 654 /// A set of registered painters 655 #[cfg(feature = "servo")] 656 pub trait RegisteredSpeculativePainters: Sync { 657 /// Look up a speculative painter 658 fn get(&self, name: &Atom) -> Option<&dyn RegisteredSpeculativePainter>; 659 }