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::new(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(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::new(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        // Step 1: Let font face be a fresh FontFace object. Set font face’s status attribute to
286        // "unloaded", Set its internal [[FontStatusPromise]] slot to a fresh pending Promise
287        // object.
288        //
289        // Parse the family argument, and the members of the descriptors argument, according to
290        // the grammars of the corresponding descriptors of the CSS @font-face rule. If the
291        // source argument is a CSSOMString, parse it according to the grammar of the CSS src
292        // descriptor of the @font-face rule. If any of them fail to parse correctly, reject
293        // font face’s [[FontStatusPromise]] with a DOMException named "SyntaxError", set font
294        // face’s corresponding attributes to the empty string, and set font face’s status
295        // attribute to "error". Otherwise, set font face’s corresponding attributes to the
296        // serialization of the parsed values.
297        //
298        // Return font face. If font face’s status is "error", terminate this algorithm;
299        // otherwise, complete the rest of these steps asynchronously.
300        //
301        // TODO: The rest of the algorithm is run synchronously currently.
302        let font_face = reflect_dom_object_with_proto(
303            Box::new(Self::new_inherited(
304                cx,
305                global,
306                font_family,
307                url_source,
308                descriptors,
309            )),
310            global,
311            proto,
312            CanGc::from_cx(cx),
313        );
314
315        if font_face.Status() == FontFaceLoadStatus::Error {
316            return font_face;
317        }
318
319        // Step 2. If the source argument was a BufferSource, set font face’s internal
320        // [[Data]] slot to the passed argument.
321        // Step 3. If font face’s [[Data]] slot is not null, queue a task to run the following steps
322        // synchronously:
323        let font_face_bytes = match source {
324            StringOrArrayBufferViewOrArrayBuffer::String(_) => {
325                return font_face;
326            },
327            StringOrArrayBufferViewOrArrayBuffer::ArrayBufferView(view) => view.to_vec(),
328            StringOrArrayBufferViewOrArrayBuffer::ArrayBuffer(buffer) => buffer.to_vec(),
329        };
330
331        let trusted_font_face = Trusted::new(&*font_face);
332        let trusted_global = Trusted::new(global);
333        global
334            .task_manager()
335            .font_loading_task_source()
336            .queue(task!(
337                load_font_from_arraybuffer: move |cx| {
338                    let font_face = trusted_font_face.root();
339                    let global = trusted_global.root();
340
341                    font_face.load_from_data(cx, &global, font_face_bytes);
342                }
343            ));
344
345        font_face
346    }
347
348    /// Step 3 of <https://drafts.csswg.org/css-font-loading/#font-face-constructor>
349    fn load_from_data(&self, cx: &mut JSContext, global: &GlobalScope, data: Vec<u8>) {
350        // Step 3.1 Set font face’s status attribute to "loading".
351        self.status.set(FontFaceLoadStatus::Loading);
352
353        // Step 3.2 For each FontFaceSet font face is in:
354        if let Some(font_face_set) = self.font_face_set.get() {
355            font_face_set.handle_font_face_status_changed(cx, self);
356        }
357
358        // Asynchronously, attempt to parse the data in it as a font. When this is completed,
359        // successfully or not, queue a task to run the following steps synchronously:
360        // FIXME: This is not asynchronous.
361        let parsed_font_face_rule = self.font_face_rule(global);
362        let result = parsed_font_face_rule
363            .ok()
364            .and_then(|parsed_font_face_rule| {
365                global
366                    .as_window()
367                    .font_context()
368                    .construct_web_font_from_data(&data, (&parsed_font_face_rule).into())
369            });
370
371        if let Some(template) = result {
372            // Step 1. If the load was successful, font face now represents the parsed font; fulfill font face’s
373            // [[FontStatusPromise]] with font face, and set its status attribute to "loaded".
374            self.font_status_promise.resolve_native(cx, &self);
375            self.status.set(FontFaceLoadStatus::Loaded);
376            *self.template.borrow_mut() = Some(template);
377
378            // For each FontFaceSet font face is in:
379            if let Some(font_face_set) = self.font_face_set.get() {
380                // Add font face to the FontFaceSet’s [[LoadedFonts]] list.
381                // Remove font face from the FontFaceSet’s [[LoadingFonts]] list.
382                // If font was the last item in that list (and so the list is now empty),
383                // switch the FontFaceSet to loaded.
384                font_face_set.handle_font_face_status_changed(cx, self);
385            }
386        } else {
387            // Step 2. Otherwise, reject font face’s [[FontStatusPromise]] with a DOMException named "SyntaxError"
388            // and set font face’s status attribute to "error".
389            self.font_status_promise
390                .reject_error(cx, Error::Syntax(None));
391            self.status.set(FontFaceLoadStatus::Error);
392
393            // For each FontFaceSet font face is in:
394            if let Some(font_face_set) = self.font_face_set.get() {
395                // Add font face to the FontFaceSet’s [[FailedFonts]] list.
396                // Remove font face from the FontFaceSet’s [[LoadingFonts]] list.
397                // If font was the last item in that list (and so the list is now empty),
398                // switch the FontFaceSet to loaded.
399                font_face_set.handle_font_face_status_changed(cx, self);
400            }
401        }
402    }
403
404    pub(super) fn set_associated_font_face_set(&self, font_face_set: &FontFaceSet) {
405        self.font_face_set.set(Some(font_face_set));
406    }
407
408    pub(super) fn template(&self) -> Option<(LowercaseFontFamilyName, FontTemplate)> {
409        self.template.borrow().clone()
410    }
411
412    /// Implements the body of the setter for the descriptor attributes of the [`FontFace`] interface.
413    ///
414    /// <https://drafts.csswg.org/css-font-loading/#fontface-interface>:
415    /// On setting, parse the string according to the grammar for the corresponding @font-face
416    /// descriptor. If it does not match the grammar, throw a SyntaxError; otherwise, set the attribute
417    /// to the serialization of the parsed value.
418    fn validate_and_set_descriptors(&self, new_descriptors: FontFaceDescriptors) -> ErrorResult {
419        let global = self.global();
420        let parsed_font_face_rule = parse_font_face_descriptors(
421            &global,
422            &self.family_name.borrow(),
423            None,
424            &new_descriptors,
425        )?;
426
427        *self.descriptors.borrow_mut() =
428            serialize_parsed_descriptors(&parsed_font_face_rule.descriptors);
429        Ok(())
430    }
431
432    fn font_face_rule(&self, global: &GlobalScope) -> Fallible<FontFaceRule> {
433        // TODO: We should not have to parse the descriptors over and over again here.
434        // We can probably store them on the `FontFace` instead.
435        parse_font_face_descriptors(
436            global,
437            &self.family_name.borrow(),
438            None,
439            &self.descriptors.borrow(),
440        )
441    }
442}
443
444impl FontFaceMethods<crate::DomTypeHolder> for FontFace {
445    /// <https://drafts.csswg.org/css-font-loading/#dom-fontface-family>
446    fn Family(&self) -> DOMString {
447        self.family_name.borrow().clone()
448    }
449
450    /// <https://drafts.csswg.org/css-font-loading/#dom-fontface-family>
451    fn SetFamily(&self, family_name: DOMString) -> ErrorResult {
452        let descriptors = self.descriptors.borrow();
453        let global = self.global();
454        let _ = parse_font_face_descriptors(&global, &family_name, None, &descriptors)?;
455        *self.family_name.borrow_mut() = family_name;
456        Ok(())
457    }
458
459    /// <https://drafts.csswg.org/css-font-loading/#dom-fontface-style>
460    fn Style(&self) -> DOMString {
461        self.descriptors.borrow().style.clone()
462    }
463
464    /// <https://drafts.csswg.org/css-font-loading/#dom-fontface-style>
465    fn SetStyle(&self, value: DOMString) -> ErrorResult {
466        let mut new_descriptors = self.descriptors.borrow().clone();
467        new_descriptors.style = value;
468        self.validate_and_set_descriptors(new_descriptors)
469    }
470
471    /// <https://drafts.csswg.org/css-font-loading/#dom-fontface-weight>
472    fn Weight(&self) -> DOMString {
473        self.descriptors.borrow().weight.clone()
474    }
475
476    /// <https://drafts.csswg.org/css-font-loading/#dom-fontface-weight>
477    fn SetWeight(&self, value: DOMString) -> ErrorResult {
478        let mut new_descriptors = self.descriptors.borrow().clone();
479        new_descriptors.weight = value;
480        self.validate_and_set_descriptors(new_descriptors)
481    }
482
483    /// <https://drafts.csswg.org/css-font-loading/#dom-fontface-stretch>
484    fn Stretch(&self) -> DOMString {
485        self.descriptors.borrow().stretch.clone()
486    }
487
488    /// <https://drafts.csswg.org/css-font-loading/#dom-fontface-stretch>
489    fn SetStretch(&self, value: DOMString) -> ErrorResult {
490        let mut new_descriptors = self.descriptors.borrow().clone();
491        new_descriptors.stretch = value;
492        self.validate_and_set_descriptors(new_descriptors)
493    }
494
495    /// <https://drafts.csswg.org/css-font-loading/#dom-fontface-unicoderange>
496    fn UnicodeRange(&self) -> DOMString {
497        self.descriptors.borrow().unicodeRange.clone()
498    }
499
500    /// <https://drafts.csswg.org/css-font-loading/#dom-fontface-unicoderange>
501    fn SetUnicodeRange(&self, value: DOMString) -> ErrorResult {
502        let mut new_descriptors = self.descriptors.borrow().clone();
503        new_descriptors.unicodeRange = value;
504        self.validate_and_set_descriptors(new_descriptors)
505    }
506
507    /// <https://drafts.csswg.org/css-font-loading/#dom-fontface-featuresettings>
508    fn FeatureSettings(&self) -> DOMString {
509        self.descriptors.borrow().featureSettings.clone()
510    }
511
512    /// <https://drafts.csswg.org/css-font-loading/#dom-fontface-featuresettings>
513    fn SetFeatureSettings(&self, value: DOMString) -> ErrorResult {
514        let mut new_descriptors = self.descriptors.borrow().clone();
515        new_descriptors.featureSettings = value;
516        self.validate_and_set_descriptors(new_descriptors)
517    }
518
519    /// <https://drafts.csswg.org/css-font-loading/#dom-fontface-variationsettings>
520    fn VariationSettings(&self) -> DOMString {
521        self.descriptors.borrow().variationSettings.clone()
522    }
523
524    /// <https://drafts.csswg.org/css-font-loading/#dom-fontface-variationsettings>
525    fn SetVariationSettings(&self, value: DOMString) -> ErrorResult {
526        let mut new_descriptors = self.descriptors.borrow().clone();
527        new_descriptors.variationSettings = value;
528        self.validate_and_set_descriptors(new_descriptors)
529    }
530
531    /// <https://drafts.csswg.org/css-font-loading/#dom-fontface-display>
532    fn Display(&self) -> DOMString {
533        self.descriptors.borrow().display.clone()
534    }
535
536    /// <https://drafts.csswg.org/css-font-loading/#dom-fontface-display>
537    fn SetDisplay(&self, value: DOMString) -> ErrorResult {
538        let mut new_descriptors = self.descriptors.borrow().clone();
539        new_descriptors.display = value;
540        self.validate_and_set_descriptors(new_descriptors)
541    }
542
543    /// <https://drafts.csswg.org/css-font-loading/#dom-fontface-ascentoverride>
544    fn AscentOverride(&self) -> DOMString {
545        self.descriptors.borrow().ascentOverride.clone()
546    }
547
548    /// <https://drafts.csswg.org/css-font-loading/#dom-fontface-ascentoverride>
549    fn SetAscentOverride(&self, value: DOMString) -> ErrorResult {
550        let mut new_descriptors = self.descriptors.borrow().clone();
551        new_descriptors.ascentOverride = value;
552        self.validate_and_set_descriptors(new_descriptors)
553    }
554
555    /// <https://drafts.csswg.org/css-font-loading/#dom-fontface-descentoverride>
556    fn DescentOverride(&self) -> DOMString {
557        self.descriptors.borrow().descentOverride.clone()
558    }
559
560    /// <https://drafts.csswg.org/css-font-loading/#dom-fontface-descentoverride>
561    fn SetDescentOverride(&self, value: DOMString) -> ErrorResult {
562        let mut new_descriptors = self.descriptors.borrow().clone();
563        new_descriptors.descentOverride = value;
564        self.validate_and_set_descriptors(new_descriptors)
565    }
566
567    /// <https://drafts.csswg.org/css-font-loading/#dom-fontface-linegapoverride>
568    fn LineGapOverride(&self) -> DOMString {
569        self.descriptors.borrow().lineGapOverride.clone()
570    }
571
572    /// <https://drafts.csswg.org/css-font-loading/#dom-fontface-linegapoverride>
573    fn SetLineGapOverride(&self, value: DOMString) -> ErrorResult {
574        let mut new_descriptors = self.descriptors.borrow().clone();
575        new_descriptors.lineGapOverride = value;
576        self.validate_and_set_descriptors(new_descriptors)
577    }
578
579    /// <https://drafts.csswg.org/css-font-loading/#dom-fontface-status>
580    fn Status(&self) -> FontFaceLoadStatus {
581        self.status.get()
582    }
583
584    /// The load() method of FontFace forces a url-based font face to request its font data and
585    /// load. For fonts constructed from a buffer source, or fonts that are already loading or
586    /// loaded, it does nothing.
587    /// <https://drafts.csswg.org/css-font-loading/#font-face-load>
588    fn Load(&self, cx: &mut JSContext) -> Rc<Promise> {
589        let Some(sources) = self.urls.borrow_mut().take() else {
590            // Step 2. If font face’s [[Urls]] slot is null, or its status attribute is anything
591            // other than "unloaded", return font face’s [[FontStatusPromise]] and abort these
592            // steps.
593            return self.font_status_promise.clone();
594        };
595
596        // FontFace must not be loaded at this point as `self.urls` is not None, implying `Load`
597        // wasn't called already. In our implementation, `urls` is set after parsing, so it
598        // cannot be `Some` if the status is `Error`.
599        debug_assert_eq!(self.status.get(), FontFaceLoadStatus::Unloaded);
600
601        let global = self.global();
602        let trusted = Trusted::new(self);
603        let task_source = global
604            .task_manager()
605            .font_loading_task_source()
606            .to_sendable();
607
608        let finished_callback = Box::new(
609            move |family_name: LowercaseFontFamilyName, load_result: Option<_>| {
610                let trusted = trusted.clone();
611
612                // Step 5. When the load operation completes, successfully or not, queue a task to
613                // run the following steps synchronously:
614                task_source.queue(task!(resolve_font_face_load_task: move |cx| {
615                    let font_face = trusted.root();
616
617                    match load_result {
618                        None => {
619                            // Step 5.1. If the attempt to load fails, reject font face’s
620                            // [[FontStatusPromise]] with a DOMException whose name is "NetworkError"
621                            // and set font face’s status attribute to "error".
622                            font_face.status.set(FontFaceLoadStatus::Error);
623                            font_face.font_status_promise.reject_error(cx, Error::Network(None));
624                        }
625                        Some(template) => {
626                            // Step 5.2. Otherwise, font face now represents the loaded font;
627                            // fulfill font face’s [[FontStatusPromise]] with font face and set
628                            // font face’s status attribute to "loaded".
629                            font_face.status.set(FontFaceLoadStatus::Loaded);
630                            let old_template = font_face.template.borrow_mut().replace((family_name, template));
631                            debug_assert!(old_template.is_none(), "FontFace's template must be intialized only once");
632                            font_face.font_status_promise.resolve_native(cx, &font_face);
633                        }
634                    }
635
636                    if let Some(font_face_set) = font_face.font_face_set.get() {
637                        // For each FontFaceSet font face is in: ...
638                        //
639                        // This implements steps 5.1.1, 5.1.2, 5.2.1 and 5.2.2 - these
640                        // take care of changing the status of the `FontFaceSet` in which this
641                        // `FontFace` is a member, for both failed and successful load.
642                        font_face_set.handle_font_face_status_changed(cx, &font_face);
643                    }
644                }));
645            },
646        );
647
648        // We parse the descriptors again because they are stored as `DOMString`s in this `FontFace`
649        // but the `load_web_font_for_script` API needs parsed values.
650        let parsed_font_face_rule = self
651            .font_face_rule(&global)
652            .expect("Parsing shouldn't fail as descriptors are valid by construction");
653
654        // Construct a WebFontDocumentContext object for the current document.
655        let document_context = global.as_window().web_font_context();
656
657        // Step 4. Using the value of font face’s [[Urls]] slot, attempt to load a font as defined
658        // in [CSS-FONTS-3], as if it was the value of a @font-face rule’s src descriptor.
659        // TODO: FontFaceSet is not supported on Workers yet. The `as_window` call below should be
660        // replaced when we do support it.
661        global.as_window().font_context().load_web_font_for_script(
662            global.webview_id(),
663            sources,
664            (&parsed_font_face_rule).into(),
665            finished_callback,
666            &document_context,
667        );
668
669        // Step 3. Set font face’s status attribute to "loading", return font face’s
670        // [[FontStatusPromise]], and continue executing the rest of this algorithm asynchronously.
671        self.status.set(FontFaceLoadStatus::Loading);
672
673        // See <https://github.com/w3c/csswg-drafts/issues/13235>:
674        // All browsers switch the FontFaceSet to loading, but this is currently missing
675        // from the specification.
676        if let Some(font_face_set) = self.font_face_set.get() {
677            font_face_set.handle_font_face_status_changed(cx, self);
678        }
679
680        self.font_status_promise.clone()
681    }
682
683    /// <https://drafts.csswg.org/css-font-loading/#dom-fontface-loaded>
684    fn Loaded(&self) -> Rc<Promise> {
685        self.font_status_promise.clone()
686    }
687
688    /// <https://drafts.csswg.org/css-font-loading/#font-face-constructor>
689    fn Constructor(
690        cx: &mut JSContext,
691        window: &Window,
692        proto: Option<HandleObject>,
693        family: DOMString,
694        source: UnionTypes::StringOrArrayBufferViewOrArrayBuffer,
695        descriptors: &FontFaceDescriptors,
696    ) -> DomRoot<FontFace> {
697        let global = window.as_global_scope();
698        FontFace::new(cx, global, proto, family, source, descriptors)
699    }
700}