style: Part 1: Implement @scroll-timeline in style system

Define the data structure for @scroll-timeline rule, the parsing code,
and the serialization.

Differential Revision: https://phabricator.services.mozilla.com/D125764
This commit is contained in:
Boris Chiou 2023-05-27 07:53:35 +02:00 committed by Oriol Brufau
parent 3e251f50fc
commit 111c8d616f
3 changed files with 345 additions and 17 deletions

View file

@ -20,6 +20,7 @@ mod page_rule;
mod rule_list;
mod rule_parser;
mod rules_iterator;
pub mod scroll_timeline_rule;
mod style_rule;
mod stylesheet;
pub mod supports_rule;

View file

@ -0,0 +1,315 @@
/* 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/. */
//! scroll-timeline-at-rule: https://drafts.csswg.org/scroll-animations/#scroll-timeline-at-rule
use crate::parser::{Parse, ParserContext};
use crate::shared_lock::{SharedRwLockReadGuard, ToCssWithGuard};
use crate::str::CssStringWriter;
use crate::values::specified::{LengthPercentage, Number};
use crate::values::{AtomIdent, TimelineName};
use cssparser::{AtRuleParser, CowRcStr, DeclarationParser, Parser, SourceLocation, Token};
use selectors::parser::SelectorParseErrorKind;
use std::fmt::{self, Debug, Write};
use style_traits::{CssWriter, ParseError, StyleParseErrorKind, ToCss};
/// A [`@scroll-timeline`][descriptors] rule.
///
/// [descriptors] https://drafts.csswg.org/scroll-animations/#scroll-timeline-descriptors
#[derive(Clone, Debug, ToShmem)]
pub struct ScrollTimelineRule {
/// The name of the current scroll timeline.
pub name: TimelineName,
/// The descriptors.
pub descriptors: ScrollTimelineDescriptors,
/// The line and column of the rule's source code.
pub source_location: SourceLocation,
}
impl ToCssWithGuard for ScrollTimelineRule {
fn to_css(&self, _guard: &SharedRwLockReadGuard, dest: &mut CssStringWriter) -> fmt::Result {
let mut dest = CssWriter::new(dest);
dest.write_str("@scroll-timeline ")?;
self.name.to_css(&mut dest)?;
dest.write_str(" { ")?;
self.descriptors.to_css(&mut dest)?;
dest.write_str("}")
}
}
/// The descriptors of @scroll-timeline.
///
/// https://drafts.csswg.org/scroll-animations/#scroll-timeline-descriptors
#[derive(Clone, Debug, Default, ToShmem)]
pub struct ScrollTimelineDescriptors {
/// The source of the current scroll timeline.
pub source: Option<Source>,
/// The orientation of the current scroll timeline.
pub orientation: Option<Orientation>,
/// The scroll timeline's scrollOffsets.
pub offsets: Option<ScrollOffsets>,
}
impl Parse for ScrollTimelineDescriptors {
fn parse<'i, 't>(
context: &ParserContext,
input: &mut Parser<'i, 't>,
) -> Result<Self, ParseError<'i>> {
use crate::cssparser::DeclarationListParser;
use crate::error_reporting::ContextualParseError;
let mut descriptors = ScrollTimelineDescriptors::default();
let parser = ScrollTimelineDescriptorsParser {
context,
descriptors: &mut descriptors,
};
let mut iter = DeclarationListParser::new(input, parser);
while let Some(declaration) = iter.next() {
if let Err((error, slice)) = declaration {
let location = error.location;
let error = ContextualParseError::UnsupportedRule(slice, error);
context.log_css_error(location, error)
}
}
Ok(descriptors)
}
}
// Basically, this is used for the serialization of CSSScrollTimelineRule, so we follow the
// instructions in https://drafts.csswg.org/scroll-animations-1/#serialize-a-cssscrolltimelinerule.
impl ToCss for ScrollTimelineDescriptors {
fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
where
W: Write,
{
if let Some(ref value) = self.source {
dest.write_str("source: ")?;
value.to_css(dest)?;
dest.write_str("; ")?;
}
if let Some(ref value) = self.orientation {
dest.write_str("orientation: ")?;
value.to_css(dest)?;
dest.write_str("; ")?;
}
// https://github.com/w3c/csswg-drafts/issues/6617
if let Some(ref value) = self.offsets {
dest.write_str("scroll-offsets: ")?;
value.to_css(dest)?;
dest.write_str("; ")?;
}
Ok(())
}
}
struct ScrollTimelineDescriptorsParser<'a, 'b: 'a> {
context: &'a ParserContext<'b>,
descriptors: &'a mut ScrollTimelineDescriptors,
}
impl<'a, 'b, 'i> AtRuleParser<'i> for ScrollTimelineDescriptorsParser<'a, 'b> {
type Prelude = ();
type AtRule = ();
type Error = StyleParseErrorKind<'i>;
}
impl<'a, 'b, 'i> DeclarationParser<'i> for ScrollTimelineDescriptorsParser<'a, 'b> {
type Declaration = ();
type Error = StyleParseErrorKind<'i>;
fn parse_value<'t>(
&mut self,
name: CowRcStr<'i>,
input: &mut Parser<'i, 't>,
) -> Result<(), ParseError<'i>> {
macro_rules! parse_descriptor {
(
$( $name: tt / $ident: ident, )*
) => {
match_ignore_ascii_case! { &*name,
$(
$name => {
let value = input.parse_entirely(|i| Parse::parse(self.context, i))?;
self.descriptors.$ident = Some(value)
},
)*
_ => {
return Err(input.new_custom_error(
SelectorParseErrorKind::UnexpectedIdent(name.clone()),
))
}
}
}
}
parse_descriptor! {
"source" / source,
"orientation" / orientation,
"scroll-offsets" / offsets,
};
Ok(())
}
}
/// The scroll-timeline source.
///
/// https://drafts.csswg.org/scroll-animations/#descdef-scroll-timeline-source
#[derive(Clone, Debug, Parse, PartialEq, ToCss, ToShmem)]
pub enum Source {
/// The scroll container.
Selector(ScrollTimelineSelector),
/// The initial value. The scrollingElement of the Document associated with the Window that is
/// the current global object.
Auto,
/// Null. However, it's not clear what is the expected behavior of this. See the spec issue:
/// https://drafts.csswg.org/scroll-animations/#issue-0d1e73bd
None,
}
/// The scroll-timeline orientation.
/// https://drafts.csswg.org/scroll-animations/#descdef-scroll-timeline-orientation
///
/// Note: the initial orientation is auto, and we will treat it as block, the same as the
/// definition of ScrollTimelineOptions (WebIDL API).
/// https://drafts.csswg.org/scroll-animations/#dom-scrolltimelineoptions-orientation
#[derive(Clone, Copy, Debug, MallocSizeOf, Eq, Parse, PartialEq, PartialOrd, ToCss, ToShmem)]
pub enum Orientation {
/// The initial value.
Auto,
/// The direction along the block axis. This is the default value.
Block,
/// The direction along the inline axis
Inline,
/// The physical horizontal direction.
Horizontal,
/// The physical vertical direction.
Vertical,
}
/// Scroll-timeline offsets. We treat None as an empty vector.
/// value: none | <scroll-timeline-offset>#
///
/// https://drafts.csswg.org/scroll-animations/#descdef-scroll-timeline-scroll-offsets
#[derive(Clone, Debug, ToCss, ToShmem)]
#[css(comma)]
pub struct ScrollOffsets(#[css(if_empty = "none", iterable)] Box<[ScrollTimelineOffset]>);
impl Parse for ScrollOffsets {
fn parse<'i, 't>(
context: &ParserContext,
input: &mut Parser<'i, 't>,
) -> Result<Self, ParseError<'i>> {
if input.try_parse(|i| i.expect_ident_matching("none")).is_ok() {
return Ok(ScrollOffsets(Box::new([])));
}
Ok(ScrollOffsets(
input
.parse_comma_separated(|i| ScrollTimelineOffset::parse(context, i))?
.into_boxed_slice(),
))
}
}
/// A <scroll-timeline-offset>.
/// value: auto | <length-percentage> | <element-offset>
///
/// https://drafts.csswg.org/scroll-animations/#typedef-scroll-timeline-offset
#[derive(Clone, Debug, Parse, PartialEq, ToCss, ToShmem)]
pub enum ScrollTimelineOffset {
/// The initial value. A container-based offset.
Auto,
/// A container-based offset with the distance indicated by the value along source's scroll
/// range in orientation.
LengthPercentage(LengthPercentage),
/// An element-based offset.
ElementOffset(ElementOffset),
}
/// An <element-offset-edge>.
///
/// https://drafts.csswg.org/scroll-animations-1/#typedef-element-offset-edge
#[derive(Clone, Copy, Debug, MallocSizeOf, Eq, Parse, PartialEq, PartialOrd, ToCss, ToShmem)]
pub enum ElementOffsetEdge {
/// Start edge
Start,
/// End edge.
End,
}
/// An <element-offset>.
/// value: selector( <id-selector> ) [<element-offset-edge> || <number>]?
///
/// https://drafts.csswg.org/scroll-animations-1/#typedef-element-offset
#[derive(Clone, Debug, PartialEq, ToCss, ToShmem)]
pub struct ElementOffset {
/// The target whose intersection with source's scrolling box determines the concrete scroll
/// offset.
target: ScrollTimelineSelector,
/// An optional value of <element-offset-edge>. If not provided, the default value is start.
edge: Option<ElementOffsetEdge>,
/// An optional value of threshold. If not provided, the default value is 0.
threshold: Option<Number>,
}
impl Parse for ElementOffset {
fn parse<'i, 't>(
context: &ParserContext,
input: &mut Parser<'i, 't>,
) -> Result<Self, ParseError<'i>> {
let target = ScrollTimelineSelector::parse(context, input)?;
// Parse `[<element-offset-edge> || <number>]?`
let mut edge = input.try_parse(ElementOffsetEdge::parse).ok();
let threshold = input.try_parse(|i| Number::parse(context, i)).ok();
if edge.is_none() {
edge = input.try_parse(ElementOffsetEdge::parse).ok();
}
Ok(ElementOffset {
target,
edge,
threshold,
})
}
}
/// The type of the selector ID.
#[derive(Clone, Eq, PartialEq, ToShmem)]
pub struct ScrollTimelineSelector(AtomIdent);
impl Parse for ScrollTimelineSelector {
fn parse<'i, 't>(
_context: &ParserContext,
input: &mut Parser<'i, 't>,
) -> Result<Self, ParseError<'i>> {
// Parse `selector(<id-selector>)`.
input.expect_function_matching("selector")?;
input.parse_nested_block(|i| match i.next()? {
Token::IDHash(id) => Ok(ScrollTimelineSelector(id.as_ref().into())),
_ => Err(i.new_custom_error(StyleParseErrorKind::UnspecifiedError)),
})
}
}
impl ToCss for ScrollTimelineSelector {
fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
where
W: Write,
{
use crate::cssparser::ToCss as CssparserToCss;
dest.write_str("selector(")?;
dest.write_char('#')?;
self.0.to_css(dest)?;
dest.write_char(')')
}
}
impl Debug for ScrollTimelineSelector {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.to_css(&mut CssWriter::new(f))
}
}

