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