summaryrefslogtreecommitdiffstats
path: root/servo/components/style/stylesheets
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--servo/components/style/stylesheets/cascading_at_rule.rs70
-rw-r--r--servo/components/style/stylesheets/container_rule.rs632
-rw-r--r--servo/components/style/stylesheets/counter_style_rule.rs7
-rw-r--r--servo/components/style/stylesheets/document_rule.rs309
-rw-r--r--servo/components/style/stylesheets/font_face_rule.rs7
-rw-r--r--servo/components/style/stylesheets/font_feature_values_rule.rs463
-rw-r--r--servo/components/style/stylesheets/font_palette_values_rule.rs252
-rw-r--r--servo/components/style/stylesheets/import_rule.rs213
-rw-r--r--servo/components/style/stylesheets/keyframes_rule.rs664
-rw-r--r--servo/components/style/stylesheets/layer_rule.rs239
-rw-r--r--servo/components/style/stylesheets/loader.rs30
-rw-r--r--servo/components/style/stylesheets/media_rule.rs71
-rw-r--r--servo/components/style/stylesheets/mod.rs557
-rw-r--r--servo/components/style/stylesheets/namespace_rule.rs43
-rw-r--r--servo/components/style/stylesheets/origin.rs247
-rw-r--r--servo/components/style/stylesheets/page_rule.rs145
-rw-r--r--servo/components/style/stylesheets/rule_list.rs189
-rw-r--r--servo/components/style/stylesheets/rule_parser.rs796
-rw-r--r--servo/components/style/stylesheets/rules_iterator.rs330
-rw-r--r--servo/components/style/stylesheets/style_rule.rs79
-rw-r--r--servo/components/style/stylesheets/stylesheet.rs608
-rw-r--r--servo/components/style/stylesheets/supports_rule.rs433
-rw-r--r--servo/components/style/stylesheets/viewport_rule.rs791
23 files changed, 7175 insertions, 0 deletions
diff --git a/servo/components/style/stylesheets/cascading_at_rule.rs b/servo/components/style/stylesheets/cascading_at_rule.rs
new file mode 100644
index 0000000000..b23b0720fe
--- /dev/null
+++ b/servo/components/style/stylesheets/cascading_at_rule.rs
@@ -0,0 +1,70 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+//! Cascading at-rule types and traits
+
+use crate::stylesheets::Origin;
+use std::fmt::{self, Write};
+use style_traits::{CssWriter, ToCss};
+
+/// Computes the cascade precedence as according to
+/// <http://dev.w3.org/csswg/css-cascade/#cascade-origin>
+#[inline]
+fn cascade_precendence(origin: Origin, important: bool) -> u8 {
+ match (origin, important) {
+ (Origin::UserAgent, true) => 1,
+ (Origin::User, true) => 2,
+ (Origin::Author, true) => 3,
+ (Origin::Author, false) => 4,
+ (Origin::User, false) => 5,
+ (Origin::UserAgent, false) => 6,
+ }
+}
+
+/// Cascading rule descriptor implementation.
+/// This is only used for at-rules which can cascade. These are @viewport and
+/// @page, although we don't currently implement @page as such.
+#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem)]
+pub struct DescriptorDeclaration<T> {
+ /// Origin of the declaration
+ pub origin: Origin,
+ /// Declaration value
+ pub descriptor: T,
+ /// Indicates the presence of a !important property.
+ pub important: bool,
+}
+
+impl<T> DescriptorDeclaration<T> {
+ #[allow(missing_docs)]
+ pub fn new(origin: Origin, descriptor: T, important: bool) -> Self {
+ Self {
+ origin,
+ descriptor,
+ important,
+ }
+ }
+ /// Returns true iff self is equal or higher precedence to the other.
+ pub fn higher_or_equal_precendence(&self, other: &Self) -> bool {
+ let self_precedence = cascade_precendence(self.origin, self.important);
+ let other_precedence = cascade_precendence(other.origin, other.important);
+
+ self_precedence <= other_precedence
+ }
+}
+
+impl<T> ToCss for DescriptorDeclaration<T>
+where
+ T: ToCss,
+{
+ fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
+ where
+ W: Write,
+ {
+ self.descriptor.to_css(dest)?;
+ if self.important {
+ dest.write_str(" !important")?;
+ }
+ dest.write_char(';')
+ }
+}
diff --git a/servo/components/style/stylesheets/container_rule.rs b/servo/components/style/stylesheets/container_rule.rs
new file mode 100644
index 0000000000..f9d488b9b4
--- /dev/null
+++ b/servo/components/style/stylesheets/container_rule.rs
@@ -0,0 +1,632 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+//! A [`@container`][container] rule.
+//!
+//! [container]: https://drafts.csswg.org/css-contain-3/#container-rule
+
+use crate::computed_value_flags::ComputedValueFlags;
+use crate::dom::TElement;
+use crate::logical_geometry::{LogicalSize, WritingMode};
+use crate::media_queries::Device;
+use crate::parser::ParserContext;
+use crate::properties::ComputedValues;
+use crate::queries::condition::KleeneValue;
+use crate::queries::feature::{AllowsRanges, Evaluator, FeatureFlags, QueryFeatureDescription};
+use crate::queries::values::Orientation;
+use crate::queries::{FeatureType, QueryCondition};
+use crate::shared_lock::{
+ DeepCloneParams, DeepCloneWithLock, Locked, SharedRwLock, SharedRwLockReadGuard, ToCssWithGuard,
+};
+use crate::str::CssStringWriter;
+use crate::stylesheets::CssRules;
+use crate::values::computed::{CSSPixelLength, ContainerType, Context, Ratio};
+use crate::values::specified::ContainerName;
+use app_units::Au;
+use cssparser::{Parser, SourceLocation};
+use euclid::default::Size2D;
+#[cfg(feature = "gecko")]
+use malloc_size_of::{MallocSizeOfOps, MallocUnconditionalShallowSizeOf};
+use servo_arc::Arc;
+use std::fmt::{self, Write};
+use style_traits::{CssWriter, ParseError, ToCss};
+
+/// A container rule.
+#[derive(Debug, ToShmem)]
+pub struct ContainerRule {
+ /// The container query and name.
+ pub condition: Arc<ContainerCondition>,
+ /// The nested rules inside the block.
+ pub rules: Arc<Locked<CssRules>>,
+ /// The source position where this rule was found.
+ pub source_location: SourceLocation,
+}
+
+impl ContainerRule {
+ /// Returns the query condition.
+ pub fn query_condition(&self) -> &QueryCondition {
+ &self.condition.condition
+ }
+
+ /// Returns the query name filter.
+ pub fn container_name(&self) -> &ContainerName {
+ &self.condition.name
+ }
+
+ /// Measure heap usage.
+ #[cfg(feature = "gecko")]
+ pub fn size_of(&self, guard: &SharedRwLockReadGuard, ops: &mut MallocSizeOfOps) -> usize {
+ // Measurement of other fields may be added later.
+ self.rules.unconditional_shallow_size_of(ops) +
+ self.rules.read_with(guard).size_of(guard, ops)
+ }
+}
+
+impl DeepCloneWithLock for ContainerRule {
+ fn deep_clone_with_lock(
+ &self,
+ lock: &SharedRwLock,
+ guard: &SharedRwLockReadGuard,
+ params: &DeepCloneParams,
+ ) -> Self {
+ let rules = self.rules.read_with(guard);
+ Self {
+ condition: self.condition.clone(),
+ rules: Arc::new(lock.wrap(rules.deep_clone_with_lock(lock, guard, params))),
+ source_location: self.source_location.clone(),
+ }
+ }
+}
+
+impl ToCssWithGuard for ContainerRule {
+ fn to_css(&self, guard: &SharedRwLockReadGuard, dest: &mut CssStringWriter) -> fmt::Result {
+ dest.write_str("@container ")?;
+ {
+ let mut writer = CssWriter::new(dest);
+ if !self.condition.name.is_none() {
+ self.condition.name.to_css(&mut writer)?;
+ writer.write_char(' ')?;
+ }
+ self.condition.condition.to_css(&mut writer)?;
+ }
+ self.rules.read_with(guard).to_css_block(guard, dest)
+ }
+}
+
+/// A container condition and filter, combined.
+#[derive(Debug, ToShmem, ToCss)]
+pub struct ContainerCondition {
+ #[css(skip_if = "ContainerName::is_none")]
+ name: ContainerName,
+ condition: QueryCondition,
+ #[css(skip)]
+ flags: FeatureFlags,
+}
+
+/// The result of a successful container query lookup.
+pub struct ContainerLookupResult<E> {
+ /// The relevant container.
+ pub element: E,
+ /// The sizing / writing-mode information of the container.
+ pub info: ContainerInfo,
+ /// The style of the element.
+ pub style: Arc<ComputedValues>,
+}
+
+fn container_type_axes(ty_: ContainerType, wm: WritingMode) -> FeatureFlags {
+ match ty_ {
+ ContainerType::Size => FeatureFlags::all_container_axes(),
+ ContainerType::InlineSize => {
+ let physical_axis = if wm.is_vertical() {
+ FeatureFlags::CONTAINER_REQUIRES_HEIGHT_AXIS
+ } else {
+ FeatureFlags::CONTAINER_REQUIRES_WIDTH_AXIS
+ };
+ FeatureFlags::CONTAINER_REQUIRES_INLINE_AXIS | physical_axis
+ },
+ ContainerType::Normal => FeatureFlags::empty(),
+ }
+}
+
+enum TraversalResult<T> {
+ InProgress,
+ StopTraversal,
+ Done(T),
+}
+
+fn traverse_container<E, F, R>(
+ mut e: E,
+ originating_element_style: Option<&ComputedValues>,
+ evaluator: F,
+) -> Option<(E, R)>
+where
+ E: TElement,
+ F: Fn(E, Option<&ComputedValues>) -> TraversalResult<R>,
+{
+ if originating_element_style.is_some() {
+ match evaluator(e, originating_element_style) {
+ TraversalResult::InProgress => {},
+ TraversalResult::StopTraversal => return None,
+ TraversalResult::Done(result) => return Some((e, result)),
+ }
+ }
+ while let Some(element) = e.traversal_parent() {
+ match evaluator(element, None) {
+ TraversalResult::InProgress => {},
+ TraversalResult::StopTraversal => return None,
+ TraversalResult::Done(result) => return Some((element, result)),
+ }
+ e = element;
+ }
+
+ None
+}
+
+impl ContainerCondition {
+ /// Parse a container condition.
+ pub fn parse<'a>(
+ context: &ParserContext,
+ input: &mut Parser<'a, '_>,
+ ) -> Result<Self, ParseError<'a>> {
+ let name = input
+ .try_parse(|input| ContainerName::parse_for_query(context, input))
+ .ok()
+ .unwrap_or_else(ContainerName::none);
+ let condition = QueryCondition::parse(context, input, FeatureType::Container)?;
+ let flags = condition.cumulative_flags();
+ Ok(Self {
+ name,
+ condition,
+ flags,
+ })
+ }
+
+ fn valid_container_info<E>(
+ &self,
+ potential_container: E,
+ originating_element_style: Option<&ComputedValues>,
+ ) -> TraversalResult<ContainerLookupResult<E>>
+ where
+ E: TElement,
+ {
+ let data;
+ let style = match originating_element_style {
+ Some(s) => s,
+ None => {
+ data = match potential_container.borrow_data() {
+ Some(d) => d,
+ None => return TraversalResult::InProgress,
+ };
+ &**data.styles.primary()
+ },
+ };
+ let wm = style.writing_mode;
+ let box_style = style.get_box();
+
+ // Filter by container-type.
+ let container_type = box_style.clone_container_type();
+ let available_axes = container_type_axes(container_type, wm);
+ if !available_axes.contains(self.flags.container_axes()) {
+ return TraversalResult::InProgress;
+ }
+
+ // Filter by container-name.
+ let container_name = box_style.clone_container_name();
+ for filter_name in self.name.0.iter() {
+ if !container_name.0.contains(filter_name) {
+ return TraversalResult::InProgress;
+ }
+ }
+
+ let size = potential_container.query_container_size(&box_style.clone_display());
+ let style = style.to_arc();
+ TraversalResult::Done(ContainerLookupResult {
+ element: potential_container,
+ info: ContainerInfo { size, wm },
+ style,
+ })
+ }
+
+ /// Performs container lookup for a given element.
+ pub fn find_container<E>(
+ &self,
+ e: E,
+ originating_element_style: Option<&ComputedValues>,
+ ) -> Option<ContainerLookupResult<E>>
+ where
+ E: TElement,
+ {
+ match traverse_container(
+ e,
+ originating_element_style,
+ |element, originating_element_style| {
+ self.valid_container_info(element, originating_element_style)
+ },
+ ) {
+ Some((_, result)) => Some(result),
+ None => None,
+ }
+ }
+
+ /// Tries to match a container query condition for a given element.
+ pub(crate) fn matches<E>(
+ &self,
+ device: &Device,
+ element: E,
+ originating_element_style: Option<&ComputedValues>,
+ invalidation_flags: &mut ComputedValueFlags,
+ ) -> KleeneValue
+ where
+ E: TElement,
+ {
+ let result = self.find_container(element, originating_element_style);
+ let (container, info) = match result {
+ Some(r) => (Some(r.element), Some((r.info, r.style))),
+ None => (None, None),
+ };
+ // Set up the lookup for the container in question, as the condition may be using container query lengths.
+ let size_query_container_lookup = ContainerSizeQuery::for_option_element(container, None);
+ Context::for_container_query_evaluation(
+ device,
+ info,
+ size_query_container_lookup,
+ |context| {
+ let matches = self.condition.matches(context);
+ if context
+ .style()
+ .flags()
+ .contains(ComputedValueFlags::USES_VIEWPORT_UNITS)
+ {
+ // TODO(emilio): Might need something similar to improve
+ // invalidation of font relative container-query lengths.
+ invalidation_flags
+ .insert(ComputedValueFlags::USES_VIEWPORT_UNITS_ON_CONTAINER_QUERIES);
+ }
+ matches
+ },
+ )
+ }
+}
+
+/// Information needed to evaluate an individual container query.
+#[derive(Copy, Clone)]
+pub struct ContainerInfo {
+ size: Size2D<Option<Au>>,
+ wm: WritingMode,
+}
+
+impl ContainerInfo {
+ fn size(&self) -> Option<Size2D<Au>> {
+ Some(Size2D::new(self.size.width?, self.size.height?))
+ }
+}
+
+fn eval_width(context: &Context) -> Option<CSSPixelLength> {
+ let info = context.container_info.as_ref()?;
+ Some(CSSPixelLength::new(info.size.width?.to_f32_px()))
+}
+
+fn eval_height(context: &Context) -> Option<CSSPixelLength> {
+ let info = context.container_info.as_ref()?;
+ Some(CSSPixelLength::new(info.size.height?.to_f32_px()))
+}
+
+fn eval_inline_size(context: &Context) -> Option<CSSPixelLength> {
+ let info = context.container_info.as_ref()?;
+ Some(CSSPixelLength::new(
+ LogicalSize::from_physical(info.wm, info.size)
+ .inline?
+ .to_f32_px(),
+ ))
+}
+
+fn eval_block_size(context: &Context) -> Option<CSSPixelLength> {
+ let info = context.container_info.as_ref()?;
+ Some(CSSPixelLength::new(
+ LogicalSize::from_physical(info.wm, info.size)
+ .block?
+ .to_f32_px(),
+ ))
+}
+
+fn eval_aspect_ratio(context: &Context) -> Option<Ratio> {
+ let info = context.container_info.as_ref()?;
+ Some(Ratio::new(
+ info.size.width?.0 as f32,
+ info.size.height?.0 as f32,
+ ))
+}
+
+fn eval_orientation(context: &Context, value: Option<Orientation>) -> KleeneValue {
+ let size = match context.container_info.as_ref().and_then(|info| info.size()) {
+ Some(size) => size,
+ None => return KleeneValue::Unknown,
+ };
+ KleeneValue::from(Orientation::eval(size, value))
+}
+
+/// https://drafts.csswg.org/css-contain-3/#container-features
+///
+/// TODO: Support style queries, perhaps.
+pub static CONTAINER_FEATURES: [QueryFeatureDescription; 6] = [
+ feature!(
+ atom!("width"),
+ AllowsRanges::Yes,
+ Evaluator::OptionalLength(eval_width),
+ FeatureFlags::CONTAINER_REQUIRES_WIDTH_AXIS,
+ ),
+ feature!(
+ atom!("height"),
+ AllowsRanges::Yes,
+ Evaluator::OptionalLength(eval_height),
+ FeatureFlags::CONTAINER_REQUIRES_HEIGHT_AXIS,
+ ),
+ feature!(
+ atom!("inline-size"),
+ AllowsRanges::Yes,
+ Evaluator::OptionalLength(eval_inline_size),
+ FeatureFlags::CONTAINER_REQUIRES_INLINE_AXIS,
+ ),
+ feature!(
+ atom!("block-size"),
+ AllowsRanges::Yes,
+ Evaluator::OptionalLength(eval_block_size),
+ FeatureFlags::CONTAINER_REQUIRES_BLOCK_AXIS,
+ ),
+ feature!(
+ atom!("aspect-ratio"),
+ AllowsRanges::Yes,
+ Evaluator::OptionalNumberRatio(eval_aspect_ratio),
+ // XXX from_bits_truncate is const, but the pipe operator isn't, so this
+ // works around it.
+ FeatureFlags::from_bits_truncate(
+ FeatureFlags::CONTAINER_REQUIRES_BLOCK_AXIS.bits() |
+ FeatureFlags::CONTAINER_REQUIRES_INLINE_AXIS.bits()
+ ),
+ ),
+ feature!(
+ atom!("orientation"),
+ AllowsRanges::No,
+ keyword_evaluator!(eval_orientation, Orientation),
+ FeatureFlags::from_bits_truncate(
+ FeatureFlags::CONTAINER_REQUIRES_BLOCK_AXIS.bits() |
+ FeatureFlags::CONTAINER_REQUIRES_INLINE_AXIS.bits()
+ ),
+ ),
+];
+
+/// Result of a container size query, signifying the hypothetical containment boundary in terms of physical axes.
+/// Defined by up to two size containers. Queries on logical axes are resolved with respect to the querying
+/// element's writing mode.
+#[derive(Copy, Clone, Default)]
+pub struct ContainerSizeQueryResult {
+ width: Option<Au>,
+ height: Option<Au>,
+}
+
+impl ContainerSizeQueryResult {
+ fn get_viewport_size(context: &Context) -> Size2D<Au> {
+ use crate::values::specified::ViewportVariant;
+ context.viewport_size_for_viewport_unit_resolution(ViewportVariant::Small)
+ }
+
+ fn get_logical_viewport_size(context: &Context) -> LogicalSize<Au> {
+ LogicalSize::from_physical(
+ context.builder.writing_mode,
+ Self::get_viewport_size(context),
+ )
+ }
+
+ /// Get the inline-size of the query container.
+ pub fn get_container_inline_size(&self, context: &Context) -> Au {
+ if context.builder.writing_mode.is_horizontal() {
+ if let Some(w) = self.width {
+ return w;
+ }
+ } else {
+ if let Some(h) = self.height {
+ return h;
+ }
+ }
+ Self::get_logical_viewport_size(context).inline
+ }
+
+ /// Get the block-size of the query container.
+ pub fn get_container_block_size(&self, context: &Context) -> Au {
+ if context.builder.writing_mode.is_horizontal() {
+ self.get_container_height(context)
+ } else {
+ self.get_container_width(context)
+ }
+ }
+
+ /// Get the width of the query container.
+ pub fn get_container_width(&self, context: &Context) -> Au {
+ if let Some(w) = self.width {
+ return w;
+ }
+ Self::get_viewport_size(context).width
+ }
+
+ /// Get the height of the query container.
+ pub fn get_container_height(&self, context: &Context) -> Au {
+ if let Some(h) = self.height {
+ return h;
+ }
+ Self::get_viewport_size(context).height
+ }
+
+ // Merge the result of a subsequent lookup, preferring the initial result.
+ fn merge(self, new_result: Self) -> Self {
+ let mut result = self;
+ if let Some(width) = new_result.width {
+ result.width.get_or_insert(width);
+ }
+ if let Some(height) = new_result.height {
+ result.height.get_or_insert(height);
+ }
+ result
+ }
+
+ fn is_complete(&self) -> bool {
+ self.width.is_some() && self.height.is_some()
+ }
+}
+
+/// Unevaluated lazy container size query.
+pub enum ContainerSizeQuery<'a> {
+ /// Query prior to evaluation.
+ NotEvaluated(Box<dyn Fn() -> ContainerSizeQueryResult + 'a>),
+ /// Cached evaluated result.
+ Evaluated(ContainerSizeQueryResult),
+}
+
+impl<'a> ContainerSizeQuery<'a> {
+ fn evaluate_potential_size_container<E>(
+ e: E,
+ originating_element_style: Option<&ComputedValues>,
+ ) -> TraversalResult<ContainerSizeQueryResult>
+ where
+ E: TElement,
+ {
+ let data;
+ let style = match originating_element_style {
+ Some(s) => s,
+ None => {
+ data = match e.borrow_data() {
+ Some(d) => d,
+ None => return TraversalResult::InProgress,
+ };
+ &**data.styles.primary()
+ },
+ };
+ if !style
+ .flags
+ .contains(ComputedValueFlags::SELF_OR_ANCESTOR_HAS_SIZE_CONTAINER_TYPE)
+ {
+ // We know we won't find a size container.
+ return TraversalResult::StopTraversal;
+ }
+
+ let wm = style.writing_mode;
+ let box_style = style.get_box();
+
+ let container_type = box_style.clone_container_type();
+ let size = e.query_container_size(&box_style.clone_display());
+ match container_type {
+ ContainerType::Size => TraversalResult::Done(ContainerSizeQueryResult {
+ width: size.width,
+ height: size.height,
+ }),
+ ContainerType::InlineSize => {
+ if wm.is_horizontal() {
+ TraversalResult::Done(ContainerSizeQueryResult {
+ width: size.width,
+ height: None,
+ })
+ } else {
+ TraversalResult::Done(ContainerSizeQueryResult {
+ width: None,
+ height: size.height,
+ })
+ }
+ },
+ ContainerType::Normal => TraversalResult::InProgress,
+ }
+ }
+
+ /// Find the query container size for a given element. Meant to be used as a callback for new().
+ fn lookup<E>(
+ element: E,
+ originating_element_style: Option<&ComputedValues>,
+ ) -> ContainerSizeQueryResult
+ where
+ E: TElement + 'a,
+ {
+ match traverse_container(
+ element,
+ originating_element_style,
+ |e, originating_element_style| {
+ Self::evaluate_potential_size_container(e, originating_element_style)
+ },
+ ) {
+ Some((container, result)) => {
+ if result.is_complete() {
+ result
+ } else {
+ // Traverse up from the found size container to see if we can get a complete containment.
+ result.merge(Self::lookup(container, None))
+ }
+ },
+ None => ContainerSizeQueryResult::default(),
+ }
+ }
+
+ /// Create a new instance of the container size query for given element, with a deferred lookup callback.
+ pub fn for_element<E>(element: E, originating_element_style: Option<&'a ComputedValues>) -> Self
+ where
+ E: TElement + 'a,
+ {
+ let parent;
+ let data;
+ let style = match originating_element_style {
+ Some(s) => Some(s),
+ None => {
+ // No need to bother if we're the top element.
+ parent = match element.traversal_parent() {
+ Some(parent) => parent,
+ None => return Self::none(),
+ };
+ data = parent.borrow_data();
+ data.as_ref().map(|data| &**data.styles.primary())
+ },
+ };
+ let should_traverse = match style {
+ Some(style) => style
+ .flags
+ .contains(ComputedValueFlags::SELF_OR_ANCESTOR_HAS_SIZE_CONTAINER_TYPE),
+ None => true, // `display: none`, still want to show a correct computed value, so give it a try.
+ };
+ if should_traverse {
+ return Self::NotEvaluated(Box::new(move || {
+ Self::lookup(element, originating_element_style)
+ }));
+ }
+ Self::none()
+ }
+
+ /// Create a new instance, but with optional element.
+ pub fn for_option_element<E>(
+ element: Option<E>,
+ originating_element_style: Option<&'a ComputedValues>,
+ ) -> Self
+ where
+ E: TElement + 'a,
+ {
+ if let Some(e) = element {
+ Self::for_element(e, originating_element_style)
+ } else {
+ Self::none()
+ }
+ }
+
+ /// Create a query that evaluates to empty, for cases where container size query is not required.
+ pub fn none() -> Self {
+ ContainerSizeQuery::Evaluated(ContainerSizeQueryResult::default())
+ }
+
+ /// Get the result of the container size query, doing the lookup if called for the first time.
+ pub fn get(&mut self) -> ContainerSizeQueryResult {
+ match self {
+ Self::NotEvaluated(lookup) => {
+ *self = Self::Evaluated((lookup)());
+ match self {
+ Self::Evaluated(info) => *info,
+ _ => unreachable!("Just evaluated but not set?"),
+ }
+ },
+ Self::Evaluated(info) => *info,
+ }
+ }
+}
diff --git a/servo/components/style/stylesheets/counter_style_rule.rs b/servo/components/style/stylesheets/counter_style_rule.rs
new file mode 100644
index 0000000000..974b76b806
--- /dev/null
+++ b/servo/components/style/stylesheets/counter_style_rule.rs
@@ -0,0 +1,7 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+#![allow(missing_docs)]
+
+pub use crate::counter_style::CounterStyleRuleData as CounterStyleRule;
diff --git a/servo/components/style/stylesheets/document_rule.rs b/servo/components/style/stylesheets/document_rule.rs
new file mode 100644
index 0000000000..c4e7085580
--- /dev/null
+++ b/servo/components/style/stylesheets/document_rule.rs
@@ -0,0 +1,309 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+//! [@document rules](https://www.w3.org/TR/2012/WD-css3-conditional-20120911/#at-document)
+//! initially in CSS Conditional Rules Module Level 3, @document has been postponed to the level 4.
+//! We implement the prefixed `@-moz-document`.
+
+use crate::media_queries::Device;
+use crate::parser::{Parse, ParserContext};
+use crate::shared_lock::{DeepCloneParams, DeepCloneWithLock, Locked};
+use crate::shared_lock::{SharedRwLock, SharedRwLockReadGuard, ToCssWithGuard};
+use crate::str::CssStringWriter;
+use crate::stylesheets::CssRules;
+use crate::values::CssUrl;
+use cssparser::{Parser, SourceLocation};
+#[cfg(feature = "gecko")]
+use malloc_size_of::{MallocSizeOfOps, MallocUnconditionalShallowSizeOf};
+use servo_arc::Arc;
+use std::fmt::{self, Write};
+use style_traits::{CssWriter, ParseError, StyleParseErrorKind, ToCss};
+
+#[derive(Debug, ToShmem)]
+/// 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 DocumentRule {
+ /// Measure heap usage.
+ #[cfg(feature = "gecko")]
+ pub fn size_of(&self, guard: &SharedRwLockReadGuard, ops: &mut MallocSizeOfOps) -> usize {
+ // Measurement of other fields may be added later.
+ self.rules.unconditional_shallow_size_of(ops) +
+ self.rules.read_with(guard).size_of(guard, ops)
+ }
+}
+
+impl ToCssWithGuard for DocumentRule {
+ fn to_css(&self, guard: &SharedRwLockReadGuard, dest: &mut CssStringWriter) -> fmt::Result {
+ dest.write_str("@-moz-document ")?;
+ self.condition.to_css(&mut CssWriter::new(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 DocumentRule {
+ /// Deep clones this DocumentRule.
+ fn deep_clone_with_lock(
+ &self,
+ lock: &SharedRwLock,
+ guard: &SharedRwLockReadGuard,
+ params: &DeepCloneParams,
+ ) -> 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, params))),
+ source_location: self.source_location.clone(),
+ }
+ }
+}
+
+/// The kind of media document that the rule will match.
+#[derive(Clone, Copy, Debug, Parse, PartialEq, ToCss, ToShmem)]
+#[allow(missing_docs)]
+pub enum MediaDocumentKind {
+ All,
+ Plugin,
+ Image,
+ Video,
+}
+
+/// A matching function for a `@document` rule's condition.
+#[derive(Clone, Debug, ToCss, ToShmem)]
+pub enum DocumentMatchingFunction {
+ /// Exact URL matching function. It evaluates to true whenever the
+ /// URL of the document being styled is exactly the URL given.
+ Url(CssUrl),
+ /// URL prefix matching function. It evaluates to true whenever the
+ /// URL of the document being styled has the argument to the
+ /// function as an initial substring (which is true when the two
+ /// strings are equal). When the argument is the empty string,
+ /// it evaluates to true for all documents.
+ #[css(function)]
+ UrlPrefix(String),
+ /// Domain matching function. It evaluates to true whenever the URL
+ /// of the document being styled has a host subcomponent and that
+ /// host subcomponent is exactly the argument to the ‘domain()’
+ /// function or a final substring of the host component is a
+ /// period (U+002E) immediately followed by the argument to the
+ /// ‘domain()’ function.
+ #[css(function)]
+ Domain(String),
+ /// Regular expression matching function. It evaluates to true
+ /// whenever the regular expression matches the entirety of the URL
+ /// of the document being styled.
+ #[css(function)]
+ Regexp(String),
+ /// Matching function for a media document.
+ #[css(function)]
+ MediaDocument(MediaDocumentKind),
+ /// Matching function for a plain-text document.
+ #[css(function)]
+ PlainTextDocument(()),
+ /// Matching function for a document that can be observed by other content
+ /// documents.
+ #[css(function)]
+ UnobservableDocument(()),
+}
+
+macro_rules! parse_quoted_or_unquoted_string {
+ ($input:ident, $url_matching_function:expr) => {
+ $input.parse_nested_block(|input| {
+ let start = input.position();
+ input
+ .parse_entirely(|input| {
+ let string = input.expect_string()?;
+ Ok($url_matching_function(string.as_ref().to_owned()))
+ })
+ .or_else(|_: ParseError| {
+ while let Ok(_) = input.next() {}
+ Ok($url_matching_function(input.slice_from(start).to_string()))
+ })
+ })
+ };
+}
+
+impl DocumentMatchingFunction {
+ /// Parse a URL matching function for a`@document` rule's condition.
+ pub fn parse<'i, 't>(
+ context: &ParserContext,
+ input: &mut Parser<'i, 't>,
+ ) -> Result<Self, ParseError<'i>> {
+ if let Ok(url) = input.try_parse(|input| CssUrl::parse(context, input)) {
+ return Ok(DocumentMatchingFunction::Url(url));
+ }
+
+ let location = input.current_source_location();
+ let function = input.expect_function()?.clone();
+ match_ignore_ascii_case! { &function,
+ "url-prefix" => {
+ parse_quoted_or_unquoted_string!(input, DocumentMatchingFunction::UrlPrefix)
+ },
+ "domain" => {
+ parse_quoted_or_unquoted_string!(input, DocumentMatchingFunction::Domain)
+ },
+ "regexp" => {
+ input.parse_nested_block(|input| {
+ Ok(DocumentMatchingFunction::Regexp(
+ input.expect_string()?.as_ref().to_owned(),
+ ))
+ })
+ },
+ "media-document" => {
+ input.parse_nested_block(|input| {
+ let kind = MediaDocumentKind::parse(input)?;
+ Ok(DocumentMatchingFunction::MediaDocument(kind))
+ })
+ },
+
+ "plain-text-document" => {
+ input.parse_nested_block(|input| {
+ input.expect_exhausted()?;
+ Ok(DocumentMatchingFunction::PlainTextDocument(()))
+ })
+ },
+
+ "unobservable-document" => {
+ input.parse_nested_block(|input| {
+ input.expect_exhausted()?;
+ Ok(DocumentMatchingFunction::UnobservableDocument(()))
+ })
+ },
+
+ _ => {
+ Err(location.new_custom_error(
+ StyleParseErrorKind::UnexpectedFunction(function.clone())
+ ))
+ },
+ }
+ }
+
+ #[cfg(feature = "gecko")]
+ /// Evaluate a URL matching function.
+ pub fn evaluate(&self, device: &Device) -> bool {
+ use crate::gecko_bindings::bindings::Gecko_DocumentRule_UseForPresentation;
+ use crate::gecko_bindings::structs::DocumentMatchingFunction as GeckoDocumentMatchingFunction;
+ use nsstring::nsCStr;
+
+ let func = match *self {
+ DocumentMatchingFunction::Url(_) => GeckoDocumentMatchingFunction::URL,
+ DocumentMatchingFunction::UrlPrefix(_) => GeckoDocumentMatchingFunction::URLPrefix,
+ DocumentMatchingFunction::Domain(_) => GeckoDocumentMatchingFunction::Domain,
+ DocumentMatchingFunction::Regexp(_) => GeckoDocumentMatchingFunction::RegExp,
+ DocumentMatchingFunction::MediaDocument(_) => {
+ GeckoDocumentMatchingFunction::MediaDocument
+ },
+ DocumentMatchingFunction::PlainTextDocument(..) => {
+ GeckoDocumentMatchingFunction::PlainTextDocument
+ },
+ DocumentMatchingFunction::UnobservableDocument(..) => {
+ GeckoDocumentMatchingFunction::UnobservableDocument
+ },
+ };
+
+ let pattern = nsCStr::from(match *self {
+ DocumentMatchingFunction::Url(ref url) => url.as_str(),
+ DocumentMatchingFunction::UrlPrefix(ref pat) |
+ DocumentMatchingFunction::Domain(ref pat) |
+ DocumentMatchingFunction::Regexp(ref pat) => pat,
+ DocumentMatchingFunction::MediaDocument(kind) => match kind {
+ MediaDocumentKind::All => "all",
+ MediaDocumentKind::Image => "image",
+ MediaDocumentKind::Plugin => "plugin",
+ MediaDocumentKind::Video => "video",
+ },
+ DocumentMatchingFunction::PlainTextDocument(()) |
+ DocumentMatchingFunction::UnobservableDocument(()) => "",
+ });
+ unsafe { Gecko_DocumentRule_UseForPresentation(device.document(), &*pattern, func) }
+ }
+
+ #[cfg(not(feature = "gecko"))]
+ /// Evaluate a URL matching function.
+ pub fn evaluate(&self, _: &Device) -> bool {
+ false
+ }
+}
+
+/// A `@document` rule's condition.
+///
+/// <https://www.w3.org/TR/2012/WD-css3-conditional-20120911/#at-document>
+///
+/// The `@document` rule's condition is written as a comma-separated list of
+/// URL matching functions, and the condition evaluates to true whenever any
+/// one of those functions evaluates to true.
+#[derive(Clone, Debug, ToCss, ToShmem)]
+#[css(comma)]
+pub struct DocumentCondition(#[css(iterable)] Vec<DocumentMatchingFunction>);
+
+impl DocumentCondition {
+ /// Parse a document condition.
+ pub fn parse<'i, 't>(
+ context: &ParserContext,
+ input: &mut Parser<'i, 't>,
+ ) -> Result<Self, ParseError<'i>> {
+ let conditions =
+ input.parse_comma_separated(|input| DocumentMatchingFunction::parse(context, input))?;
+
+ let condition = DocumentCondition(conditions);
+ if !condition.allowed_in(context) {
+ return Err(
+ input.new_custom_error(StyleParseErrorKind::UnsupportedAtRule(
+ "-moz-document".into(),
+ )),
+ );
+ }
+ Ok(condition)
+ }
+
+ /// Evaluate a document condition.
+ pub fn evaluate(&self, device: &Device) -> bool {
+ self.0
+ .iter()
+ .any(|url_matching_function| url_matching_function.evaluate(device))
+ }
+
+ #[cfg(feature = "servo")]
+ fn allowed_in(&self, _: &ParserContext) -> bool {
+ false
+ }
+
+ #[cfg(feature = "gecko")]
+ fn allowed_in(&self, context: &ParserContext) -> bool {
+ use static_prefs::pref;
+
+ if context.in_ua_or_chrome_sheet() {
+ return true;
+ }
+
+ if pref!("layout.css.moz-document.content.enabled") {
+ return true;
+ }
+
+ // Allow a single url-prefix() for compatibility.
+ //
+ // See bug 1446470 and dependencies.
+ if self.0.len() != 1 {
+ return false;
+ }
+
+ // NOTE(emilio): This technically allows url-prefix("") too, but...
+ match self.0[0] {
+ DocumentMatchingFunction::UrlPrefix(ref prefix) => prefix.is_empty(),
+ _ => false,
+ }
+ }
+}
diff --git a/servo/components/style/stylesheets/font_face_rule.rs b/servo/components/style/stylesheets/font_face_rule.rs
new file mode 100644
index 0000000000..78f3b338b2
--- /dev/null
+++ b/servo/components/style/stylesheets/font_face_rule.rs
@@ -0,0 +1,7 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+#![allow(missing_docs)]
+
+pub use crate::font_face::FontFaceRuleData as FontFaceRule;
diff --git a/servo/components/style/stylesheets/font_feature_values_rule.rs b/servo/components/style/stylesheets/font_feature_values_rule.rs
new file mode 100644
index 0000000000..784628f0dd
--- /dev/null
+++ b/servo/components/style/stylesheets/font_feature_values_rule.rs
@@ -0,0 +1,463 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+//! The [`@font-feature-values`][font-feature-values] at-rule.
+//!
+//! [font-feature-values]: https://drafts.csswg.org/css-fonts-3/#at-font-feature-values-rule
+
+use crate::error_reporting::ContextualParseError;
+#[cfg(feature = "gecko")]
+use crate::gecko_bindings::bindings::Gecko_AppendFeatureValueHashEntry;
+#[cfg(feature = "gecko")]
+use crate::gecko_bindings::structs::{self, gfxFontFeatureValueSet, nsTArray};
+use crate::parser::{Parse, ParserContext};
+use crate::shared_lock::{SharedRwLockReadGuard, ToCssWithGuard};
+use crate::str::CssStringWriter;
+use crate::stylesheets::CssRuleType;
+use crate::values::computed::font::FamilyName;
+use crate::values::serialize_atom_identifier;
+use crate::Atom;
+use cssparser::{AtRuleParser, BasicParseErrorKind, CowRcStr};
+use cssparser::{DeclarationListParser, DeclarationParser, Parser};
+use cssparser::{ParserState, QualifiedRuleParser, RuleListParser, SourceLocation, Token};
+use std::fmt::{self, Write};
+use style_traits::{CssWriter, ParseError, StyleParseErrorKind, ToCss};
+
+/// A @font-feature-values block declaration.
+/// It is `<ident>: <integer>+`.
+/// This struct can take 3 value types.
+/// - `SingleValue` is to keep just one unsigned integer value.
+/// - `PairValues` is to keep one or two unsigned integer values.
+/// - `VectorValues` is to keep a list of unsigned integer values.
+#[derive(Clone, Debug, PartialEq, ToShmem)]
+pub struct FFVDeclaration<T> {
+ /// An `<ident>` for declaration name.
+ pub name: Atom,
+ /// An `<integer>+` for declaration value.
+ pub value: T,
+}
+
+impl<T: ToCss> ToCss for FFVDeclaration<T> {
+ fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
+ where
+ W: Write,
+ {
+ serialize_atom_identifier(&self.name, dest)?;
+ dest.write_str(": ")?;
+ self.value.to_css(dest)?;
+ dest.write_str(";")
+ }
+}
+
+/// A trait for @font-feature-values rule to gecko values conversion.
+#[cfg(feature = "gecko")]
+pub trait ToGeckoFontFeatureValues {
+ /// Sets the equivalent of declaration to gecko `nsTArray<u32>` array.
+ fn to_gecko_font_feature_values(&self, array: &mut nsTArray<u32>);
+}
+
+/// A @font-feature-values block declaration value that keeps one value.
+#[derive(Clone, Debug, PartialEq, ToCss, ToShmem)]
+pub struct SingleValue(pub u32);
+
+impl Parse for SingleValue {
+ fn parse<'i, 't>(
+ _context: &ParserContext,
+ input: &mut Parser<'i, 't>,
+ ) -> Result<SingleValue, ParseError<'i>> {
+ let location = input.current_source_location();
+ match *input.next()? {
+ Token::Number {
+ int_value: Some(v), ..
+ } if v >= 0 => Ok(SingleValue(v as u32)),
+ ref t => Err(location.new_unexpected_token_error(t.clone())),
+ }
+ }
+}
+
+#[cfg(feature = "gecko")]
+impl ToGeckoFontFeatureValues for SingleValue {
+ fn to_gecko_font_feature_values(&self, array: &mut nsTArray<u32>) {
+ unsafe {
+ array.set_len_pod(1);
+ }
+ array[0] = self.0 as u32;
+ }
+}
+
+/// A @font-feature-values block declaration value that keeps one or two values.
+#[derive(Clone, Debug, PartialEq, ToCss, ToShmem)]
+pub struct PairValues(pub u32, pub Option<u32>);
+
+impl Parse for PairValues {
+ fn parse<'i, 't>(
+ _context: &ParserContext,
+ input: &mut Parser<'i, 't>,
+ ) -> Result<PairValues, ParseError<'i>> {
+ let location = input.current_source_location();
+ let first = match *input.next()? {
+ Token::Number {
+ int_value: Some(a), ..
+ } if a >= 0 => a as u32,
+ ref t => return Err(location.new_unexpected_token_error(t.clone())),
+ };
+ let location = input.current_source_location();
+ match input.next() {
+ Ok(&Token::Number {
+ int_value: Some(b), ..
+ }) if b >= 0 => Ok(PairValues(first, Some(b as u32))),
+ // It can't be anything other than number.
+ Ok(t) => Err(location.new_unexpected_token_error(t.clone())),
+ // It can be just one value.
+ Err(_) => Ok(PairValues(first, None)),
+ }
+ }
+}
+
+#[cfg(feature = "gecko")]
+impl ToGeckoFontFeatureValues for PairValues {
+ fn to_gecko_font_feature_values(&self, array: &mut nsTArray<u32>) {
+ let len = if self.1.is_some() { 2 } else { 1 };
+
+ unsafe {
+ array.set_len_pod(len);
+ }
+ array[0] = self.0 as u32;
+ if let Some(second) = self.1 {
+ array[1] = second as u32;
+ };
+ }
+}
+
+/// A @font-feature-values block declaration value that keeps a list of values.
+#[derive(Clone, Debug, PartialEq, ToCss, ToShmem)]
+pub struct VectorValues(#[css(iterable)] pub Vec<u32>);
+
+impl Parse for VectorValues {
+ fn parse<'i, 't>(
+ _context: &ParserContext,
+ input: &mut Parser<'i, 't>,
+ ) -> Result<VectorValues, ParseError<'i>> {
+ let mut vec = vec![];
+ loop {
+ let location = input.current_source_location();
+ match input.next() {
+ Ok(&Token::Number {
+ int_value: Some(a), ..
+ }) if a >= 0 => {
+ vec.push(a as u32);
+ },
+ // It can't be anything other than number.
+ Ok(t) => return Err(location.new_unexpected_token_error(t.clone())),
+ Err(_) => break,
+ }
+ }
+
+ if vec.len() == 0 {
+ return Err(input.new_error(BasicParseErrorKind::EndOfInput));
+ }
+
+ Ok(VectorValues(vec))
+ }
+}
+
+#[cfg(feature = "gecko")]
+impl ToGeckoFontFeatureValues for VectorValues {
+ fn to_gecko_font_feature_values(&self, array: &mut nsTArray<u32>) {
+ array.assign_from_iter_pod(self.0.iter().map(|v| *v));
+ }
+}
+
+/// Parses a list of `FamilyName`s.
+pub fn parse_family_name_list<'i, 't>(
+ context: &ParserContext,
+ input: &mut Parser<'i, 't>,
+) -> Result<Vec<FamilyName>, ParseError<'i>> {
+ input
+ .parse_comma_separated(|i| FamilyName::parse(context, i))
+ .map_err(|e| e.into())
+}
+
+/// @font-feature-values inside block parser. Parses a list of `FFVDeclaration`.
+/// (`<ident>: <integer>+`)
+struct FFVDeclarationsParser<'a, 'b: 'a, T: 'a> {
+ context: &'a ParserContext<'b>,
+ declarations: &'a mut Vec<FFVDeclaration<T>>,
+}
+
+/// Default methods reject all at rules.
+impl<'a, 'b, 'i, T> AtRuleParser<'i> for FFVDeclarationsParser<'a, 'b, T> {
+ type Prelude = ();
+ type AtRule = ();
+ type Error = StyleParseErrorKind<'i>;
+}
+
+impl<'a, 'b, 'i, T> DeclarationParser<'i> for FFVDeclarationsParser<'a, 'b, T>
+where
+ T: Parse,
+{
+ type Declaration = ();
+ type Error = StyleParseErrorKind<'i>;
+
+ fn parse_value<'t>(
+ &mut self,
+ name: CowRcStr<'i>,
+ input: &mut Parser<'i, 't>,
+ ) -> Result<(), ParseError<'i>> {
+ let value = input.parse_entirely(|i| T::parse(self.context, i))?;
+ let new = FFVDeclaration {
+ name: Atom::from(&*name),
+ value: value,
+ };
+ update_or_push(&mut self.declarations, new);
+ Ok(())
+ }
+}
+
+macro_rules! font_feature_values_blocks {
+ (
+ blocks = [
+ $( #[$doc: meta] $name: tt $ident: ident / $ident_camel: ident / $gecko_enum: ident: $ty: ty, )*
+ ]
+ ) => {
+ /// The [`@font-feature-values`][font-feature-values] at-rule.
+ ///
+ /// [font-feature-values]: https://drafts.csswg.org/css-fonts-3/#at-font-feature-values-rule
+ #[derive(Clone, Debug, PartialEq, ToShmem)]
+ pub struct FontFeatureValuesRule {
+ /// Font family list for @font-feature-values rule.
+ /// Family names cannot contain generic families. FamilyName
+ /// also accepts only non-generic names.
+ pub family_names: Vec<FamilyName>,
+ $(
+ #[$doc]
+ pub $ident: Vec<FFVDeclaration<$ty>>,
+ )*
+ /// The line and column of the rule's source code.
+ pub source_location: SourceLocation,
+ }
+
+ impl FontFeatureValuesRule {
+ /// Creates an empty FontFeatureValuesRule with given location and family name list.
+ fn new(family_names: Vec<FamilyName>, location: SourceLocation) -> Self {
+ FontFeatureValuesRule {
+ family_names: family_names,
+ $(
+ $ident: vec![],
+ )*
+ source_location: location,
+ }
+ }
+
+ /// Parses a `FontFeatureValuesRule`.
+ pub fn parse(
+ context: &ParserContext,
+ input: &mut Parser,
+ family_names: Vec<FamilyName>,
+ location: SourceLocation,
+ ) -> Self {
+ let mut rule = FontFeatureValuesRule::new(family_names, location);
+
+ {
+ let mut iter = RuleListParser::new_for_nested_rule(input, FontFeatureValuesRuleParser {
+ context: context,
+ rule: &mut rule,
+ });
+ while let Some(result) = iter.next() {
+ if let Err((error, slice)) = result {
+ let location = error.location;
+ let error = ContextualParseError::UnsupportedRule(slice, error);
+ context.log_css_error(location, error);
+ }
+ }
+ }
+ rule
+ }
+
+ /// Prints inside of `@font-feature-values` block.
+ pub fn value_to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
+ where
+ W: Write,
+ {
+ $(
+ if self.$ident.len() > 0 {
+ dest.write_str(concat!("@", $name, " {\n"))?;
+ let iter = self.$ident.iter();
+ for val in iter {
+ val.to_css(dest)?;
+ dest.write_str("\n")?
+ }
+ dest.write_str("}\n")?
+ }
+ )*
+ Ok(())
+ }
+
+ /// Returns length of all at-rules.
+ pub fn len(&self) -> usize {
+ let mut len = 0;
+ $(
+ len += self.$ident.len();
+ )*
+ len
+ }
+
+ /// Convert to Gecko gfxFontFeatureValueSet.
+ #[cfg(feature = "gecko")]
+ pub fn set_at_rules(&self, dest: *mut gfxFontFeatureValueSet) {
+ for ref family in self.family_names.iter() {
+ let family = family.name.to_ascii_lowercase();
+ $(
+ if self.$ident.len() > 0 {
+ for val in self.$ident.iter() {
+ let array = unsafe {
+ Gecko_AppendFeatureValueHashEntry(
+ dest,
+ family.as_ptr(),
+ structs::$gecko_enum,
+ val.name.as_ptr()
+ )
+ };
+ unsafe {
+ val.value.to_gecko_font_feature_values(&mut *array);
+ }
+ }
+ }
+ )*
+ }
+ }
+ }
+
+ impl ToCssWithGuard for FontFeatureValuesRule {
+ fn to_css(&self, _guard: &SharedRwLockReadGuard, dest: &mut CssStringWriter) -> fmt::Result {
+ dest.write_str("@font-feature-values ")?;
+ self.family_names.to_css(&mut CssWriter::new(dest))?;
+ dest.write_str(" {\n")?;
+ self.value_to_css(&mut CssWriter::new(dest))?;
+ dest.write_str("}")
+ }
+ }
+
+ /// Updates with new value if same `ident` exists, otherwise pushes to the vector.
+ fn update_or_push<T>(vec: &mut Vec<FFVDeclaration<T>>, element: FFVDeclaration<T>) {
+ let position = vec.iter().position(|ref val| val.name == element.name);
+ if let Some(index) = position {
+ vec[index].value = element.value;
+ } else {
+ vec.push(element);
+ }
+ }
+
+ /// Keeps the information about block type like @swash, @styleset etc.
+ enum BlockType {
+ $(
+ $ident_camel,
+ )*
+ }
+
+ /// Parser for `FontFeatureValuesRule`. Parses all blocks
+ /// <feature-type> {
+ /// <feature-value-declaration-list>
+ /// }
+ /// <feature-type> = @stylistic | @historical-forms | @styleset |
+ /// @character-variant | @swash | @ornaments | @annotation
+ struct FontFeatureValuesRuleParser<'a> {
+ context: &'a ParserContext<'a>,
+ rule: &'a mut FontFeatureValuesRule,
+ }
+
+ /// Default methods reject all qualified rules.
+ impl<'a, 'i> QualifiedRuleParser<'i> for FontFeatureValuesRuleParser<'a> {
+ type Prelude = ();
+ type QualifiedRule = ();
+ type Error = StyleParseErrorKind<'i>;
+ }
+
+ impl<'a, 'i> AtRuleParser<'i> for FontFeatureValuesRuleParser<'a> {
+ type Prelude = BlockType;
+ type AtRule = ();
+ type Error = StyleParseErrorKind<'i>;
+
+ fn parse_prelude<'t>(
+ &mut self,
+ name: CowRcStr<'i>,
+ input: &mut Parser<'i, 't>,
+ ) -> Result<BlockType, ParseError<'i>> {
+ match_ignore_ascii_case! { &*name,
+ $(
+ $name => Ok(BlockType::$ident_camel),
+ )*
+ _ => Err(input.new_error(BasicParseErrorKind::AtRuleBodyInvalid)),
+ }
+ }
+
+ fn parse_block<'t>(
+ &mut self,
+ prelude: BlockType,
+ _: &ParserState,
+ input: &mut Parser<'i, 't>
+ ) -> Result<Self::AtRule, ParseError<'i>> {
+ debug_assert_eq!(self.context.rule_type(), CssRuleType::FontFeatureValues);
+ match prelude {
+ $(
+ BlockType::$ident_camel => {
+ let parser = FFVDeclarationsParser {
+ context: &self.context,
+ declarations: &mut self.rule.$ident,
+ };
+
+ let mut iter = DeclarationListParser::new(input, parser);
+ while let Some(declaration) = iter.next() {
+ if let Err((error, slice)) = declaration {
+ let location = error.location;
+ let error = ContextualParseError::UnsupportedKeyframePropertyDeclaration(
+ slice, error
+ );
+ self.context.log_css_error(location, error);
+ }
+ }
+ },
+ )*
+ }
+
+ Ok(())
+ }
+ }
+ }
+}
+
+font_feature_values_blocks! {
+ blocks = [
+ #[doc = "A @swash blocksck. \
+ Specifies a feature name that will work with the swash() \
+ functional notation of font-variant-alternates."]
+ "swash" swash / Swash / NS_FONT_VARIANT_ALTERNATES_SWASH: SingleValue,
+
+ #[doc = "A @stylistic block. \
+ Specifies a feature name that will work with the annotation() \
+ functional notation of font-variant-alternates."]
+ "stylistic" stylistic / Stylistic / NS_FONT_VARIANT_ALTERNATES_STYLISTIC: SingleValue,
+
+ #[doc = "A @ornaments block. \
+ Specifies a feature name that will work with the ornaments() ] \
+ functional notation of font-variant-alternates."]
+ "ornaments" ornaments / Ornaments / NS_FONT_VARIANT_ALTERNATES_ORNAMENTS: SingleValue,
+
+ #[doc = "A @annotation block. \
+ Specifies a feature name that will work with the stylistic() \
+ functional notation of font-variant-alternates."]
+ "annotation" annotation / Annotation / NS_FONT_VARIANT_ALTERNATES_ANNOTATION: SingleValue,
+
+ #[doc = "A @character-variant block. \
+ Specifies a feature name that will work with the styleset() \
+ functional notation of font-variant-alternates. The value can be a pair."]
+ "character-variant" character_variant / CharacterVariant / NS_FONT_VARIANT_ALTERNATES_CHARACTER_VARIANT:
+ PairValues,
+
+ #[doc = "A @styleset block. \
+ Specifies a feature name that will work with the character-variant() \
+ functional notation of font-variant-alternates. The value can be a list."]
+ "styleset" styleset / Styleset / NS_FONT_VARIANT_ALTERNATES_STYLESET: VectorValues,
+ ]
+}
diff --git a/servo/components/style/stylesheets/font_palette_values_rule.rs b/servo/components/style/stylesheets/font_palette_values_rule.rs
new file mode 100644
index 0000000000..a5422ce725
--- /dev/null
+++ b/servo/components/style/stylesheets/font_palette_values_rule.rs
@@ -0,0 +1,252 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+//! The [`@font-palette-values`][font-palette-values] at-rule.
+//!
+//! [font-palette-values]: https://drafts.csswg.org/css-fonts/#font-palette-values
+
+use crate::error_reporting::ContextualParseError;
+use crate::parser::{Parse, ParserContext};
+use crate::gecko_bindings::bindings::Gecko_AppendPaletteValueHashEntry;
+use crate::gecko_bindings::bindings::{Gecko_SetFontPaletteBase, Gecko_SetFontPaletteOverride};
+use crate::gecko_bindings::structs::gfx::FontPaletteValueSet;
+use crate::gecko_bindings::structs::gfx::FontPaletteValueSet_PaletteValues_kLight;
+use crate::gecko_bindings::structs::gfx::FontPaletteValueSet_PaletteValues_kDark;
+use crate::shared_lock::{SharedRwLockReadGuard, ToCssWithGuard};
+use crate::str::CssStringWriter;
+use crate::values::computed::font::FamilyName;
+use crate::values::specified::Color as SpecifiedColor;
+use crate::values::specified::NonNegativeInteger;
+use crate::values::DashedIdent;
+use cssparser::{AtRuleParser, CowRcStr};
+use cssparser::{DeclarationParser, DeclarationListParser, Parser};
+use cssparser::{QualifiedRuleParser, SourceLocation};
+use std::fmt::{self, Write};
+use style_traits::{CssWriter, ParseError, StyleParseErrorKind, ToCss};
+use style_traits::{Comma, OneOrMoreSeparated};
+use selectors::parser::SelectorParseErrorKind;
+use crate::stylesheets::font_feature_values_rule::parse_family_name_list;
+
+#[allow(missing_docs)]
+#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem)]
+pub struct FontPaletteOverrideColor {
+ index: NonNegativeInteger,
+ color: SpecifiedColor,
+}
+
+impl Parse for FontPaletteOverrideColor {
+ fn parse<'i, 't>(
+ context: &ParserContext,
+ input: &mut Parser<'i, 't>,
+ ) -> Result<FontPaletteOverrideColor, ParseError<'i>> {
+ let index = NonNegativeInteger::parse(context, input)?;
+ let location = input.current_source_location();
+ let color = SpecifiedColor::parse(context, input)?;
+ // Only absolute colors are accepted here.
+ if let SpecifiedColor::Numeric { parsed: _, authored: _ } = color {
+ Ok(FontPaletteOverrideColor{ index, color })
+ } else {
+ Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError))
+ }
+ }
+}
+
+impl ToCss for FontPaletteOverrideColor {
+ fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
+ where
+ W: fmt::Write,
+ {
+ self.index.to_css(dest)?;
+ dest.write_str(" ")?;
+ self.color.to_css(dest)
+ }
+}
+
+impl OneOrMoreSeparated for FontPaletteOverrideColor {
+ type S = Comma;
+}
+
+impl OneOrMoreSeparated for FamilyName {
+ type S = Comma;
+}
+
+#[allow(missing_docs)]
+#[derive(Clone, Debug, MallocSizeOf, Parse, PartialEq, ToCss, ToShmem)]
+pub enum FontPaletteBase {
+ Light,
+ Dark,
+ Index(NonNegativeInteger),
+}
+
+/// The [`@font-palette-values`][font-palette-values] at-rule.
+///
+/// [font-palette-values]: https://drafts.csswg.org/css-fonts/#font-palette-values
+#[derive(Clone, Debug, PartialEq, ToShmem)]
+pub struct FontPaletteValuesRule {
+ /// Palette name.
+ pub name: DashedIdent,
+ /// Font family list for @font-palette-values rule.
+ /// Family names cannot contain generic families. FamilyName
+ /// also accepts only non-generic names.
+ pub family_names: Vec<FamilyName>,
+ /// The base palette.
+ pub base_palette: Option<FontPaletteBase>,
+ /// The list of override colors.
+ pub override_colors: Vec<FontPaletteOverrideColor>,
+ /// The line and column of the rule's source code.
+ pub source_location: SourceLocation,
+}
+
+impl FontPaletteValuesRule {
+ /// Creates an empty FontPaletteValuesRule with given location and name.
+ fn new(name: DashedIdent, location: SourceLocation) -> Self {
+ FontPaletteValuesRule {
+ name,
+ family_names: vec![],
+ base_palette: None,
+ override_colors: vec![],
+ source_location: location,
+ }
+ }
+
+ /// Parses a `FontPaletteValuesRule`.
+ pub fn parse(
+ context: &ParserContext,
+ input: &mut Parser,
+ name: DashedIdent,
+ location: SourceLocation,
+ ) -> Self {
+ let mut rule = FontPaletteValuesRule::new(name, location);
+ {
+ let parser = FontPaletteValuesDeclarationParser {
+ context: context,
+ rule: &mut rule,
+ };
+ let mut iter = DeclarationListParser::new(input, parser);
+ while let Some(declaration) = iter.next() {
+ if let Err((error, slice)) = declaration {
+ let location = error.location;
+ let error = ContextualParseError::UnsupportedFontPaletteValuesDescriptor(slice, error);
+ context.log_css_error(location, error);
+ }
+ }
+ }
+ rule
+ }
+
+ /// Prints inside of `@font-palette-values` block.
+ fn value_to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
+ where
+ W: Write,
+ {
+ if !self.family_names.is_empty() {
+ dest.write_str("font-family: ")?;
+ self.family_names.to_css(dest)?;
+ dest.write_str("; ")?;
+ }
+ if let Some(base) = &self.base_palette {
+ dest.write_str("base-palette: ")?;
+ base.to_css(dest)?;
+ dest.write_str("; ")?;
+ }
+ if !self.override_colors.is_empty() {
+ dest.write_str("override-colors: ")?;
+ self.override_colors.to_css(dest)?;
+ dest.write_str("; ")?;
+ }
+ Ok(())
+ }
+
+ /// Convert to Gecko FontPaletteValueSet.
+ pub fn to_gecko_palette_value_set(&self, dest: *mut FontPaletteValueSet) {
+ for ref family in self.family_names.iter() {
+ let family = family.name.to_ascii_lowercase();
+ let palette_values = unsafe {
+ Gecko_AppendPaletteValueHashEntry(
+ dest,
+ family.as_ptr(),
+ self.name.0.as_ptr()
+ )
+ };
+ if let Some(base_palette) = &self.base_palette {
+ unsafe {
+ Gecko_SetFontPaletteBase(palette_values, match &base_palette {
+ FontPaletteBase::Light => FontPaletteValueSet_PaletteValues_kLight,
+ FontPaletteBase::Dark => FontPaletteValueSet_PaletteValues_kDark,
+ FontPaletteBase::Index(i) => i.0.value() as i32,
+ });
+ }
+ }
+ for c in &self.override_colors {
+ if let SpecifiedColor::Numeric { parsed, authored: _ } = &c.color {
+ unsafe {
+ Gecko_SetFontPaletteOverride(palette_values,
+ c.index.0.value(),
+ *parsed);
+ }
+ }
+ }
+ }
+ }
+}
+
+impl ToCssWithGuard for FontPaletteValuesRule {
+ fn to_css(&self, _guard: &SharedRwLockReadGuard, dest: &mut CssStringWriter) -> fmt::Result {
+ dest.write_str("@font-palette-values ")?;
+ self.name.to_css(&mut CssWriter::new(dest))?;
+ dest.write_str(" { ")?;
+ self.value_to_css(&mut CssWriter::new(dest))?;
+ dest.write_str("}")
+ }
+}
+
+/// Parser for declarations in `FontPaletteValuesRule`.
+struct FontPaletteValuesDeclarationParser<'a> {
+ context: &'a ParserContext<'a>,
+ rule: &'a mut FontPaletteValuesRule,
+}
+
+impl<'a, 'i> AtRuleParser<'i> for FontPaletteValuesDeclarationParser<'a> {
+ type Prelude = ();
+ type AtRule = ();
+ type Error = StyleParseErrorKind<'i>;
+}
+
+impl<'a, 'i> QualifiedRuleParser<'i> for FontPaletteValuesDeclarationParser<'a> {
+ type Prelude = ();
+ type QualifiedRule = ();
+ type Error = StyleParseErrorKind<'i>;
+}
+
+fn parse_override_colors<'i, 't>(
+ context: &ParserContext,
+ input: &mut Parser<'i, 't>,
+) -> Result<Vec<FontPaletteOverrideColor>, ParseError<'i>> {
+ input.parse_comma_separated(|i| FontPaletteOverrideColor::parse(context, i))
+}
+
+impl<'a, 'b, 'i> DeclarationParser<'i> for FontPaletteValuesDeclarationParser<'a> {
+ type Declaration = ();
+ type Error = StyleParseErrorKind<'i>;
+
+ fn parse_value<'t>(
+ &mut self,
+ name: CowRcStr<'i>,
+ input: &mut Parser<'i, 't>,
+ ) -> Result<(), ParseError<'i>> {
+ match_ignore_ascii_case! { &*name,
+ "font-family" => {
+ self.rule.family_names = parse_family_name_list(self.context, input)?
+ },
+ "base-palette" => {
+ self.rule.base_palette = Some(input.parse_entirely(|i| FontPaletteBase::parse(self.context, i))?)
+ },
+ "override-colors" => {
+ self.rule.override_colors = parse_override_colors(self.context, input)?
+ },
+ _ => return Err(input.new_custom_error(SelectorParseErrorKind::UnexpectedIdent(name.clone()))),
+ }
+ Ok(())
+ }
+}
diff --git a/servo/components/style/stylesheets/import_rule.rs b/servo/components/style/stylesheets/import_rule.rs
new file mode 100644
index 0000000000..e80571c9cb
--- /dev/null
+++ b/servo/components/style/stylesheets/import_rule.rs
@@ -0,0 +1,213 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+//! The [`@import`][import] at-rule.
+//!
+//! [import]: https://drafts.csswg.org/css-cascade-3/#at-import
+
+use crate::media_queries::MediaList;
+use crate::shared_lock::{DeepCloneParams, DeepCloneWithLock};
+use crate::shared_lock::{SharedRwLock, SharedRwLockReadGuard, ToCssWithGuard};
+use crate::str::CssStringWriter;
+use crate::stylesheets::layer_rule::LayerName;
+use crate::stylesheets::{CssRule, StylesheetInDocument};
+use crate::values::CssUrl;
+use cssparser::SourceLocation;
+use std::fmt::{self, Write};
+use style_traits::{CssWriter, ToCss};
+use to_shmem::{self, SharedMemoryBuilder, ToShmem};
+
+/// A sheet that is held from an import rule.
+#[cfg(feature = "gecko")]
+#[derive(Debug)]
+pub enum ImportSheet {
+ /// A bonafide stylesheet.
+ Sheet(crate::gecko::data::GeckoStyleSheet),
+ /// An @import created while parsing off-main-thread, whose Gecko sheet has
+ /// yet to be created and attached.
+ Pending,
+}
+
+#[cfg(feature = "gecko")]
+impl ImportSheet {
+ /// Creates a new ImportSheet from a GeckoStyleSheet.
+ pub fn new(sheet: crate::gecko::data::GeckoStyleSheet) -> Self {
+ ImportSheet::Sheet(sheet)
+ }
+
+ /// Creates a pending ImportSheet for a load that has not started yet.
+ pub fn new_pending() -> Self {
+ ImportSheet::Pending
+ }
+
+ /// Returns a reference to the GeckoStyleSheet in this ImportSheet, if it
+ /// exists.
+ pub fn as_sheet(&self) -> Option<&crate::gecko::data::GeckoStyleSheet> {
+ match *self {
+ ImportSheet::Sheet(ref s) => {
+ debug_assert!(!s.hack_is_null());
+ if s.hack_is_null() {
+ return None;
+ }
+ Some(s)
+ },
+ ImportSheet::Pending => None,
+ }
+ }
+
+ /// Returns the media list for this import rule.
+ pub fn media<'a>(&'a self, guard: &'a SharedRwLockReadGuard) -> Option<&'a MediaList> {
+ self.as_sheet().and_then(|s| s.media(guard))
+ }
+
+ /// Returns the rule list for this import rule.
+ pub fn rules<'a>(&'a self, guard: &'a SharedRwLockReadGuard) -> &'a [CssRule] {
+ match self.as_sheet() {
+ Some(s) => s.rules(guard),
+ None => &[],
+ }
+ }
+}
+
+#[cfg(feature = "gecko")]
+impl DeepCloneWithLock for ImportSheet {
+ fn deep_clone_with_lock(
+ &self,
+ _lock: &SharedRwLock,
+ _guard: &SharedRwLockReadGuard,
+ params: &DeepCloneParams,
+ ) -> Self {
+ use crate::gecko::data::GeckoStyleSheet;
+ use crate::gecko_bindings::bindings;
+ match *self {
+ ImportSheet::Sheet(ref s) => {
+ let clone = unsafe {
+ bindings::Gecko_StyleSheet_Clone(s.raw() as *const _, params.reference_sheet)
+ };
+ ImportSheet::Sheet(unsafe { GeckoStyleSheet::from_addrefed(clone) })
+ },
+ ImportSheet::Pending => ImportSheet::Pending,
+ }
+ }
+}
+
+/// A sheet that is held from an import rule.
+#[cfg(feature = "servo")]
+#[derive(Debug)]
+pub struct ImportSheet(pub ::servo_arc::Arc<crate::stylesheets::Stylesheet>);
+
+#[cfg(feature = "servo")]
+impl ImportSheet {
+ /// Returns the media list for this import rule.
+ pub fn media<'a>(&'a self, guard: &'a SharedRwLockReadGuard) -> Option<&'a MediaList> {
+ self.0.media(guard)
+ }
+
+ /// Returns the rules for this import rule.
+ pub fn rules<'a>(&'a self, guard: &'a SharedRwLockReadGuard) -> &'a [CssRule] {
+ self.0.rules()
+ }
+}
+
+#[cfg(feature = "servo")]
+impl DeepCloneWithLock for ImportSheet {
+ fn deep_clone_with_lock(
+ &self,
+ _lock: &SharedRwLock,
+ _guard: &SharedRwLockReadGuard,
+ _params: &DeepCloneParams,
+ ) -> Self {
+ use servo_arc::Arc;
+
+ ImportSheet(Arc::new((&*self.0).clone()))
+ }
+}
+
+/// The layer keyword or function in an import rule.
+#[derive(Debug, Clone)]
+pub struct ImportLayer {
+ /// The layer name, or None for an anonymous layer.
+ pub name: Option<LayerName>,
+}
+
+impl ToCss for ImportLayer {
+ fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
+ where
+ W: Write,
+ {
+ match self.name {
+ None => dest.write_str("layer"),
+ Some(ref name) => {
+ dest.write_str("layer(")?;
+ name.to_css(dest)?;
+ dest.write_char(')')
+ },
+ }
+ }
+}
+
+/// 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: CssUrl,
+
+ /// The stylesheet is always present. However, in the case of gecko async
+ /// parsing, we don't actually have a Gecko sheet at first, and so the
+ /// ImportSheet just has stub behavior until it appears.
+ pub stylesheet: ImportSheet,
+
+ /// A `layer()` function name.
+ pub layer: Option<ImportLayer>,
+
+ /// The line and column of the rule's source code.
+ pub source_location: SourceLocation,
+}
+
+impl ToShmem for ImportRule {
+ fn to_shmem(&self, _builder: &mut SharedMemoryBuilder) -> to_shmem::Result<Self> {
+ Err(String::from(
+ "ToShmem failed for ImportRule: cannot handle imported style sheets",
+ ))
+ }
+}
+
+impl DeepCloneWithLock for ImportRule {
+ fn deep_clone_with_lock(
+ &self,
+ lock: &SharedRwLock,
+ guard: &SharedRwLockReadGuard,
+ params: &DeepCloneParams,
+ ) -> Self {
+ ImportRule {
+ url: self.url.clone(),
+ stylesheet: self.stylesheet.deep_clone_with_lock(lock, guard, params),
+ layer: self.layer.clone(),
+ source_location: self.source_location.clone(),
+ }
+ }
+}
+
+impl ToCssWithGuard for ImportRule {
+ fn to_css(&self, guard: &SharedRwLockReadGuard, dest: &mut CssStringWriter) -> fmt::Result {
+ dest.write_str("@import ")?;
+ self.url.to_css(&mut CssWriter::new(dest))?;
+
+ if let Some(media) = self.stylesheet.media(guard) {
+ if !media.is_empty() {
+ dest.write_char(' ')?;
+ media.to_css(&mut CssWriter::new(dest))?;
+ }
+ }
+
+ if let Some(ref layer) = self.layer {
+ dest.write_char(' ')?;
+ layer.to_css(&mut CssWriter::new(dest))?;
+ }
+
+ dest.write_char(';')
+ }
+}
diff --git a/servo/components/style/stylesheets/keyframes_rule.rs b/servo/components/style/stylesheets/keyframes_rule.rs
new file mode 100644
index 0000000000..dd04dbf01a
--- /dev/null
+++ b/servo/components/style/stylesheets/keyframes_rule.rs
@@ -0,0 +1,664 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+//! Keyframes: https://drafts.csswg.org/css-animations/#keyframes
+
+use crate::error_reporting::ContextualParseError;
+use crate::parser::ParserContext;
+use crate::properties::longhands::animation_composition::single_value::SpecifiedValue as SpecifiedComposition;
+use crate::properties::longhands::transition_timing_function::single_value::SpecifiedValue as SpecifiedTimingFunction;
+use crate::properties::LonghandIdSet;
+use crate::properties::{Importance, PropertyDeclaration};
+use crate::properties::{LonghandId, PropertyDeclarationBlock, PropertyId};
+use crate::properties::{PropertyDeclarationId, SourcePropertyDeclaration};
+use crate::shared_lock::{DeepCloneParams, DeepCloneWithLock, SharedRwLock, SharedRwLockReadGuard};
+use crate::shared_lock::{Locked, ToCssWithGuard};
+use crate::str::CssStringWriter;
+use crate::stylesheets::rule_parser::VendorPrefix;
+use crate::stylesheets::{CssRuleType, StylesheetContents};
+use crate::values::{serialize_percentage, KeyframesName};
+use cssparser::{
+ parse_one_rule, DeclarationListParser, DeclarationParser, ParserState, SourceLocation, Token,
+};
+use cssparser::{AtRuleParser, CowRcStr, Parser, ParserInput, QualifiedRuleParser, RuleListParser};
+use servo_arc::Arc;
+use std::fmt::{self, Write};
+use style_traits::{CssWriter, ParseError, ParsingMode, StyleParseErrorKind, ToCss};
+
+/// A [`@keyframes`][keyframes] rule.
+///
+/// [keyframes]: https://drafts.csswg.org/css-animations/#keyframes
+#[derive(Debug, ToShmem)]
+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(&self, guard: &SharedRwLockReadGuard, dest: &mut CssStringWriter) -> fmt::Result {
+ dest.write_str("@keyframes ")?;
+ self.name.to_css(&mut CssWriter::new(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> {
+ let mut input = ParserInput::new(selector);
+ if let Ok(selector) = Parser::new(&mut input).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,
+ params: &DeepCloneParams,
+ ) -> Self {
+ KeyframesRule {
+ name: self.name.clone(),
+ keyframes: self
+ .keyframes
+ .iter()
+ .map(|x| {
+ Arc::new(
+ lock.wrap(x.read_with(guard).deep_clone_with_lock(lock, guard, params)),
+ )
+ })
+ .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.
+#[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq, PartialOrd, ToShmem)]
+pub struct KeyframePercentage(pub f32);
+
+impl ::std::cmp::Ord for KeyframePercentage {
+ #[inline]
+ fn cmp(&self, other: &Self) -> ::std::cmp::Ordering {
+ // We know we have a number from 0 to 1, so unwrap() here is safe.
+ self.0.partial_cmp(&other.0).unwrap()
+ }
+}
+
+impl ::std::cmp::Eq for KeyframePercentage {}
+
+impl ToCss for KeyframePercentage {
+ fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
+ where
+ W: Write,
+ {
+ serialize_percentage(self.0, dest)
+ }
+}
+
+impl KeyframePercentage {
+ /// Trivially constructs a new `KeyframePercentage`.
+ #[inline]
+ pub fn new(value: f32) -> KeyframePercentage {
+ debug_assert!(value >= 0. && value <= 1.);
+ KeyframePercentage(value)
+ }
+
+ fn parse<'i, 't>(input: &mut Parser<'i, 't>) -> Result<KeyframePercentage, ParseError<'i>> {
+ let token = input.next()?.clone();
+ match token {
+ Token::Ident(ref identifier) if identifier.as_ref().eq_ignore_ascii_case("from") => {
+ Ok(KeyframePercentage::new(0.))
+ },
+ Token::Ident(ref identifier) if identifier.as_ref().eq_ignore_ascii_case("to") => {
+ Ok(KeyframePercentage::new(1.))
+ },
+ Token::Percentage {
+ unit_value: percentage,
+ ..
+ } if percentage >= 0. && percentage <= 1. => Ok(KeyframePercentage::new(percentage)),
+ _ => Err(input.new_unexpected_token_error(token)),
+ }
+ }
+}
+
+/// A keyframes selector is a list of percentages or from/to symbols, which are
+/// converted at parse time to percentages.
+#[derive(Clone, Debug, Eq, PartialEq, ToCss, ToShmem)]
+#[css(comma)]
+pub struct KeyframeSelector(#[css(iterable)] Vec<KeyframePercentage>);
+
+impl KeyframeSelector {
+ /// Return the list of percentages this selector contains.
+ #[inline]
+ pub fn percentages(&self) -> &[KeyframePercentage] {
+ &self.0
+ }
+
+ /// A dummy public function so we can write a unit test for this.
+ pub fn new_for_unit_testing(percentages: Vec<KeyframePercentage>) -> KeyframeSelector {
+ KeyframeSelector(percentages)
+ }
+
+ /// Parse a keyframe selector from CSS input.
+ pub fn parse<'i, 't>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i>> {
+ input
+ .parse_comma_separated(KeyframePercentage::parse)
+ .map(KeyframeSelector)
+ }
+}
+
+/// A keyframe.
+#[derive(Debug, ToShmem)]
+pub struct Keyframe {
+ /// The selector this keyframe was specified from.
+ pub selector: KeyframeSelector,
+
+ /// The declaration block that was declared inside this keyframe.
+ ///
+ /// Note that `!important` rules in keyframes don't apply, but we keep this
+ /// `Arc` just for convenience.
+ pub block: Arc<Locked<PropertyDeclarationBlock>>,
+
+ /// The line and column of the rule's source code.
+ pub source_location: SourceLocation,
+}
+
+impl ToCssWithGuard for Keyframe {
+ fn to_css(&self, guard: &SharedRwLockReadGuard, dest: &mut CssStringWriter) -> fmt::Result {
+ self.selector.to_css(&mut CssWriter::new(dest))?;
+ dest.write_str(" { ")?;
+ self.block.read_with(guard).to_css(dest)?;
+ dest.write_str(" }")?;
+ Ok(())
+ }
+}
+
+impl Keyframe {
+ /// Parse a CSS keyframe.
+ pub fn parse<'i>(
+ css: &'i str,
+ parent_stylesheet_contents: &StylesheetContents,
+ lock: &SharedRwLock,
+ ) -> Result<Arc<Locked<Self>>, ParseError<'i>> {
+ let url_data = parent_stylesheet_contents.url_data.read();
+ let namespaces = parent_stylesheet_contents.namespaces.read();
+ let mut context = ParserContext::new(
+ parent_stylesheet_contents.origin,
+ &url_data,
+ Some(CssRuleType::Keyframe),
+ ParsingMode::DEFAULT,
+ parent_stylesheet_contents.quirks_mode,
+ None,
+ None,
+ );
+ context.namespaces = Some(&*namespaces);
+ let mut input = ParserInput::new(css);
+ let mut input = Parser::new(&mut input);
+
+ let mut declarations = SourcePropertyDeclaration::new();
+ let mut rule_parser = KeyframeListParser {
+ context: &context,
+ shared_lock: &lock,
+ declarations: &mut declarations,
+ };
+ parse_one_rule(&mut input, &mut rule_parser)
+ }
+}
+
+impl DeepCloneWithLock for Keyframe {
+ /// Deep clones this Keyframe.
+ fn deep_clone_with_lock(
+ &self,
+ lock: &SharedRwLock,
+ guard: &SharedRwLockReadGuard,
+ _params: &DeepCloneParams,
+ ) -> Keyframe {
+ Keyframe {
+ selector: self.selector.clone(),
+ block: Arc::new(lock.wrap(self.block.read_with(guard).clone())),
+ source_location: self.source_location.clone(),
+ }
+ }
+}
+
+/// A keyframes step value. This can be a synthetised keyframes animation, that
+/// is, one autogenerated from the current computed values, or a list of
+/// declarations to apply.
+///
+/// TODO: Find a better name for this?
+#[derive(Clone, Debug, MallocSizeOf)]
+pub enum KeyframesStepValue {
+ /// A step formed by a declaration block specified by the CSS.
+ Declarations {
+ /// The declaration block per se.
+ #[cfg_attr(
+ feature = "gecko",
+ ignore_malloc_size_of = "XXX: Primary ref, measure if DMD says it's worthwhile"
+ )]
+ #[cfg_attr(feature = "servo", ignore_malloc_size_of = "Arc")]
+ block: Arc<Locked<PropertyDeclarationBlock>>,
+ },
+ /// A synthetic step computed from the current computed values at the time
+ /// of the animation.
+ ComputedValues,
+}
+
+/// A single step from a keyframe animation.
+#[derive(Clone, Debug, MallocSizeOf)]
+pub struct KeyframesStep {
+ /// The percentage of the animation duration when this step starts.
+ pub start_percentage: KeyframePercentage,
+ /// Declarations that will determine the final style during the step, or
+ /// `ComputedValues` if this is an autogenerated step.
+ pub value: KeyframesStepValue,
+ /// Whether an animation-timing-function declaration exists in the list of
+ /// declarations.
+ ///
+ /// This is used to know when to override the keyframe animation style.
+ pub declared_timing_function: bool,
+ /// Whether an animation-composition declaration exists in the list of
+ /// declarations.
+ ///
+ /// This is used to know when to override the keyframe animation style.
+ pub declared_composition: bool,
+}
+
+impl KeyframesStep {
+ #[inline]
+ fn new(
+ start_percentage: KeyframePercentage,
+ value: KeyframesStepValue,
+ guard: &SharedRwLockReadGuard,
+ ) -> Self {
+ let mut declared_timing_function = false;
+ let mut declared_composition = false;
+ if let KeyframesStepValue::Declarations { ref block } = value {
+ for prop_decl in block.read_with(guard).declarations().iter() {
+ match *prop_decl {
+ PropertyDeclaration::AnimationTimingFunction(..) => {
+ declared_timing_function = true;
+ },
+ PropertyDeclaration::AnimationComposition(..) => {
+ declared_composition = true;
+ },
+ _ => continue,
+ }
+ // Don't need to continue the loop if both are found.
+ if declared_timing_function && declared_composition {
+ break;
+ }
+ }
+ }
+
+ KeyframesStep {
+ start_percentage,
+ value,
+ declared_timing_function,
+ declared_composition,
+ }
+ }
+
+ /// Return specified PropertyDeclaration.
+ #[inline]
+ fn get_declared_property<'a>(
+ &'a self,
+ guard: &'a SharedRwLockReadGuard,
+ property: LonghandId,
+ ) -> Option<&'a PropertyDeclaration> {
+ match self.value {
+ KeyframesStepValue::Declarations { ref block } => {
+ let guard = block.read_with(guard);
+ let (declaration, _) = guard
+ .get(PropertyDeclarationId::Longhand(property))
+ .unwrap();
+ match *declaration {
+ PropertyDeclaration::CSSWideKeyword(..) => None,
+ // FIXME: Bug 1710735: Support css variable in @keyframes rule.
+ PropertyDeclaration::WithVariables(..) => None,
+ _ => Some(declaration),
+ }
+ },
+ KeyframesStepValue::ComputedValues => {
+ panic!("Shouldn't happen to set this property in missing keyframes")
+ },
+ }
+ }
+
+ /// Return specified TransitionTimingFunction if this KeyframesSteps has
+ /// 'animation-timing-function'.
+ pub fn get_animation_timing_function(
+ &self,
+ guard: &SharedRwLockReadGuard,
+ ) -> Option<SpecifiedTimingFunction> {
+ if !self.declared_timing_function {
+ return None;
+ }
+
+ self.get_declared_property(guard, LonghandId::AnimationTimingFunction)
+ .map(|decl| {
+ match *decl {
+ PropertyDeclaration::AnimationTimingFunction(ref value) => {
+ // Use the first value
+ value.0[0].clone()
+ },
+ _ => unreachable!("Unexpected PropertyDeclaration"),
+ }
+ })
+ }
+
+ /// Return CompositeOperation if this KeyframesSteps has 'animation-composition'.
+ pub fn get_animation_composition(
+ &self,
+ guard: &SharedRwLockReadGuard,
+ ) -> Option<SpecifiedComposition> {
+ if !self.declared_composition {
+ return None;
+ }
+
+ self.get_declared_property(guard, LonghandId::AnimationComposition)
+ .map(|decl| {
+ match *decl {
+ PropertyDeclaration::AnimationComposition(ref value) => {
+ // Use the first value
+ value.0[0].clone()
+ },
+ _ => unreachable!("Unexpected PropertyDeclaration"),
+ }
+ })
+ }
+}
+
+/// This structure represents a list of animation steps computed from the list
+/// of keyframes, in order.
+///
+/// It only takes into account animable properties.
+#[derive(Clone, Debug, MallocSizeOf)]
+pub struct KeyframesAnimation {
+ /// The difference steps of the animation.
+ pub steps: Vec<KeyframesStep>,
+ /// The properties that change in this animation.
+ pub properties_changed: LonghandIdSet,
+ /// Vendor prefix type the @keyframes has.
+ pub vendor_prefix: Option<VendorPrefix>,
+}
+
+/// Get all the animated properties in a keyframes animation.
+fn get_animated_properties(
+ keyframes: &[Arc<Locked<Keyframe>>],
+ guard: &SharedRwLockReadGuard,
+) -> LonghandIdSet {
+ let mut ret = LonghandIdSet::new();
+ // NB: declarations are already deduplicated, so we don't have to check for
+ // it here.
+ for keyframe in keyframes {
+ let keyframe = keyframe.read_with(&guard);
+ let block = keyframe.block.read_with(guard);
+ // CSS Animations spec clearly defines that properties with !important
+ // in keyframe rules are invalid and ignored, but it's still ambiguous
+ // whether we should drop the !important properties or retain the
+ // properties when they are set via CSSOM. So we assume there might
+ // be properties with !important in keyframe rules here.
+ // See the spec issue https://github.com/w3c/csswg-drafts/issues/1824
+ for declaration in block.normal_declaration_iter() {
+ let longhand_id = match declaration.id() {
+ PropertyDeclarationId::Longhand(id) => id,
+ _ => continue,
+ };
+
+ if longhand_id == LonghandId::Display {
+ continue;
+ }
+
+ if !longhand_id.is_animatable() {
+ continue;
+ }
+
+ ret.insert(longhand_id);
+ }
+ }
+
+ ret
+}
+
+impl KeyframesAnimation {
+ /// Create a keyframes animation from a given list of keyframes.
+ ///
+ /// This will return a keyframe animation with empty steps and
+ /// properties_changed if the list of keyframes is empty, or there are no
+ /// animated properties obtained from the keyframes.
+ ///
+ /// Otherwise, this will compute and sort the steps used for the animation,
+ /// and return the animation object.
+ pub fn from_keyframes(
+ keyframes: &[Arc<Locked<Keyframe>>],
+ vendor_prefix: Option<VendorPrefix>,
+ guard: &SharedRwLockReadGuard,
+ ) -> Self {
+ let mut result = KeyframesAnimation {
+ steps: vec![],
+ properties_changed: LonghandIdSet::new(),
+ vendor_prefix,
+ };
+
+ if keyframes.is_empty() {
+ return result;
+ }
+
+ result.properties_changed = get_animated_properties(keyframes, guard);
+ if result.properties_changed.is_empty() {
+ return result;
+ }
+
+ for keyframe in keyframes {
+ let keyframe = keyframe.read_with(&guard);
+ for percentage in keyframe.selector.0.iter() {
+ result.steps.push(KeyframesStep::new(
+ *percentage,
+ KeyframesStepValue::Declarations {
+ block: keyframe.block.clone(),
+ },
+ guard,
+ ));
+ }
+ }
+
+ // Sort by the start percentage, so we can easily find a frame.
+ result.steps.sort_by_key(|step| step.start_percentage);
+
+ // Prepend autogenerated keyframes if appropriate.
+ if result.steps[0].start_percentage.0 != 0. {
+ result.steps.insert(
+ 0,
+ KeyframesStep::new(
+ KeyframePercentage::new(0.),
+ KeyframesStepValue::ComputedValues,
+ guard,
+ ),
+ );
+ }
+
+ if result.steps.last().unwrap().start_percentage.0 != 1. {
+ result.steps.push(KeyframesStep::new(
+ KeyframePercentage::new(1.),
+ KeyframesStepValue::ComputedValues,
+ guard,
+ ));
+ }
+
+ result
+ }
+}
+
+/// Parses a keyframes list, like:
+/// 0%, 50% {
+/// width: 50%;
+/// }
+///
+/// 40%, 60%, 100% {
+/// width: 100%;
+/// }
+struct KeyframeListParser<'a> {
+ context: &'a ParserContext<'a>,
+ shared_lock: &'a SharedRwLock,
+ declarations: &'a mut SourcePropertyDeclaration,
+}
+
+/// Parses a keyframe list from CSS input.
+pub fn parse_keyframe_list(
+ context: &ParserContext,
+ input: &mut Parser,
+ shared_lock: &SharedRwLock,
+) -> Vec<Arc<Locked<Keyframe>>> {
+ debug_assert!(
+ context.namespaces.is_some(),
+ "Parsing a keyframe list from a context without namespaces?"
+ );
+
+ let mut declarations = SourcePropertyDeclaration::new();
+ RuleListParser::new_for_nested_rule(
+ input,
+ KeyframeListParser {
+ context,
+ shared_lock,
+ declarations: &mut declarations,
+ },
+ )
+ .filter_map(Result::ok)
+ .collect()
+}
+
+impl<'a, 'i> AtRuleParser<'i> for KeyframeListParser<'a> {
+ type Prelude = ();
+ type AtRule = Arc<Locked<Keyframe>>;
+ type Error = StyleParseErrorKind<'i>;
+}
+
+impl<'a, 'i> QualifiedRuleParser<'i> for KeyframeListParser<'a> {
+ type Prelude = KeyframeSelector;
+ type QualifiedRule = Arc<Locked<Keyframe>>;
+ type Error = StyleParseErrorKind<'i>;
+
+ fn parse_prelude<'t>(
+ &mut self,
+ input: &mut Parser<'i, 't>,
+ ) -> Result<Self::Prelude, ParseError<'i>> {
+ let start_position = input.position();
+ KeyframeSelector::parse(input).map_err(|e| {
+ let location = e.location;
+ let error = ContextualParseError::InvalidKeyframeRule(
+ input.slice_from(start_position),
+ e.clone(),
+ );
+ self.context.log_css_error(location, error);
+ e
+ })
+ }
+
+ fn parse_block<'t>(
+ &mut self,
+ selector: Self::Prelude,
+ start: &ParserState,
+ input: &mut Parser<'i, 't>,
+ ) -> Result<Self::QualifiedRule, ParseError<'i>> {
+ let context = ParserContext::new_with_rule_type(
+ self.context,
+ CssRuleType::Keyframe,
+ self.context.namespaces.unwrap(),
+ );
+
+ let parser = KeyframeDeclarationParser {
+ context: &context,
+ declarations: self.declarations,
+ };
+ let mut iter = DeclarationListParser::new(input, parser);
+ let mut block = PropertyDeclarationBlock::new();
+ while let Some(declaration) = iter.next() {
+ match declaration {
+ Ok(()) => {
+ block.extend(iter.parser.declarations.drain(), Importance::Normal);
+ },
+ Err((error, slice)) => {
+ iter.parser.declarations.clear();
+ let location = error.location;
+ let error =
+ ContextualParseError::UnsupportedKeyframePropertyDeclaration(slice, error);
+ context.log_css_error(location, error);
+ },
+ }
+ // `parse_important` is not called here, `!important` is not allowed in keyframe blocks.
+ }
+ Ok(Arc::new(self.shared_lock.wrap(Keyframe {
+ selector,
+ block: Arc::new(self.shared_lock.wrap(block)),
+ source_location: start.source_location(),
+ })))
+ }
+}
+
+struct KeyframeDeclarationParser<'a, 'b: 'a> {
+ context: &'a ParserContext<'b>,
+ declarations: &'a mut SourcePropertyDeclaration,
+}
+
+/// Default methods reject all at rules.
+impl<'a, 'b, 'i> AtRuleParser<'i> for KeyframeDeclarationParser<'a, 'b> {
+ type Prelude = ();
+ type AtRule = ();
+ type Error = StyleParseErrorKind<'i>;
+}
+
+impl<'a, 'b, 'i> DeclarationParser<'i> for KeyframeDeclarationParser<'a, 'b> {
+ type Declaration = ();
+ type Error = StyleParseErrorKind<'i>;
+
+ fn parse_value<'t>(
+ &mut self,
+ name: CowRcStr<'i>,
+ input: &mut Parser<'i, 't>,
+ ) -> Result<(), ParseError<'i>> {
+ let id = match PropertyId::parse(&name, self.context) {
+ Ok(id) => id,
+ Err(()) => {
+ return Err(input.new_custom_error(StyleParseErrorKind::UnknownProperty(name)));
+ },
+ };
+
+ // TODO(emilio): Shouldn't this use parse_entirely?
+ PropertyDeclaration::parse_into(self.declarations, id, self.context, input)?;
+
+ // In case there is still unparsed text in the declaration, we should
+ // roll back.
+ input.expect_exhausted()?;
+
+ Ok(())
+ }
+}
diff --git a/servo/components/style/stylesheets/layer_rule.rs b/servo/components/style/stylesheets/layer_rule.rs
new file mode 100644
index 0000000000..c724fd81f1
--- /dev/null
+++ b/servo/components/style/stylesheets/layer_rule.rs
@@ -0,0 +1,239 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+//! A [`@layer`][layer] rule.
+//!
+//! [layer]: https://drafts.csswg.org/css-cascade-5/#layering
+
+use crate::parser::{Parse, ParserContext};
+use crate::shared_lock::{DeepCloneParams, DeepCloneWithLock, Locked};
+use crate::shared_lock::{SharedRwLock, SharedRwLockReadGuard, ToCssWithGuard};
+use crate::values::AtomIdent;
+
+use super::CssRules;
+
+use cssparser::{Parser, SourceLocation, Token};
+use servo_arc::Arc;
+use smallvec::SmallVec;
+use std::fmt::{self, Write};
+use style_traits::{CssWriter, ParseError, ToCss};
+
+/// The order of a given layer. We use 16 bits so that we can pack LayerOrder
+/// and CascadeLevel in a single 32-bit struct. If we need more bits we can go
+/// back to packing CascadeLevel in a single byte as we did before.
+#[derive(Clone, Copy, Debug, Eq, Hash, MallocSizeOf, PartialEq, PartialOrd, Ord)]
+pub struct LayerOrder(u16);
+
+impl LayerOrder {
+ /// The order of the root layer.
+ pub const fn root() -> Self {
+ Self(std::u16::MAX - 1)
+ }
+
+ /// The order of the style attribute layer.
+ pub const fn style_attribute() -> Self {
+ Self(std::u16::MAX)
+ }
+
+ /// Returns whether this layer is for the style attribute, which behaves
+ /// differently in terms of !important, see
+ /// https://github.com/w3c/csswg-drafts/issues/6872
+ ///
+ /// (This is a bit silly, mind-you, but it's needed so that revert-layer
+ /// behaves correctly).
+ #[inline]
+ pub fn is_style_attribute_layer(&self) -> bool {
+ *self == Self::style_attribute()
+ }
+
+ /// The first cascade layer order.
+ pub const fn first() -> Self {
+ Self(0)
+ }
+
+ /// Increment the cascade layer order.
+ #[inline]
+ pub fn inc(&mut self) {
+ if self.0 != std::u16::MAX - 1 {
+ self.0 += 1;
+ }
+ }
+}
+
+/// A `<layer-name>`: https://drafts.csswg.org/css-cascade-5/#typedef-layer-name
+#[derive(Clone, Debug, Eq, Hash, MallocSizeOf, PartialEq, ToShmem)]
+pub struct LayerName(pub SmallVec<[AtomIdent; 1]>);
+
+impl LayerName {
+ /// Returns an empty layer name (which isn't a valid final state, so caller
+ /// is responsible to fill up the name before use).
+ pub fn new_empty() -> Self {
+ Self(Default::default())
+ }
+
+ /// Returns a synthesized name for an anonymous layer.
+ pub fn new_anonymous() -> Self {
+ use std::sync::atomic::{AtomicUsize, Ordering};
+ static NEXT_ANONYMOUS_LAYER_NAME: AtomicUsize = AtomicUsize::new(0);
+
+ let mut name = SmallVec::new();
+ let next_id = NEXT_ANONYMOUS_LAYER_NAME.fetch_add(1, Ordering::Relaxed);
+ // The parens don't _technically_ prevent conflicts with authors, as
+ // authors could write escaped parens as part of the identifier, I
+ // think, but highly reduces the possibility.
+ name.push(AtomIdent::from(&*format!("-moz-anon-layer({})", next_id)));
+
+ LayerName(name)
+ }
+
+ /// Returns the names of the layers. That is, for a layer like `foo.bar`,
+ /// it'd return [foo, bar].
+ pub fn layer_names(&self) -> &[AtomIdent] {
+ &self.0
+ }
+}
+
+impl Parse for LayerName {
+ fn parse<'i, 't>(
+ _: &ParserContext,
+ input: &mut Parser<'i, 't>,
+ ) -> Result<Self, ParseError<'i>> {
+ let mut result = SmallVec::new();
+ result.push(AtomIdent::from(&**input.expect_ident()?));
+ loop {
+ let next_name = input.try_parse(|input| -> Result<AtomIdent, ParseError<'i>> {
+ match input.next_including_whitespace()? {
+ Token::Delim('.') => {},
+ other => {
+ let t = other.clone();
+ return Err(input.new_unexpected_token_error(t));
+ },
+ }
+
+ let name = match input.next_including_whitespace()? {
+ Token::Ident(ref ident) => ident,
+ other => {
+ let t = other.clone();
+ return Err(input.new_unexpected_token_error(t));
+ },
+ };
+
+ Ok(AtomIdent::from(&**name))
+ });
+
+ match next_name {
+ Ok(name) => result.push(name),
+ Err(..) => break,
+ }
+ }
+ Ok(LayerName(result))
+ }
+}
+
+impl ToCss for LayerName {
+ fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
+ where
+ W: Write,
+ {
+ let mut first = true;
+ for name in self.0.iter() {
+ if !first {
+ dest.write_char('.')?;
+ }
+ first = false;
+ name.to_css(dest)?;
+ }
+ Ok(())
+ }
+}
+
+#[derive(Debug, ToShmem)]
+/// A block `@layer <name>? { ... }`
+/// https://drafts.csswg.org/css-cascade-5/#layer-block
+pub struct LayerBlockRule {
+ /// The layer name, or `None` if anonymous.
+ pub name: Option<LayerName>,
+ /// The nested rules.
+ pub rules: Arc<Locked<CssRules>>,
+ /// The source position where this rule was found.
+ pub source_location: SourceLocation,
+}
+
+impl ToCssWithGuard for LayerBlockRule {
+ fn to_css(
+ &self,
+ guard: &SharedRwLockReadGuard,
+ dest: &mut crate::str::CssStringWriter,
+ ) -> fmt::Result {
+ dest.write_str("@layer")?;
+ if let Some(ref name) = self.name {
+ dest.write_char(' ')?;
+ name.to_css(&mut CssWriter::new(dest))?;
+ }
+ self.rules.read_with(guard).to_css_block(guard, dest)
+ }
+}
+
+impl DeepCloneWithLock for LayerBlockRule {
+ fn deep_clone_with_lock(
+ &self,
+ lock: &SharedRwLock,
+ guard: &SharedRwLockReadGuard,
+ params: &DeepCloneParams,
+ ) -> Self {
+ Self {
+ name: self.name.clone(),
+ rules: Arc::new(
+ lock.wrap(
+ self.rules
+ .read_with(guard)
+ .deep_clone_with_lock(lock, guard, params),
+ ),
+ ),
+ source_location: self.source_location.clone(),
+ }
+ }
+}
+
+/// A statement `@layer <name>, <name>, <name>;`
+///
+/// https://drafts.csswg.org/css-cascade-5/#layer-empty
+#[derive(Clone, Debug, ToShmem)]
+pub struct LayerStatementRule {
+ /// The list of layers to sort.
+ pub names: Vec<LayerName>,
+ /// The source position where this rule was found.
+ pub source_location: SourceLocation,
+}
+
+impl ToCssWithGuard for LayerStatementRule {
+ fn to_css(
+ &self,
+ _: &SharedRwLockReadGuard,
+ dest: &mut crate::str::CssStringWriter,
+ ) -> fmt::Result {
+ let mut writer = CssWriter::new(dest);
+ writer.write_str("@layer ")?;
+ let mut first = true;
+ for name in &*self.names {
+ if !first {
+ writer.write_str(", ")?;
+ }
+ first = false;
+ name.to_css(&mut writer)?;
+ }
+ writer.write_char(';')
+ }
+}
+
+impl DeepCloneWithLock for LayerStatementRule {
+ fn deep_clone_with_lock(
+ &self,
+ _: &SharedRwLock,
+ _: &SharedRwLockReadGuard,
+ _: &DeepCloneParams,
+ ) -> Self {
+ self.clone()
+ }
+}
diff --git a/servo/components/style/stylesheets/loader.rs b/servo/components/style/stylesheets/loader.rs
new file mode 100644
index 0000000000..45f7d22a55
--- /dev/null
+++ b/servo/components/style/stylesheets/loader.rs
@@ -0,0 +1,30 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+//! The stylesheet loader is the abstraction used to trigger network requests
+//! for `@import` rules.
+
+use crate::media_queries::MediaList;
+use crate::parser::ParserContext;
+use crate::shared_lock::{Locked, SharedRwLock};
+use crate::stylesheets::import_rule::{ImportLayer, ImportRule};
+use crate::values::CssUrl;
+use cssparser::SourceLocation;
+use servo_arc::Arc;
+
+/// 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, and return
+ /// the constructed `@import` rule.
+ fn request_stylesheet(
+ &self,
+ url: CssUrl,
+ location: SourceLocation,
+ context: &ParserContext,
+ lock: &SharedRwLock,
+ media: Arc<Locked<MediaList>>,
+ layer: Option<ImportLayer>,
+ ) -> Arc<Locked<ImportRule>>;
+}
diff --git a/servo/components/style/stylesheets/media_rule.rs b/servo/components/style/stylesheets/media_rule.rs
new file mode 100644
index 0000000000..cde60a16bf
--- /dev/null
+++ b/servo/components/style/stylesheets/media_rule.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 https://mozilla.org/MPL/2.0/. */
+
+//! An [`@media`][media] rule.
+//!
+//! [media]: https://drafts.csswg.org/css-conditional/#at-ruledef-media
+
+use crate::media_queries::MediaList;
+use crate::shared_lock::{DeepCloneParams, DeepCloneWithLock, Locked};
+use crate::shared_lock::{SharedRwLock, SharedRwLockReadGuard, ToCssWithGuard};
+use crate::str::CssStringWriter;
+use crate::stylesheets::CssRules;
+use cssparser::SourceLocation;
+#[cfg(feature = "gecko")]
+use malloc_size_of::{MallocSizeOfOps, MallocUnconditionalShallowSizeOf};
+use servo_arc::Arc;
+use std::fmt::{self, Write};
+use style_traits::{CssWriter, ToCss};
+
+/// An [`@media`][media] rule.
+///
+/// [media]: https://drafts.csswg.org/css-conditional/#at-ruledef-media
+#[derive(Debug, ToShmem)]
+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 MediaRule {
+ /// Measure heap usage.
+ #[cfg(feature = "gecko")]
+ pub fn size_of(&self, guard: &SharedRwLockReadGuard, ops: &mut MallocSizeOfOps) -> usize {
+ // Measurement of other fields may be added later.
+ self.rules.unconditional_shallow_size_of(ops) +
+ self.rules.read_with(guard).size_of(guard, ops)
+ }
+}
+
+impl ToCssWithGuard for MediaRule {
+ // Serialization of MediaRule is not specced.
+ // https://drafts.csswg.org/cssom/#serialize-a-css-rule CSSMediaRule
+ fn to_css(&self, guard: &SharedRwLockReadGuard, dest: &mut CssStringWriter) -> fmt::Result {
+ dest.write_str("@media ")?;
+ self.media_queries
+ .read_with(guard)
+ .to_css(&mut CssWriter::new(dest))?;
+ self.rules.read_with(guard).to_css_block(guard, dest)
+ }
+}
+
+impl DeepCloneWithLock for MediaRule {
+ fn deep_clone_with_lock(
+ &self,
+ lock: &SharedRwLock,
+ guard: &SharedRwLockReadGuard,
+ params: &DeepCloneParams,
+ ) -> 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, params))),
+ source_location: self.source_location.clone(),
+ }
+ }
+}
diff --git a/servo/components/style/stylesheets/mod.rs b/servo/components/style/stylesheets/mod.rs
new file mode 100644
index 0000000000..01b0c07d7a
--- /dev/null
+++ b/servo/components/style/stylesheets/mod.rs
@@ -0,0 +1,557 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+//! Style sheets and their CSS rules.
+
+mod cascading_at_rule;
+pub mod container_rule;
+mod counter_style_rule;
+mod document_rule;
+mod font_face_rule;
+pub mod font_feature_values_rule;
+pub mod font_palette_values_rule;
+pub mod import_rule;
+pub mod keyframes_rule;
+pub mod layer_rule;
+mod loader;
+mod media_rule;
+mod namespace_rule;
+pub mod origin;
+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;
+
+#[cfg(feature = "gecko")]
+use crate::gecko_bindings::sugar::refptr::RefCounted;
+#[cfg(feature = "gecko")]
+use crate::gecko_bindings::{bindings, structs};
+use crate::parser::ParserContext;
+use crate::shared_lock::{DeepCloneParams, DeepCloneWithLock, Locked};
+use crate::shared_lock::{SharedRwLock, SharedRwLockReadGuard, ToCssWithGuard};
+use crate::str::CssStringWriter;
+use cssparser::{parse_one_rule, Parser, ParserInput};
+#[cfg(feature = "gecko")]
+use malloc_size_of::{MallocSizeOfOps, MallocUnconditionalShallowSizeOf};
+use servo_arc::Arc;
+use std::fmt;
+#[cfg(feature = "gecko")]
+use std::mem::{self, ManuallyDrop};
+use style_traits::ParsingMode;
+#[cfg(feature = "gecko")]
+use to_shmem::{self, SharedMemoryBuilder, ToShmem};
+
+pub use self::container_rule::ContainerRule;
+pub use self::counter_style_rule::CounterStyleRule;
+pub use self::document_rule::DocumentRule;
+pub use self::font_face_rule::FontFaceRule;
+pub use self::font_feature_values_rule::FontFeatureValuesRule;
+pub use self::font_palette_values_rule::FontPaletteValuesRule;
+pub use self::import_rule::ImportRule;
+pub use self::keyframes_rule::KeyframesRule;
+pub use self::layer_rule::{LayerBlockRule, LayerStatementRule};
+pub use self::loader::StylesheetLoader;
+pub use self::media_rule::MediaRule;
+pub use self::namespace_rule::NamespaceRule;
+pub use self::origin::{Origin, OriginSet, OriginSetIterator, PerOrigin, PerOriginIter};
+pub use self::page_rule::{PageRule, PageSelector, PageSelectors};
+pub use self::rule_list::{CssRules, CssRulesHelpers};
+pub use self::rule_parser::{InsertRuleContext, State, TopLevelRuleParser};
+pub use self::rules_iterator::{AllRules, EffectiveRules};
+pub use self::rules_iterator::{
+ EffectiveRulesIterator, NestedRuleIterationCondition, RulesIterator,
+};
+pub use self::style_rule::StyleRule;
+pub use self::stylesheet::{AllowImportRules, SanitizationData, SanitizationKind};
+pub use self::stylesheet::{DocumentStyleSheet, Namespaces, Stylesheet};
+pub use self::stylesheet::{StylesheetContents, StylesheetInDocument, UserAgentStylesheets};
+pub use self::supports_rule::SupportsRule;
+pub use self::viewport_rule::ViewportRule;
+
+/// The CORS mode used for a CSS load.
+#[repr(u8)]
+#[derive(Clone, Copy, Debug, Eq, PartialEq, ToShmem)]
+pub enum CorsMode {
+ /// No CORS mode, so cross-origin loads can be done.
+ None,
+ /// Anonymous CORS request.
+ Anonymous,
+}
+
+/// Extra data that the backend may need to resolve url values.
+///
+/// If the usize's lowest bit is 0, then this is a strong reference to a
+/// structs::URLExtraData object.
+///
+/// Otherwise, shifting the usize's bits the right by one gives the
+/// UserAgentStyleSheetID value corresponding to the style sheet whose
+/// URLExtraData this is, which is stored in URLExtraData_sShared. We don't
+/// hold a strong reference to that object from here, but we rely on that
+/// array's objects being held alive until shutdown.
+///
+/// We use this packed representation rather than an enum so that
+/// `from_ptr_ref` can work.
+#[cfg(feature = "gecko")]
+#[derive(PartialEq)]
+#[repr(C)]
+pub struct UrlExtraData(usize);
+
+/// Extra data that the backend may need to resolve url values.
+#[cfg(not(feature = "gecko"))]
+pub type UrlExtraData = ::servo_url::ServoUrl;
+
+#[cfg(feature = "gecko")]
+impl Clone for UrlExtraData {
+ fn clone(&self) -> UrlExtraData {
+ UrlExtraData::new(self.ptr())
+ }
+}
+
+#[cfg(feature = "gecko")]
+impl Drop for UrlExtraData {
+ fn drop(&mut self) {
+ // No need to release when we have an index into URLExtraData_sShared.
+ if self.0 & 1 == 0 {
+ unsafe {
+ self.as_ref().release();
+ }
+ }
+ }
+}
+
+#[cfg(feature = "gecko")]
+impl ToShmem for UrlExtraData {
+ fn to_shmem(&self, _builder: &mut SharedMemoryBuilder) -> to_shmem::Result<Self> {
+ if self.0 & 1 == 0 {
+ let shared_extra_datas = unsafe { &structs::URLExtraData_sShared };
+ let self_ptr = self.as_ref() as *const _ as *mut _;
+ let sheet_id = shared_extra_datas
+ .iter()
+ .position(|r| r.mRawPtr == self_ptr);
+ let sheet_id = match sheet_id {
+ Some(id) => id,
+ None => {
+ return Err(String::from(
+ "ToShmem failed for UrlExtraData: expected sheet's URLExtraData to be in \
+ URLExtraData::sShared",
+ ));
+ },
+ };
+ Ok(ManuallyDrop::new(UrlExtraData((sheet_id << 1) | 1)))
+ } else {
+ Ok(ManuallyDrop::new(UrlExtraData(self.0)))
+ }
+ }
+}
+
+#[cfg(feature = "gecko")]
+impl UrlExtraData {
+ /// Create a new UrlExtraData wrapping a pointer to the specified Gecko
+ /// URLExtraData object.
+ pub fn new(ptr: *mut structs::URLExtraData) -> UrlExtraData {
+ unsafe {
+ (*ptr).addref();
+ }
+ UrlExtraData(ptr as usize)
+ }
+
+ /// True if this URL scheme is chrome.
+ #[inline]
+ pub fn chrome_rules_enabled(&self) -> bool {
+ self.as_ref().mChromeRulesEnabled
+ }
+
+ /// Create a reference to this `UrlExtraData` from a reference to pointer.
+ ///
+ /// The pointer must be valid and non null.
+ ///
+ /// This method doesn't touch refcount.
+ #[inline]
+ pub unsafe fn from_ptr_ref(ptr: &*mut structs::URLExtraData) -> &Self {
+ mem::transmute(ptr)
+ }
+
+ /// Returns a pointer to the Gecko URLExtraData object.
+ pub fn ptr(&self) -> *mut structs::URLExtraData {
+ if self.0 & 1 == 0 {
+ self.0 as *mut structs::URLExtraData
+ } else {
+ unsafe {
+ let sheet_id = self.0 >> 1;
+ structs::URLExtraData_sShared[sheet_id].mRawPtr
+ }
+ }
+ }
+
+ fn as_ref(&self) -> &structs::URLExtraData {
+ unsafe { &*(self.ptr() as *const structs::URLExtraData) }
+ }
+}
+
+#[cfg(feature = "gecko")]
+impl fmt::Debug for UrlExtraData {
+ fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
+ macro_rules! define_debug_struct {
+ ($struct_name:ident, $gecko_class:ident, $debug_fn:ident) => {
+ struct $struct_name(*mut structs::$gecko_class);
+ impl fmt::Debug for $struct_name {
+ fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
+ use nsstring::nsCString;
+ let mut spec = nsCString::new();
+ unsafe {
+ bindings::$debug_fn(self.0, &mut spec);
+ }
+ spec.fmt(formatter)
+ }
+ }
+ };
+ }
+
+ define_debug_struct!(DebugURI, nsIURI, Gecko_nsIURI_Debug);
+ define_debug_struct!(
+ DebugReferrerInfo,
+ nsIReferrerInfo,
+ Gecko_nsIReferrerInfo_Debug
+ );
+
+ formatter
+ .debug_struct("URLExtraData")
+ .field("chrome_rules_enabled", &self.chrome_rules_enabled())
+ .field(
+ "base",
+ &DebugURI(self.as_ref().mBaseURI.raw::<structs::nsIURI>()),
+ )
+ .field(
+ "referrer",
+ &DebugReferrerInfo(
+ self.as_ref()
+ .mReferrerInfo
+ .raw::<structs::nsIReferrerInfo>(),
+ ),
+ )
+ .finish()
+ }
+}
+
+// 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 {}
+
+/// A CSS rule.
+///
+/// TODO(emilio): Lots of spec links should be around.
+#[derive(Clone, Debug, ToShmem)]
+#[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>>),
+ Container(Arc<Locked<ContainerRule>>),
+ FontFace(Arc<Locked<FontFaceRule>>),
+ FontFeatureValues(Arc<Locked<FontFeatureValuesRule>>),
+ FontPaletteValues(Arc<Locked<FontPaletteValuesRule>>),
+ CounterStyle(Arc<Locked<CounterStyleRule>>),
+ Viewport(Arc<Locked<ViewportRule>>),
+ Keyframes(Arc<Locked<KeyframesRule>>),
+ Supports(Arc<Locked<SupportsRule>>),
+ Page(Arc<Locked<PageRule>>),
+ Document(Arc<Locked<DocumentRule>>),
+ LayerBlock(Arc<Locked<LayerBlockRule>>),
+ LayerStatement(Arc<Locked<LayerStatementRule>>),
+}
+
+impl CssRule {
+ /// Measure heap usage.
+ #[cfg(feature = "gecko")]
+ fn size_of(&self, guard: &SharedRwLockReadGuard, ops: &mut MallocSizeOfOps) -> usize {
+ match *self {
+ // Not all fields are currently fully measured. Extra measurement
+ // may be added later.
+ CssRule::Namespace(_) => 0,
+
+ // We don't need to measure ImportRule::stylesheet because we measure
+ // it on the C++ side in the child list of the ServoStyleSheet.
+ CssRule::Import(_) => 0,
+
+ CssRule::Style(ref lock) => {
+ lock.unconditional_shallow_size_of(ops) + lock.read_with(guard).size_of(guard, ops)
+ },
+
+ CssRule::Media(ref lock) => {
+ lock.unconditional_shallow_size_of(ops) + lock.read_with(guard).size_of(guard, ops)
+ },
+
+ CssRule::Container(ref lock) => {
+ lock.unconditional_shallow_size_of(ops) + lock.read_with(guard).size_of(guard, ops)
+ },
+
+ CssRule::FontFace(_) => 0,
+ CssRule::FontFeatureValues(_) => 0,
+ CssRule::FontPaletteValues(_) => 0,
+ CssRule::CounterStyle(_) => 0,
+ CssRule::Viewport(_) => 0,
+ CssRule::Keyframes(_) => 0,
+
+ CssRule::Supports(ref lock) => {
+ lock.unconditional_shallow_size_of(ops) + lock.read_with(guard).size_of(guard, ops)
+ },
+
+ CssRule::Page(ref lock) => {
+ lock.unconditional_shallow_size_of(ops) + lock.read_with(guard).size_of(guard, ops)
+ },
+
+ CssRule::Document(ref lock) => {
+ lock.unconditional_shallow_size_of(ops) + lock.read_with(guard).size_of(guard, ops)
+ },
+
+ // TODO(emilio): Add memory reporting for these rules.
+ CssRule::LayerBlock(_) | CssRule::LayerStatement(_) => 0,
+ }
+ }
+}
+
+/// https://drafts.csswg.org/cssom-1/#dom-cssrule-type
+#[allow(missing_docs)]
+#[derive(Clone, Copy, Debug, Eq, FromPrimitive, PartialEq)]
+#[repr(u8)]
+pub enum CssRuleType {
+ // https://drafts.csswg.org/cssom/#the-cssrule-interface
+ Style = 1,
+ // Charset = 2, // Historical
+ 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, // Not implemented yet.
+ 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/#om-fontfeaturevalues
+ FontFeatureValues = 14,
+ // https://drafts.csswg.org/css-device-adapt/#css-rule-interface
+ Viewport = 15,
+ // After viewport, all rules should return 0 from the API, but we still need
+ // a constant somewhere.
+ LayerBlock = 16,
+ LayerStatement = 17,
+ Container = 18,
+ FontPaletteValues = 19,
+}
+
+#[allow(missing_docs)]
+pub enum RulesMutateError {
+ Syntax,
+ IndexSize,
+ HierarchyRequest,
+ InvalidState,
+}
+
+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::FontFeatureValues(_) => CssRuleType::FontFeatureValues,
+ CssRule::FontPaletteValues(_) => CssRuleType::FontPaletteValues,
+ 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,
+ CssRule::LayerBlock(_) => CssRuleType::LayerBlock,
+ CssRule::LayerStatement(_) => CssRuleType::LayerStatement,
+ CssRule::Container(_) => CssRuleType::Container,
+ }
+ }
+
+ /// 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,
+ insert_rule_context: InsertRuleContext,
+ parent_stylesheet_contents: &StylesheetContents,
+ shared_lock: &SharedRwLock,
+ state: State,
+ loader: Option<&dyn StylesheetLoader>,
+ allow_import_rules: AllowImportRules,
+ ) -> Result<Self, RulesMutateError> {
+ let url_data = parent_stylesheet_contents.url_data.read();
+ let context = ParserContext::new(
+ parent_stylesheet_contents.origin,
+ &url_data,
+ None,
+ ParsingMode::DEFAULT,
+ parent_stylesheet_contents.quirks_mode,
+ None,
+ None,
+ );
+
+ let mut input = ParserInput::new(css);
+ let mut input = Parser::new(&mut input);
+
+ let mut guard = parent_stylesheet_contents.namespaces.write();
+
+ // nested rules are in the body state
+ let mut rule_parser = TopLevelRuleParser {
+ context,
+ shared_lock: &shared_lock,
+ loader,
+ state,
+ dom_error: None,
+ namespaces: &mut *guard,
+ insert_rule_context: Some(insert_rule_context),
+ allow_import_rules,
+ };
+
+ match parse_one_rule(&mut input, &mut rule_parser) {
+ Ok((_, rule)) => Ok(rule),
+ Err(_) => Err(rule_parser.dom_error.unwrap_or(RulesMutateError::Syntax)),
+ }
+ }
+}
+
+impl DeepCloneWithLock for CssRule {
+ /// Deep clones this CssRule.
+ fn deep_clone_with_lock(
+ &self,
+ lock: &SharedRwLock,
+ guard: &SharedRwLockReadGuard,
+ params: &DeepCloneParams,
+ ) -> 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)
+ .deep_clone_with_lock(lock, guard, params);
+ CssRule::Import(Arc::new(lock.wrap(rule)))
+ },
+ CssRule::Style(ref arc) => {
+ let rule = arc.read_with(guard);
+ CssRule::Style(Arc::new(
+ lock.wrap(rule.deep_clone_with_lock(lock, guard, params)),
+ ))
+ },
+ CssRule::Container(ref arc) => {
+ let rule = arc.read_with(guard);
+ CssRule::Container(Arc::new(
+ lock.wrap(rule.deep_clone_with_lock(lock, guard, params)),
+ ))
+ },
+ CssRule::Media(ref arc) => {
+ let rule = arc.read_with(guard);
+ CssRule::Media(Arc::new(
+ lock.wrap(rule.deep_clone_with_lock(lock, guard, params)),
+ ))
+ },
+ CssRule::FontFace(ref arc) => {
+ let rule = arc.read_with(guard);
+ CssRule::FontFace(Arc::new(lock.wrap(rule.clone())))
+ },
+ CssRule::FontFeatureValues(ref arc) => {
+ let rule = arc.read_with(guard);
+ CssRule::FontFeatureValues(Arc::new(lock.wrap(rule.clone())))
+ },
+ CssRule::FontPaletteValues(ref arc) => {
+ let rule = arc.read_with(guard);
+ CssRule::FontPaletteValues(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, params)),
+ ))
+ },
+ CssRule::Supports(ref arc) => {
+ let rule = arc.read_with(guard);
+ CssRule::Supports(Arc::new(
+ lock.wrap(rule.deep_clone_with_lock(lock, guard, params)),
+ ))
+ },
+ CssRule::Page(ref arc) => {
+ let rule = arc.read_with(guard);
+ CssRule::Page(Arc::new(
+ lock.wrap(rule.deep_clone_with_lock(lock, guard, params)),
+ ))
+ },
+ CssRule::Document(ref arc) => {
+ let rule = arc.read_with(guard);
+ CssRule::Document(Arc::new(
+ lock.wrap(rule.deep_clone_with_lock(lock, guard, params)),
+ ))
+ },
+ CssRule::LayerStatement(ref arc) => {
+ let rule = arc.read_with(guard);
+ CssRule::LayerStatement(Arc::new(
+ lock.wrap(rule.deep_clone_with_lock(lock, guard, params)),
+ ))
+ },
+ CssRule::LayerBlock(ref arc) => {
+ let rule = arc.read_with(guard);
+ CssRule::LayerBlock(Arc::new(
+ lock.wrap(rule.deep_clone_with_lock(lock, guard, params)),
+ ))
+ },
+ }
+ }
+}
+
+impl ToCssWithGuard for CssRule {
+ // https://drafts.csswg.org/cssom/#serialize-a-css-rule
+ fn to_css(&self, guard: &SharedRwLockReadGuard, dest: &mut CssStringWriter) -> fmt::Result {
+ 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::FontFeatureValues(ref lock) => lock.read_with(guard).to_css(guard, dest),
+ CssRule::FontPaletteValues(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),
+ CssRule::LayerBlock(ref lock) => lock.read_with(guard).to_css(guard, dest),
+ CssRule::LayerStatement(ref lock) => lock.read_with(guard).to_css(guard, dest),
+ CssRule::Container(ref lock) => lock.read_with(guard).to_css(guard, dest),
+ }
+ }
+}
diff --git a/servo/components/style/stylesheets/namespace_rule.rs b/servo/components/style/stylesheets/namespace_rule.rs
new file mode 100644
index 0000000000..ad980b70a8
--- /dev/null
+++ b/servo/components/style/stylesheets/namespace_rule.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 https://mozilla.org/MPL/2.0/. */
+
+//! The `@namespace` at-rule.
+
+use crate::shared_lock::{SharedRwLockReadGuard, ToCssWithGuard};
+use crate::str::CssStringWriter;
+use crate::{Namespace, Prefix};
+use cssparser::SourceLocation;
+use std::fmt::{self, Write};
+use style_traits::{CssWriter, ToCss};
+
+/// A `@namespace` rule.
+#[derive(Clone, Debug, PartialEq, ToShmem)]
+#[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(
+ &self,
+ _guard: &SharedRwLockReadGuard,
+ dest_str: &mut CssStringWriter,
+ ) -> fmt::Result {
+ let mut dest = CssWriter::new(dest_str);
+ dest.write_str("@namespace ")?;
+ if let Some(ref prefix) = self.prefix {
+ prefix.to_css(&mut dest)?;
+ dest.write_char(' ')?;
+ }
+ dest.write_str("url(")?;
+ self.url.to_string().to_css(&mut dest)?;
+ dest.write_str(");")
+ }
+}
diff --git a/servo/components/style/stylesheets/origin.rs b/servo/components/style/stylesheets/origin.rs
new file mode 100644
index 0000000000..27ad3fa184
--- /dev/null
+++ b/servo/components/style/stylesheets/origin.rs
@@ -0,0 +1,247 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+//! [CSS cascade origins](https://drafts.csswg.org/css-cascade/#cascading-origins).
+
+use std::marker::PhantomData;
+use std::ops::BitOrAssign;
+
+/// Each style rule has an origin, which determines where it enters the cascade.
+///
+/// <https://drafts.csswg.org/css-cascade/#cascading-origins>
+#[derive(Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq, ToShmem, PartialOrd, Ord)]
+#[repr(u8)]
+pub enum Origin {
+ /// <https://drafts.csswg.org/css-cascade/#cascade-origin-user-agent>
+ UserAgent = 0x1,
+
+ /// <https://drafts.csswg.org/css-cascade/#cascade-origin-user>
+ User = 0x2,
+
+ /// <https://drafts.csswg.org/css-cascade/#cascade-origin-author>
+ Author = 0x4,
+}
+
+impl Origin {
+ /// Returns an origin that goes in order for `index`.
+ ///
+ /// This is used for iterating across origins.
+ fn from_index(index: i8) -> Option<Self> {
+ Some(match index {
+ 0 => Origin::Author,
+ 1 => Origin::User,
+ 2 => Origin::UserAgent,
+ _ => return None,
+ })
+ }
+
+ fn to_index(self) -> i8 {
+ match self {
+ Origin::Author => 0,
+ Origin::User => 1,
+ Origin::UserAgent => 2,
+ }
+ }
+
+ /// Returns an iterator from this origin, towards all the less specific
+ /// origins. So for `UserAgent`, it'd iterate through all origins.
+ #[inline]
+ pub fn following_including(self) -> OriginSetIterator {
+ OriginSetIterator {
+ set: OriginSet::ORIGIN_USER | OriginSet::ORIGIN_AUTHOR | OriginSet::ORIGIN_USER_AGENT,
+ cur: self.to_index(),
+ rev: true,
+ }
+ }
+}
+
+bitflags! {
+ /// A set of origins. This is equivalent to Gecko's OriginFlags.
+ #[derive(MallocSizeOf)]
+ pub struct OriginSet: u8 {
+ /// <https://drafts.csswg.org/css-cascade/#cascade-origin-user-agent>
+ const ORIGIN_USER_AGENT = Origin::UserAgent as u8;
+ /// <https://drafts.csswg.org/css-cascade/#cascade-origin-user>
+ const ORIGIN_USER = Origin::User as u8;
+ /// <https://drafts.csswg.org/css-cascade/#cascade-origin-author>
+ const ORIGIN_AUTHOR = Origin::Author as u8;
+ }
+}
+
+impl OriginSet {
+ /// Returns an iterator over the origins present in this `OriginSet`.
+ ///
+ /// See the `OriginSet` documentation for information about the order
+ /// origins are iterated.
+ pub fn iter(&self) -> OriginSetIterator {
+ OriginSetIterator {
+ set: *self,
+ cur: 0,
+ rev: false,
+ }
+ }
+}
+
+impl From<Origin> for OriginSet {
+ fn from(origin: Origin) -> Self {
+ Self::from_bits_truncate(origin as u8)
+ }
+}
+
+impl BitOrAssign<Origin> for OriginSet {
+ fn bitor_assign(&mut self, origin: Origin) {
+ *self |= OriginSet::from(origin);
+ }
+}
+
+/// Iterates over the origins present in an `OriginSet`, in order from
+/// highest priority (author) to lower (user agent).
+#[derive(Clone)]
+pub struct OriginSetIterator {
+ set: OriginSet,
+ cur: i8,
+ rev: bool,
+}
+
+impl Iterator for OriginSetIterator {
+ type Item = Origin;
+
+ fn next(&mut self) -> Option<Origin> {
+ loop {
+ let origin = Origin::from_index(self.cur)?;
+
+ if self.rev {
+ self.cur -= 1;
+ } else {
+ self.cur += 1;
+ }
+
+ if self.set.contains(origin.into()) {
+ return Some(origin);
+ }
+ }
+ }
+}
+
+/// An object that stores a `T` for each origin of the CSS cascade.
+#[derive(Debug, Default, MallocSizeOf)]
+pub struct PerOrigin<T> {
+ /// Data for `Origin::UserAgent`.
+ pub user_agent: T,
+
+ /// Data for `Origin::User`.
+ pub user: T,
+
+ /// Data for `Origin::Author`.
+ pub author: T,
+}
+
+impl<T> PerOrigin<T> {
+ /// Returns a reference to the per-origin data for the specified origin.
+ #[inline]
+ pub fn borrow_for_origin(&self, origin: &Origin) -> &T {
+ match *origin {
+ Origin::UserAgent => &self.user_agent,
+ Origin::User => &self.user,
+ Origin::Author => &self.author,
+ }
+ }
+
+ /// Returns a mutable reference to the per-origin data for the specified
+ /// origin.
+ #[inline]
+ pub fn borrow_mut_for_origin(&mut self, origin: &Origin) -> &mut T {
+ match *origin {
+ Origin::UserAgent => &mut self.user_agent,
+ Origin::User => &mut self.user,
+ Origin::Author => &mut self.author,
+ }
+ }
+
+ /// Iterates over references to per-origin extra style data, from highest
+ /// level (author) to lowest (user agent).
+ pub fn iter_origins(&self) -> PerOriginIter<T> {
+ PerOriginIter {
+ data: &self,
+ cur: 0,
+ rev: false,
+ }
+ }
+
+ /// Iterates over references to per-origin extra style data, from lowest
+ /// level (user agent) to highest (author).
+ pub fn iter_origins_rev(&self) -> PerOriginIter<T> {
+ PerOriginIter {
+ data: &self,
+ cur: 2,
+ rev: true,
+ }
+ }
+
+ /// Iterates over mutable references to per-origin extra style data, from
+ /// highest level (author) to lowest (user agent).
+ pub fn iter_mut_origins(&mut self) -> PerOriginIterMut<T> {
+ PerOriginIterMut {
+ data: self,
+ cur: 0,
+ _marker: PhantomData,
+ }
+ }
+}
+
+/// Iterator over `PerOrigin<T>`, from highest level (author) to lowest
+/// (user agent).
+///
+/// We rely on this specific order for correctly looking up @font-face,
+/// @counter-style and @keyframes rules.
+pub struct PerOriginIter<'a, T: 'a> {
+ data: &'a PerOrigin<T>,
+ cur: i8,
+ rev: bool,
+}
+
+impl<'a, T> Iterator for PerOriginIter<'a, T>
+where
+ T: 'a,
+{
+ type Item = (&'a T, Origin);
+
+ fn next(&mut self) -> Option<Self::Item> {
+ let origin = Origin::from_index(self.cur)?;
+
+ self.cur += if self.rev { -1 } else { 1 };
+
+ Some((self.data.borrow_for_origin(&origin), origin))
+ }
+}
+
+/// Like `PerOriginIter<T>`, but iterates over mutable references to the
+/// per-origin data.
+///
+/// We must use unsafe code here since it's not possible for the borrow
+/// checker to know that we are safely returning a different reference
+/// each time from `next()`.
+pub struct PerOriginIterMut<'a, T: 'a> {
+ data: *mut PerOrigin<T>,
+ cur: i8,
+ _marker: PhantomData<&'a mut PerOrigin<T>>,
+}
+
+impl<'a, T> Iterator for PerOriginIterMut<'a, T>
+where
+ T: 'a,
+{
+ type Item = (&'a mut T, Origin);
+
+ fn next(&mut self) -> Option<Self::Item> {
+ let origin = Origin::from_index(self.cur)?;
+
+ self.cur += 1;
+
+ Some((
+ unsafe { (*self.data).borrow_mut_for_origin(&origin) },
+ origin,
+ ))
+ }
+}
diff --git a/servo/components/style/stylesheets/page_rule.rs b/servo/components/style/stylesheets/page_rule.rs
new file mode 100644
index 0000000000..5cd2458aa2
--- /dev/null
+++ b/servo/components/style/stylesheets/page_rule.rs
@@ -0,0 +1,145 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+//! A [`@page`][page] rule.
+//!
+//! [page]: https://drafts.csswg.org/css2/page.html#page-box
+
+use crate::parser::{Parse, ParserContext};
+use crate::properties::PropertyDeclarationBlock;
+use crate::shared_lock::{DeepCloneParams, DeepCloneWithLock, Locked};
+use crate::shared_lock::{SharedRwLock, SharedRwLockReadGuard, ToCssWithGuard};
+use crate::str::CssStringWriter;
+use crate::values::{AtomIdent, CustomIdent};
+use cssparser::{Parser, SourceLocation};
+#[cfg(feature = "gecko")]
+use malloc_size_of::{MallocSizeOf, MallocSizeOfOps, MallocUnconditionalShallowSizeOf};
+use servo_arc::Arc;
+use std::fmt::{self, Write};
+use style_traits::{CssWriter, ParseError, ToCss};
+
+/// Type of a single [`@page`][page selector]
+///
+/// We do not support pseudo selectors yet.
+/// [page-selectors]: https://drafts.csswg.org/css2/page.html#page-selectors
+#[derive(Clone, Debug, Eq, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToCss, ToShmem)]
+pub struct PageSelector(pub AtomIdent);
+
+impl PageSelector {
+ /// Checks if the ident matches a page-name's ident.
+ ///
+ /// This does not currently take pseudo selectors into account.
+ #[inline]
+ pub fn ident_matches(&self, other: &CustomIdent) -> bool {
+ self.0 .0 == other.0
+ }
+}
+
+impl Parse for PageSelector {
+ fn parse<'i, 't>(
+ _context: &ParserContext,
+ input: &mut Parser<'i, 't>,
+ ) -> Result<Self, ParseError<'i>> {
+ let s = input.expect_ident()?;
+ Ok(PageSelector(AtomIdent::from(&**s)))
+ }
+}
+
+/// A list of [`@page`][page selectors]
+///
+/// [page-selectors]: https://drafts.csswg.org/css2/page.html#page-selectors
+#[derive(Clone, Debug, Default, MallocSizeOf, ToCss, ToShmem)]
+#[css(comma)]
+pub struct PageSelectors(#[css(iterable)] pub Box<[PageSelector]>);
+
+impl PageSelectors {
+ /// Creates a new PageSelectors from a Vec, as from parse_comma_separated
+ #[inline]
+ pub fn new(s: Vec<PageSelector>) -> Self {
+ PageSelectors(s.into())
+ }
+ /// Returns true iff there are any page selectors
+ #[inline]
+ pub fn is_empty(&self) -> bool {
+ self.as_slice().is_empty()
+ }
+ /// Get the underlying PageSelector data as a slice
+ #[inline]
+ pub fn as_slice(&self) -> &[PageSelector] {
+ &*self.0
+ }
+}
+
+impl Parse for PageSelectors {
+ fn parse<'i, 't>(
+ context: &ParserContext,
+ input: &mut Parser<'i, 't>,
+ ) -> Result<Self, ParseError<'i>> {
+ Ok(PageSelectors::new(input.parse_comma_separated(|i| {
+ PageSelector::parse(context, i)
+ })?))
+ }
+}
+
+/// A [`@page`][page] rule.
+///
+/// This implements only a limited subset of the CSS
+/// 2.2 syntax.
+///
+/// [page]: https://drafts.csswg.org/css2/page.html#page-box
+/// [page-selectors]: https://drafts.csswg.org/css2/page.html#page-selectors
+#[derive(Clone, Debug, ToShmem)]
+pub struct PageRule {
+ /// Selectors of the page-rule
+ pub selectors: PageSelectors,
+ /// 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 PageRule {
+ /// Measure heap usage.
+ #[cfg(feature = "gecko")]
+ pub fn size_of(&self, guard: &SharedRwLockReadGuard, ops: &mut MallocSizeOfOps) -> usize {
+ // Measurement of other fields may be added later.
+ self.block.unconditional_shallow_size_of(ops) +
+ self.block.read_with(guard).size_of(ops) +
+ self.selectors.size_of(ops)
+ }
+}
+
+impl ToCssWithGuard for PageRule {
+ /// Serialization of PageRule is not specced, adapted from steps for
+ /// StyleRule.
+ fn to_css(&self, guard: &SharedRwLockReadGuard, dest: &mut CssStringWriter) -> fmt::Result {
+ dest.write_str("@page ")?;
+ if !self.selectors.is_empty() {
+ self.selectors.to_css(&mut CssWriter::new(dest))?;
+ dest.write_char(' ')?;
+ }
+ dest.write_str("{ ")?;
+ let declaration_block = self.block.read_with(guard);
+ declaration_block.to_css(dest)?;
+ if !declaration_block.declarations().is_empty() {
+ dest.write_char(' ')?;
+ }
+ dest.write_char('}')
+ }
+}
+
+impl DeepCloneWithLock for PageRule {
+ fn deep_clone_with_lock(
+ &self,
+ lock: &SharedRwLock,
+ guard: &SharedRwLockReadGuard,
+ _params: &DeepCloneParams,
+ ) -> Self {
+ PageRule {
+ selectors: self.selectors.clone(),
+ block: Arc::new(lock.wrap(self.block.read_with(&guard).clone())),
+ source_location: self.source_location.clone(),
+ }
+ }
+}
diff --git a/servo/components/style/stylesheets/rule_list.rs b/servo/components/style/stylesheets/rule_list.rs
new file mode 100644
index 0000000000..c246d7ae6b
--- /dev/null
+++ b/servo/components/style/stylesheets/rule_list.rs
@@ -0,0 +1,189 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+//! A list of CSS rules.
+
+use crate::shared_lock::{DeepCloneParams, DeepCloneWithLock, Locked};
+use crate::shared_lock::{SharedRwLock, SharedRwLockReadGuard, ToCssWithGuard};
+use crate::str::CssStringWriter;
+use crate::stylesheets::loader::StylesheetLoader;
+use crate::stylesheets::rule_parser::{InsertRuleContext, State};
+use crate::stylesheets::stylesheet::StylesheetContents;
+use crate::stylesheets::{AllowImportRules, CssRule, RulesMutateError};
+#[cfg(feature = "gecko")]
+use malloc_size_of::{MallocShallowSizeOf, MallocSizeOfOps};
+use servo_arc::{Arc, RawOffsetArc};
+use std::fmt::{self, Write};
+
+/// A list of CSS rules.
+#[derive(Debug, ToShmem)]
+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,
+ params: &DeepCloneParams,
+ ) -> Self {
+ CssRules(
+ self.0
+ .iter()
+ .map(|x| x.deep_clone_with_lock(lock, guard, params))
+ .collect(),
+ )
+ }
+}
+
+impl CssRules {
+ /// Measure heap usage.
+ #[cfg(feature = "gecko")]
+ pub fn size_of(&self, guard: &SharedRwLockReadGuard, ops: &mut MallocSizeOfOps) -> usize {
+ let mut n = self.0.shallow_size_of(ops);
+ for rule in self.0.iter() {
+ n += rule.size_of(guard, ops);
+ }
+ n
+ }
+
+ /// 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(())
+ }
+
+ /// Serializes this CSSRules to CSS text as a block of rules.
+ ///
+ /// This should be speced into CSSOM spec at some point. See
+ /// <https://github.com/w3c/csswg-drafts/issues/1985>
+ pub fn to_css_block(
+ &self,
+ guard: &SharedRwLockReadGuard,
+ dest: &mut CssStringWriter,
+ ) -> fmt::Result {
+ dest.write_str(" {")?;
+ for rule in self.0.iter() {
+ dest.write_str("\n ")?;
+ rule.to_css(guard, dest)?;
+ }
+ dest.write_str("\n}")
+ }
+}
+
+/// 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_contents: &StylesheetContents,
+ index: usize,
+ nested: bool,
+ loader: Option<&dyn StylesheetLoader>,
+ allow_import_rules: AllowImportRules,
+ ) -> Result<CssRule, RulesMutateError>;
+}
+
+impl CssRulesHelpers for RawOffsetArc<Locked<CssRules>> {
+ fn insert_rule(
+ &self,
+ lock: &SharedRwLock,
+ rule: &str,
+ parent_stylesheet_contents: &StylesheetContents,
+ index: usize,
+ nested: bool,
+ loader: Option<&dyn StylesheetLoader>,
+ allow_import_rules: AllowImportRules,
+ ) -> Result<CssRule, RulesMutateError> {
+ let new_rule = {
+ 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
+ let insert_rule_context = InsertRuleContext {
+ rule_list: &rules.0,
+ index,
+ };
+
+ let state = if nested {
+ State::Body
+ } else if index == 0 {
+ State::Start
+ } else {
+ insert_rule_context.max_rule_state_at_index(index - 1)
+ };
+
+ // Steps 3, 4, 5, 6
+ CssRule::parse(
+ &rule,
+ insert_rule_context,
+ parent_stylesheet_contents,
+ lock,
+ state,
+ loader,
+ allow_import_rules,
+ )?
+ };
+
+ {
+ let mut write_guard = lock.write();
+ let rules = self.write_with(&mut write_guard);
+ rules.0.insert(index, new_rule.clone());
+ }
+
+ Ok(new_rule)
+ }
+}
diff --git a/servo/components/style/stylesheets/rule_parser.rs b/servo/components/style/stylesheets/rule_parser.rs
new file mode 100644
index 0000000000..b86e31aead
--- /dev/null
+++ b/servo/components/style/stylesheets/rule_parser.rs
@@ -0,0 +1,796 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+//! Parsing of the stylesheet contents.
+
+use crate::counter_style::{parse_counter_style_body, parse_counter_style_name_definition};
+use crate::error_reporting::ContextualParseError;
+use crate::font_face::parse_font_face_block;
+use crate::media_queries::MediaList;
+use crate::parser::{Parse, ParserContext};
+use crate::properties::parse_property_declaration_list;
+use crate::selector_parser::{SelectorImpl, SelectorParser};
+use crate::shared_lock::{Locked, SharedRwLock};
+use crate::str::starts_with_ignore_ascii_case;
+use crate::stylesheets::container_rule::{ContainerCondition, ContainerRule};
+use crate::stylesheets::document_rule::DocumentCondition;
+use crate::stylesheets::font_feature_values_rule::parse_family_name_list;
+use crate::stylesheets::import_rule::ImportLayer;
+use crate::stylesheets::keyframes_rule::parse_keyframe_list;
+use crate::stylesheets::layer_rule::{LayerBlockRule, LayerName, LayerStatementRule};
+use crate::stylesheets::stylesheet::Namespaces;
+use crate::stylesheets::supports_rule::SupportsCondition;
+use crate::stylesheets::{
+ viewport_rule, AllowImportRules, CorsMode, CssRule, CssRuleType, CssRules, DocumentRule,
+ FontFeatureValuesRule, FontPaletteValuesRule, KeyframesRule, MediaRule, NamespaceRule,
+ PageRule, PageSelectors, RulesMutateError, StyleRule, StylesheetLoader, SupportsRule,
+ ViewportRule,
+};
+use crate::values::computed::font::FamilyName;
+use crate::values::{CssUrl, CustomIdent, DashedIdent, KeyframesName};
+use crate::{Namespace, Prefix};
+use cssparser::{
+ AtRuleParser, BasicParseError, BasicParseErrorKind, CowRcStr, Parser, ParserState,
+ QualifiedRuleParser, RuleListParser, SourcePosition,
+};
+use selectors::SelectorList;
+use servo_arc::Arc;
+use style_traits::{ParseError, StyleParseErrorKind};
+
+/// The information we need particularly to do CSSOM insertRule stuff.
+pub struct InsertRuleContext<'a> {
+ /// The rule list we're about to insert into.
+ pub rule_list: &'a [CssRule],
+ /// The index we're about to get inserted at.
+ pub index: usize,
+}
+
+impl<'a> InsertRuleContext<'a> {
+ /// Returns the max rule state allowable for insertion at a given index in
+ /// the rule list.
+ pub fn max_rule_state_at_index(&self, index: usize) -> State {
+ let rule = match self.rule_list.get(index) {
+ Some(rule) => rule,
+ None => return State::Body,
+ };
+ match rule {
+ CssRule::Import(..) => State::Imports,
+ CssRule::Namespace(..) => State::Namespaces,
+ CssRule::LayerStatement(..) => {
+ // If there are @import / @namespace after this layer, then
+ // we're in the early-layers phase, otherwise we're in the body
+ // and everything is fair game.
+ let next_non_layer_statement_rule = self.rule_list[index + 1..]
+ .iter()
+ .find(|r| !matches!(*r, CssRule::LayerStatement(..)));
+ if let Some(non_layer) = next_non_layer_statement_rule {
+ if matches!(*non_layer, CssRule::Import(..) | CssRule::Namespace(..)) {
+ return State::EarlyLayers;
+ }
+ }
+ State::Body
+ },
+ _ => State::Body,
+ }
+ }
+}
+
+/// The parser for the top-level rules in a stylesheet.
+pub struct TopLevelRuleParser<'a> {
+ /// 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 dyn StylesheetLoader>,
+ /// The top-level parser context.
+ ///
+ /// This won't contain any namespaces, and only nested parsers created with
+ /// `ParserContext::new_with_rule_type` will.
+ pub context: ParserContext<'a>,
+ /// The current state of the parser.
+ pub state: State,
+ /// Whether we have tried to parse was invalid due to being in the wrong
+ /// place (e.g. an @import rule was found while in the `Body` state). Reset
+ /// to `false` when `take_had_hierarchy_error` is called.
+ pub dom_error: Option<RulesMutateError>,
+ /// 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: &'a mut Namespaces,
+ /// The info we need insert a rule in a list.
+ pub insert_rule_context: Option<InsertRuleContext<'a>>,
+ /// Whether @import rules will be allowed.
+ pub allow_import_rules: AllowImportRules,
+}
+
+impl<'b> TopLevelRuleParser<'b> {
+ fn nested<'a: 'b>(&'a self) -> NestedRuleParser<'a, 'b> {
+ NestedRuleParser {
+ shared_lock: self.shared_lock,
+ context: &self.context,
+ namespaces: &self.namespaces,
+ }
+ }
+
+ /// Returns the current state of the parser.
+ pub fn state(&self) -> State {
+ self.state
+ }
+
+ /// Checks whether we can parse a rule that would transition us to
+ /// `new_state`.
+ ///
+ /// This is usually a simple branch, but we may need more bookkeeping if
+ /// doing `insertRule` from CSSOM.
+ fn check_state(&mut self, new_state: State) -> bool {
+ if self.state > new_state {
+ self.dom_error = Some(RulesMutateError::HierarchyRequest);
+ return false;
+ }
+
+ let ctx = match self.insert_rule_context {
+ Some(ref ctx) => ctx,
+ None => return true,
+ };
+
+ let max_rule_state = ctx.max_rule_state_at_index(ctx.index);
+ if new_state > max_rule_state {
+ self.dom_error = Some(RulesMutateError::HierarchyRequest);
+ return false;
+ }
+
+ // If there's anything that isn't a namespace rule (or import rule, but
+ // we checked that already at the beginning), reject with a
+ // StateError.
+ if new_state == State::Namespaces &&
+ ctx.rule_list[ctx.index..]
+ .iter()
+ .any(|r| !matches!(*r, CssRule::Namespace(..)))
+ {
+ self.dom_error = Some(RulesMutateError::InvalidState);
+ return false;
+ }
+
+ true
+ }
+}
+
+/// The current state of the parser.
+#[derive(Clone, Copy, Eq, Ord, PartialEq, PartialOrd)]
+pub enum State {
+ /// We haven't started parsing rules.
+ Start = 1,
+ /// We're parsing early `@layer` statement rules.
+ EarlyLayers = 2,
+ /// We're parsing `@import` and early `@layer` statement rules.
+ Imports = 3,
+ /// We're parsing `@namespace` rules.
+ Namespaces = 4,
+ /// We're parsing the main body of the stylesheet.
+ Body = 5,
+}
+
+#[derive(Clone, Debug, MallocSizeOf, ToShmem)]
+/// Vendor prefix.
+pub enum VendorPrefix {
+ /// -moz prefix.
+ Moz,
+ /// -webkit prefix.
+ WebKit,
+}
+
+/// A rule prelude for at-rule with block.
+pub enum AtRulePrelude {
+ /// A @font-face rule prelude.
+ FontFace,
+ /// A @font-feature-values rule prelude, with its FamilyName list.
+ FontFeatureValues(Vec<FamilyName>),
+ /// A @font-palette-values rule prelude, with its identifier.
+ FontPaletteValues(DashedIdent),
+ /// A @counter-style rule prelude, with its counter style name.
+ CounterStyle(CustomIdent),
+ /// A @media rule prelude, with its media queries.
+ Media(Arc<Locked<MediaList>>),
+ /// A @container rule prelude.
+ Container(Arc<ContainerCondition>),
+ /// An @supports rule, with its conditional
+ Supports(SupportsCondition),
+ /// A @viewport rule prelude.
+ Viewport,
+ /// A @keyframes rule, with its animation name and vendor prefix if exists.
+ Keyframes(KeyframesName, Option<VendorPrefix>),
+ /// A @page rule prelude, with its page name if it exists.
+ Page(PageSelectors),
+ /// A @document rule, with its conditional.
+ Document(DocumentCondition),
+ /// A @import rule prelude.
+ Import(CssUrl, Arc<Locked<MediaList>>, Option<ImportLayer>),
+ /// A @namespace rule prelude.
+ Namespace(Option<Prefix>, Namespace),
+ /// A @layer rule prelude.
+ Layer(Vec<LayerName>),
+}
+
+impl<'a, 'i> AtRuleParser<'i> for TopLevelRuleParser<'a> {
+ type Prelude = AtRulePrelude;
+ type AtRule = (SourcePosition, CssRule);
+ type Error = StyleParseErrorKind<'i>;
+
+ fn parse_prelude<'t>(
+ &mut self,
+ name: CowRcStr<'i>,
+ input: &mut Parser<'i, 't>,
+ ) -> Result<AtRulePrelude, ParseError<'i>> {
+ match_ignore_ascii_case! { &*name,
+ "import" => {
+ if !self.check_state(State::Imports) {
+ return Err(input.new_custom_error(StyleParseErrorKind::UnexpectedImportRule))
+ }
+
+ if let AllowImportRules::No = self.allow_import_rules {
+ return Err(input.new_custom_error(StyleParseErrorKind::DisallowedImportRule))
+ }
+
+ // FIXME(emilio): We should always be able to have a loader
+ // around! See bug 1533783.
+ if self.loader.is_none() {
+ error!("Saw @import rule, but no way to trigger the load");
+ return Err(input.new_custom_error(StyleParseErrorKind::UnexpectedImportRule))
+ }
+
+ let url_string = input.expect_url_or_string()?.as_ref().to_owned();
+ let url = CssUrl::parse_from_string(url_string, &self.context, CorsMode::None);
+
+ let layer = if !static_prefs::pref!("layout.css.cascade-layers.enabled") {
+ None
+ } else if input.try_parse(|input| input.expect_ident_matching("layer")).is_ok() {
+ Some(ImportLayer {
+ name: None,
+ })
+ } else {
+ input.try_parse(|input| {
+ input.expect_function_matching("layer")?;
+ input.parse_nested_block(|input| {
+ LayerName::parse(&self.context, input)
+ }).map(|name| ImportLayer {
+ name: Some(name),
+ })
+ }).ok()
+ };
+
+ let media = MediaList::parse(&self.context, input);
+ let media = Arc::new(self.shared_lock.wrap(media));
+
+ return Ok(AtRulePrelude::Import(url, media, layer));
+ },
+ "namespace" => {
+ if !self.check_state(State::Namespaces) {
+ return Err(input.new_custom_error(StyleParseErrorKind::UnexpectedNamespaceRule))
+ }
+
+ let prefix = input.try_parse(|i| i.expect_ident_cloned())
+ .map(|s| Prefix::from(s.as_ref())).ok();
+ let maybe_namespace = match input.expect_url_or_string() {
+ Ok(url_or_string) => url_or_string,
+ Err(BasicParseError { kind: BasicParseErrorKind::UnexpectedToken(t), location }) => {
+ return Err(location.new_custom_error(StyleParseErrorKind::UnexpectedTokenWithinNamespace(t)))
+ }
+ Err(e) => return Err(e.into()),
+ };
+ let url = Namespace::from(maybe_namespace.as_ref());
+ return Ok(AtRulePrelude::Namespace(prefix, url));
+ },
+ // @charset is removed by rust-cssparser if it’s the first rule in the stylesheet
+ // anything left is invalid.
+ "charset" => {
+ self.dom_error = Some(RulesMutateError::HierarchyRequest);
+ return Err(input.new_custom_error(StyleParseErrorKind::UnexpectedCharsetRule))
+ },
+ "layer" => {
+ let state_to_check = if self.state <= State::EarlyLayers {
+ // The real state depends on whether there's a block or not.
+ // We don't know that yet, but the parse_block check deals
+ // with that.
+ State::EarlyLayers
+ } else {
+ State::Body
+ };
+ if !self.check_state(state_to_check) {
+ return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
+ }
+ },
+ _ => {
+ // All other rules have blocks, so we do this check early in
+ // parse_block instead.
+ }
+ }
+
+ AtRuleParser::parse_prelude(&mut self.nested(), name, input)
+ }
+
+ #[inline]
+ fn parse_block<'t>(
+ &mut self,
+ prelude: AtRulePrelude,
+ start: &ParserState,
+ input: &mut Parser<'i, 't>,
+ ) -> Result<Self::AtRule, ParseError<'i>> {
+ if !self.check_state(State::Body) {
+ return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
+ }
+ let rule = AtRuleParser::parse_block(&mut self.nested(), prelude, start, input)?;
+ self.state = State::Body;
+ Ok((start.position(), rule))
+ }
+
+ #[inline]
+ fn rule_without_block(
+ &mut self,
+ prelude: AtRulePrelude,
+ start: &ParserState,
+ ) -> Result<Self::AtRule, ()> {
+ let rule = match prelude {
+ AtRulePrelude::Import(url, media, layer) => {
+ let loader = self
+ .loader
+ .expect("Expected a stylesheet loader for @import");
+
+ let import_rule = loader.request_stylesheet(
+ url,
+ start.source_location(),
+ &self.context,
+ &self.shared_lock,
+ media,
+ layer,
+ );
+
+ self.state = State::Imports;
+ CssRule::Import(import_rule)
+ },
+ AtRulePrelude::Namespace(prefix, url) => {
+ let prefix = if let Some(prefix) = prefix {
+ self.namespaces.prefixes.insert(prefix.clone(), url.clone());
+ Some(prefix)
+ } else {
+ self.namespaces.default = Some(url.clone());
+ None
+ };
+
+ self.state = State::Namespaces;
+ CssRule::Namespace(Arc::new(self.shared_lock.wrap(NamespaceRule {
+ prefix,
+ url,
+ source_location: start.source_location(),
+ })))
+ },
+ AtRulePrelude::Layer(ref names) => {
+ if names.is_empty() {
+ return Err(());
+ }
+ if self.state <= State::EarlyLayers {
+ self.state = State::EarlyLayers;
+ } else {
+ self.state = State::Body;
+ }
+ AtRuleParser::rule_without_block(&mut self.nested(), prelude, start)
+ .expect("All validity checks on the nested parser should be done before changing self.state")
+ },
+ _ => AtRuleParser::rule_without_block(&mut self.nested(), prelude, start)?,
+ };
+
+ Ok((start.position(), rule))
+ }
+}
+
+impl<'a, 'i> QualifiedRuleParser<'i> for TopLevelRuleParser<'a> {
+ type Prelude = SelectorList<SelectorImpl>;
+ type QualifiedRule = (SourcePosition, CssRule);
+ type Error = StyleParseErrorKind<'i>;
+
+ #[inline]
+ fn parse_prelude<'t>(
+ &mut self,
+ input: &mut Parser<'i, 't>,
+ ) -> Result<Self::Prelude, ParseError<'i>> {
+ if !self.check_state(State::Body) {
+ return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
+ }
+
+ QualifiedRuleParser::parse_prelude(&mut self.nested(), input)
+ }
+
+ #[inline]
+ fn parse_block<'t>(
+ &mut self,
+ prelude: Self::Prelude,
+ start: &ParserState,
+ input: &mut Parser<'i, 't>,
+ ) -> Result<Self::QualifiedRule, ParseError<'i>> {
+ let rule = QualifiedRuleParser::parse_block(&mut self.nested(), prelude, start, input)?;
+ self.state = State::Body;
+ Ok((start.position(), rule))
+ }
+}
+
+#[derive(Clone)] // shallow, relatively cheap .clone
+struct NestedRuleParser<'a, 'b: 'a> {
+ shared_lock: &'a SharedRwLock,
+ context: &'a ParserContext<'b>,
+ namespaces: &'a Namespaces,
+}
+
+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, rule_type, self.namespaces);
+
+ let nested_parser = NestedRuleParser {
+ shared_lock: self.shared_lock,
+ context: &context,
+ namespaces: self.namespaces,
+ };
+
+ 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((error, slice)) => {
+ let location = error.location;
+ let error = ContextualParseError::InvalidRule(slice, error);
+ self.context.log_css_error(location, error);
+ },
+ }
+ }
+ CssRules::new(rules, self.shared_lock)
+ }
+}
+
+impl<'a, 'b, 'i> AtRuleParser<'i> for NestedRuleParser<'a, 'b> {
+ type Prelude = AtRulePrelude;
+ type AtRule = CssRule;
+ type Error = StyleParseErrorKind<'i>;
+
+ fn parse_prelude<'t>(
+ &mut self,
+ name: CowRcStr<'i>,
+ input: &mut Parser<'i, 't>,
+ ) -> Result<Self::Prelude, ParseError<'i>> {
+ Ok(match_ignore_ascii_case! { &*name,
+ "media" => {
+ let media_queries = MediaList::parse(self.context, input);
+ let arc = Arc::new(self.shared_lock.wrap(media_queries));
+ AtRulePrelude::Media(arc)
+ },
+ "supports" => {
+ let cond = SupportsCondition::parse(input)?;
+ AtRulePrelude::Supports(cond)
+ },
+ "font-face" => {
+ AtRulePrelude::FontFace
+ },
+ "container" if static_prefs::pref!("layout.css.container-queries.enabled") => {
+ let condition = Arc::new(ContainerCondition::parse(self.context, input)?);
+ AtRulePrelude::Container(condition)
+ },
+ "layer" if static_prefs::pref!("layout.css.cascade-layers.enabled") => {
+ let names = input.try_parse(|input| {
+ input.parse_comma_separated(|input| {
+ LayerName::parse(self.context, input)
+ })
+ }).unwrap_or_default();
+ AtRulePrelude::Layer(names)
+ },
+ "font-feature-values" if cfg!(feature = "gecko") => {
+ let family_names = parse_family_name_list(self.context, input)?;
+ AtRulePrelude::FontFeatureValues(family_names)
+ },
+ "font-palette-values" if static_prefs::pref!("layout.css.font-palette.enabled") => {
+ let name = DashedIdent::parse(self.context, input)?;
+ AtRulePrelude::FontPaletteValues(name)
+ },
+ "counter-style" if cfg!(feature = "gecko") => {
+ let name = parse_counter_style_name_definition(input)?;
+ AtRulePrelude::CounterStyle(name)
+ },
+ "viewport" if viewport_rule::enabled() => {
+ AtRulePrelude::Viewport
+ },
+ "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(input.new_custom_error(StyleParseErrorKind::UnsupportedAtRule(name.clone())))
+ }
+ let name = KeyframesName::parse(self.context, input)?;
+ AtRulePrelude::Keyframes(name, prefix)
+ },
+ "page" if cfg!(feature = "gecko") => {
+ AtRulePrelude::Page(if static_prefs::pref!("layout.css.named-pages.enabled") {
+ input.try_parse(|i| PageSelectors::parse(self.context, i)).unwrap_or_default()
+ } else {
+ PageSelectors::default()
+ })
+ },
+ "-moz-document" if cfg!(feature = "gecko") => {
+ let cond = DocumentCondition::parse(self.context, input)?;
+ AtRulePrelude::Document(cond)
+ },
+ _ => return Err(input.new_custom_error(StyleParseErrorKind::UnsupportedAtRule(name.clone())))
+ })
+ }
+
+ fn parse_block<'t>(
+ &mut self,
+ prelude: AtRulePrelude,
+ start: &ParserState,
+ input: &mut Parser<'i, 't>,
+ ) -> Result<CssRule, ParseError<'i>> {
+ match prelude {
+ AtRulePrelude::FontFace => {
+ let context = ParserContext::new_with_rule_type(
+ self.context,
+ CssRuleType::FontFace,
+ self.namespaces,
+ );
+
+ Ok(CssRule::FontFace(Arc::new(self.shared_lock.wrap(
+ parse_font_face_block(&context, input, start.source_location()).into(),
+ ))))
+ },
+ AtRulePrelude::FontFeatureValues(family_names) => {
+ let context = ParserContext::new_with_rule_type(
+ self.context,
+ CssRuleType::FontFeatureValues,
+ self.namespaces,
+ );
+
+ Ok(CssRule::FontFeatureValues(Arc::new(self.shared_lock.wrap(
+ FontFeatureValuesRule::parse(
+ &context,
+ input,
+ family_names,
+ start.source_location(),
+ ),
+ ))))
+ },
+ AtRulePrelude::FontPaletteValues(name) => {
+ let context = ParserContext::new_with_rule_type(
+ self.context,
+ CssRuleType::FontPaletteValues,
+ self.namespaces,
+ );
+
+ Ok(CssRule::FontPaletteValues(Arc::new(self.shared_lock.wrap(
+ FontPaletteValuesRule::parse(
+ &context,
+ input,
+ name,
+ start.source_location(),
+ ),
+ ))))
+ },
+ AtRulePrelude::CounterStyle(name) => {
+ let context = ParserContext::new_with_rule_type(
+ self.context,
+ CssRuleType::CounterStyle,
+ self.namespaces,
+ );
+
+ Ok(CssRule::CounterStyle(Arc::new(
+ self.shared_lock.wrap(
+ parse_counter_style_body(name, &context, input, start.source_location())?
+ .into(),
+ ),
+ )))
+ },
+ AtRulePrelude::Media(media_queries) => {
+ Ok(CssRule::Media(Arc::new(self.shared_lock.wrap(MediaRule {
+ media_queries,
+ rules: self.parse_nested_rules(input, CssRuleType::Media),
+ source_location: start.source_location(),
+ }))))
+ },
+ AtRulePrelude::Supports(condition) => {
+ let eval_context = ParserContext::new_with_rule_type(
+ self.context,
+ CssRuleType::Style,
+ self.namespaces,
+ );
+
+ let enabled = condition.eval(&eval_context, self.namespaces);
+ Ok(CssRule::Supports(Arc::new(self.shared_lock.wrap(
+ SupportsRule {
+ condition,
+ rules: self.parse_nested_rules(input, CssRuleType::Supports),
+ enabled,
+ source_location: start.source_location(),
+ },
+ ))))
+ },
+ AtRulePrelude::Viewport => {
+ let context = ParserContext::new_with_rule_type(
+ self.context,
+ CssRuleType::Viewport,
+ self.namespaces,
+ );
+
+ Ok(CssRule::Viewport(Arc::new(
+ self.shared_lock.wrap(ViewportRule::parse(&context, input)?),
+ )))
+ },
+ AtRulePrelude::Keyframes(name, vendor_prefix) => {
+ let context = ParserContext::new_with_rule_type(
+ self.context,
+ CssRuleType::Keyframes,
+ self.namespaces,
+ );
+
+ Ok(CssRule::Keyframes(Arc::new(self.shared_lock.wrap(
+ KeyframesRule {
+ name,
+ keyframes: parse_keyframe_list(&context, input, self.shared_lock),
+ vendor_prefix,
+ source_location: start.source_location(),
+ },
+ ))))
+ },
+ AtRulePrelude::Page(selectors) => {
+ let context = ParserContext::new_with_rule_type(
+ self.context,
+ CssRuleType::Page,
+ self.namespaces,
+ );
+
+ let declarations = parse_property_declaration_list(&context, input, None);
+ Ok(CssRule::Page(Arc::new(self.shared_lock.wrap(PageRule {
+ selectors,
+ block: Arc::new(self.shared_lock.wrap(declarations)),
+ source_location: start.source_location(),
+ }))))
+ },
+ AtRulePrelude::Document(condition) => {
+ if !cfg!(feature = "gecko") {
+ unreachable!()
+ }
+ Ok(CssRule::Document(Arc::new(self.shared_lock.wrap(
+ DocumentRule {
+ condition,
+ rules: self.parse_nested_rules(input, CssRuleType::Document),
+ source_location: start.source_location(),
+ },
+ ))))
+ },
+ AtRulePrelude::Container(condition) => Ok(CssRule::Container(Arc::new(
+ self.shared_lock.wrap(ContainerRule {
+ condition,
+ rules: self.parse_nested_rules(input, CssRuleType::Container),
+ source_location: start.source_location(),
+ }),
+ ))),
+ AtRulePrelude::Layer(names) => {
+ let name = match names.len() {
+ 0 | 1 => names.into_iter().next(),
+ _ => return Err(input.new_error(BasicParseErrorKind::AtRuleBodyInvalid)),
+ };
+ Ok(CssRule::LayerBlock(Arc::new(self.shared_lock.wrap(
+ LayerBlockRule {
+ name,
+ rules: self.parse_nested_rules(input, CssRuleType::LayerBlock),
+ source_location: start.source_location(),
+ },
+ ))))
+ },
+ AtRulePrelude::Import(..) | AtRulePrelude::Namespace(..) => {
+ // These rules don't have blocks.
+ Err(input.new_unexpected_token_error(cssparser::Token::CurlyBracketBlock))
+ },
+ }
+ }
+
+ #[inline]
+ fn rule_without_block(
+ &mut self,
+ prelude: AtRulePrelude,
+ start: &ParserState,
+ ) -> Result<Self::AtRule, ()> {
+ Ok(match prelude {
+ AtRulePrelude::Layer(names) => {
+ if names.is_empty() {
+ return Err(());
+ }
+ CssRule::LayerStatement(Arc::new(self.shared_lock.wrap(LayerStatementRule {
+ names,
+ source_location: start.source_location(),
+ })))
+ },
+ _ => return Err(()),
+ })
+ }
+}
+
+#[inline(never)]
+fn check_for_useless_selector(
+ input: &mut Parser,
+ context: &ParserContext,
+ selectors: &SelectorList<SelectorImpl>,
+) {
+ use cssparser::ToCss;
+
+ 'selector_loop: for selector in selectors.0.iter() {
+ let mut current = selector.iter();
+ loop {
+ let mut found_host = false;
+ let mut found_non_host = false;
+ for component in &mut current {
+ if component.is_host() {
+ found_host = true;
+ } else {
+ found_non_host = true;
+ }
+ if found_host && found_non_host {
+ let location = input.current_source_location();
+ context.log_css_error(
+ location,
+ ContextualParseError::NeverMatchingHostSelector(selector.to_css_string()),
+ );
+ continue 'selector_loop;
+ }
+ }
+ if current.next_sequence().is_none() {
+ break;
+ }
+ }
+ }
+}
+
+impl<'a, 'b, 'i> QualifiedRuleParser<'i> for NestedRuleParser<'a, 'b> {
+ type Prelude = SelectorList<SelectorImpl>;
+ type QualifiedRule = CssRule;
+ type Error = StyleParseErrorKind<'i>;
+
+ fn parse_prelude<'t>(
+ &mut self,
+ input: &mut Parser<'i, 't>,
+ ) -> Result<Self::Prelude, ParseError<'i>> {
+ let selector_parser = SelectorParser {
+ stylesheet_origin: self.context.stylesheet_origin,
+ namespaces: self.namespaces,
+ url_data: self.context.url_data,
+ for_supports_rule: false,
+ };
+ let selectors = SelectorList::parse(&selector_parser, input)?;
+ if self.context.error_reporting_enabled() {
+ check_for_useless_selector(input, &self.context, &selectors);
+ }
+ Ok(selectors)
+ }
+
+ fn parse_block<'t>(
+ &mut self,
+ selectors: Self::Prelude,
+ start: &ParserState,
+ input: &mut Parser<'i, 't>,
+ ) -> Result<CssRule, ParseError<'i>> {
+ let context =
+ ParserContext::new_with_rule_type(self.context, CssRuleType::Style, self.namespaces);
+
+ let declarations = parse_property_declaration_list(&context, input, Some(&selectors));
+ let block = Arc::new(self.shared_lock.wrap(declarations));
+ Ok(CssRule::Style(Arc::new(self.shared_lock.wrap(StyleRule {
+ selectors,
+ block,
+ source_location: start.source_location(),
+ }))))
+ }
+}
diff --git a/servo/components/style/stylesheets/rules_iterator.rs b/servo/components/style/stylesheets/rules_iterator.rs
new file mode 100644
index 0000000000..00c095bb8b
--- /dev/null
+++ b/servo/components/style/stylesheets/rules_iterator.rs
@@ -0,0 +1,330 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+//! An iterator over a list of rules.
+
+use crate::context::QuirksMode;
+use crate::media_queries::Device;
+use crate::shared_lock::SharedRwLockReadGuard;
+use crate::stylesheets::{CssRule, DocumentRule, ImportRule, MediaRule, SupportsRule};
+use smallvec::SmallVec;
+use std::slice;
+
+/// An iterator over a list of rules.
+pub struct RulesIterator<'a, 'b, C>
+where
+ 'b: 'a,
+ C: NestedRuleIterationCondition + 'static,
+{
+ device: &'a Device,
+ quirks_mode: QuirksMode,
+ guard: &'a SharedRwLockReadGuard<'b>,
+ stack: SmallVec<[slice::Iter<'a, CssRule>; 3]>,
+ _phantom: ::std::marker::PhantomData<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: slice::Iter<'a, CssRule>,
+ ) -> Self {
+ let mut stack = SmallVec::new();
+ stack.push(rules);
+ Self {
+ device,
+ quirks_mode,
+ guard,
+ stack,
+ _phantom: ::std::marker::PhantomData,
+ }
+ }
+
+ /// Skips all the remaining children of the last nested rule processed.
+ pub fn skip_children(&mut self) {
+ self.stack.pop();
+ }
+
+ /// Returns the children of `rule`, and whether `rule` is effective.
+ pub fn children(
+ rule: &'a CssRule,
+ device: &'a Device,
+ quirks_mode: QuirksMode,
+ guard: &'a SharedRwLockReadGuard<'_>,
+ effective: &mut bool,
+ ) -> Option<slice::Iter<'a, CssRule>> {
+ *effective = true;
+ match *rule {
+ CssRule::Namespace(_) |
+ CssRule::Style(_) |
+ CssRule::FontFace(_) |
+ CssRule::CounterStyle(_) |
+ CssRule::Viewport(_) |
+ CssRule::Keyframes(_) |
+ CssRule::Page(_) |
+ CssRule::LayerStatement(_) |
+ CssRule::FontFeatureValues(_) |
+ CssRule::FontPaletteValues(_) => None,
+ CssRule::Import(ref import_rule) => {
+ let import_rule = import_rule.read_with(guard);
+ if !C::process_import(guard, device, quirks_mode, import_rule) {
+ *effective = false;
+ return None;
+ }
+ Some(import_rule.stylesheet.rules(guard).iter())
+ },
+ CssRule::Document(ref doc_rule) => {
+ let doc_rule = doc_rule.read_with(guard);
+ if !C::process_document(guard, device, quirks_mode, doc_rule) {
+ *effective = false;
+ return None;
+ }
+ Some(doc_rule.rules.read_with(guard).0.iter())
+ },
+ CssRule::Container(ref lock) => {
+ let container_rule = lock.read_with(guard);
+ Some(container_rule.rules.read_with(guard).0.iter())
+ },
+ CssRule::Media(ref lock) => {
+ let media_rule = lock.read_with(guard);
+ if !C::process_media(guard, device, quirks_mode, media_rule) {
+ *effective = false;
+ return None;
+ }
+ Some(media_rule.rules.read_with(guard).0.iter())
+ },
+ CssRule::Supports(ref lock) => {
+ let supports_rule = lock.read_with(guard);
+ if !C::process_supports(guard, device, quirks_mode, supports_rule) {
+ *effective = false;
+ return None;
+ }
+ Some(supports_rule.rules.read_with(guard).0.iter())
+ },
+ CssRule::LayerBlock(ref lock) => {
+ let layer_rule = lock.read_with(guard);
+ Some(layer_rule.rules.read_with(guard).0.iter())
+ },
+ }
+ }
+}
+
+impl<'a, 'b, C> Iterator for RulesIterator<'a, 'b, C>
+where
+ 'b: 'a,
+ C: NestedRuleIterationCondition + 'static,
+{
+ type Item = &'a CssRule;
+
+ fn next(&mut self) -> Option<Self::Item> {
+ while !self.stack.is_empty() {
+ let rule = {
+ let nested_iter = self.stack.last_mut().unwrap();
+ match nested_iter.next() {
+ Some(r) => r,
+ None => {
+ self.stack.pop();
+ continue;
+ },
+ }
+ };
+
+ let mut effective = true;
+ let children = Self::children(
+ rule,
+ self.device,
+ self.quirks_mode,
+ self.guard,
+ &mut effective,
+ );
+ if !effective {
+ continue;
+ }
+
+ if let Some(children) = children {
+ // NOTE: It's important that `children` gets pushed even if
+ // empty, so that `skip_children()` works as expected.
+ self.stack.push(children);
+ }
+
+ return Some(rule);
+ }
+
+ None
+ }
+}
+
+/// RulesIterator.
+pub trait NestedRuleIterationCondition {
+ /// Whether we should process the nested rules in a given `@import` rule.
+ fn process_import(
+ guard: &SharedRwLockReadGuard,
+ device: &Device,
+ quirks_mode: QuirksMode,
+ rule: &ImportRule,
+ ) -> bool;
+
+ /// Whether we should process the nested rules in a given `@media` rule.
+ fn process_media(
+ guard: &SharedRwLockReadGuard,
+ device: &Device,
+ quirks_mode: QuirksMode,
+ rule: &MediaRule,
+ ) -> bool;
+
+ /// Whether we should process the nested rules in a given `@-moz-document`
+ /// rule.
+ fn process_document(
+ guard: &SharedRwLockReadGuard,
+ device: &Device,
+ quirks_mode: QuirksMode,
+ rule: &DocumentRule,
+ ) -> bool;
+
+ /// Whether we should process the nested rules in a given `@supports` rule.
+ fn process_supports(
+ guard: &SharedRwLockReadGuard,
+ device: &Device,
+ quirks_mode: QuirksMode,
+ rule: &SupportsRule,
+ ) -> bool;
+}
+
+/// A struct that represents the condition that a rule applies to the document.
+pub struct EffectiveRules;
+
+impl EffectiveRules {
+ /// Returns whether a given rule is effective.
+ pub fn is_effective(
+ guard: &SharedRwLockReadGuard,
+ device: &Device,
+ quirks_mode: QuirksMode,
+ rule: &CssRule,
+ ) -> bool {
+ match *rule {
+ CssRule::Import(ref import_rule) => {
+ let import_rule = import_rule.read_with(guard);
+ Self::process_import(guard, device, quirks_mode, import_rule)
+ },
+ CssRule::Document(ref doc_rule) => {
+ let doc_rule = doc_rule.read_with(guard);
+ Self::process_document(guard, device, quirks_mode, doc_rule)
+ },
+ CssRule::Media(ref lock) => {
+ let media_rule = lock.read_with(guard);
+ Self::process_media(guard, device, quirks_mode, media_rule)
+ },
+ CssRule::Supports(ref lock) => {
+ let supports_rule = lock.read_with(guard);
+ Self::process_supports(guard, device, quirks_mode, supports_rule)
+ },
+ _ => true,
+ }
+ }
+}
+
+impl NestedRuleIterationCondition for EffectiveRules {
+ fn process_import(
+ guard: &SharedRwLockReadGuard,
+ device: &Device,
+ quirks_mode: QuirksMode,
+ rule: &ImportRule,
+ ) -> bool {
+ match rule.stylesheet.media(guard) {
+ Some(m) => m.evaluate(device, quirks_mode),
+ None => true,
+ }
+ }
+
+ fn process_media(
+ guard: &SharedRwLockReadGuard,
+ device: &Device,
+ quirks_mode: QuirksMode,
+ rule: &MediaRule,
+ ) -> bool {
+ rule.media_queries
+ .read_with(guard)
+ .evaluate(device, quirks_mode)
+ }
+
+ fn process_document(
+ _: &SharedRwLockReadGuard,
+ device: &Device,
+ _: QuirksMode,
+ rule: &DocumentRule,
+ ) -> bool {
+ rule.condition.evaluate(device)
+ }
+
+ fn process_supports(
+ _: &SharedRwLockReadGuard,
+ _: &Device,
+ _: QuirksMode,
+ rule: &SupportsRule,
+ ) -> bool {
+ rule.enabled
+ }
+}
+
+/// A filter that processes all the rules in a rule list.
+pub struct AllRules;
+
+impl NestedRuleIterationCondition for AllRules {
+ fn process_import(
+ _: &SharedRwLockReadGuard,
+ _: &Device,
+ _: QuirksMode,
+ _: &ImportRule,
+ ) -> bool {
+ true
+ }
+
+ fn process_media(_: &SharedRwLockReadGuard, _: &Device, _: QuirksMode, _: &MediaRule) -> bool {
+ true
+ }
+
+ fn process_document(
+ _: &SharedRwLockReadGuard,
+ _: &Device,
+ _: QuirksMode,
+ _: &DocumentRule,
+ ) -> bool {
+ true
+ }
+
+ fn process_supports(
+ _: &SharedRwLockReadGuard,
+ _: &Device,
+ _: QuirksMode,
+ _: &SupportsRule,
+ ) -> bool {
+ true
+ }
+}
+
+/// An iterator over all the effective rules of a stylesheet.
+///
+/// NOTE: This iterator recurses into `@import` rules.
+pub type EffectiveRulesIterator<'a, 'b> = RulesIterator<'a, 'b, EffectiveRules>;
+
+impl<'a, 'b> EffectiveRulesIterator<'a, 'b> {
+ /// Returns an iterator over the effective children of a rule, even if
+ /// `rule` itself is not effective.
+ pub fn effective_children(
+ device: &'a Device,
+ quirks_mode: QuirksMode,
+ guard: &'a SharedRwLockReadGuard<'b>,
+ rule: &'a CssRule,
+ ) -> Self {
+ let children =
+ RulesIterator::<AllRules>::children(rule, device, quirks_mode, guard, &mut false);
+ EffectiveRulesIterator::new(device, quirks_mode, guard, children.unwrap_or([].iter()))
+ }
+}
diff --git a/servo/components/style/stylesheets/style_rule.rs b/servo/components/style/stylesheets/style_rule.rs
new file mode 100644
index 0000000000..905e067f73
--- /dev/null
+++ b/servo/components/style/stylesheets/style_rule.rs
@@ -0,0 +1,79 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+//! A style rule.
+
+use crate::properties::PropertyDeclarationBlock;
+use crate::selector_parser::SelectorImpl;
+use crate::shared_lock::{DeepCloneParams, DeepCloneWithLock, Locked};
+use crate::shared_lock::{SharedRwLock, SharedRwLockReadGuard, ToCssWithGuard};
+use crate::str::CssStringWriter;
+use cssparser::SourceLocation;
+#[cfg(feature = "gecko")]
+use malloc_size_of::MallocUnconditionalShallowSizeOf;
+#[cfg(feature = "gecko")]
+use malloc_size_of::{MallocSizeOf, MallocSizeOfOps};
+use selectors::SelectorList;
+use servo_arc::Arc;
+use std::fmt::{self, Write};
+
+/// A style rule, with selectors and declarations.
+#[derive(Debug, ToShmem)]
+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,
+ _params: &DeepCloneParams,
+ ) -> StyleRule {
+ StyleRule {
+ selectors: self.selectors.clone(),
+ block: Arc::new(lock.wrap(self.block.read_with(guard).clone())),
+ source_location: self.source_location.clone(),
+ }
+ }
+}
+
+impl StyleRule {
+ /// Measure heap usage.
+ #[cfg(feature = "gecko")]
+ pub fn size_of(&self, guard: &SharedRwLockReadGuard, ops: &mut MallocSizeOfOps) -> usize {
+ let mut n = 0;
+ n += self.selectors.0.size_of(ops);
+ n += self.block.unconditional_shallow_size_of(ops) +
+ self.block.read_with(guard).size_of(ops);
+ n
+ }
+}
+
+impl ToCssWithGuard for StyleRule {
+ /// https://drafts.csswg.org/cssom/#serialize-a-css-rule CSSStyleRule
+ fn to_css(&self, guard: &SharedRwLockReadGuard, dest: &mut CssStringWriter) -> fmt::Result {
+ 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/servo/components/style/stylesheets/stylesheet.rs b/servo/components/style/stylesheets/stylesheet.rs
new file mode 100644
index 0000000000..fb65964974
--- /dev/null
+++ b/servo/components/style/stylesheets/stylesheet.rs
@@ -0,0 +1,608 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+use crate::context::QuirksMode;
+use crate::error_reporting::{ContextualParseError, ParseErrorReporter};
+use crate::media_queries::{Device, MediaList};
+use crate::parser::ParserContext;
+use crate::shared_lock::{DeepCloneParams, DeepCloneWithLock, Locked};
+use crate::shared_lock::{SharedRwLock, SharedRwLockReadGuard};
+use crate::stylesheets::loader::StylesheetLoader;
+use crate::stylesheets::rule_parser::{State, TopLevelRuleParser};
+use crate::stylesheets::rules_iterator::{EffectiveRules, EffectiveRulesIterator};
+use crate::stylesheets::rules_iterator::{NestedRuleIterationCondition, RulesIterator};
+use crate::stylesheets::{CssRule, CssRules, Origin, UrlExtraData};
+use crate::use_counters::UseCounters;
+use crate::{Namespace, Prefix};
+use cssparser::{Parser, ParserInput, RuleListParser};
+use fxhash::FxHashMap;
+#[cfg(feature = "gecko")]
+use malloc_size_of::{MallocSizeOfOps, MallocUnconditionalShallowSizeOf};
+use parking_lot::RwLock;
+use servo_arc::Arc;
+use std::mem;
+use std::sync::atomic::{AtomicBool, Ordering};
+use style_traits::ParsingMode;
+
+/// 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<DocumentStyleSheet>,
+ /// The quirks mode stylesheet.
+ pub quirks_mode_stylesheet: DocumentStyleSheet,
+}
+
+/// A set of namespaces applying to a given stylesheet.
+///
+/// The namespace id is used in gecko
+#[derive(Clone, Debug, Default, MallocSizeOf)]
+#[allow(missing_docs)]
+pub struct Namespaces {
+ pub default: Option<Namespace>,
+ pub prefixes: FxHashMap<Prefix, Namespace>,
+}
+
+/// The contents of a given stylesheet. This effectively maps to a
+/// StyleSheetInner in Gecko.
+#[derive(Debug)]
+pub struct StylesheetContents {
+ /// List of rules in the order they were found (important for
+ /// cascading order)
+ pub rules: Arc<Locked<CssRules>>,
+ /// The origin of this stylesheet.
+ pub origin: Origin,
+ /// The url data this stylesheet should use.
+ pub url_data: RwLock<UrlExtraData>,
+ /// The namespaces that apply to this stylesheet.
+ pub namespaces: RwLock<Namespaces>,
+ /// The quirks mode of this stylesheet.
+ pub quirks_mode: QuirksMode,
+ /// This stylesheet's source map URL.
+ pub source_map_url: RwLock<Option<String>>,
+ /// This stylesheet's source URL.
+ pub source_url: RwLock<Option<String>>,
+
+ /// We don't want to allow construction outside of this file, to guarantee
+ /// that all contents are created with Arc<>.
+ _forbid_construction: (),
+}
+
+impl StylesheetContents {
+ /// Parse a given CSS string, with a given url-data, origin, and
+ /// quirks mode.
+ pub fn from_str(
+ css: &str,
+ url_data: UrlExtraData,
+ origin: Origin,
+ shared_lock: &SharedRwLock,
+ stylesheet_loader: Option<&dyn StylesheetLoader>,
+ error_reporter: Option<&dyn ParseErrorReporter>,
+ quirks_mode: QuirksMode,
+ line_number_offset: u32,
+ use_counters: Option<&UseCounters>,
+ allow_import_rules: AllowImportRules,
+ sanitization_data: Option<&mut SanitizationData>,
+ ) -> Arc<Self> {
+ let namespaces = RwLock::new(Namespaces::default());
+ let (rules, source_map_url, source_url) = Stylesheet::parse_rules(
+ css,
+ &url_data,
+ origin,
+ &mut *namespaces.write(),
+ &shared_lock,
+ stylesheet_loader,
+ error_reporter,
+ quirks_mode,
+ line_number_offset,
+ use_counters,
+ allow_import_rules,
+ sanitization_data,
+ );
+
+ Arc::new(Self {
+ rules: CssRules::new(rules, &shared_lock),
+ origin,
+ url_data: RwLock::new(url_data),
+ namespaces,
+ quirks_mode,
+ source_map_url: RwLock::new(source_map_url),
+ source_url: RwLock::new(source_url),
+ _forbid_construction: (),
+ })
+ }
+
+ /// Creates a new StylesheetContents with the specified pre-parsed rules,
+ /// origin, URL data, and quirks mode.
+ ///
+ /// Since the rules have already been parsed, and the intention is that
+ /// this function is used for read only User Agent style sheets, an empty
+ /// namespace map is used, and the source map and source URLs are set to
+ /// None.
+ ///
+ /// An empty namespace map should be fine, as it is only used for parsing,
+ /// not serialization of existing selectors. Since UA sheets are read only,
+ /// we should never need the namespace map.
+ pub fn from_shared_data(
+ rules: Arc<Locked<CssRules>>,
+ origin: Origin,
+ url_data: UrlExtraData,
+ quirks_mode: QuirksMode,
+ ) -> Arc<Self> {
+ debug_assert!(rules.is_static());
+ Arc::new(Self {
+ rules,
+ origin,
+ url_data: RwLock::new(url_data),
+ namespaces: RwLock::new(Namespaces::default()),
+ quirks_mode,
+ source_map_url: RwLock::new(None),
+ source_url: RwLock::new(None),
+ _forbid_construction: (),
+ })
+ }
+
+ /// Returns a reference to the list of rules.
+ #[inline]
+ pub fn rules<'a, 'b: 'a>(&'a self, guard: &'b SharedRwLockReadGuard) -> &'a [CssRule] {
+ &self.rules.read_with(guard).0
+ }
+
+ /// Measure heap usage.
+ #[cfg(feature = "gecko")]
+ pub fn size_of(&self, guard: &SharedRwLockReadGuard, ops: &mut MallocSizeOfOps) -> usize {
+ if self.rules.is_static() {
+ return 0;
+ }
+ // Measurement of other fields may be added later.
+ self.rules.unconditional_shallow_size_of(ops) +
+ self.rules.read_with(guard).size_of(guard, ops)
+ }
+}
+
+impl DeepCloneWithLock for StylesheetContents {
+ fn deep_clone_with_lock(
+ &self,
+ lock: &SharedRwLock,
+ guard: &SharedRwLockReadGuard,
+ params: &DeepCloneParams,
+ ) -> Self {
+ // Make a deep clone of the rules, using the new lock.
+ let rules = self
+ .rules
+ .read_with(guard)
+ .deep_clone_with_lock(lock, guard, params);
+
+ Self {
+ rules: Arc::new(lock.wrap(rules)),
+ quirks_mode: self.quirks_mode,
+ origin: self.origin,
+ url_data: RwLock::new((*self.url_data.read()).clone()),
+ namespaces: RwLock::new((*self.namespaces.read()).clone()),
+ source_map_url: RwLock::new((*self.source_map_url.read()).clone()),
+ source_url: RwLock::new((*self.source_url.read()).clone()),
+ _forbid_construction: (),
+ }
+ }
+}
+
+/// The structure servo uses to represent a stylesheet.
+#[derive(Debug)]
+pub struct Stylesheet {
+ /// The contents of this stylesheet.
+ pub contents: Arc<StylesheetContents>,
+ /// The lock used for objects inside this stylesheet
+ pub shared_lock: SharedRwLock,
+ /// List of media associated with the Stylesheet.
+ pub media: Arc<Locked<MediaList>>,
+ /// Whether this stylesheet should be disabled.
+ pub disabled: AtomicBool,
+}
+
+macro_rules! rule_filter {
+ ($( $method: ident($variant:ident => $rule_type: ident), )+) => {
+ $(
+ #[allow(missing_docs)]
+ fn $method<F>(&self, device: &Device, guard: &SharedRwLockReadGuard, mut f: F)
+ where F: FnMut(&crate::stylesheets::$rule_type),
+ {
+ use crate::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)
+ }
+ }
+ }
+ )+
+ }
+}
+
+/// A trait to represent a given stylesheet in a document.
+pub trait StylesheetInDocument: ::std::fmt::Debug {
+ /// Get whether this stylesheet is enabled.
+ fn enabled(&self) -> bool;
+
+ /// Get the media associated with this stylesheet.
+ fn media<'a>(&'a self, guard: &'a SharedRwLockReadGuard) -> Option<&'a MediaList>;
+
+ /// Returns a reference to the list of rules in this stylesheet.
+ fn rules<'a, 'b: 'a>(&'a self, guard: &'b SharedRwLockReadGuard) -> &'a [CssRule] {
+ self.contents().rules(guard)
+ }
+
+ /// Returns a reference to the contents of the stylesheet.
+ fn contents(&self) -> &StylesheetContents;
+
+ /// Return an iterator using the condition `C`.
+ #[inline]
+ fn iter_rules<'a, 'b, C>(
+ &'a self,
+ device: &'a Device,
+ guard: &'a SharedRwLockReadGuard<'b>,
+ ) -> RulesIterator<'a, 'b, C>
+ where
+ C: NestedRuleIterationCondition,
+ {
+ let contents = self.contents();
+ RulesIterator::new(
+ device,
+ contents.quirks_mode,
+ guard,
+ contents.rules(guard).iter(),
+ )
+ }
+
+ /// Returns whether the style-sheet applies for the current device.
+ fn is_effective_for_device(&self, device: &Device, guard: &SharedRwLockReadGuard) -> bool {
+ match self.media(guard) {
+ Some(medialist) => medialist.evaluate(device, self.contents().quirks_mode),
+ None => true,
+ }
+ }
+
+ /// Return an iterator over the effective rules within the style-sheet, as
+ /// according to the supplied `Device`.
+ #[inline]
+ fn effective_rules<'a, 'b>(
+ &'a self,
+ device: &'a Device,
+ guard: &'a SharedRwLockReadGuard<'b>,
+ ) -> EffectiveRulesIterator<'a, 'b> {
+ self.iter_rules::<EffectiveRules>(device, guard)
+ }
+
+ rule_filter! {
+ effective_style_rules(Style => StyleRule),
+ effective_viewport_rules(Viewport => ViewportRule),
+ }
+}
+
+impl StylesheetInDocument for Stylesheet {
+ fn media<'a>(&'a self, guard: &'a SharedRwLockReadGuard) -> Option<&'a MediaList> {
+ Some(self.media.read_with(guard))
+ }
+
+ fn enabled(&self) -> bool {
+ !self.disabled()
+ }
+
+ #[inline]
+ fn contents(&self) -> &StylesheetContents {
+ &self.contents
+ }
+}
+
+/// A simple wrapper over an `Arc<Stylesheet>`, with pointer comparison, and
+/// suitable for its use in a `StylesheetSet`.
+#[derive(Clone, Debug)]
+#[cfg_attr(feature = "servo", derive(MallocSizeOf))]
+pub struct DocumentStyleSheet(
+ #[cfg_attr(feature = "servo", ignore_malloc_size_of = "Arc")] pub Arc<Stylesheet>,
+);
+
+impl PartialEq for DocumentStyleSheet {
+ fn eq(&self, other: &Self) -> bool {
+ Arc::ptr_eq(&self.0, &other.0)
+ }
+}
+
+impl StylesheetInDocument for DocumentStyleSheet {
+ fn media<'a>(&'a self, guard: &'a SharedRwLockReadGuard) -> Option<&'a MediaList> {
+ self.0.media(guard)
+ }
+
+ fn enabled(&self) -> bool {
+ self.0.enabled()
+ }
+
+ #[inline]
+ fn contents(&self) -> &StylesheetContents {
+ self.0.contents()
+ }
+}
+
+/// The kind of sanitization to use when parsing a stylesheet.
+#[repr(u8)]
+#[derive(Clone, Copy, Debug, PartialEq)]
+pub enum SanitizationKind {
+ /// Perform no sanitization.
+ None,
+ /// Allow only @font-face, style rules, and @namespace.
+ Standard,
+ /// Allow everything but conditional rules.
+ NoConditionalRules,
+}
+
+/// Whether @import rules are allowed.
+#[repr(u8)]
+#[derive(Clone, Copy, Debug, PartialEq)]
+pub enum AllowImportRules {
+ /// @import rules will be parsed.
+ Yes,
+ /// @import rules will not be parsed.
+ No,
+}
+
+impl SanitizationKind {
+ fn allows(self, rule: &CssRule) -> bool {
+ debug_assert_ne!(self, SanitizationKind::None);
+ // NOTE(emilio): If this becomes more complex (not filtering just by
+ // top-level rules), we should thread all the data through nested rules
+ // and such. But this doesn't seem necessary at the moment.
+ let is_standard = matches!(self, SanitizationKind::Standard);
+ match *rule {
+ CssRule::Document(..) |
+ CssRule::Media(..) |
+ CssRule::Supports(..) |
+ CssRule::Import(..) |
+ CssRule::Container(..) |
+ // TODO(emilio): Perhaps Layer should not be always sanitized? But
+ // we sanitize @media and co, so this seems safer for now.
+ CssRule::LayerStatement(..) |
+ CssRule::LayerBlock(..) => false,
+
+ CssRule::FontFace(..) | CssRule::Namespace(..) | CssRule::Style(..) => true,
+
+ CssRule::Keyframes(..) |
+ CssRule::Page(..) |
+ CssRule::FontFeatureValues(..) |
+ CssRule::FontPaletteValues(..) |
+ CssRule::Viewport(..) |
+ CssRule::CounterStyle(..) => !is_standard,
+ }
+ }
+}
+
+/// A struct to hold the data relevant to style sheet sanitization.
+#[derive(Debug)]
+pub struct SanitizationData {
+ kind: SanitizationKind,
+ output: String,
+}
+
+impl SanitizationData {
+ /// Create a new input for sanitization.
+ #[inline]
+ pub fn new(kind: SanitizationKind) -> Option<Self> {
+ if matches!(kind, SanitizationKind::None) {
+ return None;
+ }
+ Some(Self {
+ kind,
+ output: String::new(),
+ })
+ }
+
+ /// Take the sanitized output.
+ #[inline]
+ pub fn take(self) -> String {
+ self.output
+ }
+}
+
+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<&dyn StylesheetLoader>,
+ error_reporter: Option<&dyn ParseErrorReporter>,
+ line_number_offset: u32,
+ allow_import_rules: AllowImportRules,
+ ) {
+ let namespaces = RwLock::new(Namespaces::default());
+
+ // FIXME: Consider adding use counters to Servo?
+ let (rules, source_map_url, source_url) = Self::parse_rules(
+ css,
+ &url_data,
+ existing.contents.origin,
+ &mut *namespaces.write(),
+ &existing.shared_lock,
+ stylesheet_loader,
+ error_reporter,
+ existing.contents.quirks_mode,
+ line_number_offset,
+ /* use_counters = */ None,
+ allow_import_rules,
+ /* sanitization_data = */ None,
+ );
+
+ *existing.contents.url_data.write() = url_data;
+ mem::swap(
+ &mut *existing.contents.namespaces.write(),
+ &mut *namespaces.write(),
+ );
+
+ // Acquire the lock *after* parsing, to minimize the exclusive section.
+ let mut guard = existing.shared_lock.write();
+ *existing.contents.rules.write_with(&mut guard) = CssRules(rules);
+ *existing.contents.source_map_url.write() = source_map_url;
+ *existing.contents.source_url.write() = source_url;
+ }
+
+ fn parse_rules(
+ css: &str,
+ url_data: &UrlExtraData,
+ origin: Origin,
+ namespaces: &mut Namespaces,
+ shared_lock: &SharedRwLock,
+ stylesheet_loader: Option<&dyn StylesheetLoader>,
+ error_reporter: Option<&dyn ParseErrorReporter>,
+ quirks_mode: QuirksMode,
+ line_number_offset: u32,
+ use_counters: Option<&UseCounters>,
+ allow_import_rules: AllowImportRules,
+ mut sanitization_data: Option<&mut SanitizationData>,
+ ) -> (Vec<CssRule>, Option<String>, Option<String>) {
+ let mut rules = Vec::new();
+ let mut input = ParserInput::new_with_line_number_offset(css, line_number_offset);
+ let mut input = Parser::new(&mut input);
+
+ let context = ParserContext::new(
+ origin,
+ url_data,
+ None,
+ ParsingMode::DEFAULT,
+ quirks_mode,
+ error_reporter,
+ use_counters,
+ );
+
+ let rule_parser = TopLevelRuleParser {
+ shared_lock,
+ loader: stylesheet_loader,
+ context,
+ state: State::Start,
+ dom_error: None,
+ insert_rule_context: None,
+ namespaces,
+ allow_import_rules,
+ };
+
+ {
+ let mut iter = RuleListParser::new_for_stylesheet(&mut input, rule_parser);
+
+ loop {
+ let result = match iter.next() {
+ Some(result) => result,
+ None => break,
+ };
+ match result {
+ Ok((rule_start, rule)) => {
+ if let Some(ref mut data) = sanitization_data {
+ if !data.kind.allows(&rule) {
+ continue;
+ }
+ let end = iter.input.position().byte_index();
+ data.output.push_str(&css[rule_start.byte_index()..end]);
+ }
+ // Use a fallible push here, and if it fails, just fall
+ // out of the loop. This will cause the page to be
+ // shown incorrectly, but it's better than OOMing.
+ if rules.try_reserve(1).is_err() {
+ break;
+ }
+ rules.push(rule);
+ },
+ Err((error, slice)) => {
+ let location = error.location;
+ let error = ContextualParseError::InvalidRule(slice, error);
+ iter.parser.context.log_css_error(location, error);
+ },
+ }
+ }
+ }
+
+ let source_map_url = input.current_source_map_url().map(String::from);
+ let source_url = input.current_source_url().map(String::from);
+ (rules, source_map_url, source_url)
+ }
+
+ /// 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<&dyn StylesheetLoader>,
+ error_reporter: Option<&dyn ParseErrorReporter>,
+ quirks_mode: QuirksMode,
+ line_number_offset: u32,
+ allow_import_rules: AllowImportRules,
+ ) -> Self {
+ // FIXME: Consider adding use counters to Servo?
+ let contents = StylesheetContents::from_str(
+ css,
+ url_data,
+ origin,
+ &shared_lock,
+ stylesheet_loader,
+ error_reporter,
+ quirks_mode,
+ line_number_offset,
+ /* use_counters = */ None,
+ allow_import_rules,
+ /* sanitized_output = */ None,
+ );
+
+ Stylesheet {
+ contents,
+ shared_lock,
+ media,
+ disabled: AtomicBool::new(false),
+ }
+ }
+
+ /// 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
+ }
+}
+
+#[cfg(feature = "servo")]
+impl Clone for Stylesheet {
+ fn clone(&self) -> Self {
+ // 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 media, using the new lock.
+ let media = self.media.read_with(&guard).clone();
+ let media = Arc::new(lock.wrap(media));
+ let contents = Arc::new(self.contents.deep_clone_with_lock(
+ &lock,
+ &guard,
+ &DeepCloneParams,
+ ));
+
+ Stylesheet {
+ contents,
+ media: media,
+ shared_lock: lock,
+ disabled: AtomicBool::new(self.disabled.load(Ordering::SeqCst)),
+ }
+ }
+}
diff --git a/servo/components/style/stylesheets/supports_rule.rs b/servo/components/style/stylesheets/supports_rule.rs
new file mode 100644
index 0000000000..14b9afa18e
--- /dev/null
+++ b/servo/components/style/stylesheets/supports_rule.rs
@@ -0,0 +1,433 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+//! [@supports rules](https://drafts.csswg.org/css-conditional-3/#at-supports)
+
+use crate::font_face::{FontFaceSourceFormatKeyword, FontFaceSourceTechFlags};
+use crate::parser::ParserContext;
+use crate::properties::{PropertyDeclaration, PropertyId, SourcePropertyDeclaration};
+use crate::selector_parser::{SelectorImpl, SelectorParser};
+use crate::shared_lock::{DeepCloneParams, DeepCloneWithLock, Locked};
+use crate::shared_lock::{SharedRwLock, SharedRwLockReadGuard, ToCssWithGuard};
+use crate::str::CssStringWriter;
+use crate::stylesheets::{CssRuleType, CssRules, Namespaces};
+use cssparser::parse_important;
+use cssparser::{Delimiter, Parser, SourceLocation, Token};
+use cssparser::{ParseError as CssParseError, ParserInput};
+#[cfg(feature = "gecko")]
+use malloc_size_of::{MallocSizeOfOps, MallocUnconditionalShallowSizeOf};
+use selectors::parser::{Selector, SelectorParseErrorKind};
+use servo_arc::Arc;
+use std::ffi::{CStr, CString};
+use std::fmt::{self, Write};
+use std::str;
+use style_traits::{CssWriter, ParseError, StyleParseErrorKind, ToCss};
+
+/// An [`@supports`][supports] rule.
+///
+/// [supports]: https://drafts.csswg.org/css-conditional-3/#at-supports
+#[derive(Debug, ToShmem)]
+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 SupportsRule {
+ /// Measure heap usage.
+ #[cfg(feature = "gecko")]
+ pub fn size_of(&self, guard: &SharedRwLockReadGuard, ops: &mut MallocSizeOfOps) -> usize {
+ // Measurement of other fields may be added later.
+ self.rules.unconditional_shallow_size_of(ops) +
+ self.rules.read_with(guard).size_of(guard, ops)
+ }
+}
+
+impl ToCssWithGuard for SupportsRule {
+ fn to_css(&self, guard: &SharedRwLockReadGuard, dest: &mut CssStringWriter) -> fmt::Result {
+ dest.write_str("@supports ")?;
+ self.condition.to_css(&mut CssWriter::new(dest))?;
+ self.rules.read_with(guard).to_css_block(guard, dest)
+ }
+}
+
+impl DeepCloneWithLock for SupportsRule {
+ fn deep_clone_with_lock(
+ &self,
+ lock: &SharedRwLock,
+ guard: &SharedRwLockReadGuard,
+ params: &DeepCloneParams,
+ ) -> 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, params))),
+ enabled: self.enabled,
+ source_location: self.source_location.clone(),
+ }
+ }
+}
+
+/// An @supports condition
+///
+/// <https://drafts.csswg.org/css-conditional-3/#at-supports>
+#[derive(Clone, Debug, ToShmem)]
+pub enum SupportsCondition {
+ /// `not (condition)`
+ Not(Box<SupportsCondition>),
+ /// `(condition)`
+ Parenthesized(Box<SupportsCondition>),
+ /// `(condition) and (condition) and (condition) ..`
+ And(Vec<SupportsCondition>),
+ /// `(condition) or (condition) or (condition) ..`
+ Or(Vec<SupportsCondition>),
+ /// `property-ident: value` (value can be any tokens)
+ Declaration(Declaration),
+ /// A `selector()` function.
+ Selector(RawSelector),
+ /// `-moz-bool-pref("pref-name")`
+ /// Since we need to pass it through FFI to get the pref value,
+ /// we store it as CString directly.
+ MozBoolPref(CString),
+ /// `font-format(<font-format>)`
+ FontFormat(FontFaceSourceFormatKeyword),
+ /// `font-tech(<font-tech>)`
+ FontTech(FontFaceSourceTechFlags),
+ /// `(any tokens)` or `func(any tokens)`
+ FutureSyntax(String),
+}
+
+impl SupportsCondition {
+ /// Parse a condition
+ ///
+ /// <https://drafts.csswg.org/css-conditional/#supports_condition>
+ pub fn parse<'i, 't>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i>> {
+ if input.try_parse(|i| i.expect_ident_matching("not")).is_ok() {
+ let inner = SupportsCondition::parse_in_parens(input)?;
+ return Ok(SupportsCondition::Not(Box::new(inner)));
+ }
+
+ let in_parens = SupportsCondition::parse_in_parens(input)?;
+
+ let location = input.current_source_location();
+ let (keyword, wrapper) = match input.next() {
+ // End of input
+ Err(..) => return Ok(in_parens),
+ Ok(&Token::Ident(ref ident)) => {
+ match_ignore_ascii_case! { &ident,
+ "and" => ("and", SupportsCondition::And as fn(_) -> _),
+ "or" => ("or", SupportsCondition::Or as fn(_) -> _),
+ _ => return Err(location.new_custom_error(SelectorParseErrorKind::UnexpectedIdent(ident.clone())))
+ }
+ },
+ Ok(t) => return Err(location.new_unexpected_token_error(t.clone())),
+ };
+
+ let mut conditions = Vec::with_capacity(2);
+ conditions.push(in_parens);
+ loop {
+ conditions.push(SupportsCondition::parse_in_parens(input)?);
+ if input
+ .try_parse(|input| input.expect_ident_matching(keyword))
+ .is_err()
+ {
+ // Did not find the expected keyword.
+ // If we found some other token, it will be rejected by
+ // `Parser::parse_entirely` somewhere up the stack.
+ return Ok(wrapper(conditions));
+ }
+ }
+ }
+
+ /// Parses a functional supports condition.
+ fn parse_functional<'i, 't>(
+ function: &str,
+ input: &mut Parser<'i, 't>,
+ ) -> Result<Self, ParseError<'i>> {
+ match_ignore_ascii_case! { function,
+ // Although this is an internal syntax, it is not necessary
+ // to check parsing context as far as we accept any
+ // unexpected token as future syntax, and evaluate it to
+ // false when not in chrome / ua sheet.
+ // See https://drafts.csswg.org/css-conditional-3/#general_enclosed
+ "-moz-bool-pref" => {
+ let name = {
+ let name = input.expect_string()?;
+ CString::new(name.as_bytes())
+ }.map_err(|_| input.new_custom_error(StyleParseErrorKind::UnspecifiedError))?;
+ Ok(SupportsCondition::MozBoolPref(name))
+ },
+ "selector" => {
+ let pos = input.position();
+ consume_any_value(input)?;
+ Ok(SupportsCondition::Selector(RawSelector(
+ input.slice_from(pos).to_owned()
+ )))
+ },
+ "font-format" if static_prefs::pref!("layout.css.font-tech.enabled") => {
+ let kw = FontFaceSourceFormatKeyword::parse(input)?;
+ Ok(SupportsCondition::FontFormat(kw))
+ },
+ "font-tech" if static_prefs::pref!("layout.css.font-tech.enabled") => {
+ let flag = FontFaceSourceTechFlags::parse_one(input)?;
+ Ok(SupportsCondition::FontTech(flag))
+ },
+ _ => {
+ Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError))
+ },
+ }
+ }
+
+ /// <https://drafts.csswg.org/css-conditional-3/#supports_condition_in_parens>
+ fn parse_in_parens<'i, 't>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i>> {
+ // Whitespace is normally taken care of in `Parser::next`,
+ // but we want to not include it in `pos` for the SupportsCondition::FutureSyntax cases.
+ while input.try_parse(Parser::expect_whitespace).is_ok() {}
+ let pos = input.position();
+ let location = input.current_source_location();
+ match *input.next()? {
+ Token::ParenthesisBlock => {
+ let nested = input
+ .try_parse(|input| input.parse_nested_block(parse_condition_or_declaration));
+ if nested.is_ok() {
+ return nested;
+ }
+ },
+ Token::Function(ref ident) => {
+ let ident = ident.clone();
+ let nested = input.try_parse(|input| {
+ input.parse_nested_block(|input| {
+ SupportsCondition::parse_functional(&ident, input)
+ })
+ });
+ if nested.is_ok() {
+ return nested;
+ }
+ },
+ ref t => return Err(location.new_unexpected_token_error(t.clone())),
+ }
+ input.parse_nested_block(consume_any_value)?;
+ Ok(SupportsCondition::FutureSyntax(
+ input.slice_from(pos).to_owned(),
+ ))
+ }
+
+ /// Evaluate a supports condition
+ pub fn eval(&self, cx: &ParserContext, namespaces: &Namespaces) -> bool {
+ match *self {
+ SupportsCondition::Not(ref cond) => !cond.eval(cx, namespaces),
+ SupportsCondition::Parenthesized(ref cond) => cond.eval(cx, namespaces),
+ SupportsCondition::And(ref vec) => vec.iter().all(|c| c.eval(cx, namespaces)),
+ SupportsCondition::Or(ref vec) => vec.iter().any(|c| c.eval(cx, namespaces)),
+ SupportsCondition::Declaration(ref decl) => decl.eval(cx),
+ SupportsCondition::MozBoolPref(ref name) => eval_moz_bool_pref(name, cx),
+ SupportsCondition::Selector(ref selector) => selector.eval(cx, namespaces),
+ SupportsCondition::FontFormat(ref format) => eval_font_format(format),
+ SupportsCondition::FontTech(ref tech) => eval_font_tech(tech),
+ SupportsCondition::FutureSyntax(_) => false,
+ }
+ }
+}
+
+#[cfg(feature = "gecko")]
+fn eval_moz_bool_pref(name: &CStr, cx: &ParserContext) -> bool {
+ use crate::gecko_bindings::bindings;
+ if !cx.in_ua_or_chrome_sheet() {
+ return false;
+ }
+ unsafe { bindings::Gecko_GetBoolPrefValue(name.as_ptr()) }
+}
+
+fn eval_font_format(kw: &FontFaceSourceFormatKeyword) -> bool {
+ use crate::gecko_bindings::bindings;
+ unsafe { bindings::Gecko_IsFontFormatSupported(*kw) }
+}
+
+fn eval_font_tech(flag: &FontFaceSourceTechFlags) -> bool {
+ use crate::gecko_bindings::bindings;
+ unsafe { bindings::Gecko_IsFontTechSupported(*flag) }
+}
+
+#[cfg(feature = "servo")]
+fn eval_moz_bool_pref(_: &CStr, _: &ParserContext) -> bool {
+ false
+}
+
+/// supports_condition | declaration
+/// <https://drafts.csswg.org/css-conditional/#dom-css-supports-conditiontext-conditiontext>
+pub fn parse_condition_or_declaration<'i, 't>(
+ input: &mut Parser<'i, 't>,
+) -> Result<SupportsCondition, ParseError<'i>> {
+ if let Ok(condition) = input.try_parse(SupportsCondition::parse) {
+ Ok(SupportsCondition::Parenthesized(Box::new(condition)))
+ } else {
+ Declaration::parse(input).map(SupportsCondition::Declaration)
+ }
+}
+
+impl ToCss for SupportsCondition {
+ fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
+ where
+ W: Write,
+ {
+ match *self {
+ SupportsCondition::Not(ref cond) => {
+ dest.write_str("not ")?;
+ cond.to_css(dest)
+ },
+ SupportsCondition::Parenthesized(ref cond) => {
+ dest.write_str("(")?;
+ cond.to_css(dest)?;
+ dest.write_str(")")
+ },
+ SupportsCondition::And(ref vec) => {
+ let mut first = true;
+ for cond in vec {
+ if !first {
+ dest.write_str(" and ")?;
+ }
+ first = false;
+ cond.to_css(dest)?;
+ }
+ Ok(())
+ },
+ SupportsCondition::Or(ref vec) => {
+ let mut first = true;
+ for cond in vec {
+ if !first {
+ dest.write_str(" or ")?;
+ }
+ first = false;
+ cond.to_css(dest)?;
+ }
+ Ok(())
+ },
+ SupportsCondition::Declaration(ref decl) => {
+ dest.write_str("(")?;
+ decl.to_css(dest)?;
+ dest.write_str(")")
+ },
+ SupportsCondition::Selector(ref selector) => {
+ dest.write_str("selector(")?;
+ selector.to_css(dest)?;
+ dest.write_str(")")
+ },
+ SupportsCondition::MozBoolPref(ref name) => {
+ dest.write_str("-moz-bool-pref(")?;
+ let name =
+ str::from_utf8(name.as_bytes()).expect("Should be parsed from valid UTF-8");
+ name.to_css(dest)?;
+ dest.write_str(")")
+ },
+ SupportsCondition::FontFormat(ref kw) => {
+ dest.write_str("font-format(")?;
+ kw.to_css(dest)?;
+ dest.write_str(")")
+ },
+ SupportsCondition::FontTech(ref flag) => {
+ dest.write_str("font-tech(")?;
+ flag.to_css(dest)?;
+ dest.write_str(")")
+ },
+ SupportsCondition::FutureSyntax(ref s) => dest.write_str(&s),
+ }
+ }
+}
+
+#[derive(Clone, Debug, ToShmem)]
+/// A possibly-invalid CSS selector.
+pub struct RawSelector(pub String);
+
+impl ToCss for RawSelector {
+ fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
+ where
+ W: Write,
+ {
+ dest.write_str(&self.0)
+ }
+}
+
+impl RawSelector {
+ /// Tries to evaluate a `selector()` function.
+ pub fn eval(&self, context: &ParserContext, namespaces: &Namespaces) -> bool {
+ let mut input = ParserInput::new(&self.0);
+ let mut input = Parser::new(&mut input);
+ input
+ .parse_entirely(|input| -> Result<(), CssParseError<()>> {
+ let parser = SelectorParser {
+ namespaces,
+ stylesheet_origin: context.stylesheet_origin,
+ url_data: context.url_data,
+ for_supports_rule: true,
+ };
+
+ Selector::<SelectorImpl>::parse(&parser, input)
+ .map_err(|_| input.new_custom_error(()))?;
+
+ Ok(())
+ })
+ .is_ok()
+ }
+}
+
+#[derive(Clone, Debug, ToShmem)]
+/// A possibly-invalid property declaration
+pub struct Declaration(pub String);
+
+impl ToCss for Declaration {
+ fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
+ where
+ W: Write,
+ {
+ dest.write_str(&self.0)
+ }
+}
+
+/// <https://drafts.csswg.org/css-syntax-3/#typedef-any-value>
+fn consume_any_value<'i, 't>(input: &mut Parser<'i, 't>) -> Result<(), ParseError<'i>> {
+ input.expect_no_error_token().map_err(|err| err.into())
+}
+
+impl Declaration {
+ /// Parse a declaration
+ pub fn parse<'i, 't>(input: &mut Parser<'i, 't>) -> Result<Declaration, ParseError<'i>> {
+ let pos = input.position();
+ input.expect_ident()?;
+ input.expect_colon()?;
+ consume_any_value(input)?;
+ Ok(Declaration(input.slice_from(pos).to_owned()))
+ }
+
+ /// Determine if a declaration parses
+ ///
+ /// <https://drafts.csswg.org/css-conditional-3/#support-definition>
+ pub fn eval(&self, context: &ParserContext) -> bool {
+ debug_assert_eq!(context.rule_type(), CssRuleType::Style);
+
+ let mut input = ParserInput::new(&self.0);
+ let mut input = Parser::new(&mut input);
+ input
+ .parse_entirely(|input| -> Result<(), CssParseError<()>> {
+ let prop = input.expect_ident_cloned().unwrap();
+ input.expect_colon().unwrap();
+
+ let id =
+ PropertyId::parse(&prop, context).map_err(|_| input.new_custom_error(()))?;
+
+ let mut declarations = SourcePropertyDeclaration::new();
+ input.parse_until_before(Delimiter::Bang, |input| {
+ PropertyDeclaration::parse_into(&mut declarations, id, &context, input)
+ .map_err(|_| input.new_custom_error(()))
+ })?;
+ let _ = input.try_parse(parse_important);
+ Ok(())
+ })
+ .is_ok()
+ }
+}
diff --git a/servo/components/style/stylesheets/viewport_rule.rs b/servo/components/style/stylesheets/viewport_rule.rs
new file mode 100644
index 0000000000..3d69f88e33
--- /dev/null
+++ b/servo/components/style/stylesheets/viewport_rule.rs
@@ -0,0 +1,791 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+//! The [`@viewport`][at] at-rule and [`meta`][meta] element.
+//!
+//! [at]: https://drafts.csswg.org/css-device-adapt/#atviewport-rule
+//! [meta]: https://drafts.csswg.org/css-device-adapt/#viewport-meta
+
+use crate::context::QuirksMode;
+use crate::error_reporting::ContextualParseError;
+use crate::media_queries::Device;
+use crate::parser::{Parse, ParserContext};
+use crate::properties::StyleBuilder;
+use crate::rule_cache::RuleCacheConditions;
+use crate::shared_lock::{SharedRwLockReadGuard, StylesheetGuards, ToCssWithGuard};
+use crate::str::CssStringWriter;
+use crate::stylesheets::cascading_at_rule::DescriptorDeclaration;
+use crate::stylesheets::container_rule::ContainerSizeQuery;
+use crate::stylesheets::{Origin, StylesheetInDocument};
+use crate::values::computed::{Context, ToComputedValue};
+use crate::values::generics::length::LengthPercentageOrAuto;
+use crate::values::generics::NonNegative;
+use crate::values::specified::{self, NoCalcLength};
+use crate::values::specified::{NonNegativeLengthPercentageOrAuto, ViewportPercentageLength};
+use app_units::Au;
+use cssparser::CowRcStr;
+use cssparser::{parse_important, AtRuleParser, DeclarationListParser, DeclarationParser, Parser};
+use euclid::Size2D;
+use selectors::parser::SelectorParseErrorKind;
+use std::borrow::Cow;
+use std::fmt::{self, Write};
+use std::iter::Enumerate;
+use std::str::Chars;
+use style_traits::viewport::{Orientation, UserZoom, ViewportConstraints, Zoom};
+use style_traits::{CssWriter, ParseError, PinchZoomFactor, StyleParseErrorKind, ToCss};
+
+/// Whether parsing and processing of `@viewport` rules is enabled.
+#[cfg(feature = "servo")]
+pub fn enabled() -> bool {
+ use servo_config::pref;
+ pref!(layout.viewport.enabled)
+}
+
+/// Whether parsing and processing of `@viewport` rules is enabled.
+#[cfg(not(feature = "servo"))]
+pub fn enabled() -> bool {
+ false // Gecko doesn't support @viewport.
+}
+
+macro_rules! declare_viewport_descriptor {
+ ( $( $variant_name: expr => $variant: ident($data: ident), )+ ) => {
+ declare_viewport_descriptor_inner!([] [ $( $variant_name => $variant($data), )+ ] 0);
+ };
+}
+
+macro_rules! declare_viewport_descriptor_inner {
+ (
+ [ $( $assigned_variant_name: expr =>
+ $assigned_variant: ident($assigned_data: ident) = $assigned_discriminant: expr, )* ]
+ [
+ $next_variant_name: expr => $next_variant: ident($next_data: ident),
+ $( $variant_name: expr => $variant: ident($data: ident), )*
+ ]
+ $next_discriminant: expr
+ ) => {
+ declare_viewport_descriptor_inner! {
+ [
+ $( $assigned_variant_name => $assigned_variant($assigned_data) = $assigned_discriminant, )*
+ $next_variant_name => $next_variant($next_data) = $next_discriminant,
+ ]
+ [ $( $variant_name => $variant($data), )* ]
+ $next_discriminant + 1
+ }
+ };
+
+ (
+ [ $( $assigned_variant_name: expr =>
+ $assigned_variant: ident($assigned_data: ident) = $assigned_discriminant: expr, )* ]
+ [ ]
+ $number_of_variants: expr
+ ) => {
+ #[derive(Clone, Debug, PartialEq, ToShmem)]
+ #[cfg_attr(feature = "servo", derive(MallocSizeOf))]
+ #[allow(missing_docs)]
+ pub enum ViewportDescriptor {
+ $(
+ $assigned_variant($assigned_data),
+ )+
+ }
+
+ const VIEWPORT_DESCRIPTOR_VARIANTS: usize = $number_of_variants;
+
+ impl ViewportDescriptor {
+ #[allow(missing_docs)]
+ pub fn discriminant_value(&self) -> usize {
+ match *self {
+ $(
+ ViewportDescriptor::$assigned_variant(..) => $assigned_discriminant,
+ )*
+ }
+ }
+ }
+
+ impl ToCss for ViewportDescriptor {
+ fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
+ where
+ W: Write,
+ {
+ match *self {
+ $(
+ ViewportDescriptor::$assigned_variant(ref val) => {
+ dest.write_str($assigned_variant_name)?;
+ dest.write_str(": ")?;
+ val.to_css(dest)?;
+ },
+ )*
+ }
+ dest.write_str(";")
+ }
+ }
+ };
+}
+
+declare_viewport_descriptor! {
+ "min-width" => MinWidth(ViewportLength),
+ "max-width" => MaxWidth(ViewportLength),
+
+ "min-height" => MinHeight(ViewportLength),
+ "max-height" => MaxHeight(ViewportLength),
+
+ "zoom" => Zoom(Zoom),
+ "min-zoom" => MinZoom(Zoom),
+ "max-zoom" => MaxZoom(Zoom),
+
+ "user-zoom" => UserZoom(UserZoom),
+ "orientation" => Orientation(Orientation),
+}
+
+trait FromMeta: Sized {
+ fn from_meta(value: &str) -> Option<Self>;
+}
+
+/// ViewportLength is a length | percentage | auto | extend-to-zoom
+/// See:
+/// * http://dev.w3.org/csswg/css-device-adapt/#min-max-width-desc
+/// * http://dev.w3.org/csswg/css-device-adapt/#extend-to-zoom
+#[allow(missing_docs)]
+#[cfg_attr(feature = "servo", derive(MallocSizeOf))]
+#[derive(Clone, Debug, PartialEq, ToCss, ToShmem)]
+pub enum ViewportLength {
+ Specified(NonNegativeLengthPercentageOrAuto),
+ ExtendToZoom,
+}
+
+impl FromMeta for ViewportLength {
+ fn from_meta(value: &str) -> Option<ViewportLength> {
+ macro_rules! specified {
+ ($value:expr) => {
+ ViewportLength::Specified(LengthPercentageOrAuto::LengthPercentage(NonNegative(
+ specified::LengthPercentage::Length($value),
+ )))
+ };
+ }
+
+ Some(match value {
+ v if v.eq_ignore_ascii_case("device-width") => specified!(
+ NoCalcLength::ViewportPercentage(ViewportPercentageLength::Vw(100.))
+ ),
+ v if v.eq_ignore_ascii_case("device-height") => specified!(
+ NoCalcLength::ViewportPercentage(ViewportPercentageLength::Vh(100.))
+ ),
+ _ => match value.parse::<f32>() {
+ Ok(n) if n >= 0. => specified!(NoCalcLength::from_px(n.max(1.).min(10000.))),
+ Ok(_) => return None,
+ Err(_) => specified!(NoCalcLength::from_px(1.)),
+ },
+ })
+ }
+}
+
+impl ViewportLength {
+ fn parse<'i, 't>(
+ context: &ParserContext,
+ input: &mut Parser<'i, 't>,
+ ) -> Result<Self, ParseError<'i>> {
+ // we explicitly do not accept 'extend-to-zoom', since it is a UA
+ // internal value for <META> viewport translation
+ NonNegativeLengthPercentageOrAuto::parse(context, input).map(ViewportLength::Specified)
+ }
+}
+
+impl FromMeta for Zoom {
+ fn from_meta(value: &str) -> Option<Zoom> {
+ Some(match value {
+ v if v.eq_ignore_ascii_case("yes") => Zoom::Number(1.),
+ v if v.eq_ignore_ascii_case("no") => Zoom::Number(0.1),
+ v if v.eq_ignore_ascii_case("device-width") => Zoom::Number(10.),
+ v if v.eq_ignore_ascii_case("device-height") => Zoom::Number(10.),
+ _ => match value.parse::<f32>() {
+ Ok(n) if n >= 0. => Zoom::Number(n.max(0.1).min(10.)),
+ Ok(_) => return None,
+ Err(_) => Zoom::Number(0.1),
+ },
+ })
+ }
+}
+
+impl FromMeta for UserZoom {
+ fn from_meta(value: &str) -> Option<UserZoom> {
+ Some(match value {
+ v if v.eq_ignore_ascii_case("yes") => UserZoom::Zoom,
+ v if v.eq_ignore_ascii_case("no") => UserZoom::Fixed,
+ v if v.eq_ignore_ascii_case("device-width") => UserZoom::Zoom,
+ v if v.eq_ignore_ascii_case("device-height") => UserZoom::Zoom,
+ _ => match value.parse::<f32>() {
+ Ok(n) if n >= 1. || n <= -1. => UserZoom::Zoom,
+ _ => UserZoom::Fixed,
+ },
+ })
+ }
+}
+
+struct ViewportRuleParser<'a, 'b: 'a> {
+ context: &'a ParserContext<'b>,
+}
+
+#[allow(missing_docs)]
+pub type ViewportDescriptorDeclaration = DescriptorDeclaration<ViewportDescriptor>;
+
+fn parse_shorthand<'i, 't>(
+ context: &ParserContext,
+ input: &mut Parser<'i, 't>,
+) -> Result<(ViewportLength, ViewportLength), ParseError<'i>> {
+ let min = ViewportLength::parse(context, input)?;
+ match input.try_parse(|i| ViewportLength::parse(context, i)) {
+ Err(_) => Ok((min.clone(), min)),
+ Ok(max) => Ok((min, max)),
+ }
+}
+
+impl<'a, 'b, 'i> AtRuleParser<'i> for ViewportRuleParser<'a, 'b> {
+ type Prelude = ();
+ type AtRule = Vec<ViewportDescriptorDeclaration>;
+ type Error = StyleParseErrorKind<'i>;
+}
+
+impl<'a, 'b, 'i> DeclarationParser<'i> for ViewportRuleParser<'a, 'b> {
+ type Declaration = Vec<ViewportDescriptorDeclaration>;
+ type Error = StyleParseErrorKind<'i>;
+
+ fn parse_value<'t>(
+ &mut self,
+ name: CowRcStr<'i>,
+ input: &mut Parser<'i, 't>,
+ ) -> Result<Vec<ViewportDescriptorDeclaration>, ParseError<'i>> {
+ macro_rules! declaration {
+ ($declaration:ident($parse:expr)) => {
+ declaration!($declaration {
+ value: $parse(input)?,
+ important: input.try_parse(parse_important).is_ok(),
+ })
+ };
+ ($declaration:ident { value: $value:expr, important: $important:expr, }) => {
+ ViewportDescriptorDeclaration::new(
+ self.context.stylesheet_origin,
+ ViewportDescriptor::$declaration($value),
+ $important,
+ )
+ };
+ }
+
+ macro_rules! ok {
+ ($declaration:ident($parse:expr)) => {
+ Ok(vec![declaration!($declaration($parse))])
+ };
+ (shorthand -> [$min:ident, $max:ident]) => {{
+ let shorthand = parse_shorthand(self.context, input)?;
+ let important = input.try_parse(parse_important).is_ok();
+
+ Ok(vec![
+ declaration!($min {
+ value: shorthand.0,
+ important: important,
+ }),
+ declaration!($max {
+ value: shorthand.1,
+ important: important,
+ }),
+ ])
+ }};
+ }
+
+ match_ignore_ascii_case! { &*name,
+ "min-width" => ok!(MinWidth(|i| ViewportLength::parse(self.context, i))),
+ "max-width" => ok!(MaxWidth(|i| ViewportLength::parse(self.context, i))),
+ "width" => ok!(shorthand -> [MinWidth, MaxWidth]),
+ "min-height" => ok!(MinHeight(|i| ViewportLength::parse(self.context, i))),
+ "max-height" => ok!(MaxHeight(|i| ViewportLength::parse(self.context, i))),
+ "height" => ok!(shorthand -> [MinHeight, MaxHeight]),
+ "zoom" => ok!(Zoom(Zoom::parse)),
+ "min-zoom" => ok!(MinZoom(Zoom::parse)),
+ "max-zoom" => ok!(MaxZoom(Zoom::parse)),
+ "user-zoom" => ok!(UserZoom(UserZoom::parse)),
+ "orientation" => ok!(Orientation(Orientation::parse)),
+ _ => Err(input.new_custom_error(SelectorParseErrorKind::UnexpectedIdent(name.clone()))),
+ }
+ }
+}
+
+/// A `@viewport` rule.
+#[derive(Clone, Debug, PartialEq, ToShmem)]
+#[cfg_attr(feature = "servo", derive(MallocSizeOf))]
+pub struct ViewportRule {
+ /// The declarations contained in this @viewport rule.
+ pub declarations: Vec<ViewportDescriptorDeclaration>,
+}
+
+/// Whitespace as defined by DEVICE-ADAPT § 9.2
+// TODO: should we just use whitespace as defined by HTML5?
+const WHITESPACE: &'static [char] = &['\t', '\n', '\r', ' '];
+
+/// Separators as defined by DEVICE-ADAPT § 9.2
+// need to use \x2c instead of ',' due to test-tidy
+const SEPARATOR: &'static [char] = &['\x2c', ';'];
+
+#[inline]
+fn is_whitespace_separator_or_equals(c: &char) -> bool {
+ WHITESPACE.contains(c) || SEPARATOR.contains(c) || *c == '='
+}
+
+impl ViewportRule {
+ /// Parse a single @viewport rule.
+ ///
+ /// TODO(emilio): This could use the `Parse` trait now.
+ pub fn parse<'i, 't>(
+ context: &ParserContext,
+ input: &mut Parser<'i, 't>,
+ ) -> Result<Self, ParseError<'i>> {
+ let parser = ViewportRuleParser { context };
+
+ let mut cascade = Cascade::new();
+ let mut parser = DeclarationListParser::new(input, parser);
+ while let Some(result) = parser.next() {
+ match result {
+ Ok(declarations) => {
+ for declarations in declarations {
+ cascade.add(Cow::Owned(declarations))
+ }
+ },
+ Err((error, slice)) => {
+ let location = error.location;
+ let error = ContextualParseError::UnsupportedViewportDescriptorDeclaration(
+ slice, error,
+ );
+ context.log_css_error(location, error);
+ },
+ }
+ }
+ Ok(ViewportRule {
+ declarations: cascade.finish(),
+ })
+ }
+}
+
+impl ViewportRule {
+ #[allow(missing_docs)]
+ pub fn from_meta(content: &str) -> Option<ViewportRule> {
+ let mut declarations = vec![None; VIEWPORT_DESCRIPTOR_VARIANTS];
+ macro_rules! push_descriptor {
+ ($descriptor:ident($value:expr)) => {{
+ let descriptor = ViewportDescriptor::$descriptor($value);
+ let discriminant = descriptor.discriminant_value();
+ declarations[discriminant] = Some(ViewportDescriptorDeclaration::new(
+ Origin::Author,
+ descriptor,
+ false,
+ ));
+ }};
+ }
+
+ let mut has_width = false;
+ let mut has_height = false;
+ let mut has_zoom = false;
+
+ let mut iter = content.chars().enumerate();
+
+ macro_rules! start_of_name {
+ ($iter:ident) => {
+ $iter
+ .by_ref()
+ .skip_while(|&(_, c)| is_whitespace_separator_or_equals(&c))
+ .next()
+ };
+ }
+
+ while let Some((start, _)) = start_of_name!(iter) {
+ let property = ViewportRule::parse_meta_property(content, &mut iter, start);
+
+ if let Some((name, value)) = property {
+ macro_rules! push {
+ ($descriptor:ident($translate:path)) => {
+ if let Some(value) = $translate(value) {
+ push_descriptor!($descriptor(value));
+ }
+ };
+ }
+
+ match name {
+ n if n.eq_ignore_ascii_case("width") => {
+ if let Some(value) = ViewportLength::from_meta(value) {
+ push_descriptor!(MinWidth(ViewportLength::ExtendToZoom));
+ push_descriptor!(MaxWidth(value));
+ has_width = true;
+ }
+ },
+ n if n.eq_ignore_ascii_case("height") => {
+ if let Some(value) = ViewportLength::from_meta(value) {
+ push_descriptor!(MinHeight(ViewportLength::ExtendToZoom));
+ push_descriptor!(MaxHeight(value));
+ has_height = true;
+ }
+ },
+ n if n.eq_ignore_ascii_case("initial-scale") => {
+ if let Some(value) = Zoom::from_meta(value) {
+ push_descriptor!(Zoom(value));
+ has_zoom = true;
+ }
+ },
+ n if n.eq_ignore_ascii_case("minimum-scale") => push!(MinZoom(Zoom::from_meta)),
+ n if n.eq_ignore_ascii_case("maximum-scale") => push!(MaxZoom(Zoom::from_meta)),
+ n if n.eq_ignore_ascii_case("user-scalable") => {
+ push!(UserZoom(UserZoom::from_meta))
+ },
+ _ => {},
+ }
+ }
+ }
+
+ // DEVICE-ADAPT § 9.4 - The 'width' and 'height' properties
+ // http://dev.w3.org/csswg/css-device-adapt/#width-and-height-properties
+ if !has_width && has_zoom {
+ if has_height {
+ push_descriptor!(MinWidth(ViewportLength::Specified(
+ LengthPercentageOrAuto::Auto
+ )));
+ push_descriptor!(MaxWidth(ViewportLength::Specified(
+ LengthPercentageOrAuto::Auto
+ )));
+ } else {
+ push_descriptor!(MinWidth(ViewportLength::ExtendToZoom));
+ push_descriptor!(MaxWidth(ViewportLength::ExtendToZoom));
+ }
+ }
+
+ let declarations: Vec<_> = declarations.into_iter().filter_map(|entry| entry).collect();
+ if !declarations.is_empty() {
+ Some(ViewportRule {
+ declarations: declarations,
+ })
+ } else {
+ None
+ }
+ }
+
+ fn parse_meta_property<'a>(
+ content: &'a str,
+ iter: &mut Enumerate<Chars<'a>>,
+ start: usize,
+ ) -> Option<(&'a str, &'a str)> {
+ fn end_of_token(iter: &mut Enumerate<Chars>) -> Option<(usize, char)> {
+ iter.by_ref()
+ .skip_while(|&(_, c)| !is_whitespace_separator_or_equals(&c))
+ .next()
+ }
+
+ fn skip_whitespace(iter: &mut Enumerate<Chars>) -> Option<(usize, char)> {
+ iter.by_ref()
+ .skip_while(|&(_, c)| WHITESPACE.contains(&c))
+ .next()
+ }
+
+ // <name> <whitespace>* '='
+ let end = match end_of_token(iter) {
+ Some((end, c)) if WHITESPACE.contains(&c) => match skip_whitespace(iter) {
+ Some((_, c)) if c == '=' => end,
+ _ => return None,
+ },
+ Some((end, c)) if c == '=' => end,
+ _ => return None,
+ };
+ let name = &content[start..end];
+
+ // <whitespace>* <value>
+ let start = match skip_whitespace(iter) {
+ Some((start, c)) if !SEPARATOR.contains(&c) => start,
+ _ => return None,
+ };
+ let value = match end_of_token(iter) {
+ Some((end, _)) => &content[start..end],
+ _ => &content[start..],
+ };
+
+ Some((name, value))
+ }
+}
+
+impl ToCssWithGuard for ViewportRule {
+ // Serialization of ViewportRule is not specced.
+ fn to_css(&self, _guard: &SharedRwLockReadGuard, dest: &mut CssStringWriter) -> fmt::Result {
+ dest.write_str("@viewport { ")?;
+ let mut iter = self.declarations.iter();
+ iter.next().unwrap().to_css(&mut CssWriter::new(dest))?;
+ for declaration in iter {
+ dest.write_str(" ")?;
+ declaration.to_css(&mut CssWriter::new(dest))?;
+ }
+ dest.write_str(" }")
+ }
+}
+
+#[allow(missing_docs)]
+pub struct Cascade {
+ declarations: Vec<Option<(usize, ViewportDescriptorDeclaration)>>,
+ count_so_far: usize,
+}
+
+#[allow(missing_docs)]
+impl Cascade {
+ pub fn new() -> Self {
+ Cascade {
+ declarations: vec![None; VIEWPORT_DESCRIPTOR_VARIANTS],
+ count_so_far: 0,
+ }
+ }
+
+ pub fn from_stylesheets<'a, I, S>(
+ stylesheets: I,
+ guards: &StylesheetGuards,
+ device: &Device,
+ ) -> Self
+ where
+ I: Iterator<Item = (&'a S, Origin)>,
+ S: StylesheetInDocument + 'static,
+ {
+ let mut cascade = Self::new();
+ for (stylesheet, origin) in stylesheets {
+ stylesheet.effective_viewport_rules(device, guards.for_origin(origin), |rule| {
+ for declaration in &rule.declarations {
+ cascade.add(Cow::Borrowed(declaration))
+ }
+ })
+ }
+ cascade
+ }
+
+ pub fn add(&mut self, declaration: Cow<ViewportDescriptorDeclaration>) {
+ let descriptor = declaration.descriptor.discriminant_value();
+
+ match self.declarations[descriptor] {
+ Some((ref mut order_of_appearance, ref mut entry_declaration)) => {
+ if declaration.higher_or_equal_precendence(entry_declaration) {
+ *entry_declaration = declaration.into_owned();
+ *order_of_appearance = self.count_so_far;
+ }
+ },
+ ref mut entry @ None => {
+ *entry = Some((self.count_so_far, declaration.into_owned()));
+ },
+ }
+ self.count_so_far += 1;
+ }
+
+ pub fn finish(mut self) -> Vec<ViewportDescriptorDeclaration> {
+ // sort the descriptors by order of appearance
+ self.declarations
+ .sort_by_key(|entry| entry.as_ref().map(|&(index, _)| index));
+ self.declarations
+ .into_iter()
+ .filter_map(|entry| entry.map(|(_, decl)| decl))
+ .collect()
+ }
+}
+
+/// Just a helper trait to be able to implement methods on ViewportConstraints.
+pub trait MaybeNew {
+ /// Create a ViewportConstraints from a viewport size and a `@viewport`
+ /// rule.
+ fn maybe_new(
+ device: &Device,
+ rule: &ViewportRule,
+ quirks_mode: QuirksMode,
+ ) -> Option<ViewportConstraints>;
+}
+
+impl MaybeNew for ViewportConstraints {
+ fn maybe_new(
+ device: &Device,
+ rule: &ViewportRule,
+ quirks_mode: QuirksMode,
+ ) -> Option<ViewportConstraints> {
+ use std::cmp;
+
+ if rule.declarations.is_empty() {
+ return None;
+ }
+
+ let mut min_width = None;
+ let mut max_width = None;
+
+ let mut min_height = None;
+ let mut max_height = None;
+
+ let mut initial_zoom = None;
+ let mut min_zoom = None;
+ let mut max_zoom = None;
+
+ let mut user_zoom = UserZoom::Zoom;
+ let mut orientation = Orientation::Auto;
+
+ // collapse the list of declarations into descriptor values
+ for declaration in &rule.declarations {
+ match declaration.descriptor {
+ ViewportDescriptor::MinWidth(ref value) => min_width = Some(value),
+ ViewportDescriptor::MaxWidth(ref value) => max_width = Some(value),
+
+ ViewportDescriptor::MinHeight(ref value) => min_height = Some(value),
+ ViewportDescriptor::MaxHeight(ref value) => max_height = Some(value),
+
+ ViewportDescriptor::Zoom(value) => initial_zoom = value.to_f32(),
+ ViewportDescriptor::MinZoom(value) => min_zoom = value.to_f32(),
+ ViewportDescriptor::MaxZoom(value) => max_zoom = value.to_f32(),
+
+ ViewportDescriptor::UserZoom(value) => user_zoom = value,
+ ViewportDescriptor::Orientation(value) => orientation = value,
+ }
+ }
+
+ // TODO: return `None` if all descriptors are either absent or initial value
+
+ macro_rules! choose {
+ ($op:ident, $opta:expr, $optb:expr) => {
+ match ($opta, $optb) {
+ (None, None) => None,
+ (a, None) => a,
+ (None, b) => b,
+ (Some(a), Some(b)) => Some(a.$op(b)),
+ }
+ };
+ }
+ macro_rules! min {
+ ($opta:expr, $optb:expr) => {
+ choose!(min, $opta, $optb)
+ };
+ }
+ macro_rules! max {
+ ($opta:expr, $optb:expr) => {
+ choose!(max, $opta, $optb)
+ };
+ }
+
+ // DEVICE-ADAPT § 6.2.1 Resolve min-zoom and max-zoom values
+ if min_zoom.is_some() && max_zoom.is_some() {
+ max_zoom = Some(min_zoom.unwrap().max(max_zoom.unwrap()))
+ }
+
+ // DEVICE-ADAPT § 6.2.2 Constrain zoom value to the [min-zoom, max-zoom] range
+ if initial_zoom.is_some() {
+ initial_zoom = max!(min_zoom, min!(max_zoom, initial_zoom));
+ }
+
+ // DEVICE-ADAPT § 6.2.3 Resolve non-auto lengths to pixel lengths
+ let initial_viewport = device.au_viewport_size();
+
+ let mut conditions = RuleCacheConditions::default();
+ let context = Context::new(
+ // Note: DEVICE-ADAPT § 5. states that relative length values are
+ // resolved against initial values
+ StyleBuilder::for_inheritance(device, None, None),
+ quirks_mode,
+ &mut conditions,
+ ContainerSizeQuery::none(),
+ );
+
+ // DEVICE-ADAPT § 9.3 Resolving 'extend-to-zoom'
+ let extend_width;
+ let extend_height;
+ if let Some(extend_zoom) = max!(initial_zoom, max_zoom) {
+ let scale_factor = 1. / extend_zoom;
+ extend_width = Some(initial_viewport.width.scale_by(scale_factor));
+ extend_height = Some(initial_viewport.height.scale_by(scale_factor));
+ } else {
+ extend_width = None;
+ extend_height = None;
+ }
+
+ macro_rules! to_pixel_length {
+ ($value:ident, $dimension:ident, $extend_to:ident => $auto_extend_to:expr) => {
+ if let Some($value) = $value {
+ match *$value {
+ ViewportLength::Specified(ref length) => match *length {
+ LengthPercentageOrAuto::Auto => None,
+ LengthPercentageOrAuto::LengthPercentage(ref lop) => Some(
+ lop.to_computed_value(&context)
+ .to_used_value(initial_viewport.$dimension),
+ ),
+ },
+ ViewportLength::ExtendToZoom => {
+ // $extend_to will be 'None' if 'extend-to-zoom' is 'auto'
+ match ($extend_to, $auto_extend_to) {
+ (None, None) => None,
+ (a, None) => a,
+ (None, b) => b,
+ (a, b) => cmp::max(a, b),
+ }
+ },
+ }
+ } else {
+ None
+ }
+ };
+ }
+
+ // DEVICE-ADAPT § 9.3 states that max-descriptors need to be resolved
+ // before min-descriptors.
+ // http://dev.w3.org/csswg/css-device-adapt/#resolve-extend-to-zoom
+ let max_width = to_pixel_length!(max_width, width, extend_width => None);
+ let max_height = to_pixel_length!(max_height, height, extend_height => None);
+
+ let min_width = to_pixel_length!(min_width, width, extend_width => max_width);
+ let min_height = to_pixel_length!(min_height, height, extend_height => max_height);
+
+ // DEVICE-ADAPT § 6.2.4 Resolve initial width and height from min/max descriptors
+ macro_rules! resolve {
+ ($min:ident, $max:ident, $initial:expr) => {
+ if $min.is_some() || $max.is_some() {
+ let max = match $max {
+ Some(max) => cmp::min(max, $initial),
+ None => $initial,
+ };
+
+ Some(match $min {
+ Some(min) => cmp::max(min, max),
+ None => max,
+ })
+ } else {
+ None
+ }
+ };
+ }
+
+ let width = resolve!(min_width, max_width, initial_viewport.width);
+ let height = resolve!(min_height, max_height, initial_viewport.height);
+
+ // DEVICE-ADAPT § 6.2.5 Resolve width value
+ let width = if width.is_none() && height.is_none() {
+ Some(initial_viewport.width)
+ } else {
+ width
+ };
+
+ let width = width.unwrap_or_else(|| match initial_viewport.height {
+ Au(0) => initial_viewport.width,
+ initial_height => {
+ let ratio = initial_viewport.width.to_f32_px() / initial_height.to_f32_px();
+ Au::from_f32_px(height.unwrap().to_f32_px() * ratio)
+ },
+ });
+
+ // DEVICE-ADAPT § 6.2.6 Resolve height value
+ let height = height.unwrap_or_else(|| match initial_viewport.width {
+ Au(0) => initial_viewport.height,
+ initial_width => {
+ let ratio = initial_viewport.height.to_f32_px() / initial_width.to_f32_px();
+ Au::from_f32_px(width.to_f32_px() * ratio)
+ },
+ });
+
+ Some(ViewportConstraints {
+ size: Size2D::new(width.to_f32_px(), height.to_f32_px()),
+
+ // TODO: compute a zoom factor for 'auto' as suggested by DEVICE-ADAPT § 10.
+ initial_zoom: PinchZoomFactor::new(initial_zoom.unwrap_or(1.)),
+ min_zoom: min_zoom.map(PinchZoomFactor::new),
+ max_zoom: max_zoom.map(PinchZoomFactor::new),
+
+ user_zoom: user_zoom,
+ orientation: orientation,
+ })
+ }
+}