Skip to main content

script/dom/css/
fontface.rs

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/. */
4
5use std::cell::{Cell, Ref, RefCell};
6
7use cssparser::Parser;
8use dom_struct::dom_struct;
9use fonts::{
10    FontContext, FontContextWebFontMethods, FontFaceRuleInfo, FontTemplate, LowercaseFontFamilyName,
11};
12use js::context::JSContext;
13use js::rust::HandleObject;
14use script_bindings::cell::DomRefCell;
15use script_bindings::domstring::TracedDOMString;
16use script_bindings::reflector::{Reflector, reflect_dom_object_with_proto};
17use servo_arc::Arc as ServoArc;
18use style::error_reporting::ParseErrorReporter;
19use style::font_face::SourceList;
20use style::properties::font_face::Descriptors;
21use style::stylesheets::{CssRuleType, FontFaceRule, UrlExtraData};
22use style_traits::{ParsingMode, ToCss};
23
24use crate::css::css::parser_context_for_document_with_reporter;
25use crate::dom::bindings::buffer_source::get_buffer_source_copy;
26use crate::dom::bindings::codegen::Bindings::FontFaceBinding::{
27    FontFaceDescriptors as FontFaceInputDescriptors, FontFaceLoadStatus, FontFaceMethods,
28};
29use crate::dom::bindings::codegen::Bindings::WindowBinding::WindowMethods;
30use crate::dom::bindings::codegen::UnionTypes;
31use crate::dom::bindings::codegen::UnionTypes::StringOrArrayBufferViewOrArrayBuffer;
32use crate::dom::bindings::error::{Error, ErrorResult, Fallible};
33use crate::dom::bindings::refcounted::Trusted;
34use crate::dom::bindings::reflector::DomGlobal;
35use crate::dom::bindings::root::{DomRoot, MutNullableDom};
36use crate::dom::bindings::str::DOMString;
37use crate::dom::css::fontfaceset::FontFaceSet;
38use crate::dom::globalscope::GlobalScope;
39use crate::dom::node::NodeTraits;
40use crate::dom::promise::{Promise, RootedPromise, TracedPromise};
41use crate::dom::window::Window;
42
43/// <https://drafts.csswg.org/css-font-loading/#fontface-interface>
44#[dom_struct]
45pub struct FontFace {
46    reflector: Reflector,
47    status: Cell<FontFaceLoadStatus>,
48    family_name: DomRefCell<TracedDOMString>,
49
50    #[no_trace = "Does not contain managed objects"]
51    descriptors: DomRefCell<FontFaceDescriptors>,
52
53    /// A reference to the [`FontFaceSet`] that this `FontFace` is a member of, if it has been
54    /// added to one. `None` otherwise. The spec suggests that a `FontFace` can be a member of
55    /// multiple `FontFaceSet`s, but this doesn't seem to be the case in practice, as the
56    /// `FontFaceSet` constructor is not exposed on the global scope.
57    font_face_set: MutNullableDom<FontFaceSet>,
58
59    /// This holds the [`FontTemplate`] resulting from loading this `FontFace`, to be used when the
60    /// `FontFace` is added to the global `FontFaceSet` and thus the `[FontContext]`.
61    //
62    // TODO: This could potentially share the `FontTemplateRef` created by `FontContext`, rather
63    // than having its own copy of the template.
64    #[no_trace = "Does not contain managed objects"]
65    template: RefCell<Option<(LowercaseFontFamilyName, FontTemplate)>>,
66
67    #[no_trace = "Does not contain managed objects"]
68    /// <https://drafts.csswg.org/css-font-loading/#m-fontface-urls-slot>
69    urls: DomRefCell<Option<SourceList>>,
70
71    /// <https://drafts.csswg.org/css-font-loading/#dom-fontface-fontstatuspromise-slot>
72    font_status_promise: TracedPromise,
73
74    /// The `@font-face` rule that this `FontFace` object is [css-connected] to, if any.
75    ///
76    /// [css-connected]: https://drafts.csswg.org/css-font-loading/#css-connected
77    #[no_trace]
78    #[conditional_malloc_size_of]
79    css_font_face_rule: DomRefCell<Option<ServoArc<FontFaceRuleInfo>>>,
80}
81
82/// Given the various font face descriptors, construct the equivalent `@font-face` css rule as a
83/// string and parse it using `style` crate. Returns `Err(Error::Syntax)` if parsing fails.
84///
85/// Due to lack of support in the `style` crate, parsing the whole `@font-face` rule is much easier
86/// to implement than parsing each declaration on its own.
87fn parse_font_face_descriptors(
88    global: &GlobalScope,
89    family_name: &TracedDOMString,
90    sources: Option<&DOMString>,
91    input_descriptors: &FontFaceDescriptors,
92) -> Fallible<FontFaceRule> {
93    let window = global.as_window(); // TODO: Support calling FontFace APIs from Worker
94    let document = window.Document();
95    let url_data = UrlExtraData(document.owner_global().api_base_url().get_arc());
96    let error_reporter = FontFaceErrorReporter {
97        not_encountered_error: Cell::new(true),
98    };
99    let parser_context = parser_context_for_document_with_reporter(
100        &document,
101        CssRuleType::FontFace,
102        ParsingMode::DEFAULT,
103        &url_data,
104        &error_reporter,
105    );
106
107    let FontFaceDescriptors {
108        ascent_override,
109        descent_override,
110        display,
111        feature_settings,
112        line_gap_override,
113        width,
114        style,
115        unicode_range,
116        variation_settings,
117        weight,
118    } = input_descriptors;
119
120    let maybe_sources = sources.map_or_else(String::new, |sources| format!("src: {sources};"));
121    let font_face_rule = format!(
122        r"
123        ascent-override: {ascent_override};
124        descent-override: {descent_override};
125        font-display: {display};
126        font-family: {family_name};
127        font-feature-settings: {feature_settings};
128        font-style: {style};
129        font-variation-settings: {variation_settings};
130        font-weight: {weight};
131        font-width: {width};
132        line-gap-override: {line_gap_override};
133        unicode-range: {unicode_range};
134        {maybe_sources}
135    "
136    );
137
138    // TODO: Should this be the source location in the script that invoked the font face API?
139    let location = cssparser::SourceLocation { line: 0, column: 0 };
140    let mut parser = Parser::new(&font_face_rule);
141    let mut parsed_font_face_rule =
142        style::font_face::parse_font_face_block(&parser_context, &mut parser, location);
143
144    if let Some(ref mut sources) = parsed_font_face_rule.descriptors.src {
145        let supported_sources: Vec<_> = sources
146            .0
147            .iter()
148            .rev()
149            .filter(FontContext::is_supported_web_font_source)
150            .cloned()
151            .collect();
152        if supported_sources.is_empty() {
153            error_reporter.not_encountered_error.set(false);
154        } else {
155            sources.0 = supported_sources;
156        }
157    }
158
159    if error_reporter.not_encountered_error.get() {
160        Ok(parsed_font_face_rule)
161    } else {
162        Err(Error::Syntax(Some(
163            "Failed to parse `@font-face` descriptors".into(),
164        )))
165    }
166}
167
168#[derive(Clone, MallocSizeOf)]
169pub(crate) struct FontFaceDescriptors {
170    ascent_override: DOMString,
171    descent_override: DOMString,
172    display: DOMString,
173    feature_settings: DOMString,
174    line_gap_override: DOMString,
175    style: DOMString,
176    unicode_range: DOMString,
177    variation_settings: DOMString,
178    weight: DOMString,
179    width: DOMString,
180}
181
182impl From<&FontFaceInputDescriptors> for FontFaceDescriptors {
183    fn from(descriptors: &FontFaceInputDescriptors) -> Self {
184        Self {
185            ascent_override: descriptors.ascentOverride.clone(),
186            descent_override: descriptors.descentOverride.clone(),
187            display: descriptors.display.clone(),
188            feature_settings: descriptors.featureSettings.clone(),
189            line_gap_override: descriptors.lineGapOverride.clone(),
190            style: descriptors.style.clone(),
191            unicode_range: descriptors.unicodeRange.clone(),
192            variation_settings: descriptors.variationSettings.clone(),
193            weight: descriptors.weight.clone(),
194            width: descriptors
195                .width
196                .clone()
197                .unwrap_or(descriptors.stretch.clone()),
198        }
199    }
200}
201
202/// Converts the descriptors of a `@font-face` rule (as defined by stylo) to
203/// the the IDL `FontFaceDescriptors` dictionary used by the JS interface.
204fn serialize_parsed_descriptors(descriptors: &Descriptors) -> FontFaceDescriptors {
205    FontFaceDescriptors {
206        ascent_override: descriptors.ascent_override.to_css_string().into(),
207        descent_override: descriptors.descent_override.to_css_string().into(),
208        display: descriptors.font_display.to_css_string().into(),
209        feature_settings: descriptors.font_feature_settings.to_css_string().into(),
210        line_gap_override: descriptors.line_gap_override.to_css_string().into(),
211        style: descriptors.font_style.to_css_string().into(),
212        unicode_range: descriptors.unicode_range.to_css_string().into(),
213        variation_settings: descriptors.font_variation_settings.to_css_string().into(),
214        weight: descriptors.font_weight.to_css_string().into(),
215        width: descriptors.font_width.to_css_string().into(),
216    }
217}
218
219struct FontFaceErrorReporter {
220    not_encountered_error: Cell<bool>,
221}
222
223impl ParseErrorReporter for FontFaceErrorReporter {
224    fn report_error(
225        &self,
226        _url: &UrlExtraData,
227        _location: cssparser::SourceLocation,
228        _error: style::error_reporting::ContextualParseError,
229    ) {
230        self.not_encountered_error.set(false);
231    }
232}
233
234impl FontFace {
235    /// Construct a [`FontFace`] to be used in the case of failure in parsing the
236    /// font face descriptors.
237    fn new_failed_font_face(
238        cx: &mut JSContext,
239        global: &GlobalScope,
240        proto: Option<HandleObject>,
241    ) -> DomRoot<Self> {
242        let font_status_promise = Promise::new(cx, global);
243        // If any of them fail to parse correctly, reject font face’s [[FontStatusPromise]] with a
244        // DOMException named "SyntaxError"
245        font_status_promise
246            .reject_error(cx, Error::Syntax(Some("Failed to parse font face".into())));
247
248        // set font face’s corresponding attributes to the empty string, and set font face’s status
249        // attribute to "error"
250        reflect_dom_object_with_proto(
251            cx,
252            Box::new(Self {
253                reflector: Reflector::new(),
254                font_face_set: MutNullableDom::default(),
255                font_status_promise: font_status_promise.to_traced(),
256                family_name: DomRefCell::default(),
257                urls: Default::default(),
258                descriptors: DomRefCell::new(FontFaceDescriptors {
259                    ascent_override: DOMString::new(),
260                    descent_override: DOMString::new(),
261                    display: DOMString::new(),
262                    feature_settings: DOMString::new(),
263                    line_gap_override: DOMString::new(),
264                    style: DOMString::new(),
265                    unicode_range: DOMString::new(),
266                    variation_settings: DOMString::new(),
267                    weight: DOMString::new(),
268                    width: DOMString::new(),
269                }),
270                status: Cell::new(FontFaceLoadStatus::Error),
271                template: RefCell::default(),
272                css_font_face_rule: Default::default(),
273            }),
274            global,
275            proto,
276        )
277    }
278
279    /// <https://drafts.csswg.org/css-font-loading/#font-face-constructor>
280    fn new_inherited(
281        family_name: DOMString,
282        urls: Option<SourceList>,
283        descriptors: &Descriptors,
284        font_status_promise: &RootedPromise,
285    ) -> Self {
286        Self {
287            reflector: Reflector::new(),
288
289            // Set font face’s status attribute to "unloaded".
290            status: Cell::new(FontFaceLoadStatus::Unloaded),
291
292            // Set font face’s corresponding attributes to the serialization of the parsed values.
293            descriptors: DomRefCell::new(serialize_parsed_descriptors(descriptors)),
294
295            font_face_set: MutNullableDom::default(),
296            family_name: DomRefCell::new(family_name.traced()),
297            urls: DomRefCell::new(urls),
298            template: RefCell::default(),
299            font_status_promise: font_status_promise.to_traced(),
300            css_font_face_rule: Default::default(),
301        }
302    }
303
304    /// <https://drafts.csswg.org/css-font-loading/#font-face-constructor>
305    pub(crate) fn new(
306        cx: &mut JSContext,
307        global: &GlobalScope,
308        proto: Option<HandleObject>,
309        font_family: DOMString,
310        urls: Option<SourceList>,
311        descriptors: &Descriptors,
312        font_status_promise: &RootedPromise,
313    ) -> DomRoot<Self> {
314        reflect_dom_object_with_proto(
315            cx,
316            Box::new(Self::new_inherited(
317                font_family,
318                urls,
319                descriptors,
320                font_status_promise,
321            )),
322            global,
323            proto,
324        )
325    }
326
327    /// Constructs a unrooted `FontFace` object for a font that is backed by a `@font-face` rule.
328    pub(crate) fn new_inherited_for_web_font(
329        family_name: DOMString,
330        descriptors: FontFaceDescriptors,
331        src: Option<SourceList>,
332        font_status_promise: &RootedPromise,
333        font_face_rule: ServoArc<FontFaceRuleInfo>,
334    ) -> Self {
335        Self {
336            reflector: Reflector::new(),
337            status: Cell::new(FontFaceLoadStatus::Loading),
338            descriptors: DomRefCell::new(descriptors),
339            font_face_set: MutNullableDom::default(),
340            family_name: DomRefCell::new(family_name.traced()),
341            urls: DomRefCell::new(src),
342            template: RefCell::default(),
343            font_status_promise: font_status_promise.to_traced(),
344            css_font_face_rule: DomRefCell::new(Some(font_face_rule)),
345        }
346    }
347
348    /// Constructs a `FontFace` object for a font that is backed by a `@font-face` rule.
349    pub(crate) fn new_for_web_font(
350        cx: &mut JSContext,
351        global: &GlobalScope,
352        font_face_rule: ServoArc<FontFaceRuleInfo>,
353    ) -> Option<DomRoot<Self>> {
354        let Some(family_name) = font_face_rule
355            .descriptors
356            .font_family
357            .as_ref()
358            .map(|name| TracedDOMString::from(name.name.clone()).root())
359        else {
360            // Web fonts without a family name are not loaded, and they should not appear in document.fonts either.
361            return None;
362        };
363
364        // https://drafts.csswg.org/css-font-loading/#font-face-css-connection
365        // > The FontFace object corresponding to a @font-face rule has its family, style, weight, stretch,
366        // > unicodeRange, variant, and featureSettings attributes set to the same value as the corresponding
367        // > descriptors in the @font-face rule.
368        let descriptors = serialize_parsed_descriptors(&font_face_rule.descriptors);
369
370        let font_status_promise = Promise::new(cx, global);
371        Some(reflect_dom_object_with_proto(
372            cx,
373            Box::new(Self::new_inherited_for_web_font(
374                family_name,
375                descriptors,
376                font_face_rule.descriptors.src.clone(),
377                &font_status_promise,
378                font_face_rule,
379            )),
380            global,
381            None,
382        ))
383    }
384
385    /// Mark this font face as *not* [css-connected].
386    ///
387    /// [css-connected]: https://drafts.csswg.org/css-font-loading/#css-connected
388    pub(crate) fn disconnect_from_css(&self) {
389        *self.css_font_face_rule.borrow_mut() = None;
390    }
391
392    /// <https://drafts.csswg.org/css-font-loading/#css-connected>
393    pub(crate) fn is_css_connected(&self) -> bool {
394        self.css_font_face_rule().is_some()
395    }
396
397    pub(crate) fn css_font_face_rule(&self) -> Ref<'_, Option<ServoArc<FontFaceRuleInfo>>> {
398        self.css_font_face_rule.borrow()
399    }
400
401    /// Return true if the `FontFace` is [css-connected] *and* was created by the provided
402    /// `@font-face` rule.
403    ///
404    /// [css-connected]: https://drafts.csswg.org/css-font-loading/#css-connected
405    pub(crate) fn is_connected_to_font_face_rule(
406        &self,
407        target_rule: &ServoArc<FontFaceRuleInfo>,
408    ) -> bool {
409        self.css_font_face_rule()
410            .as_ref()
411            .is_some_and(|connected_rule| ServoArc::ptr_eq(connected_rule, target_rule))
412    }
413
414    /// Step 3 of <https://drafts.csswg.org/css-font-loading/#font-face-constructor>
415    fn load_from_data(&self, cx: &mut JSContext, global: &GlobalScope, data: Vec<u8>) {
416        // Step 3.1 Set font face’s status attribute to "loading".
417        self.status.set(FontFaceLoadStatus::Loading);
418
419        // Step 3.2 For each FontFaceSet font face is in:
420        if let Some(font_face_set) = self.font_face_set.get() {
421            font_face_set.handle_font_face_status_changed(cx, self);
422        }
423
424        // Asynchronously, attempt to parse the data in it as a font. When this is completed,
425        // successfully or not, queue a task to run the following steps synchronously:
426        // FIXME: This is not asynchronous.
427        let parsed_font_face_rule = self.font_face_rule(global);
428        let result = parsed_font_face_rule
429            .ok()
430            .and_then(|parsed_font_face_rule| {
431                global
432                    .as_window()
433                    .font_context()
434                    .construct_web_font_from_data(&data, (&parsed_font_face_rule).into())
435            });
436
437        if let Some(template) = result {
438            // Step 1. If the load was successful, font face now represents the parsed font; fulfill font face’s
439            // [[FontStatusPromise]] with font face, and set its status attribute to "loaded".
440            self.font_status_promise.resolve_native(cx, &self);
441            self.status.set(FontFaceLoadStatus::Loaded);
442            *self.template.borrow_mut() = Some(template);
443
444            // For each FontFaceSet font face is in:
445            if let Some(font_face_set) = self.font_face_set.get() {
446                // Add font face to the FontFaceSet’s [[LoadedFonts]] list.
447                // Remove font face from the FontFaceSet’s [[LoadingFonts]] list.
448                // If font was the last item in that list (and so the list is now empty),
449                // switch the FontFaceSet to loaded.
450                font_face_set.handle_font_face_status_changed(cx, self);
451            }
452        } else {
453            // Step 2. Otherwise, reject font face’s [[FontStatusPromise]] with a DOMException named "SyntaxError"
454            // and set font face’s status attribute to "error".
455            self.font_status_promise
456                .reject_error(cx, Error::Syntax(Some("Failed to parse font data".into())));
457            self.status.set(FontFaceLoadStatus::Error);
458
459            // For each FontFaceSet font face is in:
460            if let Some(font_face_set) = self.font_face_set.get() {
461                // Add font face to the FontFaceSet’s [[FailedFonts]] list.
462                // Remove font face from the FontFaceSet’s [[LoadingFonts]] list.
463                // If font was the last item in that list (and so the list is now empty),
464                // switch the FontFaceSet to loaded.
465                font_face_set.handle_font_face_status_changed(cx, self);
466            }
467        }
468    }
469
470    pub(super) fn set_associated_font_face_set(&self, font_face_set: &FontFaceSet) {
471        self.font_face_set.set(Some(font_face_set));
472    }
473
474    pub(super) fn template(&self) -> Option<(LowercaseFontFamilyName, FontTemplate)> {
475        self.template.borrow().clone()
476    }
477
478    /// Implements the body of the setter for the descriptor attributes of the [`FontFace`] interface.
479    ///
480    /// <https://drafts.csswg.org/css-font-loading/#fontface-interface>:
481    /// On setting, parse the string according to the grammar for the corresponding @font-face
482    /// descriptor. If it does not match the grammar, throw a SyntaxError; otherwise, set the attribute
483    /// to the serialization of the parsed value.
484    fn validate_and_set_descriptors(&self, new_descriptors: FontFaceDescriptors) -> ErrorResult {
485        let global = self.global();
486        let parsed_font_face_rule = parse_font_face_descriptors(
487            &global,
488            &self.family_name.borrow(),
489            None,
490            &new_descriptors,
491        )?;
492
493        *self.descriptors.borrow_mut() =
494            serialize_parsed_descriptors(&parsed_font_face_rule.descriptors);
495        Ok(())
496    }
497
498    fn font_face_rule(&self, global: &GlobalScope) -> Fallible<FontFaceRule> {
499        // TODO: We should not have to parse the descriptors over and over again here.
500        // We can probably store them on the `FontFace` instead.
501        parse_font_face_descriptors(
502            global,
503            &self.family_name.borrow(),
504            None,
505            &self.descriptors.borrow(),
506        )
507    }
508}
509
510impl FontFaceMethods<crate::DomTypeHolder> for FontFace {
511    /// <https://drafts.csswg.org/css-font-loading/#dom-fontface-family>
512    fn Family(&self) -> DOMString {
513        self.family_name.borrow().clone().root()
514    }
515
516    /// <https://drafts.csswg.org/css-font-loading/#dom-fontface-family>
517    fn SetFamily(&self, family_name: DOMString) -> ErrorResult {
518        let descriptors = self.descriptors.borrow();
519        let global = self.global();
520        let _ = parse_font_face_descriptors(&global, &family_name, None, &descriptors)?;
521        *self.family_name.borrow_mut() = family_name.traced();
522        Ok(())
523    }
524
525    /// <https://drafts.csswg.org/css-font-loading/#dom-fontface-style>
526    fn Style(&self) -> DOMString {
527        self.descriptors.borrow().style.clone()
528    }
529
530    /// <https://drafts.csswg.org/css-font-loading/#dom-fontface-style>
531    fn SetStyle(&self, value: DOMString) -> ErrorResult {
532        let mut new_descriptors = self.descriptors.borrow().clone();
533        new_descriptors.style = value;
534        self.validate_and_set_descriptors(new_descriptors)
535    }
536
537    /// <https://drafts.csswg.org/css-font-loading/#dom-fontface-weight>
538    fn Weight(&self) -> DOMString {
539        self.descriptors.borrow().weight.clone()
540    }
541
542    /// <https://drafts.csswg.org/css-font-loading/#dom-fontface-weight>
543    fn SetWeight(&self, value: DOMString) -> ErrorResult {
544        let mut new_descriptors = self.descriptors.borrow().clone();
545        new_descriptors.weight = value;
546        self.validate_and_set_descriptors(new_descriptors)
547    }
548
549    /// <https://drafts.csswg.org/css-font-loading/#dom-fontface-width>
550    fn Width(&self) -> DOMString {
551        self.descriptors.borrow().width.clone()
552    }
553
554    /// <https://drafts.csswg.org/css-font-loading/#dom-fontface-width>
555    fn SetWidth(&self, value: DOMString) -> ErrorResult {
556        let mut new_descriptors = self.descriptors.borrow().clone();
557        new_descriptors.width = value;
558        self.validate_and_set_descriptors(new_descriptors)
559    }
560
561    /// <https://drafts.csswg.org/css-font-loading/#dom-fontface-unicoderange>
562    fn UnicodeRange(&self) -> DOMString {
563        self.descriptors.borrow().unicode_range.clone()
564    }
565
566    /// <https://drafts.csswg.org/css-font-loading/#dom-fontface-unicoderange>
567    fn SetUnicodeRange(&self, value: DOMString) -> ErrorResult {
568        let mut new_descriptors = self.descriptors.borrow().clone();
569        new_descriptors.unicode_range = value;
570        self.validate_and_set_descriptors(new_descriptors)
571    }
572
573    /// <https://drafts.csswg.org/css-font-loading/#dom-fontface-featuresettings>
574    fn FeatureSettings(&self) -> DOMString {
575        self.descriptors.borrow().feature_settings.clone()
576    }
577
578    /// <https://drafts.csswg.org/css-font-loading/#dom-fontface-featuresettings>
579    fn SetFeatureSettings(&self, value: DOMString) -> ErrorResult {
580        let mut new_descriptors = self.descriptors.borrow().clone();
581        new_descriptors.feature_settings = value;
582        self.validate_and_set_descriptors(new_descriptors)
583    }
584
585    /// <https://drafts.csswg.org/css-font-loading/#dom-fontface-variationsettings>
586    fn VariationSettings(&self) -> DOMString {
587        self.descriptors.borrow().variation_settings.clone()
588    }
589
590    /// <https://drafts.csswg.org/css-font-loading/#dom-fontface-variationsettings>
591    fn SetVariationSettings(&self, value: DOMString) -> ErrorResult {
592        let mut new_descriptors = self.descriptors.borrow().clone();
593        new_descriptors.variation_settings = value;
594        self.validate_and_set_descriptors(new_descriptors)
595    }
596
597    /// <https://drafts.csswg.org/css-font-loading/#dom-fontface-display>
598    fn Display(&self) -> DOMString {
599        self.descriptors.borrow().display.clone()
600    }
601
602    /// <https://drafts.csswg.org/css-font-loading/#dom-fontface-display>
603    fn SetDisplay(&self, value: DOMString) -> ErrorResult {
604        let mut new_descriptors = self.descriptors.borrow().clone();
605        new_descriptors.display = value;
606        self.validate_and_set_descriptors(new_descriptors)
607    }
608
609    /// <https://drafts.csswg.org/css-font-loading/#dom-fontface-ascentoverride>
610    fn AscentOverride(&self) -> DOMString {
611        self.descriptors.borrow().ascent_override.clone()
612    }
613
614    /// <https://drafts.csswg.org/css-font-loading/#dom-fontface-ascentoverride>
615    fn SetAscentOverride(&self, value: DOMString) -> ErrorResult {
616        let mut new_descriptors = self.descriptors.borrow().clone();
617        new_descriptors.ascent_override = value;
618        self.validate_and_set_descriptors(new_descriptors)
619    }
620
621    /// <https://drafts.csswg.org/css-font-loading/#dom-fontface-descentoverride>
622    fn DescentOverride(&self) -> DOMString {
623        self.descriptors.borrow().descent_override.clone()
624    }
625
626    /// <https://drafts.csswg.org/css-font-loading/#dom-fontface-descentoverride>
627    fn SetDescentOverride(&self, value: DOMString) -> ErrorResult {
628        let mut new_descriptors = self.descriptors.borrow().clone();
629        new_descriptors.descent_override = value;
630        self.validate_and_set_descriptors(new_descriptors)
631    }
632
633    /// <https://drafts.csswg.org/css-font-loading/#dom-fontface-linegapoverride>
634    fn LineGapOverride(&self) -> DOMString {
635        self.descriptors.borrow().line_gap_override.clone()
636    }
637
638    /// <https://drafts.csswg.org/css-font-loading/#dom-fontface-linegapoverride>
639    fn SetLineGapOverride(&self, value: DOMString) -> ErrorResult {
640        let mut new_descriptors = self.descriptors.borrow().clone();
641        new_descriptors.line_gap_override = value;
642        self.validate_and_set_descriptors(new_descriptors)
643    }
644
645    /// <https://drafts.csswg.org/css-font-loading/#dom-fontface-status>
646    fn Status(&self) -> FontFaceLoadStatus {
647        self.status.get()
648    }
649
650    /// The load() method of FontFace forces a url-based font face to request its font data and
651    /// load. For fonts constructed from a buffer source, or fonts that are already loading or
652    /// loaded, it does nothing.
653    /// <https://drafts.csswg.org/css-font-loading/#font-face-load>
654    fn Load(&self, cx: &mut JSContext) -> RootedPromise {
655        // Step 2. If font face’s [[Urls]] slot is null, or its status attribute is anything
656        // other than "unloaded", return font face’s [[FontStatusPromise]] and abort these
657        // steps.
658        let Some(sources) = self.urls.safe_borrow_mut(cx.no_gc()).take() else {
659            return self.font_status_promise.root(cx);
660        };
661        if self.status.get() != FontFaceLoadStatus::Unloaded {
662            return self.font_status_promise.root(cx);
663        }
664
665        let global = self.global();
666        let trusted = Trusted::new(self);
667        let task_source = global
668            .task_manager()
669            .font_loading_task_source()
670            .to_sendable();
671
672        let finished_callback = Box::new(
673            move |family_name: LowercaseFontFamilyName, load_result: Option<_>| {
674                let trusted = trusted.clone();
675
676                // Step 5. When the load operation completes, successfully or not, queue a task to
677                // run the following steps synchronously:
678                task_source.queue(task!(resolve_font_face_load_task: move |cx| {
679                    let font_face = trusted.root();
680
681                    match load_result {
682                        None => {
683                            // Step 5.1. If the attempt to load fails, reject font face’s
684                            // [[FontStatusPromise]] with a DOMException whose name is "NetworkError"
685                            // and set font face’s status attribute to "error".
686                            font_face.status.set(FontFaceLoadStatus::Error);
687                            font_face.font_status_promise.reject_error(cx, Error::Network(Some("Failed to load font data".into())));
688                        }
689                        Some(template) => {
690                            // Step 5.2. Otherwise, font face now represents the loaded font;
691                            // fulfill font face’s [[FontStatusPromise]] with font face and set
692                            // font face’s status attribute to "loaded".
693                            font_face.status.set(FontFaceLoadStatus::Loaded);
694                            let old_template = font_face.template.borrow_mut().replace((family_name, template));
695                            debug_assert!(old_template.is_none(), "FontFace's template must be intialized only once");
696                            font_face.font_status_promise.resolve_native(cx, &font_face);
697                        }
698                    }
699
700                    if let Some(font_face_set) = font_face.font_face_set.get() {
701                        // For each FontFaceSet font face is in: ...
702                        //
703                        // This implements steps 5.1.1, 5.1.2, 5.2.1 and 5.2.2 - these
704                        // take care of changing the status of the `FontFaceSet` in which this
705                        // `FontFace` is a member, for both failed and successful load.
706                        font_face_set.handle_font_face_status_changed(cx, &font_face);
707                    }
708                }));
709            },
710        );
711
712        // We parse the descriptors again because they are stored as `DOMString`s in this `FontFace`
713        // but the `load_web_font_for_script` API needs parsed values.
714        let parsed_font_face_rule = self
715            .font_face_rule(&global)
716            .expect("Parsing shouldn't fail as descriptors are valid by construction");
717
718        // Construct a WebFontDocumentContext object for the current document.
719        let document_context = global.as_window().web_font_context(cx.no_gc());
720
721        // Step 4. Using the value of font face’s [[Urls]] slot, attempt to load a font as defined
722        // in [CSS-FONTS-3], as if it was the value of a @font-face rule’s src descriptor.
723        // TODO: FontFaceSet is not supported on Workers yet. The `as_window` call below should be
724        // replaced when we do support it.
725        global.as_window().font_context().load_web_font_for_script(
726            global.webview_id(),
727            sources,
728            (&parsed_font_face_rule).into(),
729            finished_callback,
730            &document_context,
731        );
732
733        // Step 3. Set font face’s status attribute to "loading", return font face’s
734        // [[FontStatusPromise]], and continue executing the rest of this algorithm asynchronously.
735        self.status.set(FontFaceLoadStatus::Loading);
736
737        // See <https://github.com/w3c/csswg-drafts/issues/13235>:
738        // All browsers switch the FontFaceSet to loading, but this is currently missing
739        // from the specification.
740        if let Some(font_face_set) = self.font_face_set.get() {
741            font_face_set.handle_font_face_status_changed(cx, self);
742        }
743
744        self.font_status_promise.root(cx)
745    }
746
747    /// <https://drafts.csswg.org/css-font-loading/#dom-fontface-loaded>
748    fn Loaded(&self, cx: &JSContext) -> RootedPromise {
749        self.font_status_promise.root(cx)
750    }
751
752    /// <https://drafts.csswg.org/css-font-loading/#font-face-constructor>
753    fn Constructor(
754        cx: &mut JSContext,
755        window: &Window,
756        proto: Option<HandleObject>,
757        family: DOMString,
758        source: UnionTypes::StringOrArrayBufferViewOrArrayBuffer,
759        descriptors: &FontFaceInputDescriptors,
760    ) -> DomRoot<FontFace> {
761        // Step 2. If the source argument was a CSSOMString, set font face’s internal [[Urls]] slot to the string.
762        let url_source = if let StringOrArrayBufferViewOrArrayBuffer::String(source) = &source {
763            Some(source)
764        } else {
765            None
766        };
767        // All the rest of the comments are part of step 1:
768
769        // Parse the family argument, and the members of the descriptors argument,
770        // according to the grammars of the corresponding descriptors of the CSS @font-face rule If
771        // the source argument is a CSSOMString, parse it according to the grammar of the CSS src
772        // descriptor of the @font-face rule.
773        let global = window.as_global_scope();
774        let parse_result =
775            parse_font_face_descriptors(global, &family, url_source, &descriptors.into());
776
777        let Ok(ref parsed_font_face_rule) = parse_result else {
778            // If any of them fail to parse correctly, reject font face’s
779            // [[FontStatusPromise]] with a DOMException named "SyntaxError", set font face’s
780            // corresponding attributes to the empty string, and set font face’s status attribute
781            // to "error".
782            return Self::new_failed_font_face(cx, global, proto);
783        };
784
785        // Set its internal [[FontStatusPromise]] slot to a fresh pending Promise object.
786        let font_status_promise = Promise::new(cx, global);
787
788        let sources = parsed_font_face_rule.descriptors.src.clone();
789        // Let font face be a fresh FontFace object.
790        let font_face = FontFace::new(
791            cx,
792            global,
793            proto,
794            family,
795            sources,
796            &parsed_font_face_rule.descriptors,
797            &font_status_promise,
798        );
799
800        // If font face’s status is "error", terminate this algorithm;
801        // otherwise, complete the rest of these steps asynchronously.
802        if font_face.Status() == FontFaceLoadStatus::Error {
803            return font_face;
804        }
805
806        // Step 2. If the source argument was a BufferSource, set font face’s internal
807        // [[Data]] slot to the passed argument.
808        // Step 3. If font face’s [[Data]] slot is not null, queue a task to run the following steps
809        // synchronously:
810        let font_face_bytes = match &source {
811            StringOrArrayBufferViewOrArrayBuffer::String(_) => {
812                // Return font face.
813                return font_face;
814            },
815            StringOrArrayBufferViewOrArrayBuffer::ArrayBufferView(view) => {
816                get_buffer_source_copy(view.into())
817            },
818            StringOrArrayBufferViewOrArrayBuffer::ArrayBuffer(buffer) => {
819                get_buffer_source_copy(buffer.into())
820            },
821        };
822        let trusted_font_face = Trusted::new(&*font_face);
823        let trusted_global = Trusted::new(global);
824        global
825            .task_manager()
826            .font_loading_task_source()
827            .queue(task!(
828                load_font_from_arraybuffer: move |cx| {
829                    let font_face = trusted_font_face.root();
830                    let global = trusted_global.root();
831
832                    font_face.load_from_data(cx, &global, font_face_bytes);
833                }
834            ));
835
836        // Return font face.
837        font_face
838    }
839}