diff --git a/servo/src/components/gfx/font_cache_task.rs b/servo/src/components/gfx/font_cache_task.rs index a3f7f8c2882c..1dc919c5d268 100644 --- a/servo/src/components/gfx/font_cache_task.rs +++ b/servo/src/components/gfx/font_cache_task.rs @@ -11,6 +11,8 @@ use std::collections::HashMap; use sync::Arc; use font_template::{FontTemplate, FontTemplateDescriptor}; use platform::font_template::FontTemplateData; +use servo_net::resource_task::{ResourceTask, load_whole_resource}; +use url::Url; /// A list of font templates that make up a given font family. struct FontFamily { @@ -40,13 +42,35 @@ impl FontFamily { } } + // If a request is made for a font family that exists, + // pick the first valid font in the family if we failed + // to find an exact match for the descriptor. + for template in self.templates.mut_iter() { + let maybe_template = template.get(); + if maybe_template.is_some() { + return maybe_template; + } + } + None } + + fn add_template(&mut self, identifier: &str, maybe_data: Option>) { + for template in self.templates.iter() { + if template.identifier() == identifier { + return; + } + } + + let template = FontTemplate::new(identifier, maybe_data); + self.templates.push(template); + } } /// Commands that the FontContext sends to the font cache task. pub enum Command { GetFontTemplate(String, FontTemplateDescriptor, Sender), + AddWebFont(Vec, String, Sender<()>), Exit(Sender<()>), } @@ -61,7 +85,9 @@ struct FontCache { port: Receiver, generic_fonts: HashMap, local_families: HashMap, + web_families: HashMap, font_context: FontContextHandle, + resource_task: ResourceTask, } impl FontCache { @@ -72,7 +98,6 @@ impl FontCache { match msg { GetFontTemplate(family, descriptor, result) => { let maybe_font_template = self.get_font_template(&family, &descriptor); - let font_template = match maybe_font_template { Some(font_template) => font_template, None => self.get_last_resort_template(&descriptor), @@ -80,6 +105,25 @@ impl FontCache { result.send(GetFontTemplateReply(font_template)); } + AddWebFont(urls, family_name, result) => { + for url in urls.iter() { + let maybe_resource = load_whole_resource(&self.resource_task, url.clone()); + match maybe_resource { + Ok((_, bytes)) => { + if !self.web_families.contains_key(&family_name) { + let family = FontFamily::new(); + self.web_families.insert(family_name.clone(), family); + } + let family = self.web_families.get_mut(&family_name); + family.add_template(format!("{}", url).as_slice(), Some(bytes)); + }, + Err(msg) => { + fail!(msg); + } + } + } + result.send(()); + } Exit(result) => { result.send(()); break; @@ -105,9 +149,8 @@ impl FontCache { } } - fn find_font_in_family<'a>(&'a mut self, - family_name: &String, - desc: &FontTemplateDescriptor) -> Option> { + fn find_font_in_local_family<'a>(&'a mut self, family_name: &String, desc: &FontTemplateDescriptor) + -> Option> { // TODO(Issue #188): look up localized font family names if canonical name not found // look up canonical name if self.local_families.contains_key(family_name) { @@ -116,8 +159,7 @@ impl FontCache { if s.templates.len() == 0 { get_variations_for_family(family_name.as_slice(), |path| { - let template = FontTemplate::new(path.as_slice()); - s.templates.push(template); + s.add_template(path.as_slice(), None); }); } @@ -135,16 +177,31 @@ impl FontCache { } } + fn find_font_in_web_family<'a>(&'a mut self, family_name: &String, desc: &FontTemplateDescriptor) + -> Option> { + if self.web_families.contains_key(family_name) { + let family = self.web_families.get_mut(family_name); + let maybe_font = family.find_font_for_style(desc, &self.font_context); + maybe_font + } else { + None + } + } + fn get_font_template(&mut self, family: &String, desc: &FontTemplateDescriptor) -> Option> { let transformed_family_name = self.transform_family(family); - self.find_font_in_family(&transformed_family_name, desc) + let mut maybe_template = self.find_font_in_web_family(&transformed_family_name, desc); + if maybe_template.is_none() { + maybe_template = self.find_font_in_local_family(&transformed_family_name, desc); + } + maybe_template } fn get_last_resort_template(&mut self, desc: &FontTemplateDescriptor) -> Arc { let last_resort = get_last_resort_font_families(); for family in last_resort.iter() { - let maybe_font_in_family = self.find_font_in_family(family, desc); + let maybe_font_in_family = self.find_font_in_local_family(family, desc); if maybe_font_in_family.is_some() { return maybe_font_in_family.unwrap(); } @@ -162,7 +219,7 @@ pub struct FontCacheTask { } impl FontCacheTask { - pub fn new() -> FontCacheTask { + pub fn new(resource_task: ResourceTask) -> FontCacheTask { let (chan, port) = channel(); spawn(proc() { @@ -178,7 +235,9 @@ impl FontCacheTask { port: port, generic_fonts: generic_fonts, local_families: HashMap::new(), + web_families: HashMap::new(), font_context: FontContextHandle::new(), + resource_task: resource_task, }; cache.refresh_local_families(); @@ -205,6 +264,12 @@ impl FontCacheTask { } } + pub fn add_web_font(&mut self, urls: Vec, family: &str) { + let (response_chan, response_port) = channel(); + self.chan.send(AddWebFont(urls, family.to_string(), response_chan)); + response_port.recv(); + } + pub fn exit(&self) { let (response_chan, response_port) = channel(); self.chan.send(Exit(response_chan)); diff --git a/servo/src/components/gfx/font_context.rs b/servo/src/components/gfx/font_context.rs index a9cccae61fb1..0a1ef69e0ced 100644 --- a/servo/src/components/gfx/font_context.rs +++ b/servo/src/components/gfx/font_context.rs @@ -34,7 +34,7 @@ fn create_scaled_font(backend: BackendType, template: &Arc, pt #[cfg(target_os="macos")] fn create_scaled_font(backend: BackendType, template: &Arc, pt_size: f64) -> ScaledFont { - let cgfont = template.ctfont.copy_to_CGFont(); + let cgfont = template.ctfont.get_ref().copy_to_CGFont(); ScaledFont::new(backend, &cgfont, pt_size as AzFloat) } diff --git a/servo/src/components/gfx/font_template.rs b/servo/src/components/gfx/font_template.rs index 12753da68a18..3f4916b69c56 100644 --- a/servo/src/components/gfx/font_template.rs +++ b/servo/src/components/gfx/font_template.rs @@ -42,21 +42,44 @@ impl PartialEq for FontTemplateDescriptor { pub struct FontTemplate { identifier: String, descriptor: Option, - data: Option>, + weak_ref: Option>, + strong_ref: Option>, // GWTODO: Add code path to unset the strong_ref for web fonts! + is_valid: bool, } /// Holds all of the template information for a font that /// is common, regardless of the number of instances of /// this font handle per thread. impl FontTemplate { - pub fn new(identifier: &str) -> FontTemplate { + pub fn new(identifier: &str, maybe_bytes: Option>) -> FontTemplate { + let maybe_data = match maybe_bytes { + Some(_) => Some(FontTemplateData::new(identifier, maybe_bytes)), + None => None, + }; + + let maybe_strong_ref = match maybe_data { + Some(data) => Some(Arc::new(data)), + None => None, + }; + + let maybe_weak_ref = match maybe_strong_ref { + Some(ref strong_ref) => Some(strong_ref.downgrade()), + None => None, + }; + FontTemplate { identifier: identifier.to_string(), descriptor: None, - data: None, + weak_ref: maybe_weak_ref, + strong_ref: maybe_strong_ref, + is_valid: true, } } + pub fn identifier<'a>(&'a self) -> &'a str { + self.identifier.as_slice() + } + /// Get the data for creating a font if it matches a given descriptor. pub fn get_if_matches(&mut self, fctx: &FontContextHandle, requested_desc: &FontTemplateDescriptor) -> Option> { @@ -74,19 +97,29 @@ impl FontTemplate { } }, None => { - let data = self.get_data(); - let handle = FontHandleMethods::new_from_template(fctx, data.clone(), None); - let handle: FontHandle = match handle { - Ok(handle) => handle, - Err(()) => fail!("TODO - Handle failure to create a font from template."), - }; - let actual_desc = FontTemplateDescriptor::new(handle.boldness(), - handle.is_italic()); - let desc_match = actual_desc == *requested_desc; + if self.is_valid { + let data = self.get_data(); + let handle: Result = FontHandleMethods::new_from_template(fctx, data.clone(), None); + match handle { + Ok(handle) => { + let actual_desc = FontTemplateDescriptor::new(handle.boldness(), + handle.is_italic()); + let desc_match = actual_desc == *requested_desc; - self.descriptor = Some(actual_desc); - if desc_match { - Some(data) + self.descriptor = Some(actual_desc); + self.is_valid = true; + if desc_match { + Some(data) + } else { + None + } + } + Err(()) => { + self.is_valid = false; + debug!("Unable to create a font from template {}", self.identifier); + None + } + } } else { None } @@ -94,11 +127,19 @@ impl FontTemplate { } } + /// Get the data for creating a font. + pub fn get(&mut self) -> Option> { + match self.is_valid { + true => Some(self.get_data()), + false => None + } + } + /// Get the font template data. If any strong references still /// exist, it will return a clone, otherwise it will load the /// font data and store a weak reference to it internally. pub fn get_data(&mut self) -> Arc { - let maybe_data = match self.data { + let maybe_data = match self.weak_ref { Some(ref data) => data.upgrade(), None => None, }; @@ -106,8 +147,9 @@ impl FontTemplate { match maybe_data { Some(data) => data, None => { - let template_data = Arc::new(FontTemplateData::new(self.identifier.as_slice())); - self.data = Some(template_data.downgrade()); + assert!(self.strong_ref.is_none()); + let template_data = Arc::new(FontTemplateData::new(self.identifier.as_slice(), None)); + self.weak_ref = Some(template_data.downgrade()); template_data } } diff --git a/servo/src/components/gfx/gfx.rs b/servo/src/components/gfx/gfx.rs index 32fa30aba51a..3e3ad8a308e9 100644 --- a/servo/src/components/gfx/gfx.rs +++ b/servo/src/components/gfx/gfx.rs @@ -29,6 +29,7 @@ extern crate servo_util = "util"; extern crate servo_msg = "msg"; extern crate style; extern crate sync; +extern crate url = "url_"; // Eventually we would like the shaper to be pluggable, as many operating systems have their own // shapers. For now, however, this is a hard dependency. diff --git a/servo/src/components/gfx/platform/android/font_template.rs b/servo/src/components/gfx/platform/android/font_template.rs index d1a821a0bf39..e8c058ee9ace 100644 --- a/servo/src/components/gfx/platform/android/font_template.rs +++ b/servo/src/components/gfx/platform/android/font_template.rs @@ -15,10 +15,17 @@ pub struct FontTemplateData { } impl FontTemplateData { - pub fn new(identifier: &str) -> FontTemplateData { - // TODO: Handle file load failure! - let mut file = File::open_mode(&Path::new(identifier), io::Open, io::Read).unwrap(); - let bytes = file.read_to_end().unwrap(); + pub fn new(identifier: &str, font_data: Option>) -> FontTemplateData { + let bytes = match font_data { + Some(bytes) => { + bytes + }, + None => { + // TODO: Handle file load failure! + let mut file = File::open_mode(&Path::new(identifier), io::Open, io::Read).unwrap(); + file.read_to_end().unwrap() + }, + }; FontTemplateData { bytes: bytes, diff --git a/servo/src/components/gfx/platform/linux/font_template.rs b/servo/src/components/gfx/platform/linux/font_template.rs index e00a1018317c..663ea64ab297 100644 --- a/servo/src/components/gfx/platform/linux/font_template.rs +++ b/servo/src/components/gfx/platform/linux/font_template.rs @@ -15,10 +15,17 @@ pub struct FontTemplateData { } impl FontTemplateData { - pub fn new(identifier: &str) -> FontTemplateData { - // TODO: Handle file load failure! - let mut file = File::open_mode(&Path::new(identifier), io::Open, io::Read).unwrap(); - let bytes = file.read_to_end().unwrap(); + pub fn new(identifier: &str, font_data: Option>) -> FontTemplateData { + let bytes = match font_data { + Some(bytes) => { + bytes + }, + None => { + // TODO: Handle file load failure! + let mut file = File::open_mode(&Path::new(identifier), io::Open, io::Read).unwrap(); + file.read_to_end().unwrap() + }, + }; FontTemplateData { bytes: bytes, diff --git a/servo/src/components/gfx/platform/macos/font.rs b/servo/src/components/gfx/platform/macos/font.rs index a6a5f092d784..682c44088d20 100644 --- a/servo/src/components/gfx/platform/macos/font.rs +++ b/servo/src/components/gfx/platform/macos/font.rs @@ -65,13 +65,17 @@ impl FontHandleMethods for FontHandle { Some(s) => s, None => 0.0 }; - let ct_result = core_text::font::new_from_name(template.identifier.as_slice(), size); - ct_result.and_then(|ctfont| { - Ok(FontHandle { - font_data: template.clone(), - ctfont: ctfont, - }) - }) + match template.ctfont { + Some(ref ctfont) => { + Ok(FontHandle { + font_data: template.clone(), + ctfont: ctfont.clone_with_font_size(size), + }) + } + None => { + Err(()) + } + } } fn get_template(&self) -> Arc { diff --git a/servo/src/components/gfx/platform/macos/font_template.rs b/servo/src/components/gfx/platform/macos/font_template.rs index bda41186da31..8641d491523e 100644 --- a/servo/src/components/gfx/platform/macos/font_template.rs +++ b/servo/src/components/gfx/platform/macos/font_template.rs @@ -2,6 +2,8 @@ * 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/. */ +use core_graphics::data_provider::CGDataProvider; +use core_graphics::font::CGFont; use core_text::font::CTFont; use core_text; @@ -10,15 +12,28 @@ use core_text; /// CTFont object is cached here for use by the /// render functions that create CGFont references. pub struct FontTemplateData { - pub ctfont: CTFont, + pub ctfont: Option, pub identifier: String, } impl FontTemplateData { - pub fn new(identifier: &str) -> FontTemplateData { - let ctfont_result = core_text::font::new_from_name(identifier.as_slice(), 0.0); + pub fn new(identifier: &str, font_data: Option>) -> FontTemplateData { + let ctfont = match font_data { + Some(bytes) => { + let fontprov = CGDataProvider::from_buffer(bytes.as_slice()); + let cgfont_result = CGFont::from_data_provider(fontprov); + match cgfont_result { + Ok(cgfont) => Some(core_text::font::new_from_CGFont(&cgfont, 0.0)), + Err(_) => None + } + }, + None => { + Some(core_text::font::new_from_name(identifier.as_slice(), 0.0).unwrap()) + } + }; + FontTemplateData { - ctfont: ctfont_result.unwrap(), + ctfont: ctfont, identifier: identifier.to_string(), } } diff --git a/servo/src/components/layout/layout_task.rs b/servo/src/components/layout/layout_task.rs index 3d9cca467378..c40ed74f342d 100644 --- a/servo/src/components/layout/layout_task.rs +++ b/servo/src/components/layout/layout_task.rs @@ -59,6 +59,7 @@ use std::comm::{channel, Sender, Receiver}; use std::mem; use std::ptr; use style::{AuthorOrigin, Stylesheet, Stylist}; +use style::CSSFontFaceRule; use sync::{Arc, Mutex}; use url::Url; @@ -447,7 +448,25 @@ impl LayoutTask { } fn handle_add_stylesheet(&mut self, sheet: Stylesheet) { - self.stylist.add_stylesheet(sheet, AuthorOrigin) + // Find all font-face rules and notify the font cache of them. + // GWTODO: Need to handle unloading web fonts (when we handle unloading stylesheets!) + // GWTODO: Need to handle font-face nested within media rules. + for rule in sheet.rules.iter() { + match rule { + &CSSFontFaceRule(ref font_face_rule) => { + let mut font_urls = vec!(); + for source_line in font_face_rule.source_lines.iter() { + for source in source_line.sources.iter() { + font_urls.push(source.url.clone()); + } + } + self.font_cache_task.add_web_font(font_urls, font_face_rule.family.as_slice()); + }, + _ => {} + } + } + + self.stylist.add_stylesheet(sheet, AuthorOrigin); } /// Retrieves the flow tree root from the root node. diff --git a/servo/src/components/main/servo.rs b/servo/src/components/main/servo.rs index fcf3f9fd3153..09225b9ae965 100644 --- a/servo/src/components/main/servo.rs +++ b/servo/src/components/main/servo.rs @@ -125,7 +125,7 @@ pub fn run(opts: opts::Opts) { } else { ImageCacheTask::new(resource_task.clone()) }; - let font_cache_task = FontCacheTask::new(); + let font_cache_task = FontCacheTask::new(resource_task.clone()); let constellation_chan = Constellation::::start( compositor_chan, opts, diff --git a/servo/src/components/style/font_face.rs b/servo/src/components/style/font_face.rs new file mode 100644 index 000000000000..228c5d459aeb --- /dev/null +++ b/servo/src/components/style/font_face.rs @@ -0,0 +1,207 @@ +/* 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/. */ + +use cssparser::ast::*; +use cssparser::parse_declaration_list; +use errors::{ErrorLoggerIterator, log_css_error}; +use std::ascii::StrAsciiExt; +use parsing_utils::one_component_value; +use stylesheets::{CSSRule, CSSFontFaceRule}; +use url::{Url, UrlParser}; + +#[deriving(PartialEq)] +pub enum FontFaceFormat { + UnknownFormat, + WoffFormat, + TtfFormat, + SvgFormat, + EotFormat, +} + +pub struct FontFaceSource { + pub url: Url, + pub format_hints: Vec, +} + +pub struct FontFaceSourceLine { + pub sources: Vec +} + +pub struct FontFaceRule { + pub family: String, + pub source_lines: Vec, +} + +pub fn parse_font_face_rule(rule: AtRule, parent_rules: &mut Vec, base_url: &Url) { + let mut maybe_family = None; + let mut source_lines = vec!(); + + if rule.prelude.as_slice().skip_whitespace().next().is_some() { + log_css_error(rule.location, "@font-face prelude contains unexpected characters"); + return; + } + + let block = match rule.block { + Some(block) => block, + None => { + log_css_error(rule.location, "Invalid @font-face rule"); + return + } + }; + + for item in ErrorLoggerIterator(parse_declaration_list(block.move_iter())) { + match item { + DeclAtRule(rule) => log_css_error( + rule.location, format!("Unsupported at-rule in declaration list: @{:s}", rule.name).as_slice()), + Declaration(Declaration{ location: location, name: name, value: value, important: _}) => { + + let name_lower = name.as_slice().to_ascii_lower(); + match name_lower.as_slice() { + "font-family" => { + // FIXME(#2802): Share code with the font-family parser. + match one_component_value(value.as_slice()) { + Some(&String(ref string_value)) => { + maybe_family = Some(string_value.clone()); + }, + _ => { + log_css_error(location, format!("Unsupported font-family string {:s}", name).as_slice()); + } + } + }, + "src" => { + let mut iter = value.as_slice().skip_whitespace(); + let mut sources = vec!(); + let mut syntax_error = false; + + 'outer: loop { + + // url() or local() should be next + let maybe_url = match iter.next() { + Some(&URL(ref string_value)) => { + let maybe_url = UrlParser::new().base_url(base_url).parse(string_value.as_slice()); + let url = maybe_url.unwrap_or_else(|_| Url::parse("about:invalid").unwrap()); + Some(url) + }, + Some(&Function(ref string_value, ref _values)) => { + match string_value.as_slice() { + "local" => { + log_css_error(location, "local font face is not supported yet - skipping"); + None + }, + _ => { + log_css_error(location, format!("Unexpected token {}", string_value).as_slice()); + syntax_error = true; + break; + } + } + }, + _ => { + log_css_error(location, "Unsupported declaration type"); + syntax_error = true; + break; + } + }; + + let mut next_token = iter.next(); + + match maybe_url { + Some(url) => { + let mut source = FontFaceSource { + url: url, + format_hints: vec!(), + }; + + // optional format, or comma to start loop again + match next_token { + Some(&Function(ref string_value, ref values)) => { + match string_value.as_slice() { + "format" => { + let mut format_iter = values.as_slice().skip_whitespace(); + + loop { + let fmt_token = format_iter.next(); + match fmt_token { + Some(&String(ref format_hint)) => { + let hint = match format_hint.as_slice() { + "embedded-opentype" => EotFormat, + "woff" => WoffFormat, + "truetype" | "opentype" => TtfFormat, + "svg" => SvgFormat, + _ => UnknownFormat, + }; + source.format_hints.push(hint); + }, + _ => { + log_css_error(location, "Unexpected token"); + syntax_error = true; + break 'outer; + } + } + + let comma_token = format_iter.next(); + match comma_token { + Some(&Comma) => {}, + None => { + break; + } + _ => { + log_css_error(location, "Unexpected token"); + syntax_error = true; + break 'outer; + } + } + } + }, + _ => { + log_css_error(location, + format!("Unsupported token {}", string_value).as_slice()); + syntax_error = true; + break; + } + } + next_token = iter.next(); + }, + _ => {} + } + + sources.push(source); + }, + None => {}, + } + + // after url or optional format, comes comma or end + match next_token { + Some(&Comma) => {}, + None => break, + _ => { + log_css_error(location, "Unexpected token type"); + syntax_error = true; + break; + } + } + } + + if !syntax_error && sources.len() > 0 { + let source_line = FontFaceSourceLine { + sources: sources + }; + source_lines.push(source_line); + } + }, + _ => { + log_css_error(location, format!("Unsupported declaration {:s}", name).as_slice()); + } + } + } + } + } + + if maybe_family.is_some() && source_lines.len() > 0 { + let font_face_rule = FontFaceRule { + family: maybe_family.unwrap(), + source_lines: source_lines, + }; + parent_rules.push(CSSFontFaceRule(font_face_rule)); + } +} diff --git a/servo/src/components/style/style.rs b/servo/src/components/style/style.rs index 3d67eb761a1c..f4c5ba56e7ee 100644 --- a/servo/src/components/style/style.rs +++ b/servo/src/components/style/style.rs @@ -30,7 +30,7 @@ extern crate servo_util = "util"; // Public API -pub use stylesheets::{Stylesheet, CSSRule, StyleRule}; +pub use stylesheets::{Stylesheet, CSSRule, StyleRule, CSSFontFaceRule}; pub use selector_matching::{Stylist, StylesheetOrigin, UserAgentOrigin, AuthorOrigin, UserOrigin}; pub use selector_matching::{MatchedProperty, matches_compound_selector}; pub use properties::{cascade, cascade_anonymous}; @@ -45,6 +45,7 @@ pub use selectors::{NamespaceConstraint, Selector, CompoundSelector, SimpleSelec pub use selectors::{parse_selector_list}; pub use namespaces::NamespaceMap; pub use media_queries::{MediaRule, MediaQueryList, MediaQuery, Device, MediaType, MediaQueryType}; +pub use font_face::{FontFaceFormat, FontFaceRule, FontFaceSource,FontFaceSourceLine, TtfFormat}; mod stylesheets; mod errors; @@ -55,3 +56,4 @@ mod namespaces; mod node; mod media_queries; mod parsing_utils; +mod font_face; diff --git a/servo/src/components/style/stylesheets.rs b/servo/src/components/style/stylesheets.rs index 8992477de004..a1e78bc2a180 100644 --- a/servo/src/components/style/stylesheets.rs +++ b/servo/src/components/style/stylesheets.rs @@ -16,6 +16,7 @@ use errors::{ErrorLoggerIterator, log_css_error}; use namespaces::{NamespaceMap, parse_namespace_rule}; use media_queries::{MediaRule, parse_media_rule}; use media_queries; +use font_face::{FontFaceRule, parse_font_face_rule}; pub struct Stylesheet { @@ -28,6 +29,7 @@ pub struct Stylesheet { pub enum CSSRule { CSSStyleRule(StyleRule), CSSMediaRule(MediaRule), + CSSFontFaceRule(FontFaceRule), } @@ -143,6 +145,7 @@ pub fn parse_nested_at_rule(lower_name: &str, rule: AtRule, parent_rules: &mut Vec, namespaces: &NamespaceMap, base_url: &Url) { match lower_name { "media" => parse_media_rule(rule, parent_rules, namespaces, base_url), + "font-face" => parse_font_face_rule(rule, parent_rules, base_url), _ => log_css_error(rule.location, format!("Unsupported at-rule: @{:s}", lower_name).as_slice()) } @@ -156,7 +159,8 @@ pub fn iter_style_rules<'a>(rules: &[CSSRule], device: &media_queries::Device, CSSStyleRule(ref rule) => callback(rule), CSSMediaRule(ref rule) => if rule.media_queries.evaluate(device) { iter_style_rules(rule.rules.as_slice(), device, |s| callback(s)) - } + }, + CSSFontFaceRule(_) => {}, } } }