Skip to main content

script/dom/css/
fontface.rs

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