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