Skip to main content

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