Auto merge of #16455 - servo:counter-style, r=upsuper

Bug 1354970 - Add @counter-style rules

<!-- Reviewable:start -->
This change is [<img src="https://reviewable.io/review_button.svg" height="34" align="absmiddle" alt="Reviewable"/>](https://reviewable.io/reviews/servo/servo/16455)
<!-- Reviewable:end -->
This commit is contained in:
bors-servo 2017-04-26 00:59:58 -05:00 committed by GitHub
commit 2eff661ebb
27 changed files with 1140 additions and 134 deletions

View file

@ -12,7 +12,20 @@ use std::path::Path;
fn main() {
let static_atoms = Path::new(&env::var("CARGO_MANIFEST_DIR").unwrap()).join("static_atoms.txt");
let static_atoms = BufReader::new(File::open(&static_atoms).unwrap());
string_cache_codegen::AtomType::new("Atom", "atom!")
let mut atom_type = string_cache_codegen::AtomType::new("Atom", "atom!");
macro_rules! predefined {
($($name: expr,)+) => {
{
$(
atom_type.atom($name);
)+
}
}
}
include!("../style/counter_style/predefined.rs");
atom_type
.atoms(static_atoms.lines().map(Result::unwrap))
.write_to_file(&Path::new(&env::var("OUT_DIR").unwrap()).join("atom.rs"))
.unwrap();

View file

@ -8,6 +8,8 @@ left
center
right
none
hidden
submit
button

View file

@ -273,6 +273,7 @@ impl<'a,'b> ResolveGeneratedContentFragmentMutator<'a,'b> {
self.traversal.list_item.truncate_to_level(self.level);
for &(ref counter_name, value) in &fragment.style().get_counters().counter_reset.0 {
let counter_name = &*counter_name.0;
if let Some(ref mut counter) = self.traversal.counters.get_mut(counter_name) {
counter.reset(self.level, value);
continue
@ -280,10 +281,11 @@ impl<'a,'b> ResolveGeneratedContentFragmentMutator<'a,'b> {
let mut counter = Counter::new();
counter.reset(self.level, value);
self.traversal.counters.insert((*counter_name).clone(), counter);
self.traversal.counters.insert(counter_name.to_owned(), counter);
}
for &(ref counter_name, value) in &fragment.style().get_counters().counter_increment.0 {
let counter_name = &*counter_name.0;
if let Some(ref mut counter) = self.traversal.counters.get_mut(counter_name) {
counter.increment(self.level, value);
continue
@ -291,7 +293,7 @@ impl<'a,'b> ResolveGeneratedContentFragmentMutator<'a,'b> {
let mut counter = Counter::new();
counter.increment(self.level, value);
self.traversal.counters.insert((*counter_name).clone(), counter);
self.traversal.counters.insert(counter_name.to_owned(), counter);
}
self.incremented = true

View file

@ -5,7 +5,7 @@
use cssparser::Parser;
use dom::bindings::codegen::Bindings::CSSKeyframesRuleBinding;
use dom::bindings::codegen::Bindings::CSSKeyframesRuleBinding::CSSKeyframesRuleMethods;
use dom::bindings::error::{Error, ErrorResult};
use dom::bindings::error::ErrorResult;
use dom::bindings::inheritance::Castable;
use dom::bindings::js::{MutNullableJS, Root};
use dom::bindings::reflector::{DomObject, reflect_dom_object};
@ -16,11 +16,11 @@ use dom::cssrulelist::{CSSRuleList, RulesSource};
use dom::cssstylesheet::CSSStyleSheet;
use dom::window::Window;
use dom_struct::dom_struct;
use servo_atoms::Atom;
use std::sync::Arc;
use style::keyframes::{Keyframe, KeyframeSelector};
use style::shared_lock::{Locked, ToCssWithGuard};
use style::stylesheets::KeyframesRule;
use style::values::KeyframesName;
#[dom_struct]
pub struct CSSKeyframesRule {
@ -107,23 +107,17 @@ impl CSSKeyframesRuleMethods for CSSKeyframesRule {
// https://drafts.csswg.org/css-animations/#dom-csskeyframesrule-name
fn Name(&self) -> DOMString {
let guard = self.cssrule.shared_lock().read();
DOMString::from(&*self.keyframesrule.read_with(&guard).name)
DOMString::from(&**self.keyframesrule.read_with(&guard).name.as_atom())
}
// https://drafts.csswg.org/css-animations/#dom-csskeyframesrule-name
fn SetName(&self, value: DOMString) -> ErrorResult {
// https://github.com/w3c/csswg-drafts/issues/801
// Setting this property to a CSS-wide keyword or `none` will
// throw a Syntax Error.
match_ignore_ascii_case! { &value,
"initial" => return Err(Error::Syntax),
"inherit" => return Err(Error::Syntax),
"unset" => return Err(Error::Syntax),
"none" => return Err(Error::Syntax),
_ => ()
}
// Spec deviation: https://github.com/w3c/csswg-drafts/issues/801
// Setting this property to a CSS-wide keyword or `none` does not throw,
// it stores a value that serializes as a quoted string.
let name = KeyframesName::from_ident(value.into());
let mut guard = self.cssrule.shared_lock().write();
self.keyframesrule.write_with(&mut guard).name = Atom::from(value);
self.keyframesrule.write_with(&mut guard).name = name;
Ok(())
}
}

View file

@ -78,6 +78,7 @@ impl CSSRule {
StyleCssRule::Import(s) => Root::upcast(CSSImportRule::new(window, parent_stylesheet, s)),
StyleCssRule::Style(s) => Root::upcast(CSSStyleRule::new(window, parent_stylesheet, s)),
StyleCssRule::FontFace(s) => Root::upcast(CSSFontFaceRule::new(window, parent_stylesheet, s)),
StyleCssRule::CounterStyle(_) => unimplemented!(),
StyleCssRule::Keyframes(s) => Root::upcast(CSSKeyframesRule::new(window, parent_stylesheet, s)),
StyleCssRule::Media(s) => Root::upcast(CSSMediaRule::new(window, parent_stylesheet, s)),
StyleCssRule::Namespace(s) => Root::upcast(CSSNamespaceRule::new(window, parent_stylesheet, s)),

View file

@ -464,13 +464,19 @@ pub fn maybe_start_animations(context: &SharedStyleContext,
let box_style = new_style.get_box();
for (i, name) in box_style.animation_name_iter().enumerate() {
let name = if let Some(atom) = name.as_atom() {
atom
} else {
continue
};
debug!("maybe_start_animations: name={}", name);
let total_duration = box_style.animation_duration_mod(i).seconds();
if total_duration == 0. {
continue
}
if let Some(ref anim) = context.stylist.animations().get(&name.0) {
if let Some(ref anim) = context.stylist.animations().get(name) {
debug!("maybe_start_animations: animation {} found", name);
// If this animation doesn't have any keyframe, we can just continue
@ -506,7 +512,7 @@ pub fn maybe_start_animations(context: &SharedStyleContext,
new_animations_sender
.send(Animation::Keyframes(node, name.0.clone(), KeyframesAnimationState {
.send(Animation::Keyframes(node, name.clone(), KeyframesAnimationState {
started_at: animation_start,
duration: duration as f64,
delay: delay as f64,
@ -584,9 +590,10 @@ pub fn update_style_for_animation(context: &SharedStyleContext,
debug_assert!(!animation.steps.is_empty());
let maybe_index = style.get_box()
let maybe_index = style
.get_box()
.animation_name_iter()
.position(|animation_name| *name == animation_name.0);
.position(|animation_name| Some(name) == animation_name.as_atom());
let index = match maybe_index {
Some(index) => index,

View file

@ -0,0 +1,632 @@
/* 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 http://mozilla.org/MPL/2.0/. */
//! The [`@counter-style`][counter-style] at-rule.
//!
//! [counter-style]: https://drafts.csswg.org/css-counter-styles/
use Atom;
use cssparser::{AtRuleParser, DeclarationListParser, DeclarationParser, Parser, Token};
use cssparser::{serialize_string, serialize_identifier};
#[cfg(feature = "gecko")] use gecko::rules::CounterStyleDescriptors;
#[cfg(feature = "gecko")] use gecko_bindings::structs::nsCSSCounterDesc;
use parser::{ParserContext, log_css_error, Parse};
use shared_lock::{SharedRwLockReadGuard, ToCssWithGuard};
use std::ascii::AsciiExt;
use std::borrow::Cow;
use std::fmt;
use std::ops::Range;
use style_traits::{ToCss, OneOrMoreCommaSeparated};
use values::CustomIdent;
/// Parse the prelude of an @counter-style rule
pub fn parse_counter_style_name(input: &mut Parser) -> Result<CustomIdent, ()> {
macro_rules! predefined {
($($name: expr,)+) => {
{
ascii_case_insensitive_phf_map! {
// FIXME: use static atoms https://github.com/rust-lang/rust/issues/33156
predefined -> &'static str = {
$(
$name => $name,
)+
}
}
let ident = input.expect_ident()?;
if let Some(&lower_cased) = predefined(&ident) {
Ok(CustomIdent(Atom::from(lower_cased)))
} else {
// https://github.com/w3c/csswg-drafts/issues/1295 excludes "none"
CustomIdent::from_ident(ident, &["none"])
}
}
}
}
include!("predefined.rs")
}
/// Parse the body (inside `{}`) of an @counter-style rule
pub fn parse_counter_style_body(name: CustomIdent, context: &ParserContext, input: &mut Parser)
-> Result<CounterStyleRule, ()> {
let start = input.position();
let mut rule = CounterStyleRule::empty(name);
{
let parser = CounterStyleRuleParser {
context: context,
rule: &mut rule,
};
let mut iter = DeclarationListParser::new(input, parser);
while let Some(declaration) = iter.next() {
if let Err(range) = declaration {
let pos = range.start;
let message = format!("Unsupported @counter-style descriptor declaration: '{}'",
iter.input.slice(range));
log_css_error(iter.input, pos, &*message, context);
}
}
}
let error = match *rule.system() {
ref system @ System::Cyclic |
ref system @ System::Fixed { .. } |
ref system @ System::Symbolic |
ref system @ System::Alphabetic |
ref system @ System::Numeric
if rule.symbols.is_none() => {
let system = system.to_css_string();
Some(format!("Invalid @counter-style rule: 'system: {}' without 'symbols'", system))
}
ref system @ System::Alphabetic |
ref system @ System::Numeric
if rule.symbols().unwrap().0.len() < 2 => {
let system = system.to_css_string();
Some(format!("Invalid @counter-style rule: 'system: {}' less than two 'symbols'",
system))
}
System::Additive if rule.additive_symbols.is_none() => {
let s = "Invalid @counter-style rule: 'system: additive' without 'additive-symbols'";
Some(s.to_owned())
}
System::Extends(_) if rule.symbols.is_some() => {
let s = "Invalid @counter-style rule: 'system: extends …' with 'symbols'";
Some(s.to_owned())
}
System::Extends(_) if rule.additive_symbols.is_some() => {
let s = "Invalid @counter-style rule: 'system: extends …' with 'additive-symbols'";
Some(s.to_owned())
}
_ => None
};
if let Some(message) = error {
log_css_error(input, start, &message, context);
Err(())
} else {
Ok(rule)
}
}
struct CounterStyleRuleParser<'a, 'b: 'a> {
context: &'a ParserContext<'b>,
rule: &'a mut CounterStyleRule,
}
/// Default methods reject all at rules.
impl<'a, 'b> AtRuleParser for CounterStyleRuleParser<'a, 'b> {
type Prelude = ();
type AtRule = ();
}
macro_rules! accessor {
(#[$doc: meta] $name: tt $ident: ident: $ty: ty = !) => {
#[$doc]
pub fn $ident(&self) -> Option<&$ty> {
self.$ident.as_ref()
}
};
(#[$doc: meta] $name: tt $ident: ident: $ty: ty = $initial: expr) => {
#[$doc]
pub fn $ident(&self) -> Cow<$ty> {
if let Some(ref value) = self.$ident {
Cow::Borrowed(value)
} else {
Cow::Owned($initial)
}
}
}
}
macro_rules! counter_style_descriptors {
(
$( #[$doc: meta] $name: tt $ident: ident / $gecko_ident: ident: $ty: ty = $initial: tt )+
) => {
/// An @counter-style rule
#[derive(Debug)]
pub struct CounterStyleRule {
name: CustomIdent,
$(
#[$doc]
$ident: Option<$ty>,
)+
}
impl CounterStyleRule {
fn empty(name: CustomIdent) -> Self {
CounterStyleRule {
name: name,
$(
$ident: None,
)+
}
}
$(
accessor!(#[$doc] $name $ident: $ty = $initial);
)+
/// Convert to Gecko types
#[cfg(feature = "gecko")]
pub fn set_descriptors(&self, descriptors: &mut CounterStyleDescriptors) {
$(
if let Some(ref value) = self.$ident {
descriptors[nsCSSCounterDesc::$gecko_ident as usize].set_from(value)
}
)*
}
}
impl<'a, 'b> DeclarationParser for CounterStyleRuleParser<'a, 'b> {
type Declaration = ();
fn parse_value(&mut self, name: &str, input: &mut Parser) -> Result<(), ()> {
match_ignore_ascii_case! { name,
$(
$name => {
// DeclarationParser also calls parse_entirely
// so wed normally not need to,
// but in this case we do because we set the value as a side effect
// rather than returning it.
let value = input.parse_entirely(|i| Parse::parse(self.context, i))?;
self.rule.$ident = Some(value)
}
)*
_ => return Err(())
}
Ok(())
}
}
impl ToCssWithGuard for CounterStyleRule {
fn to_css<W>(&self, _guard: &SharedRwLockReadGuard, dest: &mut W) -> fmt::Result
where W: fmt::Write {
dest.write_str("@counter-style ")?;
self.name.to_css(dest)?;
dest.write_str(" {\n")?;
$(
if let Some(ref value) = self.$ident {
dest.write_str(concat!(" ", $name, ": "))?;
ToCss::to_css(value, dest)?;
dest.write_str(";\n")?;
}
)+
dest.write_str("}")
}
}
}
}
counter_style_descriptors! {
/// https://drafts.csswg.org/css-counter-styles/#counter-style-system
"system" system / eCSSCounterDesc_System: System = {
System::Symbolic
}
/// https://drafts.csswg.org/css-counter-styles/#counter-style-negative
"negative" negative / eCSSCounterDesc_Negative: Negative = {
Negative(Symbol::String("-".to_owned()), None)
}
/// https://drafts.csswg.org/css-counter-styles/#counter-style-prefix
"prefix" prefix / eCSSCounterDesc_Prefix: Symbol = {
Symbol::String("".to_owned())
}
/// https://drafts.csswg.org/css-counter-styles/#counter-style-suffix
"suffix" suffix / eCSSCounterDesc_Suffix: Symbol = {
Symbol::String(". ".to_owned())
}
/// https://drafts.csswg.org/css-counter-styles/#counter-style-range
"range" range / eCSSCounterDesc_Range: Ranges = {
Ranges(Vec::new()) // Empty Vec represents 'auto'
}
/// https://drafts.csswg.org/css-counter-styles/#counter-style-pad
"pad" pad / eCSSCounterDesc_Pad: Pad = {
Pad(0, Symbol::String("".to_owned()))
}
/// https://drafts.csswg.org/css-counter-styles/#counter-style-fallback
"fallback" fallback / eCSSCounterDesc_Fallback: Fallback = {
// FIXME https://bugzilla.mozilla.org/show_bug.cgi?id=1359323 use atom!()
Fallback(CustomIdent(Atom::from("decimal")))
}
/// https://drafts.csswg.org/css-counter-styles/#descdef-counter-style-symbols
"symbols" symbols / eCSSCounterDesc_Symbols: Symbols = !
/// https://drafts.csswg.org/css-counter-styles/#descdef-counter-style-additive-symbols
"additive-symbols" additive_symbols / eCSSCounterDesc_AdditiveSymbols: AdditiveSymbols = !
/// https://drafts.csswg.org/css-counter-styles/#counter-style-speak-as
"speak-as" speak_as / eCSSCounterDesc_SpeakAs: SpeakAs = {
SpeakAs::Auto
}
}
/// https://drafts.csswg.org/css-counter-styles/#counter-style-system
#[derive(Debug, Clone)]
pub enum System {
/// 'cyclic'
Cyclic,
/// 'numeric'
Numeric,
/// 'alphabetic'
Alphabetic,
/// 'symbolic'
Symbolic,
/// 'additive'
Additive,
/// 'fixed <integer>?'
Fixed {
/// '<integer>?'
first_symbol_value: Option<i32>
},
/// 'extends <counter-style-name>'
Extends(CustomIdent),
}
impl Parse for System {
fn parse(_context: &ParserContext, input: &mut Parser) -> Result<Self, ()> {
match_ignore_ascii_case! { &input.expect_ident()?,
"cyclic" => Ok(System::Cyclic),
"numeric" => Ok(System::Numeric),
"alphabetic" => Ok(System::Alphabetic),
"symbolic" => Ok(System::Symbolic),
"additive" => Ok(System::Additive),
"fixed" => {
let first_symbol_value = input.try(|i| i.expect_integer()).ok();
Ok(System::Fixed { first_symbol_value: first_symbol_value })
}
"extends" => {
let other = parse_counter_style_name(input)?;
Ok(System::Extends(other))
}
_ => Err(())
}
}
}
impl ToCss for System {
fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
match *self {
System::Cyclic => dest.write_str("cyclic"),
System::Numeric => dest.write_str("numeric"),
System::Alphabetic => dest.write_str("alphabetic"),
System::Symbolic => dest.write_str("symbolic"),
System::Additive => dest.write_str("additive"),
System::Fixed { first_symbol_value } => {
if let Some(value) = first_symbol_value {
write!(dest, "fixed {}", value)
} else {
dest.write_str("fixed")
}
}
System::Extends(ref other) => {
dest.write_str("extends ")?;
other.to_css(dest)
}
}
}
}
/// https://drafts.csswg.org/css-counter-styles/#typedef-symbol
#[derive(Debug, Clone)]
pub enum Symbol {
/// <string>
String(String),
/// <ident>
Ident(String),
// Not implemented:
// /// <image>
// Image(Image),
}
impl Parse for Symbol {
fn parse(_context: &ParserContext, input: &mut Parser) -> Result<Self, ()> {
match input.next() {
Ok(Token::QuotedString(s)) => Ok(Symbol::String(s.into_owned())),
Ok(Token::Ident(s)) => Ok(Symbol::Ident(s.into_owned())),
_ => Err(())
}
}
}
impl ToCss for Symbol {
fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
match *self {
Symbol::String(ref s) => serialize_string(s, dest),
Symbol::Ident(ref s) => serialize_identifier(s, dest),
}
}
}
/// https://drafts.csswg.org/css-counter-styles/#counter-style-negative
#[derive(Debug, Clone)]
pub struct Negative(pub Symbol, pub Option<Symbol>);
impl Parse for Negative {
fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ()> {
Ok(Negative(
Symbol::parse(context, input)?,
input.try(|input| Symbol::parse(context, input)).ok(),
))
}
}
impl ToCss for Negative {
fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
self.0.to_css(dest)?;
if let Some(ref symbol) = self.1 {
dest.write_char(' ')?;
symbol.to_css(dest)?
}
Ok(())
}
}
/// https://drafts.csswg.org/css-counter-styles/#counter-style-range
///
/// Empty Vec represents 'auto'
#[derive(Debug, Clone)]
pub struct Ranges(pub Vec<Range<Option<i32>>>);
impl Parse for Ranges {
fn parse(_context: &ParserContext, input: &mut Parser) -> Result<Self, ()> {
if input.try(|input| input.expect_ident_matching("auto")).is_ok() {
Ok(Ranges(Vec::new()))
} else {
input.parse_comma_separated(|input| {
let opt_start = parse_bound(input)?;
let opt_end = parse_bound(input)?;
if let (Some(start), Some(end)) = (opt_start, opt_end) {
if start > end {
return Err(())
}
}
Ok(opt_start..opt_end)
}).map(Ranges)
}
}
}
fn parse_bound(input: &mut Parser) -> Result<Option<i32>, ()> {
match input.next() {
Ok(Token::Number(ref v)) if v.int_value.is_some() => Ok(Some(v.int_value.unwrap())),
Ok(Token::Ident(ref ident)) if ident.eq_ignore_ascii_case("infinite") => Ok(None),
_ => Err(())
}
}
impl ToCss for Ranges {
fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
let mut iter = self.0.iter();
if let Some(first) = iter.next() {
range_to_css(first, dest)?;
for item in iter {
dest.write_str(", ")?;
range_to_css(item, dest)?;
}
Ok(())
} else {
dest.write_str("auto")
}
}
}
fn range_to_css<W>(range: &Range<Option<i32>>, dest: &mut W) -> fmt::Result
where W: fmt::Write {
bound_to_css(range.start, dest)?;
dest.write_char(' ')?;
bound_to_css(range.end, dest)
}
fn bound_to_css<W>(range: Option<i32>, dest: &mut W) -> fmt::Result where W: fmt::Write {
if let Some(finite) = range {
write!(dest, "{}", finite)
} else {
dest.write_str("infinite")
}
}
/// https://drafts.csswg.org/css-counter-styles/#counter-style-pad
#[derive(Debug, Clone)]
pub struct Pad(pub u32, pub Symbol);
impl Parse for Pad {
fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ()> {
let pad_with = input.try(|input| Symbol::parse(context, input));
let min_length = input.expect_integer()?;
if min_length < 0 {
return Err(())
}
let pad_with = pad_with.or_else(|()| Symbol::parse(context, input))?;
Ok(Pad(min_length as u32, pad_with))
}
}
impl ToCss for Pad {
fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
write!(dest, "{} ", self.0)?;
self.1.to_css(dest)
}
}
/// https://drafts.csswg.org/css-counter-styles/#counter-style-fallback
#[derive(Debug, Clone)]
pub struct Fallback(pub CustomIdent);
impl Parse for Fallback {
fn parse(_context: &ParserContext, input: &mut Parser) -> Result<Self, ()> {
parse_counter_style_name(input).map(Fallback)
}
}
impl ToCss for Fallback {
fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
self.0.to_css(dest)
}
}
/// https://drafts.csswg.org/css-counter-styles/#descdef-counter-style-symbols
#[derive(Debug, Clone)]
pub struct Symbols(pub Vec<Symbol>);
impl Parse for Symbols {
fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ()> {
let mut symbols = Vec::new();
loop {
if let Ok(s) = input.try(|input| Symbol::parse(context, input)) {
symbols.push(s)
} else {
if symbols.is_empty() {
return Err(())
} else {
return Ok(Symbols(symbols))
}
}
}
}
}
impl ToCss for Symbols {
fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
let mut iter = self.0.iter();
let first = iter.next().expect("expected at least one symbol");
first.to_css(dest)?;
for item in iter {
dest.write_char(' ')?;
item.to_css(dest)?;
}
Ok(())
}
}
/// https://drafts.csswg.org/css-counter-styles/#descdef-counter-style-additive-symbols
#[derive(Debug, Clone)]
pub struct AdditiveSymbols(pub Vec<AdditiveTuple>);
impl Parse for AdditiveSymbols {
fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ()> {
let tuples = Vec::<AdditiveTuple>::parse(context, input)?;
// FIXME maybe? https://github.com/w3c/csswg-drafts/issues/1220
if tuples.windows(2).any(|window| window[0].value <= window[1].value) {
return Err(())
}
Ok(AdditiveSymbols(tuples))
}
}
impl ToCss for AdditiveSymbols {
fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
self.0.to_css(dest)
}
}
/// <integer> && <symbol>
#[derive(Debug, Clone)]
pub struct AdditiveTuple {
value: u32,
symbol: Symbol,
}
impl OneOrMoreCommaSeparated for AdditiveTuple {}
impl Parse for AdditiveTuple {
fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ()> {
let symbol = input.try(|input| Symbol::parse(context, input));
let value = input.expect_integer()?;
if value < 0 {
return Err(())
}
let symbol = symbol.or_else(|()| Symbol::parse(context, input))?;
Ok(AdditiveTuple {
value: value as u32,
symbol: symbol,
})
}
}
impl ToCss for AdditiveTuple {
fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
write!(dest, "{} ", self.value)?;
self.symbol.to_css(dest)
}
}
/// https://drafts.csswg.org/css-counter-styles/#counter-style-speak-as
#[derive(Debug, Clone)]
pub enum SpeakAs {
/// auto
Auto,
/// bullets
Bullets,
/// numbers
Numbers,
/// words
Words,
// /// spell-out, not supported, see bug 1024178
// SpellOut,
/// <counter-style-name>
Other(CustomIdent),
}
impl Parse for SpeakAs {
fn parse(_context: &ParserContext, input: &mut Parser) -> Result<Self, ()> {
let mut is_spell_out = false;
let result = input.try(|input| {
match_ignore_ascii_case! { &input.expect_ident()?,
"auto" => Ok(SpeakAs::Auto),
"bullets" => Ok(SpeakAs::Bullets),
"numbers" => Ok(SpeakAs::Numbers),
"words" => Ok(SpeakAs::Words),
"spell-out" => {
is_spell_out = true;
Err(())
}
_ => Err(())
}
});
if is_spell_out {
// spell-out is not supported, but dont parse it as a <counter-style-name>.
// See bug 1024178.
return Err(())
}
result.or_else(|()| {
Ok(SpeakAs::Other(parse_counter_style_name(input)?))
})
}
}
impl ToCss for SpeakAs {
fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
match *self {
SpeakAs::Auto => dest.write_str("auto"),
SpeakAs::Bullets => dest.write_str("bullets"),
SpeakAs::Numbers => dest.write_str("numbers"),
SpeakAs::Words => dest.write_str("words"),
SpeakAs::Other(ref other) => other.to_css(dest),
}
}
}

View file

@ -0,0 +1,61 @@
/* 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 http://mozilla.org/MPL/2.0/. */
predefined! {
"decimal",
"decimal-leading-zero",
"arabic-indic",
"armenian",
"upper-armenian",
"lower-armenian",
"bengali",
"cambodian",
"khmer",
"cjk-decimal",
"devanagari",
"georgian",
"gujarati",
"gurmukhi",
"hebrew",
"kannada",
"lao",
"malayalam",
"mongolian",
"myanmar",
"oriya",
"persian",
"lower-roman",
"upper-roman",
"tamil",
"telugu",
"thai",
"tibetan",
"lower-alpha",
"lower-latin",
"upper-alpha",
"upper-latin",
"cjk-earthly-branch",
"cjk-heavenly-stem",
"lower-greek",
"hiragana",
"hiragana-iroha",
"katakana",
"katakana-iroha",
"disc",
"circle",
"square",
"disclosure-open",
"disclosure-closed",
"japanese-informal",
"japanese-formal",
"korean-hangul-formal",
"korean-hanja-informal",
"korean-hanja-formal",
"simp-chinese-informal",
"simp-chinese-formal",
"trad-chinese-informal",
"trad-chinese-formal",
"cjk-ideographic",
"ethiopic-numeric",
}

View file

@ -0,0 +1,32 @@
#!/usr/bin/env python
# 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 http://mozilla.org/MPL/2.0/. */
import os.path
import re
import urllib
def main(filename):
names = [
re.search('>([^>]+)(</dfn>|<a class="self-link")', line).group(1)
for line in urllib.urlopen("https://drafts.csswg.org/css-counter-styles/")
if 'data-dfn-for="<counter-style-name>"' in line
or 'data-dfn-for="<counter-style>"' in line
]
with open(filename, "wb") as f:
f.write("""\
/* 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 http://mozilla.org/MPL/2.0/. */
predefined! {
""")
for name in names:
f.write(' "%s",\n' % name)
f.write('}\n')
if __name__ == "__main__":
main(os.path.join(os.path.dirname(__file__), "predefined.rs"))

View file

@ -6,10 +6,11 @@
use computed_values::{font_style, font_weight, font_stretch};
use computed_values::font_family::FamilyName;
use counter_style;
use cssparser::UnicodeRange;
use font_face::{FontFaceRuleData, Source};
use gecko_bindings::bindings;
use gecko_bindings::structs::{self, nsCSSFontFaceRule, nsCSSValue};
use gecko_bindings::structs::{self, nsCSSFontFaceRule, nsCSSValue, nsCSSCounterDesc};
use gecko_bindings::sugar::ns_css_value::ToNsCssValue;
use gecko_bindings::sugar::refptr::{RefPtr, UniqueRefPtr};
use shared_lock::{ToCssWithGuard, SharedRwLockReadGuard};
@ -133,3 +134,123 @@ impl ToCssWithGuard for FontFaceRule {
write!(dest, "{}", css_text)
}
}
/// The type of nsCSSCounterStyleRule::mValues
pub type CounterStyleDescriptors = [nsCSSValue; nsCSSCounterDesc::eCSSCounterDesc_COUNT as usize];
impl ToNsCssValue for counter_style::System {
fn convert(&self, nscssvalue: &mut nsCSSValue) {
use counter_style::System::*;
match *self {
Cyclic => nscssvalue.set_enum(structs::NS_STYLE_COUNTER_SYSTEM_CYCLIC as i32),
Numeric => nscssvalue.set_enum(structs::NS_STYLE_COUNTER_SYSTEM_NUMERIC as i32),
Alphabetic => nscssvalue.set_enum(structs::NS_STYLE_COUNTER_SYSTEM_ALPHABETIC as i32),
Symbolic => nscssvalue.set_enum(structs::NS_STYLE_COUNTER_SYSTEM_SYMBOLIC as i32),
Additive => nscssvalue.set_enum(structs::NS_STYLE_COUNTER_SYSTEM_ADDITIVE as i32),
Fixed { first_symbol_value } => {
let mut a = nsCSSValue::null();
let mut b = nsCSSValue::null();
a.set_enum(structs::NS_STYLE_COUNTER_SYSTEM_FIXED as i32);
b.set_integer(first_symbol_value.unwrap_or(1));
//nscssvalue.set_pair(a, b); // FIXME: add bindings for nsCSSValue::SetPairValue
}
Extends(ref other) => {
let mut a = nsCSSValue::null();
let mut b = nsCSSValue::null();
a.set_enum(structs::NS_STYLE_COUNTER_SYSTEM_EXTENDS as i32);
b.set_string_from_atom(&other.0);
//nscssvalue.set_pair(a, b); // FIXME: add bindings for nsCSSValue::SetPairValue
}
}
}
}
impl ToNsCssValue for counter_style::Negative {
fn convert(&self, nscssvalue: &mut nsCSSValue) {
if let Some(ref second) = self.1 {
let mut a = nsCSSValue::null();
let mut b = nsCSSValue::null();
a.set_from(&self.0);
b.set_from(second);
//nscssvalue.set_pair(a, b); // FIXME: add bindings for nsCSSValue::SetPairValue
} else {
nscssvalue.set_from(&self.0)
}
}
}
impl ToNsCssValue for counter_style::Symbol {
fn convert(&self, nscssvalue: &mut nsCSSValue) {
match *self {
counter_style::Symbol::String(ref s) => nscssvalue.set_string(s),
counter_style::Symbol::Ident(ref s) => nscssvalue.set_ident(s),
}
}
}
impl ToNsCssValue for counter_style::Ranges {
fn convert(&self, _nscssvalue: &mut nsCSSValue) {
if self.0.is_empty() {
//nscssvalue.set_auto(); // FIXME: add bindings for nsCSSValue::SetAutoValue
} else {
for range in &self.0 {
fn set_bound(bound: Option<i32>, nscssvalue: &mut nsCSSValue) {
if let Some(finite) = bound {
nscssvalue.set_integer(finite)
} else {
nscssvalue.set_enum(structs::NS_STYLE_COUNTER_RANGE_INFINITE as i32)
}
}
let mut start = nsCSSValue::null();
let mut end = nsCSSValue::null();
set_bound(range.start, &mut start);
set_bound(range.end, &mut end);
// FIXME: add bindings for nsCSSValuePairList
}
}
}
}
impl ToNsCssValue for counter_style::Pad {
fn convert(&self, _nscssvalue: &mut nsCSSValue) {
let mut min_length = nsCSSValue::null();
let mut pad_with = nsCSSValue::null();
min_length.set_integer(self.0 as i32);
pad_with.set_from(&self.1);
// FIXME: add bindings for nsCSSValue::SetPairValue
//nscssvalue.set_pair(min_length, pad_with);
}
}
impl ToNsCssValue for counter_style::Fallback {
fn convert(&self, nscssvalue: &mut nsCSSValue) {
nscssvalue.set_ident_from_atom(&self.0 .0)
}
}
impl ToNsCssValue for counter_style::Symbols {
fn convert(&self, _nscssvalue: &mut nsCSSValue) {
debug_assert!(!self.0.is_empty());
// FIXME: add bindings for nsCSSValueList
}
}
impl ToNsCssValue for counter_style::AdditiveSymbols {
fn convert(&self, _nscssvalue: &mut nsCSSValue) {
debug_assert!(!self.0.is_empty());
// FIXME: add bindings for nsCSSValuePairList
}
}
impl ToNsCssValue for counter_style::SpeakAs {
fn convert(&self, nscssvalue: &mut nsCSSValue) {
use counter_style::SpeakAs::*;
match *self {
Auto => {} //nscssvalue.set_auto(), // FIXME: add bindings for nsCSSValue::SetAutoValue
Bullets => nscssvalue.set_enum(structs::NS_STYLE_COUNTER_SPEAKAS_BULLETS as i32),
Numbers => nscssvalue.set_enum(structs::NS_STYLE_COUNTER_SPEAKAS_NUMBERS as i32),
Words => nscssvalue.set_enum(structs::NS_STYLE_COUNTER_SPEAKAS_WORDS as i32),
Other(ref other) => nscssvalue.set_ident_from_atom(&other.0),
}
}
}

View file

@ -124,6 +124,11 @@ impl nsCSSValue {
self.set_string_from_atom_internal(s, nsCSSUnit::eCSSUnit_String)
}
/// Set to a ident value from the given atom
pub fn set_ident_from_atom(&mut self, s: &Atom) {
self.set_string_from_atom_internal(s, nsCSSUnit::eCSSUnit_Ident)
}
/// Set to an identifier value
pub fn set_ident(&mut self, s: &str) {
self.set_string_internal(s, nsCSSUnit::eCSSUnit_Ident)

View file

@ -13,6 +13,7 @@ use gecko_bindings::bindings::Gecko_ReleaseAtom;
use gecko_bindings::structs::nsIAtom;
use nsstring::nsAString;
use precomputed_hash::PrecomputedHash;
use std::ascii::AsciiExt;
use std::borrow::{Cow, Borrow};
use std::char::{self, DecodeUtf16};
use std::fmt::{self, Write};
@ -163,6 +164,12 @@ impl WeakAtom {
}
}
/// Returns whether this atom is the empty string.
#[inline]
pub fn is_empty(&self) -> bool {
self.len() == 0
}
/// Returns the atom as a mutable pointer.
#[inline]
pub fn as_ptr(&self) -> *mut nsIAtom {
@ -218,6 +225,25 @@ impl Atom {
Atom(WeakAtom::new(ptr))
}
}
/// Return whether two atoms are ASCII-case-insensitive matches
pub fn eq_ignore_ascii_case(&self, other: &Self) -> bool {
let a = self.as_slice();
let b = other.as_slice();
a.len() == b.len() && a.iter().zip(b).all(|(&a16, &b16)| {
if a16 <= 0x7F && b16 <= 0x7F {
(a16 as u8).eq_ignore_ascii_case(&(b16 as u8))
} else {
a16 == b16
}
})
}
/// Return whether this atom is an ASCII-case-insensitive match for the given string
pub fn eq_str_ignore_ascii_case(&self, other: &str) -> bool {
self.chars().map(|r| r.map(|c: char| c.to_ascii_lowercase()))
.eq(other.chars().map(|c: char| Ok(c.to_ascii_lowercase())))
}
}
impl Hash for Atom {

View file

@ -89,6 +89,7 @@ pub mod bloom;
pub mod cache;
pub mod cascade_info;
pub mod context;
pub mod counter_style;
pub mod custom_properties;
pub mod data;
pub mod dom;

View file

@ -560,7 +560,7 @@ trait PrivateMatchMethods: TElement {
pseudo: Option<&PseudoElement>) -> bool {
let ref new_box_style = new_values.get_box();
let has_new_animation_style = new_box_style.animation_name_count() >= 1 &&
new_box_style.animation_name_at(0).0.len() != 0;
new_box_style.animation_name_at(0).0.is_some();
let has_animations = self.has_css_animations(pseudo);
old_values.as_ref().map_or(has_new_animation_style, |ref old| {

View file

@ -62,7 +62,7 @@ use std::ptr;
use std::sync::Arc;
use std::cmp;
use values::computed::ToComputedValue;
use values::{Either, Auto};
use values::{Either, Auto, KeyframesName};
use computed_values::border_style;
pub mod style_structs {
@ -1175,11 +1175,10 @@ fn static_assert() {
% for value in GRID_LINES:
pub fn set_${value.name}(&mut self, v: longhands::${value.name}::computed_value::T) {
use nsstring::nsCString;
use gecko_bindings::structs::{nsStyleGridLine_kMinLine, nsStyleGridLine_kMaxLine};
let ident = v.ident.unwrap_or(String::new());
self.gecko.${value.gecko}.mLineName.assign_utf8(&nsCString::from(&*ident));
self.gecko.${value.gecko}.mLineName.assign_utf8(&ident);
self.gecko.${value.gecko}.mHasSpan = v.is_span;
self.gecko.${value.gecko}.mInteger = v.integer.map(|i| {
// clamping the integer between a range
@ -2197,23 +2196,28 @@ fn static_assert() {
}
pub fn set_animation_name(&mut self, v: longhands::animation_name::computed_value::T) {
use nsstring::nsCString;
debug_assert!(!v.0.is_empty());
unsafe { self.gecko.mAnimations.ensure_len(v.0.len()) };
self.gecko.mAnimationNameCount = v.0.len() as u32;
for (servo, gecko) in v.0.into_iter().zip(self.gecko.mAnimations.iter_mut()) {
// TODO This is inefficient. We should fix this in bug 1329169.
gecko.mName.assign_utf8(&nsCString::from(servo.0.to_string()));
gecko.mName.assign(match servo.0 {
Some(ref name) => name.as_atom().as_slice(),
None => &[], // Empty string for 'none'
});
}
}
pub fn animation_name_at(&self, index: usize)
-> longhands::animation_name::computed_value::SingleComputedValue {
use Atom;
use properties::longhands::animation_name::single_value::SpecifiedValue as AnimationName;
// XXX: Is there any effective ways?
AnimationName(Atom::from(String::from_utf16_lossy(&self.gecko.mAnimations[index].mName[..])))
let atom = &self.gecko.mAnimations[index].mName;
if atom.is_empty() {
AnimationName(None)
} else {
AnimationName(Some(KeyframesName::from_ident(atom.to_string())))
}
}
pub fn copy_animation_name_from(&mut self, other: &Self) {
unsafe { self.gecko.mAnimations.ensure_len(other.gecko.mAnimations.len()) };
@ -2886,15 +2890,14 @@ fn static_assert() {
pub fn set_quotes(&mut self, other: longhands::quotes::computed_value::T) {
use gecko_bindings::bindings::Gecko_NewStyleQuoteValues;
use gecko_bindings::sugar::refptr::UniqueRefPtr;
use nsstring::nsCString;
let mut refptr = unsafe {
UniqueRefPtr::from_addrefed(Gecko_NewStyleQuoteValues(other.0.len() as u32))
};
for (servo, gecko) in other.0.into_iter().zip(refptr.mQuotePairs.iter_mut()) {
gecko.first.assign_utf8(&nsCString::from(&*servo.0));
gecko.second.assign_utf8(&nsCString::from(&*servo.1));
gecko.first.assign_utf8(&servo.0);
gecko.second.assign_utf8(&servo.1);
}
unsafe { self.gecko.mQuotes.set_move(refptr.get()) }
@ -3381,7 +3384,6 @@ fn static_assert() {
<%call expr="impl_simple_copy('text_emphasis_position', 'mTextEmphasisPosition')"></%call>
pub fn set_text_emphasis_style(&mut self, v: longhands::text_emphasis_style::computed_value::T) {
use nsstring::nsCString;
use properties::longhands::text_emphasis_style::computed_value::T;
use properties::longhands::text_emphasis_style::ShapeKeyword;
@ -3408,7 +3410,7 @@ fn static_assert() {
(structs::NS_STYLE_TEXT_EMPHASIS_STYLE_STRING, &**s)
},
};
self.gecko.mTextEmphasisStyleString.assign_utf8(&nsCString::from(s));
self.gecko.mTextEmphasisStyleString.assign_utf8(s);
self.gecko.mTextEmphasisStyle = te as u8;
}
@ -3489,12 +3491,11 @@ fn static_assert() {
use properties::longhands::text_overflow::{SpecifiedValue, Side};
fn set(side: &mut nsStyleTextOverflowSide, value: &Side) {
use nsstring::nsCString;
let ty = match *value {
Side::Clip => structs::NS_STYLE_TEXT_OVERFLOW_CLIP,
Side::Ellipsis => structs::NS_STYLE_TEXT_OVERFLOW_ELLIPSIS,
Side::String(ref s) => {
side.mString.assign_utf8(&nsCString::from(&**s));
side.mString.assign_utf8(s);
structs::NS_STYLE_TEXT_OVERFLOW_STRING
}
};
@ -4011,9 +4012,9 @@ clip-path
unsafe {
bindings::Gecko_ClearAndResizeCounter${counter_property}s(&mut self.gecko,
v.0.len() as u32);
for (i, item) in v.0.into_iter().enumerate() {
self.gecko.m${counter_property}s[i].mCounter.assign_utf8(&item.0);
self.gecko.m${counter_property}s[i].mValue = item.1;
for (i, (name, value)) in v.0.into_iter().enumerate() {
self.gecko.m${counter_property}s[i].mCounter.assign(name.0.as_slice());
self.gecko.m${counter_property}s[i].mValue = value;
}
}
}

View file

@ -796,7 +796,7 @@ ${helpers.single_keyword("overflow-x", "visible hidden scroll auto",
use std::ops::Deref;
use style_traits::ToCss;
use values::computed::ComputedValueAsSpecified;
use values::HasViewportPercentage;
use values::{HasViewportPercentage, KeyframesName};
pub mod computed_value {
pub use super::SpecifiedValue as T;
@ -804,7 +804,14 @@ ${helpers.single_keyword("overflow-x", "visible hidden scroll auto",
#[derive(Clone, Debug, Hash, Eq, PartialEq)]
#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
pub struct SpecifiedValue(pub Atom);
pub struct SpecifiedValue(pub Option<KeyframesName>);
impl SpecifiedValue {
/// As an Atom
pub fn as_atom(&self) -> Option< &Atom> {
self.0.as_ref().map(|n| n.as_atom())
}
}
#[inline]
pub fn get_initial_value() -> computed_value::T {
@ -813,49 +820,33 @@ ${helpers.single_keyword("overflow-x", "visible hidden scroll auto",
#[inline]
pub fn get_initial_specified_value() -> SpecifiedValue {
SpecifiedValue(atom!(""))
SpecifiedValue(None)
}
impl fmt::Display for SpecifiedValue {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.0.fmt(f)
self.to_css(f)
}
}
impl ToCss for SpecifiedValue {
fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
if self.0 == atom!("") {
dest.write_str("none")
if let Some(ref name) = self.0 {
name.to_css(dest)
} else {
dest.write_str(&*self.0.to_string())
dest.write_str("none")
}
}
}
impl Parse for SpecifiedValue {
fn parse(_context: &ParserContext, input: &mut ::cssparser::Parser) -> Result<Self, ()> {
use cssparser::Token;
use properties::CSSWideKeyword;
use std::ascii::AsciiExt;
let atom = match input.next() {
Ok(Token::Ident(ref value)) => {
if CSSWideKeyword::from_ident(value).is_some() {
// We allow any ident for the animation-name except one
// of the CSS-wide keywords.
return Err(());
} else if value.eq_ignore_ascii_case("none") {
// FIXME We may want to support `@keyframes ""` at some point.
atom!("")
fn parse(context: &ParserContext, input: &mut ::cssparser::Parser) -> Result<Self, ()> {
if let Ok(name) = input.try(|input| KeyframesName::parse(context, input)) {
Ok(SpecifiedValue(Some(name)))
} else {
Atom::from(&**value)
input.expect_ident_matching("none").map(|()| SpecifiedValue(None))
}
}
Ok(Token::QuotedString(value)) => Atom::from(&*value),
_ => return Err(()),
};
Ok(SpecifiedValue(atom))
}
}
no_viewport_percentage!(SpecifiedValue);

View file

@ -240,21 +240,22 @@
use std::fmt;
use style_traits::ToCss;
use super::content;
use values::HasViewportPercentage;
use values::{HasViewportPercentage, CustomIdent};
use cssparser::{Token, serialize_identifier};
use std::borrow::{Cow, ToOwned};
#[derive(Debug, Clone, PartialEq)]
pub struct SpecifiedValue(pub Vec<(String, specified::Integer)>);
pub struct SpecifiedValue(pub Vec<(CustomIdent, specified::Integer)>);
pub mod computed_value {
use std::fmt;
use style_traits::ToCss;
use values::CustomIdent;
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
pub struct T(pub Vec<(String, i32)>);
pub struct T(pub Vec<(CustomIdent, i32)>);
impl ToCss for T {
fn to_css<W>(&self, dest: &mut W) -> fmt::Result
@ -266,14 +267,14 @@
}
let mut first = true;
for pair in &self.0 {
for &(ref name, value) in &self.0 {
if !first {
try!(dest.write_str(" "));
dest.write_str(" ")?;
}
first = false;
try!(serialize_identifier(&pair.0, dest));
try!(dest.write_str(" "));
try!(pair.1.to_css(dest));
name.to_css(dest)?;
dest.write_str(" ")?;
value.to_css(dest)?;
}
Ok(())
}
@ -284,14 +285,14 @@
type ComputedValue = computed_value::T;
fn to_computed_value(&self, context: &Context) -> Self::ComputedValue {
computed_value::T(self.0.iter().map(|entry| {
(entry.0.clone(), entry.1.to_computed_value(context))
computed_value::T(self.0.iter().map(|&(ref name, ref value)| {
(name.clone(), value.to_computed_value(context))
}).collect::<Vec<_>>())
}
fn from_computed_value(computed: &Self::ComputedValue) -> Self {
SpecifiedValue(computed.0.iter().map(|entry| {
(entry.0.clone(), specified::Integer::from_computed_value(&entry.1))
SpecifiedValue(computed.0.iter().map(|&(ref name, ref value)| {
(name.clone(), specified::Integer::from_computed_value(&value))
}).collect::<Vec<_>>())
}
}
@ -311,14 +312,14 @@
return dest.write_str("none");
}
let mut first = true;
for pair in &self.0 {
for &(ref name, ref value) in &self.0 {
if !first {
try!(dest.write_str(" "));
dest.write_str(" ")?;
}
first = false;
try!(serialize_identifier(&pair.0, dest));
try!(dest.write_str(" "));
try!(pair.1.to_css(dest));
name.to_css(dest)?;
dest.write_str(" ")?;
value.to_css(dest)?;
}
Ok(())
@ -339,13 +340,7 @@
let mut counters = Vec::new();
loop {
let counter_name = match input.next() {
Ok(Token::Ident(ident)) => {
if CSSWideKeyword::from_ident(&ident).is_some() || ident.eq_ignore_ascii_case("none") {
// Don't accept CSS-wide keywords or none as the counter name.
return Err(());
}
(*ident).to_owned()
}
Ok(Token::Ident(ident)) => CustomIdent::from_ident(ident, &["none"])?,
Ok(_) => return Err(()),
Err(_) => break,
};

View file

@ -444,6 +444,7 @@ impl CSSWideKeyword {
/// to a CSSWideKeyword.
pub fn from_ident<'i>(ident: &Cow<'i, str>) -> Option<Self> {
match_ignore_ascii_case! { ident,
// If modifying this set of keyword, also update values::CustomIdent::from_ident
"initial" => Some(CSSWideKeyword::Initial),
"inherit" => Some(CSSWideKeyword::Inherit),
"unset" => Some(CSSWideKeyword::Unset),
@ -1616,7 +1617,7 @@ pub mod style_structs {
/// Returns whether there is any animation specified with
/// animation-name other than `none`.
pub fn specifies_animations(&self) -> bool {
self.animation_name_iter().any(|name| name.0 != atom!(""))
self.animation_name_iter().any(|name| name.0.is_some())
}
/// Returns whether there are any transitions specified.

View file

@ -7,8 +7,9 @@
#![deny(missing_docs)]
use {Atom, Prefix, Namespace};
use counter_style::{CounterStyleRule, parse_counter_style_name, parse_counter_style_body};
use cssparser::{AtRuleParser, Parser, QualifiedRuleParser};
use cssparser::{AtRuleType, RuleListParser, Token, parse_one_rule};
use cssparser::{AtRuleType, RuleListParser, parse_one_rule};
use cssparser::ToCss as ParserToCss;
use error_reporting::{ParseErrorReporter, NullReporter};
#[cfg(feature = "servo")]
@ -40,6 +41,7 @@ use str::starts_with_ignore_ascii_case;
use style_traits::ToCss;
use stylist::FnvHashMap;
use supports::SupportsCondition;
use values::{CustomIdent, KeyframesName};
use values::specified::url::SpecifiedUrl;
use viewport::ViewportRule;
@ -289,6 +291,7 @@ pub enum CssRule {
Style(Arc<Locked<StyleRule>>),
Media(Arc<Locked<MediaRule>>),
FontFace(Arc<Locked<FontFaceRule>>),
CounterStyle(Arc<Locked<CounterStyleRule>>),
Viewport(Arc<Locked<ViewportRule>>),
Keyframes(Arc<Locked<KeyframesRule>>),
Supports(Arc<Locked<SupportsRule>>),
@ -335,6 +338,7 @@ impl CssRule {
CssRule::Import(_) => CssRuleType::Import,
CssRule::Media(_) => CssRuleType::Media,
CssRule::FontFace(_) => CssRuleType::FontFace,
CssRule::CounterStyle(_) => CssRuleType::CounterStyle,
CssRule::Keyframes(_) => CssRuleType::Keyframes,
CssRule::Namespace(_) => CssRuleType::Namespace,
CssRule::Viewport(_) => CssRuleType::Viewport,
@ -372,6 +376,7 @@ impl CssRule {
CssRule::Namespace(_) |
CssRule::Style(_) |
CssRule::FontFace(_) |
CssRule::CounterStyle(_) |
CssRule::Viewport(_) |
CssRule::Keyframes(_) |
CssRule::Page(_) => {
@ -445,6 +450,7 @@ impl ToCssWithGuard for CssRule {
CssRule::Import(ref lock) => lock.read_with(guard).to_css(guard, dest),
CssRule::Style(ref lock) => lock.read_with(guard).to_css(guard, dest),
CssRule::FontFace(ref lock) => lock.read_with(guard).to_css(guard, dest),
CssRule::CounterStyle(ref lock) => lock.read_with(guard).to_css(guard, dest),
CssRule::Viewport(ref lock) => lock.read_with(guard).to_css(guard, dest),
CssRule::Keyframes(ref lock) => lock.read_with(guard).to_css(guard, dest),
CssRule::Media(ref lock) => lock.read_with(guard).to_css(guard, dest),
@ -513,7 +519,7 @@ impl ToCssWithGuard for ImportRule {
#[derive(Debug)]
pub struct KeyframesRule {
/// The name of the current animation.
pub name: Atom,
pub name: KeyframesName,
/// The keyframes specified for this CSS rule.
pub keyframes: Vec<Arc<Locked<Keyframe>>>,
/// Vendor prefix type the @keyframes has.
@ -525,7 +531,7 @@ impl ToCssWithGuard for KeyframesRule {
fn to_css<W>(&self, guard: &SharedRwLockReadGuard, dest: &mut W) -> fmt::Result
where W: fmt::Write {
try!(dest.write_str("@keyframes "));
try!(dest.write_str(&*self.name.to_string()));
try!(self.name.to_css(dest));
try!(dest.write_str(" { "));
let iter = self.keyframes.iter();
let mut first = true;
@ -834,6 +840,7 @@ rule_filter! {
effective_style_rules(Style => StyleRule),
effective_media_rules(Media => MediaRule),
effective_font_face_rules(FontFace => FontFaceRule),
effective_counter_style_rules(CounterStyle => CounterStyleRule),
effective_viewport_rules(Viewport => ViewportRule),
effective_keyframes_rules(Keyframes => KeyframesRule),
effective_supports_rules(Supports => SupportsRule),
@ -915,6 +922,8 @@ pub enum VendorPrefix {
enum AtRulePrelude {
/// A @font-face rule prelude.
FontFace,
/// A @counter-style rule prelude, with its counter style name.
CounterStyle(CustomIdent),
/// A @media rule prelude, with its media queries.
Media(Arc<Locked<MediaList>>),
/// An @supports rule, with its conditional
@ -922,7 +931,7 @@ enum AtRulePrelude {
/// A @viewport rule prelude.
Viewport,
/// A @keyframes rule, with its animation name and vendor prefix if exists.
Keyframes(Atom, Option<VendorPrefix>),
Keyframes(KeyframesName, Option<VendorPrefix>),
/// A @page rule prelude.
Page,
}
@ -1102,6 +1111,20 @@ impl<'a, 'b> AtRuleParser for NestedRuleParser<'a, 'b> {
"font-face" => {
Ok(AtRuleType::WithBlock(AtRulePrelude::FontFace))
},
"counter-style" => {
if !cfg!(feature = "gecko") {
// Support for this rule is not fully implemented in Servo yet.
return Err(())
}
let name = parse_counter_style_name(input)?;
// ASCII-case-insensitive matches for "decimal" are already lower-cased
// by `parse_counter_style_name`, so we can use == here.
// FIXME: https://bugzilla.mozilla.org/show_bug.cgi?id=1359323 use atom!("decimal")
if name.0 == Atom::from("decimal") {
return Err(())
}
Ok(AtRuleType::WithBlock(AtRulePrelude::CounterStyle(name)))
},
"viewport" => {
if is_viewport_enabled() {
Ok(AtRuleType::WithBlock(AtRulePrelude::Viewport))
@ -1122,13 +1145,9 @@ impl<'a, 'b> AtRuleParser for NestedRuleParser<'a, 'b> {
// Servo should not support @-moz-keyframes.
return Err(())
}
let name = match input.next() {
Ok(Token::Ident(ref value)) if value != "none" => Atom::from(&**value),
Ok(Token::QuotedString(value)) => Atom::from(&*value),
_ => return Err(())
};
let name = KeyframesName::parse(self.context, input)?;
Ok(AtRuleType::WithBlock(AtRulePrelude::Keyframes(Atom::from(name), prefix)))
Ok(AtRuleType::WithBlock(AtRulePrelude::Keyframes(name, prefix)))
},
"page" => {
if cfg!(feature = "gecko") {
@ -1148,6 +1167,11 @@ impl<'a, 'b> AtRuleParser for NestedRuleParser<'a, 'b> {
Ok(CssRule::FontFace(Arc::new(self.shared_lock.wrap(
parse_font_face_block(&context, input).into()))))
}
AtRulePrelude::CounterStyle(name) => {
let context = ParserContext::new_with_rule_type(self.context, Some(CssRuleType::CounterStyle));
Ok(CssRule::CounterStyle(Arc::new(self.shared_lock.wrap(
parse_counter_style_body(name, &context, input)?))))
}
AtRulePrelude::Media(media_queries) => {
Ok(CssRule::Media(Arc::new(self.shared_lock.wrap(MediaRule {
media_queries: media_queries,

View file

@ -349,13 +349,13 @@ impl Stylist {
// Don't let a prefixed keyframes animation override a non-prefixed one.
let needs_insertion = keyframes_rule.vendor_prefix.is_none() ||
self.animations.get(&keyframes_rule.name).map_or(true, |rule|
self.animations.get(keyframes_rule.name.as_atom()).map_or(true, |rule|
rule.vendor_prefix.is_some());
if needs_insertion {
let animation = KeyframesAnimation::from_keyframes(
&keyframes_rule.keyframes, keyframes_rule.vendor_prefix.clone(), guard);
debug!("Found valid keyframe animation: {:?}", animation);
self.animations.insert(keyframes_rule.name.clone(), animation);
self.animations.insert(keyframes_rule.name.as_atom().clone(), animation);
}
}
CssRule::FontFace(ref rule) => {

View file

@ -8,9 +8,13 @@
#![deny(missing_docs)]
pub use cssparser::{RGBA, Parser};
use Atom;
pub use cssparser::{RGBA, Token, Parser, serialize_identifier, serialize_string};
use parser::{Parse, ParserContext};
use std::ascii::AsciiExt;
use std::borrow::Cow;
use std::fmt::{self, Debug};
use std::hash;
use style_traits::ToCss;
macro_rules! define_numbered_css_keyword_enum {
@ -211,6 +215,93 @@ impl<A: ToComputedValue, B: ToComputedValue> ToComputedValue for Either<A, B> {
}
}
/// https://drafts.csswg.org/css-values-4/#custom-idents
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
pub struct CustomIdent(pub Atom);
impl CustomIdent {
/// Parse an already-tokenizer identifier
pub fn from_ident(ident: Cow<str>, excluding: &[&str]) -> Result<Self, ()> {
match_ignore_ascii_case! { &ident,
"initial" | "inherit" | "unset" | "default" => return Err(()),
_ => {}
};
if excluding.iter().any(|s| ident.eq_ignore_ascii_case(s)) {
Err(())
} else {
Ok(CustomIdent(ident.into()))
}
}
}
impl ToCss for CustomIdent {
fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
serialize_identifier(&self.0.to_string(), dest)
}
}
/// https://drafts.csswg.org/css-animations/#typedef-keyframes-name
#[derive(Debug, Clone)]
#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
pub enum KeyframesName {
/// <custom-ident>
Ident(CustomIdent),
/// <string>
QuotedString(Atom),
}
impl KeyframesName {
/// https://drafts.csswg.org/css-animations/#dom-csskeyframesrule-name
pub fn from_ident(value: String) -> Self {
match CustomIdent::from_ident((&*value).into(), &["none"]) {
Ok(ident) => KeyframesName::Ident(ident),
Err(()) => KeyframesName::QuotedString(value.into()),
}
}
/// The name as an Atom
pub fn as_atom(&self) -> &Atom {
match *self {
KeyframesName::Ident(ref ident) => &ident.0,
KeyframesName::QuotedString(ref atom) => atom,
}
}
}
impl Eq for KeyframesName {}
impl PartialEq for KeyframesName {
fn eq(&self, other: &Self) -> bool {
self.as_atom() == other.as_atom()
}
}
impl hash::Hash for KeyframesName {
fn hash<H>(&self, state: &mut H) where H: hash::Hasher {
self.as_atom().hash(state)
}
}
impl Parse for KeyframesName {
fn parse(_context: &ParserContext, input: &mut Parser) -> Result<Self, ()> {
match input.next() {
Ok(Token::Ident(s)) => Ok(KeyframesName::Ident(CustomIdent::from_ident(s, &["none"])?)),
Ok(Token::QuotedString(s)) => Ok(KeyframesName::QuotedString(s.into())),
_ => Err(())
}
}
}
impl ToCss for KeyframesName {
fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
match *self {
KeyframesName::Ident(ref ident) => ident.to_css(dest),
KeyframesName::QuotedString(ref atom) => serialize_string(&atom.to_string(), dest),
}
}
}
// A type for possible values for min- and max- flavors of width,
// height, block-size, and inline-size.
define_css_keyword_enum!(ExtremumLength:

View file

@ -13,7 +13,7 @@ extern crate parking_lot;
extern crate rayon;
extern crate rustc_serialize;
extern crate selectors;
#[macro_use] extern crate servo_atoms;
extern crate servo_atoms;
extern crate servo_config;
extern crate servo_url;
extern crate style;

View file

@ -7,6 +7,7 @@ use servo_atoms::Atom;
use style::parser::Parse;
use style::properties::longhands::animation_iteration_count::single_value::computed_value::T as AnimationIterationCount;
use style::properties::longhands::animation_name;
use style::values::{KeyframesName, CustomIdent};
use style_traits::ToCss;
#[test]
@ -14,13 +15,13 @@ fn test_animation_name() {
use self::animation_name::single_value::SpecifiedValue as SingleValue;
let other_name = Atom::from("other-name");
assert_eq!(parse_longhand!(animation_name, "none"),
animation_name::SpecifiedValue(vec![SingleValue(atom!(""))]));
animation_name::SpecifiedValue(vec![SingleValue(None)]));
assert_eq!(parse_longhand!(animation_name, "other-name, none, 'other-name', \"other-name\""),
animation_name::SpecifiedValue(
vec![SingleValue(other_name.clone()),
SingleValue(atom!("")),
SingleValue(other_name.clone()),
SingleValue(other_name.clone())]));
vec![SingleValue(Some(KeyframesName::Ident(CustomIdent(other_name.clone())))),
SingleValue(None),
SingleValue(Some(KeyframesName::QuotedString(other_name.clone()))),
SingleValue(Some(KeyframesName::QuotedString(other_name.clone())))]));
}
#[test]

View file

@ -8,6 +8,7 @@ use style::properties::{PropertyDeclaration, Importance, PropertyId};
use style::properties::longhands::outline_color::computed_value::T as ComputedColor;
use style::properties::parse_property_declaration_list;
use style::values::{RGBA, Auto};
use style::values::CustomIdent;
use style::values::specified::{BorderStyle, BorderWidth, CSSColor, Length, NoCalcLength};
use style::values::specified::{LengthOrPercentage, LengthOrPercentageOrAuto, LengthOrPercentageOrAutoOrContent};
use style::values::specified::url::SpecifiedUrl;
@ -1259,8 +1260,8 @@ mod shorthand_serialization {
fn counter_increment_with_properties_should_serialize_correctly() {
let mut properties = Vec::new();
properties.push(("counter1".to_owned(), Integer::new(1)));
properties.push(("counter2".to_owned(), Integer::new(-4)));
properties.push((CustomIdent("counter1".into()), Integer::new(1)));
properties.push((CustomIdent("counter2".into()), Integer::new(-4)));
let counter_increment = CounterIncrement(properties);
let counter_increment_css = "counter1 1 counter2 -4";

View file

@ -23,6 +23,7 @@ use style::properties::longhands::animation_play_state;
use style::shared_lock::SharedRwLock;
use style::stylesheets::{Origin, Namespaces};
use style::stylesheets::{Stylesheet, NamespaceRule, CssRule, CssRules, StyleRule, KeyframesRule};
use style::values::{KeyframesName, CustomIdent};
use style::values::specified::{LengthOrPercentageOrAuto, Percentage};
pub fn block_from<I>(iterable: I) -> PropertyDeclarationBlock
@ -221,7 +222,7 @@ fn test_parse_stylesheet() {
]))),
}))),
CssRule::Keyframes(Arc::new(stylesheet.shared_lock.wrap(KeyframesRule {
name: "foo".into(),
name: KeyframesName::Ident(CustomIdent("foo".into())),
keyframes: vec![
Arc::new(stylesheet.shared_lock.wrap(Keyframe {
selector: KeyframeSelector::new_for_unit_testing(

View file

@ -553680,7 +553680,7 @@
"testharness"
],
"cssom/CSSKeyframesRule.html": [
"3efb8e5cef257a0b433192742d526709357b24c7",
"bca997a63c1389ef6d14aac2f32ab770fbd15ec4",
"testharness"
],
"cssom/CSSNamespaceRule.html": [

View file

@ -10,6 +10,7 @@
0% { top: 0px; }
100% { top: 200px; }
}
@keyframes empty {}
</style>
<script>
@ -54,16 +55,18 @@
assert_equals(keyframe.cssRules[2].cssText, "0% { top: 50px; }", "CSSKeyframesRule cssRule cssText attribute after deleteRule function");
assert_equals(keyframe.cssRules[3], undefined, "CSSKeyframesRule cssRule cssText attribute after deleteRule function");
keyframe.name = "bar";
assert_equals(keyframe.name, "bar", "CSSKeyframesRule name setter");
var empty = document.styleSheets[0].cssRules[1];
empty.name = "bar";
assert_equals(empty.name, "bar", "CSSKeyframesRule name setter");
assert_equals(empty.cssText.replace(/\s/g, ""), "@keyframesbar{}", "CSSKeyframesRule cssText attribute");
assert_throws("SyntaxError",
function () { keyframe.name = "initial"; },
"CSSKeyframesRule name setter on invalid keyword.");
empty.name = "initial";
assert_equals(empty.name, "initial", "CSSKeyframesRule name setter, CSS-wide keyword");
assert_equals(empty.cssText.replace(/\s/g, ""), "@keyframes\"initial\"{}", "CSSKeyframesRule cssText attribute with CSS-wide keyword name");
assert_throws("SyntaxError",
function () { keyframe.name = "none"; },
"CSSKeyframesRule name setter on invalid keyword.");
empty.name = "none";
assert_equals(empty.name, "none", "CSSKeyframesRule name setter, 'none'");
assert_equals(empty.cssText.replace(/\s/g, ""), "@keyframes\"none\"{}", "CSSKeyframesRule cssText attribute with 'none' name");
});
</script>
</head>