diff options
36 files changed, 2298 insertions, 1995 deletions
diff --git a/components/script/dom/bindings/trace.rs b/components/script/dom/bindings/trace.rs index 64ef88da5e3..db3cd3b5c42 100644 --- a/components/script/dom/bindings/trace.rs +++ b/components/script/dom/bindings/trace.rs @@ -95,16 +95,15 @@ use std::time::{SystemTime, Instant}; use style::attr::{AttrIdentifier, AttrValue, LengthOrPercentageOrAuto}; use style::context::QuirksMode; use style::element_state::*; -use style::keyframes::Keyframe; use style::media_queries::MediaList; use style::properties::PropertyDeclarationBlock; use style::selector_parser::{PseudoElement, Snapshot}; use style::shared_lock::{SharedRwLock as StyleSharedRwLock, Locked as StyleLocked}; use style::stylearc::Arc as StyleArc; use style::stylesheets::{CssRules, FontFaceRule, KeyframesRule, MediaRule}; -use style::stylesheets::{NamespaceRule, StyleRule, ImportRule, SupportsRule}; +use style::stylesheets::{NamespaceRule, StyleRule, ImportRule, SupportsRule, ViewportRule}; +use style::stylesheets::keyframes_rule::Keyframe; use style::values::specified::Length; -use style::viewport::ViewportRule; use time::Duration; use uuid::Uuid; use webrender_traits::{WebGLBufferId, WebGLError, WebGLFramebufferId, WebGLProgramId}; diff --git a/components/script/dom/css.rs b/components/script/dom/css.rs index d56575b9d38..521c09ed4c2 100644 --- a/components/script/dom/css.rs +++ b/components/script/dom/css.rs @@ -12,7 +12,7 @@ use dom_struct::dom_struct; use style::context::QuirksMode; use style::parser::{PARSING_MODE_DEFAULT, ParserContext}; use style::stylesheets::CssRuleType; -use style::supports::{Declaration, parse_condition_or_declaration}; +use style::stylesheets::supports_rule::{Declaration, parse_condition_or_declaration}; #[dom_struct] pub struct CSS { diff --git a/components/script/dom/csskeyframerule.rs b/components/script/dom/csskeyframerule.rs index 46dac811d9f..bd8b0a47fc0 100644 --- a/components/script/dom/csskeyframerule.rs +++ b/components/script/dom/csskeyframerule.rs @@ -12,9 +12,9 @@ use dom::cssstyledeclaration::{CSSModificationAccess, CSSStyleDeclaration, CSSSt use dom::cssstylesheet::CSSStyleSheet; use dom::window::Window; use dom_struct::dom_struct; -use style::keyframes::Keyframe; use style::shared_lock::{Locked, ToCssWithGuard}; use style::stylearc::Arc; +use style::stylesheets::keyframes_rule::Keyframe; #[dom_struct] pub struct CSSKeyframeRule { diff --git a/components/script/dom/csskeyframesrule.rs b/components/script/dom/csskeyframesrule.rs index f48abe60751..42cb4b374fa 100644 --- a/components/script/dom/csskeyframesrule.rs +++ b/components/script/dom/csskeyframesrule.rs @@ -16,10 +16,9 @@ use dom::cssrulelist::{CSSRuleList, RulesSource}; use dom::cssstylesheet::CSSStyleSheet; use dom::window::Window; use dom_struct::dom_struct; -use style::keyframes::{Keyframe, KeyframeSelector}; use style::shared_lock::{Locked, ToCssWithGuard}; use style::stylearc::Arc; -use style::stylesheets::KeyframesRule; +use style::stylesheets::keyframes_rule::{KeyframesRule, Keyframe, KeyframeSelector}; use style::values::KeyframesName; #[dom_struct] diff --git a/components/script/dom/csssupportsrule.rs b/components/script/dom/csssupportsrule.rs index cba47f0acd7..5d01c2cfdd3 100644 --- a/components/script/dom/csssupportsrule.rs +++ b/components/script/dom/csssupportsrule.rs @@ -17,7 +17,7 @@ use style::parser::{PARSING_MODE_DEFAULT, ParserContext}; use style::shared_lock::{Locked, ToCssWithGuard}; use style::stylearc::Arc; use style::stylesheets::{CssRuleType, SupportsRule}; -use style::supports::SupportsCondition; +use style::stylesheets::supports_rule::SupportsCondition; use style_traits::ToCss; #[dom_struct] diff --git a/components/script/dom/cssviewportrule.rs b/components/script/dom/cssviewportrule.rs index 4438aabb53e..a3b259a4ab5 100644 --- a/components/script/dom/cssviewportrule.rs +++ b/components/script/dom/cssviewportrule.rs @@ -12,7 +12,7 @@ use dom::window::Window; use dom_struct::dom_struct; use style::shared_lock::{Locked, ToCssWithGuard}; use style::stylearc::Arc; -use style::viewport::ViewportRule; +use style::stylesheets::ViewportRule; #[dom_struct] pub struct CSSViewportRule { diff --git a/components/script/dom/htmlmetaelement.rs b/components/script/dom/htmlmetaelement.rs index 30bce19c299..888855dc7b6 100644 --- a/components/script/dom/htmlmetaelement.rs +++ b/components/script/dom/htmlmetaelement.rs @@ -26,8 +26,7 @@ use style::attr::AttrValue; use style::media_queries::MediaList; use style::str::HTML_SPACE_CHARACTERS; use style::stylearc::Arc; -use style::stylesheets::{Stylesheet, CssRule, CssRules, Origin}; -use style::viewport::ViewportRule; +use style::stylesheets::{Stylesheet, CssRule, CssRules, Origin, ViewportRule}; #[dom_struct] pub struct HTMLMetaElement { diff --git a/components/style/animation.rs b/components/style/animation.rs index d7eab3f314e..005d7948e7a 100644 --- a/components/style/animation.rs +++ b/components/style/animation.rs @@ -11,7 +11,6 @@ use context::SharedStyleContext; use dom::OpaqueNode; use euclid::point::Point2D; use font_metrics::FontMetricsProvider; -use keyframes::{KeyframesStep, KeyframesStepValue}; use properties::{self, CascadeFlags, ComputedValues, Importance}; use properties::animated_properties::{AnimatedProperty, TransitionProperty}; use properties::longhands::animation_direction::computed_value::single_value::T as AnimationDirection; @@ -22,6 +21,7 @@ use properties::longhands::transition_timing_function::single_value::computed_va use rule_tree::CascadeLevel; use std::sync::mpsc::Sender; use stylearc::Arc; +use stylesheets::keyframes_rule::{KeyframesStep, KeyframesStepValue}; use timer::Timer; use values::computed::Time; diff --git a/components/style/gecko/arc_types.rs b/components/style/gecko/arc_types.rs index 6619059d54c..3ff90bc9584 100644 --- a/components/style/gecko/arc_types.rs +++ b/components/style/gecko/arc_types.rs @@ -17,7 +17,6 @@ use gecko_bindings::bindings::{ServoComputedValues, ServoCssRules}; use gecko_bindings::structs::{RawServoDeclarationBlock, RawServoStyleRule}; use gecko_bindings::structs::RawServoAnimationValue; use gecko_bindings::sugar::ownership::{HasArcFFI, HasFFI}; -use keyframes::Keyframe; use media_queries::MediaList; use properties::{ComputedValues, PropertyDeclarationBlock}; use properties::animated_properties::AnimationValue; @@ -26,6 +25,7 @@ use shared_lock::Locked; use std::{mem, ptr}; use stylesheets::{CssRules, Stylesheet, StyleRule, ImportRule, KeyframesRule, MediaRule}; use stylesheets::{NamespaceRule, PageRule, SupportsRule, DocumentRule}; +use stylesheets::keyframes_rule::Keyframe; macro_rules! impl_arc_ffi { ($servo_type:ty => $gecko_type:ty [$addref:ident, $release:ident]) => { diff --git a/components/style/lib.rs b/components/style/lib.rs index 3c4241b0df9..4856610b9ed 100644 --- a/components/style/lib.rs +++ b/components/style/lib.rs @@ -101,7 +101,6 @@ pub mod context; pub mod counter_style; pub mod custom_properties; pub mod data; -pub mod document_condition; pub mod dom; pub mod element_state; #[cfg(feature = "servo")] mod encoding_support; @@ -111,7 +110,6 @@ pub mod font_metrics; #[cfg(feature = "gecko")] #[allow(unsafe_code)] pub mod gecko; #[cfg(feature = "gecko")] #[allow(unsafe_code)] pub mod gecko_bindings; pub mod invalidation; -pub mod keyframes; #[allow(missing_docs)] // TODO. pub mod logical_geometry; pub mod matching; @@ -134,14 +132,12 @@ pub mod style_adjuster; pub mod stylearc; pub mod stylesheet_set; pub mod stylesheets; -pub mod supports; pub mod thread_state; pub mod timer; pub mod traversal; #[macro_use] #[allow(non_camel_case_types)] pub mod values; -pub mod viewport; use std::fmt; use style_traits::ToCss; diff --git a/components/style/shared_lock.rs b/components/style/shared_lock.rs index 360118d472e..c87008a85a0 100644 --- a/components/style/shared_lock.rs +++ b/components/style/shared_lock.rs @@ -226,6 +226,17 @@ pub trait ToCssWithGuard { } } +/// A trait to do a deep clone of a given CSS type. Gets a lock and a read +/// guard, in order to be able to read and clone nested structures. +pub trait DeepCloneWithLock : Sized { + /// Deep clones this object. + fn deep_clone_with_lock( + &self, + lock: &SharedRwLock, + guard: &SharedRwLockReadGuard + ) -> Self; +} + /// Guards for a document #[derive(Clone)] pub struct StylesheetGuards<'a> { diff --git a/components/style/stylesheets.rs b/components/style/stylesheets.rs deleted file mode 100644 index 7196bd35034..00000000000 --- a/components/style/stylesheets.rs +++ /dev/null @@ -1,1943 +0,0 @@ -/* 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 http://mozilla.org/MPL/2.0/. */ - -//! Style sheets and their CSS rules. - -#![deny(missing_docs)] - -use {Atom, Prefix, Namespace}; -use context::QuirksMode; -use counter_style::{parse_counter_style_name, parse_counter_style_body}; -#[cfg(feature = "servo")] -use counter_style::CounterStyleRuleData; -use cssparser::{AtRuleParser, Parser, QualifiedRuleParser}; -use cssparser::{AtRuleType, RuleListParser, parse_one_rule, SourceLocation}; -use cssparser::ToCss as ParserToCss; -use document_condition::DocumentCondition; -use error_reporting::{ParseErrorReporter, NullReporter}; -#[cfg(feature = "servo")] -use font_face::FontFaceRuleData; -use font_face::parse_font_face_block; -#[cfg(feature = "gecko")] -pub use gecko::rules::{CounterStyleRule, FontFaceRule}; -#[cfg(feature = "gecko")] -use gecko_bindings::structs::URLExtraData; -#[cfg(feature = "gecko")] -use gecko_bindings::sugar::refptr::RefPtr; -use keyframes::{Keyframe, KeyframeSelector, parse_keyframe_list}; -use media_queries::{Device, MediaList, parse_media_query_list}; -use parking_lot::RwLock; -use parser::{PARSING_MODE_DEFAULT, Parse, ParserContext, log_css_error}; -use properties::{PropertyDeclarationBlock, parse_property_declaration_list}; -use selector_parser::{SelectorImpl, SelectorParser}; -use selectors::parser::SelectorList; -#[cfg(feature = "servo")] -use servo_config::prefs::PREFS; -#[cfg(not(feature = "gecko"))] -use servo_url::ServoUrl; -use shared_lock::{SharedRwLock, Locked, ToCssWithGuard, SharedRwLockReadGuard}; -use smallvec::SmallVec; -use std::{fmt, mem}; -use std::borrow::Borrow; -use std::mem::align_of; -use std::os::raw::c_void; -use std::slice; -use std::sync::atomic::{AtomicBool, Ordering}; -use str::starts_with_ignore_ascii_case; -use style_traits::ToCss; -use stylearc::Arc; -use stylist::FnvHashMap; -use supports::SupportsCondition; -use values::{CustomIdent, KeyframesName}; -use values::specified::NamespaceId; -use values::specified::url::SpecifiedUrl; -use viewport::ViewportRule; - - -/// Extra data that the backend may need to resolve url values. -#[cfg(not(feature = "gecko"))] -pub type UrlExtraData = ServoUrl; - -/// Extra data that the backend may need to resolve url values. -#[cfg(feature = "gecko")] -pub type UrlExtraData = RefPtr<URLExtraData>; - -#[cfg(feature = "gecko")] -impl UrlExtraData { - /// Returns a string for the url. - /// - /// Unimplemented currently. - pub fn as_str(&self) -> &str { - // TODO - "(stylo: not supported)" - } -} - -// XXX We probably need to figure out whether we should mark Eq here. -// It is currently marked so because properties::UnparsedValue wants Eq. -#[cfg(feature = "gecko")] -impl Eq for UrlExtraData {} - -/// Each style rule has an origin, which determines where it enters the cascade. -/// -/// http://dev.w3.org/csswg/css-cascade/#cascading-origins -#[derive(Clone, PartialEq, Eq, Copy, Debug)] -#[cfg_attr(feature = "servo", derive(HeapSizeOf))] -pub enum Origin { - /// http://dev.w3.org/csswg/css-cascade/#cascade-origin-ua - UserAgent, - - /// http://dev.w3.org/csswg/css-cascade/#cascade-origin-author - Author, - - /// http://dev.w3.org/csswg/css-cascade/#cascade-origin-user - User, -} - -/// A set of namespaces applying to a given stylesheet. -/// -/// The namespace id is used in gecko -#[derive(Clone, Default, Debug)] -#[allow(missing_docs)] -pub struct Namespaces { - pub default: Option<(Namespace, NamespaceId)>, - pub prefixes: FnvHashMap<Prefix, (Namespace, NamespaceId)>, -} - -/// Like gecko_bindings::structs::MallocSizeOf, but without the Option<> wrapper. Note that -/// functions of this type should not be called via do_malloc_size_of(), rather than directly. -pub type MallocSizeOfFn = unsafe extern "C" fn(ptr: *const c_void) -> usize; - -/// Call malloc_size_of on ptr, first checking that the allocation isn't empty. -pub unsafe fn do_malloc_size_of<T>(malloc_size_of: MallocSizeOfFn, ptr: *const T) -> usize { - if ptr as usize <= align_of::<T>() { - 0 - } else { - malloc_size_of(ptr as *const c_void) - } -} - -/// Trait for measuring the size of heap data structures. -pub trait MallocSizeOf { - /// Measure the size of any heap-allocated structures that hang off this value, but not the - /// space taken up by the value itself. - fn malloc_size_of_children(&self, malloc_size_of: MallocSizeOfFn) -> usize; -} - -/// Like MallocSizeOf, but operates with the global SharedRwLockReadGuard locked. -pub trait MallocSizeOfWithGuard { - /// Like MallocSizeOf::malloc_size_of_children, but with a |guard| argument. - fn malloc_size_of_children(&self, guard: &SharedRwLockReadGuard, - malloc_size_of: MallocSizeOfFn) -> usize; -} - -impl<A: MallocSizeOf, B: MallocSizeOf> MallocSizeOf for (A, B) { - fn malloc_size_of_children(&self, malloc_size_of: MallocSizeOfFn) -> usize { - self.0.malloc_size_of_children(malloc_size_of) + - self.1.malloc_size_of_children(malloc_size_of) - } -} - -impl<T: MallocSizeOf> MallocSizeOf for Vec<T> { - fn malloc_size_of_children(&self, malloc_size_of: MallocSizeOfFn) -> usize { - self.iter().fold( - unsafe { do_malloc_size_of(malloc_size_of, self.as_ptr()) }, - |n, elem| n + elem.malloc_size_of_children(malloc_size_of)) - } -} - -impl<T: MallocSizeOfWithGuard> MallocSizeOfWithGuard for Vec<T> { - fn malloc_size_of_children(&self, guard: &SharedRwLockReadGuard, - malloc_size_of: MallocSizeOfFn) -> usize { - self.iter().fold( - unsafe { do_malloc_size_of(malloc_size_of, self.as_ptr()) }, - |n, elem| n + elem.malloc_size_of_children(guard, malloc_size_of)) - } -} - -/// A list of CSS rules. -#[derive(Debug)] -pub struct CssRules(pub Vec<CssRule>); - -impl CssRules { - /// Whether this CSS rules is empty. - pub fn is_empty(&self) -> bool { - self.0.is_empty() - } - - /// Creates a deep clone where each CssRule has also been cloned with - /// the provided lock. - fn deep_clone_with_lock(&self, - lock: &SharedRwLock) -> CssRules { - CssRules( - self.0.iter().map(|ref x| x.deep_clone_with_lock(lock)).collect() - ) - } -} - -impl MallocSizeOfWithGuard for CssRules { - fn malloc_size_of_children(&self, guard: &SharedRwLockReadGuard, - malloc_size_of: MallocSizeOfFn) -> usize { - self.0.malloc_size_of_children(guard, malloc_size_of) - } -} - -#[allow(missing_docs)] -pub enum RulesMutateError { - Syntax, - IndexSize, - HierarchyRequest, - InvalidState, -} - -impl From<SingleRuleParseError> for RulesMutateError { - fn from(other: SingleRuleParseError) -> Self { - match other { - SingleRuleParseError::Syntax => RulesMutateError::Syntax, - SingleRuleParseError::Hierarchy => RulesMutateError::HierarchyRequest, - } - } -} - -impl CssRules { - #[allow(missing_docs)] - pub fn new(rules: Vec<CssRule>, shared_lock: &SharedRwLock) -> Arc<Locked<CssRules>> { - Arc::new(shared_lock.wrap(CssRules(rules))) - } - - fn only_ns_or_import(&self) -> bool { - self.0.iter().all(|r| { - match *r { - CssRule::Namespace(..) | - CssRule::Import(..) => true, - _ => false - } - }) - } - - /// https://drafts.csswg.org/cssom/#remove-a-css-rule - pub fn remove_rule(&mut self, index: usize) -> Result<(), RulesMutateError> { - // Step 1, 2 - if index >= self.0.len() { - return Err(RulesMutateError::IndexSize); - } - - { - // Step 3 - let ref rule = self.0[index]; - - // Step 4 - if let CssRule::Namespace(..) = *rule { - if !self.only_ns_or_import() { - return Err(RulesMutateError::InvalidState); - } - } - } - - // Step 5, 6 - self.0.remove(index); - Ok(()) - } -} - -/// A trait to implement helpers for `Arc<Locked<CssRules>>`. -pub trait CssRulesHelpers { - /// https://drafts.csswg.org/cssom/#insert-a-css-rule - /// - /// Written in this funky way because parsing an @import rule may cause us - /// to clone a stylesheet from the same document due to caching in the CSS - /// loader. - /// - /// TODO(emilio): We could also pass the write guard down into the loader - /// instead, but that seems overkill. - fn insert_rule(&self, - lock: &SharedRwLock, - rule: &str, - parent_stylesheet: &Stylesheet, - index: usize, - nested: bool, - loader: Option<&StylesheetLoader>) - -> Result<CssRule, RulesMutateError>; -} - -impl CssRulesHelpers for Arc<Locked<CssRules>> { - fn insert_rule(&self, - lock: &SharedRwLock, - rule: &str, - parent_stylesheet: &Stylesheet, - index: usize, - nested: bool, - loader: Option<&StylesheetLoader>) - -> Result<CssRule, RulesMutateError> { - let state = { - let read_guard = lock.read(); - let rules = self.read_with(&read_guard); - - // Step 1, 2 - if index > rules.0.len() { - return Err(RulesMutateError::IndexSize); - } - - // Computes the parser state at the given index - if nested { - None - } else if index == 0 { - Some(State::Start) - } else { - rules.0.get(index - 1).map(CssRule::rule_state) - } - }; - - // Step 3, 4 - // XXXManishearth should we also store the namespace map? - let (new_rule, new_state) = - try!(CssRule::parse(&rule, parent_stylesheet, state, loader)); - - { - let mut write_guard = lock.write(); - let mut rules = self.write_with(&mut write_guard); - // Step 5 - // Computes the maximum allowed parser state at a given index. - let rev_state = rules.0.get(index).map_or(State::Body, CssRule::rule_state); - if new_state > rev_state { - // We inserted a rule too early, e.g. inserting - // a regular style rule before @namespace rules - return Err(RulesMutateError::HierarchyRequest); - } - - // Step 6 - if let CssRule::Namespace(..) = new_rule { - if !rules.only_ns_or_import() { - return Err(RulesMutateError::InvalidState); - } - } - - rules.0.insert(index, new_rule.clone()); - } - - Ok(new_rule) - } -} - - -/// The structure servo uses to represent a stylesheet. -#[derive(Debug)] -pub struct Stylesheet { - /// List of rules in the order they were found (important for - /// cascading order) - pub rules: Arc<Locked<CssRules>>, - /// List of media associated with the Stylesheet. - pub media: Arc<Locked<MediaList>>, - /// The origin of this stylesheet. - pub origin: Origin, - /// The url data this stylesheet should use. - pub url_data: UrlExtraData, - /// The lock used for objects inside this stylesheet - pub shared_lock: SharedRwLock, - /// The namespaces that apply to this stylesheet. - pub namespaces: RwLock<Namespaces>, - /// Whether this stylesheet would be dirty when the viewport size changes. - pub dirty_on_viewport_size_change: AtomicBool, - /// Whether this stylesheet should be disabled. - pub disabled: AtomicBool, - /// The quirks mode of this stylesheet. - pub quirks_mode: QuirksMode, -} - -impl MallocSizeOfWithGuard for Stylesheet { - fn malloc_size_of_children(&self, guard: &SharedRwLockReadGuard, - malloc_size_of: MallocSizeOfFn) -> usize { - // Measurement of other fields may be added later. - self.rules.read_with(guard).malloc_size_of_children(guard, malloc_size_of) - } -} - -/// This structure holds the user-agent and user stylesheets. -pub struct UserAgentStylesheets { - /// The lock used for user-agent stylesheets. - pub shared_lock: SharedRwLock, - /// The user or user agent stylesheets. - pub user_or_user_agent_stylesheets: Vec<Stylesheet>, - /// The quirks mode stylesheet. - pub quirks_mode_stylesheet: Stylesheet, -} - - -/// A CSS rule. -/// -/// TODO(emilio): Lots of spec links should be around. -#[derive(Debug, Clone)] -#[allow(missing_docs)] -pub enum CssRule { - // No Charset here, CSSCharsetRule has been removed from CSSOM - // https://drafts.csswg.org/cssom/#changes-from-5-december-2013 - - Namespace(Arc<Locked<NamespaceRule>>), - Import(Arc<Locked<ImportRule>>), - Style(Arc<Locked<StyleRule>>), - Media(Arc<Locked<MediaRule>>), - FontFace(Arc<Locked<FontFaceRule>>), - CounterStyle(Arc<Locked<CounterStyleRule>>), - Viewport(Arc<Locked<ViewportRule>>), - Keyframes(Arc<Locked<KeyframesRule>>), - Supports(Arc<Locked<SupportsRule>>), - Page(Arc<Locked<PageRule>>), - Document(Arc<Locked<DocumentRule>>), -} - -impl MallocSizeOfWithGuard for CssRule { - fn malloc_size_of_children(&self, guard: &SharedRwLockReadGuard, - malloc_size_of: MallocSizeOfFn) -> usize { - match *self { - CssRule::Style(ref lock) => { - lock.read_with(guard).malloc_size_of_children(guard, malloc_size_of) - }, - // Measurement of these fields may be added later. - CssRule::Import(_) => 0, - CssRule::Media(_) => 0, - CssRule::FontFace(_) => 0, - CssRule::CounterStyle(_) => 0, - CssRule::Keyframes(_) => 0, - CssRule::Namespace(_) => 0, - CssRule::Viewport(_) => 0, - CssRule::Supports(_) => 0, - CssRule::Page(_) => 0, - CssRule::Document(_) => 0, - } - } -} - -#[allow(missing_docs)] -#[derive(PartialEq, Eq, Copy, Clone)] -pub enum CssRuleType { - // https://drafts.csswg.org/cssom/#the-cssrule-interface - Style = 1, - Charset = 2, - Import = 3, - Media = 4, - FontFace = 5, - Page = 6, - // https://drafts.csswg.org/css-animations-1/#interface-cssrule-idl - Keyframes = 7, - Keyframe = 8, - // https://drafts.csswg.org/cssom/#the-cssrule-interface - Margin = 9, - Namespace = 10, - // https://drafts.csswg.org/css-counter-styles-3/#extentions-to-cssrule-interface - CounterStyle = 11, - // https://drafts.csswg.org/css-conditional-3/#extentions-to-cssrule-interface - Supports = 12, - // https://www.w3.org/TR/2012/WD-css3-conditional-20120911/#extentions-to-cssrule-interface - Document = 13, - // https://drafts.csswg.org/css-fonts-3/#om-fontfeaturevalues - FontFeatureValues = 14, - // https://drafts.csswg.org/css-device-adapt/#css-rule-interface - Viewport = 15, -} - -#[allow(missing_docs)] -pub enum SingleRuleParseError { - Syntax, - Hierarchy, -} - -impl CssRule { - #[allow(missing_docs)] - pub fn rule_type(&self) -> CssRuleType { - match *self { - CssRule::Style(_) => CssRuleType::Style, - CssRule::Import(_) => CssRuleType::Import, - CssRule::Media(_) => CssRuleType::Media, - CssRule::FontFace(_) => CssRuleType::FontFace, - CssRule::CounterStyle(_) => CssRuleType::CounterStyle, - CssRule::Keyframes(_) => CssRuleType::Keyframes, - CssRule::Namespace(_) => CssRuleType::Namespace, - CssRule::Viewport(_) => CssRuleType::Viewport, - CssRule::Supports(_) => CssRuleType::Supports, - CssRule::Page(_) => CssRuleType::Page, - CssRule::Document(_) => CssRuleType::Document, - } - } - - fn rule_state(&self) -> State { - match *self { - // CssRule::Charset(..) => State::Start, - CssRule::Import(..) => State::Imports, - CssRule::Namespace(..) => State::Namespaces, - _ => State::Body, - } - } - - // input state is None for a nested rule - // Returns a parsed CSS rule and the final state of the parser - #[allow(missing_docs)] - pub fn parse(css: &str, - parent_stylesheet: &Stylesheet, - state: Option<State>, - loader: Option<&StylesheetLoader>) - -> Result<(Self, State), SingleRuleParseError> { - let error_reporter = NullReporter; - let context = ParserContext::new( - parent_stylesheet.origin, - &parent_stylesheet.url_data, - &error_reporter, - None, - PARSING_MODE_DEFAULT, - parent_stylesheet.quirks_mode - ); - - let mut input = Parser::new(css); - - let mut guard = parent_stylesheet.namespaces.write(); - - // nested rules are in the body state - let state = state.unwrap_or(State::Body); - let mut rule_parser = TopLevelRuleParser { - stylesheet_origin: parent_stylesheet.origin, - context: context, - shared_lock: &parent_stylesheet.shared_lock, - loader: loader, - state: state, - namespaces: Some(&mut *guard), - }; - match parse_one_rule(&mut input, &mut rule_parser) { - Ok(result) => Ok((result, rule_parser.state)), - Err(_) => { - if let State::Invalid = rule_parser.state { - Err(SingleRuleParseError::Hierarchy) - } else { - Err(SingleRuleParseError::Syntax) - } - } - } - } - - /// Deep clones this CssRule. - fn deep_clone_with_lock( - &self, - lock: &SharedRwLock - ) -> CssRule { - let guard = lock.read(); - match *self { - CssRule::Namespace(ref arc) => { - let rule = arc.read_with(&guard); - CssRule::Namespace(Arc::new(lock.wrap(rule.clone()))) - }, - CssRule::Import(ref arc) => { - let rule = arc.read_with(&guard); - CssRule::Import(Arc::new(lock.wrap(rule.clone()))) - }, - CssRule::Style(ref arc) => { - let rule = arc.read_with(&guard); - CssRule::Style(Arc::new( - lock.wrap(rule.deep_clone_with_lock(lock)))) - }, - CssRule::Media(ref arc) => { - let rule = arc.read_with(&guard); - CssRule::Media(Arc::new( - lock.wrap(rule.deep_clone_with_lock(lock)))) - }, - CssRule::FontFace(ref arc) => { - let rule = arc.read_with(&guard); - CssRule::FontFace(Arc::new(lock.wrap(rule.clone()))) - }, - CssRule::CounterStyle(ref arc) => { - let rule = arc.read_with(&guard); - CssRule::CounterStyle(Arc::new(lock.wrap(rule.clone()))) - }, - CssRule::Viewport(ref arc) => { - let rule = arc.read_with(&guard); - CssRule::Viewport(Arc::new(lock.wrap(rule.clone()))) - }, - CssRule::Keyframes(ref arc) => { - let rule = arc.read_with(&guard); - CssRule::Keyframes(Arc::new( - lock.wrap(rule.deep_clone_with_lock(lock)))) - }, - CssRule::Supports(ref arc) => { - let rule = arc.read_with(&guard); - CssRule::Supports(Arc::new( - lock.wrap(rule.deep_clone_with_lock(lock)))) - }, - CssRule::Page(ref arc) => { - let rule = arc.read_with(&guard); - CssRule::Page(Arc::new( - lock.wrap(rule.deep_clone_with_lock(lock)))) - }, - CssRule::Document(ref arc) => { - let rule = arc.read_with(&guard); - CssRule::Document(Arc::new( - lock.wrap(rule.deep_clone_with_lock(lock)))) - }, - } - } -} - -impl ToCssWithGuard for CssRule { - // https://drafts.csswg.org/cssom/#serialize-a-css-rule - fn to_css<W>(&self, guard: &SharedRwLockReadGuard, dest: &mut W) -> fmt::Result - where W: fmt::Write { - match *self { - CssRule::Namespace(ref lock) => lock.read_with(guard).to_css(guard, dest), - CssRule::Import(ref lock) => lock.read_with(guard).to_css(guard, dest), - CssRule::Style(ref lock) => lock.read_with(guard).to_css(guard, dest), - CssRule::FontFace(ref lock) => lock.read_with(guard).to_css(guard, dest), - CssRule::CounterStyle(ref lock) => lock.read_with(guard).to_css(guard, dest), - CssRule::Viewport(ref lock) => lock.read_with(guard).to_css(guard, dest), - CssRule::Keyframes(ref lock) => lock.read_with(guard).to_css(guard, dest), - CssRule::Media(ref lock) => lock.read_with(guard).to_css(guard, dest), - CssRule::Supports(ref lock) => lock.read_with(guard).to_css(guard, dest), - CssRule::Page(ref lock) => lock.read_with(guard).to_css(guard, dest), - CssRule::Document(ref lock) => lock.read_with(guard).to_css(guard, dest), - } - } -} - -/// Calculates the location of a rule's source given an offset. -fn get_location_with_offset(location: SourceLocation, offset: u64) - -> SourceLocation { - SourceLocation { - line: location.line + offset as usize - 1, - column: location.column, - } -} - -#[derive(Clone, Debug, PartialEq)] -#[allow(missing_docs)] -pub struct NamespaceRule { - /// `None` for the default Namespace - pub prefix: Option<Prefix>, - pub url: Namespace, - pub source_location: SourceLocation, -} - -impl ToCssWithGuard for NamespaceRule { - // https://drafts.csswg.org/cssom/#serialize-a-css-rule CSSNamespaceRule - fn to_css<W>(&self, _guard: &SharedRwLockReadGuard, dest: &mut W) -> fmt::Result - where W: fmt::Write { - try!(dest.write_str("@namespace ")); - if let Some(ref prefix) = self.prefix { - try!(dest.write_str(&*prefix.to_string())); - try!(dest.write_str(" ")); - } - try!(dest.write_str("url(\"")); - try!(dest.write_str(&*self.url.to_string())); - dest.write_str("\");") - } -} - - -/// The [`@import`][import] at-rule. -/// -/// [import]: https://drafts.csswg.org/css-cascade-3/#at-import -#[derive(Debug)] -pub struct ImportRule { - /// The `<url>` this `@import` rule is loading. - pub url: SpecifiedUrl, - - /// The stylesheet is always present. - /// - /// It contains an empty list of rules and namespace set that is updated - /// when it loads. - pub stylesheet: Arc<Stylesheet>, - - /// The line and column of the rule's source code. - pub source_location: SourceLocation, -} - -impl Clone for ImportRule { - fn clone(&self) -> ImportRule { - let stylesheet: &Stylesheet = self.stylesheet.borrow(); - ImportRule { - url: self.url.clone(), - stylesheet: Arc::new(stylesheet.clone()), - source_location: self.source_location.clone(), - } - } -} - -impl ToCssWithGuard for ImportRule { - fn to_css<W>(&self, guard: &SharedRwLockReadGuard, dest: &mut W) -> fmt::Result - where W: fmt::Write { - try!(dest.write_str("@import ")); - try!(self.url.to_css(dest)); - let media = self.stylesheet.media.read_with(guard); - if !media.is_empty() { - try!(dest.write_str(" ")); - try!(media.to_css(dest)); - } - dest.write_str(";") - } -} - -/// A [`@keyframes`][keyframes] rule. -/// -/// [keyframes]: https://drafts.csswg.org/css-animations/#keyframes -#[derive(Debug)] -pub struct KeyframesRule { - /// The name of the current animation. - pub name: KeyframesName, - /// The keyframes specified for this CSS rule. - pub keyframes: Vec<Arc<Locked<Keyframe>>>, - /// Vendor prefix type the @keyframes has. - pub vendor_prefix: Option<VendorPrefix>, - /// The line and column of the rule's source code. - pub source_location: SourceLocation, -} - -impl ToCssWithGuard for KeyframesRule { - // Serialization of KeyframesRule is not specced. - fn to_css<W>(&self, guard: &SharedRwLockReadGuard, dest: &mut W) -> fmt::Result - where W: fmt::Write { - try!(dest.write_str("@keyframes ")); - try!(self.name.to_css(dest)); - try!(dest.write_str(" {")); - let iter = self.keyframes.iter(); - for lock in iter { - try!(dest.write_str("\n")); - let keyframe = lock.read_with(&guard); - try!(keyframe.to_css(guard, dest)); - } - dest.write_str("\n}") - } -} - -impl KeyframesRule { - /// Returns the index of the last keyframe that matches the given selector. - /// If the selector is not valid, or no keyframe is found, returns None. - /// - /// Related spec: - /// https://drafts.csswg.org/css-animations-1/#interface-csskeyframesrule-findrule - pub fn find_rule(&self, guard: &SharedRwLockReadGuard, selector: &str) -> Option<usize> { - if let Ok(selector) = Parser::new(selector).parse_entirely(KeyframeSelector::parse) { - for (i, keyframe) in self.keyframes.iter().enumerate().rev() { - if keyframe.read_with(guard).selector == selector { - return Some(i); - } - } - } - None - } -} - -impl KeyframesRule { - /// Deep clones this KeyframesRule. - fn deep_clone_with_lock(&self, - lock: &SharedRwLock) -> KeyframesRule { - let guard = lock.read(); - KeyframesRule { - name: self.name.clone(), - keyframes: self.keyframes.iter() - .map(|ref x| Arc::new(lock.wrap( - x.read_with(&guard).deep_clone_with_lock(lock)))) - .collect(), - vendor_prefix: self.vendor_prefix.clone(), - source_location: self.source_location.clone(), - } - } -} - -#[allow(missing_docs)] -#[derive(Debug)] -pub struct MediaRule { - pub media_queries: Arc<Locked<MediaList>>, - pub rules: Arc<Locked<CssRules>>, - pub source_location: SourceLocation, -} - -impl ToCssWithGuard for MediaRule { - // Serialization of MediaRule is not specced. - // https://drafts.csswg.org/cssom/#serialize-a-css-rule CSSMediaRule - fn to_css<W>(&self, guard: &SharedRwLockReadGuard, dest: &mut W) -> fmt::Result - where W: fmt::Write { - try!(dest.write_str("@media ")); - try!(self.media_queries.read_with(guard).to_css(dest)); - try!(dest.write_str(" {")); - for rule in self.rules.read_with(guard).0.iter() { - try!(dest.write_str(" ")); - try!(rule.to_css(guard, dest)); - } - dest.write_str(" }") - } -} - -impl MediaRule { - /// Deep clones this MediaRule. - fn deep_clone_with_lock(&self, - lock: &SharedRwLock) -> MediaRule { - let guard = lock.read(); - let media_queries = self.media_queries.read_with(&guard); - let rules = self.rules.read_with(&guard); - MediaRule { - media_queries: Arc::new(lock.wrap(media_queries.clone())), - rules: Arc::new(lock.wrap(rules.deep_clone_with_lock(lock))), - source_location: self.source_location.clone(), - } - } -} - - -#[derive(Debug)] -/// An @supports rule -pub struct SupportsRule { - /// The parsed condition - pub condition: SupportsCondition, - /// Child rules - pub rules: Arc<Locked<CssRules>>, - /// The result of evaluating the condition - pub enabled: bool, - /// The line and column of the rule's source code. - pub source_location: SourceLocation, -} - -impl ToCssWithGuard for SupportsRule { - fn to_css<W>(&self, guard: &SharedRwLockReadGuard, dest: &mut W) -> fmt::Result - where W: fmt::Write { - try!(dest.write_str("@supports ")); - try!(self.condition.to_css(dest)); - try!(dest.write_str(" {")); - for rule in self.rules.read_with(guard).0.iter() { - try!(dest.write_str(" ")); - try!(rule.to_css(guard, dest)); - } - dest.write_str(" }") - } -} - -impl SupportsRule { - fn deep_clone_with_lock(&self, - lock: &SharedRwLock) -> SupportsRule { - let guard = lock.read(); - let rules = self.rules.read_with(&guard); - SupportsRule { - condition: self.condition.clone(), - rules: Arc::new(lock.wrap(rules.deep_clone_with_lock(lock))), - enabled: self.enabled, - source_location: self.source_location.clone(), - } - } -} - -/// A [`@page`][page] rule. This implements only a limited subset of the CSS 2.2 syntax. In this -/// subset, [page selectors][page-selectors] are not implemented. -/// -/// [page]: https://drafts.csswg.org/css2/page.html#page-box -/// [page-selectors]: https://drafts.csswg.org/css2/page.html#page-selectors -#[allow(missing_docs)] -#[derive(Debug)] -pub struct PageRule { - pub block: Arc<Locked<PropertyDeclarationBlock>>, - pub source_location: SourceLocation, -} - -impl ToCssWithGuard for PageRule { - // Serialization of PageRule is not specced, adapted from steps for StyleRule. - fn to_css<W>(&self, guard: &SharedRwLockReadGuard, dest: &mut W) -> fmt::Result - where W: fmt::Write { - dest.write_str("@page { ")?; - let declaration_block = self.block.read_with(guard); - declaration_block.to_css(dest)?; - if declaration_block.declarations().len() > 0 { - write!(dest, " ")?; - } - dest.write_str("}") - } -} - -impl PageRule { - fn deep_clone_with_lock(&self, - lock: &SharedRwLock) -> PageRule { - let guard = lock.read(); - PageRule { - block: Arc::new(lock.wrap(self.block.read_with(&guard).clone())), - source_location: self.source_location.clone(), - } - } -} - -#[allow(missing_docs)] -#[derive(Debug)] -pub struct StyleRule { - pub selectors: SelectorList<SelectorImpl>, - pub block: Arc<Locked<PropertyDeclarationBlock>>, - pub source_location: SourceLocation, -} - -impl MallocSizeOfWithGuard for StyleRule { - fn malloc_size_of_children(&self, guard: &SharedRwLockReadGuard, - malloc_size_of: MallocSizeOfFn) -> usize { - // Measurement of other fields may be added later. - self.block.read_with(guard).malloc_size_of_children(malloc_size_of) - } -} - -impl ToCssWithGuard for StyleRule { - // https://drafts.csswg.org/cssom/#serialize-a-css-rule CSSStyleRule - fn to_css<W>(&self, guard: &SharedRwLockReadGuard, dest: &mut W) -> fmt::Result - where W: fmt::Write { - // Step 1 - try!(self.selectors.to_css(dest)); - // Step 2 - try!(dest.write_str(" { ")); - // Step 3 - let declaration_block = self.block.read_with(guard); - try!(declaration_block.to_css(dest)); - // Step 4 - if declaration_block.declarations().len() > 0 { - try!(write!(dest, " ")); - } - // Step 5 - try!(dest.write_str("}")); - Ok(()) - } -} - -impl StyleRule { - /// Deep clones this StyleRule. - fn deep_clone_with_lock(&self, - lock: &SharedRwLock) -> StyleRule { - let guard = lock.read(); - StyleRule { - selectors: self.selectors.clone(), - block: Arc::new(lock.wrap(self.block.read_with(&guard).clone())), - source_location: self.source_location.clone(), - } - } -} - -/// A @font-face rule -#[cfg(feature = "servo")] -pub type FontFaceRule = FontFaceRuleData; - -/// A @counter-style rule -#[cfg(feature = "servo")] -pub type CounterStyleRule = CounterStyleRuleData; - -#[derive(Debug)] -/// A @-moz-document rule -pub struct DocumentRule { - /// The parsed condition - pub condition: DocumentCondition, - /// Child rules - pub rules: Arc<Locked<CssRules>>, - /// The line and column of the rule's source code. - pub source_location: SourceLocation, -} - -impl ToCssWithGuard for DocumentRule { - fn to_css<W>(&self, guard: &SharedRwLockReadGuard, dest: &mut W) -> fmt::Result - where W: fmt::Write { - try!(dest.write_str("@-moz-document ")); - try!(self.condition.to_css(dest)); - try!(dest.write_str(" {")); - for rule in self.rules.read_with(guard).0.iter() { - try!(dest.write_str(" ")); - try!(rule.to_css(guard, dest)); - } - dest.write_str(" }") - } -} - -impl DocumentRule { - /// Deep clones this DocumentRule. - fn deep_clone_with_lock(&self, - lock: &SharedRwLock) -> DocumentRule { - let guard = lock.read(); - let rules = self.rules.read_with(&guard); - DocumentRule { - condition: self.condition.clone(), - rules: Arc::new(lock.wrap(rules.deep_clone_with_lock(lock))), - source_location: self.source_location.clone(), - } - } -} - -/// A trait that describes statically which rules are iterated for a given -/// 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 NestedRuleIterationCondition for EffectiveRules { - fn process_import( - guard: &SharedRwLockReadGuard, - device: &Device, - quirks_mode: QuirksMode, - rule: &ImportRule) - -> bool - { - rule.stylesheet.media.read_with(guard).evaluate(device, quirks_mode) - } - - 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>; - -/// 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<C>, -} - -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: &'a CssRules) - -> Self - { - let mut stack = SmallVec::new(); - stack.push(rules.0.iter()); - Self { - device: device, - quirks_mode: quirks_mode, - guard: guard, - stack: 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(); - } -} - -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<Self::Item> { - let mut nested_iter_finished = false; - while !self.stack.is_empty() { - if nested_iter_finished { - self.stack.pop(); - nested_iter_finished = false; - continue; - } - - let rule; - let sub_iter; - { - let mut nested_iter = self.stack.last_mut().unwrap(); - rule = match nested_iter.next() { - Some(r) => r, - None => { - nested_iter_finished = true; - continue - } - }; - - sub_iter = match *rule { - CssRule::Import(ref import_rule) => { - let import_rule = import_rule.read_with(self.guard); - if !C::process_import(self.guard, self.device, self.quirks_mode, import_rule) { - continue; - } - Some(import_rule.stylesheet.rules.read_with(self.guard).0.iter()) - } - CssRule::Document(ref doc_rule) => { - let doc_rule = doc_rule.read_with(self.guard); - if !C::process_document(self.guard, self.device, self.quirks_mode, doc_rule) { - continue; - } - Some(doc_rule.rules.read_with(self.guard).0.iter()) - } - CssRule::Media(ref lock) => { - let media_rule = lock.read_with(self.guard); - if !C::process_media(self.guard, self.device, self.quirks_mode, media_rule) { - continue; - } - Some(media_rule.rules.read_with(self.guard).0.iter()) - } - CssRule::Supports(ref lock) => { - let supports_rule = lock.read_with(self.guard); - if !C::process_supports(self.guard, self.device, self.quirks_mode, supports_rule) { - continue; - } - Some(supports_rule.rules.read_with(self.guard).0.iter()) - } - CssRule::Namespace(_) | - CssRule::Style(_) | - CssRule::FontFace(_) | - CssRule::CounterStyle(_) | - CssRule::Viewport(_) | - CssRule::Keyframes(_) | - CssRule::Page(_) => None, - }; - } - - if let Some(sub_iter) = sub_iter { - self.stack.push(sub_iter); - } - - return Some(rule); - } - - None - } -} - -impl Stylesheet { - /// Updates an empty stylesheet from a given string of text. - pub fn update_from_str(existing: &Stylesheet, - css: &str, - url_data: &UrlExtraData, - stylesheet_loader: Option<&StylesheetLoader>, - error_reporter: &ParseErrorReporter, - line_number_offset: u64) { - let namespaces = RwLock::new(Namespaces::default()); - // FIXME: we really should update existing.url_data with the given url_data, - // otherwise newly inserted rule may not have the right base url. - let (rules, dirty_on_viewport_size_change) = - Stylesheet::parse_rules( - css, - url_data, - existing.origin, - &mut *namespaces.write(), - &existing.shared_lock, - stylesheet_loader, - error_reporter, - existing.quirks_mode, - line_number_offset - ); - - mem::swap(&mut *existing.namespaces.write(), &mut *namespaces.write()); - existing.dirty_on_viewport_size_change - .store(dirty_on_viewport_size_change, Ordering::Release); - - // Acquire the lock *after* parsing, to minimize the exclusive section. - let mut guard = existing.shared_lock.write(); - *existing.rules.write_with(&mut guard) = CssRules(rules); - } - - fn parse_rules( - css: &str, - url_data: &UrlExtraData, - origin: Origin, - namespaces: &mut Namespaces, - shared_lock: &SharedRwLock, - stylesheet_loader: Option<&StylesheetLoader>, - error_reporter: &ParseErrorReporter, - quirks_mode: QuirksMode, - line_number_offset: u64 - ) -> (Vec<CssRule>, bool) { - let mut rules = Vec::new(); - let mut input = Parser::new(css); - - let context = - ParserContext::new_with_line_number_offset( - origin, - url_data, - error_reporter, - line_number_offset, - PARSING_MODE_DEFAULT, - quirks_mode - ); - - let rule_parser = TopLevelRuleParser { - stylesheet_origin: origin, - shared_lock: shared_lock, - loader: stylesheet_loader, - context: context, - state: State::Start, - namespaces: Some(namespaces), - }; - - input.look_for_viewport_percentages(); - - { - let mut iter = - RuleListParser::new_for_stylesheet(&mut input, rule_parser); - - while let Some(result) = iter.next() { - match result { - Ok(rule) => rules.push(rule), - Err(range) => { - let pos = range.start; - let message = format!("Invalid rule: '{}'", iter.input.slice(range)); - log_css_error(iter.input, pos, &*message, &iter.parser.context); - } - } - } - } - - (rules, input.seen_viewport_percentages()) - } - - /// Creates an empty stylesheet and parses it with a given base url, origin - /// and media. - /// - /// Effectively creates a new stylesheet and forwards the hard work to - /// `Stylesheet::update_from_str`. - pub fn from_str(css: &str, - url_data: UrlExtraData, - origin: Origin, - media: Arc<Locked<MediaList>>, - shared_lock: SharedRwLock, - stylesheet_loader: Option<&StylesheetLoader>, - error_reporter: &ParseErrorReporter, - quirks_mode: QuirksMode, - line_number_offset: u64) - -> Stylesheet { - let namespaces = RwLock::new(Namespaces::default()); - let (rules, dirty_on_viewport_size_change) = Stylesheet::parse_rules( - css, - &url_data, - origin, - &mut *namespaces.write(), - &shared_lock, - stylesheet_loader, - error_reporter, - quirks_mode, - line_number_offset, - ); - - Stylesheet { - origin: origin, - url_data: url_data, - namespaces: namespaces, - rules: CssRules::new(rules, &shared_lock), - media: media, - shared_lock: shared_lock, - dirty_on_viewport_size_change: AtomicBool::new(dirty_on_viewport_size_change), - disabled: AtomicBool::new(false), - quirks_mode: quirks_mode, - } - } - - /// Whether this stylesheet can be dirty on viewport size change. - pub fn dirty_on_viewport_size_change(&self) -> bool { - self.dirty_on_viewport_size_change.load(Ordering::SeqCst) - } - - /// When CSSOM inserts a rule or declaration into this stylesheet, it needs to call this method - /// with the return value of `cssparser::Parser::seen_viewport_percentages`. - /// - /// FIXME: actually make these calls - /// - /// Note: when *removing* a rule or declaration that contains a viewport percentage, - /// to keep the flag accurate we’d need to iterator through the rest of the stylesheet to - /// check for *other* such values. - /// - /// Instead, we conservatively assume there might be some. - /// Restyling will some some more work than necessary, but give correct results. - pub fn inserted_has_viewport_percentages(&self, has_viewport_percentages: bool) { - self.dirty_on_viewport_size_change.fetch_or(has_viewport_percentages, Ordering::SeqCst); - } - - /// Returns whether the style-sheet applies for the current device depending - /// on the associated MediaList. - /// - /// Always true if no associated MediaList exists. - pub fn is_effective_for_device(&self, device: &Device, guard: &SharedRwLockReadGuard) -> bool { - self.media.read_with(guard).evaluate(device, self.quirks_mode) - } - - /// Return an iterator over the effective rules within the style-sheet, as - /// according to the supplied `Device`. - #[inline] - pub fn effective_rules<'a, 'b>( - &'a self, - device: &'a Device, - guard: &'a SharedRwLockReadGuard<'b>) - -> EffectiveRulesIterator<'a, 'b> - { - self.iter_rules::<'a, 'b, EffectiveRules>(device, guard) - } - - /// Return an iterator using the condition `C`. - #[inline] - pub fn iter_rules<'a, 'b, C>( - &'a self, - device: &'a Device, - guard: &'a SharedRwLockReadGuard<'b>) - -> RulesIterator<'a, 'b, C> - where C: NestedRuleIterationCondition, - { - RulesIterator::new( - device, - self.quirks_mode, - guard, - &self.rules.read_with(guard)) - } - - /// Returns whether the stylesheet has been explicitly disabled through the - /// CSSOM. - pub fn disabled(&self) -> bool { - self.disabled.load(Ordering::SeqCst) - } - - /// Records that the stylesheet has been explicitly disabled through the - /// CSSOM. - /// - /// Returns whether the the call resulted in a change in disabled state. - /// - /// Disabled stylesheets remain in the document, but their rules are not - /// added to the Stylist. - pub fn set_disabled(&self, disabled: bool) -> bool { - self.disabled.swap(disabled, Ordering::SeqCst) != disabled - } -} - -impl Clone for Stylesheet { - fn clone(&self) -> Stylesheet { - // Create a new lock for our clone. - let lock = self.shared_lock.clone(); - let guard = self.shared_lock.read(); - - // Make a deep clone of the rules, using the new lock. - let rules = self.rules.read_with(&guard); - let cloned_rules = rules.deep_clone_with_lock(&lock); - - // Make a deep clone of the media, using the new lock. - let media = self.media.read_with(&guard); - let cloned_media = media.clone(); - - Stylesheet { - rules: Arc::new(lock.wrap(cloned_rules)), - media: Arc::new(lock.wrap(cloned_media)), - origin: self.origin, - url_data: self.url_data.clone(), - shared_lock: lock, - namespaces: RwLock::new((*self.namespaces.read()).clone()), - dirty_on_viewport_size_change: AtomicBool::new( - self.dirty_on_viewport_size_change.load(Ordering::SeqCst)), - disabled: AtomicBool::new(self.disabled.load(Ordering::SeqCst)), - quirks_mode: self.quirks_mode, - } - } -} - -macro_rules! rule_filter { - ($( $method: ident($variant:ident => $rule_type: ident), )+) => { - impl Stylesheet { - $( - #[allow(missing_docs)] - pub fn $method<F>(&self, device: &Device, guard: &SharedRwLockReadGuard, mut f: F) - where F: FnMut(&$rule_type), - { - for rule in self.effective_rules(device, guard) { - if let CssRule::$variant(ref lock) = *rule { - let rule = lock.read_with(guard); - f(&rule) - } - } - } - )+ - } - } -} - -rule_filter! { - effective_style_rules(Style => StyleRule), - effective_media_rules(Media => MediaRule), - effective_font_face_rules(FontFace => FontFaceRule), - effective_counter_style_rules(CounterStyle => CounterStyleRule), - effective_viewport_rules(Viewport => ViewportRule), - effective_keyframes_rules(Keyframes => KeyframesRule), - effective_supports_rules(Supports => SupportsRule), - effective_page_rules(Page => PageRule), - effective_document_rules(Document => DocumentRule), -} - -/// The stylesheet loader is the abstraction used to trigger network requests -/// for `@import` rules. -pub trait StylesheetLoader { - /// Request a stylesheet after parsing a given `@import` rule. - /// - /// The called code is responsible to update the `stylesheet` rules field - /// when the sheet is done loading. - /// - /// The convoluted signature allows impls to look at MediaList and ImportRule - /// before they’re locked, while keeping the trait object-safe. - fn request_stylesheet( - &self, - media: Arc<Locked<MediaList>>, - make_import: &mut FnMut(Arc<Locked<MediaList>>) -> ImportRule, - make_arc: &mut FnMut(ImportRule) -> Arc<Locked<ImportRule>>, - ) -> Arc<Locked<ImportRule>>; -} - -struct NoOpLoader; - -impl StylesheetLoader for NoOpLoader { - fn request_stylesheet( - &self, - media: Arc<Locked<MediaList>>, - make_import: &mut FnMut(Arc<Locked<MediaList>>) -> ImportRule, - make_arc: &mut FnMut(ImportRule) -> Arc<Locked<ImportRule>>, - ) -> Arc<Locked<ImportRule>> { - make_arc(make_import(media)) - } -} - - -struct TopLevelRuleParser<'a> { - stylesheet_origin: Origin, - shared_lock: &'a SharedRwLock, - loader: Option<&'a StylesheetLoader>, - context: ParserContext<'a>, - state: State, - namespaces: Option<&'a mut Namespaces>, -} - -impl<'b> TopLevelRuleParser<'b> { - fn nested<'a: 'b>(&'a self) -> NestedRuleParser<'a, 'b> { - NestedRuleParser { - stylesheet_origin: self.stylesheet_origin, - shared_lock: self.shared_lock, - context: &self.context, - } - } -} - -#[derive(Eq, PartialEq, Ord, PartialOrd, Copy, Clone)] -#[allow(missing_docs)] -pub enum State { - Start = 1, - Imports = 2, - Namespaces = 3, - Body = 4, - Invalid = 5, -} - -#[derive(Clone, Debug)] -#[cfg_attr(feature = "servo", derive(HeapSizeOf))] -/// Vendor prefix. -pub enum VendorPrefix { - /// -moz prefix. - Moz, - /// -webkit prefix. - WebKit, -} - -enum AtRulePrelude { - /// A @font-face rule prelude. - FontFace(SourceLocation), - /// A @counter-style rule prelude, with its counter style name. - CounterStyle(CustomIdent), - /// A @media rule prelude, with its media queries. - Media(Arc<Locked<MediaList>>, SourceLocation), - /// An @supports rule, with its conditional - Supports(SupportsCondition, SourceLocation), - /// A @viewport rule prelude. - Viewport, - /// A @keyframes rule, with its animation name and vendor prefix if exists. - Keyframes(KeyframesName, Option<VendorPrefix>, SourceLocation), - /// A @page rule prelude. - Page(SourceLocation), - /// A @document rule, with its conditional. - Document(DocumentCondition, SourceLocation), -} - - -#[cfg(feature = "gecko")] -fn register_namespace(ns: &Namespace) -> Result<i32, ()> { - let id = unsafe { ::gecko_bindings::bindings::Gecko_RegisterNamespace(ns.0.as_ptr()) }; - if id == -1 { - Err(()) - } else { - Ok(id) - } -} - -#[cfg(feature = "servo")] -fn register_namespace(_: &Namespace) -> Result<(), ()> { - Ok(()) // servo doesn't use namespace ids -} - -impl<'a> AtRuleParser for TopLevelRuleParser<'a> { - type Prelude = AtRulePrelude; - type AtRule = CssRule; - - fn parse_prelude( - &mut self, - name: &str, - input: &mut Parser - ) -> Result<AtRuleType<AtRulePrelude, CssRule>, ()> { - let location = get_location_with_offset(input.current_source_location(), - self.context.line_number_offset); - match_ignore_ascii_case! { name, - "import" => { - if self.state > State::Imports { - self.state = State::Invalid; - return Err(()) // "@import must be before any rule but @charset" - } - - self.state = State::Imports; - let url_string = input.expect_url_or_string()?; - let specified_url = SpecifiedUrl::parse_from_string(url_string, &self.context)?; - - let media = parse_media_query_list(&self.context, input); - let media = Arc::new(self.shared_lock.wrap(media)); - - let noop_loader = NoOpLoader; - let loader = if !specified_url.is_invalid() { - self.loader.expect("Expected a stylesheet loader for @import") - } else { - &noop_loader - }; - - let mut specified_url = Some(specified_url); - let arc = loader.request_stylesheet(media, &mut |media| { - ImportRule { - url: specified_url.take().unwrap(), - stylesheet: Arc::new(Stylesheet { - rules: CssRules::new(Vec::new(), self.shared_lock), - media: media, - shared_lock: self.shared_lock.clone(), - origin: self.context.stylesheet_origin, - url_data: self.context.url_data.clone(), - namespaces: RwLock::new(Namespaces::default()), - dirty_on_viewport_size_change: AtomicBool::new(false), - disabled: AtomicBool::new(false), - quirks_mode: self.context.quirks_mode, - }), - source_location: location, - } - }, &mut |import_rule| { - Arc::new(self.shared_lock.wrap(import_rule)) - }); - - return Ok(AtRuleType::WithoutBlock(CssRule::Import(arc))) - }, - "namespace" => { - if self.state > State::Namespaces { - self.state = State::Invalid; - return Err(()) // "@namespace must be before any rule but @charset and @import" - } - self.state = State::Namespaces; - - let prefix_result = input.try(|input| input.expect_ident()); - let url = Namespace::from(try!(input.expect_url_or_string())); - - let id = register_namespace(&url)?; - - let mut namespaces = self.namespaces.as_mut().unwrap(); - - let opt_prefix = if let Ok(prefix) = prefix_result { - let prefix = Prefix::from(prefix); - namespaces - .prefixes - .insert(prefix.clone(), (url.clone(), id)); - Some(prefix) - } else { - namespaces.default = Some((url.clone(), id)); - None - }; - - return Ok(AtRuleType::WithoutBlock(CssRule::Namespace(Arc::new( - self.shared_lock.wrap(NamespaceRule { - prefix: opt_prefix, - url: url, - source_location: location, - }) - )))) - }, - // @charset is removed by rust-cssparser if it’s the first rule in the stylesheet - // anything left is invalid. - "charset" => return Err(()), // (insert appropriate error message) - _ => {} - } - // Don't allow starting with an invalid state - if self.state > State::Body { - self.state = State::Invalid; - return Err(()); - } - self.state = State::Body; - - // "Freeze" the namespace map (no more namespace rules can be parsed - // after this point), and stick it in the context. - if self.namespaces.is_some() { - let namespaces = &*self.namespaces.take().unwrap(); - self.context.namespaces = Some(namespaces); - } - AtRuleParser::parse_prelude(&mut self.nested(), name, input) - } - - #[inline] - fn parse_block(&mut self, prelude: AtRulePrelude, input: &mut Parser) -> Result<CssRule, ()> { - AtRuleParser::parse_block(&mut self.nested(), prelude, input) - } -} - - -impl<'a> QualifiedRuleParser for TopLevelRuleParser<'a> { - type Prelude = SelectorList<SelectorImpl>; - type QualifiedRule = CssRule; - - #[inline] - fn parse_prelude(&mut self, input: &mut Parser) -> Result<SelectorList<SelectorImpl>, ()> { - self.state = State::Body; - - // "Freeze" the namespace map (no more namespace rules can be parsed - // after this point), and stick it in the context. - if self.namespaces.is_some() { - let namespaces = &*self.namespaces.take().unwrap(); - self.context.namespaces = Some(namespaces); - } - - QualifiedRuleParser::parse_prelude(&mut self.nested(), input) - } - - #[inline] - fn parse_block( - &mut self, - prelude: SelectorList<SelectorImpl>, - input: &mut Parser - ) -> Result<CssRule, ()> { - QualifiedRuleParser::parse_block(&mut self.nested(), prelude, input) - } -} - -#[derive(Clone)] // shallow, relatively cheap .clone -struct NestedRuleParser<'a, 'b: 'a> { - stylesheet_origin: Origin, - shared_lock: &'a SharedRwLock, - context: &'a ParserContext<'b>, -} - -impl<'a, 'b> NestedRuleParser<'a, 'b> { - fn parse_nested_rules( - &mut self, - input: &mut Parser, - rule_type: CssRuleType - ) -> Arc<Locked<CssRules>> { - let context = ParserContext::new_with_rule_type(self.context, Some(rule_type)); - - let nested_parser = NestedRuleParser { - stylesheet_origin: self.stylesheet_origin, - shared_lock: self.shared_lock, - context: &context, - }; - - let mut iter = RuleListParser::new_for_nested_rule(input, nested_parser); - let mut rules = Vec::new(); - while let Some(result) = iter.next() { - match result { - Ok(rule) => rules.push(rule), - Err(range) => { - let pos = range.start; - let message = format!("Unsupported rule: '{}'", iter.input.slice(range)); - log_css_error(iter.input, pos, &*message, self.context); - } - } - } - CssRules::new(rules, self.shared_lock) - } -} - -#[cfg(feature = "servo")] -fn is_viewport_enabled() -> bool { - PREFS.get("layout.viewport.enabled").as_boolean().unwrap_or(false) -} - -#[cfg(not(feature = "servo"))] -fn is_viewport_enabled() -> bool { - false // Gecko doesn't support @viewport -} - -impl<'a, 'b> AtRuleParser for NestedRuleParser<'a, 'b> { - type Prelude = AtRulePrelude; - type AtRule = CssRule; - - fn parse_prelude( - &mut self, - name: &str, - input: &mut Parser - ) -> Result<AtRuleType<AtRulePrelude, CssRule>, ()> { - let location = - get_location_with_offset( - input.current_source_location(), - self.context.line_number_offset - ); - - match_ignore_ascii_case! { name, - "media" => { - let media_queries = parse_media_query_list(self.context, input); - let arc = Arc::new(self.shared_lock.wrap(media_queries)); - Ok(AtRuleType::WithBlock(AtRulePrelude::Media(arc, location))) - }, - "supports" => { - let cond = SupportsCondition::parse(input)?; - Ok(AtRuleType::WithBlock(AtRulePrelude::Supports(cond, location))) - }, - "font-face" => { - Ok(AtRuleType::WithBlock(AtRulePrelude::FontFace(location))) - }, - "counter-style" => { - if !cfg!(feature = "gecko") { - // Support for this rule is not fully implemented in Servo yet. - return Err(()) - } - let name = parse_counter_style_name(input)?; - // ASCII-case-insensitive matches for "decimal" are already lower-cased - // by `parse_counter_style_name`, so we can use == here. - // FIXME: https://bugzilla.mozilla.org/show_bug.cgi?id=1359323 use atom!("decimal") - if name.0 == Atom::from("decimal") { - return Err(()) - } - Ok(AtRuleType::WithBlock(AtRulePrelude::CounterStyle(name))) - }, - "viewport" => { - if is_viewport_enabled() { - Ok(AtRuleType::WithBlock(AtRulePrelude::Viewport)) - } else { - Err(()) - } - }, - "keyframes" | "-webkit-keyframes" | "-moz-keyframes" => { - let prefix = if starts_with_ignore_ascii_case(name, "-webkit-") { - Some(VendorPrefix::WebKit) - } else if starts_with_ignore_ascii_case(name, "-moz-") { - Some(VendorPrefix::Moz) - } else { - None - }; - if cfg!(feature = "servo") && - prefix.as_ref().map_or(false, |p| matches!(*p, VendorPrefix::Moz)) { - // Servo should not support @-moz-keyframes. - return Err(()) - } - let name = KeyframesName::parse(self.context, input)?; - - Ok(AtRuleType::WithBlock(AtRulePrelude::Keyframes(name, prefix, location))) - }, - "page" => { - if cfg!(feature = "gecko") { - Ok(AtRuleType::WithBlock(AtRulePrelude::Page(location))) - } else { - Err(()) - } - }, - "-moz-document" => { - if cfg!(feature = "gecko") { - let cond = DocumentCondition::parse(self.context, input)?; - Ok(AtRuleType::WithBlock(AtRulePrelude::Document(cond, location))) - } else { - Err(()) - } - }, - _ => Err(()) - } - } - - fn parse_block(&mut self, prelude: AtRulePrelude, input: &mut Parser) -> Result<CssRule, ()> { - match prelude { - AtRulePrelude::FontFace(location) => { - let context = ParserContext::new_with_rule_type(self.context, Some(CssRuleType::FontFace)); - Ok(CssRule::FontFace(Arc::new(self.shared_lock.wrap( - parse_font_face_block(&context, input, location).into())))) - } - AtRulePrelude::CounterStyle(name) => { - let context = ParserContext::new_with_rule_type(self.context, Some(CssRuleType::CounterStyle)); - Ok(CssRule::CounterStyle(Arc::new(self.shared_lock.wrap( - parse_counter_style_body(name, &context, input)?.into())))) - } - AtRulePrelude::Media(media_queries, location) => { - Ok(CssRule::Media(Arc::new(self.shared_lock.wrap(MediaRule { - media_queries: media_queries, - rules: self.parse_nested_rules(input, CssRuleType::Media), - source_location: location, - })))) - } - AtRulePrelude::Supports(cond, location) => { - let enabled = cond.eval(self.context); - Ok(CssRule::Supports(Arc::new(self.shared_lock.wrap(SupportsRule { - condition: cond, - rules: self.parse_nested_rules(input, CssRuleType::Supports), - enabled: enabled, - source_location: location, - })))) - } - AtRulePrelude::Viewport => { - let context = ParserContext::new_with_rule_type(self.context, Some(CssRuleType::Viewport)); - Ok(CssRule::Viewport(Arc::new(self.shared_lock.wrap( - try!(ViewportRule::parse(&context, input)))))) - } - AtRulePrelude::Keyframes(name, prefix, location) => { - let context = ParserContext::new_with_rule_type(self.context, Some(CssRuleType::Keyframes)); - Ok(CssRule::Keyframes(Arc::new(self.shared_lock.wrap(KeyframesRule { - name: name, - keyframes: parse_keyframe_list(&context, input, self.shared_lock), - vendor_prefix: prefix, - source_location: location, - })))) - } - AtRulePrelude::Page(location) => { - let context = ParserContext::new_with_rule_type(self.context, Some(CssRuleType::Page)); - let declarations = parse_property_declaration_list(&context, input); - Ok(CssRule::Page(Arc::new(self.shared_lock.wrap(PageRule { - block: Arc::new(self.shared_lock.wrap(declarations)), - source_location: location, - })))) - } - AtRulePrelude::Document(cond, location) => { - if cfg!(feature = "gecko") { - Ok(CssRule::Document(Arc::new(self.shared_lock.wrap(DocumentRule { - condition: cond, - rules: self.parse_nested_rules(input, CssRuleType::Document), - source_location: location, - })))) - } else { - unreachable!() - } - } - } - } -} - -impl<'a, 'b> QualifiedRuleParser for NestedRuleParser<'a, 'b> { - type Prelude = SelectorList<SelectorImpl>; - type QualifiedRule = CssRule; - - fn parse_prelude(&mut self, input: &mut Parser) -> Result<SelectorList<SelectorImpl>, ()> { - let selector_parser = SelectorParser { - stylesheet_origin: self.stylesheet_origin, - namespaces: self.context.namespaces.unwrap(), - }; - - SelectorList::parse(&selector_parser, input) - } - - fn parse_block( - &mut self, - prelude: SelectorList<SelectorImpl>, - input: &mut Parser - ) -> Result<CssRule, ()> { - let location = get_location_with_offset(input.current_source_location(), - self.context.line_number_offset); - let context = ParserContext::new_with_rule_type(self.context, Some(CssRuleType::Style)); - let declarations = parse_property_declaration_list(&context, input); - Ok(CssRule::Style(Arc::new(self.shared_lock.wrap(StyleRule { - selectors: prelude, - block: Arc::new(self.shared_lock.wrap(declarations)), - source_location: location, - })))) - } -} diff --git a/components/style/stylesheets/counter_style_rule.rs b/components/style/stylesheets/counter_style_rule.rs new file mode 100644 index 00000000000..a6369be9395 --- /dev/null +++ b/components/style/stylesheets/counter_style_rule.rs @@ -0,0 +1,12 @@ +/* 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 http://mozilla.org/MPL/2.0/. */ + +// TODO(emilio): unify this, components/style/counter_style.rs, and +// components/style/gecko/rules.rs +#![allow(missing_docs)] + +#[cfg(feature = "servo")] +pub use counter_style::CounterStyleRuleData as CounterStyleRule; +#[cfg(feature = "gecko")] +pub use gecko::rules::CounterStyleRule; diff --git a/components/style/document_condition.rs b/components/style/stylesheets/document_rule.rs index b4e89adcce7..0c79b0bfd8e 100644 --- a/components/style/document_condition.rs +++ b/components/style/stylesheets/document_rule.rs @@ -6,19 +6,57 @@ //! initially in CSS Conditional Rules Module Level 3, @document has been postponed to the level 4. //! We implement the prefixed `@-moz-document`. -use cssparser::{Parser, Token, serialize_string}; -#[cfg(feature = "gecko")] -use gecko_bindings::bindings::Gecko_DocumentRule_UseForPresentation; -#[cfg(feature = "gecko")] -use gecko_bindings::structs::URLMatchingFunction as GeckoUrlMatchingFunction; +use cssparser::{Parser, Token, SourceLocation, serialize_string}; use media_queries::Device; -#[cfg(feature = "gecko")] -use nsstring::nsCString; use parser::{Parse, ParserContext}; +use shared_lock::{DeepCloneWithLock, Locked, SharedRwLock, SharedRwLockReadGuard, ToCssWithGuard}; use std::fmt; use style_traits::ToCss; +use stylearc::Arc; +use stylesheets::CssRules; use values::specified::url::SpecifiedUrl; +#[derive(Debug)] +/// A @-moz-document rule +pub struct DocumentRule { + /// The parsed condition + pub condition: DocumentCondition, + /// Child rules + pub rules: Arc<Locked<CssRules>>, + /// The line and column of the rule's source code. + pub source_location: SourceLocation, +} + +impl ToCssWithGuard for DocumentRule { + fn to_css<W>(&self, guard: &SharedRwLockReadGuard, dest: &mut W) -> fmt::Result + where W: fmt::Write { + try!(dest.write_str("@-moz-document ")); + try!(self.condition.to_css(dest)); + try!(dest.write_str(" {")); + for rule in self.rules.read_with(guard).0.iter() { + try!(dest.write_str(" ")); + try!(rule.to_css(guard, dest)); + } + dest.write_str(" }") + } +} + +impl DeepCloneWithLock for DocumentRule { + /// Deep clones this DocumentRule. + fn deep_clone_with_lock( + &self, + lock: &SharedRwLock, + guard: &SharedRwLockReadGuard, + ) -> Self { + let rules = self.rules.read_with(guard); + DocumentRule { + condition: self.condition.clone(), + rules: Arc::new(lock.wrap(rules.deep_clone_with_lock(lock, guard))), + source_location: self.source_location.clone(), + } + } +} + /// A URL matching function for a `@document` rule's condition. #[derive(Clone, Debug)] pub enum UrlMatchingFunction { @@ -84,12 +122,17 @@ impl UrlMatchingFunction { #[cfg(feature = "gecko")] /// Evaluate a URL matching function. pub fn evaluate(&self, device: &Device) -> bool { + use gecko_bindings::bindings::Gecko_DocumentRule_UseForPresentation; + use gecko_bindings::structs::URLMatchingFunction as GeckoUrlMatchingFunction; + use nsstring::nsCString; + let func = match *self { UrlMatchingFunction::Url(_) => GeckoUrlMatchingFunction::eURL, UrlMatchingFunction::UrlPrefix(_) => GeckoUrlMatchingFunction::eURLPrefix, UrlMatchingFunction::Domain(_) => GeckoUrlMatchingFunction::eDomain, UrlMatchingFunction::RegExp(_) => GeckoUrlMatchingFunction::eRegExp, }; + let pattern = nsCString::from(match *self { UrlMatchingFunction::Url(ref url) => url.as_str(), UrlMatchingFunction::UrlPrefix(ref pat) | diff --git a/components/style/stylesheets/font_face_rule.rs b/components/style/stylesheets/font_face_rule.rs new file mode 100644 index 00000000000..5e178e88e55 --- /dev/null +++ b/components/style/stylesheets/font_face_rule.rs @@ -0,0 +1,12 @@ +/* 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 http://mozilla.org/MPL/2.0/. */ + +// TODO(emilio): unify this, components/style/font_face.rs, and +// components/style/gecko/rules.rs +#![allow(missing_docs)] + +#[cfg(feature = "servo")] +pub use font_face::FontFaceRuleData as FontFaceRule; +#[cfg(feature = "gecko")] +pub use gecko::rules::FontFaceRule; diff --git a/components/style/stylesheets/import_rule.rs b/components/style/stylesheets/import_rule.rs new file mode 100644 index 00000000000..96edfca2314 --- /dev/null +++ b/components/style/stylesheets/import_rule.rs @@ -0,0 +1,59 @@ +/* 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 http://mozilla.org/MPL/2.0/. */ + +//! The [`@import`][import] at-rule. +//! +//! [import]: https://drafts.csswg.org/css-cascade-3/#at-import + +use cssparser::SourceLocation; +use shared_lock::{SharedRwLockReadGuard, ToCssWithGuard}; +use std::fmt; +use style_traits::ToCss; +use stylearc::Arc; +use stylesheets::stylesheet::Stylesheet; +use values::specified::url::SpecifiedUrl; + +/// The [`@import`][import] at-rule. +/// +/// [import]: https://drafts.csswg.org/css-cascade-3/#at-import +#[derive(Debug)] +pub struct ImportRule { + /// The `<url>` this `@import` rule is loading. + pub url: SpecifiedUrl, + + /// The stylesheet is always present. + /// + /// It contains an empty list of rules and namespace set that is updated + /// when it loads. + pub stylesheet: Arc<Stylesheet>, + + /// The line and column of the rule's source code. + pub source_location: SourceLocation, +} + +impl Clone for ImportRule { + fn clone(&self) -> ImportRule { + let stylesheet: &Stylesheet = &*self.stylesheet; + ImportRule { + url: self.url.clone(), + stylesheet: Arc::new(stylesheet.clone()), + source_location: self.source_location.clone(), + } + } +} + +impl ToCssWithGuard for ImportRule { + fn to_css<W>(&self, guard: &SharedRwLockReadGuard, dest: &mut W) -> fmt::Result + where W: fmt::Write, + { + dest.write_str("@import ")?; + self.url.to_css(dest)?; + let media = self.stylesheet.media.read_with(guard); + if !media.is_empty() { + dest.write_str(" ")?; + media.to_css(dest)?; + } + dest.write_str(";") + } +} diff --git a/components/style/keyframes.rs b/components/style/stylesheets/keyframes_rule.rs index e0c589a1c32..254793c79cc 100644 --- a/components/style/keyframes.rs +++ b/components/style/stylesheets/keyframes_rule.rs @@ -4,10 +4,8 @@ //! Keyframes: https://drafts.csswg.org/css-animations/#keyframes -#![deny(missing_docs)] - use cssparser::{AtRuleParser, Parser, QualifiedRuleParser, RuleListParser}; -use cssparser::{DeclarationListParser, DeclarationParser, parse_one_rule}; +use cssparser::{DeclarationListParser, DeclarationParser, parse_one_rule, SourceLocation}; use error_reporting::NullReporter; use parser::{PARSING_MODE_DEFAULT, ParserContext, log_css_error}; use properties::{Importance, PropertyDeclaration, PropertyDeclarationBlock, PropertyId}; @@ -15,11 +13,82 @@ use properties::{PropertyDeclarationId, LonghandId, SourcePropertyDeclaration}; use properties::LonghandIdSet; use properties::animated_properties::TransitionProperty; use properties::longhands::transition_timing_function::single_value::SpecifiedValue as SpecifiedTimingFunction; -use shared_lock::{SharedRwLock, SharedRwLockReadGuard, Locked, ToCssWithGuard}; +use shared_lock::{DeepCloneWithLock, SharedRwLock, SharedRwLockReadGuard, Locked, ToCssWithGuard}; use std::fmt; use style_traits::ToCss; use stylearc::Arc; -use stylesheets::{CssRuleType, Stylesheet, VendorPrefix}; +use stylesheets::{CssRuleType, Stylesheet}; +use stylesheets::rule_parser::VendorPrefix; +use values::KeyframesName; + +/// A [`@keyframes`][keyframes] rule. +/// +/// [keyframes]: https://drafts.csswg.org/css-animations/#keyframes +#[derive(Debug)] +pub struct KeyframesRule { + /// The name of the current animation. + pub name: KeyframesName, + /// The keyframes specified for this CSS rule. + pub keyframes: Vec<Arc<Locked<Keyframe>>>, + /// Vendor prefix type the @keyframes has. + pub vendor_prefix: Option<VendorPrefix>, + /// The line and column of the rule's source code. + pub source_location: SourceLocation, +} + +impl ToCssWithGuard for KeyframesRule { + // Serialization of KeyframesRule is not specced. + fn to_css<W>(&self, guard: &SharedRwLockReadGuard, dest: &mut W) -> fmt::Result + where W: fmt::Write, + { + dest.write_str("@keyframes ")?; + self.name.to_css(dest)?; + dest.write_str(" {")?; + let iter = self.keyframes.iter(); + for lock in iter { + dest.write_str("\n")?; + let keyframe = lock.read_with(&guard); + keyframe.to_css(guard, dest)?; + } + dest.write_str("\n}") + } +} + +impl KeyframesRule { + /// Returns the index of the last keyframe that matches the given selector. + /// If the selector is not valid, or no keyframe is found, returns None. + /// + /// Related spec: + /// https://drafts.csswg.org/css-animations-1/#interface-csskeyframesrule-findrule + pub fn find_rule(&self, guard: &SharedRwLockReadGuard, selector: &str) -> Option<usize> { + if let Ok(selector) = Parser::new(selector).parse_entirely(KeyframeSelector::parse) { + for (i, keyframe) in self.keyframes.iter().enumerate().rev() { + if keyframe.read_with(guard).selector == selector { + return Some(i); + } + } + } + None + } +} + +impl DeepCloneWithLock for KeyframesRule { + fn deep_clone_with_lock( + &self, + lock: &SharedRwLock, + guard: &SharedRwLockReadGuard + ) -> Self { + KeyframesRule { + name: self.name.clone(), + keyframes: self.keyframes.iter() + .map(|ref x| Arc::new(lock.wrap( + x.read_with(guard).deep_clone_with_lock(lock, guard)))) + .collect(), + vendor_prefix: self.vendor_prefix.clone(), + source_location: self.source_location.clone(), + } + } +} /// A number from 0 to 1, indicating the percentage of the animation when this /// keyframe should run. @@ -150,14 +219,18 @@ impl Keyframe { }; parse_one_rule(&mut input, &mut rule_parser) } +} +impl DeepCloneWithLock for Keyframe { /// Deep clones this Keyframe. - pub fn deep_clone_with_lock(&self, - lock: &SharedRwLock) -> Keyframe { - let guard = lock.read(); + fn deep_clone_with_lock( + &self, + lock: &SharedRwLock, + guard: &SharedRwLockReadGuard + ) -> Keyframe { Keyframe { selector: self.selector.clone(), - block: Arc::new(lock.wrap(self.block.read_with(&guard).clone())), + block: Arc::new(lock.wrap(self.block.read_with(guard).clone())), } } } diff --git a/components/style/stylesheets/loader.rs b/components/style/stylesheets/loader.rs new file mode 100644 index 00000000000..ff38ff3ed2a --- /dev/null +++ b/components/style/stylesheets/loader.rs @@ -0,0 +1,43 @@ +/* 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 http://mozilla.org/MPL/2.0/. */ + +//! The stylesheet loader is the abstraction used to trigger network requests +//! for `@import` rules. + +use media_queries::MediaList; +use shared_lock::Locked; +use stylearc::Arc; +use stylesheets::ImportRule; + +/// The stylesheet loader is the abstraction used to trigger network requests +/// for `@import` rules. +pub trait StylesheetLoader { + /// Request a stylesheet after parsing a given `@import` rule. + /// + /// The called code is responsible to update the `stylesheet` rules field + /// when the sheet is done loading. + /// + /// The convoluted signature allows impls to look at MediaList and + /// ImportRule before they’re locked, while keeping the trait object-safe. + fn request_stylesheet( + &self, + media: Arc<Locked<MediaList>>, + make_import: &mut FnMut(Arc<Locked<MediaList>>) -> ImportRule, + make_arc: &mut FnMut(ImportRule) -> Arc<Locked<ImportRule>>, + ) -> Arc<Locked<ImportRule>>; +} + +/// A dummy loader that just creates the import rule with the empty stylesheet. +pub struct NoOpLoader; + +impl StylesheetLoader for NoOpLoader { + fn request_stylesheet( + &self, + media: Arc<Locked<MediaList>>, + make_import: &mut FnMut(Arc<Locked<MediaList>>) -> ImportRule, + make_arc: &mut FnMut(ImportRule) -> Arc<Locked<ImportRule>>, + ) -> Arc<Locked<ImportRule>> { + make_arc(make_import(media)) + } +} diff --git a/components/style/stylesheets/media_rule.rs b/components/style/stylesheets/media_rule.rs new file mode 100644 index 00000000000..13938513d60 --- /dev/null +++ b/components/style/stylesheets/media_rule.rs @@ -0,0 +1,60 @@ +/* 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 http://mozilla.org/MPL/2.0/. */ + +//! An [`@media`][media] urle. +//! +//! [media]: https://drafts.csswg.org/css-conditional/#at-ruledef-media + +use cssparser::SourceLocation; +use media_queries::MediaList; +use shared_lock::{DeepCloneWithLock, Locked, SharedRwLock, SharedRwLockReadGuard, ToCssWithGuard}; +use std::fmt; +use style_traits::ToCss; +use stylearc::Arc; +use stylesheets::CssRules; + +/// An [`@media`][media] urle. +/// +/// [media]: https://drafts.csswg.org/css-conditional/#at-ruledef-media +#[derive(Debug)] +pub struct MediaRule { + /// The list of media queries used by this media rule. + pub media_queries: Arc<Locked<MediaList>>, + /// The nested rules to this media rule. + pub rules: Arc<Locked<CssRules>>, + /// The source position where this media rule was found. + pub source_location: SourceLocation, +} + +impl ToCssWithGuard for MediaRule { + // Serialization of MediaRule is not specced. + // https://drafts.csswg.org/cssom/#serialize-a-css-rule CSSMediaRule + fn to_css<W>(&self, guard: &SharedRwLockReadGuard, dest: &mut W) -> fmt::Result + where W: fmt::Write { + dest.write_str("@media ")?; + self.media_queries.read_with(guard).to_css(dest)?; + dest.write_str(" {")?; + for rule in self.rules.read_with(guard).0.iter() { + dest.write_str(" ")?; + rule.to_css(guard, dest)?; + } + dest.write_str(" }") + } +} + +impl DeepCloneWithLock for MediaRule { + fn deep_clone_with_lock( + &self, + lock: &SharedRwLock, + guard: &SharedRwLockReadGuard + ) -> Self { + let media_queries = self.media_queries.read_with(guard); + let rules = self.rules.read_with(guard); + MediaRule { + media_queries: Arc::new(lock.wrap(media_queries.clone())), + rules: Arc::new(lock.wrap(rules.deep_clone_with_lock(lock, guard))), + source_location: self.source_location.clone(), + } + } +} diff --git a/components/style/stylesheets/memory.rs b/components/style/stylesheets/memory.rs new file mode 100644 index 00000000000..44a2e429d5b --- /dev/null +++ b/components/style/stylesheets/memory.rs @@ -0,0 +1,71 @@ +/* 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 http://mozilla.org/MPL/2.0/. */ + +//! Memory reporting for the style system when running inside of Gecko. + +use shared_lock::SharedRwLockReadGuard; +use std::os::raw::c_void; + +/// Like gecko_bindings::structs::MallocSizeOf, but without the Option<> +/// wrapper. +/// +/// Note that functions of this type should not be called via +/// do_malloc_size_of(), rather than directly. +pub type MallocSizeOfFn = unsafe extern "C" fn(ptr: *const c_void) -> usize; + +/// Call malloc_size_of on ptr, first checking that the allocation isn't empty. +pub unsafe fn do_malloc_size_of<T>(malloc_size_of: MallocSizeOfFn, ptr: *const T) -> usize { + use std::mem::align_of; + + if ptr as usize <= align_of::<T>() { + 0 + } else { + malloc_size_of(ptr as *const c_void) + } +} + +/// Trait for measuring the size of heap data structures. +pub trait MallocSizeOf { + /// Measure the size of any heap-allocated structures that hang off this + /// value, but not the space taken up by the value itself. + fn malloc_size_of_children(&self, malloc_size_of: MallocSizeOfFn) -> usize; +} + +/// Like MallocSizeOf, but operates with the global SharedRwLockReadGuard +/// locked. +pub trait MallocSizeOfWithGuard { + /// Like MallocSizeOf::malloc_size_of_children, but with a |guard| argument. + fn malloc_size_of_children( + &self, + guard: &SharedRwLockReadGuard, + malloc_size_of: MallocSizeOfFn + ) -> usize; +} + +impl<A: MallocSizeOf, B: MallocSizeOf> MallocSizeOf for (A, B) { + fn malloc_size_of_children(&self, malloc_size_of: MallocSizeOfFn) -> usize { + self.0.malloc_size_of_children(malloc_size_of) + + self.1.malloc_size_of_children(malloc_size_of) + } +} + +impl<T: MallocSizeOf> MallocSizeOf for Vec<T> { + fn malloc_size_of_children(&self, malloc_size_of: MallocSizeOfFn) -> usize { + self.iter().fold( + unsafe { do_malloc_size_of(malloc_size_of, self.as_ptr()) }, + |n, elem| n + elem.malloc_size_of_children(malloc_size_of)) + } +} + +impl<T: MallocSizeOfWithGuard> MallocSizeOfWithGuard for Vec<T> { + fn malloc_size_of_children( + &self, + guard: &SharedRwLockReadGuard, + malloc_size_of: MallocSizeOfFn, + ) -> usize { + self.iter().fold( + unsafe { do_malloc_size_of(malloc_size_of, self.as_ptr()) }, + |n, elem| n + elem.malloc_size_of_children(guard, malloc_size_of)) + } +} diff --git a/components/style/stylesheets/mod.rs b/components/style/stylesheets/mod.rs new file mode 100644 index 00000000000..bcc40f1243b --- /dev/null +++ b/components/style/stylesheets/mod.rs @@ -0,0 +1,344 @@ +/* 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 http://mozilla.org/MPL/2.0/. */ + +//! Style sheets and their CSS rules. + +mod counter_style_rule; +mod document_rule; +mod font_face_rule; +mod import_rule; +pub mod keyframes_rule; +mod loader; +mod media_rule; +mod memory; +mod namespace_rule; +mod page_rule; +mod rule_list; +mod rule_parser; +mod rules_iterator; +mod style_rule; +mod stylesheet; +pub mod supports_rule; +pub mod viewport_rule; + +use cssparser::{parse_one_rule, Parser}; +use error_reporting::NullReporter; +use parser::{ParserContext, PARSING_MODE_DEFAULT}; +use shared_lock::{DeepCloneWithLock, Locked, SharedRwLock, SharedRwLockReadGuard, ToCssWithGuard}; +use std::fmt; +use stylearc::Arc; + +pub use self::counter_style_rule::CounterStyleRule; +pub use self::document_rule::DocumentRule; +pub use self::font_face_rule::FontFaceRule; +pub use self::import_rule::ImportRule; +pub use self::keyframes_rule::KeyframesRule; +pub use self::loader::StylesheetLoader; +pub use self::media_rule::MediaRule; +pub use self::memory::{MallocSizeOf, MallocSizeOfFn, MallocSizeOfWithGuard}; +pub use self::namespace_rule::NamespaceRule; +pub use self::page_rule::PageRule; +pub use self::rule_parser::{State, TopLevelRuleParser}; +pub use self::rule_list::{CssRules, CssRulesHelpers}; +pub use self::rules_iterator::{AllRules, EffectiveRules, NestedRuleIterationCondition, RulesIterator}; +pub use self::stylesheet::{Namespaces, Stylesheet, UserAgentStylesheets}; +pub use self::style_rule::StyleRule; +pub use self::supports_rule::SupportsRule; +pub use self::viewport_rule::ViewportRule; + +/// Extra data that the backend may need to resolve url values. +#[cfg(not(feature = "gecko"))] +pub type UrlExtraData = ::servo_url::ServoUrl; + +/// Extra data that the backend may need to resolve url values. +#[cfg(feature = "gecko")] +pub type UrlExtraData = + ::gecko_bindings::sugar::refptr::RefPtr<::gecko_bindings::structs::URLExtraData>; + +#[cfg(feature = "gecko")] +impl UrlExtraData { + /// Returns a string for the url. + /// + /// Unimplemented currently. + pub fn as_str(&self) -> &str { + // TODO + "(stylo: not supported)" + } +} + +// XXX We probably need to figure out whether we should mark Eq here. +// It is currently marked so because properties::UnparsedValue wants Eq. +#[cfg(feature = "gecko")] +impl Eq for UrlExtraData {} + +/// Each style rule has an origin, which determines where it enters the cascade. +/// +/// http://dev.w3.org/csswg/css-cascade/#cascading-origins +#[derive(Clone, PartialEq, Eq, Copy, Debug)] +#[cfg_attr(feature = "servo", derive(HeapSizeOf))] +pub enum Origin { + /// http://dev.w3.org/csswg/css-cascade/#cascade-origin-ua + UserAgent, + + /// http://dev.w3.org/csswg/css-cascade/#cascade-origin-author + Author, + + /// http://dev.w3.org/csswg/css-cascade/#cascade-origin-user + User, +} + +/// A CSS rule. +/// +/// TODO(emilio): Lots of spec links should be around. +#[derive(Debug, Clone)] +#[allow(missing_docs)] +pub enum CssRule { + // No Charset here, CSSCharsetRule has been removed from CSSOM + // https://drafts.csswg.org/cssom/#changes-from-5-december-2013 + + Namespace(Arc<Locked<NamespaceRule>>), + Import(Arc<Locked<ImportRule>>), + Style(Arc<Locked<StyleRule>>), + Media(Arc<Locked<MediaRule>>), + FontFace(Arc<Locked<FontFaceRule>>), + CounterStyle(Arc<Locked<CounterStyleRule>>), + Viewport(Arc<Locked<ViewportRule>>), + Keyframes(Arc<Locked<KeyframesRule>>), + Supports(Arc<Locked<SupportsRule>>), + Page(Arc<Locked<PageRule>>), + Document(Arc<Locked<DocumentRule>>), +} + +impl MallocSizeOfWithGuard for CssRule { + fn malloc_size_of_children( + &self, + guard: &SharedRwLockReadGuard, + malloc_size_of: MallocSizeOfFn + ) -> usize { + match *self { + CssRule::Style(ref lock) => { + lock.read_with(guard).malloc_size_of_children(guard, malloc_size_of) + }, + // Measurement of these fields may be added later. + CssRule::Import(_) => 0, + CssRule::Media(_) => 0, + CssRule::FontFace(_) => 0, + CssRule::CounterStyle(_) => 0, + CssRule::Keyframes(_) => 0, + CssRule::Namespace(_) => 0, + CssRule::Viewport(_) => 0, + CssRule::Supports(_) => 0, + CssRule::Page(_) => 0, + CssRule::Document(_) => 0, + } + } +} + +#[allow(missing_docs)] +#[derive(PartialEq, Eq, Copy, Clone)] +pub enum CssRuleType { + // https://drafts.csswg.org/cssom/#the-cssrule-interface + Style = 1, + Charset = 2, + Import = 3, + Media = 4, + FontFace = 5, + Page = 6, + // https://drafts.csswg.org/css-animations-1/#interface-cssrule-idl + Keyframes = 7, + Keyframe = 8, + // https://drafts.csswg.org/cssom/#the-cssrule-interface + Margin = 9, + Namespace = 10, + // https://drafts.csswg.org/css-counter-styles-3/#extentions-to-cssrule-interface + CounterStyle = 11, + // https://drafts.csswg.org/css-conditional-3/#extentions-to-cssrule-interface + Supports = 12, + // https://www.w3.org/TR/2012/WD-css3-conditional-20120911/#extentions-to-cssrule-interface + Document = 13, + // https://drafts.csswg.org/css-fonts-3/#om-fontfeaturevalues + FontFeatureValues = 14, + // https://drafts.csswg.org/css-device-adapt/#css-rule-interface + Viewport = 15, +} + +#[allow(missing_docs)] +pub enum SingleRuleParseError { + Syntax, + Hierarchy, +} + +#[allow(missing_docs)] +pub enum RulesMutateError { + Syntax, + IndexSize, + HierarchyRequest, + InvalidState, +} + +impl From<SingleRuleParseError> for RulesMutateError { + fn from(other: SingleRuleParseError) -> Self { + match other { + SingleRuleParseError::Syntax => RulesMutateError::Syntax, + SingleRuleParseError::Hierarchy => RulesMutateError::HierarchyRequest, + } + } +} + +impl CssRule { + /// Returns the CSSOM rule type of this rule. + pub fn rule_type(&self) -> CssRuleType { + match *self { + CssRule::Style(_) => CssRuleType::Style, + CssRule::Import(_) => CssRuleType::Import, + CssRule::Media(_) => CssRuleType::Media, + CssRule::FontFace(_) => CssRuleType::FontFace, + CssRule::CounterStyle(_) => CssRuleType::CounterStyle, + CssRule::Keyframes(_) => CssRuleType::Keyframes, + CssRule::Namespace(_) => CssRuleType::Namespace, + CssRule::Viewport(_) => CssRuleType::Viewport, + CssRule::Supports(_) => CssRuleType::Supports, + CssRule::Page(_) => CssRuleType::Page, + CssRule::Document(_) => CssRuleType::Document, + } + } + + fn rule_state(&self) -> State { + match *self { + // CssRule::Charset(..) => State::Start, + CssRule::Import(..) => State::Imports, + CssRule::Namespace(..) => State::Namespaces, + _ => State::Body, + } + } + + /// Parse a CSS rule. + /// + /// Returns a parsed CSS rule and the final state of the parser. + /// + /// Input state is None for a nested rule + pub fn parse( + css: &str, + parent_stylesheet: &Stylesheet, + state: Option<State>, + loader: Option<&StylesheetLoader> + ) -> Result<(Self, State), SingleRuleParseError> { + let error_reporter = NullReporter; + let context = ParserContext::new( + parent_stylesheet.origin, + &parent_stylesheet.url_data, + &error_reporter, + None, + PARSING_MODE_DEFAULT, + parent_stylesheet.quirks_mode + ); + + let mut input = Parser::new(css); + + let mut guard = parent_stylesheet.namespaces.write(); + + // nested rules are in the body state + let state = state.unwrap_or(State::Body); + let mut rule_parser = TopLevelRuleParser { + stylesheet_origin: parent_stylesheet.origin, + context: context, + shared_lock: &parent_stylesheet.shared_lock, + loader: loader, + state: state, + namespaces: Some(&mut *guard), + }; + + match parse_one_rule(&mut input, &mut rule_parser) { + Ok(result) => Ok((result, rule_parser.state)), + Err(_) => { + Err(match rule_parser.state { + State::Invalid => SingleRuleParseError::Hierarchy, + _ => SingleRuleParseError::Syntax, + }) + } + } + } +} + +impl DeepCloneWithLock for CssRule { + /// Deep clones this CssRule. + fn deep_clone_with_lock( + &self, + lock: &SharedRwLock, + guard: &SharedRwLockReadGuard, + ) -> CssRule { + match *self { + CssRule::Namespace(ref arc) => { + let rule = arc.read_with(guard); + CssRule::Namespace(Arc::new(lock.wrap(rule.clone()))) + }, + CssRule::Import(ref arc) => { + let rule = arc.read_with(guard); + CssRule::Import(Arc::new(lock.wrap(rule.clone()))) + }, + CssRule::Style(ref arc) => { + let rule = arc.read_with(guard); + CssRule::Style(Arc::new( + lock.wrap(rule.deep_clone_with_lock(lock, guard)))) + }, + CssRule::Media(ref arc) => { + let rule = arc.read_with(guard); + CssRule::Media(Arc::new( + lock.wrap(rule.deep_clone_with_lock(lock, guard)))) + }, + CssRule::FontFace(ref arc) => { + let rule = arc.read_with(guard); + CssRule::FontFace(Arc::new(lock.wrap(rule.clone()))) + }, + CssRule::CounterStyle(ref arc) => { + let rule = arc.read_with(guard); + CssRule::CounterStyle(Arc::new(lock.wrap(rule.clone()))) + }, + CssRule::Viewport(ref arc) => { + let rule = arc.read_with(guard); + CssRule::Viewport(Arc::new(lock.wrap(rule.clone()))) + }, + CssRule::Keyframes(ref arc) => { + let rule = arc.read_with(guard); + CssRule::Keyframes(Arc::new( + lock.wrap(rule.deep_clone_with_lock(lock, guard)))) + }, + CssRule::Supports(ref arc) => { + let rule = arc.read_with(guard); + CssRule::Supports(Arc::new( + lock.wrap(rule.deep_clone_with_lock(lock, guard)))) + }, + CssRule::Page(ref arc) => { + let rule = arc.read_with(guard); + CssRule::Page(Arc::new( + lock.wrap(rule.deep_clone_with_lock(lock, guard)))) + }, + CssRule::Document(ref arc) => { + let rule = arc.read_with(guard); + CssRule::Document(Arc::new( + lock.wrap(rule.deep_clone_with_lock(lock, guard)))) + }, + } + } +} + +impl ToCssWithGuard for CssRule { + // https://drafts.csswg.org/cssom/#serialize-a-css-rule + fn to_css<W>(&self, guard: &SharedRwLockReadGuard, dest: &mut W) -> fmt::Result + where W: fmt::Write { + match *self { + CssRule::Namespace(ref lock) => lock.read_with(guard).to_css(guard, dest), + CssRule::Import(ref lock) => lock.read_with(guard).to_css(guard, dest), + CssRule::Style(ref lock) => lock.read_with(guard).to_css(guard, dest), + CssRule::FontFace(ref lock) => lock.read_with(guard).to_css(guard, dest), + CssRule::CounterStyle(ref lock) => lock.read_with(guard).to_css(guard, dest), + CssRule::Viewport(ref lock) => lock.read_with(guard).to_css(guard, dest), + CssRule::Keyframes(ref lock) => lock.read_with(guard).to_css(guard, dest), + CssRule::Media(ref lock) => lock.read_with(guard).to_css(guard, dest), + CssRule::Supports(ref lock) => lock.read_with(guard).to_css(guard, dest), + CssRule::Page(ref lock) => lock.read_with(guard).to_css(guard, dest), + CssRule::Document(ref lock) => lock.read_with(guard).to_css(guard, dest), + } + } +} diff --git a/components/style/stylesheets/namespace_rule.rs b/components/style/stylesheets/namespace_rule.rs new file mode 100644 index 00000000000..6cfba1d7234 --- /dev/null +++ b/components/style/stylesheets/namespace_rule.rs @@ -0,0 +1,39 @@ +/* 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 http://mozilla.org/MPL/2.0/. */ + +//! The `@namespace` at-rule. + +use {Namespace, Prefix}; +use cssparser::SourceLocation; +use shared_lock::{SharedRwLockReadGuard, ToCssWithGuard}; +use std::fmt; + +/// A `@namespace` rule. +#[derive(Clone, Debug, PartialEq)] +#[allow(missing_docs)] +pub struct NamespaceRule { + /// The namespace prefix, and `None` if it's the default Namespace + pub prefix: Option<Prefix>, + /// The actual namespace url. + pub url: Namespace, + /// The source location this rule was found at. + pub source_location: SourceLocation, +} + +impl ToCssWithGuard for NamespaceRule { + // https://drafts.csswg.org/cssom/#serialize-a-css-rule CSSNamespaceRule + fn to_css<W>(&self, _guard: &SharedRwLockReadGuard, dest: &mut W) -> fmt::Result + where W: fmt::Write { + dest.write_str("@namespace ")?; + if let Some(ref prefix) = self.prefix { + dest.write_str(&*prefix.to_string())?; + dest.write_str(" ")?; + } + + // FIXME(emilio): Pretty sure this needs some escaping, or something? + dest.write_str("url(\"")?; + dest.write_str(&*self.url.to_string())?; + dest.write_str("\");") + } +} diff --git a/components/style/stylesheets/page_rule.rs b/components/style/stylesheets/page_rule.rs new file mode 100644 index 00000000000..4ec3a5f6aaf --- /dev/null +++ b/components/style/stylesheets/page_rule.rs @@ -0,0 +1,60 @@ +/* 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 http://mozilla.org/MPL/2.0/. */ + +//! A [`@page`][page] rule. +//! +//! [page]: https://drafts.csswg.org/css2/page.html#page-box + +use cssparser::SourceLocation; +use properties::PropertyDeclarationBlock; +use shared_lock::{DeepCloneWithLock, Locked, SharedRwLock, SharedRwLockReadGuard, ToCssWithGuard}; +use std::fmt; +use style_traits::ToCss; +use stylearc::Arc; + +/// A [`@page`][page] rule. +/// +/// This implements only a limited subset of the CSS +/// 2.2 syntax. +/// +/// In this subset, [page selectors][page-selectors] are not implemented. +/// +/// [page]: https://drafts.csswg.org/css2/page.html#page-box +/// [page-selectors]: https://drafts.csswg.org/css2/page.html#page-selectors +#[derive(Debug)] +pub struct PageRule { + /// The declaration block this page rule contains. + pub block: Arc<Locked<PropertyDeclarationBlock>>, + /// The source position this rule was found at. + pub source_location: SourceLocation, +} + +impl ToCssWithGuard for PageRule { + /// Serialization of PageRule is not specced, adapted from steps for + /// StyleRule. + fn to_css<W>(&self, guard: &SharedRwLockReadGuard, dest: &mut W) -> fmt::Result + where W: fmt::Write, + { + dest.write_str("@page { ")?; + let declaration_block = self.block.read_with(guard); + declaration_block.to_css(dest)?; + if !declaration_block.declarations().is_empty() { + dest.write_str(" ")?; + } + dest.write_str("}") + } +} + +impl DeepCloneWithLock for PageRule { + fn deep_clone_with_lock( + &self, + lock: &SharedRwLock, + guard: &SharedRwLockReadGuard, + ) -> Self { + PageRule { + block: Arc::new(lock.wrap(self.block.read_with(&guard).clone())), + source_location: self.source_location.clone(), + } + } +} diff --git a/components/style/stylesheets/rule_list.rs b/components/style/stylesheets/rule_list.rs new file mode 100644 index 00000000000..7067760f44a --- /dev/null +++ b/components/style/stylesheets/rule_list.rs @@ -0,0 +1,168 @@ +/* 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 http://mozilla.org/MPL/2.0/. */ + +//! A list of CSS rules. + +use shared_lock::{DeepCloneWithLock, Locked, SharedRwLock, SharedRwLockReadGuard}; +use stylearc::Arc; +use stylesheets::{CssRule, RulesMutateError}; +use stylesheets::loader::StylesheetLoader; +use stylesheets::memory::{MallocSizeOfFn, MallocSizeOfWithGuard}; +use stylesheets::rule_parser::State; +use stylesheets::stylesheet::Stylesheet; + +/// A list of CSS rules. +#[derive(Debug)] +pub struct CssRules(pub Vec<CssRule>); + +impl CssRules { + /// Whether this CSS rules is empty. + pub fn is_empty(&self) -> bool { + self.0.is_empty() + } +} + +impl DeepCloneWithLock for CssRules { + fn deep_clone_with_lock( + &self, + lock: &SharedRwLock, + guard: &SharedRwLockReadGuard + ) -> Self { + CssRules( + self.0.iter().map(|ref x| x.deep_clone_with_lock(lock, guard)).collect() + ) + } +} + +impl MallocSizeOfWithGuard for CssRules { + fn malloc_size_of_children( + &self, + guard: &SharedRwLockReadGuard, + malloc_size_of: MallocSizeOfFn + ) -> usize { + self.0.malloc_size_of_children(guard, malloc_size_of) + } +} + +impl CssRules { + /// Trivially construct a new set of CSS rules. + pub fn new(rules: Vec<CssRule>, shared_lock: &SharedRwLock) -> Arc<Locked<CssRules>> { + Arc::new(shared_lock.wrap(CssRules(rules))) + } + + /// Returns whether all the rules in this list are namespace or import + /// rules. + fn only_ns_or_import(&self) -> bool { + self.0.iter().all(|r| { + match *r { + CssRule::Namespace(..) | + CssRule::Import(..) => true, + _ => false + } + }) + } + + /// https://drafts.csswg.org/cssom/#remove-a-css-rule + pub fn remove_rule(&mut self, index: usize) -> Result<(), RulesMutateError> { + // Step 1, 2 + if index >= self.0.len() { + return Err(RulesMutateError::IndexSize); + } + + { + // Step 3 + let ref rule = self.0[index]; + + // Step 4 + if let CssRule::Namespace(..) = *rule { + if !self.only_ns_or_import() { + return Err(RulesMutateError::InvalidState); + } + } + } + + // Step 5, 6 + self.0.remove(index); + Ok(()) + } +} + +/// A trait to implement helpers for `Arc<Locked<CssRules>>`. +pub trait CssRulesHelpers { + /// https://drafts.csswg.org/cssom/#insert-a-css-rule + /// + /// Written in this funky way because parsing an @import rule may cause us + /// to clone a stylesheet from the same document due to caching in the CSS + /// loader. + /// + /// TODO(emilio): We could also pass the write guard down into the loader + /// instead, but that seems overkill. + fn insert_rule(&self, + lock: &SharedRwLock, + rule: &str, + parent_stylesheet: &Stylesheet, + index: usize, + nested: bool, + loader: Option<&StylesheetLoader>) + -> Result<CssRule, RulesMutateError>; +} + +impl CssRulesHelpers for Arc<Locked<CssRules>> { + fn insert_rule(&self, + lock: &SharedRwLock, + rule: &str, + parent_stylesheet: &Stylesheet, + index: usize, + nested: bool, + loader: Option<&StylesheetLoader>) + -> Result<CssRule, RulesMutateError> { + let state = { + let read_guard = lock.read(); + let rules = self.read_with(&read_guard); + + // Step 1, 2 + if index > rules.0.len() { + return Err(RulesMutateError::IndexSize); + } + + // Computes the parser state at the given index + if nested { + None + } else if index == 0 { + Some(State::Start) + } else { + rules.0.get(index - 1).map(CssRule::rule_state) + } + }; + + // Step 3, 4 + // XXXManishearth should we also store the namespace map? + let (new_rule, new_state) = + CssRule::parse(&rule, parent_stylesheet, state, loader)?; + + { + let mut write_guard = lock.write(); + let mut rules = self.write_with(&mut write_guard); + // Step 5 + // Computes the maximum allowed parser state at a given index. + let rev_state = rules.0.get(index).map_or(State::Body, CssRule::rule_state); + if new_state > rev_state { + // We inserted a rule too early, e.g. inserting + // a regular style rule before @namespace rules + return Err(RulesMutateError::HierarchyRequest); + } + + // Step 6 + if let CssRule::Namespace(..) = new_rule { + if !rules.only_ns_or_import() { + return Err(RulesMutateError::InvalidState); + } + } + + rules.0.insert(index, new_rule.clone()); + } + + Ok(new_rule) + } +} diff --git a/components/style/stylesheets/rule_parser.rs b/components/style/stylesheets/rule_parser.rs new file mode 100644 index 00000000000..b1d250ab51a --- /dev/null +++ b/components/style/stylesheets/rule_parser.rs @@ -0,0 +1,520 @@ +/* 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 http://mozilla.org/MPL/2.0/. */ + +//! Parsing of the stylesheet contents. + +use {Namespace, Prefix}; +use counter_style::{parse_counter_style_body, parse_counter_style_name}; +use cssparser::{AtRuleParser, AtRuleType, Parser, QualifiedRuleParser, RuleListParser, SourceLocation}; +use font_face::parse_font_face_block; +use media_queries::{parse_media_query_list, MediaList}; +use parking_lot::RwLock; +use parser::{Parse, ParserContext, log_css_error}; +use properties::parse_property_declaration_list; +use selector_parser::{SelectorImpl, SelectorParser}; +use selectors::SelectorList; +use shared_lock::{Locked, SharedRwLock}; +use std::sync::atomic::AtomicBool; +use str::starts_with_ignore_ascii_case; +use stylearc::Arc; +use stylesheets::{CssRule, CssRules, CssRuleType, Origin, StylesheetLoader}; +use stylesheets::{DocumentRule, ImportRule, KeyframesRule, MediaRule, NamespaceRule, PageRule}; +use stylesheets::{StyleRule, SupportsRule, ViewportRule}; +use stylesheets::document_rule::DocumentCondition; +use stylesheets::keyframes_rule::parse_keyframe_list; +use stylesheets::loader::NoOpLoader; +use stylesheets::stylesheet::{Namespaces, Stylesheet}; +use stylesheets::supports_rule::SupportsCondition; +use values::CustomIdent; +use values::KeyframesName; +use values::specified::url::SpecifiedUrl; + +/// The parser for the top-level rules in a stylesheet. +pub struct TopLevelRuleParser<'a> { + /// The origin of the stylesheet we're parsing. + pub stylesheet_origin: Origin, + /// A reference to the lock we need to use to create rules. + pub shared_lock: &'a SharedRwLock, + /// A reference to a stylesheet loader if applicable, for `@import` rules. + pub loader: Option<&'a StylesheetLoader>, + /// The parser context. This initially won't contain any namespaces, but + /// will be populated after parsing namespace rules, if any. + pub context: ParserContext<'a>, + /// The current state of the parser. + pub state: State, + /// The namespace map we use for parsing. Needs to start as `Some()`, and + /// will be taken out after parsing namespace rules, and that reference will + /// be moved to `ParserContext`. + pub namespaces: Option<&'a mut Namespaces>, +} + +impl<'b> TopLevelRuleParser<'b> { + fn nested<'a: 'b>(&'a self) -> NestedRuleParser<'a, 'b> { + NestedRuleParser { + stylesheet_origin: self.stylesheet_origin, + shared_lock: self.shared_lock, + context: &self.context, + } + } + + /// Returns the associated parser context with this rule parser. + pub fn context(&self) -> &ParserContext { + &self.context + } + + /// Returns the current state of the parser. + pub fn state(&self) -> State { + self.state + } +} + +/// The current state of the parser. +#[derive(Eq, PartialEq, Ord, PartialOrd, Copy, Clone)] +pub enum State { + /// We haven't started parsing rules. + Start = 1, + /// We're parsing `@import` rules. + Imports = 2, + /// We're parsing `@namespace` rules. + Namespaces = 3, + /// We're parsing the main body of the stylesheet. + Body = 4, + /// We've found an invalid state (as, a namespace rule after style rules), + /// and the rest of the stylesheet should be ignored. + Invalid = 5, +} + +#[derive(Clone, Debug)] +#[cfg_attr(feature = "servo", derive(HeapSizeOf))] +/// Vendor prefix. +pub enum VendorPrefix { + /// -moz prefix. + Moz, + /// -webkit prefix. + WebKit, +} + +/// A rule prelude for a given at-rule. +pub enum AtRulePrelude { + /// A @font-face rule prelude. + FontFace(SourceLocation), + /// A @counter-style rule prelude, with its counter style name. + CounterStyle(CustomIdent), + /// A @media rule prelude, with its media queries. + Media(Arc<Locked<MediaList>>, SourceLocation), + /// An @supports rule, with its conditional + Supports(SupportsCondition, SourceLocation), + /// A @viewport rule prelude. + Viewport, + /// A @keyframes rule, with its animation name and vendor prefix if exists. + Keyframes(KeyframesName, Option<VendorPrefix>, SourceLocation), + /// A @page rule prelude. + Page(SourceLocation), + /// A @document rule, with its conditional. + Document(DocumentCondition, SourceLocation), +} + + +#[cfg(feature = "gecko")] +fn register_namespace(ns: &Namespace) -> Result<i32, ()> { + use gecko_bindings::bindings; + let id = unsafe { bindings::Gecko_RegisterNamespace(ns.0.as_ptr()) }; + if id == -1 { + Err(()) + } else { + Ok(id) + } +} + +#[cfg(feature = "servo")] +fn register_namespace(_: &Namespace) -> Result<(), ()> { + Ok(()) // servo doesn't use namespace ids +} + +impl<'a> AtRuleParser for TopLevelRuleParser<'a> { + type Prelude = AtRulePrelude; + type AtRule = CssRule; + + fn parse_prelude( + &mut self, + name: &str, + input: &mut Parser + ) -> Result<AtRuleType<AtRulePrelude, CssRule>, ()> { + let location = get_location_with_offset(input.current_source_location(), + self.context.line_number_offset); + match_ignore_ascii_case! { name, + "import" => { + if self.state > State::Imports { + self.state = State::Invalid; + return Err(()) // "@import must be before any rule but @charset" + } + + self.state = State::Imports; + let url_string = input.expect_url_or_string()?; + let specified_url = SpecifiedUrl::parse_from_string(url_string, &self.context)?; + + let media = parse_media_query_list(&self.context, input); + let media = Arc::new(self.shared_lock.wrap(media)); + + let noop_loader = NoOpLoader; + let loader = if !specified_url.is_invalid() { + self.loader.expect("Expected a stylesheet loader for @import") + } else { + &noop_loader + }; + + let mut specified_url = Some(specified_url); + let arc = loader.request_stylesheet(media, &mut |media| { + ImportRule { + url: specified_url.take().unwrap(), + stylesheet: Arc::new(Stylesheet { + rules: CssRules::new(Vec::new(), self.shared_lock), + media: media, + shared_lock: self.shared_lock.clone(), + origin: self.context.stylesheet_origin, + url_data: self.context.url_data.clone(), + namespaces: RwLock::new(Namespaces::default()), + dirty_on_viewport_size_change: AtomicBool::new(false), + disabled: AtomicBool::new(false), + quirks_mode: self.context.quirks_mode, + }), + source_location: location, + } + }, &mut |import_rule| { + Arc::new(self.shared_lock.wrap(import_rule)) + }); + + return Ok(AtRuleType::WithoutBlock(CssRule::Import(arc))) + }, + "namespace" => { + if self.state > State::Namespaces { + self.state = State::Invalid; + return Err(()) // "@namespace must be before any rule but @charset and @import" + } + self.state = State::Namespaces; + + let prefix_result = input.try(|input| input.expect_ident()); + let url = Namespace::from(try!(input.expect_url_or_string())); + + let id = register_namespace(&url)?; + + let mut namespaces = self.namespaces.as_mut().unwrap(); + + let opt_prefix = if let Ok(prefix) = prefix_result { + let prefix = Prefix::from(prefix); + namespaces + .prefixes + .insert(prefix.clone(), (url.clone(), id)); + Some(prefix) + } else { + namespaces.default = Some((url.clone(), id)); + None + }; + + return Ok(AtRuleType::WithoutBlock(CssRule::Namespace(Arc::new( + self.shared_lock.wrap(NamespaceRule { + prefix: opt_prefix, + url: url, + source_location: location, + }) + )))) + }, + // @charset is removed by rust-cssparser if it’s the first rule in the stylesheet + // anything left is invalid. + "charset" => return Err(()), // (insert appropriate error message) + _ => {} + } + // Don't allow starting with an invalid state + if self.state > State::Body { + self.state = State::Invalid; + return Err(()); + } + self.state = State::Body; + + // "Freeze" the namespace map (no more namespace rules can be parsed + // after this point), and stick it in the context. + if self.namespaces.is_some() { + let namespaces = &*self.namespaces.take().unwrap(); + self.context.namespaces = Some(namespaces); + } + AtRuleParser::parse_prelude(&mut self.nested(), name, input) + } + + #[inline] + fn parse_block(&mut self, prelude: AtRulePrelude, input: &mut Parser) -> Result<CssRule, ()> { + AtRuleParser::parse_block(&mut self.nested(), prelude, input) + } +} + + +impl<'a> QualifiedRuleParser for TopLevelRuleParser<'a> { + type Prelude = SelectorList<SelectorImpl>; + type QualifiedRule = CssRule; + + #[inline] + fn parse_prelude(&mut self, input: &mut Parser) -> Result<SelectorList<SelectorImpl>, ()> { + self.state = State::Body; + + // "Freeze" the namespace map (no more namespace rules can be parsed + // after this point), and stick it in the context. + if self.namespaces.is_some() { + let namespaces = &*self.namespaces.take().unwrap(); + self.context.namespaces = Some(namespaces); + } + + QualifiedRuleParser::parse_prelude(&mut self.nested(), input) + } + + #[inline] + fn parse_block( + &mut self, + prelude: SelectorList<SelectorImpl>, + input: &mut Parser + ) -> Result<CssRule, ()> { + QualifiedRuleParser::parse_block(&mut self.nested(), prelude, input) + } +} + +#[derive(Clone)] // shallow, relatively cheap .clone +struct NestedRuleParser<'a, 'b: 'a> { + stylesheet_origin: Origin, + shared_lock: &'a SharedRwLock, + context: &'a ParserContext<'b>, +} + +impl<'a, 'b> NestedRuleParser<'a, 'b> { + fn parse_nested_rules( + &mut self, + input: &mut Parser, + rule_type: CssRuleType + ) -> Arc<Locked<CssRules>> { + let context = ParserContext::new_with_rule_type(self.context, Some(rule_type)); + + let nested_parser = NestedRuleParser { + stylesheet_origin: self.stylesheet_origin, + shared_lock: self.shared_lock, + context: &context, + }; + + let mut iter = RuleListParser::new_for_nested_rule(input, nested_parser); + let mut rules = Vec::new(); + while let Some(result) = iter.next() { + match result { + Ok(rule) => rules.push(rule), + Err(range) => { + let pos = range.start; + let message = format!("Unsupported rule: '{}'", iter.input.slice(range)); + log_css_error(iter.input, pos, &*message, self.context); + } + } + } + CssRules::new(rules, self.shared_lock) + } +} + +#[cfg(feature = "servo")] +fn is_viewport_enabled() -> bool { + use servo_config::prefs::PREFS; + PREFS.get("layout.viewport.enabled").as_boolean().unwrap_or(false) +} + +#[cfg(not(feature = "servo"))] +fn is_viewport_enabled() -> bool { + false // Gecko doesn't support @viewport. +} + +impl<'a, 'b> AtRuleParser for NestedRuleParser<'a, 'b> { + type Prelude = AtRulePrelude; + type AtRule = CssRule; + + fn parse_prelude( + &mut self, + name: &str, + input: &mut Parser + ) -> Result<AtRuleType<AtRulePrelude, CssRule>, ()> { + let location = + get_location_with_offset( + input.current_source_location(), + self.context.line_number_offset + ); + + match_ignore_ascii_case! { name, + "media" => { + let media_queries = parse_media_query_list(self.context, input); + let arc = Arc::new(self.shared_lock.wrap(media_queries)); + Ok(AtRuleType::WithBlock(AtRulePrelude::Media(arc, location))) + }, + "supports" => { + let cond = SupportsCondition::parse(input)?; + Ok(AtRuleType::WithBlock(AtRulePrelude::Supports(cond, location))) + }, + "font-face" => { + Ok(AtRuleType::WithBlock(AtRulePrelude::FontFace(location))) + }, + "counter-style" => { + if !cfg!(feature = "gecko") { + // Support for this rule is not fully implemented in Servo yet. + return Err(()) + } + let name = parse_counter_style_name(input)?; + // ASCII-case-insensitive matches for "decimal" are already + // lower-cased by `parse_counter_style_name`, so we can use == + // here. + if name.0 == atom!("decimal") { + return Err(()) + } + Ok(AtRuleType::WithBlock(AtRulePrelude::CounterStyle(name))) + }, + "viewport" => { + if is_viewport_enabled() { + Ok(AtRuleType::WithBlock(AtRulePrelude::Viewport)) + } else { + Err(()) + } + }, + "keyframes" | "-webkit-keyframes" | "-moz-keyframes" => { + let prefix = if starts_with_ignore_ascii_case(name, "-webkit-") { + Some(VendorPrefix::WebKit) + } else if starts_with_ignore_ascii_case(name, "-moz-") { + Some(VendorPrefix::Moz) + } else { + None + }; + if cfg!(feature = "servo") && + prefix.as_ref().map_or(false, |p| matches!(*p, VendorPrefix::Moz)) { + // Servo should not support @-moz-keyframes. + return Err(()) + } + let name = KeyframesName::parse(self.context, input)?; + + Ok(AtRuleType::WithBlock(AtRulePrelude::Keyframes(name, prefix, location))) + }, + "page" => { + if cfg!(feature = "gecko") { + Ok(AtRuleType::WithBlock(AtRulePrelude::Page(location))) + } else { + Err(()) + } + }, + "-moz-document" => { + if cfg!(feature = "gecko") { + let cond = DocumentCondition::parse(self.context, input)?; + Ok(AtRuleType::WithBlock(AtRulePrelude::Document(cond, location))) + } else { + Err(()) + } + }, + _ => Err(()) + } + } + + fn parse_block( + &mut self, + prelude: AtRulePrelude, + input: &mut Parser + ) -> Result<CssRule, ()> { + match prelude { + AtRulePrelude::FontFace(location) => { + let context = ParserContext::new_with_rule_type(self.context, Some(CssRuleType::FontFace)); + Ok(CssRule::FontFace(Arc::new(self.shared_lock.wrap( + parse_font_face_block(&context, input, location).into())))) + } + AtRulePrelude::CounterStyle(name) => { + let context = ParserContext::new_with_rule_type(self.context, Some(CssRuleType::CounterStyle)); + Ok(CssRule::CounterStyle(Arc::new(self.shared_lock.wrap( + parse_counter_style_body(name, &context, input)?.into())))) + } + AtRulePrelude::Media(media_queries, location) => { + Ok(CssRule::Media(Arc::new(self.shared_lock.wrap(MediaRule { + media_queries: media_queries, + rules: self.parse_nested_rules(input, CssRuleType::Media), + source_location: location, + })))) + } + AtRulePrelude::Supports(cond, location) => { + let enabled = cond.eval(self.context); + Ok(CssRule::Supports(Arc::new(self.shared_lock.wrap(SupportsRule { + condition: cond, + rules: self.parse_nested_rules(input, CssRuleType::Supports), + enabled: enabled, + source_location: location, + })))) + } + AtRulePrelude::Viewport => { + let context = ParserContext::new_with_rule_type(self.context, Some(CssRuleType::Viewport)); + Ok(CssRule::Viewport(Arc::new(self.shared_lock.wrap( + try!(ViewportRule::parse(&context, input)))))) + } + AtRulePrelude::Keyframes(name, prefix, location) => { + let context = ParserContext::new_with_rule_type(self.context, Some(CssRuleType::Keyframes)); + Ok(CssRule::Keyframes(Arc::new(self.shared_lock.wrap(KeyframesRule { + name: name, + keyframes: parse_keyframe_list(&context, input, self.shared_lock), + vendor_prefix: prefix, + source_location: location, + })))) + } + AtRulePrelude::Page(location) => { + let context = ParserContext::new_with_rule_type(self.context, Some(CssRuleType::Page)); + let declarations = parse_property_declaration_list(&context, input); + Ok(CssRule::Page(Arc::new(self.shared_lock.wrap(PageRule { + block: Arc::new(self.shared_lock.wrap(declarations)), + source_location: location, + })))) + } + AtRulePrelude::Document(cond, location) => { + if cfg!(feature = "gecko") { + Ok(CssRule::Document(Arc::new(self.shared_lock.wrap(DocumentRule { + condition: cond, + rules: self.parse_nested_rules(input, CssRuleType::Document), + source_location: location, + })))) + } else { + unreachable!() + } + } + } + } +} + +impl<'a, 'b> QualifiedRuleParser for NestedRuleParser<'a, 'b> { + type Prelude = SelectorList<SelectorImpl>; + type QualifiedRule = CssRule; + + fn parse_prelude(&mut self, input: &mut Parser) -> Result<SelectorList<SelectorImpl>, ()> { + let selector_parser = SelectorParser { + stylesheet_origin: self.stylesheet_origin, + namespaces: self.context.namespaces.unwrap(), + }; + + SelectorList::parse(&selector_parser, input) + } + + fn parse_block( + &mut self, + prelude: SelectorList<SelectorImpl>, + input: &mut Parser + ) -> Result<CssRule, ()> { + let location = get_location_with_offset(input.current_source_location(), + self.context.line_number_offset); + let context = ParserContext::new_with_rule_type(self.context, Some(CssRuleType::Style)); + let declarations = parse_property_declaration_list(&context, input); + Ok(CssRule::Style(Arc::new(self.shared_lock.wrap(StyleRule { + selectors: prelude, + block: Arc::new(self.shared_lock.wrap(declarations)), + source_location: location, + })))) + } +} + +/// Calculates the location of a rule's source given an offset. +fn get_location_with_offset( + location: SourceLocation, + offset: u64 +) -> SourceLocation { + SourceLocation { + line: location.line + offset as usize - 1, + column: location.column, + } +} diff --git a/components/style/stylesheets/rules_iterator.rs b/components/style/stylesheets/rules_iterator.rs new file mode 100644 index 00000000000..cbcaf9e0123 --- /dev/null +++ b/components/style/stylesheets/rules_iterator.rs @@ -0,0 +1,273 @@ +/* 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 http://mozilla.org/MPL/2.0/. */ + +//! An iterator over a list of rules. + +use context::QuirksMode; +use media_queries::Device; +use shared_lock::SharedRwLockReadGuard; +use smallvec::SmallVec; +use std::slice; +use stylesheets::{CssRule, CssRules, DocumentRule, ImportRule, MediaRule, SupportsRule}; + +/// 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<C>, +} + +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: &'a CssRules) + -> Self + { + let mut stack = SmallVec::new(); + stack.push(rules.0.iter()); + Self { + device: device, + quirks_mode: quirks_mode, + guard: guard, + stack: 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(); + } +} + +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<Self::Item> { + let mut nested_iter_finished = false; + while !self.stack.is_empty() { + if nested_iter_finished { + self.stack.pop(); + nested_iter_finished = false; + continue; + } + + let rule; + let sub_iter; + { + let mut nested_iter = self.stack.last_mut().unwrap(); + rule = match nested_iter.next() { + Some(r) => r, + None => { + nested_iter_finished = true; + continue + } + }; + + sub_iter = match *rule { + CssRule::Import(ref import_rule) => { + let import_rule = import_rule.read_with(self.guard); + if !C::process_import(self.guard, + self.device, + self.quirks_mode, + import_rule) { + continue; + } + Some(import_rule.stylesheet.rules.read_with(self.guard).0.iter()) + } + CssRule::Document(ref doc_rule) => { + let doc_rule = doc_rule.read_with(self.guard); + if !C::process_document(self.guard, + self.device, + self.quirks_mode, + doc_rule) { + continue; + } + Some(doc_rule.rules.read_with(self.guard).0.iter()) + } + CssRule::Media(ref lock) => { + let media_rule = lock.read_with(self.guard); + if !C::process_media(self.guard, + self.device, + self.quirks_mode, + media_rule) { + continue; + } + Some(media_rule.rules.read_with(self.guard).0.iter()) + } + CssRule::Supports(ref lock) => { + let supports_rule = lock.read_with(self.guard); + if !C::process_supports(self.guard, + self.device, + self.quirks_mode, + supports_rule) { + continue; + } + Some(supports_rule.rules.read_with(self.guard).0.iter()) + } + CssRule::Namespace(_) | + CssRule::Style(_) | + CssRule::FontFace(_) | + CssRule::CounterStyle(_) | + CssRule::Viewport(_) | + CssRule::Keyframes(_) | + CssRule::Page(_) => None, + }; + } + + if let Some(sub_iter) = sub_iter { + self.stack.push(sub_iter); + } + + 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 NestedRuleIterationCondition for EffectiveRules { + fn process_import( + guard: &SharedRwLockReadGuard, + device: &Device, + quirks_mode: QuirksMode, + rule: &ImportRule) + -> bool + { + rule.stylesheet.media.read_with(guard).evaluate(device, quirks_mode) + } + + 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>; diff --git a/components/style/stylesheets/style_rule.rs b/components/style/stylesheets/style_rule.rs new file mode 100644 index 00000000000..c8a3da00063 --- /dev/null +++ b/components/style/stylesheets/style_rule.rs @@ -0,0 +1,76 @@ +/* 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 http://mozilla.org/MPL/2.0/. */ + +//! A style rule. + +use cssparser::SourceLocation; +use properties::PropertyDeclarationBlock; +use selector_parser::SelectorImpl; +use selectors::SelectorList; +use shared_lock::{DeepCloneWithLock, Locked, SharedRwLock, SharedRwLockReadGuard, ToCssWithGuard}; +use std::fmt; +use style_traits::ToCss; +use stylearc::Arc; +use stylesheets::{MallocSizeOf, MallocSizeOfFn, MallocSizeOfWithGuard}; + +/// A style rule, with selectors and declarations. +#[derive(Debug)] +pub struct StyleRule { + /// The list of selectors in this rule. + pub selectors: SelectorList<SelectorImpl>, + /// The declaration block with the properties it contains. + pub block: Arc<Locked<PropertyDeclarationBlock>>, + /// The location in the sheet where it was found. + pub source_location: SourceLocation, +} + +impl DeepCloneWithLock for StyleRule { + /// Deep clones this StyleRule. + fn deep_clone_with_lock( + &self, + lock: &SharedRwLock, + guard: &SharedRwLockReadGuard, + ) -> StyleRule { + StyleRule { + selectors: self.selectors.clone(), + block: Arc::new(lock.wrap(self.block.read_with(guard).clone())), + source_location: self.source_location.clone(), + } + } +} + +impl MallocSizeOfWithGuard for StyleRule { + fn malloc_size_of_children( + &self, + guard: &SharedRwLockReadGuard, + malloc_size_of: MallocSizeOfFn + ) -> usize { + // Measurement of other fields may be added later. + self.block.read_with(guard).malloc_size_of_children(malloc_size_of) + } +} + +impl ToCssWithGuard for StyleRule { + /// https://drafts.csswg.org/cssom/#serialize-a-css-rule CSSStyleRule + fn to_css<W>(&self, guard: &SharedRwLockReadGuard, dest: &mut W) -> fmt::Result + where W: fmt::Write, + { + use cssparser::ToCss; + + // Step 1 + self.selectors.to_css(dest)?; + // Step 2 + dest.write_str(" { ")?; + // Step 3 + let declaration_block = self.block.read_with(guard); + declaration_block.to_css(dest)?; + // Step 4 + if !declaration_block.declarations().is_empty() { + dest.write_str(" ")?; + } + // Step 5 + dest.write_str("}") + } +} + diff --git a/components/style/stylesheets/stylesheet.rs b/components/style/stylesheets/stylesheet.rs new file mode 100644 index 00000000000..5e1e9bb5bad --- /dev/null +++ b/components/style/stylesheets/stylesheet.rs @@ -0,0 +1,342 @@ +/* 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 http://mozilla.org/MPL/2.0/. */ + +use {Prefix, Namespace}; +use context::QuirksMode; +use cssparser::{Parser, RuleListParser}; +use error_reporting::ParseErrorReporter; +use fnv::FnvHashMap; +use media_queries::{MediaList, Device}; +use parking_lot::RwLock; +use parser::{PARSING_MODE_DEFAULT, ParserContext, log_css_error}; +use shared_lock::{DeepCloneWithLock, Locked, SharedRwLock, SharedRwLockReadGuard}; +use std::mem; +use std::sync::atomic::{AtomicBool, Ordering}; +use stylearc::Arc; +use stylesheets::{CssRule, CssRules, Origin, UrlExtraData}; +use stylesheets::loader::StylesheetLoader; +use stylesheets::memory::{MallocSizeOfFn, MallocSizeOfWithGuard}; +use stylesheets::rule_parser::{State, TopLevelRuleParser}; +use stylesheets::rules_iterator::{EffectiveRules, EffectiveRulesIterator, NestedRuleIterationCondition, RulesIterator}; +use values::specified::NamespaceId; + +/// This structure holds the user-agent and user stylesheets. +pub struct UserAgentStylesheets { + /// The lock used for user-agent stylesheets. + pub shared_lock: SharedRwLock, + /// The user or user agent stylesheets. + pub user_or_user_agent_stylesheets: Vec<Stylesheet>, + /// The quirks mode stylesheet. + pub quirks_mode_stylesheet: Stylesheet, +} + +/// A set of namespaces applying to a given stylesheet. +/// +/// The namespace id is used in gecko +#[derive(Clone, Default, Debug)] +#[allow(missing_docs)] +pub struct Namespaces { + pub default: Option<(Namespace, NamespaceId)>, + pub prefixes: FnvHashMap<Prefix, (Namespace, NamespaceId)>, +} + +/// The structure servo uses to represent a stylesheet. +#[derive(Debug)] +pub struct Stylesheet { + /// List of rules in the order they were found (important for + /// cascading order) + pub rules: Arc<Locked<CssRules>>, + /// List of media associated with the Stylesheet. + pub media: Arc<Locked<MediaList>>, + /// The origin of this stylesheet. + pub origin: Origin, + /// The url data this stylesheet should use. + pub url_data: UrlExtraData, + /// The lock used for objects inside this stylesheet + pub shared_lock: SharedRwLock, + /// The namespaces that apply to this stylesheet. + pub namespaces: RwLock<Namespaces>, + /// Whether this stylesheet would be dirty when the viewport size changes. + pub dirty_on_viewport_size_change: AtomicBool, + /// Whether this stylesheet should be disabled. + pub disabled: AtomicBool, + /// The quirks mode of this stylesheet. + pub quirks_mode: QuirksMode, +} + +impl Stylesheet { + /// Updates an empty stylesheet from a given string of text. + pub fn update_from_str(existing: &Stylesheet, + css: &str, + url_data: &UrlExtraData, + stylesheet_loader: Option<&StylesheetLoader>, + error_reporter: &ParseErrorReporter, + line_number_offset: u64) { + let namespaces = RwLock::new(Namespaces::default()); + // FIXME: we really should update existing.url_data with the given url_data, + // otherwise newly inserted rule may not have the right base url. + let (rules, dirty_on_viewport_size_change) = + Stylesheet::parse_rules( + css, + url_data, + existing.origin, + &mut *namespaces.write(), + &existing.shared_lock, + stylesheet_loader, + error_reporter, + existing.quirks_mode, + line_number_offset + ); + + mem::swap(&mut *existing.namespaces.write(), &mut *namespaces.write()); + existing.dirty_on_viewport_size_change + .store(dirty_on_viewport_size_change, Ordering::Release); + + // Acquire the lock *after* parsing, to minimize the exclusive section. + let mut guard = existing.shared_lock.write(); + *existing.rules.write_with(&mut guard) = CssRules(rules); + } + + fn parse_rules( + css: &str, + url_data: &UrlExtraData, + origin: Origin, + namespaces: &mut Namespaces, + shared_lock: &SharedRwLock, + stylesheet_loader: Option<&StylesheetLoader>, + error_reporter: &ParseErrorReporter, + quirks_mode: QuirksMode, + line_number_offset: u64 + ) -> (Vec<CssRule>, bool) { + let mut rules = Vec::new(); + let mut input = Parser::new(css); + + let context = + ParserContext::new_with_line_number_offset( + origin, + url_data, + error_reporter, + line_number_offset, + PARSING_MODE_DEFAULT, + quirks_mode + ); + + let rule_parser = TopLevelRuleParser { + stylesheet_origin: origin, + shared_lock: shared_lock, + loader: stylesheet_loader, + context: context, + state: State::Start, + namespaces: Some(namespaces), + }; + + input.look_for_viewport_percentages(); + + { + let mut iter = + RuleListParser::new_for_stylesheet(&mut input, rule_parser); + + while let Some(result) = iter.next() { + match result { + Ok(rule) => rules.push(rule), + Err(range) => { + let pos = range.start; + let message = format!("Invalid rule: '{}'", iter.input.slice(range)); + log_css_error(iter.input, pos, &*message, iter.parser.context()); + } + } + } + } + + (rules, input.seen_viewport_percentages()) + } + + /// Creates an empty stylesheet and parses it with a given base url, origin + /// and media. + /// + /// Effectively creates a new stylesheet and forwards the hard work to + /// `Stylesheet::update_from_str`. + pub fn from_str(css: &str, + url_data: UrlExtraData, + origin: Origin, + media: Arc<Locked<MediaList>>, + shared_lock: SharedRwLock, + stylesheet_loader: Option<&StylesheetLoader>, + error_reporter: &ParseErrorReporter, + quirks_mode: QuirksMode, + line_number_offset: u64) + -> Stylesheet { + let namespaces = RwLock::new(Namespaces::default()); + let (rules, dirty_on_viewport_size_change) = Stylesheet::parse_rules( + css, + &url_data, + origin, + &mut *namespaces.write(), + &shared_lock, + stylesheet_loader, + error_reporter, + quirks_mode, + line_number_offset, + ); + + Stylesheet { + origin: origin, + url_data: url_data, + namespaces: namespaces, + rules: CssRules::new(rules, &shared_lock), + media: media, + shared_lock: shared_lock, + dirty_on_viewport_size_change: AtomicBool::new(dirty_on_viewport_size_change), + disabled: AtomicBool::new(false), + quirks_mode: quirks_mode, + } + } + + /// Whether this stylesheet can be dirty on viewport size change. + pub fn dirty_on_viewport_size_change(&self) -> bool { + self.dirty_on_viewport_size_change.load(Ordering::SeqCst) + } + + /// When CSSOM inserts a rule or declaration into this stylesheet, it needs to call this method + /// with the return value of `cssparser::Parser::seen_viewport_percentages`. + /// + /// FIXME: actually make these calls + /// + /// Note: when *removing* a rule or declaration that contains a viewport percentage, + /// to keep the flag accurate we’d need to iterator through the rest of the stylesheet to + /// check for *other* such values. + /// + /// Instead, we conservatively assume there might be some. + /// Restyling will some some more work than necessary, but give correct results. + pub fn inserted_has_viewport_percentages(&self, has_viewport_percentages: bool) { + self.dirty_on_viewport_size_change.fetch_or(has_viewport_percentages, Ordering::SeqCst); + } + + /// Returns whether the style-sheet applies for the current device depending + /// on the associated MediaList. + /// + /// Always true if no associated MediaList exists. + pub fn is_effective_for_device(&self, device: &Device, guard: &SharedRwLockReadGuard) -> bool { + self.media.read_with(guard).evaluate(device, self.quirks_mode) + } + + /// Return an iterator over the effective rules within the style-sheet, as + /// according to the supplied `Device`. + #[inline] + pub fn effective_rules<'a, 'b>( + &'a self, + device: &'a Device, + guard: &'a SharedRwLockReadGuard<'b>) + -> EffectiveRulesIterator<'a, 'b> + { + self.iter_rules::<'a, 'b, EffectiveRules>(device, guard) + } + + /// Return an iterator using the condition `C`. + #[inline] + pub fn iter_rules<'a, 'b, C>( + &'a self, + device: &'a Device, + guard: &'a SharedRwLockReadGuard<'b>) + -> RulesIterator<'a, 'b, C> + where C: NestedRuleIterationCondition, + { + RulesIterator::new( + device, + self.quirks_mode, + guard, + &self.rules.read_with(guard)) + } + + /// Returns whether the stylesheet has been explicitly disabled through the + /// CSSOM. + pub fn disabled(&self) -> bool { + self.disabled.load(Ordering::SeqCst) + } + + /// Records that the stylesheet has been explicitly disabled through the + /// CSSOM. + /// + /// Returns whether the the call resulted in a change in disabled state. + /// + /// Disabled stylesheets remain in the document, but their rules are not + /// added to the Stylist. + pub fn set_disabled(&self, disabled: bool) -> bool { + self.disabled.swap(disabled, Ordering::SeqCst) != disabled + } +} + +impl Clone for Stylesheet { + fn clone(&self) -> Stylesheet { + // Create a new lock for our clone. + let lock = self.shared_lock.clone(); + let guard = self.shared_lock.read(); + + // Make a deep clone of the rules, using the new lock. + let rules = self.rules.read_with(&guard); + let cloned_rules = rules.deep_clone_with_lock(&lock, &guard); + + // Make a deep clone of the media, using the new lock. + let media = self.media.read_with(&guard); + let cloned_media = media.clone(); + + Stylesheet { + rules: Arc::new(lock.wrap(cloned_rules)), + media: Arc::new(lock.wrap(cloned_media)), + origin: self.origin, + url_data: self.url_data.clone(), + shared_lock: lock, + namespaces: RwLock::new((*self.namespaces.read()).clone()), + dirty_on_viewport_size_change: AtomicBool::new( + self.dirty_on_viewport_size_change.load(Ordering::SeqCst)), + disabled: AtomicBool::new(self.disabled.load(Ordering::SeqCst)), + quirks_mode: self.quirks_mode, + } + } +} + +impl MallocSizeOfWithGuard for Stylesheet { + fn malloc_size_of_children( + &self, + guard: &SharedRwLockReadGuard, + malloc_size_of: MallocSizeOfFn + ) -> usize { + // Measurement of other fields may be added later. + self.rules.read_with(guard).malloc_size_of_children(guard, malloc_size_of) + } +} + +macro_rules! rule_filter { + ($( $method: ident($variant:ident => $rule_type: ident), )+) => { + impl Stylesheet { + $( + #[allow(missing_docs)] + pub fn $method<F>(&self, device: &Device, guard: &SharedRwLockReadGuard, mut f: F) + where F: FnMut(&::stylesheets::$rule_type), + { + use stylesheets::CssRule; + + for rule in self.effective_rules(device, guard) { + if let CssRule::$variant(ref lock) = *rule { + let rule = lock.read_with(guard); + f(&rule) + } + } + } + )+ + } + } +} + +rule_filter! { + effective_style_rules(Style => StyleRule), + effective_media_rules(Media => MediaRule), + effective_font_face_rules(FontFace => FontFaceRule), + effective_counter_style_rules(CounterStyle => CounterStyleRule), + effective_viewport_rules(Viewport => ViewportRule), + effective_keyframes_rules(Keyframes => KeyframesRule), + effective_supports_rules(Supports => SupportsRule), + effective_page_rules(Page => PageRule), + effective_document_rules(Document => DocumentRule), +} diff --git a/components/style/supports.rs b/components/style/stylesheets/supports_rule.rs index 9b297963056..2049bbce7ec 100644 --- a/components/style/supports.rs +++ b/components/style/stylesheets/supports_rule.rs @@ -4,17 +4,64 @@ //! [@supports rules](https://drafts.csswg.org/css-conditional-3/#at-supports) -use cssparser::{parse_important, Parser, Token}; +use cssparser::{parse_important, Parser, SourceLocation, Token}; use parser::ParserContext; use properties::{PropertyId, PropertyDeclaration, SourcePropertyDeclaration}; +use shared_lock::{DeepCloneWithLock, Locked, SharedRwLock, SharedRwLockReadGuard, ToCssWithGuard}; use std::fmt; use style_traits::ToCss; -use stylesheets::CssRuleType; +use stylearc::Arc; +use stylesheets::{CssRuleType, CssRules}; + +/// An [`@supports`][supports] rule. +/// +/// [supports]: https://drafts.csswg.org/css-conditional-3/#at-supports +#[derive(Debug)] +pub struct SupportsRule { + /// The parsed condition + pub condition: SupportsCondition, + /// Child rules + pub rules: Arc<Locked<CssRules>>, + /// The result of evaluating the condition + pub enabled: bool, + /// The line and column of the rule's source code. + pub source_location: SourceLocation, +} + +impl ToCssWithGuard for SupportsRule { + fn to_css<W>(&self, guard: &SharedRwLockReadGuard, dest: &mut W) -> fmt::Result + where W: fmt::Write { + dest.write_str("@supports ")?; + self.condition.to_css(dest)?; + dest.write_str(" {")?; + for rule in self.rules.read_with(guard).0.iter() { + dest.write_str(" ")?; + rule.to_css(guard, dest)?; + } + dest.write_str(" }") + } +} + +impl DeepCloneWithLock for SupportsRule { + fn deep_clone_with_lock( + &self, + lock: &SharedRwLock, + guard: &SharedRwLockReadGuard + ) -> Self { + let rules = self.rules.read_with(guard); + SupportsRule { + condition: self.condition.clone(), + rules: Arc::new(lock.wrap(rules.deep_clone_with_lock(lock, guard))), + enabled: self.enabled, + source_location: self.source_location.clone(), + } + } +} -#[derive(Clone, Debug)] /// An @supports condition /// /// https://drafts.csswg.org/css-conditional-3/#at-supports +#[derive(Clone, Debug)] pub enum SupportsCondition { /// `not (condition)` Not(Box<SupportsCondition>), @@ -119,7 +166,8 @@ pub fn parse_condition_or_declaration(input: &mut Parser) -> Result<SupportsCond impl ToCss for SupportsCondition { fn to_css<W>(&self, dest: &mut W) -> fmt::Result - where W: fmt::Write { + where W: fmt::Write, + { match *self { SupportsCondition::Not(ref cond) => { dest.write_str("not ")?; @@ -173,7 +221,8 @@ pub struct Declaration { impl ToCss for Declaration { fn to_css<W>(&self, dest: &mut W) -> fmt::Result - where W: fmt::Write { + where W: fmt::Write + { dest.write_str(&self.prop)?; dest.write_str(":")?; // no space, the `val` already contains any possible spaces @@ -188,7 +237,7 @@ fn parse_anything(input: &mut Parser) -> String { input.slice_from(pos).to_owned() } -/// consume input till done +/// Consume input till done fn consume_all(input: &mut Parser) { while let Ok(_) = input.next() {} } diff --git a/components/style/viewport.rs b/components/style/stylesheets/viewport_rule.rs index 68ea6a3033f..a510ea5f27c 100644 --- a/components/style/viewport.rs +++ b/components/style/stylesheets/viewport_rule.rs @@ -7,8 +7,6 @@ //! [at]: https://drafts.csswg.org/css-device-adapt/#atviewport-rule //! [meta]: https://drafts.csswg.org/css-device-adapt/#viewport-meta -#![deny(missing_docs)] - use app_units::Au; use context::QuirksMode; use cssparser::{AtRuleParser, DeclarationListParser, DeclarationParser, Parser, parse_important}; diff --git a/components/style/stylist.rs b/components/style/stylist.rs index a9087b18812..3a5a2489522 100644 --- a/components/style/stylist.rs +++ b/components/style/stylist.rs @@ -15,7 +15,6 @@ use font_metrics::FontMetricsProvider; #[cfg(feature = "gecko")] use gecko_bindings::structs::{nsIAtom, StyleRuleInclusion}; use invalidation::media_queries::EffectiveMediaQueryResults; -use keyframes::KeyframesAnimation; use media_queries::Device; use properties::{self, CascadeFlags, ComputedValues}; use properties::{AnimationRules, PropertyDeclarationBlock}; @@ -42,8 +41,9 @@ use stylearc::Arc; use stylesheets::{CounterStyleRule, FontFaceRule}; use stylesheets::{CssRule, StyleRule}; use stylesheets::{Stylesheet, Origin, UserAgentStylesheets}; +use stylesheets::keyframes_rule::KeyframesAnimation; +use stylesheets::viewport_rule::{self, MaybeNew, ViewportRule}; use thread_state; -use viewport::{self, MaybeNew, ViewportRule}; pub use ::fnv::FnvHashMap; @@ -356,7 +356,7 @@ impl Stylist { self.num_rebuilds += 1; let cascaded_rule = ViewportRule { - declarations: viewport::Cascade::from_stylesheets( + declarations: viewport_rule::Cascade::from_stylesheets( doc_stylesheets.clone(), guards.author, &self.device ).finish(), }; @@ -769,7 +769,7 @@ impl Stylist { guard: &SharedRwLockReadGuard, stylesheets: &[Arc<Stylesheet>]) { let cascaded_rule = ViewportRule { - declarations: viewport::Cascade::from_stylesheets(stylesheets.iter(), guard, &device).finish(), + declarations: viewport_rule::Cascade::from_stylesheets(stylesheets.iter(), guard, &device).finish(), }; self.viewport_constraints = diff --git a/ports/geckolib/glue.rs b/ports/geckolib/glue.rs index 6383a2f3fba..4ef66b422c1 100644 --- a/ports/geckolib/glue.rs +++ b/ports/geckolib/glue.rs @@ -83,7 +83,6 @@ use style::gecko_bindings::sugar::ownership::{FFIArcHelpers, HasFFI, HasArcFFI, use style::gecko_bindings::sugar::ownership::{HasSimpleFFI, Strong}; use style::gecko_bindings::sugar::refptr::RefPtr; use style::gecko_properties::{self, style_structs}; -use style::keyframes::{Keyframe, KeyframeSelector, KeyframesStepValue}; use style::media_queries::{MediaList, parse_media_query_list}; use style::parallel; use style::parser::{PARSING_MODE_DEFAULT, ParserContext}; @@ -104,8 +103,9 @@ use style::stylesheets::{CssRule, CssRules, CssRuleType, CssRulesHelpers, Docume use style::stylesheets::{ImportRule, KeyframesRule, MallocSizeOfWithGuard, MediaRule}; use style::stylesheets::{NamespaceRule, Origin, PageRule, Stylesheet, StyleRule, SupportsRule}; use style::stylesheets::StylesheetLoader as StyleStylesheetLoader; +use style::stylesheets::keyframes_rule::{Keyframe, KeyframeSelector, KeyframesStepValue}; +use style::stylesheets::supports_rule::parse_condition_or_declaration; use style::stylist::RuleInclusion; -use style::supports::parse_condition_or_declaration; use style::thread_state; use style::timer::Timer; use style::traversal::{ANIMATION_ONLY, DomTraversal, FOR_CSS_RULE_CHANGES, FOR_RECONSTRUCT}; diff --git a/tests/unit/style/keyframes.rs b/tests/unit/style/keyframes.rs index 1f3947c5815..8f179fc4999 100644 --- a/tests/unit/style/keyframes.rs +++ b/tests/unit/style/keyframes.rs @@ -2,12 +2,12 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -use style::keyframes::{Keyframe, KeyframesAnimation, KeyframePercentage, KeyframeSelector}; -use style::keyframes::{KeyframesStep, KeyframesStepValue}; use style::properties::{PropertyDeclaration, PropertyDeclarationBlock, Importance}; use style::properties::animated_properties::TransitionProperty; use style::shared_lock::SharedRwLock; use style::stylearc::Arc; +use style::stylesheets::keyframes_rule::{Keyframe, KeyframesAnimation, KeyframePercentage, KeyframeSelector}; +use style::stylesheets::keyframes_rule::{KeyframesStep, KeyframesStepValue}; use style::values::specified::{LengthOrPercentageOrAuto, NoCalcLength}; #[test] diff --git a/tests/unit/style/parsing/supports.rs b/tests/unit/style/parsing/supports.rs index b38f1b09bc0..1521a9977c6 100644 --- a/tests/unit/style/parsing/supports.rs +++ b/tests/unit/style/parsing/supports.rs @@ -3,7 +3,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ use cssparser::Parser; -use style::supports::SupportsCondition; +use style::stylesheets::supports_rule::SupportsCondition; use style_traits::ToCss; #[test] diff --git a/tests/unit/style/stylesheets.rs b/tests/unit/style/stylesheets.rs index a161c41cabe..ce465b0f1ac 100644 --- a/tests/unit/style/stylesheets.rs +++ b/tests/unit/style/stylesheets.rs @@ -15,7 +15,6 @@ use std::sync::Mutex; use std::sync::atomic::AtomicBool; use style::context::QuirksMode; use style::error_reporting::ParseErrorReporter; -use style::keyframes::{Keyframe, KeyframeSelector, KeyframePercentage}; use style::media_queries::MediaList; use style::properties::Importance; use style::properties::{CSSWideKeyword, DeclaredValueOwned, PropertyDeclaration, PropertyDeclarationBlock}; @@ -25,6 +24,7 @@ use style::shared_lock::SharedRwLock; use style::stylearc::Arc; use style::stylesheets::{Origin, Namespaces}; use style::stylesheets::{Stylesheet, NamespaceRule, CssRule, CssRules, StyleRule, KeyframesRule}; +use style::stylesheets::keyframes_rule::{Keyframe, KeyframeSelector, KeyframePercentage}; use style::values::{KeyframesName, CustomIdent}; use style::values::specified::{LengthOrPercentageOrAuto, Percentage, PositionComponent}; diff --git a/tests/unit/style/viewport.rs b/tests/unit/style/viewport.rs index 61199660ef8..3d9c5ece244 100644 --- a/tests/unit/style/viewport.rs +++ b/tests/unit/style/viewport.rs @@ -13,10 +13,10 @@ use style::parser::{PARSING_MODE_DEFAULT, Parse, ParserContext}; use style::shared_lock::SharedRwLock; use style::stylearc::Arc; use style::stylesheets::{CssRuleType, Stylesheet, Origin}; +use style::stylesheets::viewport_rule::*; use style::values::specified::LengthOrPercentageOrAuto::{self, Auto}; use style::values::specified::NoCalcLength::{self, ViewportPercentage}; use style::values::specified::ViewportPercentageLength::Vw; -use style::viewport::*; use style_traits::PinchZoomFactor; use style_traits::viewport::*; |