script/dom/css/
fontfaceset.rs1use std::cell::RefCell;
6use std::rc::Rc;
7
8use dom_struct::dom_struct;
9use fonts::FontFaceRuleWithOrigin;
10use js::context::JSContext;
11use js::gc::Handle;
12use js::jsapi::Value;
13use js::realm::CurrentRealm;
14use js::rust::HandleObject;
15use layout_api::{QueryMsg, ReflowGoal};
16use script_bindings::cell::DomRefCell;
17use script_bindings::codegen::GenericBindings::FontFaceBinding::{
18 FontFaceLoadStatus, FontFaceMethods,
19};
20use script_bindings::like::Setlike;
21use script_bindings::reflector::reflect_dom_object_with_proto;
22
23use crate::dom::bindings::codegen::Bindings::FontFaceSetBinding::FontFaceSetMethods;
24use crate::dom::bindings::codegen::Bindings::WindowBinding::WindowMethods;
25use crate::dom::bindings::error::{Error, Fallible};
26use crate::dom::bindings::refcounted::{Trusted, TrustedPromise};
27use crate::dom::bindings::reflector::DomGlobal;
28use crate::dom::bindings::root::{Dom, DomRoot};
29use crate::dom::bindings::str::DOMString;
30use crate::dom::eventtarget::EventTarget;
31use crate::dom::fontface::FontFace;
32use crate::dom::globalscope::GlobalScope;
33use crate::dom::promise::Promise;
34use crate::dom::promisenativehandler::Callback;
35use crate::dom::types::PromiseNativeHandler;
36use crate::dom::window::Window;
37use crate::realms::enter_auto_realm;
38
39#[dom_struct]
41pub(crate) struct FontFaceSet {
42 target: EventTarget,
43
44 #[conditional_malloc_size_of]
46 promise: RefCell<Rc<Promise>>,
47
48 set_entries: DomRefCell<Vec<Dom<FontFace>>>,
49}
50
51impl FontFaceSet {
52 fn new_inherited(promise: Rc<Promise>) -> Self {
53 FontFaceSet {
54 target: EventTarget::new_inherited(),
55 promise: promise.into(),
56 set_entries: Default::default(),
57 }
58 }
59
60 pub(crate) fn new(
61 cx: &mut JSContext,
62 global: &GlobalScope,
63 proto: Option<HandleObject>,
64 ) -> DomRoot<Self> {
65 let promise = Promise::new(cx, global);
66 reflect_dom_object_with_proto(
67 cx,
68 Box::new(FontFaceSet::new_inherited(promise)),
69 global,
70 proto,
71 )
72 }
73
74 pub(super) fn handle_font_face_status_changed(&self, cx: &mut JSContext, font_face: &FontFace) {
75 match font_face.Status() {
76 FontFaceLoadStatus::Loading => {
77 self.switch_to_loading(cx);
78 },
79 FontFaceLoadStatus::Loaded => {
80 let Some(window) = DomRoot::downcast::<Window>(self.global()) else {
81 return;
82 };
83
84 let (family_name, template) = font_face
85 .template()
86 .expect("A loaded web font should have a template");
87 window
88 .font_context()
89 .add_template_to_font_context(family_name, template);
90 window.Document().dirty_all_nodes(cx.no_gc());
91 },
92 _ => {},
93 }
94 }
95
96 pub(crate) fn fulfill_ready_promise_if_needed(&self, cx: &mut JSContext) -> bool {
98 let promise = self.promise.borrow().clone();
99 if promise.is_fulfilled() {
100 return false;
101 }
102 promise.resolve_native(cx, self);
103 true
104 }
105
106 pub(crate) fn waiting_to_fullfill_promise(&self) -> bool {
107 !self.promise.borrow().is_fulfilled()
108 }
109
110 fn contains_face(&self, target: &FontFace) -> bool {
111 self.set_entries
112 .borrow()
113 .iter()
114 .any(|face| &**face == target)
115 }
116
117 fn delete_face(&self, target: &FontFace) -> bool {
119 let mut set_entries = self.set_entries.borrow_mut();
120 let Some(index) = set_entries.iter().position(|face| &**face == target) else {
121 return false;
122 };
123 set_entries.remove(index);
124 true
125 }
126
127 pub(crate) fn switch_to_loading(&self, cx: &mut JSContext) {
129 if self.promise.borrow().is_fulfilled() {
138 *self.promise.borrow_mut() = Promise::new(cx, &self.global());
139 }
140
141 }
144
145 fn flush_author_font_set(&self, cx: &mut JSContext) {
148 if let Some(window) = DomRoot::downcast::<Window>(self.global()) {
153 let document = window.Document();
154 if document.stylesheets_changed_since_last_reflow() {
155 window.reflow(cx, ReflowGoal::LayoutQuery(QueryMsg::BoxArea));
156 }
157 }
158 }
159
160 pub(crate) fn notify_font_face_rules_removed(
164 &self,
165 removed_font_face_rules: &[FontFaceRuleWithOrigin],
166 ) {
167 let entries = self.set_entries.borrow_mut();
168 for removed_font_face_rule in removed_font_face_rules {
169 let Some(matching_font_face_object) = entries
170 .iter()
171 .find(|entry| entry.is_connected_to_font_face_rule(removed_font_face_rule))
172 else {
173 if cfg!(debug_assertions) {
174 unreachable!("Removed @font-face that was not previously present");
175 }
176 log::warn!("Removed @font-face that was not previously present");
177 continue;
178 };
179
180 matching_font_face_object.disconnect_from_css();
184 }
185 }
186}
187
188impl FontFaceSetMethods<crate::DomTypeHolder> for FontFaceSet {
189 fn Ready(&self, cx: &mut JSContext) -> Rc<Promise> {
191 if self.promise.borrow().is_fulfilled() {
192 self.flush_author_font_set(cx);
195 }
196 self.promise.borrow().clone()
197 }
198
199 fn Add(&self, cx: &mut JSContext, font_face: &FontFace) -> Fallible<DomRoot<FontFaceSet>> {
201 if self.contains_face(font_face) {
204 return Ok(DomRoot::from_ref(self));
205 }
206
207 if font_face.is_css_connected() {
210 return Err(Error::InvalidModification(Some(
211 "Cannot add CSS-connected FontFace to FontFaceSet".to_owned(),
212 )));
213 }
214
215 self.set_entries.borrow_mut().push(Dom::from_ref(font_face));
217 font_face.set_associated_font_face_set(self);
218
219 self.handle_font_face_status_changed(cx, font_face);
223
224 Ok(DomRoot::from_ref(self))
226 }
227
228 fn Delete(&self, to_delete: &FontFace) -> bool {
230 if to_delete.is_css_connected() {
232 return false;
233 }
234
235 self.delete_face(to_delete)
241 }
242
243 fn Clear(&self, cx: &mut JSContext) {
245 self.flush_author_font_set(cx);
246
247 self.set_entries.borrow_mut().clear();
250
251 }
254
255 fn Load(&self, cx: &mut JSContext, _font: DOMString, _text: DOMString) -> Rc<Promise> {
257 let load_promise = Promise::new(cx, &self.global());
260
261 #[derive(MallocSizeOf, JSTraceable)]
269 struct LoadPromiseFulfillmentHandler {
270 #[conditional_malloc_size_of]
271 load_promise: Rc<Promise>,
272 }
273 impl Callback for LoadPromiseFulfillmentHandler {
274 fn callback(&self, cx: &mut CurrentRealm, _: Handle<Value>) {
275 self.load_promise
276 .resolve_native(cx, &Vec::<&FontFace>::new());
277 }
278 }
279
280 let trusted_this = Trusted::new(self);
282 let trusted_load_promise = TrustedPromise::new(load_promise.clone());
283 self.global()
284 .task_manager()
285 .font_loading_task_source()
286 .queue(task!(resolve_font_face_set_load_task: move |cx| {
287 let load_promise = trusted_load_promise.root();
288 let this = trusted_this.root();
289
290 let global = this.global();
302 let handler = PromiseNativeHandler::new(
303 cx,
304 &global,
305 Some(Box::new(LoadPromiseFulfillmentHandler {
306 load_promise,
307 })),
308 None,
309 );
310
311 let ready_promise = this.Ready(cx);
312 let mut realm = enter_auto_realm(cx, &*global);
313 ready_promise.append_native_handler(&mut realm.current_realm(), &handler);
314 }));
315
316 load_promise
318 }
319
320 fn Size(&self, cx: &mut JSContext) -> u32 {
322 self.size(cx)
323 }
324}
325
326impl Setlike for FontFaceSet {
327 type Key = DomRoot<FontFace>;
328
329 #[inline(always)]
330 fn get_index(&self, cx: &mut JSContext, index: u32) -> Option<Self::Key> {
331 self.flush_author_font_set(cx);
332 self.set_entries
333 .borrow()
334 .get(index as usize)
335 .map(|face| face.as_rooted())
336 }
337
338 #[inline(always)]
339 fn size(&self, cx: &mut JSContext) -> u32 {
340 self.flush_author_font_set(cx);
341 self.set_entries.borrow().len() as u32
342 }
343
344 #[inline(always)]
345 fn add(&self, _cx: &mut JSContext, face: Self::Key) {
346 self.set_entries.borrow_mut().push(face.as_traced());
347 }
348
349 #[inline(always)]
350 fn has(&self, cx: &mut JSContext, target: Self::Key) -> bool {
351 self.flush_author_font_set(cx);
352 self.contains_face(&target)
353 }
354
355 #[inline(always)]
356 fn clear(&self, cx: &mut JSContext) {
357 self.flush_author_font_set(cx);
358 self.set_entries.borrow_mut().clear();
359 }
360
361 #[inline(always)]
362 fn delete(&self, cx: &mut JSContext, to_delete: Self::Key) -> bool {
363 self.flush_author_font_set(cx);
364 self.delete_face(&to_delete)
365 }
366}