script/dom/css/
fontfaceset.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::rc::Rc;
6
7use dom_struct::dom_struct;
8use fonts::FontContextWebFontMethods;
9use js::rust::HandleObject;
10use script_bindings::like::Setlike;
11
12use crate::dom::bindings::cell::DomRefCell;
13use crate::dom::bindings::codegen::Bindings::FontFaceSetBinding::FontFaceSetMethods;
14use crate::dom::bindings::codegen::Bindings::WindowBinding::WindowMethods;
15use crate::dom::bindings::refcounted::TrustedPromise;
16use crate::dom::bindings::reflector::{DomGlobal, reflect_dom_object_with_proto};
17use crate::dom::bindings::root::{Dom, DomRoot};
18use crate::dom::bindings::str::DOMString;
19use crate::dom::eventtarget::EventTarget;
20use crate::dom::fontface::FontFace;
21use crate::dom::globalscope::GlobalScope;
22use crate::dom::promise::Promise;
23use crate::dom::window::Window;
24use crate::script_runtime::CanGc;
25
26/// <https://drafts.csswg.org/css-font-loading/#FontFaceSet-interface>
27#[dom_struct]
28pub(crate) struct FontFaceSet {
29    target: EventTarget,
30
31    /// <https://drafts.csswg.org/css-font-loading/#dom-fontfaceset-readypromise-slot>
32    #[conditional_malloc_size_of]
33    promise: Rc<Promise>,
34
35    set_entries: DomRefCell<Vec<Dom<FontFace>>>,
36}
37
38impl FontFaceSet {
39    fn new_inherited(global: &GlobalScope, can_gc: CanGc) -> Self {
40        FontFaceSet {
41            target: EventTarget::new_inherited(),
42            promise: Promise::new(global, can_gc),
43            set_entries: Default::default(),
44        }
45    }
46
47    pub(crate) fn new(
48        global: &GlobalScope,
49        proto: Option<HandleObject>,
50        can_gc: CanGc,
51    ) -> DomRoot<Self> {
52        reflect_dom_object_with_proto(
53            Box::new(FontFaceSet::new_inherited(global, can_gc)),
54            global,
55            proto,
56            can_gc,
57        )
58    }
59
60    pub(super) fn handle_font_face_status_changed(&self, font_face: &FontFace) {
61        if font_face.loaded() {
62            let Some(window) = DomRoot::downcast::<Window>(self.global()) else {
63                return;
64            };
65
66            let (family_name, template) = font_face
67                .template()
68                .expect("A loaded web font should have a template");
69            window
70                .font_context()
71                .add_template_to_font_context(family_name, template);
72            window.Document().dirty_all_nodes();
73        }
74    }
75
76    /// Fulfill the font ready promise, returning true if it was not already fulfilled beforehand.
77    pub(crate) fn fulfill_ready_promise_if_needed(&self, can_gc: CanGc) -> bool {
78        if self.promise.is_fulfilled() {
79            return false;
80        }
81        self.promise.resolve_native(self, can_gc);
82        true
83    }
84
85    pub(crate) fn waiting_to_fullfill_promise(&self) -> bool {
86        !self.promise.is_fulfilled()
87    }
88
89    fn contains_face(&self, target: &FontFace) -> bool {
90        self.set_entries
91            .borrow()
92            .iter()
93            .any(|face| &**face == target)
94    }
95
96    /// Removes a face from the set's set entries.
97    fn delete_face(&self, target: &FontFace) -> bool {
98        let mut set_entries = self.set_entries.borrow_mut();
99        let Some(index) = set_entries.iter().position(|face| &**face == target) else {
100            return false;
101        };
102        set_entries.remove(index);
103        true
104    }
105}
106
107impl FontFaceSetMethods<crate::DomTypeHolder> for FontFaceSet {
108    /// <https://drafts.csswg.org/css-font-loading/#dom-fontfaceset-ready>
109    fn Ready(&self) -> Rc<Promise> {
110        self.promise.clone()
111    }
112
113    /// <https://drafts.csswg.org/css-font-loading/#dom-fontfaceset-add>
114    fn Add(&self, font_face: &FontFace) -> DomRoot<FontFaceSet> {
115        // Step 1. If font is already in the FontFaceSet’s set entries,
116        // skip to the last step of this algorithm immediately.
117        if self.contains_face(font_face) {
118            return DomRoot::from_ref(self);
119        }
120
121        // TODO: Step 2. If font is CSS-connected, throw an InvalidModificationError
122        // exception and exit this algorithm immediately.
123
124        // Step 3. Add the font argument to the FontFaceSet’s set entries.
125        self.set_entries.borrow_mut().push(Dom::from_ref(font_face));
126        font_face.set_associated_font_face_set(self);
127
128        // Step 4. If font’s status attribute is "loading":
129        // Step 4.1 If the FontFaceSet’s [[LoadingFonts]] list is empty, switch the FontFaceSet to loading.
130        // Step 4.2 Append font to the FontFaceSet’s [[LoadingFonts]] list.
131        self.handle_font_face_status_changed(font_face);
132
133        // Step 5. Return the FontFaceSet.
134        DomRoot::from_ref(self)
135    }
136
137    /// <https://drafts.csswg.org/css-font-loading/#dom-fontfaceset-delete>
138    fn Delete(&self, to_delete: &FontFace) -> bool {
139        // TODO Step 1. If font is CSS-connected, return false and exit this algorithm immediately.
140
141        // Step 2. Let deleted be the result of removing font from the FontFaceSet’s set entries.
142        // TODO: Step 3. If font is present in the FontFaceSet’s [[LoadedFonts]], or [[FailedFonts]] lists, remove it.
143        // TODO: Step 4. If font is present in the FontFaceSet’s [[LoadingFonts]] list, remove it. If font was the last
144        // item in that list (and so the list is now empty), switch the FontFaceSet to loaded.
145        // Step 5. Return deleted.
146        self.delete_face(to_delete)
147    }
148
149    /// <https://drafts.csswg.org/css-font-loading/#dom-fontfaceset-clear>
150    fn Clear(&self) {
151        // Step 1. Remove all non-CSS-connected items from the FontFaceSet’s set entries,
152        // its [[LoadedFonts]] list, and its [[FailedFonts]] list.
153        self.set_entries.borrow_mut().clear();
154
155        // TODO Step 2. If the FontFaceSet’s [[LoadingFonts]] list is non-empty, remove all items from it,
156        // then switch the FontFaceSet to loaded.
157    }
158
159    /// <https://drafts.csswg.org/css-font-loading/#dom-fontfaceset-load>
160    fn Load(&self, _font: DOMString, _text: DOMString, can_gc: CanGc) -> Rc<Promise> {
161        // Step 1. Let font face set be the FontFaceSet object this method was called on. Let
162        // promise be a newly-created promise object.
163        let promise = Promise::new(&self.global(), can_gc);
164
165        // TODO: Step 3. Find the matching font faces from font face set using the font and text
166        // arguments passed to the function, and let font face list be the return value (ignoring
167        // the found faces flag). If a syntax error was returned, reject promise with a SyntaxError
168        // exception and terminate these steps.
169
170        let trusted = TrustedPromise::new(promise.clone());
171        // Step 4. Queue a task to run the following steps synchronously:
172        self.global()
173            .task_manager()
174            .font_loading_task_source()
175            .queue(task!(resolve_font_face_set_load_task: move || {
176                let promise = trusted.root();
177
178                // TODO: Step 4.1. For all of the font faces in the font face list, call their load()
179                // method.
180
181                // TODO: Step 4.2. Resolve promise with the result of waiting for all of the
182                // [[FontStatusPromise]]s of each font face in the font face list, in order.
183                let matched_fonts = Vec::<&FontFace>::new();
184                promise.resolve_native(&matched_fonts, CanGc::note());
185            }));
186
187        // Step 2. Return promise. Complete the rest of these steps asynchronously.
188        promise
189    }
190
191    /// <https://html.spec.whatwg.org/multipage/#customstateset>
192    fn Size(&self) -> u32 {
193        self.set_entries.borrow().len() as u32
194    }
195}
196
197impl Setlike for FontFaceSet {
198    type Key = DomRoot<FontFace>;
199
200    #[inline(always)]
201    fn get_index(&self, index: u32) -> Option<Self::Key> {
202        self.set_entries
203            .borrow()
204            .get(index as usize)
205            .map(|face| face.as_rooted())
206    }
207
208    #[inline(always)]
209    fn size(&self) -> u32 {
210        self.set_entries.borrow().len() as u32
211    }
212
213    #[inline(always)]
214    fn add(&self, face: Self::Key) {
215        self.set_entries.borrow_mut().push(face.as_traced());
216    }
217
218    #[inline(always)]
219    fn has(&self, target: Self::Key) -> bool {
220        self.contains_face(&target)
221    }
222
223    #[inline(always)]
224    fn clear(&self) {
225        self.set_entries.borrow_mut().clear();
226    }
227
228    #[inline(always)]
229    fn delete(&self, to_delete: Self::Key) -> bool {
230        self.delete_face(&to_delete)
231    }
232}