View file

@ -464,29 +464,33 @@ impl ToCss for CustomIdent {
}
}
/// 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,
)]
pub enum KeyframesName {
pub enum TimelineOrKeyframesName {
/// <custom-ident>
Ident(CustomIdent),
/// <string>
QuotedString(Atom),
}
impl KeyframesName {
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) => KeyframesName::Ident(ident),
None => KeyframesName::QuotedString(value.into()),
Some(ident) => Self::Ident(ident),
None => Self::QuotedString(value.into()),
}
}
/// Create a new KeyframesName from Atom.
/// Create a new TimelineOrKeyframesName from Atom.
#[cfg(feature = "gecko")]
pub fn from_atom(atom: Atom) -> Self {
debug_assert_ne!(atom, atom!(""));
@ -494,19 +498,19 @@ impl KeyframesName {
// 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.
KeyframesName::Ident(CustomIdent(atom))
Self::Ident(CustomIdent(atom))
}
/// The name as an Atom
pub fn as_atom(&self) -> &Atom {
match *self {
KeyframesName::Ident(ref ident) => &ident.0,
KeyframesName::QuotedString(ref atom) => atom,
Self::Ident(ref ident) => &ident.0,
Self::QuotedString(ref atom) => atom,
}
}
}
impl Eq for KeyframesName {}
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`.
@ -515,13 +519,13 @@ pub trait IsAuto {
fn is_auto(&self) -> bool;
}
impl PartialEq for KeyframesName {
impl PartialEq for TimelineOrKeyframesName {
fn eq(&self, other: &Self) -> bool {
self.as_atom() == other.as_atom()
}
}
impl hash::Hash for KeyframesName {
impl hash::Hash for TimelineOrKeyframesName {
fn hash<H>(&self, state: &mut H)
where
H: hash::Hasher,
@ -530,32 +534,40 @@ impl hash::Hash for KeyframesName {
}
}
impl Parse for KeyframesName {
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(KeyframesName::Ident(CustomIdent::from_ident(
Token::Ident(ref s) => Ok(Self::Ident(CustomIdent::from_ident(
location,
s,
&["none"],
)?)),
Token::QuotedString(ref s) => Ok(KeyframesName::QuotedString(Atom::from(s.as_ref()))),
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 KeyframesName {
impl ToCss for TimelineOrKeyframesName {
fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
where
W: Write,
{
match *self {
KeyframesName::Ident(ref ident) => ident.to_css(dest),
KeyframesName::QuotedString(ref atom) => atom.to_string().to_css(dest),
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;