style/stylesheets/
rules_iterator.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//! An iterator over a list of rules.
6
7use crate::context::QuirksMode;
8use crate::media_queries::Device;
9use crate::shared_lock::SharedRwLockReadGuard;
10use crate::stylesheets::{CssRule, DocumentRule, ImportRule, MediaRule, SupportsRule};
11use smallvec::SmallVec;
12use std::slice;
13
14/// An iterator over a list of rules.
15pub struct RulesIterator<'a, 'b, C>
16where
17    'b: 'a,
18    C: NestedRuleIterationCondition + 'static,
19{
20    device: &'a Device,
21    quirks_mode: QuirksMode,
22    guard: &'a SharedRwLockReadGuard<'b>,
23    stack: SmallVec<[slice::Iter<'a, CssRule>; 3]>,
24    _phantom: ::std::marker::PhantomData<C>,
25}
26
27impl<'a, 'b, C> RulesIterator<'a, 'b, C>
28where
29    'b: 'a,
30    C: NestedRuleIterationCondition + 'static,
31{
32    /// Creates a new `RulesIterator` to iterate over `rules`.
33    pub fn new(
34        device: &'a Device,
35        quirks_mode: QuirksMode,
36        guard: &'a SharedRwLockReadGuard<'b>,
37        rules: slice::Iter<'a, CssRule>,
38    ) -> Self {
39        let mut stack = SmallVec::new();
40        stack.push(rules);
41        Self {
42            device,
43            quirks_mode,
44            guard,
45            stack,
46            _phantom: ::std::marker::PhantomData,
47        }
48    }
49
50    /// Skips all the remaining children of the last nested rule processed.
51    pub fn skip_children(&mut self) {
52        self.stack.pop();
53    }
54
55    /// Returns the children of `rule`, and whether `rule` is effective.
56    pub fn children(
57        rule: &'a CssRule,
58        device: &'a Device,
59        quirks_mode: QuirksMode,
60        guard: &'a SharedRwLockReadGuard<'_>,
61        effective: &mut bool,
62    ) -> Option<slice::Iter<'a, CssRule>> {
63        *effective = true;
64        match *rule {
65            CssRule::Namespace(_)
66            | CssRule::FontFace(_)
67            | CssRule::CounterStyle(_)
68            | CssRule::Keyframes(_)
69            | CssRule::Margin(_)
70            | CssRule::Property(_)
71            | CssRule::LayerStatement(_)
72            | CssRule::FontFeatureValues(_)
73            | CssRule::FontPaletteValues(_)
74            | CssRule::NestedDeclarations(_)
75            | CssRule::PositionTry(_) => None,
76            CssRule::Page(ref page_rule) => {
77                let page_rule = page_rule.read_with(guard);
78                let rules = page_rule.rules.read_with(guard);
79                Some(rules.0.iter())
80            },
81            CssRule::Style(ref style_rule) => {
82                let style_rule = style_rule.read_with(guard);
83                style_rule
84                    .rules
85                    .as_ref()
86                    .map(|r| r.read_with(guard).0.iter())
87            },
88            CssRule::Import(ref import_rule) => {
89                let import_rule = import_rule.read_with(guard);
90                if !C::process_import(guard, device, quirks_mode, import_rule) {
91                    *effective = false;
92                    return None;
93                }
94                Some(import_rule.stylesheet.rules(guard).iter())
95            },
96            CssRule::Document(ref doc_rule) => {
97                if !C::process_document(guard, device, quirks_mode, doc_rule) {
98                    *effective = false;
99                    return None;
100                }
101                Some(doc_rule.rules.read_with(guard).0.iter())
102            },
103            CssRule::Container(ref container_rule) => {
104                Some(container_rule.rules.read_with(guard).0.iter())
105            },
106            CssRule::Media(ref media_rule) => {
107                if !C::process_media(guard, device, quirks_mode, media_rule) {
108                    *effective = false;
109                    return None;
110                }
111                Some(media_rule.rules.read_with(guard).0.iter())
112            },
113            CssRule::Supports(ref supports_rule) => {
114                if !C::process_supports(guard, device, quirks_mode, supports_rule) {
115                    *effective = false;
116                    return None;
117                }
118                Some(supports_rule.rules.read_with(guard).0.iter())
119            },
120            CssRule::LayerBlock(ref layer_rule) => Some(layer_rule.rules.read_with(guard).0.iter()),
121            CssRule::Scope(ref rule) => Some(rule.rules.read_with(guard).0.iter()),
122            CssRule::StartingStyle(ref rule) => Some(rule.rules.read_with(guard).0.iter()),
123        }
124    }
125}
126
127impl<'a, 'b, C> Iterator for RulesIterator<'a, 'b, C>
128where
129    'b: 'a,
130    C: NestedRuleIterationCondition + 'static,
131{
132    type Item = &'a CssRule;
133
134    fn next(&mut self) -> Option<Self::Item> {
135        while !self.stack.is_empty() {
136            let rule = {
137                let nested_iter = self.stack.last_mut().unwrap();
138                match nested_iter.next() {
139                    Some(r) => r,
140                    None => {
141                        self.stack.pop();
142                        continue;
143                    },
144                }
145            };
146
147            let mut effective = true;
148            let children = Self::children(
149                rule,
150                self.device,
151                self.quirks_mode,
152                self.guard,
153                &mut effective,
154            );
155            if !effective {
156                continue;
157            }
158
159            if let Some(children) = children {
160                // NOTE: It's important that `children` gets pushed even if
161                // empty, so that `skip_children()` works as expected.
162                self.stack.push(children);
163            }
164
165            return Some(rule);
166        }
167
168        None
169    }
170}
171
172/// RulesIterator.
173pub trait NestedRuleIterationCondition {
174    /// Whether we should process the nested rules in a given `@import` rule.
175    fn process_import(
176        guard: &SharedRwLockReadGuard,
177        device: &Device,
178        quirks_mode: QuirksMode,
179        rule: &ImportRule,
180    ) -> bool;
181
182    /// Whether we should process the nested rules in a given `@media` rule.
183    fn process_media(
184        guard: &SharedRwLockReadGuard,
185        device: &Device,
186        quirks_mode: QuirksMode,
187        rule: &MediaRule,
188    ) -> bool;
189
190    /// Whether we should process the nested rules in a given `@-moz-document`
191    /// rule.
192    fn process_document(
193        guard: &SharedRwLockReadGuard,
194        device: &Device,
195        quirks_mode: QuirksMode,
196        rule: &DocumentRule,
197    ) -> bool;
198
199    /// Whether we should process the nested rules in a given `@supports` rule.
200    fn process_supports(
201        guard: &SharedRwLockReadGuard,
202        device: &Device,
203        quirks_mode: QuirksMode,
204        rule: &SupportsRule,
205    ) -> bool;
206}
207
208/// A struct that represents the condition that a rule applies to the document.
209pub struct EffectiveRules;
210
211impl EffectiveRules {
212    /// Returns whether a given rule is effective.
213    pub fn is_effective(
214        guard: &SharedRwLockReadGuard,
215        device: &Device,
216        quirks_mode: QuirksMode,
217        rule: &CssRule,
218    ) -> bool {
219        match *rule {
220            CssRule::Import(ref import_rule) => {
221                let import_rule = import_rule.read_with(guard);
222                Self::process_import(guard, device, quirks_mode, import_rule)
223            },
224            CssRule::Document(ref doc_rule) => {
225                Self::process_document(guard, device, quirks_mode, doc_rule)
226            },
227            CssRule::Media(ref media_rule) => {
228                Self::process_media(guard, device, quirks_mode, media_rule)
229            },
230            CssRule::Supports(ref supports_rule) => {
231                Self::process_supports(guard, device, quirks_mode, supports_rule)
232            },
233            _ => true,
234        }
235    }
236}
237
238impl NestedRuleIterationCondition for EffectiveRules {
239    fn process_import(
240        guard: &SharedRwLockReadGuard,
241        device: &Device,
242        quirks_mode: QuirksMode,
243        rule: &ImportRule,
244    ) -> bool {
245        match rule.stylesheet.media(guard) {
246            Some(m) => m.evaluate(device, quirks_mode),
247            None => true,
248        }
249    }
250
251    fn process_media(
252        guard: &SharedRwLockReadGuard,
253        device: &Device,
254        quirks_mode: QuirksMode,
255        rule: &MediaRule,
256    ) -> bool {
257        rule.media_queries
258            .read_with(guard)
259            .evaluate(device, quirks_mode)
260    }
261
262    fn process_document(
263        _: &SharedRwLockReadGuard,
264        device: &Device,
265        _: QuirksMode,
266        rule: &DocumentRule,
267    ) -> bool {
268        rule.condition.evaluate(device)
269    }
270
271    fn process_supports(
272        _: &SharedRwLockReadGuard,
273        _: &Device,
274        _: QuirksMode,
275        rule: &SupportsRule,
276    ) -> bool {
277        rule.enabled
278    }
279}
280
281/// A filter that processes all the rules in a rule list.
282pub struct AllRules;
283
284impl NestedRuleIterationCondition for AllRules {
285    fn process_import(
286        _: &SharedRwLockReadGuard,
287        _: &Device,
288        _: QuirksMode,
289        _: &ImportRule,
290    ) -> bool {
291        true
292    }
293
294    fn process_media(_: &SharedRwLockReadGuard, _: &Device, _: QuirksMode, _: &MediaRule) -> bool {
295        true
296    }
297
298    fn process_document(
299        _: &SharedRwLockReadGuard,
300        _: &Device,
301        _: QuirksMode,
302        _: &DocumentRule,
303    ) -> bool {
304        true
305    }
306
307    fn process_supports(
308        _: &SharedRwLockReadGuard,
309        _: &Device,
310        _: QuirksMode,
311        _: &SupportsRule,
312    ) -> bool {
313        true
314    }
315}
316
317/// An iterator over all the effective rules of a stylesheet.
318///
319/// NOTE: This iterator recurses into `@import` rules.
320pub type EffectiveRulesIterator<'a, 'b> = RulesIterator<'a, 'b, EffectiveRules>;
321
322impl<'a, 'b> EffectiveRulesIterator<'a, 'b> {
323    /// Returns an iterator over the effective children of a rule, even if
324    /// `rule` itself is not effective.
325    pub fn effective_children(
326        device: &'a Device,
327        quirks_mode: QuirksMode,
328        guard: &'a SharedRwLockReadGuard<'b>,
329        rule: &'a CssRule,
330    ) -> Self {
331        let children =
332            RulesIterator::<AllRules>::children(rule, device, quirks_mode, guard, &mut false);
333        EffectiveRulesIterator::new(device, quirks_mode, guard, children.unwrap_or([].iter()))
334    }
335}