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