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