/* This Source Code Form is subject to the terms of the Mozilla Public *License,v.2.0.IfacopyoftheMPLwasnotdistributedwiththis
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
//! A property declaration block.
#![deny(missing_docs)]
usesuper::{
property_counts, AllShorthand, ComputedValues, LogicalGroupSet, LonghandIdSet,
LonghandIdSetIterator, NonCustomPropertyIdSet, PropertyDeclaration, PropertyDeclarationId,
PropertyId, ShorthandId, SourcePropertyDeclaration, SourcePropertyDeclarationDrain,
SubpropertiesVec,
}; usecrate::context::QuirksMode; usecrate::custom_properties; usecrate::error_reporting::{ContextualParseError, ParseErrorReporter}; usecrate::parser::ParserContext; usecrate::properties::{
animated_properties::{AnimationValue, AnimationValueMap},
StyleBuilder,
}; usecrate::rule_cache::RuleCacheConditions; usecrate::selector_map::PrecomputedHashSet; usecrate::selector_parser::SelectorImpl; usecrate::shared_lock::Locked; usecrate::str::{CssString, CssStringWriter}; usecrate::stylesheets::container_rule::ContainerSizeQuery; usecrate::stylesheets::{CssRuleType, Origin, UrlExtraData}; usecrate::stylist::Stylist; usecrate::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)] pubstruct 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. pubfn 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)] pubstruct 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)] pubenum Importance { /// Indicates a declaration without `!important`.
Normal,
/// Indicates a declaration with `!important`.
Important,
}
impl Importance { /// Return whether this is an important declaration. pubfn important(self) -> bool { matchself { Self::Normal => false, Self::Important => true,
}
}
}
/// A set of properties. #[derive(Clone, Debug, ToShmem, Default, MallocSizeOf)] pubstruct PropertyDeclarationIdSet {
longhands: LonghandIdSet,
custom: PrecomputedHashSet<custom_properties::Name>,
}
impl PropertyDeclarationIdSet { /// Add the given property to the set. pubfn insert(&mutself, id: PropertyDeclarationId) -> bool { match id {
PropertyDeclarationId::Longhand(id) => { ifself.longhands.contains(id) { returnfalse;
} self.longhands.insert(id); returntrue;
},
PropertyDeclarationId::Custom(name) => self.custom.insert((*name).clone()),
}
}
/// Return whether the given property is in the set. pubfn 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. pubfn remove(&mutself, id: PropertyDeclarationId) { match id {
PropertyDeclarationId::Longhand(id) => self.longhands.remove(id),
PropertyDeclarationId::Custom(name) => { self.custom.remove(name);
},
}
}
/// Remove all properties from the set. pubfn clear(&mutself) { self.longhands.clear(); self.custom.clear();
}
/// Returns whether the set is empty. #[inline] pubfn is_empty(&self) -> bool { self.longhands.is_empty() && self.custom.is_empty()
} /// Returns whether this set contains any reset longhand. #[inline] pubfn contains_any_reset(&self) -> bool { self.longhands.contains_any_reset()
}
/// Returns whether this set contains all longhands in the specified set. #[inline] pubfn contains_all_longhands(&self, longhands: &LonghandIdSet) -> bool { self.longhands.contains_all(longhands)
}
/// Returns whether this set contains all properties in the specified set. #[inline] pubfn contains_all(&self, properties: &PropertyDeclarationIdSet) -> bool { if !self.longhands.contains_all(&properties.longhands) { returnfalse;
} if properties.custom.len() > self.custom.len() { returnfalse;
}
properties
.custom
.iter()
.all(|item| self.custom.contains(item))
}
/// Iterate over the current property declaration id set. pubfn iter(&self) -> PropertyDeclarationIdSetIterator {
PropertyDeclarationIdSetIterator {
longhands: self.longhands.iter(),
custom: self.custom.iter(),
}
}
}
/// An iterator over a set of longhand ids. pubstruct 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(&mutself) -> 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. matchself.longhands.next() {
Some(id) => Some(PropertyDeclarationId::Longhand(id)),
None => matchself.custom.next() {
Some(a) => Some(PropertyDeclarationId::Custom(a)),
None => None,
},
}
}
}
/// Overridden declarations are skipped. #[cfg_attr(feature = "gecko", derive(MallocSizeOf))] #[derive(Clone, ToShmem, Default)] pubstruct 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,
}
/// Create a block with a single declaration pubfn with_one(declaration: PropertyDeclaration, importance: Importance) -> Self { letmut property_ids = PropertyDeclarationIdSet::default();
property_ids.insert(declaration.id()); letmut 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] pubfn declarations(&self) -> &[PropertyDeclaration] {
&self.declarations
}
/// The `important` flags for declarations in this block #[inline] pubfn declarations_importance(&self) -> &SmallBitVec {
&self.declarations_importance
}
/// 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] pubfn 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] pubfn any_normal(&self) -> bool {
!self.declarations_importance.all_true()
}
/// Returns a `PropertyDeclarationIdSet` representing the properties that are changed in /// this block. #[inline] pubfn property_ids(&self) -> &PropertyDeclarationIdSet {
&self.property_ids
}
/// Returns whether this block contains a declaration of a given property id. #[inline] pubfn contains(&self, id: PropertyDeclarationId) -> bool { self.property_ids.contains(id)
}
/// Returns whether this block contains any reset longhand. #[inline] pubfn 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] pubfn 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. pubfn shorthand_to_css(
&self,
shorthand: ShorthandId,
dest: &mut CssStringWriter,
) -> fmt::Result { // Step 1.2.1 of // https://drafts.csswg.org/cssom/#dom-cssstyledeclaration-getpropertyvalue letmut list = SmallVec::<[&_; 10]>::new(); letmut important_count = 0;
// Step 1.2.2 for longhand in shorthand.longhands() { // Step 1.2.2.1 let declaration = self.get(PropertyDeclarationId::Longhand(longhand));
// 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> pubfn 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) => returnself.shorthand_to_css(shorthand, dest),
Err(longhand_or_custom) => longhand_or_custom,
};
/// 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. pubfn extend(
&mutself, 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);
/// 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. pubfn push(&mutself, declaration: PropertyDeclaration, importance: Importance) -> bool { let id = declaration.id(); if !self.property_ids.insert(id) { letmut index_to_remove = None; for (i, slot) inself.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() { returnfalse;
}
/// Prepares updating this declaration block with the given /// `SourcePropertyDeclaration` and importance, and returns whether /// there is something to update. pubfn 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. letmut 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(); iflet Some(longhand_id) = longhand_id { iflet Some(logical_group) = longhand_id.logical_group() { letmut needs_append = false; for (pos, decl) inself.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. pubfn update(
&mutself,
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(); ifself.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,
} letmut 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. letmut 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] pubfn first_declaration_to_remove(&self, property: &PropertyId) -> Option<usize> { iflet Err(longhand_or_custom) = property.as_shorthand() { if !self.contains(longhand_or_custom) { return None;
}
}
/// Removes a given declaration at a given index. #[inline] fn remove_declaration_at(&mutself, 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] pubfn clear(&mutself) { 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] pubfn remove_property(&mutself, 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,
};
letmut i = first_declaration; letmut 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. pubfn single_value_to_css(
&self,
property: &PropertyId,
dest: &mut CssStringWriter,
computed_values: Option<&ComputedValues>,
stylist: &Stylist,
) -> fmt::Result { iflet Ok(shorthand) = property.as_shorthand() { returnself.shorthand_to_css(shorthand, dest);
}
// FIXME(emilio): Should this assert, or assert that the declaration is // the property we expect? let declaration = matchself.declarations.get(0) {
Some(d) => d,
None => return Err(fmt::Error),
};
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. pubfn from_animation_value_map(animation_value_map: &AnimationValueMap) -> Self { let len = animation_value_map.len(); letmut declarations = ThinVec::with_capacity(len); letmut property_ids = PropertyDeclarationIdSet::default();
for (property, animation_value) in animation_value_map.iter() {
property_ids.insert(property.as_borrowed());
declarations.push(animation_value.uncompute());
}
/// Returns true if the declaration block has a CSSWideKeyword for the given /// property. pubfn has_css_wide_keyword(&self, property: &PropertyId) -> bool { iflet Err(longhand_or_custom) = property.as_shorthand() { if !self.property_ids.contains(longhand_or_custom) { returnfalse;
}
} 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 pubfn to_css(&self, dest: &mut CssStringWriter) -> fmt::Result { letmut 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. letmut 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. letmut 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. letmut current_longhands = SmallVec::<[&_; 10]>::new(); letmut logical_groups = LogicalGroupSet::new(); letmut saw_one = false; letmut logical_mismatch = false; letmut seen = LonghandIdSet::new(); letmut 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) inself.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. iflet Some(g) = longhand.logical_group() {
logical_groups.insert(g);
}
seen.insert(longhand); if seen == longhands { break;
}
}
} elseif saw_one { iflet 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(¤t_longhands) {
None => continue,
Some(appendable_value) => appendable_value,
};
// We avoid re-serializing if we're already an // AppendableValue::Css. letmut 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;
}
// 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 ¤t_longhands { let longhand_id = match current_longhand.id() {
PropertyDeclarationId::Longhand(id) => id,
PropertyDeclarationId::Custom(..) => unreachable!(),
};
// 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,
)?;
/// A convenient enum to represent different kinds of stuff that can represent a /// _value_ in the serialization of a property declaration. pubenum 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. pubfn 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. pubfn 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)?;
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] pubfn 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,
);
/// 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] pubfn 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
};
/// The state needed to parse a declaration block. /// /// It stores declarations in output_block. #[derive(Default)] pubstruct 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. pubfn has_parsed_declarations(&self) -> bool {
!self.output_block.is_empty()
}
/// Parse a single declaration value. pubfn parse_value<'t>(
&mutself,
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(&mutself.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] pubfn report_errors_if_needed(
&mutself,
context: &ParserContext,
selectors: &[SelectorList<SelectorImpl>],
) { ifself.errors.is_empty() { return;
} self.do_report_css_errors(context, selectors);
}
/// Resets the declaration parser state, and reports the error if needed. #[inline] pubfn did_error(&mutself, 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>;
iflet 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;
} iflet 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. iflet Some(block) = block { if all_properties_in_block(block, &alias) { return;
}
}
}
}
iflet Some(ref property) = property { iflet 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. pubfn parse_property_declaration_list(
context: &ParserContext,
input: &mut Parser,
selectors: &[SelectorList<SelectorImpl>],
) -> PropertyDeclarationBlock { letmut state = DeclarationParserState::default(); letmut parser = PropertyDeclarationParser {
context,
state: &mut state,
}; letmut iter = RuleBodyParser::new(input, &mut parser); whilelet 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
}
Messung V0.5 in Prozent
¤ Dauer der Verarbeitung: 0.59 Sekunden
(vorverarbeitet am 2026-06-19)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.