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            let selector_parser = SelectorParser {
131                stylesheet_origin: context.stylesheet_origin,
132                namespaces: &context.namespaces,
133                url_data: context.url_data,
134                for_supports_rule: false,
135            };
136            let parse_relative = if for_end {
137                ParseRelative::ForScope
138            } else {
139                parse_relative
140            };
141            Ok(Some(SelectorList::parse_disallow_pseudo(
142                &selector_parser,
143                input,
144                parse_relative,
145            )?))
146        })
147    })
148}
149
150impl ScopeBounds {
151    /// Parse a container condition.
152    pub fn parse<'a>(
153        context: &ParserContext,
154        input: &mut Parser<'a, '_>,
155        parse_relative: ParseRelative,
156    ) -> Result<Self, ParseError<'a>> {
157        let start = parse_scope(context, input, parse_relative, false)?;
158        let end = parse_scope(context, input, parse_relative, true)?;
159        Ok(Self { start, end })
160    }
161}
162
163/// Types of implicit scope root.
164#[derive(Debug, Copy, Clone, MallocSizeOf)]
165pub enum ImplicitScopeRoot {
166    /// This implicit scope root is in the light tree.
167    InLightTree(OpaqueElement),
168    /// This implicit scope root is the document element, regardless of which (light|shadow) tree
169    /// the element being matched is. This is the case for e.g. if you specified an implicit scope
170    /// within a user stylesheet.
171    DocumentElement,
172    /// The implicit scope root is in a constructed stylesheet - the scope root the element
173    /// under consideration's shadow root (If one exists).
174    Constructed,
175    /// This implicit scope root is in the shadow tree.
176    InShadowTree(OpaqueElement),
177    /// This implicit scope root is the shadow host of the stylesheet-containing shadow tree.
178    ShadowHost(OpaqueElement),
179}
180
181impl ImplicitScopeRoot {
182    /// Return true if this matches the shadow host.
183    pub fn matches_shadow_host(&self) -> bool {
184        match self {
185            Self::InLightTree(..) | Self::InShadowTree(..) | Self::DocumentElement => false,
186            Self::ShadowHost(..) | Self::Constructed => true,
187        }
188    }
189
190    /// Return the implicit scope root element.
191    pub fn element(&self, current_host: Option<OpaqueElement>) -> ImplicitScopeTarget {
192        match self {
193            Self::InLightTree(e) | Self::InShadowTree(e) | Self::ShadowHost(e) => {
194                ImplicitScopeTarget::Element(*e)
195            },
196            Self::Constructed | Self::DocumentElement => {
197                if matches!(self, Self::Constructed) {
198                    if let Some(host) = current_host {
199                        return ImplicitScopeTarget::Element(host);
200                    }
201                }
202                ImplicitScopeTarget::DocumentElement
203            },
204        }
205    }
206}
207
208/// Target of this implicit scope.
209pub enum ImplicitScopeTarget {
210    /// Target matches only the specified element.
211    Element(OpaqueElement),
212    /// Implicit scope whose target is the document element.
213    DocumentElement,
214}
215
216impl ImplicitScopeTarget {
217    /// Check if this element is the implicit scope.
218    fn check<E: TElement>(&self, element: E) -> bool {
219        match self {
220            Self::Element(e) => element.opaque() == *e,
221            Self::DocumentElement => element.is_root(),
222        }
223    }
224}
225
226/// Target of this scope.
227pub enum ScopeTarget<'a> {
228    /// Target matches an element matching the specified selector list.
229    Selector(&'a SelectorList<SelectorImpl>),
230    /// Target matches an implicit scope target.
231    Implicit(ImplicitScopeTarget),
232}
233
234impl<'a> ScopeTarget<'a> {
235    /// Check if the given element is the scope.
236    fn check<E: TElement>(
237        &self,
238        element: E,
239        scope: Option<OpaqueElement>,
240        scope_subject_map: &ScopeSubjectMap,
241        context: &mut MatchingContext<E::Impl>,
242    ) -> bool {
243        match self {
244            Self::Selector(list) => context.nest_for_scope_condition(scope, |context| {
245                if scope_subject_map.early_reject(element, context.quirks_mode()) {
246                    return false;
247                }
248                for selector in list.slice().iter() {
249                    if matches_selector(selector, 0, None, &element, context) {
250                        return true;
251                    }
252                }
253                false
254            }),
255            Self::Implicit(t) => t.check(element),
256        }
257    }
258}
259
260/// A scope root candidate.
261#[derive(Clone, Copy, Debug)]
262pub struct ScopeRootCandidate {
263    /// This candidate's scope root.
264    pub root: OpaqueElement,
265    /// Ancestor hop from the element under consideration to this scope root.
266    pub proximity: ScopeProximity,
267}
268
269impl ScopeRootCandidate {
270    /// Get the element corresponding to this scope root candidate.
271    pub fn get_scope_root_element<E>(&self, originating_element: E) -> Option<E>
272    where
273        E: TElement,
274    {
275        // Could just unsafe-convert from opaque element - technically
276        // faster as well, but it doesn't seem worth having to manually
277        // assure safety every time.
278        let mut e = originating_element;
279        let hops = self.proximity.get()?;
280        for _ in 0..hops {
281            e = e.parent_element()?;
282        }
283        debug_assert_eq!(e.opaque(), self.root);
284        Some(e)
285    }
286}
287
288/// Collect potential scope roots for a given element and its scope target.
289/// The check may not pass the ceiling, if specified.
290pub fn collect_scope_roots<E>(
291    element: E,
292    ceiling: Option<OpaqueElement>,
293    context: &mut MatchingContext<E::Impl>,
294    target: &ScopeTarget,
295    matches_shadow_host: bool,
296    scope_subject_map: &ScopeSubjectMap,
297) -> Vec<ScopeRootCandidate>
298where
299    E: TElement,
300{
301    let mut result = vec![];
302    let mut parent = Some(element);
303    let mut proximity = 0usize;
304    while let Some(p) = parent {
305        if target.check(p, ceiling, scope_subject_map, context) {
306            result.push(ScopeRootCandidate {
307                root: p.opaque(),
308                proximity: ScopeProximity::new(proximity),
309            });
310            // Note that we can't really break here - we need to consider
311            // ALL scope roots to figure out whch one didn't end.
312        }
313        if ceiling == Some(p.opaque()) {
314            break;
315        }
316        parent = p.parent_element();
317        proximity += 1;
318        // We we got to the top of the shadow tree - keep going
319        // if we may match the shadow host.
320        if parent.is_none() && matches_shadow_host {
321            parent = p.containing_shadow_host();
322        }
323    }
324    result
325}
326
327/// Given the scope-end selector, check if the element is outside of the scope.
328/// That is, check if any ancestor to the root matches the scope-end selector.
329pub fn element_is_outside_of_scope<E>(
330    selector: &Selector<E::Impl>,
331    element: E,
332    root: OpaqueElement,
333    context: &mut MatchingContext<E::Impl>,
334    root_may_be_shadow_host: bool,
335) -> bool
336where
337    E: TElement,
338{
339    let mut parent = Some(element);
340    context.nest_for_scope_condition(Some(root), |context| {
341        while let Some(p) = parent {
342            if matches_selector(selector, 0, None, &p, context) {
343                return true;
344            }
345            if p.opaque() == root {
346                // Reached the top, not lying outside of scope.
347                break;
348            }
349            parent = p.parent_element();
350            if parent.is_none() && root_may_be_shadow_host {
351                if let Some(host) = p.containing_shadow_host() {
352                    // Pretty much an edge case where user specified scope-start and -end of :host
353                    return host.opaque() == root;
354                }
355            }
356        }
357        return false;
358    })
359}
360
361/// A map containing simple selectors in subjects of scope selectors.
362/// This allows fast-rejecting scopes before running the full match.
363#[derive(Clone, Debug, Default, MallocSizeOf)]
364pub struct ScopeSubjectMap {
365    buckets: SimpleBucketsMap<()>,
366    any: bool,
367}
368
369impl ScopeSubjectMap {
370    /// Add the `<scope-start>` of a scope.
371    pub fn add_bound_start(
372        &mut self,
373        selectors: &SelectorList<SelectorImpl>,
374        quirks_mode: QuirksMode,
375    ) {
376        if self.add_selector_list(selectors, quirks_mode) {
377            self.any = true;
378        }
379    }
380
381    fn add_selector_list(
382        &mut self,
383        selectors: &SelectorList<SelectorImpl>,
384        quirks_mode: QuirksMode,
385    ) -> bool {
386        let mut is_any = false;
387        for selector in selectors.slice().iter() {
388            is_any = is_any || self.add_selector(selector, quirks_mode);
389        }
390        is_any
391    }
392
393    fn add_selector(&mut self, selector: &Selector<SelectorImpl>, quirks_mode: QuirksMode) -> bool {
394        let mut is_any = true;
395        let mut iter = selector.iter();
396        while let Some(c) = iter.next() {
397            let component_any = match c {
398                Component::Class(cls) => {
399                    match self.buckets.classes.try_entry(cls.0.clone(), quirks_mode) {
400                        Ok(e) => {
401                            e.or_insert(());
402                            false
403                        },
404                        Err(_) => true,
405                    }
406                },
407                Component::ID(id) => match self.buckets.ids.try_entry(id.0.clone(), quirks_mode) {
408                    Ok(e) => {
409                        e.or_insert(());
410                        false
411                    },
412                    Err(_) => true,
413                },
414                Component::LocalName(local_name) => {
415                    self.buckets
416                        .local_names
417                        .insert(local_name.lower_name.clone(), ());
418                    false
419                },
420                Component::Is(ref list) | Component::Where(ref list) => {
421                    self.add_selector_list(list, quirks_mode)
422                },
423                _ => true,
424            };
425
426            is_any = is_any && component_any;
427        }
428        is_any
429    }
430
431    /// Shrink the map as much as possible.
432    pub fn shrink_if_needed(&mut self) {
433        self.buckets.shrink_if_needed();
434    }
435
436    /// Clear the map.
437    pub fn clear(&mut self) {
438        self.buckets.clear();
439        self.any = false;
440    }
441
442    /// Could a given element possibly be a scope root?
443    fn early_reject<E: TElement>(&self, element: E, quirks_mode: QuirksMode) -> bool {
444        if self.any {
445            return false;
446        }
447
448        if let Some(id) = element.id() {
449            if self.buckets.ids.get(id, quirks_mode).is_some() {
450                return false;
451            }
452        }
453
454        let mut found = false;
455        element.each_class(|cls| {
456            if self.buckets.classes.get(cls, quirks_mode).is_some() {
457                found = true;
458            }
459        });
460        if found {
461            return false;
462        }
463
464        if self.buckets.local_names.get(element.local_name()).is_some() {
465            return false;
466        }
467
468        true
469    }
470}
471
472/// Determine if this selector list, when used as a scope bound selector, is considered trivial.
473pub fn scope_selector_list_is_trivial(list: &SelectorList<SelectorImpl>) -> bool {
474    fn scope_selector_is_trivial(selector: &Selector<SelectorImpl>) -> bool {
475        // A selector is trivial if:
476        // * There is no selector conditional on its siblings and/or descendant to match, and
477        // * There is no dependency on sibling relations, and
478        // * There's no ID selector in the selector. A more correct approach may be to ensure that
479        //   scoping roots of the style sharing candidates and targets have matching IDs, but that
480        //   requires re-plumbing what we pass around for scope roots.
481        let mut iter = selector.iter();
482        loop {
483            while let Some(c) = iter.next() {
484                match c {
485                    Component::ID(_)
486                    | Component::Nth(_)
487                    | Component::NthOf(_)
488                    | Component::Has(_) => return false,
489                    Component::Is(ref list)
490                    | Component::Where(ref list)
491                    | Component::Negation(ref list) => {
492                        if !scope_selector_list_is_trivial(list) {
493                            return false;
494                        }
495                    },
496                    _ => (),
497                }
498            }
499
500            match iter.next_sequence() {
501                Some(c) => {
502                    if c.is_sibling() {
503                        return false;
504                    }
505                },
506                None => return true,
507            }
508        }
509    }
510
511    list.slice().iter().all(|s| scope_selector_is_trivial(s))
512}