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