data.rs (20795B)
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 //! Per-node data used in style calculation. 6 7 use crate::computed_value_flags::ComputedValueFlags; 8 use crate::context::{SharedStyleContext, StackLimitChecker}; 9 use crate::dom::TElement; 10 use crate::invalidation::element::invalidator::InvalidationResult; 11 use crate::invalidation::element::restyle_hints::RestyleHint; 12 use crate::properties::ComputedValues; 13 use crate::selector_parser::{PseudoElement, RestyleDamage, EAGER_PSEUDO_COUNT}; 14 use crate::style_resolver::{PrimaryStyle, ResolvedElementStyles, ResolvedStyle}; 15 #[cfg(feature = "gecko")] 16 use malloc_size_of::MallocSizeOfOps; 17 use selectors::matching::SelectorCaches; 18 use servo_arc::Arc; 19 use std::fmt; 20 use std::mem; 21 use std::ops::{Deref, DerefMut}; 22 23 bitflags! { 24 /// Various flags stored on ElementData. 25 #[derive(Debug, Default)] 26 pub struct ElementDataFlags: u8 { 27 /// Whether the styles changed for this restyle. 28 const WAS_RESTYLED = 1 << 0; 29 /// Whether the last traversal of this element did not do 30 /// any style computation. This is not true during the initial 31 /// styling pass, nor is it true when we restyle (in which case 32 /// WAS_RESTYLED is set). 33 /// 34 /// This bit always corresponds to the last time the element was 35 /// traversed, so each traversal simply updates it with the appropriate 36 /// value. 37 const TRAVERSED_WITHOUT_STYLING = 1 << 1; 38 39 /// Whether the primary style of this element data was reused from 40 /// another element via a rule node comparison. This allows us to 41 /// differentiate between elements that shared styles because they met 42 /// all the criteria of the style sharing cache, compared to elements 43 /// that reused style structs via rule node identity. 44 /// 45 /// The former gives us stronger transitive guarantees that allows us to 46 /// apply the style sharing cache to cousins. 47 const PRIMARY_STYLE_REUSED_VIA_RULE_NODE = 1 << 2; 48 49 /// Whether this element may have matched rules inside @starting-style. 50 const MAY_HAVE_STARTING_STYLE = 1 << 3; 51 } 52 } 53 54 /// A lazily-allocated list of styles for eagerly-cascaded pseudo-elements. 55 /// 56 /// We use an Arc so that sharing these styles via the style sharing cache does 57 /// not require duplicate allocations. We leverage the copy-on-write semantics of 58 /// Arc::make_mut(), which is free (i.e. does not require atomic RMU operations) 59 /// in servo_arc. 60 #[derive(Clone, Debug, Default)] 61 pub struct EagerPseudoStyles(Option<Arc<EagerPseudoArray>>); 62 63 #[derive(Default)] 64 struct EagerPseudoArray(EagerPseudoArrayInner); 65 type EagerPseudoArrayInner = [Option<Arc<ComputedValues>>; EAGER_PSEUDO_COUNT]; 66 67 impl Deref for EagerPseudoArray { 68 type Target = EagerPseudoArrayInner; 69 fn deref(&self) -> &Self::Target { 70 &self.0 71 } 72 } 73 74 impl DerefMut for EagerPseudoArray { 75 fn deref_mut(&mut self) -> &mut Self::Target { 76 &mut self.0 77 } 78 } 79 80 // Manually implement `Clone` here because the derived impl of `Clone` for 81 // array types assumes the value inside is `Copy`. 82 impl Clone for EagerPseudoArray { 83 fn clone(&self) -> Self { 84 let mut clone = Self::default(); 85 for i in 0..EAGER_PSEUDO_COUNT { 86 clone[i] = self.0[i].clone(); 87 } 88 clone 89 } 90 } 91 92 // Override Debug to print which pseudos we have, and substitute the rule node 93 // for the much-more-verbose ComputedValues stringification. 94 impl fmt::Debug for EagerPseudoArray { 95 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 96 write!(f, "EagerPseudoArray {{ ")?; 97 for i in 0..EAGER_PSEUDO_COUNT { 98 if let Some(ref values) = self[i] { 99 write!( 100 f, 101 "{:?}: {:?}, ", 102 PseudoElement::from_eager_index(i), 103 &values.rules 104 )?; 105 } 106 } 107 write!(f, "}}") 108 } 109 } 110 111 // Can't use [None; EAGER_PSEUDO_COUNT] here because it complains 112 // about Copy not being implemented for our Arc type. 113 #[cfg(feature = "gecko")] 114 const EMPTY_PSEUDO_ARRAY: &'static EagerPseudoArrayInner = &[None, None, None, None]; 115 #[cfg(feature = "servo")] 116 const EMPTY_PSEUDO_ARRAY: &'static EagerPseudoArrayInner = &[None, None, None]; 117 118 impl EagerPseudoStyles { 119 /// Returns whether there are any pseudo styles. 120 pub fn is_empty(&self) -> bool { 121 self.0.is_none() 122 } 123 124 /// Grabs a reference to the list of styles, if they exist. 125 pub fn as_optional_array(&self) -> Option<&EagerPseudoArrayInner> { 126 match self.0 { 127 None => None, 128 Some(ref x) => Some(&x.0), 129 } 130 } 131 132 /// Grabs a reference to the list of styles or a list of None if 133 /// there are no styles to be had. 134 pub fn as_array(&self) -> &EagerPseudoArrayInner { 135 self.as_optional_array().unwrap_or(EMPTY_PSEUDO_ARRAY) 136 } 137 138 /// Returns a reference to the style for a given eager pseudo, if it exists. 139 pub fn get(&self, pseudo: &PseudoElement) -> Option<&Arc<ComputedValues>> { 140 debug_assert!(pseudo.is_eager()); 141 self.0 142 .as_ref() 143 .and_then(|p| p[pseudo.eager_index()].as_ref()) 144 } 145 146 /// Sets the style for the eager pseudo. 147 pub fn set(&mut self, pseudo: &PseudoElement, value: Arc<ComputedValues>) { 148 if self.0.is_none() { 149 self.0 = Some(Arc::new(Default::default())); 150 } 151 let arr = Arc::make_mut(self.0.as_mut().unwrap()); 152 arr[pseudo.eager_index()] = Some(value); 153 } 154 } 155 156 /// The styles associated with a node, including the styles for any 157 /// pseudo-elements. 158 #[derive(Clone, Default)] 159 pub struct ElementStyles { 160 /// The element's style. 161 pub primary: Option<Arc<ComputedValues>>, 162 /// A list of the styles for the element's eagerly-cascaded pseudo-elements. 163 pub pseudos: EagerPseudoStyles, 164 } 165 166 // There's one of these per rendered elements so it better be small. 167 size_of_test!(ElementStyles, 16); 168 169 /// Information on how this element uses viewport units. 170 #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] 171 pub enum ViewportUnitUsage { 172 /// No viewport units are used. 173 None = 0, 174 /// There are viewport units used from regular style rules (which means we 175 /// should re-cascade). 176 FromDeclaration, 177 /// There are viewport units used from container queries (which means we 178 /// need to re-selector-match). 179 FromQuery, 180 } 181 182 impl ElementStyles { 183 /// Returns the primary style. 184 pub fn get_primary(&self) -> Option<&Arc<ComputedValues>> { 185 self.primary.as_ref() 186 } 187 188 /// Returns the primary style. Panic if no style available. 189 pub fn primary(&self) -> &Arc<ComputedValues> { 190 self.primary.as_ref().unwrap() 191 } 192 193 /// Whether this element `display` value is `none`. 194 pub fn is_display_none(&self) -> bool { 195 self.primary().get_box().clone_display().is_none() 196 } 197 198 /// Whether this element uses viewport units. 199 pub fn viewport_unit_usage(&self) -> ViewportUnitUsage { 200 fn usage_from_flags(flags: ComputedValueFlags) -> ViewportUnitUsage { 201 if flags.intersects(ComputedValueFlags::USES_VIEWPORT_UNITS_ON_CONTAINER_QUERIES) { 202 return ViewportUnitUsage::FromQuery; 203 } 204 if flags.intersects(ComputedValueFlags::USES_VIEWPORT_UNITS) { 205 return ViewportUnitUsage::FromDeclaration; 206 } 207 ViewportUnitUsage::None 208 } 209 210 let mut usage = usage_from_flags(self.primary().flags); 211 for pseudo_style in self.pseudos.as_array() { 212 if let Some(ref pseudo_style) = pseudo_style { 213 usage = std::cmp::max(usage, usage_from_flags(pseudo_style.flags)); 214 } 215 } 216 217 usage 218 } 219 220 #[cfg(feature = "gecko")] 221 fn size_of_excluding_cvs(&self, _ops: &mut MallocSizeOfOps) -> usize { 222 // As the method name suggests, we don't measures the ComputedValues 223 // here, because they are measured on the C++ side. 224 225 // XXX: measure the EagerPseudoArray itself, but not the ComputedValues 226 // within it. 227 228 0 229 } 230 } 231 232 // We manually implement Debug for ElementStyles so that we can avoid the 233 // verbose stringification of every property in the ComputedValues. We 234 // substitute the rule node instead. 235 impl fmt::Debug for ElementStyles { 236 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 237 write!( 238 f, 239 "ElementStyles {{ primary: {:?}, pseudos: {:?} }}", 240 self.primary.as_ref().map(|x| &x.rules), 241 self.pseudos 242 ) 243 } 244 } 245 246 /// Style system data associated with an Element. 247 /// 248 /// In Gecko, this hangs directly off the Element. Servo, this is embedded 249 /// inside of layout data, which itself hangs directly off the Element. In 250 /// both cases, it is wrapped inside an AtomicRefCell to ensure thread safety. 251 #[derive(Debug, Default)] 252 pub struct ElementData { 253 /// The styles for the element and its pseudo-elements. 254 pub styles: ElementStyles, 255 256 /// The restyle damage, indicating what kind of layout changes are required 257 /// afte restyling. 258 pub damage: RestyleDamage, 259 260 /// The restyle hint, which indicates whether selectors need to be rematched 261 /// for this element, its children, and its descendants. 262 pub hint: RestyleHint, 263 264 /// Flags. 265 pub flags: ElementDataFlags, 266 } 267 268 // There's one of these per rendered elements so it better be small. 269 size_of_test!(ElementData, 24); 270 271 /// The kind of restyle that a single element should do. 272 #[derive(Debug)] 273 pub enum RestyleKind { 274 /// We need to run selector matching plus re-cascade, that is, a full 275 /// restyle. 276 MatchAndCascade, 277 /// We need to recascade with some replacement rule, such as the style 278 /// attribute, or animation rules. 279 CascadeWithReplacements(RestyleHint), 280 /// We only need to recascade, for example, because only inherited 281 /// properties in the parent changed. 282 CascadeOnly, 283 } 284 285 impl ElementData { 286 /// Invalidates style for this element, its descendants, and later siblings, 287 /// based on the snapshot of the element that we took when attributes or 288 /// state changed. 289 pub fn invalidate_style_if_needed<'a, E: TElement>( 290 &mut self, 291 element: E, 292 shared_context: &SharedStyleContext, 293 stack_limit_checker: Option<&StackLimitChecker>, 294 selector_caches: &'a mut SelectorCaches, 295 ) -> InvalidationResult { 296 // In animation-only restyle we shouldn't touch snapshot at all. 297 if shared_context.traversal_flags.for_animation_only() { 298 return InvalidationResult::empty(); 299 } 300 301 use crate::invalidation::element::invalidator::TreeStyleInvalidator; 302 use crate::invalidation::element::state_and_attributes::StateAndAttrInvalidationProcessor; 303 304 debug!( 305 "invalidate_style_if_needed: {:?}, flags: {:?}, has_snapshot: {}, \ 306 handled_snapshot: {}, pseudo: {:?}", 307 element, 308 shared_context.traversal_flags, 309 element.has_snapshot(), 310 element.handled_snapshot(), 311 element.implemented_pseudo_element() 312 ); 313 314 if !element.has_snapshot() || element.handled_snapshot() { 315 return InvalidationResult::empty(); 316 } 317 318 let mut processor = 319 StateAndAttrInvalidationProcessor::new(shared_context, element, self, selector_caches); 320 321 let invalidator = TreeStyleInvalidator::new(element, stack_limit_checker, &mut processor); 322 323 let result = invalidator.invalidate(); 324 325 unsafe { element.set_handled_snapshot() } 326 debug_assert!(element.handled_snapshot()); 327 328 result 329 } 330 331 /// Returns true if this element has styles. 332 #[inline] 333 pub fn has_styles(&self) -> bool { 334 self.styles.primary.is_some() 335 } 336 337 /// Returns this element's styles as resolved styles to use for sharing. 338 pub fn share_styles(&self) -> ResolvedElementStyles { 339 ResolvedElementStyles { 340 primary: self.share_primary_style(), 341 pseudos: self.styles.pseudos.clone(), 342 } 343 } 344 345 /// Returns this element's primary style as a resolved style to use for sharing. 346 pub fn share_primary_style(&self) -> PrimaryStyle { 347 let reused_via_rule_node = self 348 .flags 349 .contains(ElementDataFlags::PRIMARY_STYLE_REUSED_VIA_RULE_NODE); 350 let may_have_starting_style = self 351 .flags 352 .contains(ElementDataFlags::MAY_HAVE_STARTING_STYLE); 353 354 PrimaryStyle { 355 style: ResolvedStyle(self.styles.primary().clone()), 356 reused_via_rule_node, 357 may_have_starting_style, 358 } 359 } 360 361 /// Sets a new set of styles, returning the old ones. 362 pub fn set_styles(&mut self, new_styles: ResolvedElementStyles) -> ElementStyles { 363 self.flags.set( 364 ElementDataFlags::PRIMARY_STYLE_REUSED_VIA_RULE_NODE, 365 new_styles.primary.reused_via_rule_node, 366 ); 367 self.flags.set( 368 ElementDataFlags::MAY_HAVE_STARTING_STYLE, 369 new_styles.primary.may_have_starting_style, 370 ); 371 372 mem::replace(&mut self.styles, new_styles.into()) 373 } 374 375 /// Returns the kind of restyling that we're going to need to do on this 376 /// element, based of the stored restyle hint. 377 pub fn restyle_kind(&self, shared_context: &SharedStyleContext) -> Option<RestyleKind> { 378 let style = match self.styles.primary { 379 Some(ref s) => s, 380 None => return Some(RestyleKind::MatchAndCascade), 381 }; 382 383 if shared_context.traversal_flags.for_animation_only() { 384 return self.restyle_kind_for_animation(shared_context); 385 } 386 387 let hint = self.hint; 388 if hint.is_empty() { 389 return None; 390 } 391 392 let needs_to_match_self = hint.intersects(RestyleHint::RESTYLE_SELF) 393 || (hint.intersects(RestyleHint::RESTYLE_SELF_IF_PSEUDO) && style.is_pseudo_style()); 394 if needs_to_match_self { 395 return Some(RestyleKind::MatchAndCascade); 396 } 397 398 if hint.has_replacements() { 399 debug_assert!( 400 !hint.has_animation_hint(), 401 "Animation only restyle hint should have already processed" 402 ); 403 return Some(RestyleKind::CascadeWithReplacements( 404 hint & RestyleHint::replacements(), 405 )); 406 } 407 408 let needs_to_recascade_self = hint.intersects(RestyleHint::RECASCADE_SELF) 409 || (hint.intersects(RestyleHint::RECASCADE_SELF_IF_INHERIT_RESET_STYLE) 410 && style 411 .flags 412 .contains(ComputedValueFlags::INHERITS_RESET_STYLE)); 413 if needs_to_recascade_self { 414 return Some(RestyleKind::CascadeOnly); 415 } 416 417 None 418 } 419 420 /// Returns the kind of restyling for animation-only restyle. 421 fn restyle_kind_for_animation( 422 &self, 423 shared_context: &SharedStyleContext, 424 ) -> Option<RestyleKind> { 425 debug_assert!(shared_context.traversal_flags.for_animation_only()); 426 debug_assert!(self.has_styles()); 427 428 // FIXME: We should ideally restyle here, but it is a hack to work around our weird 429 // animation-only traversal stuff: If we're display: none and the rules we could 430 // match could change, we consider our style up-to-date. This is because re-cascading with 431 // and old style doesn't guarantee returning the correct animation style (that's 432 // bug 1393323). So if our display changed, and it changed from display: none, we would 433 // incorrectly forget about it and wouldn't be able to correctly style our descendants 434 // later. 435 // XXX Figure out if this still makes sense. 436 let hint = self.hint; 437 if self.styles.is_display_none() && hint.intersects(RestyleHint::RESTYLE_SELF) { 438 return None; 439 } 440 441 let style = self.styles.primary(); 442 // Return either CascadeWithReplacements or CascadeOnly in case of animation-only restyle. 443 // I.e. animation-only restyle never does selector matching. 444 if hint.has_animation_hint() { 445 return Some(RestyleKind::CascadeWithReplacements( 446 hint & RestyleHint::for_animations(), 447 )); 448 } 449 450 let needs_to_recascade_self = hint.intersects(RestyleHint::RECASCADE_SELF) 451 || (hint.intersects(RestyleHint::RECASCADE_SELF_IF_INHERIT_RESET_STYLE) 452 && style 453 .flags 454 .contains(ComputedValueFlags::INHERITS_RESET_STYLE)); 455 if needs_to_recascade_self { 456 return Some(RestyleKind::CascadeOnly); 457 } 458 return None; 459 } 460 461 /// Drops any restyle state from the element. 462 /// 463 /// FIXME(bholley): The only caller of this should probably just assert that the hint is empty 464 /// and call clear_flags_and_damage(). 465 #[inline] 466 pub fn clear_restyle_state(&mut self) { 467 self.hint = RestyleHint::empty(); 468 self.clear_restyle_flags_and_damage(); 469 } 470 471 /// Drops restyle flags and damage from the element. 472 #[inline] 473 pub fn clear_restyle_flags_and_damage(&mut self) { 474 self.damage = RestyleDamage::empty(); 475 self.flags.remove(ElementDataFlags::WAS_RESTYLED); 476 } 477 478 /// Mark this element as restyled, which is useful to know whether we need 479 /// to do a post-traversal. 480 pub fn set_restyled(&mut self) { 481 self.flags.insert(ElementDataFlags::WAS_RESTYLED); 482 self.flags 483 .remove(ElementDataFlags::TRAVERSED_WITHOUT_STYLING); 484 } 485 486 /// Returns true if this element was restyled. 487 #[inline] 488 pub fn is_restyle(&self) -> bool { 489 self.flags.contains(ElementDataFlags::WAS_RESTYLED) 490 } 491 492 /// Mark that we traversed this element without computing any style for it. 493 pub fn set_traversed_without_styling(&mut self) { 494 self.flags 495 .insert(ElementDataFlags::TRAVERSED_WITHOUT_STYLING); 496 } 497 498 /// Returns whether this element has been part of a restyle. 499 #[inline] 500 pub fn contains_restyle_data(&self) -> bool { 501 self.is_restyle() || !self.hint.is_empty() || !self.damage.is_empty() 502 } 503 504 /// Returns whether it is safe to perform cousin sharing based on the ComputedValues 505 /// identity of the primary style in this ElementData. There are a few subtle things 506 /// to check. 507 /// 508 /// First, if a parent element was already styled and we traversed past it without 509 /// restyling it, that may be because our clever invalidation logic was able to prove 510 /// that the styles of that element would remain unchanged despite changes to the id 511 /// or class attributes. However, style sharing relies on the strong guarantee that all 512 /// the classes and ids up the respective parent chains are identical. As such, if we 513 /// skipped styling for one (or both) of the parents on this traversal, we can't share 514 /// styles across cousins. Note that this is a somewhat conservative check. We could 515 /// tighten it by having the invalidation logic explicitly flag elements for which it 516 /// ellided styling. 517 /// 518 /// Second, we want to only consider elements whose ComputedValues match due to a hit 519 /// in the style sharing cache, rather than due to the rule-node-based reuse that 520 /// happens later in the styling pipeline. The former gives us the stronger guarantees 521 /// we need for style sharing, the latter does not. 522 pub fn safe_for_cousin_sharing(&self) -> bool { 523 if self.flags.intersects( 524 ElementDataFlags::TRAVERSED_WITHOUT_STYLING 525 | ElementDataFlags::PRIMARY_STYLE_REUSED_VIA_RULE_NODE, 526 ) { 527 return false; 528 } 529 if !self 530 .styles 531 .primary() 532 .get_box() 533 .clone_container_type() 534 .is_normal() 535 { 536 return false; 537 } 538 true 539 } 540 541 /// Measures memory usage. 542 #[cfg(feature = "gecko")] 543 pub fn size_of_excluding_cvs(&self, ops: &mut MallocSizeOfOps) -> usize { 544 let n = self.styles.size_of_excluding_cvs(ops); 545 546 // We may measure more fields in the future if DMD says it's worth it. 547 548 n 549 } 550 551 /// Returns true if this element data may need to compute the starting style for CSS 552 /// transitions. 553 #[inline] 554 pub fn may_have_starting_style(&self) -> bool { 555 self.flags 556 .contains(ElementDataFlags::MAY_HAVE_STARTING_STYLE) 557 } 558 }