summaryrefslogtreecommitdiffstats
path: root/servo/components/style/properties/declaration_block.rs
diff options
context:
space:
mode:
Diffstat (limited to 'servo/components/style/properties/declaration_block.rs')
-rw-r--r--servo/components/style/properties/declaration_block.rs1642
1 files changed, 1642 insertions, 0 deletions
diff --git a/servo/components/style/properties/declaration_block.rs b/servo/components/style/properties/declaration_block.rs
new file mode 100644
index 0000000000..81d7148e62
--- /dev/null
+++ b/servo/components/style/properties/declaration_block.rs
@@ -0,0 +1,1642 @@
+/* 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 property declaration block.
+
+#![deny(missing_docs)]
+
+use super::{
+ property_counts, AllShorthand, ComputedValues, LogicalGroupSet, LonghandIdSet,
+ LonghandIdSetIterator, NonCustomPropertyIdSet, PropertyDeclaration, PropertyDeclarationId,
+ PropertyId, ShorthandId, SourcePropertyDeclaration, SourcePropertyDeclarationDrain,
+ SubpropertiesVec,
+};
+use crate::context::QuirksMode;
+use crate::custom_properties;
+use crate::error_reporting::{ContextualParseError, ParseErrorReporter};
+use crate::parser::ParserContext;
+use crate::properties::{
+ animated_properties::{AnimationValue, AnimationValueMap},
+ StyleBuilder,
+};
+use crate::rule_cache::RuleCacheConditions;
+use crate::selector_map::PrecomputedHashSet;
+use crate::selector_parser::SelectorImpl;
+use crate::shared_lock::Locked;
+use crate::str::{CssString, CssStringWriter};
+use crate::stylesheets::container_rule::ContainerSizeQuery;
+use crate::stylesheets::{CssRuleType, Origin, UrlExtraData};
+use crate::stylist::Stylist;
+use crate::values::computed::Context;
+use cssparser::{
+ parse_important, AtRuleParser, CowRcStr, DeclarationParser, Delimiter, ParseErrorKind, Parser,
+ ParserInput, QualifiedRuleParser, RuleBodyItemParser, RuleBodyParser,
+};
+use itertools::Itertools;
+use selectors::SelectorList;
+use servo_arc::Arc;
+use smallbitvec::{self, SmallBitVec};
+use smallvec::SmallVec;
+use std::fmt::{self, Write};
+use std::iter::{DoubleEndedIterator, Zip};
+use std::slice::Iter;
+use style_traits::{CssWriter, ParseError, ParsingMode, StyleParseErrorKind, ToCss};
+use thin_vec::ThinVec;
+
+/// A set of property declarations including animations and transitions.
+#[derive(Default)]
+pub struct AnimationDeclarations {
+ /// Declarations for animations.
+ pub animations: Option<Arc<Locked<PropertyDeclarationBlock>>>,
+ /// Declarations for transitions.
+ pub transitions: Option<Arc<Locked<PropertyDeclarationBlock>>>,
+}
+
+impl AnimationDeclarations {
+ /// Whether or not this `AnimationDeclarations` is empty.
+ pub fn is_empty(&self) -> bool {
+ self.animations.is_none() && self.transitions.is_none()
+ }
+}
+
+/// An enum describes how a declaration should update
+/// the declaration block.
+#[derive(Clone, Copy, Debug, Eq, PartialEq)]
+enum DeclarationUpdate {
+ /// The given declaration doesn't update anything.
+ None,
+ /// The given declaration is new, and should be append directly.
+ Append,
+ /// The given declaration can be updated in-place at the given position.
+ UpdateInPlace { pos: usize },
+ /// The given declaration cannot be updated in-place, and an existing
+ /// one needs to be removed at the given position.
+ AppendAndRemove { pos: usize },
+}
+
+/// A struct describes how a declaration block should be updated by
+/// a `SourcePropertyDeclaration`.
+#[derive(Default)]
+pub struct SourcePropertyDeclarationUpdate {
+ updates: SubpropertiesVec<DeclarationUpdate>,
+ new_count: usize,
+ any_removal: bool,
+}
+
+/// A declaration [importance][importance].
+///
+/// [importance]: https://drafts.csswg.org/css-cascade/#importance
+#[derive(Clone, Copy, Debug, Eq, Hash, MallocSizeOf, PartialEq)]
+pub enum Importance {
+ /// Indicates a declaration without `!important`.
+ Normal,
+
+ /// Indicates a declaration with `!important`.
+ Important,
+}
+
+impl Default for Importance {
+ fn default() -> Self {
+ Self::Normal
+ }
+}
+
+impl Importance {
+ /// Return whether this is an important declaration.
+ pub fn important(self) -> bool {
+ match self {
+ Self::Normal => false,
+ Self::Important => true,
+ }
+ }
+}
+
+/// A set of properties.
+#[derive(Clone, Debug, ToShmem, Default, MallocSizeOf)]
+pub struct PropertyDeclarationIdSet {
+ longhands: LonghandIdSet,
+ custom: PrecomputedHashSet<custom_properties::Name>,
+}
+
+impl PropertyDeclarationIdSet {
+ /// Add the given property to the set.
+ pub fn insert(&mut self, id: PropertyDeclarationId) -> bool {
+ match id {
+ PropertyDeclarationId::Longhand(id) => {
+ if self.longhands.contains(id) {
+ return false;
+ }
+ self.longhands.insert(id);
+ return true;
+ },
+ PropertyDeclarationId::Custom(name) => self.custom.insert((*name).clone()),
+ }
+ }
+
+ /// Return whether the given property is in the set.
+ pub fn contains(&self, id: PropertyDeclarationId) -> bool {
+ match id {
+ PropertyDeclarationId::Longhand(id) => self.longhands.contains(id),
+ PropertyDeclarationId::Custom(name) => self.custom.contains(name),
+ }
+ }
+
+ /// Remove the given property from the set.
+ pub fn remove(&mut self, id: PropertyDeclarationId) {
+ match id {
+ PropertyDeclarationId::Longhand(id) => self.longhands.remove(id),
+ PropertyDeclarationId::Custom(name) => {
+ self.custom.remove(name);
+ },
+ }
+ }
+
+ /// Remove all properties from the set.
+ pub fn clear(&mut self) {
+ self.longhands.clear();
+ self.custom.clear();
+ }
+
+ /// Returns whether the set is empty.
+ #[inline]
+ pub fn is_empty(&self) -> bool {
+ self.longhands.is_empty() && self.custom.is_empty()
+ }
+ /// Returns whether this set contains any reset longhand.
+ #[inline]
+ pub fn contains_any_reset(&self) -> bool {
+ self.longhands.contains_any_reset()
+ }
+
+ /// Returns whether this set contains all longhands in the specified set.
+ #[inline]
+ pub fn contains_all_longhands(&self, longhands: &LonghandIdSet) -> bool {
+ self.longhands.contains_all(longhands)
+ }
+
+ /// Returns whether this set contains all properties in the specified set.
+ #[inline]
+ pub fn contains_all(&self, properties: &PropertyDeclarationIdSet) -> bool {
+ if !self.longhands.contains_all(&properties.longhands) {
+ return false;
+ }
+ if properties.custom.len() > self.custom.len() {
+ return false;
+ }
+ properties
+ .custom
+ .iter()
+ .all(|item| self.custom.contains(item))
+ }
+
+ /// Iterate over the current property declaration id set.
+ pub fn iter(&self) -> PropertyDeclarationIdSetIterator {
+ PropertyDeclarationIdSetIterator {
+ longhands: self.longhands.iter(),
+ custom: self.custom.iter(),
+ }
+ }
+}
+
+/// An iterator over a set of longhand ids.
+pub struct PropertyDeclarationIdSetIterator<'a> {
+ longhands: LonghandIdSetIterator<'a>,
+ custom: std::collections::hash_set::Iter<'a, custom_properties::Name>,
+}
+
+impl<'a> Iterator for PropertyDeclarationIdSetIterator<'a> {
+ type Item = PropertyDeclarationId<'a>;
+
+ fn next(&mut self) -> Option<Self::Item> {
+ // LonghandIdSetIterator's implementation always returns None
+ // after it did it once, so the code below will then continue
+ // to iterate over the custom properties.
+ match self.longhands.next() {
+ Some(id) => Some(PropertyDeclarationId::Longhand(id)),
+ None => match self.custom.next() {
+ Some(a) => Some(PropertyDeclarationId::Custom(a)),
+ None => None,
+ },
+ }
+ }
+}
+
+/// Overridden declarations are skipped.
+#[cfg_attr(feature = "gecko", derive(MallocSizeOf))]
+#[derive(Clone, ToShmem, Default)]
+pub struct PropertyDeclarationBlock {
+ /// The group of declarations, along with their importance.
+ ///
+ /// Only deduplicated declarations appear here.
+ declarations: ThinVec<PropertyDeclaration>,
+
+ /// The "important" flag for each declaration in `declarations`.
+ declarations_importance: SmallBitVec,
+
+ /// The set of properties that are present in the block.
+ property_ids: PropertyDeclarationIdSet,
+}
+
+/// Iterator over `(PropertyDeclaration, Importance)` pairs.
+pub struct DeclarationImportanceIterator<'a> {
+ iter: Zip<Iter<'a, PropertyDeclaration>, smallbitvec::Iter<'a>>,
+}
+
+impl<'a> Default for DeclarationImportanceIterator<'a> {
+ fn default() -> Self {
+ Self {
+ iter: [].iter().zip(smallbitvec::Iter::default()),
+ }
+ }
+}
+
+impl<'a> DeclarationImportanceIterator<'a> {
+ /// Constructor.
+ fn new(declarations: &'a [PropertyDeclaration], important: &'a SmallBitVec) -> Self {
+ DeclarationImportanceIterator {
+ iter: declarations.iter().zip(important.iter()),
+ }
+ }
+}
+
+impl<'a> Iterator for DeclarationImportanceIterator<'a> {
+ type Item = (&'a PropertyDeclaration, Importance);
+
+ #[inline]
+ fn next(&mut self) -> Option<Self::Item> {
+ self.iter.next().map(|(decl, important)| {
+ (
+ decl,
+ if important {
+ Importance::Important
+ } else {
+ Importance::Normal
+ },
+ )
+ })
+ }
+
+ #[inline]
+ fn size_hint(&self) -> (usize, Option<usize>) {
+ self.iter.size_hint()
+ }
+}
+
+impl<'a> DoubleEndedIterator for DeclarationImportanceIterator<'a> {
+ #[inline(always)]
+ fn next_back(&mut self) -> Option<Self::Item> {
+ self.iter.next_back().map(|(decl, important)| {
+ (
+ decl,
+ if important {
+ Importance::Important
+ } else {
+ Importance::Normal
+ },
+ )
+ })
+ }
+}
+
+/// Iterator for AnimationValue to be generated from PropertyDeclarationBlock.
+pub struct AnimationValueIterator<'a, 'cx, 'cx_a: 'cx> {
+ iter: DeclarationImportanceIterator<'a>,
+ context: &'cx mut Context<'cx_a>,
+ default_values: &'a ComputedValues,
+}
+
+impl<'a, 'cx, 'cx_a: 'cx> AnimationValueIterator<'a, 'cx, 'cx_a> {
+ fn new(
+ declarations: &'a PropertyDeclarationBlock,
+ context: &'cx mut Context<'cx_a>,
+ default_values: &'a ComputedValues,
+ ) -> AnimationValueIterator<'a, 'cx, 'cx_a> {
+ AnimationValueIterator {
+ iter: declarations.declaration_importance_iter(),
+ context,
+ default_values,
+ }
+ }
+}
+
+impl<'a, 'cx, 'cx_a: 'cx> Iterator for AnimationValueIterator<'a, 'cx, 'cx_a> {
+ type Item = AnimationValue;
+ #[inline]
+ fn next(&mut self) -> Option<Self::Item> {
+ loop {
+ let (decl, importance) = self.iter.next()?;
+
+ if importance.important() {
+ continue;
+ }
+
+ let animation =
+ AnimationValue::from_declaration(decl, &mut self.context, self.default_values);
+
+ if let Some(anim) = animation {
+ return Some(anim);
+ }
+ }
+ }
+}
+
+impl fmt::Debug for PropertyDeclarationBlock {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ self.declarations.fmt(f)
+ }
+}
+
+impl PropertyDeclarationBlock {
+ /// Returns the number of declarations in the block.
+ #[inline]
+ pub fn len(&self) -> usize {
+ self.declarations.len()
+ }
+
+ /// Returns whether the block is empty.
+ #[inline]
+ pub fn is_empty(&self) -> bool {
+ self.declarations.is_empty()
+ }
+
+ /// Create an empty block
+ #[inline]
+ pub fn new() -> Self {
+ PropertyDeclarationBlock {
+ declarations: ThinVec::new(),
+ declarations_importance: SmallBitVec::new(),
+ property_ids: PropertyDeclarationIdSet::default(),
+ }
+ }
+
+ /// Create a block with a single declaration
+ pub fn with_one(declaration: PropertyDeclaration, importance: Importance) -> Self {
+ let mut property_ids = PropertyDeclarationIdSet::default();
+ property_ids.insert(declaration.id());
+ let mut declarations = ThinVec::with_capacity(1);
+ declarations.push(declaration);
+ PropertyDeclarationBlock {
+ declarations,
+ declarations_importance: SmallBitVec::from_elem(1, importance.important()),
+ property_ids,
+ }
+ }
+
+ /// The declarations in this block
+ #[inline]
+ pub fn declarations(&self) -> &[PropertyDeclaration] {
+ &self.declarations
+ }
+
+ /// The `important` flags for declarations in this block
+ #[inline]
+ pub fn declarations_importance(&self) -> &SmallBitVec {
+ &self.declarations_importance
+ }
+
+ /// Iterate over `(PropertyDeclaration, Importance)` pairs
+ #[inline]
+ pub fn declaration_importance_iter(&self) -> DeclarationImportanceIterator {
+ DeclarationImportanceIterator::new(&self.declarations, &self.declarations_importance)
+ }
+
+ /// Iterate over `PropertyDeclaration` for Importance::Normal
+ #[inline]
+ pub fn normal_declaration_iter<'a>(
+ &'a self,
+ ) -> impl DoubleEndedIterator<Item = &'a PropertyDeclaration> {
+ self.declaration_importance_iter()
+ .filter(|(_, importance)| !importance.important())
+ .map(|(declaration, _)| declaration)
+ }
+
+ /// Return an iterator of (AnimatableLonghand, AnimationValue).
+ #[inline]
+ pub fn to_animation_value_iter<'a, 'cx, 'cx_a: 'cx>(
+ &'a self,
+ context: &'cx mut Context<'cx_a>,
+ default_values: &'a ComputedValues,
+ ) -> AnimationValueIterator<'a, 'cx, 'cx_a> {
+ AnimationValueIterator::new(self, context, default_values)
+ }
+
+ /// Returns whether this block contains any declaration with `!important`.
+ ///
+ /// This is based on the `declarations_importance` bit-vector,
+ /// which should be maintained whenever `declarations` is changed.
+ #[inline]
+ pub fn any_important(&self) -> bool {
+ !self.declarations_importance.all_false()
+ }
+
+ /// Returns whether this block contains any declaration without `!important`.
+ ///
+ /// This is based on the `declarations_importance` bit-vector,
+ /// which should be maintained whenever `declarations` is changed.
+ #[inline]
+ pub fn any_normal(&self) -> bool {
+ !self.declarations_importance.all_true()
+ }
+
+ /// Returns a `PropertyDeclarationIdSet` representing the properties that are changed in
+ /// this block.
+ #[inline]
+ pub fn property_ids(&self) -> &PropertyDeclarationIdSet {
+ &self.property_ids
+ }
+
+ /// Returns whether this block contains a declaration of a given property id.
+ #[inline]
+ pub fn contains(&self, id: PropertyDeclarationId) -> bool {
+ self.property_ids.contains(id)
+ }
+
+ /// Returns whether this block contains any reset longhand.
+ #[inline]
+ pub fn contains_any_reset(&self) -> bool {
+ self.property_ids.contains_any_reset()
+ }
+
+ /// Get a declaration for a given property.
+ ///
+ /// NOTE: This is linear time in the case of custom properties or in the
+ /// case the longhand is actually in the declaration block.
+ #[inline]
+ pub fn get(
+ &self,
+ property: PropertyDeclarationId,
+ ) -> Option<(&PropertyDeclaration, Importance)> {
+ if !self.contains(property) {
+ return None;
+ }
+ self.declaration_importance_iter()
+ .find(|(declaration, _)| declaration.id() == property)
+ }
+
+ /// Tries to serialize a given shorthand from the declarations in this
+ /// block.
+ pub fn shorthand_to_css(
+ &self,
+ shorthand: ShorthandId,
+ dest: &mut CssStringWriter,
+ ) -> fmt::Result {
+ // Step 1.2.1 of
+ // https://drafts.csswg.org/cssom/#dom-cssstyledeclaration-getpropertyvalue
+ let mut list = SmallVec::<[&_; 10]>::new();
+ let mut important_count = 0;
+
+ // Step 1.2.2
+ for longhand in shorthand.longhands() {
+ // Step 1.2.2.1
+ let declaration = self.get(PropertyDeclarationId::Longhand(longhand));
+
+ // Step 1.2.2.2 & 1.2.2.3
+ match declaration {
+ Some((declaration, importance)) => {
+ list.push(declaration);
+ if importance.important() {
+ important_count += 1;
+ }
+ },
+ None => return Ok(()),
+ }
+ }
+
+ // If there is one or more longhand with important, and one or more
+ // without important, we don't serialize it as a shorthand.
+ if important_count > 0 && important_count != list.len() {
+ return Ok(());
+ }
+
+ // Step 1.2.3
+ // We don't print !important when serializing individual properties,
+ // so we treat this as a normal-importance property
+ match shorthand.get_shorthand_appendable_value(&list) {
+ Some(appendable_value) => append_declaration_value(dest, appendable_value),
+ None => return Ok(()),
+ }
+ }
+
+ /// Find the value of the given property in this block and serialize it
+ ///
+ /// <https://drafts.csswg.org/cssom/#dom-cssstyledeclaration-getpropertyvalue>
+ pub fn property_value_to_css(
+ &self,
+ property: &PropertyId,
+ dest: &mut CssStringWriter,
+ ) -> fmt::Result {
+ // Step 1.1: done when parsing a string to PropertyId
+
+ // Step 1.2
+ let longhand_or_custom = match property.as_shorthand() {
+ Ok(shorthand) => return self.shorthand_to_css(shorthand, dest),
+ Err(longhand_or_custom) => longhand_or_custom,
+ };
+
+ if let Some((value, _importance)) = self.get(longhand_or_custom) {
+ // Step 2
+ value.to_css(dest)
+ } else {
+ // Step 3
+ Ok(())
+ }
+ }
+
+ /// <https://drafts.csswg.org/cssom/#dom-cssstyledeclaration-getpropertypriority>
+ pub fn property_priority(&self, property: &PropertyId) -> Importance {
+ // Step 1: done when parsing a string to PropertyId
+
+ // Step 2
+ match property.as_shorthand() {
+ Ok(shorthand) => {
+ // Step 2.1 & 2.2 & 2.3
+ if shorthand.longhands().all(|l| {
+ self.get(PropertyDeclarationId::Longhand(l))
+ .map_or(false, |(_, importance)| importance.important())
+ }) {
+ Importance::Important
+ } else {
+ Importance::Normal
+ }
+ },
+ Err(longhand_or_custom) => {
+ // Step 3
+ self.get(longhand_or_custom)
+ .map_or(Importance::Normal, |(_, importance)| importance)
+ },
+ }
+ }
+
+ /// Adds or overrides the declaration for a given property in this block.
+ ///
+ /// See the documentation of `push` to see what impact `source` has when the
+ /// property is already there.
+ pub fn extend(
+ &mut self,
+ mut drain: SourcePropertyDeclarationDrain,
+ importance: Importance,
+ ) -> bool {
+ let all_shorthand_len = match drain.all_shorthand {
+ AllShorthand::NotSet => 0,
+ AllShorthand::CSSWideKeyword(_) | AllShorthand::WithVariables(_) => {
+ property_counts::ALL_SHORTHAND_EXPANDED
+ },
+ };
+ let push_calls_count = drain.declarations.len() + all_shorthand_len;
+
+ // With deduplication the actual length increase may be less than this.
+ self.declarations.reserve(push_calls_count);
+
+ let mut changed = false;
+ for decl in &mut drain.declarations {
+ changed |= self.push(decl, importance);
+ }
+ drain
+ .all_shorthand
+ .declarations()
+ .fold(changed, |changed, decl| {
+ changed | self.push(decl, importance)
+ })
+ }
+
+ /// Adds or overrides the declaration for a given property in this block.
+ ///
+ /// Returns whether the declaration has changed.
+ ///
+ /// This is only used for parsing and internal use.
+ pub fn push(&mut self, declaration: PropertyDeclaration, importance: Importance) -> bool {
+ let id = declaration.id();
+ if !self.property_ids.insert(id) {
+ let mut index_to_remove = None;
+ for (i, slot) in self.declarations.iter_mut().enumerate() {
+ if slot.id() != id {
+ continue;
+ }
+
+ let important = self.declarations_importance[i];
+
+ // For declarations from parsing, non-important declarations
+ // shouldn't override existing important one.
+ if important && !importance.important() {
+ return false;
+ }
+
+ index_to_remove = Some(i);
+ break;
+ }
+
+ if let Some(index) = index_to_remove {
+ self.declarations.remove(index);
+ self.declarations_importance.remove(index);
+ self.declarations.push(declaration);
+ self.declarations_importance.push(importance.important());
+ return true;
+ }
+ }
+
+ self.declarations.push(declaration);
+ self.declarations_importance.push(importance.important());
+ true
+ }
+
+ /// Prepares updating this declaration block with the given
+ /// `SourcePropertyDeclaration` and importance, and returns whether
+ /// there is something to update.
+ pub fn prepare_for_update(
+ &self,
+ source_declarations: &SourcePropertyDeclaration,
+ importance: Importance,
+ updates: &mut SourcePropertyDeclarationUpdate,
+ ) -> bool {
+ debug_assert!(updates.updates.is_empty());
+ // Check whether we are updating for an all shorthand change.
+ if !matches!(source_declarations.all_shorthand, AllShorthand::NotSet) {
+ debug_assert!(source_declarations.declarations.is_empty());
+ return source_declarations
+ .all_shorthand
+ .declarations()
+ .any(|decl| {
+ !self.contains(decl.id()) ||
+ self.declarations
+ .iter()
+ .enumerate()
+ .find(|&(_, ref d)| d.id() == decl.id())
+ .map_or(true, |(i, d)| {
+ let important = self.declarations_importance[i];
+ *d != decl || important != importance.important()
+ })
+ });
+ }
+ // Fill `updates` with update information.
+ let mut any_update = false;
+ let new_count = &mut updates.new_count;
+ let any_removal = &mut updates.any_removal;
+ let updates = &mut updates.updates;
+ updates.extend(
+ source_declarations
+ .declarations
+ .iter()
+ .map(|declaration| {
+ if !self.contains(declaration.id()) {
+ return DeclarationUpdate::Append;
+ }
+ let longhand_id = declaration.id().as_longhand();
+ if let Some(longhand_id) = longhand_id {
+ if let Some(logical_group) = longhand_id.logical_group() {
+ let mut needs_append = false;
+ for (pos, decl) in self.declarations.iter().enumerate().rev() {
+ let id = match decl.id().as_longhand() {
+ Some(id) => id,
+ None => continue,
+ };
+ if id == longhand_id {
+ if needs_append {
+ return DeclarationUpdate::AppendAndRemove { pos };
+ }
+ let important = self.declarations_importance[pos];
+ if decl == declaration && important == importance.important() {
+ return DeclarationUpdate::None;
+ }
+ return DeclarationUpdate::UpdateInPlace { pos };
+ }
+ if !needs_append &&
+ id.logical_group() == Some(logical_group) &&
+ id.is_logical() != longhand_id.is_logical()
+ {
+ needs_append = true;
+ }
+ }
+ unreachable!("Longhand should be found in loop above");
+ }
+ }
+ self.declarations
+ .iter()
+ .enumerate()
+ .find(|&(_, ref decl)| decl.id() == declaration.id())
+ .map_or(DeclarationUpdate::Append, |(pos, decl)| {
+ let important = self.declarations_importance[pos];
+ if decl == declaration && important == importance.important() {
+ DeclarationUpdate::None
+ } else {
+ DeclarationUpdate::UpdateInPlace { pos }
+ }
+ })
+ })
+ .inspect(|update| {
+ if matches!(update, DeclarationUpdate::None) {
+ return;
+ }
+ any_update = true;
+ match update {
+ DeclarationUpdate::Append => {
+ *new_count += 1;
+ },
+ DeclarationUpdate::AppendAndRemove { .. } => {
+ *any_removal = true;
+ },
+ _ => {},
+ }
+ }),
+ );
+ any_update
+ }
+
+ /// Update this declaration block with the given data.
+ pub fn update(
+ &mut self,
+ drain: SourcePropertyDeclarationDrain,
+ importance: Importance,
+ updates: &mut SourcePropertyDeclarationUpdate,
+ ) {
+ let important = importance.important();
+ if !matches!(drain.all_shorthand, AllShorthand::NotSet) {
+ debug_assert!(updates.updates.is_empty());
+ for decl in drain.all_shorthand.declarations() {
+ let id = decl.id();
+ if self.property_ids.insert(id) {
+ self.declarations.push(decl);
+ self.declarations_importance.push(important);
+ } else {
+ let (idx, slot) = self
+ .declarations
+ .iter_mut()
+ .enumerate()
+ .find(|&(_, ref d)| d.id() == decl.id())
+ .unwrap();
+ *slot = decl;
+ self.declarations_importance.set(idx, important);
+ }
+ }
+ return;
+ }
+
+ self.declarations.reserve(updates.new_count);
+ if updates.any_removal {
+ // Prepare for removal and fixing update positions.
+ struct UpdateOrRemoval<'a> {
+ item: &'a mut DeclarationUpdate,
+ pos: usize,
+ remove: bool,
+ }
+ let mut updates_and_removals: SubpropertiesVec<UpdateOrRemoval> = updates
+ .updates
+ .iter_mut()
+ .filter_map(|item| {
+ let (pos, remove) = match *item {
+ DeclarationUpdate::UpdateInPlace { pos } => (pos, false),
+ DeclarationUpdate::AppendAndRemove { pos } => (pos, true),
+ _ => return None,
+ };
+ Some(UpdateOrRemoval { item, pos, remove })
+ })
+ .collect();
+ // Execute removals. It's important to do it in reverse index order,
+ // so that removing doesn't invalidate following positions.
+ updates_and_removals.sort_unstable_by_key(|update| update.pos);
+ updates_and_removals
+ .iter()
+ .rev()
+ .filter(|update| update.remove)
+ .for_each(|update| {
+ self.declarations.remove(update.pos);
+ self.declarations_importance.remove(update.pos);
+ });
+ // Fixup pos field for updates.
+ let mut removed_count = 0;
+ for update in updates_and_removals.iter_mut() {
+ if update.remove {
+ removed_count += 1;
+ continue;
+ }
+ debug_assert_eq!(
+ *update.item,
+ DeclarationUpdate::UpdateInPlace { pos: update.pos }
+ );
+ *update.item = DeclarationUpdate::UpdateInPlace {
+ pos: update.pos - removed_count,
+ };
+ }
+ }
+ // Execute updates and appends.
+ for (decl, update) in drain.declarations.zip_eq(updates.updates.iter()) {
+ match *update {
+ DeclarationUpdate::None => {},
+ DeclarationUpdate::Append | DeclarationUpdate::AppendAndRemove { .. } => {
+ self.property_ids.insert(decl.id());
+ self.declarations.push(decl);
+ self.declarations_importance.push(important);
+ },
+ DeclarationUpdate::UpdateInPlace { pos } => {
+ self.declarations[pos] = decl;
+ self.declarations_importance.set(pos, important);
+ },
+ }
+ }
+ updates.updates.clear();
+ }
+
+ /// Returns the first declaration that would be removed by removing
+ /// `property`.
+ #[inline]
+ pub fn first_declaration_to_remove(&self, property: &PropertyId) -> Option<usize> {
+ if let Err(longhand_or_custom) = property.as_shorthand() {
+ if !self.contains(longhand_or_custom) {
+ return None;
+ }
+ }
+
+ self.declarations
+ .iter()
+ .position(|declaration| declaration.id().is_or_is_longhand_of(property))
+ }
+
+ /// Removes a given declaration at a given index.
+ #[inline]
+ fn remove_declaration_at(&mut self, i: usize) {
+ self.property_ids.remove(self.declarations[i].id());
+ self.declarations_importance.remove(i);
+ self.declarations.remove(i);
+ }
+
+ /// Clears all the declarations from this block.
+ #[inline]
+ pub fn clear(&mut self) {
+ self.declarations_importance.clear();
+ self.declarations.clear();
+ self.property_ids.clear();
+ }
+
+ /// <https://drafts.csswg.org/cssom/#dom-cssstyledeclaration-removeproperty>
+ ///
+ /// `first_declaration` needs to be the result of
+ /// `first_declaration_to_remove`.
+ #[inline]
+ pub fn remove_property(&mut self, property: &PropertyId, first_declaration: usize) {
+ debug_assert_eq!(
+ Some(first_declaration),
+ self.first_declaration_to_remove(property)
+ );
+ debug_assert!(self.declarations[first_declaration]
+ .id()
+ .is_or_is_longhand_of(property));
+
+ self.remove_declaration_at(first_declaration);
+
+ let shorthand = match property.as_shorthand() {
+ Ok(s) => s,
+ Err(_longhand_or_custom) => return,
+ };
+
+ let mut i = first_declaration;
+ let mut len = self.len();
+ while i < len {
+ if !self.declarations[i].id().is_longhand_of(shorthand) {
+ i += 1;
+ continue;
+ }
+
+ self.remove_declaration_at(i);
+ len -= 1;
+ }
+ }
+
+ /// Take a declaration block known to contain a single property and serialize it.
+ pub fn single_value_to_css(
+ &self,
+ property: &PropertyId,
+ dest: &mut CssStringWriter,
+ computed_values: Option<&ComputedValues>,
+ stylist: &Stylist,
+ ) -> fmt::Result {
+ if let Ok(shorthand) = property.as_shorthand() {
+ return self.shorthand_to_css(shorthand, dest);
+ }
+
+ // FIXME(emilio): Should this assert, or assert that the declaration is
+ // the property we expect?
+ let declaration = match self.declarations.get(0) {
+ Some(d) => d,
+ None => return Err(fmt::Error),
+ };
+
+ let mut rule_cache_conditions = RuleCacheConditions::default();
+ let mut context = Context::new(
+ StyleBuilder::new(
+ stylist.device(),
+ Some(stylist),
+ computed_values,
+ None,
+ None,
+ false,
+ ),
+ stylist.quirks_mode(),
+ &mut rule_cache_conditions,
+ ContainerSizeQuery::none(),
+ );
+
+ if let Some(cv) = computed_values {
+ context.builder.custom_properties = cv.custom_properties.clone();
+ };
+
+ match (declaration, computed_values) {
+ // If we have a longhand declaration with variables, those variables
+ // will be stored as unparsed values.
+ //
+ // As a temporary measure to produce sensible results in Gecko's
+ // getKeyframes() implementation for CSS animations, if
+ // |computed_values| is supplied, we use it to expand such variable
+ // declarations. This will be fixed properly in Gecko bug 1391537.
+ (&PropertyDeclaration::WithVariables(ref declaration), Some(_)) => declaration
+ .value
+ .substitute_variables(
+ declaration.id,
+ &context.builder.custom_properties,
+ stylist,
+ &context,
+ &mut Default::default(),
+ )
+ .to_css(dest),
+ (ref d, _) => d.to_css(dest),
+ }
+ }
+
+ /// Convert AnimationValueMap to PropertyDeclarationBlock.
+ pub fn from_animation_value_map(animation_value_map: &AnimationValueMap) -> Self {
+ let len = animation_value_map.len();
+ let mut declarations = ThinVec::with_capacity(len);
+ let mut property_ids = PropertyDeclarationIdSet::default();
+
+ for (property, animation_value) in animation_value_map.iter() {
+ property_ids.insert(property.as_borrowed());
+ declarations.push(animation_value.uncompute());
+ }
+
+ PropertyDeclarationBlock {
+ declarations,
+ property_ids,
+ declarations_importance: SmallBitVec::from_elem(len, false),
+ }
+ }
+
+ /// Returns true if the declaration block has a CSSWideKeyword for the given
+ /// property.
+ pub fn has_css_wide_keyword(&self, property: &PropertyId) -> bool {
+ if let Err(longhand_or_custom) = property.as_shorthand() {
+ if !self.property_ids.contains(longhand_or_custom) {
+ return false;
+ }
+ }
+ self.declarations.iter().any(|decl| {
+ decl.id().is_or_is_longhand_of(property) && decl.get_css_wide_keyword().is_some()
+ })
+ }
+
+ /// Like the method on ToCss, but without the type parameter to avoid
+ /// accidentally monomorphizing this large function multiple times for
+ /// different writers.
+ ///
+ /// https://drafts.csswg.org/cssom/#serialize-a-css-declaration-block
+ pub fn to_css(&self, dest: &mut CssStringWriter) -> fmt::Result {
+ let mut is_first_serialization = true; // trailing serializations should have a prepended space
+
+ // Step 1 -> dest = result list
+
+ // Step 2
+ //
+ // NOTE(emilio): We reuse this set for both longhands and shorthands
+ // with subtly different meaning. For longhands, only longhands that
+ // have actually been serialized (either by themselves, or as part of a
+ // shorthand) appear here. For shorthands, all the shorthands that we've
+ // attempted to serialize appear here.
+ let mut already_serialized = NonCustomPropertyIdSet::new();
+
+ // Step 3
+ 'declaration_loop: for (declaration, importance) in self.declaration_importance_iter() {
+ // Step 3.1
+ let property = declaration.id();
+ let longhand_id = match property {
+ PropertyDeclarationId::Longhand(id) => id,
+ PropertyDeclarationId::Custom(..) => {
+ // Given the invariants that there are no duplicate
+ // properties in a declaration block, and that custom
+ // properties can't be part of a shorthand, we can just care
+ // about them here.
+ append_serialization(
+ dest,
+ &property,
+ AppendableValue::Declaration(declaration),
+ importance,
+ &mut is_first_serialization,
+ )?;
+ continue;
+ },
+ };
+
+ // Step 3.2
+ if already_serialized.contains(longhand_id.into()) {
+ continue;
+ }
+
+ // Steps 3.3 & 3.4
+ for shorthand in longhand_id.shorthands() {
+ // We already attempted to serialize this shorthand before.
+ if already_serialized.contains(shorthand.into()) {
+ continue;
+ }
+ already_serialized.insert(shorthand.into());
+
+ if shorthand.is_legacy_shorthand() {
+ continue;
+ }
+
+ // Step 3.3.1:
+ // Let longhands be an array consisting of all CSS
+ // declarations in declaration block’s declarations that
+ // that are not in already serialized and have a property
+ // name that maps to one of the shorthand properties in
+ // shorthands.
+ let longhands = {
+ // TODO(emilio): This could just index in an array if we
+ // remove pref-controlled longhands.
+ let mut ids = LonghandIdSet::new();
+ for longhand in shorthand.longhands() {
+ ids.insert(longhand);
+ }
+ ids
+ };
+
+ // Step 3.4.2
+ // If all properties that map to shorthand are not present
+ // in longhands, continue with the steps labeled shorthand
+ // loop.
+ if !self.property_ids.contains_all_longhands(&longhands) {
+ continue;
+ }
+
+ // Step 3.4.3:
+ // Let current longhands be an empty array.
+ let mut current_longhands = SmallVec::<[&_; 10]>::new();
+ let mut logical_groups = LogicalGroupSet::new();
+ let mut saw_one = false;
+ let mut logical_mismatch = false;
+ let mut seen = LonghandIdSet::new();
+ let mut important_count = 0;
+
+ // Step 3.4.4:
+ // Append all CSS declarations in longhands that have a
+ // property name that maps to shorthand to current longhands.
+ for (declaration, importance) in self.declaration_importance_iter() {
+ let longhand = match declaration.id() {
+ PropertyDeclarationId::Longhand(id) => id,
+ PropertyDeclarationId::Custom(..) => continue,
+ };
+
+ if longhands.contains(longhand) {
+ saw_one = true;
+ if importance.important() {
+ important_count += 1;
+ }
+ current_longhands.push(declaration);
+ if shorthand != ShorthandId::All {
+ // All is special because it contains both physical
+ // and logical longhands.
+ if let Some(g) = longhand.logical_group() {
+ logical_groups.insert(g);
+ }
+ seen.insert(longhand);
+ if seen == longhands {
+ break;
+ }
+ }
+ } else if saw_one {
+ if let Some(g) = longhand.logical_group() {
+ if logical_groups.contains(g) {
+ logical_mismatch = true;
+ break;
+ }
+ }
+ }
+ }
+
+ // 3.4.5:
+ // If there is one or more CSS declarations in current
+ // longhands have their important flag set and one or more
+ // with it unset, continue with the steps labeled shorthand
+ // loop.
+ let is_important = important_count > 0;
+ if is_important && important_count != current_longhands.len() {
+ continue;
+ }
+
+ // 3.4.6:
+ // If there’s any declaration in declaration block in between
+ // the first and the last longhand in current longhands which
+ // belongs to the same logical property group, but has a
+ // different mapping logic as any of the longhands in current
+ // longhands, and is not in current longhands, continue with
+ // the steps labeled shorthand loop.
+ if logical_mismatch {
+ continue;
+ }
+
+ let importance = if is_important {
+ Importance::Important
+ } else {
+ Importance::Normal
+ };
+
+ // 3.4.7:
+ // Let value be the result of invoking serialize a CSS value
+ // of current longhands.
+ let appendable_value =
+ match shorthand.get_shorthand_appendable_value(&current_longhands) {
+ None => continue,
+ Some(appendable_value) => appendable_value,
+ };
+
+ // We avoid re-serializing if we're already an
+ // AppendableValue::Css.
+ let mut v = CssString::new();
+ let value = match appendable_value {
+ AppendableValue::Css(css) => {
+ debug_assert!(!css.is_empty());
+ appendable_value
+ },
+ other => {
+ append_declaration_value(&mut v, other)?;
+
+ // 3.4.8:
+ // If value is the empty string, continue with the
+ // steps labeled shorthand loop.
+ if v.is_empty() {
+ continue;
+ }
+
+ AppendableValue::Css({
+ // Safety: serialization only generates valid utf-8.
+ #[cfg(feature = "gecko")]
+ unsafe {
+ v.as_str_unchecked()
+ }
+ #[cfg(feature = "servo")]
+ &v
+ })
+ },
+ };
+
+ // 3.4.9:
+ // Let serialized declaration be the result of invoking
+ // serialize a CSS declaration with property name shorthand,
+ // value value, and the important flag set if the CSS
+ // declarations in current longhands have their important
+ // flag set.
+ //
+ // 3.4.10:
+ // Append serialized declaration to list.
+ append_serialization(
+ dest,
+ &shorthand,
+ value,
+ importance,
+ &mut is_first_serialization,
+ )?;
+
+ // 3.4.11:
+ // Append the property names of all items of current
+ // longhands to already serialized.
+ for current_longhand in &current_longhands {
+ let longhand_id = match current_longhand.id() {
+ PropertyDeclarationId::Longhand(id) => id,
+ PropertyDeclarationId::Custom(..) => unreachable!(),
+ };
+
+ // Substep 9
+ already_serialized.insert(longhand_id.into());
+ }
+
+ // 3.4.12:
+ // Continue with the steps labeled declaration loop.
+ continue 'declaration_loop;
+ }
+
+ // Steps 3.5, 3.6 & 3.7:
+ // Let value be the result of invoking serialize a CSS value of
+ // declaration.
+ //
+ // Let serialized declaration be the result of invoking
+ // serialize a CSS declaration with property name property,
+ // value value, and the important flag set if declaration has
+ // its important flag set.
+ //
+ // Append serialized declaration to list.
+ append_serialization(
+ dest,
+ &property,
+ AppendableValue::Declaration(declaration),
+ importance,
+ &mut is_first_serialization,
+ )?;
+
+ // Step 3.8:
+ // Append property to already serialized.
+ already_serialized.insert(longhand_id.into());
+ }
+
+ // Step 4
+ Ok(())
+ }
+}
+
+/// A convenient enum to represent different kinds of stuff that can represent a
+/// _value_ in the serialization of a property declaration.
+pub enum AppendableValue<'a, 'b: 'a> {
+ /// A given declaration, of which we'll serialize just the value.
+ Declaration(&'a PropertyDeclaration),
+ /// A set of declarations for a given shorthand.
+ ///
+ /// FIXME: This needs more docs, where are the shorthands expanded? We print
+ /// the property name before-hand, don't we?
+ DeclarationsForShorthand(ShorthandId, &'a [&'b PropertyDeclaration]),
+ /// A raw CSS string, coming for example from a property with CSS variables,
+ /// or when storing a serialized shorthand value before appending directly.
+ Css(&'a str),
+}
+
+/// Potentially appends whitespace after the first (property: value;) pair.
+fn handle_first_serialization<W>(dest: &mut W, is_first_serialization: &mut bool) -> fmt::Result
+where
+ W: Write,
+{
+ if !*is_first_serialization {
+ dest.write_char(' ')
+ } else {
+ *is_first_serialization = false;
+ Ok(())
+ }
+}
+
+/// Append a given kind of appendable value to a serialization.
+pub fn append_declaration_value<'a, 'b: 'a>(
+ dest: &mut CssStringWriter,
+ appendable_value: AppendableValue<'a, 'b>,
+) -> fmt::Result {
+ match appendable_value {
+ AppendableValue::Css(css) => dest.write_str(css),
+ AppendableValue::Declaration(decl) => decl.to_css(dest),
+ AppendableValue::DeclarationsForShorthand(shorthand, decls) => {
+ shorthand.longhands_to_css(decls, dest)
+ },
+ }
+}
+
+/// Append a given property and value pair to a serialization.
+pub fn append_serialization<'a, 'b: 'a, N>(
+ dest: &mut CssStringWriter,
+ property_name: &N,
+ appendable_value: AppendableValue<'a, 'b>,
+ importance: Importance,
+ is_first_serialization: &mut bool,
+) -> fmt::Result
+where
+ N: ToCss,
+{
+ handle_first_serialization(dest, is_first_serialization)?;
+
+ property_name.to_css(&mut CssWriter::new(dest))?;
+ dest.write_str(": ")?;
+
+ append_declaration_value(dest, appendable_value)?;
+
+ if importance.important() {
+ dest.write_str(" !important")?;
+ }
+
+ dest.write_char(';')
+}
+
+/// A helper to parse the style attribute of an element, in order for this to be
+/// shared between Servo and Gecko.
+///
+/// Inline because we call this cross-crate.
+#[inline]
+pub fn parse_style_attribute(
+ input: &str,
+ url_data: &UrlExtraData,
+ error_reporter: Option<&dyn ParseErrorReporter>,
+ quirks_mode: QuirksMode,
+ rule_type: CssRuleType,
+) -> PropertyDeclarationBlock {
+ let context = ParserContext::new(
+ Origin::Author,
+ url_data,
+ Some(rule_type),
+ ParsingMode::DEFAULT,
+ quirks_mode,
+ /* namespaces = */ Default::default(),
+ error_reporter,
+ None,
+ );
+
+ let mut input = ParserInput::new(input);
+ parse_property_declaration_list(&context, &mut Parser::new(&mut input), &[])
+}
+
+/// Parse a given property declaration. Can result in multiple
+/// `PropertyDeclaration`s when expanding a shorthand, for example.
+///
+/// This does not attempt to parse !important at all.
+#[inline]
+pub fn parse_one_declaration_into(
+ declarations: &mut SourcePropertyDeclaration,
+ id: PropertyId,
+ input: &str,
+ origin: Origin,
+ url_data: &UrlExtraData,
+ error_reporter: Option<&dyn ParseErrorReporter>,
+ parsing_mode: ParsingMode,
+ quirks_mode: QuirksMode,
+ rule_type: CssRuleType,
+) -> Result<(), ()> {
+ let context = ParserContext::new(
+ origin,
+ url_data,
+ Some(rule_type),
+ parsing_mode,
+ quirks_mode,
+ /* namespaces = */ Default::default(),
+ error_reporter,
+ None,
+ );
+
+ let property_id_for_error_reporting = if context.error_reporting_enabled() {
+ Some(id.clone())
+ } else {
+ None
+ };
+
+ let mut input = ParserInput::new(input);
+ let mut parser = Parser::new(&mut input);
+ let start_position = parser.position();
+ parser
+ .parse_entirely(|parser| {
+ PropertyDeclaration::parse_into(declarations, id, &context, parser)
+ })
+ .map_err(|err| {
+ if context.error_reporting_enabled() {
+ report_one_css_error(
+ &context,
+ None,
+ &[],
+ err,
+ parser.slice_from(start_position),
+ property_id_for_error_reporting,
+ )
+ }
+ })
+}
+
+/// A struct to parse property declarations.
+struct PropertyDeclarationParser<'a, 'b: 'a, 'i> {
+ context: &'a ParserContext<'b>,
+ state: &'a mut DeclarationParserState<'i>,
+}
+
+/// The state needed to parse a declaration block.
+///
+/// It stores declarations in output_block.
+#[derive(Default)]
+pub struct DeclarationParserState<'i> {
+ /// The output block where results are stored.
+ output_block: PropertyDeclarationBlock,
+ /// Declarations from the last declaration parsed. (note that a shorthand might expand to
+ /// multiple declarations).
+ declarations: SourcePropertyDeclaration,
+ /// The importance from the last declaration parsed.
+ importance: Importance,
+ /// A list of errors that have happened so far. Not all of them might be reported.
+ errors: SmallParseErrorVec<'i>,
+ /// The last parsed property id, if any.
+ last_parsed_property_id: Option<PropertyId>,
+}
+
+impl<'i> DeclarationParserState<'i> {
+ /// Returns whether any parsed declarations have been parsed so far.
+ pub fn has_parsed_declarations(&self) -> bool {
+ !self.output_block.is_empty()
+ }
+
+ /// Takes the parsed declarations.
+ pub fn take_declarations(&mut self) -> PropertyDeclarationBlock {
+ std::mem::take(&mut self.output_block)
+ }
+
+ /// Parse a single declaration value.
+ pub fn parse_value<'t>(
+ &mut self,
+ context: &ParserContext,
+ name: CowRcStr<'i>,
+ input: &mut Parser<'i, 't>,
+ ) -> Result<(), ParseError<'i>> {
+ let id = match PropertyId::parse(&name, context) {
+ Ok(id) => id,
+ Err(..) => {
+ return Err(input.new_custom_error(StyleParseErrorKind::UnknownProperty(name)));
+ },
+ };
+ if context.error_reporting_enabled() {
+ self.last_parsed_property_id = Some(id.clone());
+ }
+ input.parse_until_before(Delimiter::Bang, |input| {
+ PropertyDeclaration::parse_into(&mut self.declarations, id, context, input)
+ })?;
+ self.importance = match input.try_parse(parse_important) {
+ Ok(()) => Importance::Important,
+ Err(_) => Importance::Normal,
+ };
+ // In case there is still unparsed text in the declaration, we should roll back.
+ input.expect_exhausted()?;
+ self.output_block
+ .extend(self.declarations.drain(), self.importance);
+ // We've successfully parsed a declaration, so forget about
+ // `last_parsed_property_id`. It'd be wrong to associate any
+ // following error with this property.
+ self.last_parsed_property_id = None;
+ Ok(())
+ }
+
+ /// Reports any CSS errors that have ocurred if needed.
+ #[inline]
+ pub fn report_errors_if_needed(
+ &mut self,
+ context: &ParserContext,
+ selectors: &[SelectorList<SelectorImpl>],
+ ) {
+ if self.errors.is_empty() {
+ return;
+ }
+ self.do_report_css_errors(context, selectors);
+ }
+
+ #[cold]
+ fn do_report_css_errors(
+ &mut self,
+ context: &ParserContext,
+ selectors: &[SelectorList<SelectorImpl>],
+ ) {
+ for (error, slice, property) in self.errors.drain(..) {
+ report_one_css_error(
+ context,
+ Some(&self.output_block),
+ selectors,
+ error,
+ slice,
+ property,
+ )
+ }
+ }
+
+ /// Resets the declaration parser state, and reports the error if needed.
+ #[inline]
+ pub fn did_error(&mut self, context: &ParserContext, error: ParseError<'i>, slice: &'i str) {
+ self.declarations.clear();
+ if !context.error_reporting_enabled() {
+ return;
+ }
+ let property = self.last_parsed_property_id.take();
+ self.errors.push((error, slice, property));
+ }
+}
+
+/// Default methods reject all at rules.
+impl<'a, 'b, 'i> AtRuleParser<'i> for PropertyDeclarationParser<'a, 'b, 'i> {
+ type Prelude = ();
+ type AtRule = ();
+ type Error = StyleParseErrorKind<'i>;
+}
+
+/// Default methods reject all rules.
+impl<'a, 'b, 'i> QualifiedRuleParser<'i> for PropertyDeclarationParser<'a, 'b, 'i> {
+ type Prelude = ();
+ type QualifiedRule = ();
+ type Error = StyleParseErrorKind<'i>;
+}
+
+/// Based on NonMozillaVendorIdentifier from Gecko's CSS parser.
+fn is_non_mozilla_vendor_identifier(name: &str) -> bool {
+ (name.starts_with("-") && !name.starts_with("-moz-")) || name.starts_with("_")
+}
+
+impl<'a, 'b, 'i> DeclarationParser<'i> for PropertyDeclarationParser<'a, 'b, 'i> {
+ type Declaration = ();
+ type Error = StyleParseErrorKind<'i>;
+
+ fn parse_value<'t>(
+ &mut self,
+ name: CowRcStr<'i>,
+ input: &mut Parser<'i, 't>,
+ ) -> Result<(), ParseError<'i>> {
+ self.state.parse_value(self.context, name, input)
+ }
+}
+
+impl<'a, 'b, 'i> RuleBodyItemParser<'i, (), StyleParseErrorKind<'i>>
+ for PropertyDeclarationParser<'a, 'b, 'i>
+{
+ fn parse_declarations(&self) -> bool {
+ true
+ }
+ // TODO(emilio): Nesting.
+ fn parse_qualified(&self) -> bool {
+ false
+ }
+}
+
+type SmallParseErrorVec<'i> = SmallVec<[(ParseError<'i>, &'i str, Option<PropertyId>); 2]>;
+
+fn alias_of_known_property(name: &str) -> Option<PropertyId> {
+ let mut prefixed = String::with_capacity(name.len() + 5);
+ prefixed.push_str("-moz-");
+ prefixed.push_str(name);
+ PropertyId::parse_enabled_for_all_content(&prefixed).ok()
+}
+
+#[cold]
+fn report_one_css_error<'i>(
+ context: &ParserContext,
+ block: Option<&PropertyDeclarationBlock>,
+ selectors: &[SelectorList<SelectorImpl>],
+ mut error: ParseError<'i>,
+ slice: &str,
+ property: Option<PropertyId>,
+) {
+ debug_assert!(context.error_reporting_enabled());
+
+ fn all_properties_in_block(block: &PropertyDeclarationBlock, property: &PropertyId) -> bool {
+ match property.as_shorthand() {
+ Ok(id) => id
+ .longhands()
+ .all(|longhand| block.contains(PropertyDeclarationId::Longhand(longhand))),
+ Err(longhand_or_custom) => block.contains(longhand_or_custom),
+ }
+ }
+
+ if let ParseErrorKind::Custom(StyleParseErrorKind::UnknownProperty(ref name)) = error.kind {
+ if is_non_mozilla_vendor_identifier(name) {
+ // If the unrecognized property looks like a vendor-specific property,
+ // silently ignore it instead of polluting the error output.
+ return;
+ }
+ if let Some(alias) = alias_of_known_property(name) {
+ // This is an unknown property, but its -moz-* version is known.
+ // We don't want to report error if the -moz-* version is already
+ // specified.
+ if let Some(block) = block {
+ if all_properties_in_block(block, &alias) {
+ return;
+ }
+ }
+ }
+ }
+
+ if let Some(ref property) = property {
+ if let Some(block) = block {
+ if all_properties_in_block(block, property) {
+ return;
+ }
+ }
+ error = match *property {
+ PropertyId::Custom(ref c) => {
+ StyleParseErrorKind::new_invalid(format!("--{}", c), error)
+ },
+ _ => StyleParseErrorKind::new_invalid(property.non_custom_id().unwrap().name(), error),
+ };
+ }
+
+ let location = error.location;
+ let error = ContextualParseError::UnsupportedPropertyDeclaration(slice, error, selectors);
+ context.log_css_error(location, error);
+}
+
+/// Parse a list of property declarations and return a property declaration
+/// block.
+pub fn parse_property_declaration_list(
+ context: &ParserContext,
+ input: &mut Parser,
+ selectors: &[SelectorList<SelectorImpl>],
+) -> PropertyDeclarationBlock {
+ let mut state = DeclarationParserState::default();
+ let mut parser = PropertyDeclarationParser {
+ context,
+ state: &mut state,
+ };
+ let mut iter = RuleBodyParser::new(input, &mut parser);
+ while let Some(declaration) = iter.next() {
+ match declaration {
+ Ok(()) => {},
+ Err((error, slice)) => iter.parser.state.did_error(context, error, slice),
+ }
+ }
+ parser.state.report_errors_if_needed(context, selectors);
+ state.output_block
+}