Bug 1756130 [wpt PR 32898] - [CSP] Enhance unsafe-eval test to check both realms...
[gecko.git] / servo / components / style / font_face.rs
blob53e406d4b2fb3bc5ce02e2bd4531c6891168dc18
1 /* This Source Code Form is subject to the terms of the Mozilla Public
2  * License, v. 2.0. If a copy of the MPL was not distributed with this
3  * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
5 //! The [`@font-face`][ff] at-rule.
6 //!
7 //! [ff]: https://drafts.csswg.org/css-fonts/#at-font-face-rule
9 use crate::error_reporting::ContextualParseError;
10 use crate::parser::{Parse, ParserContext};
11 #[cfg(feature = "gecko")]
12 use crate::properties::longhands::font_language_override;
13 use crate::shared_lock::{SharedRwLockReadGuard, ToCssWithGuard};
14 use crate::str::CssStringWriter;
15 use crate::values::computed::font::FamilyName;
16 use crate::values::generics::font::FontStyle as GenericFontStyle;
17 #[cfg(feature = "gecko")]
18 use crate::values::specified::font::SpecifiedFontFeatureSettings;
19 use crate::values::specified::font::SpecifiedFontStyle;
20 #[cfg(feature = "gecko")]
21 use crate::values::specified::font::SpecifiedFontVariationSettings;
22 use crate::values::specified::font::{AbsoluteFontWeight, FontStretch, MetricsOverride};
23 use crate::values::specified::url::SpecifiedUrl;
24 use crate::values::specified::{Angle, NonNegativePercentage};
25 #[cfg(feature = "gecko")]
26 use cssparser::UnicodeRange;
27 use cssparser::{AtRuleParser, DeclarationListParser, DeclarationParser, Parser};
28 use cssparser::{CowRcStr, SourceLocation};
29 use selectors::parser::SelectorParseErrorKind;
30 use std::fmt::{self, Write};
31 use style_traits::values::SequenceWriter;
32 use style_traits::{Comma, CssWriter, OneOrMoreSeparated, ParseError};
33 use style_traits::{StyleParseErrorKind, ToCss};
35 /// A source for a font-face rule.
36 #[cfg_attr(feature = "servo", derive(Deserialize, Serialize))]
37 #[derive(Clone, Debug, Eq, PartialEq, ToCss, ToShmem)]
38 pub enum Source {
39     /// A `url()` source.
40     Url(UrlSource),
41     /// A `local()` source.
42     #[css(function)]
43     Local(FamilyName),
46 impl OneOrMoreSeparated for Source {
47     type S = Comma;
50 /// A POD representation for Gecko. All pointers here are non-owned and as such
51 /// can't outlive the rule they came from, but we can't enforce that via C++.
52 ///
53 /// All the strings are of course utf8.
54 #[cfg(feature = "gecko")]
55 #[repr(u8)]
56 #[allow(missing_docs)]
57 pub enum FontFaceSourceListComponent {
58     Url(*const crate::gecko::url::CssUrl),
59     Local(*mut crate::gecko_bindings::structs::nsAtom),
60     FormatHint {
61         length: usize,
62         utf8_bytes: *const u8,
63     },
66 /// A `UrlSource` represents a font-face source that has been specified with a
67 /// `url()` function.
68 ///
69 /// <https://drafts.csswg.org/css-fonts/#src-desc>
70 #[cfg_attr(feature = "servo", derive(Deserialize, Serialize))]
71 #[derive(Clone, Debug, Eq, PartialEq, ToShmem)]
72 pub struct UrlSource {
73     /// The specified url.
74     pub url: SpecifiedUrl,
75     /// The format hints specified with the `format()` function.
76     pub format_hints: Vec<String>,
79 impl ToCss for UrlSource {
80     fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
81     where
82         W: fmt::Write,
83     {
84         self.url.to_css(dest)?;
85         if !self.format_hints.is_empty() {
86             dest.write_str(" format(")?;
87             {
88                 let mut writer = SequenceWriter::new(dest, ", ");
89                 for hint in self.format_hints.iter() {
90                     writer.item(hint)?;
91                 }
92             }
93             dest.write_char(')')?;
94         }
95         Ok(())
96     }
99 /// A font-display value for a @font-face rule.
100 /// The font-display descriptor determines how a font face is displayed based
101 /// on whether and when it is downloaded and ready to use.
102 #[allow(missing_docs)]
103 #[cfg_attr(feature = "servo", derive(Deserialize, Serialize))]
104 #[derive(
105     Clone, Copy, Debug, Eq, MallocSizeOf, Parse, PartialEq, ToComputedValue, ToCss, ToShmem,
107 #[repr(u8)]
108 pub enum FontDisplay {
109     Auto,
110     Block,
111     Swap,
112     Fallback,
113     Optional,
116 macro_rules! impl_range {
117     ($range:ident, $component:ident) => {
118         impl Parse for $range {
119             fn parse<'i, 't>(
120                 context: &ParserContext,
121                 input: &mut Parser<'i, 't>,
122             ) -> Result<Self, ParseError<'i>> {
123                 let first = $component::parse(context, input)?;
124                 let second = input
125                     .try_parse(|input| $component::parse(context, input))
126                     .unwrap_or_else(|_| first.clone());
127                 Ok($range(first, second))
128             }
129         }
130         impl ToCss for $range {
131             fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
132             where
133                 W: fmt::Write,
134             {
135                 self.0.to_css(dest)?;
136                 if self.0 != self.1 {
137                     dest.write_str(" ")?;
138                     self.1.to_css(dest)?;
139                 }
140                 Ok(())
141             }
142         }
143     };
146 /// The font-weight descriptor:
148 /// https://drafts.csswg.org/css-fonts-4/#descdef-font-face-font-weight
149 #[derive(Clone, Debug, PartialEq, ToShmem)]
150 pub struct FontWeightRange(pub AbsoluteFontWeight, pub AbsoluteFontWeight);
151 impl_range!(FontWeightRange, AbsoluteFontWeight);
153 /// The computed representation of the above so Gecko can read them easily.
155 /// This one is needed because cbindgen doesn't know how to generate
156 /// specified::Number.
157 #[repr(C)]
158 #[allow(missing_docs)]
159 pub struct ComputedFontWeightRange(f32, f32);
161 #[inline]
162 fn sort_range<T: PartialOrd>(a: T, b: T) -> (T, T) {
163     if a > b {
164         (b, a)
165     } else {
166         (a, b)
167     }
170 impl FontWeightRange {
171     /// Returns a computed font-stretch range.
172     pub fn compute(&self) -> ComputedFontWeightRange {
173         let (min, max) = sort_range(self.0.compute().0, self.1.compute().0);
174         ComputedFontWeightRange(min, max)
175     }
178 /// The font-stretch descriptor:
180 /// https://drafts.csswg.org/css-fonts-4/#descdef-font-face-font-stretch
181 #[derive(Clone, Debug, PartialEq, ToShmem)]
182 pub struct FontStretchRange(pub FontStretch, pub FontStretch);
183 impl_range!(FontStretchRange, FontStretch);
185 /// The computed representation of the above, so that
186 /// Gecko can read them easily.
187 #[repr(C)]
188 #[allow(missing_docs)]
189 pub struct ComputedFontStretchRange(f32, f32);
191 impl FontStretchRange {
192     /// Returns a computed font-stretch range.
193     pub fn compute(&self) -> ComputedFontStretchRange {
194         fn compute_stretch(s: &FontStretch) -> f32 {
195             match *s {
196                 FontStretch::Keyword(ref kw) => kw.compute().0,
197                 FontStretch::Stretch(ref p) => p.0.get(),
198                 FontStretch::System(..) => unreachable!(),
199             }
200         }
202         let (min, max) = sort_range(compute_stretch(&self.0), compute_stretch(&self.1));
203         ComputedFontStretchRange(min, max)
204     }
207 /// The font-style descriptor:
209 /// https://drafts.csswg.org/css-fonts-4/#descdef-font-face-font-style
210 #[derive(Clone, Debug, PartialEq, ToShmem)]
211 #[allow(missing_docs)]
212 pub enum FontStyle {
213     Normal,
214     Italic,
215     Oblique(Angle, Angle),
218 /// The computed representation of the above, with angles in degrees, so that
219 /// Gecko can read them easily.
220 #[repr(u8)]
221 #[allow(missing_docs)]
222 pub enum ComputedFontStyleDescriptor {
223     Normal,
224     Italic,
225     Oblique(f32, f32),
228 impl Parse for FontStyle {
229     fn parse<'i, 't>(
230         context: &ParserContext,
231         input: &mut Parser<'i, 't>,
232     ) -> Result<Self, ParseError<'i>> {
233         let style = SpecifiedFontStyle::parse(context, input)?;
234         Ok(match style {
235             GenericFontStyle::Normal => FontStyle::Normal,
236             GenericFontStyle::Italic => FontStyle::Italic,
237             GenericFontStyle::Oblique(angle) => {
238                 let second_angle = input
239                     .try_parse(|input| SpecifiedFontStyle::parse_angle(context, input))
240                     .unwrap_or_else(|_| angle.clone());
242                 FontStyle::Oblique(angle, second_angle)
243             },
244         })
245     }
248 impl ToCss for FontStyle {
249     fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
250     where
251         W: fmt::Write,
252     {
253         match *self {
254             FontStyle::Normal => dest.write_str("normal"),
255             FontStyle::Italic => dest.write_str("italic"),
256             FontStyle::Oblique(ref first, ref second) => {
257                 dest.write_str("oblique")?;
258                 if *first != SpecifiedFontStyle::default_angle() || first != second {
259                     dest.write_char(' ')?;
260                     first.to_css(dest)?;
261                 }
262                 if first != second {
263                     dest.write_char(' ')?;
264                     second.to_css(dest)?;
265                 }
266                 Ok(())
267             },
268         }
269     }
272 impl FontStyle {
273     /// Returns a computed font-style descriptor.
274     pub fn compute(&self) -> ComputedFontStyleDescriptor {
275         match *self {
276             FontStyle::Normal => ComputedFontStyleDescriptor::Normal,
277             FontStyle::Italic => ComputedFontStyleDescriptor::Italic,
278             FontStyle::Oblique(ref first, ref second) => {
279                 let (min, max) = sort_range(
280                     SpecifiedFontStyle::compute_angle_degrees(first),
281                     SpecifiedFontStyle::compute_angle_degrees(second),
282                 );
283                 ComputedFontStyleDescriptor::Oblique(min, max)
284             },
285         }
286     }
289 /// Parse the block inside a `@font-face` rule.
291 /// Note that the prelude parsing code lives in the `stylesheets` module.
292 pub fn parse_font_face_block(
293     context: &ParserContext,
294     input: &mut Parser,
295     location: SourceLocation,
296 ) -> FontFaceRuleData {
297     let mut rule = FontFaceRuleData::empty(location);
298     {
299         let parser = FontFaceRuleParser {
300             context: context,
301             rule: &mut rule,
302         };
303         let mut iter = DeclarationListParser::new(input, parser);
304         while let Some(declaration) = iter.next() {
305             if let Err((error, slice)) = declaration {
306                 let location = error.location;
307                 let error = ContextualParseError::UnsupportedFontFaceDescriptor(slice, error);
308                 context.log_css_error(location, error)
309             }
310         }
311     }
312     rule
315 /// A @font-face rule that is known to have font-family and src declarations.
316 #[cfg(feature = "servo")]
317 pub struct FontFace<'a>(&'a FontFaceRuleData);
319 /// A list of effective sources that we send over through IPC to the font cache.
320 #[cfg(feature = "servo")]
321 #[derive(Clone, Debug)]
322 #[cfg_attr(feature = "servo", derive(Deserialize, Serialize))]
323 pub struct EffectiveSources(Vec<Source>);
325 #[cfg(feature = "servo")]
326 impl<'a> FontFace<'a> {
327     /// Returns the list of effective sources for that font-face, that is the
328     /// sources which don't list any format hint, or the ones which list at
329     /// least "truetype" or "opentype".
330     pub fn effective_sources(&self) -> EffectiveSources {
331         EffectiveSources(
332             self.sources()
333                 .iter()
334                 .rev()
335                 .filter(|source| {
336                     if let Source::Url(ref url_source) = **source {
337                         let hints = &url_source.format_hints;
338                         // We support only opentype fonts and truetype is an alias for
339                         // that format. Sources without format hints need to be
340                         // downloaded in case we support them.
341                         hints.is_empty() ||
342                             hints.iter().any(|hint| {
343                                 hint == "truetype" || hint == "opentype" || hint == "woff"
344                             })
345                     } else {
346                         true
347                     }
348                 })
349                 .cloned()
350                 .collect(),
351         )
352     }
355 #[cfg(feature = "servo")]
356 impl Iterator for EffectiveSources {
357     type Item = Source;
358     fn next(&mut self) -> Option<Source> {
359         self.0.pop()
360     }
362     fn size_hint(&self) -> (usize, Option<usize>) {
363         (self.0.len(), Some(self.0.len()))
364     }
367 struct FontFaceRuleParser<'a, 'b: 'a> {
368     context: &'a ParserContext<'b>,
369     rule: &'a mut FontFaceRuleData,
372 /// Default methods reject all at rules.
373 impl<'a, 'b, 'i> AtRuleParser<'i> for FontFaceRuleParser<'a, 'b> {
374     type Prelude = ();
375     type AtRule = ();
376     type Error = StyleParseErrorKind<'i>;
379 impl Parse for Source {
380     fn parse<'i, 't>(
381         context: &ParserContext,
382         input: &mut Parser<'i, 't>,
383     ) -> Result<Source, ParseError<'i>> {
384         if input
385             .try_parse(|input| input.expect_function_matching("local"))
386             .is_ok()
387         {
388             return input
389                 .parse_nested_block(|input| FamilyName::parse(context, input))
390                 .map(Source::Local);
391         }
393         let url = SpecifiedUrl::parse(context, input)?;
395         // Parsing optional format()
396         let format_hints = if input
397             .try_parse(|input| input.expect_function_matching("format"))
398             .is_ok()
399         {
400             input.parse_nested_block(|input| {
401                 input.parse_comma_separated(|input| Ok(input.expect_string()?.as_ref().to_owned()))
402             })?
403         } else {
404             vec![]
405         };
407         Ok(Source::Url(UrlSource {
408             url: url,
409             format_hints: format_hints,
410         }))
411     }
414 macro_rules! is_descriptor_enabled {
415     ("font-display") => {
416         static_prefs::pref!("layout.css.font-display.enabled")
417     };
418     ("font-variation-settings") => {
419         static_prefs::pref!("layout.css.font-variations.enabled")
420     };
421     ("ascent-override") => {
422         static_prefs::pref!("layout.css.font-metrics-overrides.enabled")
423     };
424     ("descent-override") => {
425         static_prefs::pref!("layout.css.font-metrics-overrides.enabled")
426     };
427     ("line-gap-override") => {
428         static_prefs::pref!("layout.css.font-metrics-overrides.enabled")
429     };
430     ("size-adjust") => {
431         static_prefs::pref!("layout.css.size-adjust.enabled")
432     };
433     ($name:tt) => {
434         true
435     };
438 macro_rules! font_face_descriptors_common {
439     (
440         $( #[$doc: meta] $name: tt $ident: ident / $gecko_ident: ident: $ty: ty, )*
441     ) => {
442         /// Data inside a `@font-face` rule.
443         ///
444         /// <https://drafts.csswg.org/css-fonts/#font-face-rule>
445         #[derive(Clone, Debug, PartialEq, ToShmem)]
446         pub struct FontFaceRuleData {
447             $(
448                 #[$doc]
449                 pub $ident: Option<$ty>,
450             )*
451             /// Line and column of the @font-face rule source code.
452             pub source_location: SourceLocation,
453         }
455         impl FontFaceRuleData {
456             /// Create an empty font-face rule
457             pub fn empty(location: SourceLocation) -> Self {
458                 FontFaceRuleData {
459                     $(
460                         $ident: None,
461                     )*
462                     source_location: location,
463                 }
464             }
466             /// Serialization of declarations in the FontFaceRule
467             pub fn decl_to_css(&self, dest: &mut CssStringWriter) -> fmt::Result {
468                 $(
469                     if let Some(ref value) = self.$ident {
470                         dest.write_str(concat!($name, ": "))?;
471                         ToCss::to_css(value, &mut CssWriter::new(dest))?;
472                         dest.write_str("; ")?;
473                     }
474                 )*
475                 Ok(())
476             }
477         }
479        impl<'a, 'b, 'i> DeclarationParser<'i> for FontFaceRuleParser<'a, 'b> {
480            type Declaration = ();
481            type Error = StyleParseErrorKind<'i>;
483            fn parse_value<'t>(
484                &mut self,
485                name: CowRcStr<'i>,
486                input: &mut Parser<'i, 't>,
487             ) -> Result<(), ParseError<'i>> {
488                 match_ignore_ascii_case! { &*name,
489                     $(
490                         $name if is_descriptor_enabled!($name) => {
491                             // DeclarationParser also calls parse_entirely
492                             // so we’d normally not need to,
493                             // but in this case we do because we set the value as a side effect
494                             // rather than returning it.
495                             let value = input.parse_entirely(|i| Parse::parse(self.context, i))?;
496                             self.rule.$ident = Some(value)
497                         },
498                     )*
499                     _ => return Err(input.new_custom_error(SelectorParseErrorKind::UnexpectedIdent(name.clone()))),
500                 }
501                 Ok(())
502             }
503         }
504     }
507 impl ToCssWithGuard for FontFaceRuleData {
508     // Serialization of FontFaceRule is not specced.
509     fn to_css(&self, _guard: &SharedRwLockReadGuard, dest: &mut CssStringWriter) -> fmt::Result {
510         dest.write_str("@font-face { ")?;
511         self.decl_to_css(dest)?;
512         dest.write_str("}")
513     }
516 macro_rules! font_face_descriptors {
517     (
518         mandatory descriptors = [
519             $( #[$m_doc: meta] $m_name: tt $m_ident: ident / $m_gecko_ident: ident: $m_ty: ty, )*
520         ]
521         optional descriptors = [
522             $( #[$o_doc: meta] $o_name: tt $o_ident: ident / $o_gecko_ident: ident: $o_ty: ty, )*
523         ]
524     ) => {
525         font_face_descriptors_common! {
526             $( #[$m_doc] $m_name $m_ident / $m_gecko_ident: $m_ty, )*
527             $( #[$o_doc] $o_name $o_ident / $o_gecko_ident: $o_ty, )*
528         }
530         impl FontFaceRuleData {
531             /// Per https://github.com/w3c/csswg-drafts/issues/1133 an @font-face rule
532             /// is valid as far as the CSS parser is concerned even if it doesn’t have
533             /// a font-family or src declaration.
534             ///
535             /// However both are required for the rule to represent an actual font face.
536             #[cfg(feature = "servo")]
537             pub fn font_face(&self) -> Option<FontFace> {
538                 if $( self.$m_ident.is_some() )&&* {
539                     Some(FontFace(self))
540                 } else {
541                     None
542                 }
543             }
544         }
546         #[cfg(feature = "servo")]
547         impl<'a> FontFace<'a> {
548             $(
549                 #[$m_doc]
550                 pub fn $m_ident(&self) -> &$m_ty {
551                     self.0 .$m_ident.as_ref().unwrap()
552                 }
553             )*
554         }
555     }
558 #[cfg(feature = "gecko")]
559 font_face_descriptors! {
560     mandatory descriptors = [
561         /// The name of this font face
562         "font-family" family / mFamily: FamilyName,
564         /// The alternative sources for this font face.
565         "src" sources / mSrc: Vec<Source>,
566     ]
567     optional descriptors = [
568         /// The style of this font face.
569         "font-style" style / mStyle: FontStyle,
571         /// The weight of this font face.
572         "font-weight" weight / mWeight: FontWeightRange,
574         /// The stretch of this font face.
575         "font-stretch" stretch / mStretch: FontStretchRange,
577         /// The display of this font face.
578         "font-display" display / mDisplay: FontDisplay,
580         /// The ranges of code points outside of which this font face should not be used.
581         "unicode-range" unicode_range / mUnicodeRange: Vec<UnicodeRange>,
583         /// The feature settings of this font face.
584         "font-feature-settings" feature_settings / mFontFeatureSettings: SpecifiedFontFeatureSettings,
586         /// The variation settings of this font face.
587         "font-variation-settings" variation_settings / mFontVariationSettings: SpecifiedFontVariationSettings,
589         /// The language override of this font face.
590         "font-language-override" language_override / mFontLanguageOverride: font_language_override::SpecifiedValue,
592         /// The ascent override for this font face.
593         "ascent-override" ascent_override / mAscentOverride: MetricsOverride,
595         /// The descent override for this font face.
596         "descent-override" descent_override / mDescentOverride: MetricsOverride,
598         /// The line-gap override for this font face.
599         "line-gap-override" line_gap_override / mLineGapOverride: MetricsOverride,
601         /// The size adjustment for this font face.
602         "size-adjust" size_adjust / mSizeAdjust: NonNegativePercentage,
603     ]
606 #[cfg(feature = "servo")]
607 font_face_descriptors! {
608     mandatory descriptors = [
609         /// The name of this font face
610         "font-family" family / mFamily: FamilyName,
612         /// The alternative sources for this font face.
613         "src" sources / mSrc: Vec<Source>,
614     ]
615     optional descriptors = [
616     ]