Source code

Revision control

Copy as Markdown

Other Tools

/* 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::SmallBitVec;
use smallvec::SmallVec;
use std::fmt::{self, Write};
use std::iter::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].
///
#[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>,
style: &'a ComputedValues,
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>,
style: &'a ComputedValues,
default_values: &'a ComputedValues,
) -> AnimationValueIterator<'a, 'cx, 'cx_a> {
AnimationValueIterator {
iter: declarations.declaration_importance_iter(),
context,
style,
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.style, 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>,
style: &'a ComputedValues,
default_values: &'a ComputedValues,
) -> AnimationValueIterator<'a, 'cx, 'cx_a> {
AnimationValueIterator::new(self, context, style, 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
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
///
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(())
}
}
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();
}
///
/// `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.
///
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(()) => {
if !context.allows_important_declarations() {
return Err(input.new_custom_error(StyleParseErrorKind::UnexpectedImportantDeclaration));
}
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;
}
}
// Was able to parse property ID - Either an invalid value, or is constrained
// by the rule block it's in to be invalid. In the former case, we need to unwrap
// the error to be more specific.
if !matches!(
error.kind,
ParseErrorKind::Custom(StyleParseErrorKind::UnexpectedImportantDeclaration)
) {
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
}