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