style/stylesheets/
scope_rule.rs

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 [`@scope`][scope] rule.
6//!
7//! [scope]: https://drafts.csswg.org/css-cascade-6/#scoped-styles
8
9use crate::applicable_declarations::ScopeProximity;
10use crate::dom::TElement;
11use crate::parser::ParserContext;
12use crate::selector_parser::{SelectorImpl, SelectorParser};
13use crate::shared_lock::{
14    DeepCloneWithLock, Locked, SharedRwLock, SharedRwLockReadGuard, ToCssWithGuard,
15};
16use crate::simple_buckets_map::SimpleBucketsMap;
17use crate::str::CssStringWriter;
18use crate::stylesheets::CssRules;
19use cssparser::{Parser, SourceLocation, ToCss};
20#[cfg(feature = "gecko")]
21use malloc_size_of::{
22    MallocSizeOfOps, MallocUnconditionalShallowSizeOf, MallocUnconditionalSizeOf,
23};
24use selectors::context::{MatchingContext, QuirksMode};
25use selectors::matching::matches_selector;
26use selectors::parser::{Component, ParseRelative, Selector, SelectorList};
27use selectors::OpaqueElement;
28use servo_arc::Arc;
29use std::fmt::{self, Write};
30use style_traits::{CssWriter, ParseError};
31
32/// A scoped rule.
33#[derive(Debug, ToShmem)]
34pub struct ScopeRule {
35    /// Bounds at which this rule applies.
36    pub bounds: ScopeBounds,
37    /// The nested rules inside the block.
38    pub rules: Arc<Locked<CssRules>>,
39    /// The source position where this rule was found.
40    pub source_location: SourceLocation,
41}
42
43impl DeepCloneWithLock for ScopeRule {
44    fn deep_clone_with_lock(&self, lock: &SharedRwLock, guard: &SharedRwLockReadGuard) -> Self {
45        let rules = self.rules.read_with(guard);
46        Self {
47            bounds: self.bounds.clone(),
48            rules: Arc::new(lock.wrap(rules.deep_clone_with_lock(lock, guard))),
49            source_location: self.source_location.clone(),
50        }
51    }
52}
53
54impl ToCssWithGuard for ScopeRule {
55    fn to_css(&self, guard: &SharedRwLockReadGuard, dest: &mut CssStringWriter) -> fmt::Result {
56        dest.write_str("@scope")?;
57        {
58            let mut writer = CssWriter::new(dest);
59            if let Some(start) = self.bounds.start.as_ref() {
60                writer.write_str(" (")?;
61                start.to_css(&mut writer)?;
62                writer.write_char(')')?;
63            }
64            if let Some(end) = self.bounds.end.as_ref() {
65                writer.write_str(" to (")?;
66                end.to_css(&mut writer)?;
67                writer.write_char(')')?;
68            }
69        }
70        self.rules.read_with(guard).to_css_block(guard, dest)
71    }
72}
73
74impl ScopeRule {
75    /// Measure heap usage.
76    #[cfg(feature = "gecko")]
77    pub fn size_of(&self, guard: &SharedRwLockReadGuard, ops: &mut MallocSizeOfOps) -> usize {
78        self.rules.unconditional_shallow_size_of(ops)
79            + self.rules.read_with(guard).size_of(guard, ops)
80            + self.bounds.size_of(ops)
81    }
82}
83
84/// Bounds of the scope.
85#[derive(Debug, Clone, ToShmem)]
86pub struct ScopeBounds {
87    /// Start of the scope.
88    pub start: Option<SelectorList<SelectorImpl>>,
89    /// End of the scope.
90    pub end: Option<SelectorList<SelectorImpl>>,
91}
92
93impl ScopeBounds {
94    #[cfg(feature = "gecko")]
95    fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize {
96        fn bound_size_of(
97            bound: &Option<SelectorList<SelectorImpl>>,
98            ops: &mut MallocSizeOfOps,
99        ) -> usize {
100            bound
101                .as_ref()
102                .map(|list| list.unconditional_size_of(ops))
103                .unwrap_or(0)
104        }
105        bound_size_of(&self.start, ops) + bound_size_of(&self.end, ops)
106    }
107}
108
109fn parse_scope<'a>(
110    context: &ParserContext,
111    input: &mut Parser<'a, '_>,
112    parse_relative: ParseRelative,
113    for_end: bool,
114) -> Result<Option<SelectorList<SelectorImpl>>, ParseError<'a>> {
115    input.try_parse(|input| {
116        if for_end {
117            // scope-end not existing is valid.
118            if input.try_parse(|i| i.expect_ident_matching("to")).is_err() {
119                return Ok(None);
120            }
121        }
122        let parens = input.try_parse(|i| i.expect_parenthesis_block());
123        if for_end {
124            // `@scope to {}` is NOT valid.
125            parens?;
126        } else if parens.is_err() {
127            // `@scope {}` is valid.
128            return Ok(None);
129        }
130        input.parse_nested_block(|input| {
131            if input.is_exhausted() {
132                // `@scope () {}` is valid.
133                return Ok(None);
134            }
135            let selector_parser = SelectorParser {
136                stylesheet_origin: context.stylesheet_origin,
137                namespaces: &context.namespaces,
138                url_data: context.url_data,
139                for_supports_rule: false,
140            };
141            let parse_relative = if for_end {
142                ParseRelative::ForScope
143            } else {
144                parse_relative
145            };
146            Ok(Some(SelectorList::parse_disallow_pseudo(
147                &selector_parser,
148                input,
149                parse_relative,
150            )?))
151        })
152    })
153}
154
155impl ScopeBounds {
156    /// Parse a container condition.
157    pub fn parse<'a>(
158        context: &ParserContext,
159        input: &mut Parser<'a, '_>,
160        parse_relative: ParseRelative,
161    ) -> Result<Self, ParseError<'a>> {
162        let start = parse_scope(context, input, parse_relative, false)?;
163        let end = parse_scope(context, input, parse_relative, true)?;
164        Ok(Self { start, end })
165    }
166}
167
168/// Types of implicit scope root.
169#[derive(Debug, Copy, Clone, MallocSizeOf)]
170pub enum ImplicitScopeRoot {
171    /// This implicit scope root is in the light tree.
172    InLightTree(OpaqueElement),
173    /// This implicit scope root is the document element, regardless of which (light|shadow) tree
174    /// the element being matched is. This is the case for e.g. if you specified an implicit scope
175    /// within a user stylesheet.
176    DocumentElement,
177    /// The implicit scope root is in a constructed stylesheet - the scope root the element
178    /// under consideration's shadow root (If one exists).
179    Constructed,
180    /// This implicit scope root is in the shadow tree.
181    InShadowTree(OpaqueElement),
182    /// This implicit scope root is the shadow host of the stylesheet-containing shadow tree.
183    ShadowHost(OpaqueElement),
184}
185
186impl ImplicitScopeRoot {
187    /// Return true if this matches the shadow host.
188    pub fn matches_shadow_host(&self) -> bool {
189        match self {
190            Self::InLightTree(..) | Self::InShadowTree(..) | Self::DocumentElement => false,
191            Self::ShadowHost(..) | Self::Constructed => true,
192        }
193    }
194
195    /// Return the implicit scope root element.
196    pub fn element(&self, current_host: Option<OpaqueElement>) -> ImplicitScopeTarget {
197        match self {
198            Self::InLightTree(e) | Self::InShadowTree(e) | Self::ShadowHost(e) => {
199                ImplicitScopeTarget::Element(*e)
200            },
201            Self::Constructed | Self::DocumentElement => {
202                if matches!(self, Self::Constructed) {
203                    if let Some(host) = current_host {
204                        return ImplicitScopeTarget::Element(host);
205                    }
206                }
207                ImplicitScopeTarget::DocumentElement
208            },
209        }
210    }
211}
212
213/// Target of this implicit scope.
214pub enum ImplicitScopeTarget {
215    /// Target matches only the specified element.
216    Element(OpaqueElement),
217    /// Implicit scope whose target is the document element.
218    DocumentElement,
219}
220
221impl ImplicitScopeTarget {
222    /// Check if this element is the implicit scope.
223    fn check<E: TElement>(&self, element: E) -> bool {
224        match self {
225            Self::Element(e) => element.opaque() == *e,
226            Self::DocumentElement => element.is_root(),
227        }
228    }
229}
230
231/// Target of this scope.
232pub enum ScopeTarget<'a> {
233    /// Target matches an element matching the specified selector list.
234    Selector(&'a SelectorList<SelectorImpl>),
235    /// Target matches an implicit scope target.
236    Implicit(ImplicitScopeTarget),
237}
238
239impl<'a> ScopeTarget<'a> {
240    /// Check if the given element is the scope.
241    fn check<E: TElement>(
242        &self,
243        element: E,
244        scope: Option<OpaqueElement>,
245        scope_subject_map: &ScopeSubjectMap,
246        context: &mut MatchingContext<E::Impl>,
247    ) -> bool {
248        match self {
249            Self::Selector(list) => context.nest_for_scope_condition(scope, |context| {
250                if scope_subject_map.early_reject(element, context.quirks_mode()) {
251                    return false;
252                }
253                for selector in list.slice().iter() {
254                    if matches_selector(selector, 0, None, &element, context) {
255                        return true;
256                    }
257                }
258                false
259            }),
260            Self::Implicit(t) => t.check(element),
261        }
262    }
263}
264
265/// A scope root candidate.
266#[derive(Clone, Copy, Debug)]
267pub struct ScopeRootCandidate {
268    /// This candidate's scope root.
269    pub root: OpaqueElement,
270    /// Ancestor hop from the element under consideration to this scope root.
271    pub proximity: ScopeProximity,
272}
273
274impl ScopeRootCandidate {
275    /// Get the element corresponding to this scope root candidate.
276    pub fn get_scope_root_element<E>(&self, originating_element: E) -> Option<E>
277    where
278        E: TElement,
279    {
280        // Could just unsafe-convert from opaque element - technically
281        // faster as well, but it doesn't seem worth having to manually
282        // assure safety every time.
283        let mut e = originating_element;
284        let hops = self.proximity.get()?;
285        for _ in 0..hops {
286            e = e.parent_element()?;
287        }
288        debug_assert_eq!(e.opaque(), self.root);
289        Some(e)
290    }
291}
292
293/// Collect potential scope roots for a given element and its scope target.
294/// The check may not pass the ceiling, if specified.
295pub fn collect_scope_roots<E>(
296    element: E,
297    ceiling: Option<OpaqueElement>,
298    context: &mut MatchingContext<E::Impl>,
299    target: &ScopeTarget,
300    matches_shadow_host: bool,
301    scope_subject_map: &ScopeSubjectMap,
302) -> Vec<ScopeRootCandidate>
303where
304    E: TElement,
305{
306    let mut result = vec![];
307    let mut parent = Some(element);
308    let mut proximity = 0usize;
309    while let Some(p) = parent {
310        if ceiling == Some(p.opaque()) {
311            break;
312        }
313        if target.check(p, ceiling, scope_subject_map, context) {
314            result.push(ScopeRootCandidate {
315                root: p.opaque(),
316                proximity: ScopeProximity::new(proximity),
317            });
318            // Note that we can't really break here - we need to consider
319            // ALL scope roots to figure out whch one didn't end.
320        }
321        parent = p.parent_element();
322        proximity += 1;
323        // We we got to the top of the shadow tree - keep going
324        // if we may match the shadow host.
325        if parent.is_none() && matches_shadow_host {
326            parent = p.containing_shadow_host();
327        }
328    }
329    result
330}
331
332/// Given the scope-end selector, check if the element is outside of the scope.
333/// That is, check if any ancestor to the root matches the scope-end selector.
334pub fn element_is_outside_of_scope<E>(
335    selector: &Selector<E::Impl>,
336    element: E,
337    root: OpaqueElement,
338    context: &mut MatchingContext<E::Impl>,
339    root_may_be_shadow_host: bool,
340) -> bool
341where
342    E: TElement,
343{
344    let mut parent = Some(element);
345    context.nest_for_scope_condition(Some(root), |context| {
346        while let Some(p) = parent {
347            if matches_selector(selector, 0, None, &p, context) {
348                return true;
349            }
350            if p.opaque() == root {
351                // Reached the top, not lying outside of scope.
352                break;
353            }
354            parent = p.parent_element();
355            if parent.is_none() && root_may_be_shadow_host {
356                if let Some(host) = p.containing_shadow_host() {
357                    // Pretty much an edge case where user specified scope-start and -end of :host
358                    return host.opaque() == root;
359                }
360            }
361        }
362        return false;
363    })
364}
365
366/// A map containing simple selectors in subjects of scope selectors.
367/// This allows fast-rejecting scopes before running the full match.
368#[derive(Clone, Debug, Default, MallocSizeOf)]
369pub struct ScopeSubjectMap {
370    buckets: SimpleBucketsMap<()>,
371    any: bool,
372}
373
374impl ScopeSubjectMap {
375    /// Add the `<scope-start>` of a scope.
376    pub fn add_bound_start(
377        &mut self,
378        selectors: &SelectorList<SelectorImpl>,
379        quirks_mode: QuirksMode,
380    ) {
381        if self.add_selector_list(selectors, quirks_mode) {
382            self.any = true;
383        }
384    }
385
386    fn add_selector_list(
387        &mut self,
388        selectors: &SelectorList<SelectorImpl>,
389        quirks_mode: QuirksMode,
390    ) -> bool {
391        let mut is_any = false;
392        for selector in selectors.slice().iter() {
393            is_any = is_any || self.add_selector(selector, quirks_mode);
394        }
395        is_any
396    }
397
398    fn add_selector(&mut self, selector: &Selector<SelectorImpl>, quirks_mode: QuirksMode) -> bool {
399        let mut is_any = true;
400        let mut iter = selector.iter();
401        while let Some(c) = iter.next() {
402            let component_any = match c {
403                Component::Class(cls) => {
404                    match self.buckets.classes.try_entry(cls.0.clone(), quirks_mode) {
405                        Ok(e) => {
406                            e.or_insert(());
407                            false
408                        },
409                        Err(_) => true,
410                    }
411                },
412                Component::ID(id) => match self.buckets.ids.try_entry(id.0.clone(), quirks_mode) {
413                    Ok(e) => {
414                        e.or_insert(());
415                        false
416                    },
417                    Err(_) => true,
418                },
419                Component::LocalName(local_name) => {
420                    self.buckets
421                        .local_names
422                        .insert(local_name.lower_name.clone(), ());
423                    false
424                },
425                Component::Is(ref list) | Component::Where(ref list) => {
426                    self.add_selector_list(list, quirks_mode)
427                },
428                _ => true,
429            };
430
431            is_any = is_any && component_any;
432        }
433        is_any
434    }
435
436    /// Shrink the map as much as possible.
437    pub fn shrink_if_needed(&mut self) {
438        self.buckets.shrink_if_needed();
439    }
440
441    /// Clear the map.
442    pub fn clear(&mut self) {
443        self.buckets.clear();
444        self.any = false;
445    }
446
447    /// Could a given element possibly be a scope root?
448    fn early_reject<E: TElement>(&self, element: E, quirks_mode: QuirksMode) -> bool {
449        if self.any {
450            return false;
451        }
452
453        if let Some(id) = element.id() {
454            if self.buckets.ids.get(id, quirks_mode).is_some() {
455                return false;
456            }
457        }
458
459        let mut found = false;
460        element.each_class(|cls| {
461            if self.buckets.classes.get(cls, quirks_mode).is_some() {
462                found = true;
463            }
464        });
465        if found {
466            return false;
467        }
468
469        if self.buckets.local_names.get(element.local_name()).is_some() {
470            return false;
471        }
472
473        true
474    }
475}
476
477/// Determine if this selector list, when used as a scope bound selector, is considered trivial.
478pub fn scope_selector_list_is_trivial(list: &SelectorList<SelectorImpl>) -> bool {
479    fn scope_selector_is_trivial(selector: &Selector<SelectorImpl>) -> bool {
480        // A selector is trivial if:
481        // * There is no selector conditional on its siblings and/or descendant to match, and
482        // * There is no dependency on sibling relations, and
483        // * There's no ID selector in the selector. A more correct approach may be to ensure that
484        //   scoping roots of the style sharing candidates and targets have matching IDs, but that
485        //   requires re-plumbing what we pass around for scope roots.
486        let mut iter = selector.iter();
487        loop {
488            while let Some(c) = iter.next() {
489                match c {
490                    Component::ID(_)
491                    | Component::Nth(_)
492                    | Component::NthOf(_)
493                    | Component::Has(_) => return false,
494                    Component::Is(ref list)
495                    | Component::Where(ref list)
496                    | Component::Negation(ref list) => {
497                        if !scope_selector_list_is_trivial(list) {
498                            return false;
499                        }
500                    },
501                    _ => (),
502                }
503            }
504
505            match iter.next_sequence() {
506                Some(c) => {
507                    if c.is_sibling() {
508                        return false;
509                    }
510                },
511                None => return true,
512            }
513        }
514    }
515
516    list.slice().iter().all(|s| scope_selector_is_trivial(s))
517}