1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */

//! A [`@scope`][scope] rule.
//!
//! [scope]: https://drafts.csswg.org/css-cascade-6/#scoped-styles

use crate::parser::ParserContext;
use crate::selector_parser::{SelectorImpl, SelectorParser};
use crate::shared_lock::{
    DeepCloneParams, DeepCloneWithLock, Locked, SharedRwLock, SharedRwLockReadGuard, ToCssWithGuard,
};
use crate::str::CssStringWriter;
use crate::stylesheets::CssRules;
use cssparser::{Parser, SourceLocation, ToCss};
#[cfg(feature = "gecko")]
use malloc_size_of::{MallocSizeOfOps, MallocUnconditionalSizeOf, MallocUnconditionalShallowSizeOf};
use selectors::parser::{ParseRelative, SelectorList};
use servo_arc::Arc;
use std::fmt::{self, Write};
use style_traits::CssWriter;

/// A scoped rule.
#[derive(Debug, ToShmem)]
pub struct ScopeRule {
    /// Bounds at which this rule applies.
    pub bounds: ScopeBounds,
    /// The nested rules inside the block.
    pub rules: Arc<Locked<CssRules>>,
    /// The source position where this rule was found.
    pub source_location: SourceLocation,
}

impl DeepCloneWithLock for ScopeRule {
    fn deep_clone_with_lock(
        &self,
        lock: &SharedRwLock,
        guard: &SharedRwLockReadGuard,
        params: &DeepCloneParams,
    ) -> Self {
        let rules = self.rules.read_with(guard);
        Self {
            bounds: self.bounds.clone(),
            rules: Arc::new(lock.wrap(rules.deep_clone_with_lock(lock, guard, params))),
            source_location: self.source_location.clone(),
        }
    }
}

impl ToCssWithGuard for ScopeRule {
    fn to_css(&self, guard: &SharedRwLockReadGuard, dest: &mut CssStringWriter) -> fmt::Result {
        dest.write_str("@scope")?;
        {
            let mut writer = CssWriter::new(dest);
            if let Some(start) = self.bounds.start.as_ref() {
                writer.write_str(" (")?;
                start.to_css(&mut writer)?;
                writer.write_char(')')?;
            }
            if let Some(end) = self.bounds.end.as_ref() {
                writer.write_str(" to (")?;
                end.to_css(&mut writer)?;
                writer.write_char(')')?;
            }
        }
        self.rules.read_with(guard).to_css_block(guard, dest)
    }
}

impl ScopeRule {
    /// Measure heap usage.
    #[cfg(feature = "gecko")]
    pub fn size_of(&self, guard: &SharedRwLockReadGuard, ops: &mut MallocSizeOfOps) -> usize {
        self.rules.unconditional_shallow_size_of(ops) +
            self.rules.read_with(guard).size_of(guard, ops) +
            self.bounds.size_of(ops)
    }
}

/// Bounds of the scope.
#[derive(Debug, Clone, ToShmem)]
pub struct ScopeBounds {
    /// Start of the scope.
    pub start: Option<SelectorList<SelectorImpl>>,
    /// End of the scope.
    pub end: Option<SelectorList<SelectorImpl>>,
}

impl ScopeBounds {
    #[cfg(feature = "gecko")]
    fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize {
        fn bound_size_of(bound: &Option<SelectorList<SelectorImpl>>, ops: &mut MallocSizeOfOps) -> usize {
            bound.as_ref().map(|list| list.unconditional_size_of(ops)).unwrap_or(0)
        }
        bound_size_of(&self.start, ops) + bound_size_of(&self.end, ops)
    }
}

fn parse_scope<'a>(
    context: &ParserContext,
    input: &mut Parser<'a, '_>,
    in_style_rule: bool,
    for_end: bool
) -> Option<SelectorList<SelectorImpl>> {
    input.try_parse(|input| {
        if for_end {
            input.expect_ident_matching("to")?;
        }
        input.expect_parenthesis_block()?;
        input.parse_nested_block(|input| {
            if input.is_exhausted() {
                return Ok(None);
            }
            let selector_parser = SelectorParser {
                stylesheet_origin: context.stylesheet_origin,
                namespaces: &context.namespaces,
                url_data: context.url_data,
                for_supports_rule: false,
            };
            let parse_relative = if for_end {
                ParseRelative::ForScope
            } else if in_style_rule {
                ParseRelative::ForNesting
            } else {
                ParseRelative::No
            };
            Ok(Some(SelectorList::parse_forgiving(
                &selector_parser,
                input,
                parse_relative,
            )?))
        })
    })
    .ok()
    .flatten()
}

impl ScopeBounds {
    /// Parse a container condition.
    pub fn parse<'a>(
        context: &ParserContext,
        input: &mut Parser<'a, '_>,
        in_style_rule: bool,
    ) -> Self {
        let start = parse_scope(
            context,
            input,
            in_style_rule,
            false
        );

        let end = parse_scope(
            context,
            input,
            in_style_rule,
            true
        );
        Self { start, end }
    }
}