servo/components/style/values/mod.rs
Emilio Cobos Álvarez 8bb7d98f0c style: Add support for the revert-layer keyword
This patch looks bigger than it is, but it's mostly because
of plumbing.

To implement revert-layer we need not only the cascade origin of the
declaration, but the whole cascade level, plus also the layer order.

In order to do this, encapsulate these two things inside a 32-bit
`CascadePriority` struct and plumb it through the rule tree and so on.
This allows us to remove the packing and unpacking of CascadeLevel,
though I kept the ShadowCascadeOrder limit for now in case we need to
reintroduce it.

Fix `!important` behavior of layers while at it (implementing it in
`CascadeLevel::cmp`, spec quote included since it was tricky to find)
since some revert-layer tests were depending on it.

The style attribute test is failing now, but follow-up commit fixes
it, see spec issue.

In terms of the actual keyword implementation, it's sort of
straight-forward: We implement revert and revert-layer in a shared
way, by storing the cascade priority that reverted it.

Differential Revision: https://phabricator.services.mozilla.com/D133372
2023-06-09 10:22:22 +02:00

579 lines
15 KiB
Rust

/* 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/. */
//! Common [values][values] used in CSS.
//!
//! [values]: https://drafts.csswg.org/css-values/
#![deny(missing_docs)]
use crate::parser::{Parse, ParserContext};
use crate::values::distance::{ComputeSquaredDistance, SquaredDistance};
use crate::Atom;
pub use cssparser::{serialize_identifier, serialize_name, CowRcStr, Parser};
pub use cssparser::{SourceLocation, Token, RGBA};
use precomputed_hash::PrecomputedHash;
use selectors::parser::SelectorParseErrorKind;
use std::fmt::{self, Debug, Write};
use std::hash;
use style_traits::{CssWriter, ParseError, StyleParseErrorKind, ToCss};
use to_shmem::impl_trivial_to_shmem;
#[cfg(feature = "gecko")]
pub use crate::gecko::url::CssUrl;
#[cfg(feature = "servo")]
pub use crate::servo::url::CssUrl;
pub mod animated;
pub mod computed;
pub mod distance;
pub mod generics;
pub mod resolved;
pub mod specified;
/// A CSS float value.
pub type CSSFloat = f32;
/// Normalizes a float value to zero after a set of operations that might turn
/// it into NaN.
#[inline]
pub fn normalize(v: CSSFloat) -> CSSFloat {
if v.is_nan() {
0.0
} else {
v
}
}
/// A CSS integer value.
pub type CSSInteger = i32;
define_keyword_type!(None_, "none");
define_keyword_type!(Auto, "auto");
/// Serialize an identifier which is represented as an atom.
#[cfg(feature = "gecko")]
pub fn serialize_atom_identifier<W>(ident: &Atom, dest: &mut W) -> fmt::Result
where
W: Write,
{
ident.with_str(|s| serialize_identifier(s, dest))
}
/// Serialize an identifier which is represented as an atom.
#[cfg(feature = "servo")]
pub fn serialize_atom_identifier<Static, W>(
ident: &::string_cache::Atom<Static>,
dest: &mut W,
) -> fmt::Result
where
Static: string_cache::StaticAtomSet,
W: Write,
{
serialize_identifier(&ident, dest)
}
/// Serialize a name which is represented as an Atom.
#[cfg(feature = "gecko")]
pub fn serialize_atom_name<W>(ident: &Atom, dest: &mut W) -> fmt::Result
where
W: Write,
{
ident.with_str(|s| serialize_name(s, dest))
}
/// Serialize a name which is represented as an Atom.
#[cfg(feature = "servo")]
pub fn serialize_atom_name<Static, W>(
ident: &::string_cache::Atom<Static>,
dest: &mut W,
) -> fmt::Result
where
Static: string_cache::StaticAtomSet,
W: Write,
{
serialize_name(&ident, dest)
}
/// A CSS string stored as an `Atom`.
#[repr(transparent)]
#[derive(
Clone,
Debug,
Default,
Deref,
Eq,
Hash,
MallocSizeOf,
PartialEq,
SpecifiedValueInfo,
ToComputedValue,
ToResolvedValue,
ToShmem,
)]
pub struct AtomString(pub Atom);
#[cfg(feature = "servo")]
impl AsRef<str> for AtomString {
fn as_ref(&self) -> &str {
&*self.0
}
}
impl cssparser::ToCss for AtomString {
fn to_css<W>(&self, dest: &mut W) -> fmt::Result
where
W: Write,
{
#[cfg(feature = "servo")]
{
cssparser::CssStringWriter::new(dest).write_str(self.as_ref())
}
#[cfg(feature = "gecko")]
{
self.0
.with_str(|s| cssparser::CssStringWriter::new(dest).write_str(s))
}
}
}
impl PrecomputedHash for AtomString {
#[inline]
fn precomputed_hash(&self) -> u32 {
self.0.precomputed_hash()
}
}
impl<'a> From<&'a str> for AtomString {
#[inline]
fn from(string: &str) -> Self {
Self(Atom::from(string))
}
}
/// A generic CSS `<ident>` stored as an `Atom`.
#[cfg(feature = "servo")]
#[repr(transparent)]
#[derive(Deref)]
pub struct GenericAtomIdent<Set>(pub string_cache::Atom<Set>)
where
Set: string_cache::StaticAtomSet;
/// A generic CSS `<ident>` stored as an `Atom`, for the default atom set.
#[cfg(feature = "servo")]
pub type AtomIdent = GenericAtomIdent<servo_atoms::AtomStaticSet>;
#[cfg(feature = "servo")]
impl<Set: string_cache::StaticAtomSet> style_traits::SpecifiedValueInfo for GenericAtomIdent<Set> {}
#[cfg(feature = "servo")]
impl<Set: string_cache::StaticAtomSet> Default for GenericAtomIdent<Set> {
fn default() -> Self {
Self(string_cache::Atom::default())
}
}
#[cfg(feature = "servo")]
impl<Set: string_cache::StaticAtomSet> std::fmt::Debug for GenericAtomIdent<Set> {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
self.0.fmt(f)
}
}
#[cfg(feature = "servo")]
impl<Set: string_cache::StaticAtomSet> std::hash::Hash for GenericAtomIdent<Set> {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.0.hash(state)
}
}
#[cfg(feature = "servo")]
impl<Set: string_cache::StaticAtomSet> Eq for GenericAtomIdent<Set> {}
#[cfg(feature = "servo")]
impl<Set: string_cache::StaticAtomSet> PartialEq for GenericAtomIdent<Set> {
fn eq(&self, other: &Self) -> bool {
self.0 == other.0
}
}
#[cfg(feature = "servo")]
impl<Set: string_cache::StaticAtomSet> Clone for GenericAtomIdent<Set> {
fn clone(&self) -> Self {
Self(self.0.clone())
}
}
#[cfg(feature = "servo")]
impl<Set: string_cache::StaticAtomSet> to_shmem::ToShmem for GenericAtomIdent<Set> {
fn to_shmem(&self, builder: &mut to_shmem::SharedMemoryBuilder) -> to_shmem::Result<Self> {
use std::mem::ManuallyDrop;
let atom = self.0.to_shmem(builder)?;
Ok(ManuallyDrop::new(Self(ManuallyDrop::into_inner(atom))))
}
}
#[cfg(feature = "servo")]
impl<Set: string_cache::StaticAtomSet> malloc_size_of::MallocSizeOf for GenericAtomIdent<Set> {
fn size_of(&self, ops: &mut malloc_size_of::MallocSizeOfOps) -> usize {
self.0.size_of(ops)
}
}
#[cfg(feature = "servo")]
impl<Set: string_cache::StaticAtomSet> cssparser::ToCss for GenericAtomIdent<Set> {
fn to_css<W>(&self, dest: &mut W) -> fmt::Result
where
W: Write,
{
serialize_atom_identifier(&self.0, dest)
}
}
#[cfg(feature = "servo")]
impl<Set: string_cache::StaticAtomSet> PrecomputedHash for GenericAtomIdent<Set> {
#[inline]
fn precomputed_hash(&self) -> u32 {
self.0.precomputed_hash()
}
}
#[cfg(feature = "servo")]
impl<'a, Set: string_cache::StaticAtomSet> From<&'a str> for GenericAtomIdent<Set> {
#[inline]
fn from(string: &str) -> Self {
Self(string_cache::Atom::from(string))
}
}
#[cfg(feature = "servo")]
impl<Set: string_cache::StaticAtomSet> std::borrow::Borrow<string_cache::Atom<Set>>
for GenericAtomIdent<Set>
{
#[inline]
fn borrow(&self) -> &string_cache::Atom<Set> {
&self.0
}
}
#[cfg(feature = "servo")]
impl<Set: string_cache::StaticAtomSet> GenericAtomIdent<Set> {
/// Constructs a new GenericAtomIdent.
#[inline]
pub fn new(atom: string_cache::Atom<Set>) -> Self {
Self(atom)
}
/// Cast an atom ref to an AtomIdent ref.
#[inline]
pub fn cast<'a>(atom: &'a string_cache::Atom<Set>) -> &'a Self {
let ptr = atom as *const _ as *const Self;
// safety: repr(transparent)
unsafe { &*ptr }
}
}
/// A CSS `<ident>` stored as an `Atom`.
#[cfg(feature = "gecko")]
#[repr(transparent)]
#[derive(
Clone, Debug, Default, Deref, Eq, Hash, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToShmem,
)]
pub struct AtomIdent(pub Atom);
#[cfg(feature = "gecko")]
impl cssparser::ToCss for AtomIdent {
fn to_css<W>(&self, dest: &mut W) -> fmt::Result
where
W: Write,
{
serialize_atom_identifier(&self.0, dest)
}
}
#[cfg(feature = "gecko")]
impl PrecomputedHash for AtomIdent {
#[inline]
fn precomputed_hash(&self) -> u32 {
self.0.precomputed_hash()
}
}
#[cfg(feature = "gecko")]
impl<'a> From<&'a str> for AtomIdent {
#[inline]
fn from(string: &str) -> Self {
Self(Atom::from(string))
}
}
#[cfg(feature = "gecko")]
impl AtomIdent {
/// Constructs a new AtomIdent.
#[inline]
pub fn new(atom: Atom) -> Self {
Self(atom)
}
/// Like `Atom::with` but for `AtomIdent`.
pub unsafe fn with<F, R>(ptr: *const crate::gecko_bindings::structs::nsAtom, callback: F) -> R
where
F: FnOnce(&Self) -> R,
{
Atom::with(ptr, |atom: &Atom| {
// safety: repr(transparent)
let atom = atom as *const Atom as *const AtomIdent;
callback(&*atom)
})
}
}
#[cfg(feature = "gecko")]
impl std::borrow::Borrow<crate::gecko_string_cache::WeakAtom> for AtomIdent {
#[inline]
fn borrow(&self) -> &crate::gecko_string_cache::WeakAtom {
self.0.borrow()
}
}
/// Serialize a normalized value into percentage.
pub fn serialize_percentage<W>(value: CSSFloat, dest: &mut CssWriter<W>) -> fmt::Result
where
W: Write,
{
(value * 100.).to_css(dest)?;
dest.write_str("%")
}
/// Convenience void type to disable some properties and values through types.
#[cfg_attr(feature = "servo", derive(Deserialize, MallocSizeOf, Serialize))]
#[derive(
Clone,
Copy,
Debug,
PartialEq,
SpecifiedValueInfo,
ToAnimatedValue,
ToComputedValue,
ToCss,
ToResolvedValue,
)]
pub enum Impossible {}
// FIXME(nox): This should be derived but the derive code cannot cope
// with uninhabited enums.
impl ComputeSquaredDistance for Impossible {
#[inline]
fn compute_squared_distance(&self, _other: &Self) -> Result<SquaredDistance, ()> {
match *self {}
}
}
impl_trivial_to_shmem!(Impossible);
impl Parse for Impossible {
fn parse<'i, 't>(
_context: &ParserContext,
input: &mut Parser<'i, 't>,
) -> Result<Self, ParseError<'i>> {
Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError))
}
}
/// A struct representing one of two kinds of values.
#[derive(
Animate,
Clone,
ComputeSquaredDistance,
Copy,
MallocSizeOf,
PartialEq,
Parse,
SpecifiedValueInfo,
ToAnimatedValue,
ToAnimatedZero,
ToComputedValue,
ToCss,
ToResolvedValue,
ToShmem,
)]
pub enum Either<A, B> {
/// The first value.
First(A),
/// The second kind of value.
Second(B),
}
impl<A: Debug, B: Debug> Debug for Either<A, B> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
Either::First(ref v) => v.fmt(f),
Either::Second(ref v) => v.fmt(f),
}
}
}
/// <https://drafts.csswg.org/css-values-4/#custom-idents>
#[derive(
Clone,
Debug,
Eq,
Hash,
MallocSizeOf,
PartialEq,
SpecifiedValueInfo,
ToComputedValue,
ToResolvedValue,
ToShmem,
)]
#[repr(C)]
pub struct CustomIdent(pub Atom);
impl CustomIdent {
/// Parse an already-tokenizer identifier
pub fn from_ident<'i>(
location: SourceLocation,
ident: &CowRcStr<'i>,
excluding: &[&str],
) -> Result<Self, ParseError<'i>> {
use crate::properties::CSSWideKeyword;
// https://drafts.csswg.org/css-values-4/#custom-idents:
//
// The CSS-wide keywords are not valid <custom-ident>s. The default
// keyword is reserved and is also not a valid <custom-ident>.
//
if CSSWideKeyword::from_ident(ident).is_ok() || ident.eq_ignore_ascii_case("default") {
return Err(
location.new_custom_error(SelectorParseErrorKind::UnexpectedIdent(ident.clone()))
);
}
// https://drafts.csswg.org/css-values-4/#custom-idents:
//
// Excluded keywords are excluded in all ASCII case permutations.
//
if excluding.iter().any(|s| ident.eq_ignore_ascii_case(s)) {
Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError))
} else {
Ok(CustomIdent(Atom::from(ident.as_ref())))
}
}
}
impl ToCss for CustomIdent {
fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
where
W: Write,
{
serialize_atom_identifier(&self.0, dest)
}
}
/// The <timeline-name> or <keyframes-name>.
/// The definition of these two names are the same, so we use the same type for them.
///
/// <https://drafts.csswg.org/css-animations-2/#typedef-timeline-name>
/// <https://drafts.csswg.org/css-animations/#typedef-keyframes-name>
#[derive(
Clone, Debug, MallocSizeOf, SpecifiedValueInfo, ToComputedValue, ToResolvedValue, ToShmem,
)]
#[repr(C, u8)]
pub enum TimelineOrKeyframesName {
/// <custom-ident>
Ident(CustomIdent),
/// <string>
QuotedString(Atom),
}
impl TimelineOrKeyframesName {
/// <https://drafts.csswg.org/css-animations/#dom-csskeyframesrule-name>
pub fn from_ident(value: &str) -> Self {
let location = SourceLocation { line: 0, column: 0 };
let custom_ident = CustomIdent::from_ident(location, &value.into(), &["none"]).ok();
match custom_ident {
Some(ident) => Self::Ident(ident),
None => Self::QuotedString(value.into()),
}
}
/// Create a new TimelineOrKeyframesName from Atom.
#[cfg(feature = "gecko")]
pub fn from_atom(atom: Atom) -> Self {
debug_assert_ne!(atom, atom!(""));
// FIXME: We might want to preserve <string>, but currently Gecko
// stores both of <custom-ident> and <string> into nsAtom, so
// we can't tell it.
Self::Ident(CustomIdent(atom))
}
/// The name as an Atom
pub fn as_atom(&self) -> &Atom {
match *self {
Self::Ident(ref ident) => &ident.0,
Self::QuotedString(ref atom) => atom,
}
}
}
impl Eq for TimelineOrKeyframesName {}
/// A trait that returns whether a given type is the `auto` value or not. So far
/// only needed for background-size serialization, which special-cases `auto`.
pub trait IsAuto {
/// Returns whether the value is the `auto` value.
fn is_auto(&self) -> bool;
}
impl PartialEq for TimelineOrKeyframesName {
fn eq(&self, other: &Self) -> bool {
self.as_atom() == other.as_atom()
}
}
impl hash::Hash for TimelineOrKeyframesName {
fn hash<H>(&self, state: &mut H)
where
H: hash::Hasher,
{
self.as_atom().hash(state)
}
}
impl Parse for TimelineOrKeyframesName {
fn parse<'i, 't>(
_context: &ParserContext,
input: &mut Parser<'i, 't>,
) -> Result<Self, ParseError<'i>> {
let location = input.current_source_location();
match *input.next()? {
Token::Ident(ref s) => Ok(Self::Ident(CustomIdent::from_ident(
location,
s,
&["none"],
)?)),
Token::QuotedString(ref s) => Ok(Self::QuotedString(Atom::from(s.as_ref()))),
ref t => Err(location.new_unexpected_token_error(t.clone())),
}
}
}
impl ToCss for TimelineOrKeyframesName {
fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
where
W: Write,
{
match *self {
Self::Ident(ref ident) => ident.to_css(dest),
Self::QuotedString(ref atom) => atom.to_string().to_css(dest),
}
}
}
/// The typedef of <timeline-name>.
pub type TimelineName = TimelineOrKeyframesName;
/// The typedef of <keyframes-name>.
pub type KeyframesName = TimelineOrKeyframesName;