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>
This commit is contained in:
Mukilan Thiyagarajan 2025-02-19 11:20:01 +05:30 committed by GitHub
parent 29e0fad21e
commit 56840e0a35
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
52 changed files with 1160 additions and 551 deletions

View file

@ -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(),
};

View file

@ -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 faces [[FontStatusPromise]] with a
// DOMException named "SyntaxError"
font_status_promise.reject_error(Error::Syntax);
// set font faces corresponding attributes to the empty string, and set font faces 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 faces
// [[FontStatusPromise]] with a DOMException named "SyntaxError", set font faces
// corresponding attributes to the empty string, and set font faces 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 faces status attribute to "unloaded".
status: Cell::new(FontFaceLoadStatus::Unloaded),
// Set font faces 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 faces [[Urls]] slot is null, or its status attribute is anything
// other than "unloaded", return font faces [[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 faces
// [[FontStatusPromise]] with a DOMException whose name is "NetworkError"
// and set font faces 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 faces [[FontStatusPromise]] with font face and set
// font faces 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 faces [[Urls]] slot, attempt to load a font as defined
// in [CSS-FONTS-3], as if it was the value of a @font-face rules 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 faces status attribute to "loading", return font faces
// [[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)
}
}

View file

@ -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
}
}

View file

@ -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;

View file

@ -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

View file

@ -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))
}
}

View file

@ -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
},

View file

@ -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,

View file

@ -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);

View file

@ -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,