mirror of
https://github.com/servo/servo.git
synced 2025-08-05 05:30:08 +01:00
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:
commit
2eff661ebb
27 changed files with 1140 additions and 134 deletions
|
@ -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();
|
||||
|
|
|
@ -8,6 +8,8 @@ left
|
|||
center
|
||||
right
|
||||
|
||||
none
|
||||
|
||||
hidden
|
||||
submit
|
||||
button
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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(())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)),
|
||||
|
|
|
@ -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()
|
||||
.animation_name_iter()
|
||||
.position(|animation_name| *name == animation_name.0);
|
||||
let maybe_index = style
|
||||
.get_box()
|
||||
.animation_name_iter()
|
||||
.position(|animation_name| Some(name) == animation_name.as_atom());
|
||||
|
||||
let index = match maybe_index {
|
||||
Some(index) => index,
|
||||
|
|
632
components/style/counter_style/mod.rs
Normal file
632
components/style/counter_style/mod.rs
Normal 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 we’d 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 don’t 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),
|
||||
}
|
||||
}
|
||||
}
|
61
components/style/counter_style/predefined.rs
Normal file
61
components/style/counter_style/predefined.rs
Normal 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",
|
||||
}
|
32
components/style/counter_style/update_predefined.py
Executable file
32
components/style/counter_style/update_predefined.py
Executable 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"))
|
|
@ -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),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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| {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,48 +820,32 @@ ${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!("")
|
||||
} else {
|
||||
Atom::from(&**value)
|
||||
}
|
||||
}
|
||||
Ok(Token::QuotedString(value)) => Atom::from(&*value),
|
||||
_ => return Err(()),
|
||||
};
|
||||
Ok(SpecifiedValue(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 {
|
||||
input.expect_ident_matching("none").map(|()| SpecifiedValue(None))
|
||||
}
|
||||
}
|
||||
}
|
||||
no_viewport_percentage!(SpecifiedValue);
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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>>),
|
||||
|
@ -331,15 +334,16 @@ impl CssRule {
|
|||
#[allow(missing_docs)]
|
||||
pub fn rule_type(&self) -> CssRuleType {
|
||||
match *self {
|
||||
CssRule::Style(_) => CssRuleType::Style,
|
||||
CssRule::Import(_) => CssRuleType::Import,
|
||||
CssRule::Media(_) => CssRuleType::Media,
|
||||
CssRule::FontFace(_) => CssRuleType::FontFace,
|
||||
CssRule::Style(_) => CssRuleType::Style,
|
||||
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,
|
||||
CssRule::Supports(_) => CssRuleType::Supports,
|
||||
CssRule::Page(_) => CssRuleType::Page,
|
||||
CssRule::Viewport(_) => CssRuleType::Viewport,
|
||||
CssRule::Supports(_) => CssRuleType::Supports,
|
||||
CssRule::Page(_) => CssRuleType::Page,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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,
|
||||
|
|
|
@ -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) => {
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -553680,7 +553680,7 @@
|
|||
"testharness"
|
||||
],
|
||||
"cssom/CSSKeyframesRule.html": [
|
||||
"3efb8e5cef257a0b433192742d526709357b24c7",
|
||||
"bca997a63c1389ef6d14aac2f32ab770fbd15ec4",
|
||||
"testharness"
|
||||
],
|
||||
"cssom/CSSNamespaceRule.html": [
|
||||
|
|
|
@ -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>
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue