Source code

Revision control

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/. */
// This file is a Mako template: http://www.makotemplates.org/
// Please note that valid Rust syntax may be mangled by the Mako parser.
// For example, Vec<&Foo> will be mangled as Vec&Foo>. To work around these issues, the code
// can be escaped. In the above example, Vec<<&Foo> or Vec< &Foo> achieves the desired result of Vec<&Foo>.
<%namespace name="helpers" file="/helpers.mako.rs" />
#[cfg(feature = "servo")]
use app_units::Au;
use arrayvec::{ArrayVec, Drain as ArrayVecDrain};
use servo_arc::{Arc, UniqueArc};
use std::borrow::Cow;
use std::{ops, ptr};
use std::fmt::{self, Write};
use std::mem;
use cssparser::{Parser, RGBA, TokenSerializationType};
use cssparser::ParserInput;
#[cfg(feature = "servo")] use euclid::SideOffsets2D;
use crate::context::QuirksMode;
#[cfg(feature = "gecko")] use crate::gecko_bindings::structs::{self, nsCSSPropertyID};
#[cfg(feature = "servo")] use crate::logical_geometry::LogicalMargin;
#[cfg(feature = "servo")] use crate::computed_values;
use crate::logical_geometry::WritingMode;
use malloc_size_of::{MallocSizeOf, MallocSizeOfOps};
use crate::computed_value_flags::*;
use crate::media_queries::Device;
use crate::parser::ParserContext;
use crate::properties::longhands::system_font::SystemFont;
use crate::selector_parser::PseudoElement;
use selectors::parser::SelectorParseErrorKind;
#[cfg(feature = "servo")] use servo_config::prefs;
use style_traits::{CssWriter, KeywordsCollectFn, ParseError, ParsingMode};
use style_traits::{SpecifiedValueInfo, StyleParseErrorKind, ToCss};
use to_shmem::impl_trivial_to_shmem;
use crate::stylesheets::{CssRuleType, Origin, UrlExtraData};
use crate::use_counters::UseCounters;
use crate::values::generics::text::LineHeight;
use crate::values::{computed, resolved};
use crate::values::computed::NonNegativeLength;
use crate::values::serialize_atom_name;
use crate::rule_tree::StrongRuleNode;
use crate::Zero;
use crate::str::{CssString, CssStringBorrow, CssStringWriter};
use std::cell::Cell;
pub use self::declaration_block::*;
pub use self::cascade::*;
<%!
from collections import defaultdict
from data import Method, PropertyRestrictions, Keyword, to_rust_ident, to_camel_case, SYSTEM_FONT_LONGHANDS
import os.path
%>
#[path="${repr(os.path.join(os.path.dirname(__file__), 'declaration_block.rs'))[1:-1]}"]
pub mod declaration_block;
#[path="${repr(os.path.join(os.path.dirname(__file__), 'cascade.rs'))[1:-1]}"]
pub mod cascade;
/// Conversion with fewer impls than From/Into
pub trait MaybeBoxed<Out> {
/// Convert
fn maybe_boxed(self) -> Out;
}
impl<T> MaybeBoxed<T> for T {
#[inline]
fn maybe_boxed(self) -> T { self }
}
impl<T> MaybeBoxed<Box<T>> for T {
#[inline]
fn maybe_boxed(self) -> Box<T> { Box::new(self) }
}
macro_rules! expanded {
( $( $name: ident: $value: expr ),+ ) => {
expanded!( $( $name: $value, )+ )
};
( $( $name: ident: $value: expr, )+ ) => {
Longhands {
$(
$name: MaybeBoxed::maybe_boxed($value),
)+
}
}
}
/// A module with all the code for longhand properties.
#[allow(missing_docs)]
pub mod longhands {
% for style_struct in data.style_structs:
include!("${repr(os.path.join(OUT_DIR, 'longhands/{}.rs'.format(style_struct.name_lower)))[1:-1]}");
% endfor
pub const ANIMATABLE_PROPERTY_COUNT: usize = ${sum(1 for prop in data.longhands if prop.animatable)};
}
macro_rules! unwrap_or_initial {
($prop: ident) => (unwrap_or_initial!($prop, $prop));
($prop: ident, $expr: expr) =>
($expr.unwrap_or_else(|| $prop::get_initial_specified_value()));
}
/// A module with code for all the shorthand css properties, and a few
/// serialization helpers.
#[allow(missing_docs)]
pub mod shorthands {
use cssparser::Parser;
use crate::parser::{Parse, ParserContext};
use style_traits::{ParseError, StyleParseErrorKind};
use crate::values::specified;
use style_traits::{CssWriter, ToCss};
use crate::values::specified::{BorderStyle, Color};
use std::fmt::{self, Write};
fn serialize_directional_border<W, I,>(
dest: &mut CssWriter<W>,
width: &I,
style: &BorderStyle,
color: &Color,
) -> fmt::Result
where
W: Write,
I: ToCss,
{
width.to_css(dest)?;
// FIXME(emilio): Should we really serialize the border style if it's
// `solid`?
dest.write_str(" ")?;
style.to_css(dest)?;
if *color != Color::CurrentColor {
dest.write_str(" ")?;
color.to_css(dest)?;
}
Ok(())
}
% for style_struct in data.style_structs:
include!("${repr(os.path.join(OUT_DIR, 'shorthands/{}.rs'.format(style_struct.name_lower)))[1:-1]}");
% endfor
// We didn't define the 'all' shorthand using the regular helpers:shorthand
// mechanism, since it causes some very large types to be generated.
//
// Also, make sure logical properties appear before its physical
// counter-parts, in order to prevent bugs like:
//
//
// FIXME(emilio): Adopt the resolution from:
//
//
// when there is one, whatever that is.
<%
logical_longhands = []
other_longhands = []
for p in data.longhands:
if p.name in ['direction', 'unicode-bidi']:
continue;
if not p.enabled_in_content() and not p.experimental(engine):
continue;
if p.logical:
logical_longhands.append(p.name)
else:
other_longhands.append(p.name)
data.declare_shorthand(
"all",
logical_longhands + other_longhands,
engines="gecko servo-2013 servo-2020",
)
%>
/// The max amount of longhands that the `all` shorthand will ever contain.
pub const ALL_SHORTHAND_MAX_LEN: usize = ${len(logical_longhands + other_longhands)};
}
<%
from itertools import groupby
# After this code, `data.longhands` is sorted in the following order:
# - first all keyword variants and all variants known to be Copy,
# - second all the other variants, such as all variants with the same field
# have consecutive discriminants.
# The variable `variants` contain the same entries as `data.longhands` in
# the same order, but must exist separately to the data source, because
# we then need to add three additional variants `WideKeywordDeclaration`,
# `VariableDeclaration` and `CustomDeclaration`.
variants = []
for property in data.longhands:
variants.append({
"name": property.camel_case,
"type": property.specified_type(),
"doc": "`" + property.name + "`",
"copy": property.specified_is_copy(),
})
groups = {}
keyfunc = lambda x: x["type"]
sortkeys = {}
for ty, group in groupby(sorted(variants, key=keyfunc), keyfunc):
group = list(group)
groups[ty] = group
for v in group:
if len(group) == 1:
sortkeys[v["name"]] = (not v["copy"], 1, v["name"], "")
else:
sortkeys[v["name"]] = (not v["copy"], len(group), ty, v["name"])
variants.sort(key=lambda x: sortkeys[x["name"]])
# It is extremely important to sort the `data.longhands` array here so
# that it is in the same order as `variants`, for `LonghandId` and
# `PropertyDeclarationId` to coincide.
data.longhands.sort(key=lambda x: sortkeys[x.camel_case])
%>
// WARNING: It is *really* important for the variants of `LonghandId`
// and `PropertyDeclaration` to be defined in the exact same order,
// with the exception of `CSSWideKeyword`, `WithVariables` and `Custom`,
// which don't exist in `LonghandId`.
<%
extra = [
{
"name": "CSSWideKeyword",
"type": "WideKeywordDeclaration",
"doc": "A CSS-wide keyword.",
"copy": False,
},
{
"name": "WithVariables",
"type": "VariableDeclaration",
"doc": "An unparsed declaration.",
"copy": False,
},
{
"name": "Custom",
"type": "CustomDeclaration",
"doc": "A custom property declaration.",
"copy": False,
},
]
for v in extra:
variants.append(v)
groups[v["type"]] = [v]
%>
/// Servo's representation for a property declaration.
#[derive(ToShmem)]
#[repr(u16)]
pub enum PropertyDeclaration {
% for variant in variants:
/// ${variant["doc"]}
${variant["name"]}(${variant["type"]}),
% endfor
}
#[repr(C)]
struct PropertyDeclarationVariantRepr<T> {
tag: u16,
value: T
}
impl Clone for PropertyDeclaration {
#[inline]
fn clone(&self) -> Self {
use self::PropertyDeclaration::*;
<%
[copy, others] = [list(g) for _, g in groupby(variants, key=lambda x: not x["copy"])]
%>
let self_tag = unsafe {
(*(self as *const _ as *const PropertyDeclarationVariantRepr<()>)).tag
};
if self_tag <= LonghandId::${copy[-1]["name"]} as u16 {
#[derive(Clone, Copy)]
#[repr(u16)]
enum CopyVariants {
% for v in copy:
_${v["name"]}(${v["type"]}),
% endfor
}
unsafe {
let mut out = mem::MaybeUninit::uninit();
ptr::write(
out.as_mut_ptr() as *mut CopyVariants,
*(self as *const _ as *const CopyVariants),
);
return out.assume_init();
}
}
// This function ensures that all properties not handled above
// do not have a specified value implements Copy. If you hit
// compile error here, you may want to add the type name into
// Longhand.specified_is_copy in data.py.
fn _static_assert_others_are_not_copy() {
struct Helper<T>(T);
trait AssertCopy { fn assert() {} }
trait AssertNotCopy { fn assert() {} }
impl<T: Copy> AssertCopy for Helper<T> {}
% for ty in sorted(set(x["type"] for x in others)):
impl AssertNotCopy for Helper<${ty}> {}
Helper::<${ty}>::assert();
% endfor
}
match *self {
${" |\n".join("{}(..)".format(v["name"]) for v in copy)} => {
unsafe { debug_unreachable!() }
}
% for ty, vs in groupby(others, key=lambda x: x["type"]):
<%
vs = list(vs)
%>
% if len(vs) == 1:
${vs[0]["name"]}(ref value) => {
${vs[0]["name"]}(value.clone())
}
% else:
${" |\n".join("{}(ref value)".format(v["name"]) for v in vs)} => {
unsafe {
let mut out = mem::MaybeUninit::uninit();
ptr::write(
out.as_mut_ptr() as *mut PropertyDeclarationVariantRepr<${ty}>,
PropertyDeclarationVariantRepr {
tag: *(self as *const _ as *const u16),
value: value.clone(),
},
);
out.assume_init()
}
}
% endif
% endfor
}
}
}
impl PartialEq for PropertyDeclaration {
#[inline]
fn eq(&self, other: &Self) -> bool {
use self::PropertyDeclaration::*;
unsafe {
let this_repr =
&*(self as *const _ as *const PropertyDeclarationVariantRepr<()>);
let other_repr =
&*(other as *const _ as *const PropertyDeclarationVariantRepr<()>);
if this_repr.tag != other_repr.tag {
return false;
}
match *self {
% for ty, vs in groupby(variants, key=lambda x: x["type"]):
${" |\n".join("{}(ref this)".format(v["name"]) for v in vs)} => {
let other_repr =
&*(other as *const _ as *const PropertyDeclarationVariantRepr<${ty}>);
*this == other_repr.value
}
% endfor
}
}
}
}
impl MallocSizeOf for PropertyDeclaration {
#[inline]
fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize {
use self::PropertyDeclaration::*;
match *self {
% for ty, vs in groupby(variants, key=lambda x: x["type"]):
${" | ".join("{}(ref value)".format(v["name"]) for v in vs)} => {
value.size_of(ops)
}
% endfor
}
}
}
impl PropertyDeclaration {
/// 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 {
use self::PropertyDeclaration::*;
let mut dest = CssWriter::new(dest);
match *self {
% for ty, vs in groupby(variants, key=lambda x: x["type"]):
${" | ".join("{}(ref value)".format(v["name"]) for v in vs)} => {
value.to_css(&mut dest)
}
% endfor
}
}
}
/// A module with all the code related to animated properties.
///
/// This needs to be "included" by mako at least after all longhand modules,
/// given they populate the global data.
pub mod animated_properties {
<%include file="/helpers/animated_properties.mako.rs" />
}
/// A longhand or shorthand property.
#[derive(Clone, Copy, Debug)]
pub struct NonCustomPropertyId(usize);
/// The length of all the non-custom properties.
pub const NON_CUSTOM_PROPERTY_ID_COUNT: usize =
${len(data.longhands) + len(data.shorthands) + len(data.all_aliases())};
/// The length of all counted unknown properties.
pub const COUNTED_UNKNOWN_PROPERTY_COUNT: usize = ${len(data.counted_unknown_properties)};
% if engine == "gecko":
#[allow(dead_code)]
unsafe fn static_assert_nscsspropertyid() {
% for i, property in enumerate(data.longhands + data.shorthands + data.all_aliases()):
std::mem::transmute::<[u8; ${i}], [u8; ${property.nscsspropertyid()} as usize]>([0; ${i}]); // ${property.name}
% endfor
}
% endif
impl NonCustomPropertyId {
/// Returns the underlying index, used for use counter.
pub fn bit(self) -> usize {
self.0
}
#[cfg(feature = "gecko")]
#[inline]
fn to_nscsspropertyid(self) -> nsCSSPropertyID {
// unsafe: guaranteed by static_assert_nscsspropertyid above.
unsafe { std::mem::transmute(self.0 as i32) }
}
/// Convert an `nsCSSPropertyID` into a `NonCustomPropertyId`.
#[cfg(feature = "gecko")]
#[inline]
pub fn from_nscsspropertyid(prop: nsCSSPropertyID) -> Result<Self, ()> {
let prop = prop as i32;
if prop < 0 {
return Err(());
}
if prop >= NON_CUSTOM_PROPERTY_ID_COUNT as i32 {
return Err(());
}
// unsafe: guaranteed by static_assert_nscsspropertyid above.
Ok(unsafe { std::mem::transmute(prop as usize) })
}
/// Get the property name.
#[inline]
pub fn name(self) -> &'static str {
static MAP: [&'static str; NON_CUSTOM_PROPERTY_ID_COUNT] = [
% for property in data.longhands + data.shorthands + data.all_aliases():
"${property.name}",
% endfor
];
MAP[self.0]
}
/// Returns whether this property is transitionable.
#[inline]
pub fn is_transitionable(self) -> bool {
${static_non_custom_property_id_set("TRANSITIONABLE", lambda p: p.transitionable)}
TRANSITIONABLE.contains(self)
}
/// Returns whether this property is animatable.
#[inline]
pub fn is_animatable(self) -> bool {
${static_non_custom_property_id_set("ANIMATABLE", lambda p: p.animatable)}
ANIMATABLE.contains(self)
}
#[inline]
fn enabled_for_all_content(self) -> bool {
${static_non_custom_property_id_set(
"EXPERIMENTAL",
lambda p: p.experimental(engine)
)}
${static_non_custom_property_id_set(
"ALWAYS_ENABLED",
lambda p: (not p.experimental(engine)) and p.enabled_in_content()
)}
let passes_pref_check = || {
% if engine == "gecko":
unsafe { structs::nsCSSProps_gPropertyEnabled[self.0] }
% else:
static PREF_NAME: [Option< &str>; ${
len(data.longhands) + len(data.shorthands) + len(data.all_aliases())
}] = [
% for property in data.longhands + data.shorthands + data.all_aliases():
<%
attrs = {"servo-2013": "servo_2013_pref", "servo-2020": "servo_2020_pref"}
pref = getattr(property, attrs[engine])
%>
% if pref:
Some("${pref}"),
% else:
None,
% endif
% endfor
];
let pref = match PREF_NAME[self.0] {
None => return true,
Some(pref) => pref,
};
prefs::pref_map().get(pref).as_bool().unwrap_or(false)
% endif
};
if ALWAYS_ENABLED.contains(self) {
return true
}
if EXPERIMENTAL.contains(self) && passes_pref_check() {
return true
}
false
}
fn allowed_in(self, context: &ParserContext) -> bool {
debug_assert!(
matches!(
context.rule_type(),
CssRuleType::Keyframe | CssRuleType::Page | CssRuleType::Style
),
"Declarations are only expected inside a keyframe, page, or style rule."
);
${static_non_custom_property_id_set(
"DISALLOWED_IN_KEYFRAME_BLOCK",
lambda p: not p.allowed_in_keyframe_block
)}
${static_non_custom_property_id_set(
"DISALLOWED_IN_PAGE_RULE",
lambda p: not p.allowed_in_page_rule
)}
match context.rule_type() {
CssRuleType::Keyframe if DISALLOWED_IN_KEYFRAME_BLOCK.contains(self) => {
return false;
}
CssRuleType::Page if DISALLOWED_IN_PAGE_RULE.contains(self) => {
return false;
}
_ => {}
}
self.allowed_in_ignoring_rule_type(context)
}
fn allowed_in_ignoring_rule_type(self, context: &ParserContext) -> bool {
// The semantics of these are kinda hard to reason about, what follows
// is a description of the different combinations that can happen with
// these three sets.
//
// Experimental properties are generally controlled by prefs, but an
// experimental property explicitly enabled in certain context (UA or
// chrome sheets) is always usable in the context regardless of the
// pref value.
//
// Non-experimental properties are either normal properties which are
// usable everywhere, or internal-only properties which are only usable
// in certain context they are explicitly enabled in.
if self.enabled_for_all_content() {
return true;
}
${static_non_custom_property_id_set(
"ENABLED_IN_UA_SHEETS",
lambda p: p.explicitly_enabled_in_ua_sheets()
)}
${static_non_custom_property_id_set(
"ENABLED_IN_CHROME",
lambda p: p.explicitly_enabled_in_chrome()
)}
if context.stylesheet_origin == Origin::UserAgent &&
ENABLED_IN_UA_SHEETS.contains(self)
{
return true
}
if context.chrome_rules_enabled() && ENABLED_IN_CHROME.contains(self) {
return true
}
false
}
/// The supported types of this property. The return value should be
/// style_traits::CssType when it can become a bitflags type.
fn supported_types(&self) -> u8 {
const SUPPORTED_TYPES: [u8; ${len(data.longhands) + len(data.shorthands)}] = [
% for prop in data.longhands:
<${prop.specified_type()} as SpecifiedValueInfo>::SUPPORTED_TYPES,
% endfor
% for prop in data.shorthands:
% if prop.name == "all":
0, // 'all' accepts no value other than CSS-wide keywords
% else:
<shorthands::${prop.ident}::Longhands as SpecifiedValueInfo>::SUPPORTED_TYPES,
% endif
% endfor
];
SUPPORTED_TYPES[self.0]
}
/// See PropertyId::collect_property_completion_keywords.
fn collect_property_completion_keywords(&self, f: KeywordsCollectFn) {
fn do_nothing(_: KeywordsCollectFn) {}
const COLLECT_FUNCTIONS: [fn(KeywordsCollectFn);
${len(data.longhands) + len(data.shorthands)}] = [
% for prop in data.longhands:
<${prop.specified_type()} as SpecifiedValueInfo>::collect_completion_keywords,
% endfor
% for prop in data.shorthands:
% if prop.name == "all":
do_nothing, // 'all' accepts no value other than CSS-wide keywords
% else:
<shorthands::${prop.ident}::Longhands as SpecifiedValueInfo>::
collect_completion_keywords,
% endif
% endfor
];
COLLECT_FUNCTIONS[self.0](f);
}
/// Turns this `NonCustomPropertyId` into a `PropertyId`.
#[inline]
pub fn to_property_id(self) -> PropertyId {
use std::mem::transmute;
if self.0 < ${len(data.longhands)} {
return unsafe {
PropertyId::Longhand(transmute(self.0 as u16))
}
}
if self.0 < ${len(data.longhands) + len(data.shorthands)} {
return unsafe {
PropertyId::Shorthand(transmute((self.0 - ${len(data.longhands)}) as u16))
}
}
assert!(self.0 < NON_CUSTOM_PROPERTY_ID_COUNT);
let alias_id: AliasId = unsafe {
transmute((self.0 - ${len(data.longhands) + len(data.shorthands)}) as u16)
};
match alias_id.aliased_property() {
AliasedPropertyId::Longhand(longhand) => PropertyId::LonghandAlias(longhand, alias_id),
AliasedPropertyId::Shorthand(shorthand) => PropertyId::ShorthandAlias(shorthand, alias_id),
}
}
}
impl From<LonghandId> for NonCustomPropertyId {
#[inline]
fn from(id: LonghandId) -> Self {
NonCustomPropertyId(id as usize)
}
}
impl From<ShorthandId> for NonCustomPropertyId {
#[inline]
fn from(id: ShorthandId) -> Self {
NonCustomPropertyId((id as usize) + ${len(data.longhands)})
}
}
impl From<AliasId> for NonCustomPropertyId {
#[inline]
fn from(id: AliasId) -> Self {
NonCustomPropertyId(id as usize + ${len(data.longhands) + len(data.shorthands)})
}
}
/// A set of all properties
#[derive(Clone, PartialEq)]
pub struct NonCustomPropertyIdSet {
storage: [u32; (NON_CUSTOM_PROPERTY_ID_COUNT - 1 + 32) / 32]
}
impl NonCustomPropertyIdSet {
/// Creates an empty `NonCustomPropertyIdSet`.
pub fn new() -> Self {
Self {
storage: Default::default(),
}
}
/// Insert a non-custom-property in the set.
#[inline]
pub fn insert(&mut self, id: NonCustomPropertyId) {
let bit = id.0;
self.storage[bit / 32] |= 1 << (bit % 32);
}
/// Return whether the given property is in the set
#[inline]
pub fn contains(&self, id: NonCustomPropertyId) -> bool {
let bit = id.0;
(self.storage[bit / 32] & (1 << (bit % 32))) != 0
}
}
<%def name="static_non_custom_property_id_set(name, is_member)">
static ${name}: NonCustomPropertyIdSet = NonCustomPropertyIdSet {
<%
storage = [0] * int((len(data.longhands) + len(data.shorthands) + len(data.all_aliases()) - 1 + 32) / 32)
for i, property in enumerate(data.longhands + data.shorthands + data.all_aliases()):
if is_member(property):
storage[int(i / 32)] |= 1 << (i % 32)
%>
storage: [${", ".join("0x%x" % word for word in storage)}]
};
</%def>
<%def name="static_longhand_id_set(name, is_member)">
static ${name}: LonghandIdSet = LonghandIdSet {
<%
storage = [0] * int((len(data.longhands) - 1 + 32) / 32)
for i, property in enumerate(data.longhands):
if is_member(property):
storage[int(i / 32)] |= 1 << (i % 32)
%>
storage: [${", ".join("0x%x" % word for word in storage)}]
};
</%def>
<%
logical_groups = defaultdict(list)
for prop in data.longhands:
if prop.logical_group:
logical_groups[prop.logical_group].append(prop)
for group, props in logical_groups.items():
logical_count = sum(1 for p in props if p.logical)
if logical_count * 2 != len(props):
raise RuntimeError("Logical group {} has ".format(group) +
"unbalanced logical / physical properties")
FIRST_LINE_RESTRICTIONS = PropertyRestrictions.first_line(data)
FIRST_LETTER_RESTRICTIONS = PropertyRestrictions.first_letter(data)
MARKER_RESTRICTIONS = PropertyRestrictions.marker(data)
PLACEHOLDER_RESTRICTIONS = PropertyRestrictions.placeholder(data)
CUE_RESTRICTIONS = PropertyRestrictions.cue(data)
def restriction_flags(property):
name = property.name
flags = []
if name in FIRST_LINE_RESTRICTIONS:
flags.append("APPLIES_TO_FIRST_LINE")
if name in FIRST_LETTER_RESTRICTIONS:
flags.append("APPLIES_TO_FIRST_LETTER")
if name in PLACEHOLDER_RESTRICTIONS:
flags.append("APPLIES_TO_PLACEHOLDER")
if name in MARKER_RESTRICTIONS:
flags.append("APPLIES_TO_MARKER")
if name in CUE_RESTRICTIONS:
flags.append("APPLIES_TO_CUE")
return flags
%>
/// A group for properties which may override each other
/// via logical resolution.
#[derive(Clone, Copy, Eq, Hash, PartialEq)]
pub enum LogicalGroup {
% for group in sorted(logical_groups.keys()):
/// ${group}
${to_camel_case(group)},
% endfor
}
/// A set of longhand properties
#[derive(Clone, Copy, Debug, Default, MallocSizeOf, PartialEq)]
pub struct LonghandIdSet {
storage: [u32; (${len(data.longhands)} - 1 + 32) / 32]
}
impl_trivial_to_shmem!(LonghandIdSet);
/// An iterator over a set of longhand ids.
pub struct LonghandIdSetIterator<'a> {
longhands: &'a LonghandIdSet,
cur: usize,
}
impl<'a> Iterator for LonghandIdSetIterator<'a> {
type Item = LonghandId;
fn next(&mut self) -> Option<Self::Item> {
use std::mem;
loop {
if self.cur >= ${len(data.longhands)} {
return None;
}
let id: LonghandId = unsafe { mem::transmute(self.cur as u16) };
self.cur += 1;
if self.longhands.contains(id) {
return Some(id);
}
}
}
}
impl LonghandIdSet {
#[inline]
fn reset() -> &'static Self {
${static_longhand_id_set("RESET", lambda p: not p.style_struct.inherited)}
&RESET
}
#[inline]
fn animatable() -> &'static Self {
${static_longhand_id_set("ANIMATABLE", lambda p: p.animatable)}
&ANIMATABLE
}
#[inline]
fn discrete_animatable() -> &'static Self {
${static_longhand_id_set("DISCRETE_ANIMATABLE", lambda p: p.animation_value_type == "discrete")}
&DISCRETE_ANIMATABLE
}
#[inline]
fn logical() -> &'static Self {
${static_longhand_id_set("LOGICAL", lambda p: p.logical)}
&LOGICAL
}
/// Returns the set of longhands that are ignored when document colors are
/// disabled.
#[inline]
pub fn ignored_when_colors_disabled() -> &'static Self {
${static_longhand_id_set(
"IGNORED_WHEN_COLORS_DISABLED",
lambda p: p.ignored_when_colors_disabled
)}
&IGNORED_WHEN_COLORS_DISABLED
}
/// Returns the set of properties that are declared as having no effect on
/// Gecko <scrollbar> elements or their descendant scrollbar parts.
#[cfg(debug_assertions)]
#[cfg(feature = "gecko")]
#[inline]
pub fn has_no_effect_on_gecko_scrollbars() -> &'static Self {
// data.py asserts that has_no_effect_on_gecko_scrollbars is True or
// False for properties that are inherited and Gecko pref controlled,
// and is None for all other properties.
${static_longhand_id_set(
"HAS_NO_EFFECT_ON_SCROLLBARS",
lambda p: p.has_effect_on_gecko_scrollbars is False
)}
&HAS_NO_EFFECT_ON_SCROLLBARS
}
/// Returns the set of padding properties for the purpose of disabling
/// native appearance.
#[inline]
pub fn padding_properties() -> &'static Self {
<% assert "padding" in logical_groups %>
${static_longhand_id_set(
"PADDING_PROPERTIES",
lambda p: p.logical_group == "padding"
)}
&PADDING_PROPERTIES
}
/// Returns the set of border properties for the purpose of disabling native
/// appearance.
#[inline]
pub fn border_background_properties() -> &'static Self {
${static_longhand_id_set(
"BORDER_BACKGROUND_PROPERTIES",
lambda p: (p.logical_group and p.logical_group.startswith("border")) or \
p.name in ["background-color", "background-image"]
)}
&BORDER_BACKGROUND_PROPERTIES
}
/// Iterate over the current longhand id set.
pub fn iter(&self) -> LonghandIdSetIterator {
LonghandIdSetIterator { longhands: self, cur: 0, }
}
/// Returns whether this set contains at least every longhand that `other`
/// also contains.
pub fn contains_all(&self, other: &Self) -> bool {
for (self_cell, other_cell) in self.storage.iter().zip(other.storage.iter()) {
if (*self_cell & *other_cell) != *other_cell {
return false;
}
}
true
}
/// Returns whether this set contains any longhand that `other` also contains.
pub fn contains_any(&self, other: &Self) -> bool {
for (self_cell, other_cell) in self.storage.iter().zip(other.storage.iter()) {
if (*self_cell & *other_cell) != 0 {
return true;
}
}
false
}
/// Remove all the given properties from the set.
#[inline]
pub fn remove_all(&mut self, other: &Self) {
for (self_cell, other_cell) in self.storage.iter_mut().zip(other.storage.iter()) {
*self_cell &= !*other_cell;
}
}
/// Create an empty set
#[inline]
pub fn new() -> LonghandIdSet {
LonghandIdSet { storage: [0; (${len(data.longhands)} - 1 + 32) / 32] }
}
/// Return whether the given property is in the set
#[inline]
pub fn contains(&self, id: LonghandId) -> bool {
let bit = id as usize;
(self.storage[bit / 32] & (1 << (bit % 32))) != 0
}
/// Return whether this set contains any reset longhand.
#[inline]
pub fn contains_any_reset(&self) -> bool {
self.contains_any(Self::reset())
}
/// Add the given property to the set
#[inline]
pub fn insert(&mut self, id: LonghandId) {
let bit = id as usize;
self.storage[bit / 32] |= 1 << (bit % 32);
}
/// Remove the given property from the set
#[inline]
pub fn remove(&mut self, id: LonghandId) {
let bit = id as usize;
self.storage[bit / 32] &= !(1 << (bit % 32));
}
/// Clear all bits
#[inline]
pub fn clear(&mut self) {
for cell in &mut self.storage {
*cell = 0
}
}
/// Returns whether the set is empty.
#[inline]
pub fn is_empty(&self) -> bool {
self.storage.iter().all(|c| *c == 0)
}
}
/// An enum to represent a CSS Wide keyword.
#[derive(Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq, SpecifiedValueInfo,
ToCss, ToShmem)]
pub enum CSSWideKeyword {
/// The `initial` keyword.
Initial,
/// The `inherit` keyword.
Inherit,
/// The `unset` keyword.
Unset,
/// The `revert` keyword.
Revert,
}
impl CSSWideKeyword {
fn to_str(&self) -> &'static str {
match *self {
CSSWideKeyword::Initial => "initial",
CSSWideKeyword::Inherit => "inherit",
CSSWideKeyword::Unset => "unset",
CSSWideKeyword::Revert => "revert",
}
}
}
impl CSSWideKeyword {
fn parse(input: &mut Parser) -> Result<Self, ()> {
let keyword = {
let ident = input.expect_ident().map_err(|_| ())?;
match_ignore_ascii_case! { ident,
// If modifying this set of keyword, also update values::CustomIdent::from_ident
"initial" => CSSWideKeyword::Initial,
"inherit" => CSSWideKeyword::Inherit,
"unset" => CSSWideKeyword::Unset,
"revert" => CSSWideKeyword::Revert,
_ => return Err(()),
}
};
input.expect_exhausted().map_err(|_| ())?;
Ok(keyword)
}
}
bitflags! {
/// A set of flags for properties.
pub struct PropertyFlags: u16 {
/// This property requires a stacking context.
const CREATES_STACKING_CONTEXT = 1 << 0;
/// This property has values that can establish a containing block for
/// fixed positioned and absolutely positioned elements.
const FIXPOS_CB = 1 << 1;
/// This property has values that can establish a containing block for
/// absolutely positioned elements.
const ABSPOS_CB = 1 << 2;
/// This longhand property applies to ::first-letter.
const APPLIES_TO_FIRST_LETTER = 1 << 3;
/// This longhand property applies to ::first-line.
const APPLIES_TO_FIRST_LINE = 1 << 4;
/// This longhand property applies to ::placeholder.
const APPLIES_TO_PLACEHOLDER = 1 << 5;
/// This longhand property applies to ::cue.
const APPLIES_TO_CUE = 1 << 6;
/// This longhand property applies to ::marker.
const APPLIES_TO_MARKER = 1 << 7;
/// This property is a legacy shorthand.
///
const IS_LEGACY_SHORTHAND = 1 << 8;
/* The following flags are currently not used in Rust code, they
* only need to be listed in corresponding properties so that
* they can be checked in the C++ side via ServoCSSPropList.h. */
/// This property can be animated on the compositor.
const CAN_ANIMATE_ON_COMPOSITOR = 0;
/// This shorthand property is accessible from getComputedStyle.
const SHORTHAND_IN_GETCS = 0;
}
}
/// An identifier for a given longhand property.
#[derive(Clone, Copy, Eq, Hash, MallocSizeOf, PartialEq, ToComputedValue, ToResolvedValue, ToShmem)]
#[repr(u16)]
pub enum LonghandId {
% for i, property in enumerate(data.longhands):
/// ${property.name}
${property.camel_case} = ${i},
% endfor
}
impl ToCss for LonghandId {
#[inline]
fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
where
W: Write,
{
dest.write_str(self.name())
}
}
impl fmt::Debug for LonghandId {
fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str(self.name())
}
}
impl LonghandId {
/// Get the name of this longhand property.
#[inline]
pub fn name(&self) -> &'static str {
NonCustomPropertyId::from(*self).name()
}
/// Returns whether the longhand property is inherited by default.
#[inline]
pub fn inherited(self) -> bool {
!LonghandIdSet::reset().contains(self)
}
fn shorthands(&self) -> NonCustomPropertyIterator<ShorthandId> {
// first generate longhand to shorthands lookup map
//
// NOTE(emilio): This currently doesn't exclude the "all" shorthand. It
// could potentially do so, which would speed up serialization
// algorithms and what not, I guess.
<%
from functools import cmp_to_key
longhand_to_shorthand_map = {}
num_sub_properties = {}
for shorthand in data.shorthands:
num_sub_properties[shorthand.camel_case] = len(shorthand.sub_properties)
for sub_property in shorthand.sub_properties:
if sub_property.ident not in longhand_to_shorthand_map:
longhand_to_shorthand_map[sub_property.ident] = []
longhand_to_shorthand_map[sub_property.ident].append(shorthand.camel_case)
def cmp(a, b):
return (a > b) - (a < b)
def preferred_order(x, y):
# Since we want properties in order from most subproperties to least,
# reverse the arguments to cmp from the expected order.
result = cmp(num_sub_properties.get(y, 0), num_sub_properties.get(x, 0))
if result:
return result
# Fall back to lexicographic comparison.
return cmp(x, y)
# Sort the lists of shorthand properties according to preferred order:
for shorthand_list in longhand_to_shorthand_map.values():
shorthand_list.sort(key=cmp_to_key(preferred_order))
%>
// based on lookup results for each longhand, create result arrays
% for property in data.longhands:
static ${property.ident.upper()}: &'static [ShorthandId] = &[
% for shorthand in longhand_to_shorthand_map.get(property.ident, []):
ShorthandId::${shorthand},
% endfor
];
% endfor
NonCustomPropertyIterator {
filter: NonCustomPropertyId::from(*self).enabled_for_all_content(),
iter: match *self {
% for property in data.longhands:
LonghandId::${property.camel_case} => ${property.ident.upper()},
% endfor
}.iter(),
}
}
// TODO(emilio): Should we use a function table like CASCADE_PROPERTY does
// to avoid blowing up code-size here?
fn parse_value<'i, 't>(
&self,
context: &ParserContext,
input: &mut Parser<'i, 't>,
) -> Result<PropertyDeclaration, ParseError<'i>> {
match *self {
% for property in data.longhands:
LonghandId::${property.camel_case} => {
longhands::${property.ident}::parse_declared(context, input)
}
% endfor
}
}
/// Returns whether this property is animatable.
#[inline]
pub fn is_animatable(self) -> bool {
LonghandIdSet::animatable().contains(self)
}
/// Returns whether this property is animatable in a discrete way.
#[inline]
pub fn is_discrete_animatable(self) -> bool {
LonghandIdSet::discrete_animatable().contains(self)
}
/// Converts from a LonghandId to an adequate nsCSSPropertyID.
#[cfg(feature = "gecko")]
#[inline]
pub fn to_nscsspropertyid(self) -> nsCSSPropertyID {
NonCustomPropertyId::from(self).to_nscsspropertyid()
}
#[cfg(feature = "gecko")]
#[allow(non_upper_case_globals)]
/// Returns a longhand id from Gecko's nsCSSPropertyID.
pub fn from_nscsspropertyid(id: nsCSSPropertyID) -> Result<Self, ()> {
match PropertyId::from_nscsspropertyid(id) {
Ok(PropertyId::Longhand(id)) |
Ok(PropertyId::LonghandAlias(id, _)) => Ok(id),
_ => Err(()),
}
}
/// Return whether this property is logical.
#[inline]
pub fn is_logical(self) -> bool {
LonghandIdSet::logical().contains(self)
}
/// If this is a logical property, return the corresponding physical one in
/// the given writing mode.
///
/// Otherwise, return unchanged.
#[inline]
pub fn to_physical(&self, wm: WritingMode) -> Self {
match *self {
% for property in data.longhands:
% if property.logical:
<% logical_group = property.logical_group %>
LonghandId::${property.camel_case} => {
<%helpers:logical_setter_helper name="${property.name}">
<%def name="inner(physical_ident)">
<%
physical_name = physical_ident.replace("_", "-")
physical_property = data.longhands_by_name[physical_name]
assert logical_group == physical_property.logical_group
%>
LonghandId::${to_camel_case(physical_ident)}
</%def>
</%helpers:logical_setter_helper>
}
% endif
% endfor
_ => *self
}
}
/// Return the logical group of this longhand property.
pub fn logical_group(&self) -> Option<LogicalGroup> {
const LOGICAL_GROUPS: [Option<LogicalGroup>; ${len(data.longhands)}] = [
% for prop in data.longhands:
% if prop.logical_group:
Some(LogicalGroup::${to_camel_case(prop.logical_group)}),
% else:
None,
% endif
% endfor
];
LOGICAL_GROUPS[*self as usize]
}
/// Returns PropertyFlags for given longhand property.
#[inline(always)]
pub fn flags(self) -> PropertyFlags {
// TODO(emilio): This can be simplified further as Rust gains more
// constant expression support.
const FLAGS: [u16; ${len(data.longhands)}] = [
% for property in data.longhands:
% for flag in property.flags + restriction_flags(property):
PropertyFlags::${flag}.bits |
% endfor
0,
% endfor
];
PropertyFlags::from_bits_truncate(FLAGS[self as usize])
}
/// Only a few properties are allowed to depend on the visited state of
/// links. When cascading visited styles, we can save time by only
/// processing these properties.
fn is_visited_dependent(&self) -> bool {
matches!(*self,
% if engine == "gecko":
LonghandId::ColumnRuleColor |
LonghandId::TextEmphasisColor |
LonghandId::WebkitTextFillColor |
LonghandId::WebkitTextStrokeColor |
LonghandId::TextDecorationColor |
LonghandId::Fill |
LonghandId::Stroke |
LonghandId::CaretColor |
% endif
LonghandId::BackgroundColor |
LonghandId::BorderTopColor |
LonghandId::BorderRightColor |
LonghandId::BorderBottomColor |
LonghandId::BorderLeftColor |
% if engine in ["gecko", "servo-2013"]:
LonghandId::OutlineColor |
% endif
LonghandId::Color
)
}
/// Returns true if the property is one that is ignored when document
/// colors are disabled.
#[inline]
fn ignored_when_document_colors_disabled(self) -> bool {
LonghandIdSet::ignored_when_colors_disabled().contains(self)
}
/// The computed value of some properties depends on the (sometimes
/// computed) value of *other* properties.
///
/// So we classify properties into "early" and "other", such that the only
/// dependencies can be from "other" to "early".
///
/// Unfortunately, it’s not easy to check that this classification is
/// correct.
fn is_early_property(&self) -> bool {
matches!(*self,
% if engine == "gecko":
// Needed to properly compute the writing mode, to resolve logical
// properties, and similar stuff. In this block instead of along
// `WritingMode` and `Direction` just for convenience, since it's
// Gecko-only (for now at least).
//
// see WritingMode::new.
LonghandId::TextOrientation |
// Needed to properly compute the zoomed font-size.
//
// FIXME(emilio): This could probably just be a cascade flag like
// IN_SVG_SUBTREE or such, and we could nuke this property.
LonghandId::XTextZoom |
// Needed to do font-size computation in a language-dependent way.
LonghandId::XLang |
// Needed for ruby to respect language-dependent min-font-size
// preferences properly, see bug 1165538.
LonghandId::MozMinFontSizeRatio |
// Needed to do font-size for MathML. :(
LonghandId::MozScriptLevel |
% endif
// Needed to compute the first available font, in order to
// compute font-relative units correctly.
LonghandId::FontSize |
LonghandId::FontWeight |
LonghandId::FontStretch |
LonghandId::FontStyle |
LonghandId::FontFamily |
// Needed to properly compute the writing mode, to resolve logical
// properties, and similar stuff.
LonghandId::WritingMode |
LonghandId::Direction
)
}
}
/// An iterator over all the property ids that are enabled for a given
/// shorthand, if that shorthand is enabled for all content too.
pub struct NonCustomPropertyIterator<Item: 'static> {
filter: bool,
iter: std::slice::Iter<'static, Item>,
}
impl<Item> Iterator for NonCustomPropertyIterator<Item>
where
Item: 'static + Copy + Into<NonCustomPropertyId>,
{
type Item = Item;
fn next(&mut self) -> Option<Self::Item> {
loop {
let id = *self.iter.next()?;
if !self.filter || id.into().enabled_for_all_content() {
return Some(id)
}
}
}
}
/// An identifier for a given shorthand property.
#[derive(Clone, Copy, Debug, Eq, Hash, MallocSizeOf, PartialEq, ToComputedValue, ToResolvedValue, ToShmem)]
#[repr(u16)]
pub enum ShorthandId {
% for i, property in enumerate(data.shorthands):
/// ${property.name}
${property.camel_case} = ${i},
% endfor
}
impl ToCss for ShorthandId {
#[inline]
fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
where
W: Write,
{
dest.write_str(self.name())
}
}
impl ShorthandId {
/// Get the name for this shorthand property.
#[inline]
pub fn name(&self) -> &'static str {
NonCustomPropertyId::from(*self).name()
}
/// Converts from a ShorthandId to an adequate nsCSSPropertyID.
#[cfg(feature = "gecko")]
#[inline]
pub fn to_nscsspropertyid(self) -> nsCSSPropertyID {
NonCustomPropertyId::from(self).to_nscsspropertyid()
}
/// Converts from a nsCSSPropertyID to a ShorthandId.
#[cfg(feature = "gecko")]
#[inline]
pub fn from_nscsspropertyid(prop: nsCSSPropertyID) -> Result<Self, ()> {
PropertyId::from_nscsspropertyid(prop)?.as_shorthand().map_err(|_| ())
}
/// Get the longhand ids that form this shorthand.
pub fn longhands(&self) -> NonCustomPropertyIterator<LonghandId> {
% for property in data.shorthands:
static ${property.ident.upper()}: &'static [LonghandId] = &[
% for sub in property.sub_properties:
LonghandId::${sub.camel_case},
% endfor
];
% endfor
NonCustomPropertyIterator {
filter: NonCustomPropertyId::from(*self).enabled_for_all_content(),
iter: match *self {
% for property in data.shorthands:
ShorthandId::${property.camel_case} => ${property.ident.upper()},
% endfor
}.iter()
}
}
/// Try to serialize the given declarations as this shorthand.
///
/// Returns an error if writing to the stream fails, or if the declarations
/// do not map to a shorthand.
pub fn longhands_to_css<'a, W, I>(
&self,
declarations: I,
dest: &mut CssWriter<W>,
) -> fmt::Result
where
W: Write,
I: Iterator<Item=&'a PropertyDeclaration>,
{
match *self {
ShorthandId::All => {
// No need to try to serialize the declarations as the 'all'
// shorthand, since it only accepts CSS-wide keywords (and
// variable references), which will be handled in
// get_shorthand_appendable_value.
Err(fmt::Error)
}
% for property in data.shorthands_except_all():
ShorthandId::${property.camel_case} => {
match shorthands::${property.ident}::LonghandsToSerialize::from_iter(declarations) {
Ok(longhands) => longhands.to_css(dest),
Err(_) => Err(fmt::Error)
}
},
% endfor
}
}
/// Finds and returns an appendable value for the given declarations.
///
/// Returns the optional appendable value.
pub fn get_shorthand_appendable_value<'a, I>(
self,
declarations: I,
) -> Option<AppendableValue<'a, I::IntoIter>>
where
I: IntoIterator<Item=&'a PropertyDeclaration>,
I::IntoIter: Clone,
{
let declarations = declarations.into_iter();
// Only cloning iterators (a few pointers each) not declarations.
let mut declarations2 = declarations.clone();
let mut declarations3 = declarations.clone();
let first_declaration = declarations2.next()?;
if let Some(css) = first_declaration.with_variables_from_shorthand(self) {
if declarations2.all(|d| d.with_variables_from_shorthand(self) == Some(css)) {
return Some(AppendableValue::Css {
css: CssStringBorrow::from(css),
with_variables: true,
});
}
return None;
}
// Check whether they are all the same CSS-wide keyword.
if let Some(keyword) = first_declaration.get_css_wide_keyword() {
if declarations2.all(|d| d.get_css_wide_keyword() == Some(keyword)) {
return Some(AppendableValue::Css {
css: CssStringBorrow::from(keyword.to_str()),
with_variables: false,
});
}
return None;
}
// Check whether all declarations can be serialized as part of shorthand.
if declarations3.all(|d| d.may_serialize_as_part_of_shorthand()) {
return Some(AppendableValue::DeclarationsForShorthand(self, declarations));
}
None
}
/// Returns PropertyFlags for the given shorthand property.
#[inline]
pub fn flags(self) -> PropertyFlags {
const FLAGS: [u16; ${len(data.shorthands)}] = [
% for property in data.shorthands:
% for flag in property.flags:
PropertyFlags::${flag}.bits |
% endfor
0,
% endfor
];
PropertyFlags::from_bits_truncate(FLAGS[self as usize])
}
/// Returns whether this property is a legacy shorthand.
#[inline]
pub fn is_legacy_shorthand(self) -> bool {
self.flags().contains(PropertyFlags::IS_LEGACY_SHORTHAND)
}
/// Returns the order in which this property appears relative to other
/// shorthands in idl-name-sorting order.
#[inline]
pub fn idl_name_sort_order(self) -> u32 {
<%
from data import to_idl_name
ordered = {}
sorted_shorthands = sorted(data.shorthands, key=lambda p: to_idl_name(p.ident))
for order, shorthand in enumerate(sorted_shorthands):
ordered[shorthand.ident] = order
%>
static IDL_NAME_SORT_ORDER: [u32; ${len(data.shorthands)}] = [
% for property in data.shorthands:
${ordered[property.ident]},
% endfor
];
IDL_NAME_SORT_ORDER[self as usize]
}
fn parse_into<'i, 't>(
&self,
declarations: &mut SourcePropertyDeclaration,
context: &ParserContext,
input: &mut Parser<'i, 't>,
) -> Result<(), ParseError<'i>> {
match *self {
% for shorthand in data.shorthands_except_all():
ShorthandId::${shorthand.camel_case} => {
shorthands::${shorthand.ident}::parse_into(declarations, context, input)
}
% endfor
// 'all' accepts no value other than CSS-wide keywords
ShorthandId::All => Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError))
}
}
}
/// An unparsed property value that contains `var()` functions.
#[derive(Debug, Eq, PartialEq, ToShmem)]
pub struct UnparsedValue {
/// The css serialization for this value.
css: String,
/// The first token type for this serialization.
first_token_type: TokenSerializationType,
/// The url data for resolving url values.
url_data: UrlExtraData,
/// The shorthand this came from.
from_shorthand: Option<ShorthandId>,
}
impl ToCss for UnparsedValue {
fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
where
W: Write,
{
if self.from_shorthand.is_none() {
dest.write_str(&*self.css)?;
}
Ok(())
}
}
impl UnparsedValue {
fn substitute_variables(
&self,
longhand_id: LonghandId,
custom_properties: Option<<&Arc<crate::custom_properties::CustomPropertiesMap>>,
quirks_mode: QuirksMode,
device: &Device,
) -> PropertyDeclaration {
let invalid_at_computed_value_time = || {
let keyword = if longhand_id.inherited() {
CSSWideKeyword::Inherit
} else {
CSSWideKeyword::Initial
};
PropertyDeclaration::css_wide_keyword(longhand_id, keyword)
};
let css = match crate::custom_properties::substitute(
&self.css,
self.first_token_type,
custom_properties,
device,
) {
Ok(css) => css,
Err(..) => return invalid_at_computed_value_time(),
};
// As of this writing, only the base URL is used for property
// values.
//
// NOTE(emilio): we intentionally pase `None` as the rule type here.
// If something starts depending on it, it's probably a bug, since
// it'd change how values are parsed depending on whether we're in a
// @keyframes rule or not, for example... So think twice about
// whether you want to do this!
//
// FIXME(emilio): ParsingMode is slightly fishy...
let context = ParserContext::new(
Origin::Author,
&self.url_data,
None,
ParsingMode::DEFAULT,
quirks_mode,
None,
None,
);
let mut input = ParserInput::new(&css);
let mut input = Parser::new(&mut input);
input.skip_whitespace(); // Unnecessary for correctness, but may help try() rewind less.
if let Ok(keyword) = input.try_parse(CSSWideKeyword::parse) {
return PropertyDeclaration::css_wide_keyword(longhand_id, keyword);
}
let declaration = input.parse_entirely(|input| {
match self.from_shorthand {
None => longhand_id.parse_value(&context, input),
Some(ShorthandId::All) => {
// No need to parse the 'all' shorthand as anything other
// than a CSS-wide keyword, after variable substitution.
Err(input.new_custom_error(SelectorParseErrorKind::UnexpectedIdent("all".into())))
}
% for shorthand in data.shorthands_except_all():
Some(ShorthandId::${shorthand.camel_case}) => {
shorthands::${shorthand.ident}::parse_value(&context, input)
.map(|longhands| {
match longhand_id {
<% seen = set() %>
% for property in shorthand.sub_properties:
// When animating logical properties, we end up
// physicalizing the value during the animation, but
// the value still comes from the logical shorthand.
//
// So we need to handle the physical properties too.
% for prop in [property] + property.all_physical_mapped_properties(data):
% if prop.camel_case not in seen:
LonghandId::${prop.camel_case} => {
PropertyDeclaration::${prop.camel_case}(
longhands.${property.ident}
)
}
<% seen.add(prop.camel_case) %>
% endif
% endfor
% endfor
<% del seen %>
_ => unreachable!()
}
})
}
% endfor
}
});
match declaration {
Ok(decl) => decl,
Err(..) => invalid_at_computed_value_time(),
}
}
}
/// An identifier for a given property declaration, which can be either a
/// longhand or a custom property.
#[derive(Clone, Copy, Debug, PartialEq)]
#[cfg_attr(feature = "servo", derive(MallocSizeOf))]
pub enum PropertyDeclarationId<'a> {
/// A longhand.
Longhand(LonghandId),
/// A custom property declaration.
Custom(&'a crate::custom_properties::Name),
}
impl<'a> ToCss for PropertyDeclarationId<'a> {
fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
where
W: Write,
{
match *self {
PropertyDeclarationId::Longhand(id) => dest.write_str(id.name()),
PropertyDeclarationId::Custom(ref name) => {
dest.write_str("--")?;
serialize_atom_name(name, dest)
}
}
}
}
impl<'a> PropertyDeclarationId<'a> {
/// Whether a given declaration id is either the same as `other`, or a
/// longhand of it.
pub fn is_or_is_longhand_of(&self, other: &PropertyId) -> bool {
match *self {
PropertyDeclarationId::Longhand(id) => {
match *other {
PropertyId::Longhand(other_id) |
PropertyId::LonghandAlias(other_id, _) => id == other_id,
PropertyId::Shorthand(shorthand) |
PropertyId::ShorthandAlias(shorthand, _) => self.is_longhand_of(shorthand),
PropertyId::Custom(_) => false,
}
}
PropertyDeclarationId::Custom(name) => {
matches!(*other, PropertyId::Custom(ref other_name) if name == other_name)
}
}
}
/// Whether a given declaration id is a longhand belonging to this
/// shorthand.
pub fn is_longhand_of(&self, shorthand: ShorthandId) -> bool {
match *self {
PropertyDeclarationId::Longhand(ref id) => id.shorthands().any(|s| s == shorthand),
_ => false,
}
}
/// Returns the name of the property without CSS escaping.
pub fn name(&self) -> Cow<'static, str> {
match *self {
PropertyDeclarationId::Longhand(id) => id.name().into(),
PropertyDeclarationId::Custom(name) => {
let mut s = String::new();
write!(&mut s, "--{}", name).unwrap();
s.into()
}
}
}
/// Returns longhand id if it is, None otherwise.
#[inline]
pub fn as_longhand(&self) -> Option<LonghandId> {
match *self {
PropertyDeclarationId::Longhand(id) => Some(id),
_ => None,
}
}
}
/// Servo's representation of a CSS property, that is, either a longhand, a
/// shorthand, or a custom property.
#[derive(Clone, Eq, PartialEq)]
pub enum PropertyId {
/// A longhand property.
Longhand(LonghandId),
/// A shorthand property.
Shorthand(ShorthandId),
/// An alias for a longhand property.
LonghandAlias(LonghandId, AliasId),
/// An alias for a shorthand property.
ShorthandAlias(ShorthandId, AliasId),
/// A custom property.
Custom(crate::custom_properties::Name),
}
impl fmt::Debug for PropertyId {
fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
self.to_css(&mut CssWriter::new(formatter))
}
}
impl ToCss for PropertyId {
fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
where
W: Write,
{
match *self {
PropertyId::Longhand(id) => dest.write_str(id.name()),
PropertyId::Shorthand(id) => dest.write_str(id.name()),
PropertyId::LonghandAlias(id, _) => dest.write_str(id.name()),
PropertyId::ShorthandAlias(id, _) => dest.write_str(id.name()),
PropertyId::Custom(ref name) => {
dest.write_str("--")?;
serialize_atom_name(name, dest)
}
}
}
}
/// The counted unknown property list which is used for css use counters.
///
/// FIXME: This should be just #[repr(u8)], but can't be because of ABI issues,
#[derive(Clone, Copy, Debug, Eq, FromPrimitive, Hash, PartialEq)]
#[repr(u32)]
pub enum CountedUnknownProperty {
% for prop in data.counted_unknown_properties:
/// ${prop.name}
${prop.camel_case},
% endfor
}
impl CountedUnknownProperty {
/// Parse the counted unknown property, for testing purposes only.
pub fn parse_for_testing(property_name: &str) -> Option<Self> {
ascii_case_insensitive_phf_map! {
unknown_id -> CountedUnknownProperty = {
% for property in data.counted_unknown_properties:
"${property.name}" => CountedUnknownProperty::${property.camel_case},
% endfor
}
}
unknown_id(property_name).cloned()
}
/// Returns the underlying index, used for use counter.
#[inline]
pub fn bit(self) -> usize {
self as usize
}
}
impl PropertyId {
/// Return the longhand id that this property id represents.
#[inline]
pub fn longhand_id(&self) -> Option<LonghandId> {
Some(match *self {
PropertyId::Longhand(id) => id,
PropertyId::LonghandAlias(id, _) => id,
_ => return None,
})
}
/// Returns a given property from the given name, _regardless of whether it
/// is enabled or not_, or Err(()) for unknown properties.
///
/// Do not use for non-testing purposes.
pub fn parse_unchecked_for_testing(name: &str) -> Result<Self, ()> {
Self::parse_unchecked(name, None)
}
/// Returns a given property from the given name, _regardless of whether it
/// is enabled or not_, or Err(()) for unknown properties.
fn parse_unchecked(
property_name: &str,
use_counters: Option< &UseCounters>,
) -> Result<Self, ()> {
// A special id for css use counters.
// ShorthandAlias is not used in the Servo build.
// That's why we need to allow dead_code.
#[allow(dead_code)]
pub enum StaticId {
Longhand(LonghandId),
Shorthand(ShorthandId),
LonghandAlias(LonghandId, AliasId),
ShorthandAlias(ShorthandId, AliasId),
CountedUnknown(CountedUnknownProperty),
}
ascii_case_insensitive_phf_map! {
static_id -> StaticId = {
% for (kind, properties) in [("Longhand", data.longhands), ("Shorthand", data.shorthands)]:
% for property in properties:
"${property.name}" => StaticId::${kind}(${kind}Id::${property.camel_case}),
% for alias in property.alias:
"${alias.name}" => {
StaticId::${kind}Alias(
${kind}Id::${property.camel_case},
AliasId::${alias.camel_case},
)
},
% endfor
% endfor
% endfor
% for property in data.counted_unknown_properties:
"${property.name}" => {
StaticId::CountedUnknown(CountedUnknownProperty::${property.camel_case})
},
% endfor
}
}
if let Some(id) = static_id(property_name) {
return Ok(match *id {
StaticId::Longhand(id) => PropertyId::Longhand(id),
StaticId::Shorthand(id) => {
#[cfg(feature = "gecko")]
{
// We want to count `zoom` even if disabled.
if matches!(id, ShorthandId::Zoom) {
if let Some(counters) = use_counters {
counters.non_custom_properties.record(id.into());
}
}
}
PropertyId::Shorthand(id)
},
StaticId::LonghandAlias(id, alias) => PropertyId::LonghandAlias(id, alias),
StaticId::ShorthandAlias(id, alias) => PropertyId::ShorthandAlias(id, alias),
StaticId::CountedUnknown(unknown_prop) => {
if let Some(counters) = use_counters {
counters.counted_unknown_properties.record(unknown_prop);
}
// Always return Err(()) because these aren't valid custom property names.
return Err(());
}
});
}
let name = crate::custom_properties::parse_name(property_name)?;
Ok(PropertyId::Custom(crate::custom_properties::Name::from(name)))
}
/// Parses a property name, and returns an error if it's unknown or isn't
/// enabled for all content.
#[inline]
pub fn parse_enabled_for_all_content(name: &str) -> Result<Self, ()> {
let id = Self::parse_unchecked(name, None)?;
if !id.enabled_for_all_content() {
return Err(());
}
Ok(id)
}
/// Parses a property name, and returns an error if it's unknown or isn't
/// allowed in this context.
#[inline]
pub fn parse(name: &str, context: &ParserContext) -> Result<Self, ()> {
let id = Self::parse_unchecked(name, context.use_counters)?;
if !id.allowed_in(context) {
return Err(());
}
Ok(id)
}
/// Parses a property name, and returns an error if it's unknown or isn't
/// allowed in this context, ignoring the rule_type checks.
///
/// This is useful for parsing stuff from CSS values, for example.
#[inline]
pub fn parse_ignoring_rule_type(
name: &str,
context: &ParserContext,
) -> Result<Self, ()> {
let id = Self::parse_unchecked(name, None)?;
if !id.allowed_in_ignoring_rule_type(context) {
return Err(());
}
Ok(id)
}
/// Returns a property id from Gecko's nsCSSPropertyID.
#[cfg(feature = "gecko")]
#[allow(non_upper_case_globals)]
#[inline]
pub fn from_nscsspropertyid(id: nsCSSPropertyID) -> Result<Self, ()> {
Ok(NonCustomPropertyId::from_nscsspropertyid(id)?.to_property_id())
}
/// Returns true if the property is a shorthand or shorthand alias.
#[inline]
pub fn is_shorthand(&self) -> bool {
self.as_shorthand().is_ok()
}
/// Given this property id, get it either as a shorthand or as a
/// `PropertyDeclarationId`.
pub fn as_shorthand(&self) -> Result<ShorthandId, PropertyDeclarationId> {
match *self {
PropertyId::ShorthandAlias(id, _) |
PropertyId::Shorthand(id) => Ok(id),
PropertyId::LonghandAlias(id, _) |
PropertyId::Longhand(id) => Err(PropertyDeclarationId::Longhand(id)),
PropertyId::Custom(ref name) => Err(PropertyDeclarationId::Custom(name)),
}
}
/// Returns the `NonCustomPropertyId` corresponding to this property id.
pub fn non_custom_id(&self) -> Option<NonCustomPropertyId> {
Some(match *self {
PropertyId::Custom(_) => return None,
PropertyId::Shorthand(shorthand_id) => shorthand_id.into(),
PropertyId::Longhand(longhand_id) => longhand_id.into(),
PropertyId::ShorthandAlias(_, alias_id) => alias_id.into(),
PropertyId::LonghandAlias(_, alias_id) => alias_id.into(),
})
}
/// Returns non-alias NonCustomPropertyId corresponding to this
/// property id.
fn non_custom_non_alias_id(&self) -> Option<NonCustomPropertyId> {
Some(match *self {
PropertyId::Custom(_) => return None,
PropertyId::Shorthand(id) => id.into(),
PropertyId::Longhand(id) => id.into(),
PropertyId::ShorthandAlias(id, _) => id.into(),
PropertyId::LonghandAlias(id, _) => id.into(),
})
}
/// Whether the property is enabled for all content regardless of the
/// stylesheet it was declared on (that is, in practice only checks prefs).
#[inline]
pub fn enabled_for_all_content(&self) -> bool {
let id = match self.non_custom_id() {
// Custom properties are allowed everywhere
None => return true,
Some(id) => id,
};
id.enabled_for_all_content()
}
/// Converts this PropertyId in nsCSSPropertyID, resolving aliases to the
/// resolved property, and returning eCSSPropertyExtra_variable for custom
/// properties.
#[cfg(feature = "gecko")]
#[inline]
pub fn to_nscsspropertyid_resolving_aliases(&self) -> nsCSSPropertyID {
match self.non_custom_non_alias_id() {
Some(id) => id.to_nscsspropertyid(),
None => nsCSSPropertyID::eCSSPropertyExtra_variable,
}
}
fn allowed_in(&self, context: &ParserContext) -> bool {
let id = match self.non_custom_id() {
// Custom properties are allowed everywhere
None => return true,
Some(id) => id,
};
id.allowed_in(context)
}
#[inline]
fn allowed_in_ignoring_rule_type(&self, context: &ParserContext) -> bool {
let id = match self.non_custom_id() {
// Custom properties are allowed everywhere
None => return true,
Some(id) => id,
};
id.allowed_in_ignoring_rule_type(context)
}
/// Whether the property supports the given CSS type.
/// `ty` should a bitflags of constants in style_traits::CssType.
pub fn supports_type(&self, ty: u8) -> bool {
let id = self.non_custom_non_alias_id();
id.map_or(0, |id| id.supported_types()) & ty != 0
}
/// Collect supported starting word of values of this property.
///
/// See style_traits::SpecifiedValueInfo::collect_completion_keywords for more
/// details.
pub fn collect_property_completion_keywords(&self, f: KeywordsCollectFn) {
if let Some(id) = self.non_custom_non_alias_id() {
id.collect_property_completion_keywords(f);
}
CSSWideKeyword::collect_completion_keywords(f);
}
}
/// A declaration using a CSS-wide keyword.
#[cfg_attr(feature = "gecko", derive(MallocSizeOf))]
#[derive(Clone, PartialEq, ToCss, ToShmem)]
pub struct WideKeywordDeclaration {
#[css(skip)]
id: LonghandId,
/// The CSS-wide keyword.
pub keyword: CSSWideKeyword,
}
/// An unparsed declaration that contains `var()` functions.
#[cfg_attr(feature = "gecko", derive(MallocSizeOf))]
#[derive(Clone, PartialEq, ToCss, ToShmem)]
pub struct VariableDeclaration {
#[css(skip)]
id: LonghandId,
#[cfg_attr(feature = "gecko", ignore_malloc_size_of = "XXX: how to handle this?")]
value: Arc<UnparsedValue>,
}
/// A custom property declaration value is either an unparsed value or a CSS
/// wide-keyword.
#[derive(Clone, PartialEq, ToCss, ToShmem)]
pub enum CustomDeclarationValue {
/// A value.
Value(Arc<crate::custom_properties::SpecifiedValue>),
/// A wide keyword.
CSSWideKeyword(CSSWideKeyword),
}
/// A custom property declaration with the property name and the declared value.
#[cfg_attr(feature = "gecko", derive(MallocSizeOf))]
#[derive(Clone, PartialEq, ToCss, ToShmem)]
pub struct CustomDeclaration {
/// The name of the custom property.
#[css(skip)]
pub name: crate::custom_properties::Name,
/// The value of the custom property.
#[cfg_attr(feature = "gecko", ignore_malloc_size_of = "XXX: how to handle this?")]
pub value: CustomDeclarationValue,
}
impl fmt::Debug for PropertyDeclaration {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.id().to_css(&mut CssWriter::new(f))?;
f.write_str(": ")?;
// Because PropertyDeclaration::to_css requires CssStringWriter, we can't write
// it directly to f, and need to allocate an intermediate string. This is
// fine for debug-only code.
let mut s = CssString::new();
self.to_css(&mut s)?;
write!(f, "{}", s)
}
}
impl PropertyDeclaration {
/// Given a property declaration, return the property declaration id.
#[inline]
pub fn id(&self) -> PropertyDeclarationId {
match *self {
PropertyDeclaration::Custom(ref declaration) => {
return PropertyDeclarationId::Custom(&declaration.name)
}
PropertyDeclaration::CSSWideKeyword(ref declaration) => {
return PropertyDeclarationId::Longhand(declaration.id);
}
PropertyDeclaration::WithVariables(ref declaration) => {
return PropertyDeclarationId::Longhand(declaration.id);
}
_ => {}
}
// This is just fine because PropertyDeclaration and LonghandId
// have corresponding discriminants.
let id = unsafe { *(self as *const _ as *const LonghandId) };
debug_assert_eq!(id, match *self {
% for property in data.longhands:
PropertyDeclaration::${property.camel_case}(..) => LonghandId::${property.camel_case},
% endfor
_ => id,
});
PropertyDeclarationId::Longhand(id)
}
/// Given a declaration, convert it into a declaration for a corresponding
/// physical property.
#[inline]
pub fn to_physical(&self, wm: WritingMode) -> Self {
match *self {
PropertyDeclaration::WithVariables(VariableDeclaration {
id,
ref value,
}) => {
return PropertyDeclaration::WithVariables(VariableDeclaration {
id: id.to_physical(wm),
value: value.clone(),
})
}
PropertyDeclaration::CSSWideKeyword(WideKeywordDeclaration {
id,
keyword,
}) => {
return PropertyDeclaration::CSSWideKeyword(WideKeywordDeclaration {
id: id.to_physical(wm),
keyword,
})
}
PropertyDeclaration::Custom(..) => return self.clone(),
% for prop in data.longhands:
PropertyDeclaration::${prop.camel_case}(..) => {},
% endfor
}
let mut ret = self.clone();
% for prop in data.longhands:
% for physical_property in prop.all_physical_mapped_properties(data):
% if physical_property.specified_type() != prop.specified_type():
<% raise "Logical property %s should share specified value with physical property %s" % \
(prop.name, physical_property.name) %>
% endif
% endfor
% endfor
unsafe {
let longhand_id = *(&mut ret as *mut _ as *mut LonghandId);
debug_assert_eq!(
PropertyDeclarationId::Longhand(longhand_id),
ret.id()
);
// This is just fine because PropertyDeclaration and LonghandId
// have corresponding discriminants.
*(&mut ret as *mut _ as *mut LonghandId) = longhand_id.to_physical(wm);
debug_assert_eq!(
PropertyDeclarationId::Longhand(longhand_id.to_physical(wm)),
ret.id()
);
}
ret
}
fn with_variables_from_shorthand(&self, shorthand: ShorthandId) -> Option< &str> {
match *self {
PropertyDeclaration::WithVariables(ref declaration) => {
let s = declaration.value.from_shorthand?;
if s != shorthand {
return None;
}
Some(&*declaration.value.css)
},
_ => None,
}
}
/// Returns a CSS-wide keyword declaration for a given property.
#[inline]
pub fn css_wide_keyword(id: LonghandId, keyword: CSSWideKeyword) -> Self {
Self::CSSWideKeyword(WideKeywordDeclaration { id, keyword })
}
/// Returns a CSS-wide keyword if the declaration's value is one.
#[inline]
pub fn get_css_wide_keyword(&self) -> Option<CSSWideKeyword> {
match *self {
PropertyDeclaration::CSSWideKeyword(ref declaration) => {
Some(declaration.keyword)
},
_ => None,
}
}
/// Returns whether or not the property is set by a system font
pub fn get_system(&self) -> Option<SystemFont> {
match *self {
% if engine == "gecko":
% for prop in SYSTEM_FONT_LONGHANDS:
PropertyDeclaration::${to_camel_case(prop)}(ref prop) => {
prop.get_system()
}
% endfor
% endif
_ => None,
}
}
/// Is it the default value of line-height?
pub fn is_default_line_height(&self) -> bool {
match *self {
PropertyDeclaration::LineHeight(LineHeight::Normal) => true,
_ => false
}
}
/// Returns whether the declaration may be serialized as part of a shorthand.
///
/// This method returns false if this declaration contains variable or has a
/// CSS-wide keyword value, since these values cannot be serialized as part
/// of a shorthand.
///
/// Caller should check `with_variables_from_shorthand()` and whether all
/// needed declarations has the same CSS-wide keyword first.
///
/// Note that, serialization of a shorthand may still fail because of other
/// property-specific requirement even when this method returns true for all
/// the longhand declarations.
pub fn may_serialize_as_part_of_shorthand(&self) -> bool {
match *self {
PropertyDeclaration::CSSWideKeyword(..) |
PropertyDeclaration::WithVariables(..) => false,
PropertyDeclaration::Custom(..) =>
unreachable!("Serializing a custom property as part of shorthand?"),
_ => true,
}
}
/// Return whether the value is stored as it was in the CSS source,
/// preserving whitespace (as opposed to being parsed into a more abstract
/// data structure).
///
/// This is the case of custom properties and values that contain
/// unsubstituted variables.
pub fn value_is_unparsed(&self) -> bool {
match *self {
PropertyDeclaration::WithVariables(..) => true,
PropertyDeclaration::Custom(ref declaration) => {
matches!(declaration.value, CustomDeclarationValue::Value(..))
}
_ => false,
}
}
/// Returns true if this property declaration is for one of the animatable
/// properties.
pub fn is_animatable(&self) -> bool {
match self.id() {
PropertyDeclarationId::Longhand(id) => id.is_animatable(),
PropertyDeclarationId::Custom(..) => false,
}
}
/// Returns true if this property is a custom property, false
/// otherwise.
pub fn is_custom(&self) -> bool {
matches!(*self, PropertyDeclaration::Custom(..))
}
/// The `context` parameter controls this:
///
/// > The <declaration-list> inside of <keyframe-block> accepts any CSS property
/// > except those defined in this specification,
/// > but does accept the `animation-play-state` property and interprets it specially.
///
/// This will not actually parse Importance values, and will always set things
/// to Importance::Normal. Parsing Importance values is the job of PropertyDeclarationParser,
/// we only set them here so that we don't have to reallocate
pub fn parse_into<'i, 't>(
declarations: &mut SourcePropertyDeclaration,
id: PropertyId,
context: &ParserContext,
input: &mut Parser<'i, 't>,
) -> Result<(), ParseError<'i>> {
assert!(declarations.is_empty());
debug_assert!(id.allowed_in(context), "{:?}", id);
let non_custom_id = id.non_custom_id();
let start = input.state();
match id {
PropertyId::Custom(property_name) => {
// before adding skip_whitespace here.
// This probably affects some test results.
let value = match input.try_parse(CSSWideKeyword::parse) {
Ok(keyword) => CustomDeclarationValue::CSSWideKeyword(keyword),
Err(()) => CustomDeclarationValue::Value(
crate::custom_properties::SpecifiedValue::parse(input)?
),
};
declarations.push(PropertyDeclaration::Custom(CustomDeclaration {
name: property_name,
value,
}));
return Ok(());
}
PropertyId::LonghandAlias(id, _) |
PropertyId::Longhand(id) => {
input.skip_whitespace(); // Unnecessary for correctness, but may help try() rewind less.
input.try_parse(CSSWideKeyword::parse).map(|keyword| {
PropertyDeclaration::css_wide_keyword(id, keyword)
}).or_else(|()| {
input.look_for_var_or_env_functions();
input.parse_entirely(|input| id.parse_value(context, input))
.or_else(|err| {
while let Ok(_) = input.next() {} // Look for var() after the error.
if !input.seen_var_or_env_functions() {
return Err(err);
}
input.reset(&start);
let (first_token_type, css) =
crate::custom_properties::parse_non_custom_with_var(input)?;
Ok(PropertyDeclaration::WithVariables(VariableDeclaration {
id,
value: Arc::new(UnparsedValue {
css: css.into_owned(),
first_token_type: first_token_type,
url_data: context.url_data.clone(),
from_shorthand: None,
}),
}))
})
}).map(|declaration| {
declarations.push(declaration)
})?;
}
PropertyId::ShorthandAlias(id, _) |
PropertyId::Shorthand(id) => {
input.skip_whitespace(); // Unnecessary for correctness, but may help try() rewind less.
if let Ok(keyword) = input.try_parse(CSSWideKeyword::parse) {
if id == ShorthandId::All {
declarations.all_shorthand = AllShorthand::CSSWideKeyword(keyword)
} else {
for longhand in id.longhands() {
declarations.push(PropertyDeclaration::css_wide_keyword(longhand, keyword));