Make the parser accept @font-face rules without font-family or src.

Fix #16165.

Also, it turns out that the CSSFontFaceRule IDL specified in the
css-fonts spec is not web-compatible.
Instead browsers implement a .style attribute like in CSSStyleRule:
https://github.com/w3c/csswg-drafts/issues/825

This in turn requires preserving data about which descriptors
were set or not (distinguishing unset from set to a value that happens
to be the initial value),
so this commit also makes every field `Option<_>`.
This commit is contained in:
Simon Sapin 2017-04-01 08:20:35 +02:00
parent 15fc9f3bbf
commit 61812d4d9d
7 changed files with 169 additions and 168 deletions

View file

@ -16,7 +16,6 @@ use cssparser::{AtRuleParser, DeclarationListParser, DeclarationParser, Parser};
use parser::{ParserContext, log_css_error, Parse};
use shared_lock::{SharedRwLockReadGuard, ToCssWithGuard};
use std::fmt;
use std::iter;
use style_traits::{ToCss, OneOrMoreCommaSeparated};
use values::specified::url::SpecifiedUrl;
@ -74,14 +73,12 @@ impl ToCss for UrlSource {
/// Parse the block inside a `@font-face` rule.
///
/// Note that the prelude parsing code lives in the `stylesheets` module.
pub fn parse_font_face_block(context: &ParserContext, input: &mut Parser)
-> Result<FontFaceData, ()> {
let mut rule = FontFaceData::initial();
pub fn parse_font_face_block(context: &ParserContext, input: &mut Parser) -> FontFaceRuleData {
let mut rule = FontFaceRuleData::empty();
{
let parser = FontFaceRuleParser {
context: context,
rule: &mut rule,
missing: MissingDescriptors::new(),
};
let mut iter = DeclarationListParser::new(input, parser);
while let Some(declaration) = iter.next() {
@ -92,24 +89,27 @@ pub fn parse_font_face_block(context: &ParserContext, input: &mut Parser)
log_css_error(iter.input, pos, &*message, context);
}
}
if iter.parser.missing.any() {
return Err(())
}
}
Ok(rule)
rule
}
/// A @font-face rule that is known to have font-family and src declarations.
#[cfg(feature = "servo")]
pub struct FontFace<'a>(&'a FontFaceRuleData);
/// A list of effective sources that we send over through IPC to the font cache.
#[cfg(feature = "servo")]
#[derive(Clone, Debug)]
#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))]
pub struct EffectiveSources(Vec<Source>);
impl FontFaceData {
#[cfg(feature = "servo")]
impl<'a> FontFace<'a> {
/// Returns the list of effective sources for that font-face, that is the
/// sources which don't list any format hint, or the ones which list at
/// least "truetype" or "opentype".
pub fn effective_sources(&self) -> EffectiveSources {
EffectiveSources(self.sources.iter().rev().filter(|source| {
EffectiveSources(self.sources().iter().rev().filter(|source| {
if let Source::Url(ref url_source) = **source {
let hints = &url_source.format_hints;
// We support only opentype fonts and truetype is an alias for
@ -125,7 +125,8 @@ impl FontFaceData {
}
}
impl iter::Iterator for EffectiveSources {
#[cfg(feature = "servo")]
impl Iterator for EffectiveSources {
type Item = Source;
fn next(&mut self) -> Option<Source> {
self.0.pop()
@ -134,8 +135,7 @@ impl iter::Iterator for EffectiveSources {
struct FontFaceRuleParser<'a, 'b: 'a> {
context: &'a ParserContext<'b>,
rule: &'a mut FontFaceData,
missing: MissingDescriptors,
rule: &'a mut FontFaceRuleData,
}
/// Default methods reject all at rules.
@ -172,82 +172,40 @@ impl Parse for Source {
}
}
macro_rules! font_face_descriptors {
macro_rules! font_face_descriptors_common {
(
mandatory descriptors = [
$( #[$m_doc: meta] $m_name: tt $m_ident: ident: $m_ty: ty = $m_initial: expr, )*
]
optional descriptors = [
$( #[$o_doc: meta] $o_name: tt $o_ident: ident: $o_ty: ty = $o_initial: expr, )*
]
$( #[$doc: meta] $name: tt $ident: ident: $ty: ty, )*
) => {
/// Data inside a `@font-face` rule.
///
/// https://drafts.csswg.org/css-fonts/#font-face-rule
#[derive(Debug, PartialEq, Eq)]
pub struct FontFaceData {
pub struct FontFaceRuleData {
$(
#[$m_doc]
pub $m_ident: $m_ty,
)*
$(
#[$o_doc]
pub $o_ident: $o_ty,
#[$doc]
pub $ident: Option<$ty>,
)*
}
struct MissingDescriptors {
$(
$m_ident: bool,
)*
}
impl MissingDescriptors {
fn new() -> Self {
MissingDescriptors {
impl FontFaceRuleData {
fn empty() -> Self {
FontFaceRuleData {
$(
$m_ident: true,
)*
}
}
fn any(&self) -> bool {
$(
self.$m_ident
)||*
}
}
impl FontFaceData {
fn initial() -> Self {
FontFaceData {
$(
$m_ident: $m_initial,
)*
$(
$o_ident: $o_initial,
$ident: None,
)*
}
}
}
impl ToCssWithGuard for FontFaceData {
impl ToCssWithGuard for FontFaceRuleData {
// Serialization of FontFaceRule is not specced.
fn to_css<W>(&self, _guard: &SharedRwLockReadGuard, dest: &mut W) -> fmt::Result
where W: fmt::Write {
dest.write_str("@font-face {\n")?;
$(
dest.write_str(concat!(" ", $m_name, ": "))?;
ToCss::to_css(&self.$m_ident, dest)?;
dest.write_str(";\n")?;
)*
$(
// Because of parse_font_face_block,
// this condition is always true for "src" and "font-family".
// But it can be false for other descriptors.
if self.$o_ident != $o_initial {
dest.write_str(concat!(" ", $o_name, ": "))?;
ToCss::to_css(&self.$o_ident, dest)?;
if let Some(ref value) = self.$ident {
dest.write_str(concat!(" ", $name, ": "))?;
ToCss::to_css(value, dest)?;
dest.write_str(";\n")?;
}
)*
@ -261,13 +219,7 @@ macro_rules! font_face_descriptors {
fn parse_value(&mut self, name: &str, input: &mut Parser) -> Result<(), ()> {
match_ignore_ascii_case! { name,
$(
$m_name => {
self.rule.$m_ident = Parse::parse(self.context, input)?;
self.missing.$m_ident = false
},
)*
$(
$o_name => self.rule.$o_ident = Parse::parse(self.context, input)?,
$name => self.rule.$ident = Some(Parse::parse(self.context, input)?),
)*
_ => return Err(())
}
@ -277,18 +229,67 @@ macro_rules! font_face_descriptors {
}
}
macro_rules! font_face_descriptors {
(
mandatory descriptors = [
$( #[$m_doc: meta] $m_name: tt $m_ident: ident: $m_ty: ty, )*
]
optional descriptors = [
$( #[$o_doc: meta] $o_name: tt $o_ident: ident: $o_ty: ty = $o_initial: expr, )*
]
) => {
font_face_descriptors_common! {
$( #[$m_doc] $m_name $m_ident: $m_ty, )*
$( #[$o_doc] $o_name $o_ident: $o_ty, )*
}
impl FontFaceRuleData {
/// Per https://github.com/w3c/csswg-drafts/issues/1133 an @font-face rule
/// is valid as far as the CSS parser is concerned even if it doesnt have
/// a font-family or src declaration.
///
/// However both are required for the rule to represent an actual font face.
#[cfg(feature = "servo")]
pub fn font_face(&self) -> Option<FontFace> {
if $( self.$m_ident.is_some() )&&* {
Some(FontFace(self))
} else {
None
}
}
}
#[cfg(feature = "servo")]
impl<'a> FontFace<'a> {
$(
#[$m_doc]
pub fn $m_ident(&self) -> &$m_ty {
self.0 .$m_ident.as_ref().unwrap()
}
)*
$(
#[$o_doc]
pub fn $o_ident(&self) -> $o_ty {
if let Some(ref value) = self.0 .$o_ident {
value.clone()
} else {
$o_initial
}
}
)*
}
}
}
/// css-name rust_identifier: Type = initial_value,
#[cfg(feature = "gecko")]
font_face_descriptors! {
mandatory descriptors = [
/// The name of this font face
"font-family" family: FamilyName = FamilyName {
name: atom!(""),
quoted: true,
},
"font-family" family: FamilyName,
/// The alternative sources for this font face.
"src" sources: Vec<Source> = Vec::new(),
"src" sources: Vec<Source>,
]
optional descriptors = [
/// The style of this font face
@ -304,6 +305,8 @@ font_face_descriptors! {
"unicode-range" unicode_range: Vec<UnicodeRange> = vec![
UnicodeRange { start: 0, end: 0x10FFFF }
],
// FIXME: add font-feature-settings, font-language-override, and font-display
]
}
@ -311,13 +314,10 @@ font_face_descriptors! {
font_face_descriptors! {
mandatory descriptors = [
/// The name of this font face
"font-family" family: FamilyName = FamilyName {
name: atom!(""),
quoted: true,
},
"font-family" family: FamilyName,
/// The alternative sources for this font face.
"src" sources: Vec<Source> = Vec::new(),
"src" sources: Vec<Source>,
]
optional descriptors = [
]