diff options
author | Mukilan Thiyagarajan <mukilan@igalia.com> | 2025-02-19 11:20:01 +0530 |
---|---|---|
committer | GitHub <noreply@github.com> | 2025-02-19 05:50:01 +0000 |
commit | 56840e0a3509f017f745a002332c6125431260a6 (patch) | |
tree | a9401b39fa1717127d5e99e1c6a57ad2f102b737 | |
parent | 29e0fad21ec561b1778e8d973c4e800702f1b38b (diff) | |
download | servo-56840e0a3509f017f745a002332c6125431260a6.tar.gz servo-56840e0a3509f017f745a002332c6125431260a6.zip |
script: add skeleton implementation of `FontFace` API (#35262)
This patch implements the `FontFace` interface, but with some caveats
1. The interface is only exposed on `Window`. Support for Workers will
be handled in the future.
2. The concept of `css-connected` `FontFace` is not implemented, so
`@font-face` rules in stylesheets will not be represented in the DOM.
3. The constructor only supports using `url()` strings as source
and `ArrayBuffer` and `ArrayBufferView` are not supported yet.
A skeleton implementation of the `load` method of `FontFaceSet` is also
implemented in this patch. The intention is to support some web pages
that don't load without this method.
Signed-off-by: Mukilan Thiyagarajan <mukilan@igalia.com>
52 files changed, 1157 insertions, 548 deletions
diff --git a/components/config/prefs.rs b/components/config/prefs.rs index a6dffb72b43..8a3c8595f1d 100644 --- a/components/config/prefs.rs +++ b/components/config/prefs.rs @@ -81,6 +81,7 @@ pub struct Preferences { pub dom_customelements_enabled: bool, pub dom_document_dblclick_timeout: i64, pub dom_document_dblclick_dist: i64, + pub dom_fontface_enabled: bool, pub dom_forcetouch_enabled: bool, pub dom_fullscreen_test: bool, pub dom_gamepad_enabled: bool, @@ -245,6 +246,7 @@ impl Preferences { dom_customelements_enabled: true, dom_document_dblclick_dist: 1, dom_document_dblclick_timeout: 300, + dom_fontface_enabled: false, dom_forcetouch_enabled: false, dom_fullscreen_test: false, dom_gamepad_enabled: true, diff --git a/components/fonts/font_context.rs b/components/fonts/font_context.rs index 212fb587f84..df8c7785658 100644 --- a/components/fonts/font_context.rs +++ b/components/fonts/font_context.rs @@ -11,7 +11,7 @@ use std::sync::Arc; use app_units::Au; use base::id::WebViewId; use fnv::FnvHasher; -use fonts_traits::WebFontLoadFinishedCallback; +use fonts_traits::StylesheetWebFontLoadFinishedCallback; use log::{debug, trace}; use malloc_size_of::{MallocSizeOf, MallocSizeOfOps}; use malloc_size_of_derive::MallocSizeOf; @@ -21,7 +21,9 @@ use parking_lot::{Mutex, RwLock}; use servo_arc::Arc as ServoArc; use servo_url::ServoUrl; use style::computed_values::font_variant_caps::T as FontVariantCaps; -use style::font_face::{FontFaceSourceFormat, FontFaceSourceFormatKeyword, Source, UrlSource}; +use style::font_face::{ + FontFaceSourceFormat, FontFaceSourceFormatKeyword, Source, SourceList, UrlSource, +}; use style::media_queries::Device; use style::properties::style_structs::Font as FontStyleStruct; use style::shared_lock::SharedRwLockReadGuard; @@ -133,18 +135,6 @@ impl FontContext { } } - /// Handle the situation where a web font finishes loading, specifying if the load suceeded or failed. - fn handle_web_font_load_finished( - &self, - finished_callback: &WebFontLoadFinishedCallback, - succeeded: bool, - ) { - if succeeded { - self.invalidate_font_groups_after_web_font_load(); - } - finished_callback(succeeded); - } - /// Returns a `FontGroup` representing fonts which can be used for layout, given the `style`. /// Font groups are cached, so subsequent calls with the same `style` will return a reference /// to an existing `FontGroup`. @@ -355,17 +345,138 @@ impl FontContext { fn invalidate_font_groups_after_web_font_load(&self) { self.resolved_font_groups.write().clear(); } + + pub fn is_supported_web_font_source(source: &&Source) -> bool { + let url_source = match source { + Source::Url(ref url_source) => url_source, + Source::Local(_) => return true, + }; + let format_hint = match url_source.format_hint { + Some(ref format_hint) => format_hint, + None => return true, + }; + + if matches!( + format_hint, + FontFaceSourceFormat::Keyword( + FontFaceSourceFormatKeyword::Truetype | + FontFaceSourceFormatKeyword::Opentype | + FontFaceSourceFormatKeyword::Woff | + FontFaceSourceFormatKeyword::Woff2 + ) + ) { + return true; + } + + if let FontFaceSourceFormat::String(string) = format_hint { + return string == "truetype" || + string == "opentype" || + string == "woff" || + string == "woff2"; + } + + false + } } -#[derive(Clone)] pub(crate) struct WebFontDownloadState { - webview_id: WebViewId, - pub(crate) css_font_face_descriptors: Arc<CSSFontFaceDescriptors>, + webview_id: Option<WebViewId>, + css_font_face_descriptors: CSSFontFaceDescriptors, remaining_sources: Vec<Source>, - finished_callback: WebFontLoadFinishedCallback, core_resource_thread: CoreResourceThread, - local_fonts: Arc<HashMap<Atom, Option<FontTemplateRef>>>, - pub(crate) stylesheet: DocumentStyleSheet, + local_fonts: HashMap<Atom, Option<FontTemplateRef>>, + font_context: Arc<FontContext>, + initiator: WebFontLoadInitiator, +} + +impl WebFontDownloadState { + fn new( + webview_id: Option<WebViewId>, + font_context: Arc<FontContext>, + css_font_face_descriptors: CSSFontFaceDescriptors, + initiator: WebFontLoadInitiator, + sources: Vec<Source>, + local_fonts: HashMap<Atom, Option<FontTemplateRef>>, + ) -> WebFontDownloadState { + match initiator { + WebFontLoadInitiator::Stylesheet(ref stylesheet, _) => { + font_context + .web_fonts + .write() + .handle_web_font_load_started_for_stylesheet(stylesheet); + }, + WebFontLoadInitiator::Script(_) => { + font_context + .web_fonts + .write() + .handle_web_font_load_started_for_script(); + }, + }; + let core_resource_thread = font_context.resource_threads.lock().clone(); + WebFontDownloadState { + webview_id, + css_font_face_descriptors, + remaining_sources: sources, + core_resource_thread, + local_fonts, + font_context, + initiator, + } + } + + fn handle_web_font_load_success(self, new_template: FontTemplate) { + let family_name = self.css_font_face_descriptors.family_name.clone(); + match self.initiator { + WebFontLoadInitiator::Stylesheet(ref stylesheet, ref callback) => { + let not_cancelled = self + .font_context + .web_fonts + .write() + .handle_web_font_loaded_for_stylesheet(stylesheet, family_name, new_template); + self.font_context + .invalidate_font_groups_after_web_font_load(); + callback(not_cancelled); + }, + WebFontLoadInitiator::Script(callback) => { + self.font_context + .web_fonts + .write() + .handle_web_font_load_finished_for_script(); + callback(family_name, Some(new_template)); + }, + } + } + + fn handle_web_font_load_failure(self) { + let family_name = self.css_font_face_descriptors.family_name.clone(); + match self.initiator { + WebFontLoadInitiator::Stylesheet(ref stylesheet, ref callback) => { + self.font_context + .web_fonts + .write() + .handle_web_font_load_failed_for_stylesheet(stylesheet); + callback(false); + }, + WebFontLoadInitiator::Script(callback) => { + self.font_context + .web_fonts + .write() + .handle_web_font_load_finished_for_script(); + callback(family_name, None); + }, + } + } + + fn font_load_cancelled(&self) -> bool { + match self.initiator { + WebFontLoadInitiator::Stylesheet(ref stylesheet, _) => self + .font_context + .web_fonts + .read() + .font_load_cancelled_for_stylesheet(stylesheet), + WebFontLoadInitiator::Script(_) => false, + } + } } pub trait FontContextWebFontMethods { @@ -375,8 +486,20 @@ pub trait FontContextWebFontMethods { stylesheet: &DocumentStyleSheet, guard: &SharedRwLockReadGuard, device: &Device, - finished_callback: WebFontLoadFinishedCallback, + finished_callback: StylesheetWebFontLoadFinishedCallback, ) -> usize; + fn load_web_font_for_script( + &self, + webview_id: Option<WebViewId>, + source_list: SourceList, + descriptors: CSSFontFaceDescriptors, + finished_callback: ScriptWebFontLoadFinishedCallback, + ); + fn add_template_to_font_context( + &self, + family_name: LowercaseFontFamilyName, + font_template: FontTemplate, + ); fn remove_all_web_fonts_from_stylesheet(&self, stylesheet: &DocumentStyleSheet); fn collect_unused_webrender_resources(&self, all: bool) -> (Vec<FontKey>, Vec<FontInstanceKey>); @@ -389,7 +512,7 @@ impl FontContextWebFontMethods for Arc<FontContext> { stylesheet: &DocumentStyleSheet, guard: &SharedRwLockReadGuard, device: &Device, - finished_callback: WebFontLoadFinishedCallback, + finished_callback: StylesheetWebFontLoadFinishedCallback, ) -> usize { let mut number_loading = 0; for rule in stylesheet.effective_rules(device, guard) { @@ -402,57 +525,17 @@ impl FontContextWebFontMethods for Arc<FontContext> { continue; }; - let sources: Vec<Source> = font_face - .sources() - .0 - .iter() - .rev() - .filter(is_supported_web_font_source) - .cloned() - .collect(); - if sources.is_empty() { - continue; - } - - // Fetch all local fonts first, beacause if we try to fetch them later on during the process of - // loading the list of web font `src`s we may be running in the context of the router thread, which - // means we won't be able to seend IPC messages to the FontCacheThread. - // - // TODO: This is completely wrong. The specification says that `local()` font-family should match - // against full PostScript names, but this is matching against font family names. This works... - // sometimes. - let mut local_fonts = HashMap::new(); - for source in sources.iter() { - if let Source::Local(family_name) = source { - local_fonts - .entry(family_name.name.clone()) - .or_insert_with(|| { - let family = SingleFontFamily::FamilyName(FamilyName { - name: family_name.name.clone(), - syntax: FontFamilyNameSyntax::Quoted, - }); - self.system_font_service_proxy - .find_matching_font_templates(None, &family) - .first() - .cloned() - }); - } - } + let css_font_face_descriptors = rule.into(); + let completion_handler = + WebFontLoadInitiator::Stylesheet(stylesheet.clone(), finished_callback.clone()); number_loading += 1; - self.web_fonts - .write() - .handle_web_font_load_started_for_stylesheet(stylesheet); - - self.process_next_web_font_source(WebFontDownloadState { - webview_id, - css_font_face_descriptors: Arc::new(rule.into()), - remaining_sources: sources, - finished_callback: finished_callback.clone(), - core_resource_thread: self.resource_threads.lock().clone(), - local_fonts: Arc::new(local_fonts), - stylesheet: stylesheet.clone(), - }); + self.start_loading_one_web_font( + Some(webview_id), + font_face.sources(), + css_font_face_descriptors, + completion_handler, + ); } number_loading @@ -553,15 +636,84 @@ impl FontContextWebFontMethods for Arc<FontContext> { removed_instance_keys.into_iter().collect(), ) } + + fn load_web_font_for_script( + &self, + webview_id: Option<WebViewId>, + sources: SourceList, + descriptors: CSSFontFaceDescriptors, + finished_callback: ScriptWebFontLoadFinishedCallback, + ) { + let completion_handler = WebFontLoadInitiator::Script(finished_callback); + self.start_loading_one_web_font(webview_id, &sources, descriptors, completion_handler); + } + + fn add_template_to_font_context( + &self, + family_name: LowercaseFontFamilyName, + new_template: FontTemplate, + ) { + self.web_fonts + .write() + .add_new_template(family_name, new_template); + self.invalidate_font_groups_after_web_font_load(); + } } impl FontContext { + fn start_loading_one_web_font( + self: &Arc<FontContext>, + webview_id: Option<WebViewId>, + source_list: &SourceList, + css_font_face_descriptors: CSSFontFaceDescriptors, + completion_handler: WebFontLoadInitiator, + ) { + let sources: Vec<Source> = source_list + .0 + .iter() + .rev() + .filter(Self::is_supported_web_font_source) + .cloned() + .collect(); + + // Fetch all local fonts first, beacause if we try to fetch them later on during the process of + // loading the list of web font `src`s we may be running in the context of the router thread, which + // means we won't be able to seend IPC messages to the FontCacheThread. + // + // TODO: This is completely wrong. The specification says that `local()` font-family should match + // against full PostScript names, but this is matching against font family names. This works... + // sometimes. + let mut local_fonts = HashMap::new(); + for source in sources.iter() { + if let Source::Local(family_name) = source { + local_fonts + .entry(family_name.name.clone()) + .or_insert_with(|| { + let family = SingleFontFamily::FamilyName(FamilyName { + name: family_name.name.clone(), + syntax: FontFamilyNameSyntax::Quoted, + }); + self.system_font_service_proxy + .find_matching_font_templates(None, &family) + .first() + .cloned() + }); + } + } + + self.process_next_web_font_source(WebFontDownloadState::new( + webview_id, + self.clone(), + css_font_face_descriptors, + completion_handler, + sources, + local_fonts, + )); + } + fn process_next_web_font_source(self: &Arc<FontContext>, mut state: WebFontDownloadState) { let Some(source) = state.remaining_sources.pop() else { - self.web_fonts - .write() - .handle_web_font_failed_to_load(&state); - self.handle_web_font_load_finished(&state.finished_callback, false); + state.handle_web_font_load_failure(); return; }; @@ -581,17 +733,13 @@ impl FontContext { let template = FontTemplate::new_for_local_web_font( local_template.clone(), &state.css_font_face_descriptors, - state.stylesheet.clone(), + state.initiator.stylesheet().cloned(), ) .ok()?; Some(template) }) { - let not_cancelled = self - .web_fonts - .write() - .handle_web_font_loaded(&state, new_template); - self.handle_web_font_load_finished(&state.finished_callback, not_cancelled); + state.handle_web_font_load_success(new_template); } else { this.process_next_web_font_source(state); } @@ -600,12 +748,29 @@ impl FontContext { } } +pub type ScriptWebFontLoadFinishedCallback = + Box<dyn FnOnce(LowercaseFontFamilyName, Option<FontTemplate>) + Send>; + +pub(crate) enum WebFontLoadInitiator { + Stylesheet(DocumentStyleSheet, StylesheetWebFontLoadFinishedCallback), + Script(ScriptWebFontLoadFinishedCallback), +} + +impl WebFontLoadInitiator { + pub(crate) fn stylesheet(&self) -> Option<&DocumentStyleSheet> { + match self { + Self::Stylesheet(stylesheet, _) => Some(stylesheet), + Self::Script(_) => None, + } + } +} + struct RemoteWebFontDownloader { - font_context: Arc<FontContext>, + state: Option<WebFontDownloadState>, url: ServoArc<Url>, web_font_family_name: LowercaseFontFamilyName, - response_valid: Mutex<bool>, - response_data: Mutex<Vec<u8>>, + response_valid: bool, + response_data: Vec<u8>, } enum DownloaderResponseResult { @@ -628,23 +793,21 @@ impl RemoteWebFontDownloader { }; // FIXME: This shouldn't use NoReferrer, but the current documents url - let request = RequestBuilder::new( - Some(state.webview_id), - url.clone().into(), - Referrer::NoReferrer, - ) - .destination(Destination::Font); + let request = + RequestBuilder::new(state.webview_id, url.clone().into(), Referrer::NoReferrer) + .destination(Destination::Font); + + let core_resource_thread_clone = state.core_resource_thread.clone(); debug!("Loading @font-face {} from {}", web_font_family_name, url); - let downloader = Self { - font_context, + let mut downloader = Self { url, web_font_family_name, - response_valid: Mutex::new(false), - response_data: Mutex::default(), + response_valid: false, + response_data: Vec::new(), + state: Some(state), }; - let core_resource_thread_clone = state.core_resource_thread.clone(); fetch_async( &core_resource_thread_clone, request, @@ -653,40 +816,42 @@ impl RemoteWebFontDownloader { match downloader.handle_web_font_fetch_message(response_message) { DownloaderResponseResult::InProcess => {}, DownloaderResponseResult::Finished => { - if !downloader.process_downloaded_font_and_signal_completion(&state) { - downloader - .font_context - .process_next_web_font_source(state.clone()) + if !downloader.process_downloaded_font_and_signal_completion() { + font_context.process_next_web_font_source(downloader.take_state()) } }, - DownloaderResponseResult::Failure => downloader - .font_context - .process_next_web_font_source(state.clone()), + DownloaderResponseResult::Failure => { + font_context.process_next_web_font_source(downloader.take_state()) + }, } }), ) } + fn take_state(&mut self) -> WebFontDownloadState { + self.state + .take() + .expect("must be non-None until download either succeeds or fails") + } + /// After a download finishes, try to process the downloaded data, returning true if /// the font is added successfully to the [`FontContext`] or false if it isn't. - fn process_downloaded_font_and_signal_completion(&self, state: &WebFontDownloadState) -> bool { - if self - .font_context - .web_fonts - .read() - .font_load_cancelled_for_stylesheet(&state.stylesheet) - { - self.font_context - .handle_web_font_load_finished(&state.finished_callback, false); + fn process_downloaded_font_and_signal_completion(&mut self) -> bool { + let state = self + .state + .as_ref() + .expect("must be non-None until processing is completed"); + if state.font_load_cancelled() { + self.take_state().handle_web_font_load_failure(); // Returning true here prevents trying to load the next font on the source list. return true; } - let font_data = std::mem::take(&mut *self.response_data.lock()); + let font_data = std::mem::take(&mut self.response_data); trace!( - "@font-face {} data={:?}", + "Downloaded @font-face {} ({} bytes)", self.web_font_family_name, - font_data + font_data.len() ); let font_data = match fontsan::process(&font_data) { @@ -705,6 +870,8 @@ impl RemoteWebFontDownloader { let Ok(handle) = PlatformFont::new_from_data(identifier, &font_data, None) else { return false; }; + + let state = self.take_state(); let mut descriptor = handle.descriptor(); descriptor .override_values_with_css_font_template_descriptors(&state.css_font_face_descriptors); @@ -712,20 +879,16 @@ impl RemoteWebFontDownloader { let new_template = FontTemplate::new( FontIdentifier::Web(url), descriptor, - Some(state.stylesheet.clone()), + state.initiator.stylesheet().cloned(), ); - self.font_context + + state + .font_context .font_data .write() .insert(new_template.identifier.clone(), font_data); - let not_cancelled = self - .font_context - .web_fonts - .write() - .handle_web_font_loaded(state, new_template); - self.font_context - .handle_web_font_load_finished(&state.finished_callback, not_cancelled); + state.handle_web_font_load_success(new_template); // If the load was canceled above, then we still want to return true from this function in // order to halt any attempt to load sources that come later on the source list. @@ -733,7 +896,7 @@ impl RemoteWebFontDownloader { } fn handle_web_font_fetch_message( - &self, + &mut self, response_message: FetchResponseMsg, ) -> DownloaderResponseResult { match response_message { @@ -746,7 +909,7 @@ impl RemoteWebFontDownloader { self.web_font_family_name, meta_result.is_ok() ); - *self.response_valid.lock() = meta_result.is_ok(); + self.response_valid = meta_result.is_ok(); DownloaderResponseResult::InProcess }, FetchResponseMsg::ProcessResponseChunk(_, new_bytes) => { @@ -755,8 +918,8 @@ impl RemoteWebFontDownloader { self.web_font_family_name, new_bytes ); - if *self.response_valid.lock() { - self.response_data.lock().extend(new_bytes) + if self.response_valid { + self.response_data.extend(new_bytes) } DownloaderResponseResult::InProcess }, @@ -766,7 +929,7 @@ impl RemoteWebFontDownloader { self.web_font_family_name, response ); - if response.is_err() || !*self.response_valid.lock() { + if response.is_err() || !self.response_valid { return DownloaderResponseResult::Failure; } DownloaderResponseResult::Finished @@ -804,35 +967,3 @@ impl Hash for FontGroupCacheKey { self.style.hash.hash(hasher) } } - -fn is_supported_web_font_source(source: &&Source) -> bool { - let url_source = match source { - Source::Url(ref url_source) => url_source, - Source::Local(_) => return true, - }; - let format_hint = match url_source.format_hint { - Some(ref format_hint) => format_hint, - None => return true, - }; - - if matches!( - format_hint, - FontFaceSourceFormat::Keyword( - FontFaceSourceFormatKeyword::Truetype | - FontFaceSourceFormatKeyword::Opentype | - FontFaceSourceFormatKeyword::Woff | - FontFaceSourceFormatKeyword::Woff2 - ) - ) { - return true; - } - - if let FontFaceSourceFormat::String(string) = format_hint { - return string == "truetype" || - string == "opentype" || - string == "woff" || - string == "woff2"; - } - - false -} diff --git a/components/fonts/font_store.rs b/components/fonts/font_store.rs index 5791319bb14..826be947672 100644 --- a/components/fonts/font_store.rs +++ b/components/fonts/font_store.rs @@ -12,14 +12,14 @@ use style::stylesheets::DocumentStyleSheet; use style::values::computed::{FontStyle, FontWeight}; use crate::font::FontDescriptor; -use crate::font_context::WebFontDownloadState; use crate::font_template::{FontTemplate, FontTemplateRef, FontTemplateRefMethods, IsOblique}; use crate::system_font_service::{FontIdentifier, LowercaseFontFamilyName}; #[derive(Default)] pub struct FontStore { pub(crate) families: HashMap<LowercaseFontFamilyName, FontTemplates>, - web_fonts_loading: Vec<(DocumentStyleSheet, usize)>, + web_fonts_loading_for_stylesheets: Vec<(DocumentStyleSheet, usize)>, + web_fonts_loading_for_script: usize, } pub(crate) type CrossThreadFontStore = Arc<RwLock<FontStore>>; @@ -33,13 +33,13 @@ impl FontStore { stylesheet: &DocumentStyleSheet, ) -> bool { !self - .web_fonts_loading + .web_fonts_loading_for_stylesheets .iter() .any(|(loading_stylesheet, _)| loading_stylesheet == stylesheet) } pub(crate) fn handle_stylesheet_removed(&mut self, stylesheet: &DocumentStyleSheet) { - self.web_fonts_loading + self.web_fonts_loading_for_stylesheets .retain(|(loading_stylesheet, _)| loading_stylesheet != stylesheet); } @@ -48,54 +48,82 @@ impl FontStore { stylesheet: &DocumentStyleSheet, ) { if let Some((_, count)) = self - .web_fonts_loading + .web_fonts_loading_for_stylesheets .iter_mut() .find(|(loading_stylesheet, _)| loading_stylesheet == stylesheet) { *count += 1; } else { - self.web_fonts_loading.push((stylesheet.clone(), 1)) + self.web_fonts_loading_for_stylesheets + .push((stylesheet.clone(), 1)) } } fn remove_one_web_font_loading_for_stylesheet(&mut self, stylesheet: &DocumentStyleSheet) { if let Some((_, count)) = self - .web_fonts_loading + .web_fonts_loading_for_stylesheets .iter_mut() .find(|(loading_stylesheet, _)| loading_stylesheet == stylesheet) { *count -= 1; } - self.web_fonts_loading.retain(|(_, count)| *count != 0); + self.web_fonts_loading_for_stylesheets + .retain(|(_, count)| *count != 0); } - pub(crate) fn handle_web_font_failed_to_load(&mut self, state: &WebFontDownloadState) { - self.remove_one_web_font_loading_for_stylesheet(&state.stylesheet); + pub(crate) fn handle_web_font_load_failed_for_stylesheet( + &mut self, + stylesheet: &DocumentStyleSheet, + ) { + self.remove_one_web_font_loading_for_stylesheet(stylesheet); } /// Handle a web font load finishing, adding the new font to the [`FontStore`]. If the web font /// load was canceled (for instance, if the stylesheet was removed), then do nothing and return /// false. - pub(crate) fn handle_web_font_loaded( + pub(crate) fn handle_web_font_loaded_for_stylesheet( &mut self, - state: &WebFontDownloadState, + stylesheet: &DocumentStyleSheet, + family_name: LowercaseFontFamilyName, new_template: FontTemplate, ) -> bool { // Abort processing this web font if the originating stylesheet was removed. - if self.font_load_cancelled_for_stylesheet(&state.stylesheet) { + if self.font_load_cancelled_for_stylesheet(stylesheet) { return false; } - let family_name = state.css_font_face_descriptors.family_name.clone(); + + self.add_new_template(family_name, new_template); + + self.remove_one_web_font_loading_for_stylesheet(stylesheet); + + true + } + + pub(crate) fn add_new_template( + &mut self, + family_name: LowercaseFontFamilyName, + new_template: FontTemplate, + ) { self.families .entry(family_name) .or_default() .add_template(new_template); - self.remove_one_web_font_loading_for_stylesheet(&state.stylesheet); - true + } + + pub(crate) fn handle_web_font_load_started_for_script(&mut self) { + self.web_fonts_loading_for_script += 1; + } + + pub(crate) fn handle_web_font_load_finished_for_script(&mut self) { + self.web_fonts_loading_for_script -= 1; } pub(crate) fn number_of_fonts_still_loading(&self) -> usize { - self.web_fonts_loading.iter().map(|(_, count)| count).sum() + self.web_fonts_loading_for_script + + self.web_fonts_loading_for_stylesheets + .iter() + .map(|(_, count)| count) + .sum::<usize>() } } diff --git a/components/fonts/font_template.rs b/components/fonts/font_template.rs index d7733f66274..4c2e32bd3ea 100644 --- a/components/fonts/font_template.rs +++ b/components/fonts/font_template.rs @@ -177,13 +177,13 @@ impl FontTemplate { pub fn new_for_local_web_font( local_template: FontTemplateRef, css_font_template_descriptors: &CSSFontFaceDescriptors, - stylesheet: DocumentStyleSheet, + stylesheet: Option<DocumentStyleSheet>, ) -> Result<FontTemplate, &'static str> { let mut alias_template = local_template.borrow().clone(); alias_template .descriptor .override_values_with_css_font_template_descriptors(css_font_template_descriptors); - alias_template.stylesheet = Some(stylesheet); + alias_template.stylesheet = stylesheet; Ok(alias_template) } diff --git a/components/fonts/system_font_service.rs b/components/fonts/system_font_service.rs index b29eca6b86c..e50b3144f54 100644 --- a/components/fonts/system_font_service.rs +++ b/components/fonts/system_font_service.rs @@ -565,7 +565,7 @@ impl SystemFontServiceProxy { } } -#[derive(Clone, Debug, Default, Deserialize, Eq, Hash, PartialEq, Serialize)] +#[derive(Clone, Debug, Default, Deserialize, Eq, Hash, MallocSizeOf, PartialEq, Serialize)] pub struct LowercaseFontFamilyName { inner: String, } diff --git a/components/layout_thread/lib.rs b/components/layout_thread/lib.rs index 987d8ad1839..8c275230b23 100644 --- a/components/layout_thread/lib.rs +++ b/components/layout_thread/lib.rs @@ -24,7 +24,7 @@ use fnv::FnvHashMap; use fonts::{ get_and_reset_text_shaping_performance_counter, FontContext, FontContextWebFontMethods, }; -use fonts_traits::WebFontLoadFinishedCallback; +use fonts_traits::StylesheetWebFontLoadFinishedCallback; use fxhash::{FxHashMap, FxHashSet}; use ipc_channel::ipc::IpcSender; use layout::construct::ConstructionResult; @@ -627,7 +627,7 @@ impl LayoutThread { stylesheet, guard, self.stylist.device(), - Arc::new(web_font_finished_loading_callback) as WebFontLoadFinishedCallback, + Arc::new(web_font_finished_loading_callback) as StylesheetWebFontLoadFinishedCallback, ); } diff --git a/components/layout_thread_2020/lib.rs b/components/layout_thread_2020/lib.rs index 03f47d5904c..a591226b946 100644 --- a/components/layout_thread_2020/lib.rs +++ b/components/layout_thread_2020/lib.rs @@ -23,7 +23,7 @@ use euclid::default::{Point2D as UntypedPoint2D, Rect as UntypedRect, Size2D as use euclid::{Point2D, Scale, Size2D, Vector2D}; use fnv::FnvHashMap; use fonts::{FontContext, FontContextWebFontMethods}; -use fonts_traits::WebFontLoadFinishedCallback; +use fonts_traits::StylesheetWebFontLoadFinishedCallback; use fxhash::FxHashMap; use ipc_channel::ipc::IpcSender; use layout::context::LayoutContext; @@ -602,7 +602,7 @@ impl LayoutThread { stylesheet, guard, self.stylist.device(), - Arc::new(web_font_finished_loading_callback) as WebFontLoadFinishedCallback, + Arc::new(web_font_finished_loading_callback) as StylesheetWebFontLoadFinishedCallback, ); } diff --git a/components/malloc_size_of/lib.rs b/components/malloc_size_of/lib.rs index ec633a49f9b..50d4ce30718 100644 --- a/components/malloc_size_of/lib.rs +++ b/components/malloc_size_of/lib.rs @@ -711,6 +711,7 @@ malloc_size_of_is_0!(std::sync::atomic::AtomicUsize); malloc_size_of_is_0!(std::time::Duration); malloc_size_of_is_0!(std::time::Instant); malloc_size_of_is_0!(std::time::SystemTime); +malloc_size_of_is_0!(style::font_face::SourceList); macro_rules! malloc_size_of_is_webrender_malloc_size_of( ($($ty:ty),+) => ( diff --git a/components/profile/time.rs b/components/profile/time.rs index 73a48efa764..c6afff4e7b5 100644 --- a/components/profile/time.rs +++ b/components/profile/time.rs @@ -104,6 +104,7 @@ impl Formattable for ProfilerCategory { ProfilerCategory::ScriptDocumentEvent => "Script Document Event", ProfilerCategory::ScriptEvaluate => "Script JS Evaluate", ProfilerCategory::ScriptFileRead => "Script File Read", + ProfilerCategory::ScriptFontLoading => "Script Font Loading", ProfilerCategory::ScriptHistoryEvent => "Script History Event", ProfilerCategory::ScriptImageCacheMsg => "Script Image Cache Msg", ProfilerCategory::ScriptInputEvent => "Script Input Event", diff --git a/components/script/dom/eventsource.rs b/components/script/dom/eventsource.rs index ba4c1dca9dc..bbec13d0d83 100644 --- a/components/script/dom/eventsource.rs +++ b/components/script/dom/eventsource.rs @@ -594,7 +594,7 @@ impl EventSourceMethods<crate::DomTypeHolder> for EventSource { last_event_id: String::new(), resource_timing: ResourceFetchTiming::new(ResourceTimingType::Resource), }; - let listener = NetworkListener { + let mut listener = NetworkListener { context: Arc::new(Mutex::new(context)), task_source: global.task_manager().networking_task_source().into(), }; diff --git a/components/script/dom/fontface.rs b/components/script/dom/fontface.rs new file mode 100644 index 00000000000..9fb753600fa --- /dev/null +++ b/components/script/dom/fontface.rs @@ -0,0 +1,577 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use std::cell::{Cell, RefCell}; +use std::rc::Rc; + +use cssparser::{Parser, ParserInput}; +use dom_struct::dom_struct; +use fonts::{FontContext, FontContextWebFontMethods, FontTemplate, LowercaseFontFamilyName}; +use js::rust::HandleObject; +use style::error_reporting::ParseErrorReporter; +use style::font_face::SourceList; +use style::parser::ParserContext; +use style::stylesheets::{CssRuleType, FontFaceRule, Origin, UrlExtraData}; +use style_traits::{ParsingMode, ToCss}; + +use super::bindings::cell::DomRefCell; +use super::bindings::codegen::UnionTypes::StringOrArrayBufferViewOrArrayBuffer; +use super::bindings::error::{Error, ErrorResult, Fallible}; +use super::bindings::refcounted::Trusted; +use super::bindings::reflector::DomGlobal; +use super::bindings::root::MutNullableDom; +use super::types::FontFaceSet; +use crate::dom::bindings::codegen::Bindings::FontFaceBinding::{ + FontFaceDescriptors, FontFaceLoadStatus, FontFaceMethods, +}; +use crate::dom::bindings::codegen::Bindings::WindowBinding::WindowMethods; +use crate::dom::bindings::codegen::UnionTypes; +use crate::dom::bindings::reflector::{reflect_dom_object_with_proto, Reflector}; +use crate::dom::bindings::root::DomRoot; +use crate::dom::bindings::str::DOMString; +use crate::dom::globalscope::GlobalScope; +use crate::dom::promise::Promise; +use crate::dom::window::Window; +use crate::script_runtime::CanGc; + +/// <https://drafts.csswg.org/css-font-loading/#fontface-interface> +#[dom_struct] +pub struct FontFace { + reflector: Reflector, + status: Cell<FontFaceLoadStatus>, + family_name: DomRefCell<DOMString>, + descriptors: DomRefCell<FontFaceDescriptors>, + + /// A reference to the [`FontFaceSet`] that this `FontFace` is a member of, if it has been + /// added to one. `None` otherwise. The spec suggests that a `FontFace` can be a member of + /// multiple `FontFaceSet`s, but this doesn't seem to be the case in practice, as the + /// `FontFaceSet` constructor is not exposed on the global scope. + font_face_set: MutNullableDom<FontFaceSet>, + + /// This holds the [`FontTemplate`] resulting from loading this `FontFace`, to be used when the + /// `FontFace` is added to the global `FontFaceSet` and thus the `[FontContext]`. + // + // TODO: This could potentially share the `FontTemplateRef` created by `FontContext`, rather + // than having its own copy of the template. + #[no_trace = "Does not contain managed objects"] + template: RefCell<Option<(LowercaseFontFamilyName, FontTemplate)>>, + + #[no_trace = "Does not contain managed objects"] + /// <https://drafts.csswg.org/css-font-loading/#m-fontface-urls-slot> + urls: DomRefCell<Option<SourceList>>, + + /// <https://drafts.csswg.org/css-font-loading/#dom-fontface-fontstatuspromise-slot> + #[ignore_malloc_size_of = "Rc"] + font_status_promise: Rc<Promise>, +} + +/// Given the various font face descriptors, construct the equivalent `@font-face` css rule as a +/// string and parse it using `style` crate. Returns `Err(Error::Syntax)` if parsing fails. +/// +/// Due to lack of support in the `style` crate, parsing the whole `@font-face` rule is much easier +/// to implement than parsing each declaration on its own. +fn parse_font_face_descriptors( + global: &GlobalScope, + family_name: &DOMString, + sources: Option<&str>, + input_descriptors: &FontFaceDescriptors, +) -> Fallible<FontFaceRule> { + let window = global.as_window(); // TODO: Support calling FontFace APIs from Worker + let quirks_mode = window.Document().quirks_mode(); + let url_data = UrlExtraData(window.get_url().get_arc()); + let error_reporter = FontFaceErrorReporter { + not_encountered_error: Cell::new(true), + }; + let parser_context = ParserContext::new( + Origin::Author, + &url_data, + Some(CssRuleType::FontFace), + ParsingMode::DEFAULT, + quirks_mode, + /* namespaces = */ Default::default(), + Some(&error_reporter as &dyn ParseErrorReporter), + None, + ); + + let FontFaceDescriptors { + ref ascentOverride, + ref descentOverride, + ref display, + ref featureSettings, + ref lineGapOverride, + ref stretch, + ref style, + ref unicodeRange, + ref variationSettings, + ref weight, + } = input_descriptors; + + let _ = variationSettings; // TODO: Stylo doesn't parse font-variation-settings yet. + let maybe_sources = sources.map_or_else(String::new, |sources| format!("src: {sources};")); + let font_face_rule = format!( + r" + ascent-override: {ascentOverride}; + descent-override: {descentOverride}; + font-display: {display}; + font-family: {family_name}; + font-feature-settings: {featureSettings}; + font-stretch: {stretch}; + font-style: {style}; + font-weight: {weight}; + line-gap-override: {lineGapOverride}; + unicode-range: {unicodeRange}; + {maybe_sources} + " + ); + + // TODO: Should this be the source location in the script that invoked the font face API? + let location = cssparser::SourceLocation { line: 0, column: 0 }; + let mut input = ParserInput::new(&font_face_rule); + let mut parser = Parser::new(&mut input); + let mut parsed_font_face_rule = + style::font_face::parse_font_face_block(&parser_context, &mut parser, location); + + if let Some(ref mut sources) = parsed_font_face_rule.sources { + let supported_sources: Vec<_> = sources + .0 + .iter() + .rev() + .filter(FontContext::is_supported_web_font_source) + .cloned() + .collect(); + if supported_sources.is_empty() { + error_reporter.not_encountered_error.set(false); + } else { + sources.0 = supported_sources; + } + } + + if error_reporter.not_encountered_error.get() { + Ok(parsed_font_face_rule) + } else { + Err(Error::Syntax) + } +} + +fn serialize_parsed_descriptors(font_face_rule: &FontFaceRule) -> FontFaceDescriptors { + FontFaceDescriptors { + ascentOverride: font_face_rule.ascent_override.to_css_string().into(), + descentOverride: font_face_rule.descent_override.to_css_string().into(), + display: font_face_rule.display.to_css_string().into(), + featureSettings: font_face_rule.feature_settings.to_css_string().into(), + lineGapOverride: font_face_rule.line_gap_override.to_css_string().into(), + stretch: font_face_rule.stretch.to_css_string().into(), + style: font_face_rule.style.to_css_string().into(), + unicodeRange: font_face_rule.unicode_range.to_css_string().into(), + variationSettings: font_face_rule.variation_settings.to_css_string().into(), + weight: font_face_rule.weight.to_css_string().into(), + } +} + +struct FontFaceErrorReporter { + not_encountered_error: Cell<bool>, +} + +impl ParseErrorReporter for FontFaceErrorReporter { + fn report_error( + &self, + _url: &UrlExtraData, + _location: cssparser::SourceLocation, + _error: style::error_reporting::ContextualParseError, + ) { + self.not_encountered_error.set(false); + } +} + +impl FontFace { + /// Construct a [`FontFace`] to be used in the case of failure in parsing the + /// font face descriptors. + fn new_failed_font_face(global: &GlobalScope, can_gc: CanGc) -> Self { + let font_status_promise = Promise::new(global, can_gc); + // If any of them fail to parse correctly, reject font face’s [[FontStatusPromise]] with a + // DOMException named "SyntaxError" + font_status_promise.reject_error(Error::Syntax); + + // set font face’s corresponding attributes to the empty string, and set font face’s status + // attribute to "error" + Self { + reflector: Reflector::new(), + font_face_set: MutNullableDom::default(), + font_status_promise, + family_name: DomRefCell::default(), + urls: DomRefCell::default(), + descriptors: DomRefCell::new(FontFaceDescriptors { + ascentOverride: DOMString::new(), + descentOverride: DOMString::new(), + display: DOMString::new(), + featureSettings: DOMString::new(), + lineGapOverride: DOMString::new(), + stretch: DOMString::new(), + style: DOMString::new(), + unicodeRange: DOMString::new(), + variationSettings: DOMString::new(), + weight: DOMString::new(), + }), + status: Cell::new(FontFaceLoadStatus::Error), + template: RefCell::default(), + } + } + + /// <https://drafts.csswg.org/css-font-loading/#font-face-constructor> + fn new_inherited( + global: &GlobalScope, + family_name: DOMString, + source: StringOrArrayBufferViewOrArrayBuffer, + descriptors: &FontFaceDescriptors, + can_gc: CanGc, + ) -> Self { + // TODO: Add support for ArrayBuffer and ArrayBufferView sources. + let StringOrArrayBufferViewOrArrayBuffer::String(ref source_string) = source else { + return Self::new_failed_font_face(global, can_gc); + }; + + // Step 1. Parse the family argument, and the members of the descriptors argument, + // according to the grammars of the corresponding descriptors of the CSS @font-face rule If + // the source argument is a CSSOMString, parse it according to the grammar of the CSS src + // descriptor of the @font-face rule. + let parse_result = + parse_font_face_descriptors(global, &family_name, Some(source_string), descriptors); + + let Ok(ref parsed_font_face_rule) = parse_result else { + // If any of them fail to parse correctly, reject font face’s + // [[FontStatusPromise]] with a DOMException named "SyntaxError", set font face’s + // corresponding attributes to the empty string, and set font face’s status attribute + // to "error". + return Self::new_failed_font_face(global, can_gc); + }; + + // Set its internal [[FontStatusPromise]] slot to a fresh pending Promise object. + let font_status_promise = Promise::new(global, can_gc); + + let sources = parsed_font_face_rule + .sources + .clone() + .expect("Sources should be non-None after validation"); + + // Let font face be a fresh FontFace object. + Self { + reflector: Reflector::new(), + + // Set font face’s status attribute to "unloaded". + status: Cell::new(FontFaceLoadStatus::Unloaded), + + // Set font face’s corresponding attributes to the serialization of the parsed values. + descriptors: DomRefCell::new(serialize_parsed_descriptors(parsed_font_face_rule)), + + font_face_set: MutNullableDom::default(), + family_name: DomRefCell::new(family_name.clone()), + urls: DomRefCell::new(Some(sources)), + template: RefCell::default(), + font_status_promise, + } + } + + pub(crate) fn new( + global: &GlobalScope, + proto: Option<HandleObject>, + font_family: DOMString, + source: StringOrArrayBufferViewOrArrayBuffer, + descriptors: &FontFaceDescriptors, + can_gc: CanGc, + ) -> DomRoot<Self> { + reflect_dom_object_with_proto( + Box::new(Self::new_inherited( + global, + font_family, + source, + descriptors, + can_gc, + )), + global, + proto, + can_gc, + ) + } + + pub(super) fn set_associated_font_face_set(&self, font_face_set: &FontFaceSet) { + self.font_face_set.set(Some(font_face_set)); + } + + pub(super) fn loaded(&self) -> bool { + self.status.get() == FontFaceLoadStatus::Loaded + } + + pub(super) fn template(&self) -> Option<(LowercaseFontFamilyName, FontTemplate)> { + self.template.borrow().clone() + } + + /// Implements the body of the setter for the descriptor attributes of the [`FontFace`] interface. + /// + /// <https://drafts.csswg.org/css-font-loading/#fontface-interface>: + /// On setting, parse the string according to the grammar for the corresponding @font-face + /// descriptor. If it does not match the grammar, throw a SyntaxError; otherwise, set the attribute + /// to the serialization of the parsed value. + fn validate_and_set_descriptors(&self, new_descriptors: FontFaceDescriptors) -> ErrorResult { + let global = self.global(); + let parsed_font_face_rule = parse_font_face_descriptors( + &global, + &self.family_name.borrow(), + None, + &new_descriptors, + )?; + + *self.descriptors.borrow_mut() = serialize_parsed_descriptors(&parsed_font_face_rule); + Ok(()) + } +} + +impl FontFaceMethods<crate::DomTypeHolder> for FontFace { + /// <https://drafts.csswg.org/css-font-loading/#dom-fontface-family> + fn Family(&self) -> DOMString { + self.family_name.borrow().clone() + } + + /// <https://drafts.csswg.org/css-font-loading/#dom-fontface-family> + fn SetFamily(&self, family_name: DOMString) -> ErrorResult { + let descriptors = self.descriptors.borrow(); + let global = self.global(); + let _ = parse_font_face_descriptors(&global, &family_name, None, &descriptors)?; + *self.family_name.borrow_mut() = family_name; + Ok(()) + } + + /// <https://drafts.csswg.org/css-font-loading/#dom-fontface-style> + fn Style(&self) -> DOMString { + self.descriptors.borrow().style.clone() + } + + /// <https://drafts.csswg.org/css-font-loading/#dom-fontface-style> + fn SetStyle(&self, value: DOMString) -> ErrorResult { + let mut new_descriptors = self.descriptors.borrow().clone(); + new_descriptors.style = value; + self.validate_and_set_descriptors(new_descriptors) + } + + /// <https://drafts.csswg.org/css-font-loading/#dom-fontface-weight> + fn Weight(&self) -> DOMString { + self.descriptors.borrow().weight.clone() + } + + /// <https://drafts.csswg.org/css-font-loading/#dom-fontface-weight> + fn SetWeight(&self, value: DOMString) -> ErrorResult { + let mut new_descriptors = self.descriptors.borrow().clone(); + new_descriptors.weight = value; + self.validate_and_set_descriptors(new_descriptors) + } + + /// <https://drafts.csswg.org/css-font-loading/#dom-fontface-stretch> + fn Stretch(&self) -> DOMString { + self.descriptors.borrow().stretch.clone() + } + + /// <https://drafts.csswg.org/css-font-loading/#dom-fontface-stretch> + fn SetStretch(&self, value: DOMString) -> ErrorResult { + let mut new_descriptors = self.descriptors.borrow().clone(); + new_descriptors.stretch = value; + self.validate_and_set_descriptors(new_descriptors) + } + + /// <https://drafts.csswg.org/css-font-loading/#dom-fontface-unicoderange> + fn UnicodeRange(&self) -> DOMString { + self.descriptors.borrow().unicodeRange.clone() + } + + /// <https://drafts.csswg.org/css-font-loading/#dom-fontface-unicoderange> + fn SetUnicodeRange(&self, value: DOMString) -> ErrorResult { + let mut new_descriptors = self.descriptors.borrow().clone(); + new_descriptors.unicodeRange = value; + self.validate_and_set_descriptors(new_descriptors) + } + + /// <https://drafts.csswg.org/css-font-loading/#dom-fontface-featuresettings> + fn FeatureSettings(&self) -> DOMString { + self.descriptors.borrow().featureSettings.clone() + } + + /// <https://drafts.csswg.org/css-font-loading/#dom-fontface-featuresettings> + fn SetFeatureSettings(&self, value: DOMString) -> ErrorResult { + let mut new_descriptors = self.descriptors.borrow().clone(); + new_descriptors.featureSettings = value; + self.validate_and_set_descriptors(new_descriptors) + } + + /// <https://drafts.csswg.org/css-font-loading/#dom-fontface-variationsettings> + fn VariationSettings(&self) -> DOMString { + self.descriptors.borrow().variationSettings.clone() + } + + /// <https://drafts.csswg.org/css-font-loading/#dom-fontface-variationsettings> + fn SetVariationSettings(&self, value: DOMString) -> ErrorResult { + let mut new_descriptors = self.descriptors.borrow().clone(); + new_descriptors.variationSettings = value; + self.validate_and_set_descriptors(new_descriptors) + } + + /// <https://drafts.csswg.org/css-font-loading/#dom-fontface-display> + fn Display(&self) -> DOMString { + self.descriptors.borrow().display.clone() + } + + /// <https://drafts.csswg.org/css-font-loading/#dom-fontface-display> + fn SetDisplay(&self, value: DOMString) -> ErrorResult { + let mut new_descriptors = self.descriptors.borrow().clone(); + new_descriptors.display = value; + self.validate_and_set_descriptors(new_descriptors) + } + + /// <https://drafts.csswg.org/css-font-loading/#dom-fontface-ascentoverride> + fn AscentOverride(&self) -> DOMString { + self.descriptors.borrow().ascentOverride.clone() + } + + /// <https://drafts.csswg.org/css-font-loading/#dom-fontface-ascentoverride> + fn SetAscentOverride(&self, value: DOMString) -> ErrorResult { + let mut new_descriptors = self.descriptors.borrow().clone(); + new_descriptors.ascentOverride = value; + self.validate_and_set_descriptors(new_descriptors) + } + + /// <https://drafts.csswg.org/css-font-loading/#dom-fontface-descentoverride> + fn DescentOverride(&self) -> DOMString { + self.descriptors.borrow().descentOverride.clone() + } + + /// <https://drafts.csswg.org/css-font-loading/#dom-fontface-descentoverride> + fn SetDescentOverride(&self, value: DOMString) -> ErrorResult { + let mut new_descriptors = self.descriptors.borrow().clone(); + new_descriptors.descentOverride = value; + self.validate_and_set_descriptors(new_descriptors) + } + + /// <https://drafts.csswg.org/css-font-loading/#dom-fontface-linegapoverride> + fn LineGapOverride(&self) -> DOMString { + self.descriptors.borrow().lineGapOverride.clone() + } + + /// <https://drafts.csswg.org/css-font-loading/#dom-fontface-linegapoverride> + fn SetLineGapOverride(&self, value: DOMString) -> ErrorResult { + let mut new_descriptors = self.descriptors.borrow().clone(); + new_descriptors.lineGapOverride = value; + self.validate_and_set_descriptors(new_descriptors) + } + + /// <https://drafts.csswg.org/css-font-loading/#dom-fontface-status> + fn Status(&self) -> FontFaceLoadStatus { + self.status.get() + } + + /// The load() method of FontFace forces a url-based font face to request its font data and + /// load. For fonts constructed from a buffer source, or fonts that are already loading or + /// loaded, it does nothing. + /// <https://drafts.csswg.org/css-font-loading/#font-face-load> + fn Load(&self) -> Rc<Promise> { + let Some(sources) = self.urls.borrow_mut().take() else { + // Step 2. If font face’s [[Urls]] slot is null, or its status attribute is anything + // other than "unloaded", return font face’s [[FontStatusPromise]] and abort these + // steps. + return self.font_status_promise.clone(); + }; + + // FontFace must not be loaded at this point as `self.urls` is not None, implying `Load` + // wasn't called already. In our implementation, `urls` is set after parsing, so it + // cannot be `Some` if the status is `Error`. + debug_assert_eq!(self.status.get(), FontFaceLoadStatus::Unloaded); + + let global = self.global(); + let trusted = Trusted::new(self); + let task_source = global + .task_manager() + .font_loading_task_source() + .to_sendable(); + + let finished_callback = Box::new( + move |family_name: LowercaseFontFamilyName, load_result: Option<_>| { + let trusted = trusted.clone(); + + // Step 5. When the load operation completes, successfully or not, queue a task to + // run the following steps synchronously: + task_source.queue(task!(resolve_font_face_load_task: move || { + let font_face = trusted.root(); + + match load_result { + None => { + // Step 5.1. If the attempt to load fails, reject font face’s + // [[FontStatusPromise]] with a DOMException whose name is "NetworkError" + // and set font face’s status attribute to "error". + font_face.status.set(FontFaceLoadStatus::Error); + font_face.font_status_promise.reject_error(Error::Network); + } + Some(template) => { + // Step 5.2. Otherwise, font face now represents the loaded font; + // fulfill font face’s [[FontStatusPromise]] with font face and set + // font face’s status attribute to "loaded". + font_face.status.set(FontFaceLoadStatus::Loaded); + let old_template = font_face.template.borrow_mut().replace((family_name, template)); + debug_assert!(old_template.is_none(), "FontFace's template must be intialized only once"); + font_face.font_status_promise.resolve_native(&font_face); + } + } + + if let Some(font_face_set) = font_face.font_face_set.get() { + // For each FontFaceSet font face is in: ... + // + // This implements steps 5.1.1, 5.1.2, 5.2.1 and 5.2.2 - these + // take care of changing the status of the `FontFaceSet` in which this + // `FontFace` is a member, for both failed and successful load. + font_face_set.handle_font_face_status_changed(&font_face); + } + })); + }, + ); + + // We parse the descriptors again because they are stored as `DOMString`s in this `FontFace` + // but the `load_web_font_for_script` API needs parsed values. + let parsed_font_face_rule = parse_font_face_descriptors( + &global, + &self.family_name.borrow(), + None, + &self.descriptors.borrow(), + ) + .expect("Parsing shouldn't fail as descriptors are valid by construction"); + + // Step 4. Using the value of font face’s [[Urls]] slot, attempt to load a font as defined + // in [CSS-FONTS-3], as if it was the value of a @font-face rule’s src descriptor. + // TODO: FontFaceSet is not supported on Workers yet. The `as_window` call below should be + // replaced when we do support it. + global.as_window().font_context().load_web_font_for_script( + global.webview_id(), + sources, + (&parsed_font_face_rule).into(), + finished_callback, + ); + + // Step 3. Set font face’s status attribute to "loading", return font face’s + // [[FontStatusPromise]], and continue executing the rest of this algorithm asynchronously. + self.status.set(FontFaceLoadStatus::Loading); + self.font_status_promise.clone() + } + + /// <https://drafts.csswg.org/css-font-loading/#dom-fontface-loaded> + fn Loaded(&self) -> Rc<Promise> { + self.font_status_promise.clone() + } + + /// <https://drafts.csswg.org/css-font-loading/#font-face-constructor> + fn Constructor( + window: &Window, + proto: Option<HandleObject>, + can_gc: CanGc, + family: DOMString, + source: UnionTypes::StringOrArrayBufferViewOrArrayBuffer, + descriptors: &FontFaceDescriptors, + ) -> DomRoot<FontFace> { + let global = window.as_global_scope(); + FontFace::new(global, proto, family, source, descriptors, can_gc) + } +} diff --git a/components/script/dom/fontfaceset.rs b/components/script/dom/fontfaceset.rs index dfaaf553b10..497da30680d 100644 --- a/components/script/dom/fontfaceset.rs +++ b/components/script/dom/fontfaceset.rs @@ -5,26 +5,35 @@ use std::rc::Rc; use dom_struct::dom_struct; +use fonts::FontContextWebFontMethods; use js::rust::HandleObject; +use super::bindings::reflector::DomGlobal; +use super::types::Window; use crate::dom::bindings::codegen::Bindings::FontFaceSetBinding::FontFaceSetMethods; +use crate::dom::bindings::codegen::Bindings::WindowBinding::WindowMethods; +use crate::dom::bindings::refcounted::TrustedPromise; use crate::dom::bindings::reflector::reflect_dom_object_with_proto; use crate::dom::bindings::root::DomRoot; +use crate::dom::bindings::str::DOMString; use crate::dom::eventtarget::EventTarget; +use crate::dom::fontface::FontFace; use crate::dom::globalscope::GlobalScope; use crate::dom::promise::Promise; -use crate::realms::enter_realm; use crate::script_runtime::CanGc; +/// <https://drafts.csswg.org/css-font-loading/#FontFaceSet-interface> #[dom_struct] pub(crate) struct FontFaceSet { target: EventTarget, + + /// <https://drafts.csswg.org/css-font-loading/#dom-fontfaceset-readypromise-slot> #[ignore_malloc_size_of = "Rc"] promise: Rc<Promise>, } impl FontFaceSet { - pub(crate) fn new_inherited(global: &GlobalScope, can_gc: CanGc) -> Self { + fn new_inherited(global: &GlobalScope, can_gc: CanGc) -> Self { FontFaceSet { target: EventTarget::new_inherited(), promise: Promise::new(global, can_gc), @@ -44,9 +53,24 @@ impl FontFaceSet { ) } + pub(super) fn handle_font_face_status_changed(&self, font_face: &FontFace) { + if font_face.loaded() { + let Some(window) = DomRoot::downcast::<Window>(self.global()) else { + return; + }; + + let (family_name, template) = font_face + .template() + .expect("A loaded web font should have a template"); + window + .font_context() + .add_template_to_font_context(family_name, template); + window.Document().dirty_all_nodes(); + } + } + pub(crate) fn fulfill_ready_promise_if_needed(&self) { if !self.promise.is_fulfilled() { - let _ac = enter_realm(&*self.promise); self.promise.resolve_native(self); } } @@ -57,4 +81,43 @@ impl FontFaceSetMethods<crate::DomTypeHolder> for FontFaceSet { fn Ready(&self) -> Rc<Promise> { self.promise.clone() } + + /// <https://drafts.csswg.org/css-font-loading/#dom-fontfaceset-add> + fn Add(&self, font_face: &FontFace) -> DomRoot<FontFaceSet> { + font_face.set_associated_font_face_set(self); + self.handle_font_face_status_changed(font_face); + DomRoot::from_ref(self) + } + + /// <https://drafts.csswg.org/css-font-loading/#dom-fontfaceset-load> + fn Load(&self, _font: DOMString, _text: DOMString, can_gc: CanGc) -> Rc<Promise> { + // Step 1. Let font face set be the FontFaceSet object this method was called on. Let + // promise be a newly-created promise object. + let promise = Promise::new(&self.global(), can_gc); + + // TODO: Step 3. Find the matching font faces from font face set using the font and text + // arguments passed to the function, and let font face list be the return value (ignoring + // the found faces flag). If a syntax error was returned, reject promise with a SyntaxError + // exception and terminate these steps. + + let trusted = TrustedPromise::new(promise.clone()); + // Step 4. Queue a task to run the following steps synchronously: + self.global() + .task_manager() + .font_loading_task_source() + .queue(task!(resolve_font_face_set_load_task: move || { + let promise = trusted.root(); + + // TODO: Step 4.1. For all of the font faces in the font face list, call their load() + // method. + + // TODO: Step 4.2. Resolve promise with the result of waiting for all of the + // [[FontStatusPromise]]s of each font face in the font face list, in order. + let matched_fonts = Vec::<&FontFace>::new(); + promise.resolve_native(&matched_fonts); + })); + + // Step 2. Return promise. Complete the rest of these steps asynchronously. + promise + } } diff --git a/components/script/dom/mod.rs b/components/script/dom/mod.rs index b0ea5d3aeb6..7127828224e 100644 --- a/components/script/dom/mod.rs +++ b/components/script/dom/mod.rs @@ -321,6 +321,7 @@ pub(crate) mod filelist; pub(crate) mod filereader; pub(crate) mod filereadersync; pub(crate) mod focusevent; +pub(crate) mod fontface; pub(crate) mod fontfaceset; pub(crate) mod formdata; pub(crate) mod formdataevent; diff --git a/components/script/dom/window.rs b/components/script/dom/window.rs index 375e6621992..f0b4b430ed6 100644 --- a/components/script/dom/window.rs +++ b/components/script/dom/window.rs @@ -634,6 +634,10 @@ impl Window { ) -> EventStatus { event.dispatch(self.upcast(), true, can_gc) } + + pub(crate) fn font_context(&self) -> &Arc<FontContext> { + &self.font_context + } } // https://html.spec.whatwg.org/multipage/#atob diff --git a/components/script/network_listener.rs b/components/script/network_listener.rs index 5e662b8ef76..0e18af5dd16 100644 --- a/components/script/network_listener.rs +++ b/components/script/network_listener.rs @@ -73,7 +73,7 @@ pub(crate) fn submit_timing_data( } impl<Listener: PreInvoke + Send + 'static> NetworkListener<Listener> { - pub(crate) fn notify<A: Action<Listener> + Send + 'static>(&self, action: A) { + pub(crate) fn notify<A: Action<Listener> + Send + 'static>(&mut self, action: A) { self.task_source.queue(ListenerTask { context: self.context.clone(), action, @@ -83,11 +83,11 @@ impl<Listener: PreInvoke + Send + 'static> NetworkListener<Listener> { // helps type inference impl<Listener: FetchResponseListener + PreInvoke + Send + 'static> NetworkListener<Listener> { - pub(crate) fn notify_fetch(&self, action: FetchResponseMsg) { + pub(crate) fn notify_fetch(&mut self, action: FetchResponseMsg) { self.notify(action); } - pub(crate) fn into_callback(self) -> BoxedFetchCallback { + pub(crate) fn into_callback(mut self) -> BoxedFetchCallback { Box::new(move |response_msg| self.notify_fetch(response_msg)) } } diff --git a/components/script/script_runtime.rs b/components/script/script_runtime.rs index a51871eadf4..71e93671f47 100644 --- a/components/script/script_runtime.rs +++ b/components/script/script_runtime.rs @@ -104,6 +104,7 @@ pub(crate) enum ScriptThreadEventCategory { DevtoolsMsg, DocumentEvent, FileRead, + FontLoading, FormPlannedNavigation, HistoryEvent, ImageCacheMsg, @@ -139,6 +140,7 @@ impl From<ScriptThreadEventCategory> for ProfilerCategory { ScriptThreadEventCategory::EnterFullscreen => ProfilerCategory::ScriptEnterFullscreen, ScriptThreadEventCategory::ExitFullscreen => ProfilerCategory::ScriptExitFullscreen, ScriptThreadEventCategory::FileRead => ProfilerCategory::ScriptFileRead, + ScriptThreadEventCategory::FontLoading => ProfilerCategory::ScriptFontLoading, ScriptThreadEventCategory::FormPlannedNavigation => { ProfilerCategory::ScriptPlannedNavigation }, @@ -181,6 +183,7 @@ impl From<ScriptThreadEventCategory> for ScriptHangAnnotation { ScriptThreadEventCategory::DocumentEvent => ScriptHangAnnotation::DocumentEvent, ScriptThreadEventCategory::InputEvent => ScriptHangAnnotation::InputEvent, ScriptThreadEventCategory::FileRead => ScriptHangAnnotation::FileRead, + ScriptThreadEventCategory::FontLoading => ScriptHangAnnotation::FontLoading, ScriptThreadEventCategory::FormPlannedNavigation => { ScriptHangAnnotation::FormPlannedNavigation }, diff --git a/components/script/script_thread.rs b/components/script/script_thread.rs index b0066c88768..22a4e2e9e9b 100644 --- a/components/script/script_thread.rs +++ b/components/script/script_thread.rs @@ -1614,6 +1614,9 @@ impl ScriptThread { ScriptThreadEventCategory::FileRead => { time_profile!(ProfilerCategory::ScriptFileRead, None, profiler_chan, f) }, + ScriptThreadEventCategory::FontLoading => { + time_profile!(ProfilerCategory::ScriptFontLoading, None, profiler_chan, f) + }, ScriptThreadEventCategory::FormPlannedNavigation => time_profile!( ProfilerCategory::ScriptPlannedNavigation, None, diff --git a/components/script/task_manager.rs b/components/script/task_manager.rs index eeae042b6ee..4854ef7cd56 100644 --- a/components/script/task_manager.rs +++ b/components/script/task_manager.rs @@ -134,6 +134,7 @@ impl TaskManager { task_source_functions!(self, canvas_blob_task_source, Canvas); task_source_functions!(self, dom_manipulation_task_source, DOMManipulation); task_source_functions!(self, file_reading_task_source, FileReading); + task_source_functions!(self, font_loading_task_source, FontLoading); task_source_functions!(self, gamepad_task_source, Gamepad); task_source_functions!(self, media_element_task_source, MediaElement); task_source_functions!(self, networking_task_source, Networking); diff --git a/components/script/task_source.rs b/components/script/task_source.rs index 0030dcb8c30..3d9993f6082 100644 --- a/components/script/task_source.rs +++ b/components/script/task_source.rs @@ -27,6 +27,8 @@ pub(crate) enum TaskSourceName { Canvas, DOMManipulation, FileReading, + /// <https://drafts.csswg.org/css-font-loading/#task-source> + FontLoading, HistoryTraversal, Networking, PerformanceTimeline, @@ -48,6 +50,7 @@ impl From<TaskSourceName> for ScriptThreadEventCategory { TaskSourceName::Canvas => ScriptThreadEventCategory::ScriptEvent, TaskSourceName::DOMManipulation => ScriptThreadEventCategory::ScriptEvent, TaskSourceName::FileReading => ScriptThreadEventCategory::FileRead, + TaskSourceName::FontLoading => ScriptThreadEventCategory::FontLoading, TaskSourceName::HistoryTraversal => ScriptThreadEventCategory::HistoryEvent, TaskSourceName::Networking => ScriptThreadEventCategory::NetworkEvent, TaskSourceName::PerformanceTimeline => { @@ -71,6 +74,7 @@ impl TaskSourceName { TaskSourceName::Canvas, TaskSourceName::DOMManipulation, TaskSourceName::FileReading, + TaskSourceName::FontLoading, TaskSourceName::HistoryTraversal, TaskSourceName::Networking, TaskSourceName::PerformanceTimeline, diff --git a/components/script_bindings/codegen/Bindings.conf b/components/script_bindings/codegen/Bindings.conf index bdc57b39746..57e268c9f10 100644 --- a/components/script_bindings/codegen/Bindings.conf +++ b/components/script_bindings/codegen/Bindings.conf @@ -175,6 +175,10 @@ DOMInterfaces = { 'canGc': ['Abort'], }, +'FontFaceSet': { + 'canGc': ['Load'], +}, + 'GamepadHapticActuator': { 'inRealms': ['PlayEffect', 'Reset'], 'canGc': ['PlayEffect', 'Reset'], @@ -577,6 +581,10 @@ DOMInterfaces = { } Dictionaries = { +'FontFaceDescriptors': { + 'derives': ['Clone', 'MallocSizeOf'] +}, + 'GPUCanvasConfiguration': { 'derives': ['Clone'] }, diff --git a/components/script_bindings/webidls/FontFace.webidl b/components/script_bindings/webidls/FontFace.webidl new file mode 100644 index 00000000000..e2b1cc7f02c --- /dev/null +++ b/components/script_bindings/webidls/FontFace.webidl @@ -0,0 +1,54 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +typedef DOMString CSSOMString; + +dictionary FontFaceDescriptors { + CSSOMString style = "normal"; + CSSOMString weight = "normal"; + CSSOMString stretch = "normal"; + CSSOMString unicodeRange = "U+0-10FFFF"; + CSSOMString featureSettings = "normal"; + CSSOMString variationSettings = "normal"; + CSSOMString display = "auto"; + CSSOMString ascentOverride = "normal"; + CSSOMString descentOverride = "normal"; + CSSOMString lineGapOverride = "normal"; +}; + +enum FontFaceLoadStatus { "unloaded", "loading", "loaded", "error" }; + +// https://drafts.csswg.org/css-font-loading/#fontface-interface +[Exposed=(Window /*, Worker */), Pref="dom_fontface_enabled"] // TODO: Add support for FontFace in Workers. +interface FontFace { + constructor(CSSOMString family, (CSSOMString or BufferSource) source, + optional FontFaceDescriptors descriptors = {}); + [SetterThrows] + attribute CSSOMString family; + [SetterThrows] + attribute CSSOMString style; + [SetterThrows] + attribute CSSOMString weight; + [SetterThrows] + attribute CSSOMString stretch; + [SetterThrows] + attribute CSSOMString unicodeRange; + [SetterThrows] + attribute CSSOMString featureSettings; + [SetterThrows] + attribute CSSOMString variationSettings; + [SetterThrows] + attribute CSSOMString display; + [SetterThrows] + attribute CSSOMString ascentOverride; + [SetterThrows] + attribute CSSOMString descentOverride; + [SetterThrows] + attribute CSSOMString lineGapOverride; + + readonly attribute FontFaceLoadStatus status; + + Promise<FontFace> load(); + readonly attribute Promise<FontFace> loaded; +}; diff --git a/components/script_bindings/webidls/FontFaceSet.webidl b/components/script_bindings/webidls/FontFaceSet.webidl index f114b2ac9b4..1bac3489ef6 100644 --- a/components/script_bindings/webidls/FontFaceSet.webidl +++ b/components/script_bindings/webidls/FontFaceSet.webidl @@ -19,12 +19,13 @@ enum FontFaceSetLoadStatus { "loading" , "loaded" }; */ // https://drafts.csswg.org/css-font-loading/#FontFaceSet-interface -[Exposed=(Window,Worker)] +[Exposed=(Window /*, Worker */)] interface FontFaceSet : EventTarget { // constructor(sequence<FontFace> initialFaces); // setlike<FontFace>; - // FontFaceSet add(FontFace font); + [Pref="dom_fontface_enabled"] + FontFaceSet add(FontFace font); // boolean delete(FontFace font); // undefined clear(); @@ -35,7 +36,8 @@ interface FontFaceSet : EventTarget { // check and start loads if appropriate // and fulfill promise when all loads complete - // Promise<sequence<FontFace>> load(DOMString font, optional DOMString text = " "); + [Pref="dom_fontface_enabled"] + Promise<sequence<FontFace>> load(DOMString font, optional DOMString text = " "); // return whether all fonts in the fontlist are loaded // (does not initiate load if not available) diff --git a/components/shared/background_hang_monitor/lib.rs b/components/shared/background_hang_monitor/lib.rs index ae7e587226a..223ace39c4b 100644 --- a/components/shared/background_hang_monitor/lib.rs +++ b/components/shared/background_hang_monitor/lib.rs @@ -21,6 +21,7 @@ pub enum ScriptHangAnnotation { DevtoolsMsg, DocumentEvent, FileRead, + FontLoading, FormPlannedNavigation, ImageCacheMsg, InputEvent, diff --git a/components/shared/fonts/lib.rs b/components/shared/fonts/lib.rs index 032dcfd19e0..9ba8d9425ee 100644 --- a/components/shared/fonts/lib.rs +++ b/components/shared/fonts/lib.rs @@ -17,4 +17,4 @@ int_range_index! { struct ByteIndex(isize) } -pub type WebFontLoadFinishedCallback = Arc<dyn Fn(bool) + Send + Sync + 'static>; +pub type StylesheetWebFontLoadFinishedCallback = Arc<dyn Fn(bool) + Send + Sync + 'static>; diff --git a/components/shared/net/lib.rs b/components/shared/net/lib.rs index bbdad23fa6f..b6c44cad75f 100644 --- a/components/shared/net/lib.rs +++ b/components/shared/net/lib.rs @@ -524,7 +524,7 @@ enum ToFetchThreadMessage { FetchResponse(FetchResponseMsg), } -pub type BoxedFetchCallback = Box<dyn Fn(FetchResponseMsg) + Send + 'static>; +pub type BoxedFetchCallback = Box<dyn FnMut(FetchResponseMsg) + Send + 'static>; /// A thread to handle fetches in a Servo process. This thread is responsible for /// listening for new fetch requests as well as updates on those operations and forwarding @@ -601,7 +601,7 @@ impl FetchThread { matches!(fetch_response_msg, FetchResponseMsg::ProcessResponseEOF(..)); self.active_fetches - .get(&request_id) + .get_mut(&request_id) .expect("Got fetch response for unknown fetch")( fetch_response_msg ); diff --git a/components/shared/profile/time.rs b/components/shared/profile/time.rs index ccb842a2224..78c380b70dc 100644 --- a/components/shared/profile/time.rs +++ b/components/shared/profile/time.rs @@ -90,43 +90,44 @@ pub enum ProfilerCategory { ScriptEvent = 0x66, ScriptFileRead = 0x67, - ScriptImageCacheMsg = 0x68, - ScriptInputEvent = 0x69, - ScriptNetworkEvent = 0x6a, + ScriptFontLoading = 0x68, + ScriptImageCacheMsg = 0x69, + ScriptInputEvent = 0x6a, + ScriptNetworkEvent = 0x6b, /// The script thread is parsing HTML, rather than doing other work like evaluating scripts or doing layout. - ScriptParseHTML = 0x6b, - - ScriptPlannedNavigation = 0x6c, - ScriptResize = 0x6d, - ScriptRendering = 0x6e, - ScriptSetScrollState = 0x6f, - ScriptSetViewport = 0x70, - ScriptTimerEvent = 0x71, - ScriptStylesheetLoad = 0x72, - ScriptUpdateReplacedElement = 0x73, - ScriptWebSocketEvent = 0x74, - ScriptWorkerEvent = 0x75, - ScriptServiceWorkerEvent = 0x76, + ScriptParseHTML = 0x6c, + + ScriptPlannedNavigation = 0x6d, + ScriptResize = 0x6e, + ScriptRendering = 0x6f, + ScriptSetScrollState = 0x70, + ScriptSetViewport = 0x71, + ScriptTimerEvent = 0x72, + ScriptStylesheetLoad = 0x73, + ScriptUpdateReplacedElement = 0x74, + ScriptWebSocketEvent = 0x75, + ScriptWorkerEvent = 0x76, + ScriptServiceWorkerEvent = 0x77, /// The script thread is parsing XML, rather than doing other work like evaluating scripts or doing layout. - ScriptParseXML = 0x77, + ScriptParseXML = 0x78, - ScriptEnterFullscreen = 0x78, - ScriptExitFullscreen = 0x79, - ScriptWorkletEvent = 0x7a, - ScriptPerformanceEvent = 0x7b, - ScriptHistoryEvent = 0x7c, - ScriptPortMessage = 0x7d, - ScriptWebGPUMsg = 0x7e, + ScriptEnterFullscreen = 0x79, + ScriptExitFullscreen = 0x7a, + ScriptWorkletEvent = 0x7b, + ScriptPerformanceEvent = 0x7c, + ScriptHistoryEvent = 0x7d, + ScriptPortMessage = 0x7e, + ScriptWebGPUMsg = 0x7f, /// Web performance metrics. - TimeToFirstPaint = 0x80, - TimeToFirstContentfulPaint = 0x81, - TimeToInteractive = 0x82, + TimeToFirstPaint = 0x90, + TimeToFirstContentfulPaint = 0x91, + TimeToInteractive = 0x92, - IpcReceiver = 0x83, - IpcBytesReceiver = 0x84, + IpcReceiver = 0x93, + IpcBytesReceiver = 0x94, } impl ProfilerCategory { @@ -151,6 +152,7 @@ impl ProfilerCategory { ProfilerCategory::ScriptEvaluate => "ScriptEvaluate", ProfilerCategory::ScriptEvent => "ScriptEvent", ProfilerCategory::ScriptFileRead => "ScriptFileRead", + ProfilerCategory::ScriptFontLoading => "ScriptFontLoading", ProfilerCategory::ScriptImageCacheMsg => "ScriptImageCacheMsg", ProfilerCategory::ScriptInputEvent => "ScriptInputEvent", ProfilerCategory::ScriptNetworkEvent => "ScriptNetworkEvent", diff --git a/tests/wpt/meta/__dir__.ini b/tests/wpt/meta/__dir__.ini index b5b2db98287..f69cd94922b 100644 --- a/tests/wpt/meta/__dir__.ini +++ b/tests/wpt/meta/__dir__.ini @@ -1 +1 @@ -prefs: ["dom_imagebitmap_enabled:true", "dom_offscreen_canvas_enabled:true", "dom_shadowdom_enabled:true", "dom_xpath_enabled:true", "dom_intersection_observer_enabled:true", "dom_resize_observer_enabled:true", "dom_notification_enabled:true"] +prefs: ["dom_imagebitmap_enabled:true", "dom_offscreen_canvas_enabled:true", "dom_shadowdom_enabled:true", "dom_xpath_enabled:true", "dom_intersection_observer_enabled:true", "dom_resize_observer_enabled:true", "dom_notification_enabled:true", "dom_fontface_enabled:true"] diff --git a/tests/wpt/meta/css/css-cascade/layer-cssom-order-reverse.html.ini b/tests/wpt/meta/css/css-cascade/layer-cssom-order-reverse.html.ini index 9bfa3e73791..6694f4405f2 100644 --- a/tests/wpt/meta/css/css-cascade/layer-cssom-order-reverse.html.ini +++ b/tests/wpt/meta/css/css-cascade/layer-cssom-order-reverse.html.ini @@ -1,6 +1,3 @@ [layer-cssom-order-reverse.html] - [Insert layer invalidates @font-face] - expected: FAIL - [Delete layer invalidates @font-face] expected: FAIL diff --git a/tests/wpt/meta/css/css-cascade/layer-font-face-override.html.ini b/tests/wpt/meta/css/css-cascade/layer-font-face-override.html.ini index b453839c49f..6195b20bcec 100644 --- a/tests/wpt/meta/css/css-cascade/layer-font-face-override.html.ini +++ b/tests/wpt/meta/css/css-cascade/layer-font-face-override.html.ini @@ -1,9 +1,11 @@ [layer-font-face-override.html] + bug: https://github.com/servo/servo/issues/35520 + [@font-face unlayered overrides layered] - expected: FAIL + expected: [FAIL, PASS] [@font-face override between layers] - expected: FAIL + expected: [FAIL, PASS] [@font-face override update with appended sheet 1] expected: FAIL diff --git a/tests/wpt/meta/css/css-fonts/fallback-remote-to-data-url.html.ini b/tests/wpt/meta/css/css-fonts/fallback-remote-to-data-url.html.ini index 08229124c2f..28cbce9fd3a 100644 --- a/tests/wpt/meta/css/css-fonts/fallback-remote-to-data-url.html.ini +++ b/tests/wpt/meta/css/css-fonts/fallback-remote-to-data-url.html.ini @@ -1,2 +1,3 @@ [fallback-remote-to-data-url.html] - expected: ERROR + [We should use the inline custom font to render the page when the primary remote font is loading] + expected: FAIL diff --git a/tests/wpt/meta/css/css-fonts/font-display/font-display-change.html.ini b/tests/wpt/meta/css/css-fonts/font-display/font-display-change.html.ini index 8643675f6b3..5e3c6fc0042 100644 --- a/tests/wpt/meta/css/css-fonts/font-display/font-display-change.html.ini +++ b/tests/wpt/meta/css/css-fonts/font-display/font-display-change.html.ini @@ -1,2 +1,3 @@ [font-display-change.html] - expected: TIMEOUT + bug: https://github.com/servo/servo/issues/35521 + expected: [FAIL, TIMEOUT, PASS] diff --git a/tests/wpt/meta/css/css-fonts/format-specifiers-variations.html.ini b/tests/wpt/meta/css/css-fonts/format-specifiers-variations.html.ini index 041f8eb820b..4cd865a3c24 100644 --- a/tests/wpt/meta/css/css-fonts/format-specifiers-variations.html.ini +++ b/tests/wpt/meta/css/css-fonts/format-specifiers-variations.html.ini @@ -1,16 +1,4 @@ [format-specifiers-variations.html] - [Load Ahem with format woff] - expected: FAIL - - [Load Ahem with format truetype] - expected: FAIL - - [Load Ahem with format opentype] - expected: FAIL - - [Load Ahem with format woff2] - expected: FAIL - [Load Ahem with format woff-variations] expected: FAIL @@ -22,123 +10,3 @@ [Load Ahem with format woff2-variations] expected: FAIL - - [Do not load Ahem with format xyzwoff] - expected: FAIL - - [Do not load Ahem with format xyztruetype] - expected: FAIL - - [Do not load Ahem with format xyzopentype] - expected: FAIL - - [Do not load Ahem with format xyzwoff2] - expected: FAIL - - [Do not load Ahem with format xyzwoff-variations] - expected: FAIL - - [Do not load Ahem with format xyztruetype-variations] - expected: FAIL - - [Do not load Ahem with format xyzopentype-variations] - expected: FAIL - - [Do not load Ahem with format xyzwoff2-variations] - expected: FAIL - - [Do not load Ahem with format woffxyz] - expected: FAIL - - [Do not load Ahem with format truetypexyz] - expected: FAIL - - [Do not load Ahem with format opentypexyz] - expected: FAIL - - [Do not load Ahem with format woff2xyz] - expected: FAIL - - [Do not load Ahem with format woff-variationsxyz] - expected: FAIL - - [Do not load Ahem with format truetype-variationsxyz] - expected: FAIL - - [Do not load Ahem with format opentype-variationsxyz] - expected: FAIL - - [Do not load Ahem with format woff2-variationsxyz] - expected: FAIL - - [Do not load Ahem with format wo] - expected: FAIL - - [Do not load Ahem with format truety] - expected: FAIL - - [Do not load Ahem with format openty] - expected: FAIL - - [Do not load Ahem with format wof] - expected: FAIL - - [Do not load Ahem with format woff-variatio] - expected: FAIL - - [Do not load Ahem with format truetype-variatio] - expected: FAIL - - [Do not load Ahem with format opentype-variatio] - expected: FAIL - - [Do not load Ahem with format woff2-variatio] - expected: FAIL - - [Do not load Ahem with format ff] - expected: FAIL - - [Do not load Ahem with format uetype] - expected: FAIL - - [Do not load Ahem with format entype] - expected: FAIL - - [Do not load Ahem with format ff2] - expected: FAIL - - [Do not load Ahem with format ff-variations] - expected: FAIL - - [Do not load Ahem with format uetype-variations] - expected: FAIL - - [Do not load Ahem with format entype-variations] - expected: FAIL - - [Do not load Ahem with format ff2-variations] - expected: FAIL - - [Do not load Ahem with format wff] - expected: FAIL - - [Do not load Ahem with format tretype] - expected: FAIL - - [Do not load Ahem with format opntype] - expected: FAIL - - [Do not load Ahem with format wff2] - expected: FAIL - - [Do not load Ahem with format woff-ariations] - expected: FAIL - - [Do not load Ahem with format truetye-variations] - expected: FAIL - - [Do not load Ahem with format opentye-variations] - expected: FAIL - - [Do not load Ahem with format woff2variations] - expected: FAIL diff --git a/tests/wpt/meta/css/css-fonts/parsing/font-size-adjust-computed.html.ini b/tests/wpt/meta/css/css-fonts/parsing/font-size-adjust-computed.html.ini index be39e77c50d..7236e490ccf 100644 --- a/tests/wpt/meta/css/css-fonts/parsing/font-size-adjust-computed.html.ini +++ b/tests/wpt/meta/css/css-fonts/parsing/font-size-adjust-computed.html.ini @@ -38,5 +38,20 @@ [Property font-size-adjust value 'ic-height from-font'] expected: FAIL - [CSS Fonts Module Level 5: getComputedStyle().fontSizeAdjust] + [Property font-size-adjust value 'calc(0.5)'] + expected: FAIL + + [Property font-size-adjust value 'ex-height calc(0.5)'] + expected: FAIL + + [Property font-size-adjust value 'cap-height calc(0.5)'] + expected: FAIL + + [Property font-size-adjust value 'cap-height calc(0.5 + 1)'] + expected: FAIL + + [Property font-size-adjust value 'cap-height calc(-0.5)'] + expected: FAIL + + [Property font-size-adjust value 'cap-height calc(10 + (sign(20cqw - 10px) * 5))'] expected: FAIL diff --git a/tests/wpt/meta/css/css-fonts/variations/font-parse-numeric-stretch-style-weight.html.ini b/tests/wpt/meta/css/css-fonts/variations/font-parse-numeric-stretch-style-weight.html.ini deleted file mode 100644 index 13ae17b2eb2..00000000000 --- a/tests/wpt/meta/css/css-fonts/variations/font-parse-numeric-stretch-style-weight.html.ini +++ /dev/null @@ -1,156 +0,0 @@ -[font-parse-numeric-stretch-style-weight.html] - [Valid value 100 matches 100 for weight in @font-face.] - expected: FAIL - - [Valid value 700 matches 700 for weight in @font-face.] - expected: FAIL - - [Valid value 900 matches 900 for weight in @font-face.] - expected: FAIL - - [Valid value bold matches bold for weight in @font-face.] - expected: FAIL - - [Valid value normal matches normal for weight in @font-face.] - expected: FAIL - - [Valid value 100 400 matches 100 400 for weight in @font-face.] - expected: FAIL - - [Valid value 100 101.5 matches 100 101.5 for weight in @font-face.] - expected: FAIL - - [Valid value 999.8 999.9 matches 999.8 999.9 for weight in @font-face.] - expected: FAIL - - [Valid value 500 400 matches 500 400 for weight in @font-face.] - expected: FAIL - - [Valid value 0% matches 0% for stretch in @font-face.] - expected: FAIL - - [Valid value calc(0% - 10%) matches calc(-10%) for stretch in @font-face.] - expected: FAIL - - [Valid value 100% matches 100% for stretch in @font-face.] - expected: FAIL - - [Valid value 110% matches 110% for stretch in @font-face.] - expected: FAIL - - [Valid value 111.5% matches 111.5% for stretch in @font-face.] - expected: FAIL - - [Valid value 50% 200% matches 50% 200% for stretch in @font-face.] - expected: FAIL - - [Valid value 0.1% 1% matches 0.1% 1% for stretch in @font-face.] - expected: FAIL - - [Valid value 900% 901% matches 900% 901% for stretch in @font-face.] - expected: FAIL - - [Valid value ultra-condensed matches ultra-condensed for stretch in @font-face.] - expected: FAIL - - [Valid value ultra-expanded matches ultra-expanded for stretch in @font-face.] - expected: FAIL - - [Valid value normal matches normal for style in @font-face.] - expected: FAIL - - [Valid value italic matches italic for style in @font-face.] - expected: FAIL - - [Valid value oblique matches oblique for style in @font-face.] - expected: FAIL - - [Valid value oblique 10deg matches oblique 10deg for style in @font-face.] - expected: FAIL - - [Valid value oblique 10deg 20deg matches oblique 10deg 20deg for style in @font-face.] - expected: FAIL - - [Value 0 must not be accepted as weight in @font-face.] - expected: FAIL - - [Value 0.9 must not be accepted as weight in @font-face.] - expected: FAIL - - [Value -100 200 must not be accepted as weight in @font-face.] - expected: FAIL - - [Value 100 -200 must not be accepted as weight in @font-face.] - expected: FAIL - - [Value 100 1001 must not be accepted as weight in @font-face.] - expected: FAIL - - [Value 1001 must not be accepted as weight in @font-face.] - expected: FAIL - - [Value 1000.5 must not be accepted as weight in @font-face.] - expected: FAIL - - [Value 100 200 300 must not be accepted as weight in @font-face.] - expected: FAIL - - [Value a must not be accepted as weight in @font-face.] - expected: FAIL - - [Value a b c must not be accepted as weight in @font-face.] - expected: FAIL - - [Value -0.5% must not be accepted as stretch in @font-face.] - expected: FAIL - - [Value -1% must not be accepted as stretch in @font-face.] - expected: FAIL - - [Value 60% 70% 80% must not be accepted as stretch in @font-face.] - expected: FAIL - - [Value a% must not be accepted as stretch in @font-face.] - expected: FAIL - - [Value a b c must not be accepted as stretch in @font-face.] - expected: FAIL - - [Value 0.1 must not be accepted as stretch in @font-face.] - expected: FAIL - - [Value -60% 80% must not be accepted as stretch in @font-face.] - expected: FAIL - - [Value ultra-expannnned must not be accepted as stretch in @font-face.] - expected: FAIL - - [Value 50% 0 must not be accepted as stretch in @font-face.] - expected: FAIL - - [Value oblique 100deg must not be accepted as style in @font-face.] - expected: FAIL - - [Value oblique italic must not be accepted as style in @font-face.] - expected: FAIL - - [Value oblique -91deg must not be accepted as style in @font-face.] - expected: FAIL - - [Value oblique 0 must not be accepted as style in @font-face.] - expected: FAIL - - [Value oblique 10 must not be accepted as style in @font-face.] - expected: FAIL - - [Value iiitalic must not be accepted as style in @font-face.] - expected: FAIL - - [Value 90deg must not be accepted as style in @font-face.] - expected: FAIL - - [Value 11 must not be accepted as style in @font-face.] - expected: FAIL - - [Value italic 90deg must not be accepted as style in @font-face.] - expected: FAIL diff --git a/tests/wpt/meta/css/css-masking/clip-path/clip-path-svg-text-font-loading.html.ini b/tests/wpt/meta/css/css-masking/clip-path/clip-path-svg-text-font-loading.html.ini index 103381380a3..3ea1cb4f41d 100644 --- a/tests/wpt/meta/css/css-masking/clip-path/clip-path-svg-text-font-loading.html.ini +++ b/tests/wpt/meta/css/css-masking/clip-path/clip-path-svg-text-font-loading.html.ini @@ -1,2 +1,2 @@ [clip-path-svg-text-font-loading.html] - expected: TIMEOUT + expected: FAIL diff --git a/tests/wpt/meta/css/css-values/ch-empty-pseudo-recalc-on-font-load.html.ini b/tests/wpt/meta/css/css-values/ch-empty-pseudo-recalc-on-font-load.html.ini index e9b278c3ee8..64b77b15066 100644 --- a/tests/wpt/meta/css/css-values/ch-empty-pseudo-recalc-on-font-load.html.ini +++ b/tests/wpt/meta/css/css-values/ch-empty-pseudo-recalc-on-font-load.html.ini @@ -1,5 +1,4 @@ [ch-empty-pseudo-recalc-on-font-load.html] - expected: ERROR [ch in pseudo-element ::before should be recalculated after loading a web font] expected: FAIL diff --git a/tests/wpt/meta/css/css-values/ch-pseudo-recalc-on-font-load.html.ini b/tests/wpt/meta/css/css-values/ch-pseudo-recalc-on-font-load.html.ini index 8bb7ac740bb..53b82763175 100644 --- a/tests/wpt/meta/css/css-values/ch-pseudo-recalc-on-font-load.html.ini +++ b/tests/wpt/meta/css/css-values/ch-pseudo-recalc-on-font-load.html.ini @@ -1,5 +1,4 @@ [ch-pseudo-recalc-on-font-load.html] - expected: ERROR [ch in pseudo-element ::before should be recalculated after loading a web font] expected: FAIL diff --git a/tests/wpt/meta/css/css-values/ch-recalc-on-font-load.html.ini b/tests/wpt/meta/css/css-values/ch-recalc-on-font-load.html.ini deleted file mode 100644 index 507454f0afa..00000000000 --- a/tests/wpt/meta/css/css-values/ch-recalc-on-font-load.html.ini +++ /dev/null @@ -1,10 +0,0 @@ -[ch-recalc-on-font-load.html] - expected: ERROR - [ch in a normal div should be recalculated after loading a web font] - expected: TIMEOUT - - [ch in display:contents should be recalculated after loading a web font] - expected: TIMEOUT - - [ch in display:none should be recalculated after loading a web font] - expected: TIMEOUT diff --git a/tests/wpt/meta/html/canvas/offscreen/text/2d.text.draw.fill.maxWidth.fontface.html.ini b/tests/wpt/meta/html/canvas/offscreen/text/2d.text.draw.fill.maxWidth.fontface.html.ini deleted file mode 100644 index 3e7da9f9a61..00000000000 --- a/tests/wpt/meta/html/canvas/offscreen/text/2d.text.draw.fill.maxWidth.fontface.html.ini +++ /dev/null @@ -1,4 +0,0 @@ -[2d.text.draw.fill.maxWidth.fontface.html] - [fillText works on @font-face fonts] - expected: FAIL - diff --git a/tests/wpt/meta/html/canvas/offscreen/text/2d.text.measure.width.empty.html.ini b/tests/wpt/meta/html/canvas/offscreen/text/2d.text.measure.width.empty.html.ini index e7133922d3d..2d3a3958f5c 100644 --- a/tests/wpt/meta/html/canvas/offscreen/text/2d.text.measure.width.empty.html.ini +++ b/tests/wpt/meta/html/canvas/offscreen/text/2d.text.measure.width.empty.html.ini @@ -1,6 +1,3 @@ [2d.text.measure.width.empty.html] [The empty string has zero width for OffscreenCanvas] expected: FAIL - - [The empty string has zero width] - expected: FAIL diff --git a/tests/wpt/meta/html/canvas/offscreen/text/canvas.2d.fontStretch.condensed.html.ini b/tests/wpt/meta/html/canvas/offscreen/text/canvas.2d.fontStretch.condensed.html.ini new file mode 100644 index 00000000000..f1389317ba4 --- /dev/null +++ b/tests/wpt/meta/html/canvas/offscreen/text/canvas.2d.fontStretch.condensed.html.ini @@ -0,0 +1,2 @@ +[canvas.2d.fontStretch.condensed.html] + expected: FAIL diff --git a/tests/wpt/meta/html/canvas/offscreen/text/canvas.2d.fontStretch.expanded.html.ini b/tests/wpt/meta/html/canvas/offscreen/text/canvas.2d.fontStretch.expanded.html.ini new file mode 100644 index 00000000000..dc035a8d7a6 --- /dev/null +++ b/tests/wpt/meta/html/canvas/offscreen/text/canvas.2d.fontStretch.expanded.html.ini @@ -0,0 +1,2 @@ +[canvas.2d.fontStretch.expanded.html] + expected: FAIL diff --git a/tests/wpt/meta/html/canvas/offscreen/text/canvas.2d.fontStretch.extra-condensed.html.ini b/tests/wpt/meta/html/canvas/offscreen/text/canvas.2d.fontStretch.extra-condensed.html.ini new file mode 100644 index 00000000000..b21e1ff7942 --- /dev/null +++ b/tests/wpt/meta/html/canvas/offscreen/text/canvas.2d.fontStretch.extra-condensed.html.ini @@ -0,0 +1,2 @@ +[canvas.2d.fontStretch.extra-condensed.html] + expected: FAIL diff --git a/tests/wpt/meta/html/canvas/offscreen/text/canvas.2d.fontStretch.extra-expanded.html.ini b/tests/wpt/meta/html/canvas/offscreen/text/canvas.2d.fontStretch.extra-expanded.html.ini new file mode 100644 index 00000000000..2a3dae38d26 --- /dev/null +++ b/tests/wpt/meta/html/canvas/offscreen/text/canvas.2d.fontStretch.extra-expanded.html.ini @@ -0,0 +1,2 @@ +[canvas.2d.fontStretch.extra-expanded.html] + expected: FAIL diff --git a/tests/wpt/meta/html/canvas/offscreen/text/canvas.2d.fontStretch.normal.html.ini b/tests/wpt/meta/html/canvas/offscreen/text/canvas.2d.fontStretch.normal.html.ini new file mode 100644 index 00000000000..06249acdefc --- /dev/null +++ b/tests/wpt/meta/html/canvas/offscreen/text/canvas.2d.fontStretch.normal.html.ini @@ -0,0 +1,2 @@ +[canvas.2d.fontStretch.normal.html] + expected: FAIL diff --git a/tests/wpt/meta/html/canvas/offscreen/text/canvas.2d.fontStretch.semi-condensed.html.ini b/tests/wpt/meta/html/canvas/offscreen/text/canvas.2d.fontStretch.semi-condensed.html.ini new file mode 100644 index 00000000000..82112139c28 --- /dev/null +++ b/tests/wpt/meta/html/canvas/offscreen/text/canvas.2d.fontStretch.semi-condensed.html.ini @@ -0,0 +1,2 @@ +[canvas.2d.fontStretch.semi-condensed.html] + expected: FAIL diff --git a/tests/wpt/meta/html/canvas/offscreen/text/canvas.2d.fontStretch.semi-expanded.html.ini b/tests/wpt/meta/html/canvas/offscreen/text/canvas.2d.fontStretch.semi-expanded.html.ini new file mode 100644 index 00000000000..5d96cf42cd9 --- /dev/null +++ b/tests/wpt/meta/html/canvas/offscreen/text/canvas.2d.fontStretch.semi-expanded.html.ini @@ -0,0 +1,2 @@ +[canvas.2d.fontStretch.semi-expanded.html] + expected: FAIL diff --git a/tests/wpt/meta/html/canvas/offscreen/text/canvas.2d.fontStretch.ultra-condensed.html.ini b/tests/wpt/meta/html/canvas/offscreen/text/canvas.2d.fontStretch.ultra-condensed.html.ini new file mode 100644 index 00000000000..4655dc92135 --- /dev/null +++ b/tests/wpt/meta/html/canvas/offscreen/text/canvas.2d.fontStretch.ultra-condensed.html.ini @@ -0,0 +1,2 @@ +[canvas.2d.fontStretch.ultra-condensed.html] + expected: FAIL diff --git a/tests/wpt/meta/html/canvas/offscreen/text/canvas.2d.fontStretch.ultra-expanded.html.ini b/tests/wpt/meta/html/canvas/offscreen/text/canvas.2d.fontStretch.ultra-expanded.html.ini new file mode 100644 index 00000000000..118ea5fd38c --- /dev/null +++ b/tests/wpt/meta/html/canvas/offscreen/text/canvas.2d.fontStretch.ultra-expanded.html.ini @@ -0,0 +1,2 @@ +[canvas.2d.fontStretch.ultra-expanded.html] + expected: FAIL diff --git a/tests/wpt/meta/webidl/current-realm.html.ini b/tests/wpt/meta/webidl/current-realm.html.ini index a2c668873b7..f8e0202efb1 100644 --- a/tests/wpt/meta/webidl/current-realm.html.ini +++ b/tests/wpt/meta/webidl/current-realm.html.ini @@ -4,6 +4,3 @@ [getImageData] expected: FAIL - - [FontFace's load()] - expected: FAIL diff --git a/tests/wpt/mozilla/meta/MANIFEST.json b/tests/wpt/mozilla/meta/MANIFEST.json index e1671aa476a..63bd8acee17 100644 --- a/tests/wpt/mozilla/meta/MANIFEST.json +++ b/tests/wpt/mozilla/meta/MANIFEST.json @@ -13513,7 +13513,7 @@ ] ], "interfaces.worker.js": [ - "fc621bbafeec167942f802caae43b9f2ef23b29b", + "f708bfb25594a239be31671e9fd15d6771309a12", [ "mozilla/interfaces.worker.html", {} diff --git a/tests/wpt/mozilla/tests/mozilla/interfaces.worker.js b/tests/wpt/mozilla/tests/mozilla/interfaces.worker.js index fc621bbafee..f708bfb2559 100644 --- a/tests/wpt/mozilla/tests/mozilla/interfaces.worker.js +++ b/tests/wpt/mozilla/tests/mozilla/interfaces.worker.js @@ -34,7 +34,6 @@ test_interfaces([ "FileReader", "FileReaderSync", "FinalizationRegistry", - "FontFaceSet", "FormData", "Headers", "History", |