Source code

Revision control

Other Tools

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