/* 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/. */ //! An iterator over a list of rules. use crate::context::QuirksMode; use crate::media_queries::Device; use crate::shared_lock::SharedRwLockReadGuard; use crate::stylesheets::{CssRule, DocumentRule, ImportRule, MediaRule, SupportsRule}; use smallvec::SmallVec; use std::slice; /// An iterator over a list of rules. pub struct RulesIterator<'a, 'b, C> where 'b: 'a, C: NestedRuleIterationCondition + 'static, { device: &'a Device, quirks_mode: QuirksMode, guard: &'a SharedRwLockReadGuard<'b>, stack: SmallVec<[slice::Iter<'a, CssRule>; 3]>, _phantom: ::std::marker::PhantomData, } impl<'a, 'b, C> RulesIterator<'a, 'b, C> where 'b: 'a, C: NestedRuleIterationCondition + 'static, { /// Creates a new `RulesIterator` to iterate over `rules`. pub fn new( device: &'a Device, quirks_mode: QuirksMode, guard: &'a SharedRwLockReadGuard<'b>, rules: slice::Iter<'a, CssRule>, ) -> Self { let mut stack = SmallVec::new(); stack.push(rules); Self { device, quirks_mode, guard, stack, _phantom: ::std::marker::PhantomData, } } /// Skips all the remaining children of the last nested rule processed. pub fn skip_children(&mut self) { self.stack.pop(); } /// Returns the children of `rule`, and whether `rule` is effective. pub fn children( rule: &'a CssRule, device: &'a Device, quirks_mode: QuirksMode, guard: &'a SharedRwLockReadGuard<'_>, effective: &mut bool, ) -> Option> { *effective = true; match *rule { CssRule::Namespace(_) | CssRule::FontFace(_) | CssRule::CounterStyle(_) | CssRule::Keyframes(_) | CssRule::Margin(_) | CssRule::Property(_) | CssRule::LayerStatement(_) | CssRule::FontFeatureValues(_) | CssRule::FontPaletteValues(_) | CssRule::NestedDeclarations(_) | CssRule::PositionTry(_) => None, CssRule::Page(ref page_rule) => { let page_rule = page_rule.read_with(guard); let rules = page_rule.rules.read_with(guard); Some(rules.0.iter()) }, CssRule::Style(ref style_rule) => { let style_rule = style_rule.read_with(guard); style_rule .rules .as_ref() .map(|r| r.read_with(guard).0.iter()) }, CssRule::Import(ref import_rule) => { let import_rule = import_rule.read_with(guard); if !C::process_import(guard, device, quirks_mode, import_rule) { *effective = false; return None; } Some(import_rule.stylesheet.rules(guard).iter()) }, CssRule::Document(ref doc_rule) => { if !C::process_document(guard, device, quirks_mode, doc_rule) { *effective = false; return None; } Some(doc_rule.rules.read_with(guard).0.iter()) }, CssRule::Container(ref container_rule) => { Some(container_rule.rules.read_with(guard).0.iter()) }, CssRule::Media(ref media_rule) => { if !C::process_media(guard, device, quirks_mode, media_rule) { *effective = false; return None; } Some(media_rule.rules.read_with(guard).0.iter()) }, CssRule::Supports(ref supports_rule) => { if !C::process_supports(guard, device, quirks_mode, supports_rule) { *effective = false; return None; } Some(supports_rule.rules.read_with(guard).0.iter()) }, CssRule::LayerBlock(ref layer_rule) => Some(layer_rule.rules.read_with(guard).0.iter()), CssRule::Scope(ref rule) => Some(rule.rules.read_with(guard).0.iter()), CssRule::StartingStyle(ref rule) => Some(rule.rules.read_with(guard).0.iter()), } } } impl<'a, 'b, C> Iterator for RulesIterator<'a, 'b, C> where 'b: 'a, C: NestedRuleIterationCondition + 'static, { type Item = &'a CssRule; fn next(&mut self) -> Option { while !self.stack.is_empty() { let rule = { let nested_iter = self.stack.last_mut().unwrap(); match nested_iter.next() { Some(r) => r, None => { self.stack.pop(); continue; }, } }; let mut effective = true; let children = Self::children( rule, self.device, self.quirks_mode, self.guard, &mut effective, ); if !effective { continue; } if let Some(children) = children { // NOTE: It's important that `children` gets pushed even if // empty, so that `skip_children()` works as expected. self.stack.push(children); } return Some(rule); } None } } /// RulesIterator. pub trait NestedRuleIterationCondition { /// Whether we should process the nested rules in a given `@import` rule. fn process_import( guard: &SharedRwLockReadGuard, device: &Device, quirks_mode: QuirksMode, rule: &ImportRule, ) -> bool; /// Whether we should process the nested rules in a given `@media` rule. fn process_media( guard: &SharedRwLockReadGuard, device: &Device, quirks_mode: QuirksMode, rule: &MediaRule, ) -> bool; /// Whether we should process the nested rules in a given `@-moz-document` /// rule. fn process_document( guard: &SharedRwLockReadGuard, device: &Device, quirks_mode: QuirksMode, rule: &DocumentRule, ) -> bool; /// Whether we should process the nested rules in a given `@supports` rule. fn process_supports( guard: &SharedRwLockReadGuard, device: &Device, quirks_mode: QuirksMode, rule: &SupportsRule, ) -> bool; } /// A struct that represents the condition that a rule applies to the document. pub struct EffectiveRules; impl EffectiveRules { /// Returns whether a given rule is effective. pub fn is_effective( guard: &SharedRwLockReadGuard, device: &Device, quirks_mode: QuirksMode, rule: &CssRule, ) -> bool { match *rule { CssRule::Import(ref import_rule) => { let import_rule = import_rule.read_with(guard); Self::process_import(guard, device, quirks_mode, import_rule) }, CssRule::Document(ref doc_rule) => { Self::process_document(guard, device, quirks_mode, doc_rule) }, CssRule::Media(ref media_rule) => { Self::process_media(guard, device, quirks_mode, media_rule) }, CssRule::Supports(ref supports_rule) => { Self::process_supports(guard, device, quirks_mode, supports_rule) }, _ => true, } } } impl NestedRuleIterationCondition for EffectiveRules { fn process_import( guard: &SharedRwLockReadGuard, device: &Device, quirks_mode: QuirksMode, rule: &ImportRule, ) -> bool { match rule.stylesheet.media(guard) { Some(m) => m.evaluate(device, quirks_mode), None => true, } } fn process_media( guard: &SharedRwLockReadGuard, device: &Device, quirks_mode: QuirksMode, rule: &MediaRule, ) -> bool { rule.media_queries .read_with(guard) .evaluate(device, quirks_mode) } fn process_document( _: &SharedRwLockReadGuard, device: &Device, _: QuirksMode, rule: &DocumentRule, ) -> bool { rule.condition.evaluate(device) } fn process_supports( _: &SharedRwLockReadGuard, _: &Device, _: QuirksMode, rule: &SupportsRule, ) -> bool { rule.enabled } } /// A filter that processes all the rules in a rule list. pub struct AllRules; impl NestedRuleIterationCondition for AllRules { fn process_import( _: &SharedRwLockReadGuard, _: &Device, _: QuirksMode, _: &ImportRule, ) -> bool { true } fn process_media(_: &SharedRwLockReadGuard, _: &Device, _: QuirksMode, _: &MediaRule) -> bool { true } fn process_document( _: &SharedRwLockReadGuard, _: &Device, _: QuirksMode, _: &DocumentRule, ) -> bool { true } fn process_supports( _: &SharedRwLockReadGuard, _: &Device, _: QuirksMode, _: &SupportsRule, ) -> bool { true } } /// An iterator over all the effective rules of a stylesheet. /// /// NOTE: This iterator recurses into `@import` rules. pub type EffectiveRulesIterator<'a, 'b> = RulesIterator<'a, 'b, EffectiveRules>; impl<'a, 'b> EffectiveRulesIterator<'a, 'b> { /// Returns an iterator over the effective children of a rule, even if /// `rule` itself is not effective. pub fn effective_children( device: &'a Device, quirks_mode: QuirksMode, guard: &'a SharedRwLockReadGuard<'b>, rule: &'a CssRule, ) -> Self { let children = RulesIterator::::children(rule, device, quirks_mode, guard, &mut false); EffectiveRulesIterator::new(device, quirks_mode, guard, children.unwrap_or([].iter())) } }