pub mod cascade;
pub mod declaration_block;
pub mod shorthands;
pub use self::cascade::*;
pub use self::declaration_block::*;
pub use self::generated::*;
#[macro_use]
#[allow(unsafe_code)]
#[deny(missing_docs)]
pub mod generated {
include!(concat!(env!("OUT_DIR"), "/properties.rs"));
}
use crate::applicable_declarations::RevertKind;
use crate::custom_properties::{self, ComputedSubstitutionFunctions, SubstitutionResult};
use crate::derives::*;
use crate::dom::AttributeTracker;
#[cfg(feature = "gecko")]
use crate::gecko_bindings::structs::{CSSPropertyId, NonCustomCSSPropertyId, RefPtr};
use crate::logical_geometry::WritingMode;
use crate::parser::ParserContext;
use crate::stylesheets::CssRuleType;
use crate::stylesheets::Origin;
use crate::stylist::Stylist;
use crate::typed_om::{ToTyped, TypedValue};
use crate::values::{computed, serialize_atom_name};
use arrayvec::{ArrayVec, Drain as ArrayVecDrain};
use cssparser::{match_ignore_ascii_case, Parser, ParserInput};
use rustc_hash::FxHashMap;
use servo_arc::Arc;
use std::{
borrow::Cow,
fmt::{self, Write},
mem,
};
use style_traits::{
CssString, CssWriter, KeywordsCollectFn, ParseError, ParsingMode, SpecifiedValueInfo, ToCss,
};
use thin_vec::ThinVec;
bitflags! {
#[derive(Clone, Copy)]
pub struct PropertyFlags: u16 {
const APPLIES_TO_FIRST_LETTER = 1 << 1;
const APPLIES_TO_FIRST_LINE = 1 << 2;
const APPLIES_TO_PLACEHOLDER = 1 << 3;
const APPLIES_TO_CUE = 1 << 4;
const APPLIES_TO_MARKER = 1 << 5;
const IS_LEGACY_SHORTHAND = 1 << 6;
const CAN_ANIMATE_ON_COMPOSITOR = 0;
const AFFECTS_LAYOUT = 0;
#[allow(missing_docs)]
const AFFECTS_OVERFLOW = 0;
#[allow(missing_docs)]
const AFFECTS_PAINT = 0;
}
}
#[derive(
Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToCss, ToShmem, ToTyped,
)]
pub enum CSSWideKeyword {
Initial,
Inherit,
Unset,
Revert,
RevertLayer,
RevertRule,
}
impl CSSWideKeyword {
pub fn to_str(&self) -> &'static str {
match *self {
Self::Initial => "initial",
Self::Inherit => "inherit",
Self::Unset => "unset",
Self::Revert => "revert",
Self::RevertLayer => "revert-layer",
Self::RevertRule => "revert-rule",
}
}
pub fn from_ident(ident: &str) -> Result<Self, ()> {
Ok(match_ignore_ascii_case! { ident,
"initial" => Self::Initial,
"inherit" => Self::Inherit,
"unset" => Self::Unset,
"revert" => Self::Revert,
"revert-layer" => Self::RevertLayer,
"revert-rule" if static_prefs::pref!("layout.css.revert-rule.enabled") => Self::RevertRule,
_ => return Err(()),
})
}
pub fn parse(input: &mut Parser) -> Result<Self, ()> {
let keyword = {
let ident = input.expect_ident().map_err(|_| ())?;
Self::from_ident(ident)?
};
input.expect_exhausted().map_err(|_| ())?;
Ok(keyword)
}
pub fn revert_kind(self) -> Option<RevertKind> {
Some(match self {
Self::Initial | Self::Inherit | Self::Unset => return None,
Self::Revert => RevertKind::Origin,
Self::RevertLayer => RevertKind::Layer,
Self::RevertRule => RevertKind::Rule,
})
}
}
#[derive(Clone, PartialEq, ToCss, ToShmem, MallocSizeOf)]
pub struct WideKeywordDeclaration {
#[css(skip)]
id: LonghandId,
pub keyword: CSSWideKeyword,
}
impl ToTyped for WideKeywordDeclaration {
fn to_typed(&self, dest: &mut ThinVec<TypedValue>) -> Result<(), ()> {
self.keyword.to_typed(dest)
}
}
#[derive(Clone, PartialEq, ToCss, ToShmem, MallocSizeOf, ToTyped)]
pub struct VariableDeclaration {
#[css(skip)]
id: LonghandId,
#[ignore_malloc_size_of = "Arc"]
pub value: Arc<UnparsedValue>,
}
#[derive(Clone, PartialEq, ToCss, ToShmem)]
pub enum CustomDeclarationValue {
Unparsed(Arc<custom_properties::SpecifiedValue>),
Parsed(Arc<crate::properties_and_values::value::SpecifiedValue>),
CSSWideKeyword(CSSWideKeyword),
}
#[derive(Clone, PartialEq, ToCss, ToShmem, MallocSizeOf, ToTyped)]
#[typed(todo_derive_fields)]
pub struct CustomDeclaration {
#[css(skip)]
pub name: custom_properties::Name,
#[ignore_malloc_size_of = "Arc"]
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(": ")?;
let mut s = CssString::new();
self.to_css(&mut s)?;
write!(f, "{}", s)
}
}
#[derive(
Clone, Copy, Debug, PartialEq, Eq, Hash, ToComputedValue, ToResolvedValue, ToShmem, MallocSizeOf,
)]
#[repr(C)]
pub struct NonCustomPropertyId(u16);
impl ToCss for NonCustomPropertyId {
#[inline]
fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
where
W: Write,
{
dest.write_str(self.name())
}
}
impl NonCustomPropertyId {
pub fn bit(self) -> usize {
self.0 as usize
}
#[cfg(feature = "gecko")]
#[inline]
pub fn to_noncustomcsspropertyid(self) -> NonCustomCSSPropertyId {
unsafe { mem::transmute(self.0) }
}
#[cfg(feature = "gecko")]
#[inline]
pub fn from_noncustomcsspropertyid(prop: NonCustomCSSPropertyId) -> Option<Self> {
let prop = prop as u16;
if prop >= property_counts::NON_CUSTOM as u16 {
return None;
}
Some(NonCustomPropertyId(prop))
}
pub fn unaliased(self) -> Self {
let Some(alias_id) = self.as_alias() else {
return self;
};
alias_id.aliased_property()
}
#[inline]
pub fn to_property_id(self) -> PropertyId {
PropertyId::NonCustom(self)
}
#[inline]
pub fn as_longhand(self) -> Option<LonghandId> {
if self.0 < property_counts::LONGHANDS as u16 {
return Some(unsafe { mem::transmute(self.0 as u16) });
}
None
}
#[inline]
pub fn as_shorthand(self) -> Option<ShorthandId> {
if self.0 >= property_counts::LONGHANDS as u16
&& self.0 < property_counts::LONGHANDS_AND_SHORTHANDS as u16
{
return Some(unsafe { mem::transmute(self.0 - (property_counts::LONGHANDS as u16)) });
}
None
}
#[inline]
pub fn as_alias(self) -> Option<AliasId> {
debug_assert!((self.0 as usize) < property_counts::NON_CUSTOM);
if self.0 >= property_counts::LONGHANDS_AND_SHORTHANDS as u16 {
return Some(unsafe {
mem::transmute(self.0 - (property_counts::LONGHANDS_AND_SHORTHANDS as u16))
});
}
None
}
#[inline]
pub fn longhand_or_shorthand(self) -> Result<LonghandId, ShorthandId> {
let id = self.unaliased();
match id.as_longhand() {
Some(lh) => Ok(lh),
None => Err(id.as_shorthand().unwrap()),
}
}
#[inline]
pub const fn from_longhand(id: LonghandId) -> Self {
Self(id as u16)
}
#[inline]
pub const fn from_shorthand(id: ShorthandId) -> Self {
Self((id as u16) + (property_counts::LONGHANDS as u16))
}
#[inline]
pub const fn from_alias(id: AliasId) -> Self {
Self((id as u16) + (property_counts::LONGHANDS_AND_SHORTHANDS as u16))
}
}
impl From<LonghandId> for NonCustomPropertyId {
#[inline]
fn from(id: LonghandId) -> Self {
Self::from_longhand(id)
}
}
impl From<ShorthandId> for NonCustomPropertyId {
#[inline]
fn from(id: ShorthandId) -> Self {
Self::from_shorthand(id)
}
}
impl From<AliasId> for NonCustomPropertyId {
#[inline]
fn from(id: AliasId) -> Self {
Self::from_alias(id)
}
}
#[derive(Clone, Eq, PartialEq, Debug)]
pub enum PropertyId {
NonCustom(NonCustomPropertyId),
Custom(custom_properties::Name),
}
impl ToCss for PropertyId {
fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
where
W: Write,
{
match *self {
PropertyId::NonCustom(id) => dest.write_str(id.name()),
PropertyId::Custom(ref name) => {
dest.write_str("--")?;
serialize_atom_name(name, dest)
},
}
}
}
impl PropertyId {
#[inline]
pub fn longhand_id(&self) -> Option<LonghandId> {
self.non_custom_non_alias_id()?.as_longhand()
}
pub fn is_animatable(&self) -> bool {
match self {
Self::NonCustom(id) => id.is_animatable(),
Self::Custom(_) => true,
}
}
pub fn parse_unchecked_for_testing(name: &str) -> Result<Self, ()> {
Self::parse_unchecked(name, None)
}
#[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)
}
#[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)
}
#[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)
}
#[cfg(feature = "gecko")]
#[inline]
pub fn from_noncustomcsspropertyid(id: NonCustomCSSPropertyId) -> Option<Self> {
Some(NonCustomPropertyId::from_noncustomcsspropertyid(id)?.to_property_id())
}
#[cfg(feature = "gecko")]
#[inline]
pub fn from_gecko_css_property_id(property: &CSSPropertyId) -> Option<Self> {
Some(
if property.mId == NonCustomCSSPropertyId::eCSSPropertyExtra_variable {
debug_assert!(!property.mCustomName.mRawPtr.is_null());
Self::Custom(unsafe { crate::Atom::from_raw(property.mCustomName.mRawPtr) })
} else {
Self::NonCustom(NonCustomPropertyId::from_noncustomcsspropertyid(
property.mId,
)?)
},
)
}
#[inline]
pub fn is_shorthand(&self) -> bool {
self.as_shorthand().is_ok()
}
pub fn as_shorthand(&self) -> Result<ShorthandId, PropertyDeclarationId<'_>> {
match *self {
Self::NonCustom(id) => match id.longhand_or_shorthand() {
Ok(lh) => Err(PropertyDeclarationId::Longhand(lh)),
Err(sh) => Ok(sh),
},
Self::Custom(ref name) => Err(PropertyDeclarationId::Custom(name)),
}
}
pub fn non_custom_id(&self) -> Option<NonCustomPropertyId> {
match *self {
Self::Custom(_) => None,
Self::NonCustom(id) => Some(id),
}
}
fn non_custom_non_alias_id(&self) -> Option<NonCustomPropertyId> {
self.non_custom_id().map(NonCustomPropertyId::unaliased)
}
#[inline]
pub fn enabled_for_all_content(&self) -> bool {
let id = match self.non_custom_id() {
None => return true,
Some(id) => id,
};
id.enabled_for_all_content()
}
#[cfg(feature = "gecko")]
#[inline]
pub fn to_noncustomcsspropertyid_resolving_aliases(&self) -> NonCustomCSSPropertyId {
match self.non_custom_non_alias_id() {
Some(id) => id.to_noncustomcsspropertyid(),
None => NonCustomCSSPropertyId::eCSSPropertyExtra_variable,
}
}
fn allowed_in(&self, context: &ParserContext) -> bool {
let id = match self.non_custom_id() {
None => {
return !context
.nesting_context
.rule_types
.contains(CssRuleType::PositionTry)
},
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() {
None => return true,
Some(id) => id,
};
id.allowed_in_ignoring_rule_type(context)
}
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
}
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);
}
}
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 {
#[inline]
pub fn name(&self) -> &'static str {
NonCustomPropertyId::from(*self).name()
}
#[inline]
pub fn inherited(self) -> bool {
!LonghandIdSet::reset().contains(self)
}
#[inline]
pub fn zoom_dependent(self) -> bool {
LonghandIdSet::zoom_dependent().contains(self)
}
#[inline]
pub fn ignored_when_document_colors_disabled(self) -> bool {
LonghandIdSet::ignored_when_colors_disabled().contains(self)
}
pub fn is_or_is_longhand_of(self, non_custom: NonCustomPropertyId) -> bool {
match non_custom.longhand_or_shorthand() {
Ok(lh) => self == lh,
Err(sh) => self.is_longhand_of(sh),
}
}
pub fn is_longhand_of(self, shorthand: ShorthandId) -> bool {
self.shorthands().any(|s| s == shorthand)
}
#[inline]
pub fn is_animatable(self) -> bool {
NonCustomPropertyId::from(self).is_animatable()
}
#[inline]
pub fn is_discrete_animatable(self) -> bool {
LonghandIdSet::discrete_animatable().contains(self)
}
#[cfg(feature = "gecko")]
#[inline]
pub fn to_noncustomcsspropertyid(self) -> NonCustomCSSPropertyId {
NonCustomPropertyId::from(self).to_noncustomcsspropertyid()
}
#[cfg(feature = "gecko")]
pub fn from_noncustomcsspropertyid(id: NonCustomCSSPropertyId) -> Option<Self> {
NonCustomPropertyId::from_noncustomcsspropertyid(id)?
.unaliased()
.as_longhand()
}
#[inline]
pub fn is_logical(self) -> bool {
LonghandIdSet::logical().contains(self)
}
}
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 {
#[inline]
pub fn name(&self) -> &'static str {
NonCustomPropertyId::from(*self).name()
}
#[cfg(feature = "gecko")]
#[inline]
pub fn to_noncustomcsspropertyid(self) -> NonCustomCSSPropertyId {
NonCustomPropertyId::from(self).to_noncustomcsspropertyid()
}
#[cfg(feature = "gecko")]
#[inline]
pub fn from_noncustomcsspropertyid(id: NonCustomCSSPropertyId) -> Option<Self> {
NonCustomPropertyId::from_noncustomcsspropertyid(id)?
.unaliased()
.as_shorthand()
}
pub fn get_shorthand_appendable_value<'a, 'b: 'a>(
self,
declarations: &'a [&'b PropertyDeclaration],
) -> Option<AppendableValue<'a, 'b>> {
let first_declaration = declarations.get(0)?;
let rest = || declarations.iter().skip(1);
if let Some(css) = first_declaration.with_variables_from_shorthand(self) {
if rest().all(|d| d.with_variables_from_shorthand(self) == Some(css)) {
return Some(AppendableValue::Css(css));
}
return None;
}
if let Some(keyword) = first_declaration.get_css_wide_keyword() {
if rest().all(|d| d.get_css_wide_keyword() == Some(keyword)) {
return Some(AppendableValue::Css(keyword.to_str()));
}
return None;
}
if self == ShorthandId::All {
return None;
}
if declarations
.iter()
.all(|d| d.may_serialize_as_part_of_shorthand())
{
return Some(AppendableValue::DeclarationsForShorthand(
self,
declarations,
));
}
None
}
#[inline]
pub fn is_legacy_shorthand(self) -> bool {
self.flags().contains(PropertyFlags::IS_LEGACY_SHORTHAND)
}
}
pub fn enabled_arbitrary_substitution_functions() -> &'static [&'static str] {
if static_prefs::pref!("layout.css.attr.enabled") {
&["var", "env", "attr"]
} else {
&["var", "env"]
}
}
fn parse_non_custom_property_declaration_value_into<'i>(
declarations: &mut SourcePropertyDeclaration,
context: &ParserContext,
input: &mut Parser<'i, '_>,
start: &cssparser::ParserState,
parse_entirely_into: impl FnOnce(
&mut SourcePropertyDeclaration,
&mut Parser<'i, '_>,
) -> Result<(), ParseError<'i>>,
parsed_wide_keyword: impl FnOnce(&mut SourcePropertyDeclaration, CSSWideKeyword),
parsed_custom: impl FnOnce(&mut SourcePropertyDeclaration, custom_properties::VariableValue),
) -> Result<(), ParseError<'i>> {
let mut starts_with_curly_block = false;
if let Ok(token) = input.next() {
match token {
cssparser::Token::Ident(ref ident) => match CSSWideKeyword::from_ident(ident) {
Ok(wk) => {
if input.expect_exhausted().is_ok() {
return Ok(parsed_wide_keyword(declarations, wk));
}
},
Err(()) => {},
},
cssparser::Token::CurlyBracketBlock => {
starts_with_curly_block = true;
},
_ => {},
}
};
input.reset(&start);
input.look_for_arbitrary_substitution_functions(enabled_arbitrary_substitution_functions());
let err = match parse_entirely_into(declarations, input) {
Ok(()) => {
input.seen_arbitrary_substitution_functions();
return Ok(());
},
Err(e) => e,
};
let start_pos = start.position();
let mut at_start = start_pos == input.position();
let mut invalid = false;
while let Ok(token) = input.next() {
if matches!(token, cssparser::Token::CurlyBracketBlock) {
if !starts_with_curly_block || !at_start {
invalid = true;
break;
}
} else if starts_with_curly_block {
invalid = true;
break;
}
at_start = false;
}
if !input.seen_arbitrary_substitution_functions() || invalid {
return Err(err);
}
input.reset(start);
let value = custom_properties::VariableValue::parse(
input,
Some(&context.namespaces.prefixes),
&context.url_data,
)?;
parsed_custom(declarations, value);
Ok(())
}
impl PropertyDeclaration {
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.variable_value.css)
},
_ => None,
}
}
#[inline]
pub fn css_wide_keyword(id: LonghandId, keyword: CSSWideKeyword) -> Self {
Self::CSSWideKeyword(WideKeywordDeclaration { id, keyword })
}
#[inline]
pub fn get_css_wide_keyword(&self) -> Option<CSSWideKeyword> {
match *self {
PropertyDeclaration::CSSWideKeyword(ref declaration) => Some(declaration.keyword),
_ => None,
}
}
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,
}
}
pub fn is_animatable(&self) -> bool {
self.id().is_animatable()
}
pub fn is_custom(&self) -> bool {
matches!(*self, PropertyDeclaration::Custom(..))
}
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);
input.skip_whitespace();
let start = input.state();
let non_custom_id = match id {
PropertyId::Custom(property_name) => {
let value = match input.try_parse(CSSWideKeyword::parse) {
Ok(keyword) => CustomDeclarationValue::CSSWideKeyword(keyword),
Err(()) => CustomDeclarationValue::Unparsed(Arc::new(
custom_properties::VariableValue::parse(
input,
Some(&context.namespaces.prefixes),
&context.url_data,
)?,
)),
};
declarations.push(PropertyDeclaration::Custom(CustomDeclaration {
name: property_name,
value,
}));
return Ok(());
},
PropertyId::NonCustom(id) => id,
};
match non_custom_id.longhand_or_shorthand() {
Ok(longhand_id) => {
parse_non_custom_property_declaration_value_into(
declarations,
context,
input,
&start,
|declarations, input| {
let decl = input
.parse_entirely(|input| longhand_id.parse_value(context, input))?;
declarations.push(decl);
Ok(())
},
|declarations, wk| {
declarations.push(PropertyDeclaration::css_wide_keyword(longhand_id, wk));
},
|declarations, variable_value| {
declarations.push(PropertyDeclaration::WithVariables(VariableDeclaration {
id: longhand_id,
value: Arc::new(UnparsedValue {
variable_value,
from_shorthand: None,
}),
}))
},
)?;
},
Err(shorthand_id) => {
parse_non_custom_property_declaration_value_into(
declarations,
context,
input,
&start,
|declarations, input| shorthand_id.parse_into(declarations, context, input),
|declarations, wk| {
if shorthand_id == ShorthandId::All {
declarations.all_shorthand = AllShorthand::CSSWideKeyword(wk)
} else {
for longhand in shorthand_id.longhands() {
declarations
.push(PropertyDeclaration::css_wide_keyword(longhand, wk));
}
}
},
|declarations, variable_value| {
let unparsed = Arc::new(UnparsedValue {
variable_value,
from_shorthand: Some(shorthand_id),
});
if shorthand_id == ShorthandId::All {
declarations.all_shorthand = AllShorthand::WithVariables(unparsed)
} else {
for id in shorthand_id.longhands() {
declarations.push(PropertyDeclaration::WithVariables(
VariableDeclaration {
id,
value: unparsed.clone(),
},
))
}
}
},
)?;
},
}
if let Some(use_counters) = context.use_counters {
use_counters.non_custom_properties.record(non_custom_id);
}
Ok(())
}
}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub enum OwnedPropertyDeclarationId {
Longhand(LonghandId),
Custom(custom_properties::Name),
}
impl OwnedPropertyDeclarationId {
#[inline]
pub fn is_logical(&self) -> bool {
self.as_borrowed().is_logical()
}
#[inline]
pub fn as_borrowed(&self) -> PropertyDeclarationId<'_> {
match self {
Self::Longhand(id) => PropertyDeclarationId::Longhand(*id),
Self::Custom(name) => PropertyDeclarationId::Custom(name),
}
}
#[cfg(feature = "gecko")]
#[inline]
pub fn from_gecko_css_property_id(property: &CSSPropertyId) -> Option<Self> {
Some(match PropertyId::from_gecko_css_property_id(property)? {
PropertyId::Custom(name) => Self::Custom(name),
PropertyId::NonCustom(id) => Self::Longhand(id.as_longhand()?),
})
}
}
#[derive(Clone, Copy, Debug, PartialEq, MallocSizeOf)]
pub enum PropertyDeclarationId<'a> {
Longhand(LonghandId),
Custom(&'a 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(name) => {
dest.write_str("--")?;
serialize_atom_name(name, dest)
},
}
}
}
impl<'a> PropertyDeclarationId<'a> {
#[inline(always)]
pub fn flags(&self) -> PropertyFlags {
match self {
Self::Longhand(id) => id.flags(),
Self::Custom(_) => PropertyFlags::empty(),
}
}
pub fn to_owned(&self) -> OwnedPropertyDeclarationId {
match self {
PropertyDeclarationId::Longhand(id) => OwnedPropertyDeclarationId::Longhand(*id),
PropertyDeclarationId::Custom(name) => {
OwnedPropertyDeclarationId::Custom((*name).clone())
},
}
}
pub fn is_or_is_longhand_of(&self, other: &PropertyId) -> bool {
match *self {
PropertyDeclarationId::Longhand(id) => match *other {
PropertyId::NonCustom(non_custom_id) => id.is_or_is_longhand_of(non_custom_id),
PropertyId::Custom(_) => false,
},
PropertyDeclarationId::Custom(name) => {
matches!(*other, PropertyId::Custom(ref other_name) if name == other_name)
},
}
}
pub fn is_longhand_of(&self, shorthand: ShorthandId) -> bool {
match *self {
PropertyDeclarationId::Longhand(ref id) => id.is_longhand_of(shorthand),
_ => false,
}
}
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()
},
}
}
#[inline]
pub fn as_longhand(&self) -> Option<LonghandId> {
match *self {
PropertyDeclarationId::Longhand(id) => Some(id),
_ => None,
}
}
#[inline]
pub fn is_logical(&self) -> bool {
match self {
PropertyDeclarationId::Longhand(id) => id.is_logical(),
PropertyDeclarationId::Custom(_) => false,
}
}
#[inline]
pub fn to_physical(&self, wm: WritingMode) -> Self {
match self {
Self::Longhand(id) => Self::Longhand(id.to_physical(wm)),
Self::Custom(_) => self.clone(),
}
}
#[inline]
pub fn is_animatable(&self) -> bool {
match self {
Self::Longhand(id) => id.is_animatable(),
Self::Custom(_) => true,
}
}
#[inline]
pub fn is_discrete_animatable(&self) -> bool {
match self {
Self::Longhand(longhand) => longhand.is_discrete_animatable(),
Self::Custom(_) => true,
}
}
#[cfg(feature = "gecko")]
#[inline]
pub fn to_noncustomcsspropertyid(self) -> NonCustomCSSPropertyId {
match self {
PropertyDeclarationId::Longhand(id) => id.to_noncustomcsspropertyid(),
PropertyDeclarationId::Custom(_) => NonCustomCSSPropertyId::eCSSPropertyExtra_variable,
}
}
#[cfg(feature = "gecko")]
#[inline]
pub fn to_gecko_css_property_id(&self) -> CSSPropertyId {
match self {
Self::Longhand(id) => CSSPropertyId {
mId: id.to_noncustomcsspropertyid(),
mCustomName: RefPtr::null(),
},
Self::Custom(name) => {
let mut property_id = CSSPropertyId {
mId: NonCustomCSSPropertyId::eCSSPropertyExtra_variable,
mCustomName: RefPtr::null(),
};
property_id.mCustomName.mRawPtr = (*name).clone().into_addrefed();
property_id
},
}
}
}
#[derive(Clone, PartialEq, Default)]
pub struct NonCustomPropertyIdSet {
storage: [u32; ((property_counts::NON_CUSTOM as usize) - 1 + 32) / 32],
}
impl NonCustomPropertyIdSet {
pub fn new() -> Self {
Self {
storage: Default::default(),
}
}
#[inline]
pub fn insert(&mut self, id: NonCustomPropertyId) {
let bit = id.0 as usize;
self.storage[bit / 32] |= 1 << (bit % 32);
}
#[inline]
pub fn contains(&self, id: NonCustomPropertyId) -> bool {
let bit = id.0 as usize;
(self.storage[bit / 32] & (1 << (bit % 32))) != 0
}
}
#[derive(Clone, Copy, Debug, Default, MallocSizeOf, PartialEq)]
pub struct LonghandIdSet {
storage: [u32; ((property_counts::LONGHANDS as usize) - 1 + 32) / 32],
}
to_shmem::impl_trivial_to_shmem!(LonghandIdSet);
impl LonghandIdSet {
#[inline]
pub fn new() -> Self {
Self {
storage: Default::default(),
}
}
pub fn iter(&self) -> LonghandIdSetIterator<'_> {
LonghandIdSetIterator {
chunks: &self.storage,
cur_chunk: 0,
cur_bit: 0,
}
}
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
}
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
}
#[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;
}
}
#[inline]
pub fn contains(&self, id: LonghandId) -> bool {
let bit = id as usize;
(self.storage[bit / 32] & (1 << (bit % 32))) != 0
}
#[inline]
pub fn contains_any_reset(&self) -> bool {
self.contains_any(Self::reset())
}
#[inline]
pub fn insert(&mut self, id: LonghandId) {
let bit = id as usize;
self.storage[bit / 32] |= 1 << (bit % 32);
}
#[inline]
pub fn remove(&mut self, id: LonghandId) {
let bit = id as usize;
self.storage[bit / 32] &= !(1 << (bit % 32));
}
#[inline]
pub fn clear(&mut self) {
for cell in &mut self.storage {
*cell = 0
}
}
#[inline]
pub fn is_empty(&self) -> bool {
self.storage.iter().all(|c| *c == 0)
}
}
pub struct LonghandIdSetIterator<'a> {
chunks: &'a [u32],
cur_chunk: u32,
cur_bit: u32, }
impl<'a> Iterator for LonghandIdSetIterator<'a> {
type Item = LonghandId;
fn next(&mut self) -> Option<Self::Item> {
loop {
debug_assert!(self.cur_bit < 32);
let cur_chunk = self.cur_chunk;
let cur_bit = self.cur_bit;
let chunk = *self.chunks.get(cur_chunk as usize)?;
let next_bit = (chunk >> cur_bit).trailing_zeros();
if next_bit == 32 {
self.cur_bit = 0;
self.cur_chunk += 1;
continue;
}
debug_assert!(cur_bit + next_bit < 32);
let longhand_id = cur_chunk * 32 + cur_bit + next_bit;
debug_assert!(longhand_id as usize <= property_counts::LONGHANDS);
let id: LonghandId = unsafe { mem::transmute(longhand_id as u16) };
self.cur_bit += next_bit + 1;
if self.cur_bit == 32 {
self.cur_bit = 0;
self.cur_chunk += 1;
}
return Some(id);
}
}
}
pub type SubpropertiesVec<T> = ArrayVec<T, { property_counts::MAX_SHORTHAND_EXPANDED }>;
#[derive(Default)]
pub struct SourcePropertyDeclaration {
pub declarations: SubpropertiesVec<PropertyDeclaration>,
pub all_shorthand: AllShorthand,
}
#[cfg(feature = "gecko")]
size_of_test!(SourcePropertyDeclaration, 632);
#[cfg(feature = "servo")]
size_of_test!(SourcePropertyDeclaration, 568);
impl SourcePropertyDeclaration {
#[inline]
pub fn with_one(decl: PropertyDeclaration) -> Self {
let mut result = Self::default();
result.declarations.push(decl);
result
}
pub fn drain(&mut self) -> SourcePropertyDeclarationDrain<'_> {
SourcePropertyDeclarationDrain {
declarations: self.declarations.drain(..),
all_shorthand: mem::replace(&mut self.all_shorthand, AllShorthand::NotSet),
}
}
pub fn clear(&mut self) {
self.declarations.clear();
self.all_shorthand = AllShorthand::NotSet;
}
pub fn is_empty(&self) -> bool {
self.declarations.is_empty() && matches!(self.all_shorthand, AllShorthand::NotSet)
}
pub fn push(&mut self, declaration: PropertyDeclaration) {
let _result = self.declarations.try_push(declaration);
debug_assert!(_result.is_ok());
}
}
pub struct SourcePropertyDeclarationDrain<'a> {
pub declarations:
ArrayVecDrain<'a, PropertyDeclaration, { property_counts::MAX_SHORTHAND_EXPANDED }>,
pub all_shorthand: AllShorthand,
}
#[derive(Debug, Eq, PartialEq, ToShmem)]
pub struct UnparsedValue {
pub(super) variable_value: custom_properties::VariableValue,
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() {
self.variable_value.to_css(dest)?;
}
Ok(())
}
}
impl ToTyped for UnparsedValue {
fn to_typed(&self, dest: &mut ThinVec<TypedValue>) -> Result<(), ()> {
if self.from_shorthand.is_none() {
self.variable_value.to_typed(dest)?;
return Ok(());
}
Err(())
}
}
pub type ShorthandsWithPropertyReferencesCache =
FxHashMap<(ShorthandId, LonghandId), PropertyDeclaration>;
impl UnparsedValue {
fn substitute_variables<'cache>(
&self,
longhand_id: LonghandId,
substitution_functions: &ComputedSubstitutionFunctions,
stylist: &Stylist,
computed_context: &computed::Context,
shorthand_cache: &'cache mut ShorthandsWithPropertyReferencesCache,
attribute_tracker: &mut AttributeTracker,
) -> Cow<'cache, PropertyDeclaration> {
let invalid_at_computed_value_time = || {
let keyword = if longhand_id.inherited() {
CSSWideKeyword::Inherit
} else {
CSSWideKeyword::Initial
};
Cow::Owned(PropertyDeclaration::css_wide_keyword(longhand_id, keyword))
};
if computed_context
.builder
.invalid_non_custom_properties
.contains(longhand_id)
{
return invalid_at_computed_value_time();
}
if let Some(shorthand_id) = self.from_shorthand {
let key = (shorthand_id, longhand_id);
if shorthand_cache.contains_key(&key) {
return Cow::Borrowed(&shorthand_cache[&key]);
}
}
let SubstitutionResult { css, attr_taint } = match custom_properties::substitute(
&self.variable_value,
substitution_functions,
stylist,
computed_context,
attribute_tracker,
) {
Ok(css) => css,
Err(..) => return invalid_at_computed_value_time(),
};
let context = ParserContext::new(
Origin::Author,
&self.variable_value.url_data,
None,
ParsingMode::DEFAULT,
computed_context.quirks_mode,
Default::default(),
None,
None,
attr_taint,
);
let mut input = ParserInput::new(&css);
let mut input = Parser::new(&mut input);
input.skip_whitespace();
if let Ok(keyword) = input.try_parse(CSSWideKeyword::parse) {
return Cow::Owned(PropertyDeclaration::css_wide_keyword(longhand_id, keyword));
}
let shorthand = match self.from_shorthand {
None => {
return match input.parse_entirely(|input| longhand_id.parse_value(&context, input))
{
Ok(decl) => Cow::Owned(decl),
Err(..) => invalid_at_computed_value_time(),
}
},
Some(shorthand) => shorthand,
};
let mut decls = SourcePropertyDeclaration::default();
if shorthand
.parse_into(&mut decls, &context, &mut input)
.is_err()
{
return invalid_at_computed_value_time();
}
for declaration in decls.declarations.drain(..) {
let longhand = declaration.id().as_longhand().unwrap();
if longhand.is_logical() {
let writing_mode = computed_context.builder.writing_mode;
shorthand_cache.insert(
(shorthand, longhand.to_physical(writing_mode)),
declaration.clone(),
);
}
shorthand_cache.insert((shorthand, longhand), declaration);
}
let key = (shorthand, longhand_id);
match shorthand_cache.get(&key) {
Some(decl) => Cow::Borrowed(decl),
None => invalid_at_computed_value_time(),
}
}
}
pub enum AllShorthand {
NotSet,
CSSWideKeyword(CSSWideKeyword),
WithVariables(Arc<UnparsedValue>),
}
impl Default for AllShorthand {
fn default() -> Self {
Self::NotSet
}
}
impl AllShorthand {
#[inline]
pub fn declarations(&self) -> AllShorthandDeclarationIterator<'_> {
AllShorthandDeclarationIterator {
all_shorthand: self,
longhands: ShorthandId::All.longhands(),
}
}
}
pub struct AllShorthandDeclarationIterator<'a> {
all_shorthand: &'a AllShorthand,
longhands: NonCustomPropertyIterator<LonghandId>,
}
impl<'a> Iterator for AllShorthandDeclarationIterator<'a> {
type Item = PropertyDeclaration;
#[inline]
fn next(&mut self) -> Option<Self::Item> {
match *self.all_shorthand {
AllShorthand::NotSet => None,
AllShorthand::CSSWideKeyword(ref keyword) => Some(
PropertyDeclaration::css_wide_keyword(self.longhands.next()?, *keyword),
),
AllShorthand::WithVariables(ref unparsed) => {
Some(PropertyDeclaration::WithVariables(VariableDeclaration {
id: self.longhands.next()?,
value: unparsed.clone(),
}))
},
}
}
}
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);
}
}
}
}
pub struct TransitionPropertyIterator<'a> {
style: &'a ComputedValues,
index_range: core::ops::Range<usize>,
longhand_iterator: Option<NonCustomPropertyIterator<LonghandId>>,
}
impl<'a> TransitionPropertyIterator<'a> {
pub fn from_style(style: &'a ComputedValues) -> Self {
Self {
style,
index_range: 0..style.get_ui().transition_property_count(),
longhand_iterator: None,
}
}
}
pub struct TransitionPropertyIteration {
pub property: OwnedPropertyDeclarationId,
pub index: usize,
}
impl<'a> Iterator for TransitionPropertyIterator<'a> {
type Item = TransitionPropertyIteration;
fn next(&mut self) -> Option<Self::Item> {
use crate::values::computed::TransitionProperty;
loop {
if let Some(ref mut longhand_iterator) = self.longhand_iterator {
if let Some(longhand_id) = longhand_iterator.next() {
return Some(TransitionPropertyIteration {
property: OwnedPropertyDeclarationId::Longhand(longhand_id),
index: self.index_range.start - 1,
});
}
self.longhand_iterator = None;
}
let index = self.index_range.next()?;
match self.style.get_ui().transition_property_at(index) {
TransitionProperty::NonCustom(id) => {
match id.longhand_or_shorthand() {
Ok(longhand_id) => {
return Some(TransitionPropertyIteration {
property: OwnedPropertyDeclarationId::Longhand(longhand_id),
index,
});
},
Err(shorthand_id) => {
self.longhand_iterator = Some(shorthand_id.longhands());
},
}
},
TransitionProperty::Custom(name) => {
return Some(TransitionPropertyIteration {
property: OwnedPropertyDeclarationId::Custom(name),
index,
})
},
TransitionProperty::Unsupported(..) => {},
}
}
}
}