stylesheet_set.rs (21525B)
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 //! A centralized set of stylesheets for a document. 6 7 use crate::derives::*; 8 use crate::invalidation::stylesheets::{RuleChangeKind, StylesheetInvalidationSet}; 9 use crate::media_queries::Device; 10 use crate::shared_lock::SharedRwLockReadGuard; 11 use crate::stylesheets::{ 12 CssRule, CssRuleRef, CustomMediaMap, Origin, OriginSet, PerOrigin, StylesheetInDocument, 13 }; 14 use std::mem; 15 16 /// Entry for a StylesheetSet. 17 #[derive(MallocSizeOf)] 18 struct StylesheetSetEntry<S> 19 where 20 S: StylesheetInDocument + PartialEq + 'static, 21 { 22 /// The sheet. 23 sheet: S, 24 25 /// Whether this sheet has been part of at least one flush. 26 committed: bool, 27 } 28 29 impl<S> StylesheetSetEntry<S> 30 where 31 S: StylesheetInDocument + PartialEq + 'static, 32 { 33 fn new(sheet: S) -> Self { 34 Self { 35 sheet, 36 committed: false, 37 } 38 } 39 } 40 41 /// The validity of the data in a given cascade origin. 42 #[derive(Clone, Copy, Debug, Eq, MallocSizeOf, Ord, PartialEq, PartialOrd)] 43 pub enum DataValidity { 44 /// The origin is clean, all the data already there is valid, though we may 45 /// have new sheets at the end. 46 Valid = 0, 47 48 /// The cascade data is invalid, but not the invalidation data (which is 49 /// order-independent), and thus only the cascade data should be inserted. 50 CascadeInvalid = 1, 51 52 /// Everything needs to be rebuilt. 53 FullyInvalid = 2, 54 } 55 56 impl Default for DataValidity { 57 fn default() -> Self { 58 DataValidity::Valid 59 } 60 } 61 62 /// A struct to iterate over the different stylesheets to be flushed. 63 pub struct DocumentStylesheetFlusher<'a, S> 64 where 65 S: StylesheetInDocument + PartialEq + 'static, 66 { 67 collections: &'a mut PerOrigin<SheetCollection<S>>, 68 } 69 70 /// The type of rebuild that we need to do for a given stylesheet. 71 #[derive(Clone, Copy, Debug)] 72 pub enum SheetRebuildKind { 73 /// A full rebuild, of both cascade data and invalidation data. 74 Full, 75 /// A partial rebuild, of only the cascade data. 76 CascadeOnly, 77 } 78 79 impl SheetRebuildKind { 80 /// Whether the stylesheet invalidation data should be rebuilt. 81 pub fn should_rebuild_invalidation(&self) -> bool { 82 matches!(*self, SheetRebuildKind::Full) 83 } 84 } 85 86 impl<'a, S> DocumentStylesheetFlusher<'a, S> 87 where 88 S: StylesheetInDocument + PartialEq + 'static, 89 { 90 /// Returns a flusher for `origin`. 91 pub fn flush_origin(&mut self, origin: Origin) -> SheetCollectionFlusher<'_, S> { 92 self.collections.borrow_mut_for_origin(&origin).flush() 93 } 94 95 /// Returns the list of stylesheets for `origin`. 96 /// 97 /// Only used for UA sheets. 98 pub fn origin_sheets(&self, origin: Origin) -> impl Iterator<Item = &S> { 99 self.collections.borrow_for_origin(&origin).iter() 100 } 101 } 102 103 /// A flusher struct for a given collection, that takes care of returning the 104 /// appropriate stylesheets that need work. 105 pub struct SheetCollectionFlusher<'a, S> 106 where 107 S: StylesheetInDocument + PartialEq + 'static, 108 { 109 // TODO: This can be made an iterator again once 110 // https://github.com/rust-lang/rust/pull/82771 lands on stable. 111 entries: &'a mut [StylesheetSetEntry<S>], 112 validity: DataValidity, 113 dirty: bool, 114 } 115 116 impl<'a, S> SheetCollectionFlusher<'a, S> 117 where 118 S: StylesheetInDocument + PartialEq + 'static, 119 { 120 /// Whether the collection was originally dirty. 121 #[inline] 122 pub fn dirty(&self) -> bool { 123 self.dirty 124 } 125 126 /// What the state of the sheet data is. 127 #[inline] 128 pub fn data_validity(&self) -> DataValidity { 129 self.validity 130 } 131 132 /// Returns an iterator over the remaining list of sheets to consume. 133 pub fn sheets<'b>(&'b self) -> impl Iterator<Item = &'b S> { 134 self.entries.iter().map(|entry| &entry.sheet) 135 } 136 } 137 138 impl<'a, S> SheetCollectionFlusher<'a, S> 139 where 140 S: StylesheetInDocument + PartialEq + 'static, 141 { 142 /// Iterates over all sheets and values that we have to invalidate. 143 /// 144 /// TODO(emilio): This would be nicer as an iterator but we can't do that 145 /// until https://github.com/rust-lang/rust/pull/82771 stabilizes. 146 /// 147 /// Since we don't have a good use-case for partial iteration, this does the 148 /// trick for now. 149 pub fn each(self, mut callback: impl FnMut(usize, &S, SheetRebuildKind) -> bool) { 150 for (index, potential_sheet) in self.entries.iter_mut().enumerate() { 151 let committed = mem::replace(&mut potential_sheet.committed, true); 152 let rebuild_kind = if !committed { 153 // If the sheet was uncommitted, we need to do a full rebuild 154 // anyway. 155 SheetRebuildKind::Full 156 } else { 157 match self.validity { 158 DataValidity::Valid => continue, 159 DataValidity::CascadeInvalid => SheetRebuildKind::CascadeOnly, 160 DataValidity::FullyInvalid => SheetRebuildKind::Full, 161 } 162 }; 163 164 if !callback(index, &potential_sheet.sheet, rebuild_kind) { 165 return; 166 } 167 } 168 } 169 } 170 171 #[derive(MallocSizeOf)] 172 struct SheetCollection<S> 173 where 174 S: StylesheetInDocument + PartialEq + 'static, 175 { 176 /// The actual list of stylesheets. 177 /// 178 /// This is only a list of top-level stylesheets, and as such it doesn't 179 /// include recursive `@import` rules. 180 entries: Vec<StylesheetSetEntry<S>>, 181 182 /// The validity of the data that was already there for a given origin. 183 /// 184 /// Note that an origin may appear on `origins_dirty`, but still have 185 /// `DataValidity::Valid`, if only sheets have been appended into it (in 186 /// which case the existing data is valid, but the origin needs to be 187 /// rebuilt). 188 data_validity: DataValidity, 189 190 /// Whether anything in the collection has changed. Note that this is 191 /// different from `data_validity`, in the sense that after a sheet append, 192 /// the data validity is still `Valid`, but we need to be marked as dirty. 193 dirty: bool, 194 } 195 196 impl<S> Default for SheetCollection<S> 197 where 198 S: StylesheetInDocument + PartialEq + 'static, 199 { 200 fn default() -> Self { 201 Self { 202 entries: vec![], 203 data_validity: DataValidity::Valid, 204 dirty: false, 205 } 206 } 207 } 208 209 impl<S> SheetCollection<S> 210 where 211 S: StylesheetInDocument + PartialEq + 'static, 212 { 213 /// Returns the number of stylesheets in the set. 214 fn len(&self) -> usize { 215 self.entries.len() 216 } 217 218 /// Returns the `index`th stylesheet in the set if present. 219 fn get(&self, index: usize) -> Option<&S> { 220 self.entries.get(index).map(|e| &e.sheet) 221 } 222 223 fn find_sheet_index(&self, sheet: &S) -> Option<usize> { 224 let rev_pos = self 225 .entries 226 .iter() 227 .rev() 228 .position(|entry| entry.sheet == *sheet); 229 rev_pos.map(|i| self.entries.len() - i - 1) 230 } 231 232 fn remove(&mut self, sheet: &S) { 233 let index = self.find_sheet_index(sheet); 234 if cfg!(feature = "gecko") && index.is_none() { 235 // FIXME(emilio): Make Gecko's PresShell::AddUserSheet not suck. 236 return; 237 } 238 let sheet = self.entries.remove(index.unwrap()); 239 // Removing sheets makes us tear down the whole cascade and invalidation 240 // data, but only if the sheet has been involved in at least one flush. 241 // Checking whether the sheet has been committed allows us to avoid 242 // rebuilding the world when sites quickly append and remove a 243 // stylesheet. 244 // 245 // See bug 1434756. 246 if sheet.committed { 247 self.set_data_validity_at_least(DataValidity::FullyInvalid); 248 } else { 249 self.dirty = true; 250 } 251 } 252 253 fn contains(&self, sheet: &S) -> bool { 254 self.entries.iter().any(|e| e.sheet == *sheet) 255 } 256 257 /// Appends a given sheet into the collection. 258 fn append(&mut self, sheet: S) { 259 debug_assert!(!self.contains(&sheet)); 260 self.entries.push(StylesheetSetEntry::new(sheet)); 261 // Appending sheets doesn't alter the validity of the existing data, so 262 // we don't need to change `data_validity` here. 263 // 264 // But we need to be marked as dirty, otherwise we'll never add the new 265 // sheet! 266 self.dirty = true; 267 } 268 269 fn insert_before(&mut self, sheet: S, before_sheet: &S) { 270 debug_assert!(!self.contains(&sheet)); 271 272 let index = self 273 .find_sheet_index(before_sheet) 274 .expect("`before_sheet` stylesheet not found"); 275 276 // Inserting stylesheets somewhere but at the end changes the validity 277 // of the cascade data, but not the invalidation data. 278 self.set_data_validity_at_least(DataValidity::CascadeInvalid); 279 self.entries.insert(index, StylesheetSetEntry::new(sheet)); 280 } 281 282 fn set_data_validity_at_least(&mut self, validity: DataValidity) { 283 use std::cmp; 284 285 debug_assert_ne!(validity, DataValidity::Valid); 286 287 self.dirty = true; 288 self.data_validity = cmp::max(validity, self.data_validity); 289 } 290 291 /// Returns an iterator over the current list of stylesheets. 292 fn iter(&self) -> impl Iterator<Item = &S> { 293 self.entries.iter().map(|e| &e.sheet) 294 } 295 296 /// Returns a mutable iterator over the current list of stylesheets. 297 fn iter_mut(&mut self) -> impl Iterator<Item = &mut S> { 298 self.entries.iter_mut().map(|e| &mut e.sheet) 299 } 300 301 fn flush(&mut self) -> SheetCollectionFlusher<'_, S> { 302 let dirty = mem::replace(&mut self.dirty, false); 303 let validity = mem::replace(&mut self.data_validity, DataValidity::Valid); 304 305 SheetCollectionFlusher { 306 entries: &mut self.entries, 307 dirty, 308 validity, 309 } 310 } 311 } 312 313 /// The set of stylesheets effective for a given document. 314 #[cfg_attr(feature = "servo", derive(MallocSizeOf))] 315 pub struct DocumentStylesheetSet<S> 316 where 317 S: StylesheetInDocument + PartialEq + 'static, 318 { 319 /// The collections of sheets per each origin. 320 collections: PerOrigin<SheetCollection<S>>, 321 322 /// The invalidations for stylesheets added or removed from this document. 323 invalidations: StylesheetInvalidationSet, 324 } 325 326 /// This macro defines methods common to DocumentStylesheetSet and 327 /// AuthorStylesheetSet. 328 /// 329 /// We could simplify the setup moving invalidations to SheetCollection, but 330 /// that would imply not sharing invalidations across origins of the same 331 /// documents, which is slightly annoying. 332 macro_rules! sheet_set_methods { 333 ($set_name:expr) => { 334 fn collect_invalidations_for( 335 &mut self, 336 device: Option<&Device>, 337 custom_media: &CustomMediaMap, 338 sheet: &S, 339 guard: &SharedRwLockReadGuard, 340 ) { 341 if let Some(device) = device { 342 self.invalidations 343 .collect_invalidations_for(device, custom_media, sheet, guard); 344 } 345 } 346 347 /// Appends a new stylesheet to the current set. 348 /// 349 /// No device implies not computing invalidations. 350 pub fn append_stylesheet( 351 &mut self, 352 device: Option<&Device>, 353 custom_media: &CustomMediaMap, 354 sheet: S, 355 guard: &SharedRwLockReadGuard, 356 ) { 357 debug!(concat!($set_name, "::append_stylesheet")); 358 self.collect_invalidations_for(device, custom_media, &sheet, guard); 359 let collection = self.collection_for(&sheet, guard); 360 collection.append(sheet); 361 } 362 363 /// Insert a given stylesheet before another stylesheet in the document. 364 pub fn insert_stylesheet_before( 365 &mut self, 366 device: Option<&Device>, 367 custom_media: &CustomMediaMap, 368 sheet: S, 369 before_sheet: S, 370 guard: &SharedRwLockReadGuard, 371 ) { 372 debug!(concat!($set_name, "::insert_stylesheet_before")); 373 self.collect_invalidations_for(device, custom_media, &sheet, guard); 374 375 let collection = self.collection_for(&sheet, guard); 376 collection.insert_before(sheet, &before_sheet); 377 } 378 379 /// Remove a given stylesheet from the set. 380 pub fn remove_stylesheet( 381 &mut self, 382 device: Option<&Device>, 383 custom_media: &CustomMediaMap, 384 sheet: S, 385 guard: &SharedRwLockReadGuard, 386 ) { 387 debug!(concat!($set_name, "::remove_stylesheet")); 388 self.collect_invalidations_for(device, custom_media, &sheet, guard); 389 390 let collection = self.collection_for(&sheet, guard); 391 collection.remove(&sheet) 392 } 393 394 /// Notify the set that a rule from a given stylesheet has changed 395 /// somehow. 396 pub fn rule_changed( 397 &mut self, 398 device: Option<&Device>, 399 custom_media: &CustomMediaMap, 400 sheet: &S, 401 rule: &CssRule, 402 guard: &SharedRwLockReadGuard, 403 change_kind: RuleChangeKind, 404 ancestors: &[CssRuleRef], 405 ) { 406 if let Some(device) = device { 407 let quirks_mode = device.quirks_mode(); 408 self.invalidations.rule_changed( 409 sheet, 410 rule, 411 guard, 412 device, 413 quirks_mode, 414 custom_media, 415 change_kind, 416 ancestors, 417 ); 418 } 419 420 let validity = match change_kind { 421 // Insertion / Removals need to rebuild both the cascade and 422 // invalidation data. For generic changes this is conservative, 423 // could be optimized on a per-case basis. 424 RuleChangeKind::Generic | RuleChangeKind::Insertion | RuleChangeKind::Removal => { 425 DataValidity::FullyInvalid 426 }, 427 // TODO(emilio): This, in theory, doesn't need to invalidate 428 // style data, if the rule we're modifying is actually in the 429 // CascadeData already. 430 // 431 // But this is actually a bit tricky to prove, because when we 432 // copy-on-write a stylesheet we don't bother doing a rebuild, 433 // so we may still have rules from the original stylesheet 434 // instead of the cloned one that we're modifying. So don't 435 // bother for now and unconditionally rebuild, it's no worse 436 // than what we were already doing anyway. 437 // 438 // Maybe we could record whether we saw a clone in this flush, 439 // and if so do the conservative thing, otherwise just 440 // early-return. 441 RuleChangeKind::PositionTryDeclarations | RuleChangeKind::StyleRuleDeclarations => { 442 DataValidity::FullyInvalid 443 }, 444 }; 445 446 let collection = self.collection_for(&sheet, guard); 447 collection.set_data_validity_at_least(validity); 448 } 449 }; 450 } 451 452 impl<S> DocumentStylesheetSet<S> 453 where 454 S: StylesheetInDocument + PartialEq + 'static, 455 { 456 /// Create a new empty DocumentStylesheetSet. 457 pub fn new() -> Self { 458 Self { 459 collections: Default::default(), 460 invalidations: StylesheetInvalidationSet::new(), 461 } 462 } 463 464 fn collection_for( 465 &mut self, 466 sheet: &S, 467 guard: &SharedRwLockReadGuard, 468 ) -> &mut SheetCollection<S> { 469 let origin = sheet.contents(guard).origin; 470 self.collections.borrow_mut_for_origin(&origin) 471 } 472 473 sheet_set_methods!("DocumentStylesheetSet"); 474 475 /// Returns the number of stylesheets in the set. 476 pub fn len(&self) -> usize { 477 self.collections 478 .iter_origins() 479 .fold(0, |s, (item, _)| s + item.len()) 480 } 481 482 /// Returns the count of stylesheets for a given origin. 483 #[inline] 484 pub fn sheet_count(&self, origin: Origin) -> usize { 485 self.collections.borrow_for_origin(&origin).len() 486 } 487 488 /// Returns the `index`th stylesheet in the set for the given origin. 489 #[inline] 490 pub fn get(&self, origin: Origin, index: usize) -> Option<&S> { 491 self.collections.borrow_for_origin(&origin).get(index) 492 } 493 494 /// Returns whether the given set has changed from the last flush. 495 pub fn has_changed(&self) -> bool { 496 !self.invalidations.is_empty() 497 || self 498 .collections 499 .iter_origins() 500 .any(|(collection, _)| collection.dirty) 501 } 502 503 /// Flush the current set, unmarking it as dirty, and returns a `DocumentStylesheetFlusher` in 504 /// order to rebuild the stylist and the invalidation set. 505 pub fn flush(&mut self) -> (DocumentStylesheetFlusher<'_, S>, StylesheetInvalidationSet) { 506 debug!("DocumentStylesheetSet::flush"); 507 ( 508 DocumentStylesheetFlusher { 509 collections: &mut self.collections, 510 }, 511 std::mem::take(&mut self.invalidations), 512 ) 513 } 514 515 /// Flush stylesheets, but without running any of the invalidation passes. 516 #[cfg(feature = "servo")] 517 pub fn flush_without_invalidation(&mut self) -> OriginSet { 518 debug!("DocumentStylesheetSet::flush_without_invalidation"); 519 520 let mut origins = OriginSet::empty(); 521 self.invalidations.clear(); 522 523 for (collection, origin) in self.collections.iter_mut_origins() { 524 if collection.flush().dirty() { 525 origins |= origin; 526 } 527 } 528 529 origins 530 } 531 532 /// Return an iterator over the flattened view of all the stylesheets. 533 pub fn iter(&self) -> impl Iterator<Item = (&S, Origin)> { 534 self.collections 535 .iter_origins() 536 .flat_map(|(c, o)| c.iter().map(move |s| (s, o))) 537 } 538 539 /// Return an iterator over the flattened view of all the stylesheets, mutably. 540 pub fn iter_mut(&mut self) -> impl Iterator<Item = (&mut S, Origin)> { 541 self.collections 542 .iter_mut_origins() 543 .flat_map(|(c, o)| c.iter_mut().map(move |s| (s, o))) 544 } 545 546 /// Mark the stylesheets for the specified origin as dirty, because 547 /// something external may have invalidated it. 548 pub fn force_dirty(&mut self, origins: OriginSet) { 549 self.invalidations.invalidate_fully(); 550 for origin in origins.iter_origins() { 551 // We don't know what happened, assume the worse. 552 self.collections 553 .borrow_mut_for_origin(&origin) 554 .set_data_validity_at_least(DataValidity::FullyInvalid); 555 } 556 } 557 } 558 559 /// The set of stylesheets effective for a given Shadow Root. 560 #[derive(MallocSizeOf)] 561 pub struct AuthorStylesheetSet<S> 562 where 563 S: StylesheetInDocument + PartialEq + 'static, 564 { 565 /// The actual style sheets. 566 collection: SheetCollection<S>, 567 /// The set of invalidations scheduled for this collection. 568 invalidations: StylesheetInvalidationSet, 569 } 570 571 /// A struct to flush an author style sheet collection. 572 pub struct AuthorStylesheetFlusher<'a, S> 573 where 574 S: StylesheetInDocument + PartialEq + 'static, 575 { 576 /// The actual flusher for the collection. 577 pub sheets: SheetCollectionFlusher<'a, S>, 578 } 579 580 impl<S> AuthorStylesheetSet<S> 581 where 582 S: StylesheetInDocument + PartialEq + 'static, 583 { 584 /// Create a new empty AuthorStylesheetSet. 585 #[inline] 586 pub fn new() -> Self { 587 Self { 588 collection: Default::default(), 589 invalidations: StylesheetInvalidationSet::new(), 590 } 591 } 592 593 /// Whether anything has changed since the last time this was flushed. 594 pub fn dirty(&self) -> bool { 595 self.collection.dirty 596 } 597 598 /// Whether the collection is empty. 599 pub fn is_empty(&self) -> bool { 600 self.collection.len() == 0 601 } 602 603 /// Returns the `index`th stylesheet in the collection of author styles if present. 604 pub fn get(&self, index: usize) -> Option<&S> { 605 self.collection.get(index) 606 } 607 608 /// Returns the number of author stylesheets. 609 pub fn len(&self) -> usize { 610 self.collection.len() 611 } 612 613 fn collection_for(&mut self, _: &S, _: &SharedRwLockReadGuard) -> &mut SheetCollection<S> { 614 &mut self.collection 615 } 616 617 sheet_set_methods!("AuthorStylesheetSet"); 618 619 /// Iterate over the list of stylesheets. 620 pub fn iter(&self) -> impl Iterator<Item = &S> { 621 self.collection.iter() 622 } 623 624 /// Returns a mutable iterator over the current list of stylesheets. 625 pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut S> { 626 self.collection.iter_mut() 627 } 628 629 /// Mark the sheet set dirty, as appropriate. 630 pub fn force_dirty(&mut self) { 631 self.invalidations.invalidate_fully(); 632 self.collection 633 .set_data_validity_at_least(DataValidity::FullyInvalid); 634 } 635 636 /// Flush the stylesheets for this author set. 637 pub fn flush(&mut self) -> (AuthorStylesheetFlusher<'_, S>, StylesheetInvalidationSet) { 638 ( 639 AuthorStylesheetFlusher { 640 sheets: self.collection.flush(), 641 }, 642 std::mem::take(&mut self.invalidations), 643 ) 644 } 645 }