mirror of
https://github.com/servo/servo.git
synced 2025-08-03 12:40:06 +01:00
fonts: Clean up messaging during web fonts loads (#32332)
Instead of sending a message to the script thread via IPC when a web font loads and then sending another, just give the `FontContext` a callback that send a single message to the script thread. This moves all the cache invalidation internally into `FontContext` as well. Additionally, the unused LayoutControlMessage::ExitNow enum variant is removed. Signed-off-by: Martin Robinson <mrobinson@igalia.com> Co-authored-by: Mukilan Thiyagarajan <mukilan@igalia.com>
This commit is contained in:
parent
d47c8ff2ae
commit
9f32809671
10 changed files with 150 additions and 205 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -2022,6 +2022,7 @@ dependencies = [
|
||||||
"core-foundation",
|
"core-foundation",
|
||||||
"core-graphics",
|
"core-graphics",
|
||||||
"core-text",
|
"core-text",
|
||||||
|
"crossbeam-channel",
|
||||||
"cssparser",
|
"cssparser",
|
||||||
"dwrote",
|
"dwrote",
|
||||||
"euclid",
|
"euclid",
|
||||||
|
|
|
@ -18,6 +18,7 @@ app_units = { workspace = true }
|
||||||
atomic_refcell = { workspace = true }
|
atomic_refcell = { workspace = true }
|
||||||
bitflags = { workspace = true }
|
bitflags = { workspace = true }
|
||||||
cssparser = { workspace = true }
|
cssparser = { workspace = true }
|
||||||
|
crossbeam-channel = { workspace = true }
|
||||||
euclid = { workspace = true }
|
euclid = { workspace = true }
|
||||||
fnv = { workspace = true }
|
fnv = { workspace = true }
|
||||||
fontsan = { git = "https://github.com/servo/fontsan" }
|
fontsan = { git = "https://github.com/servo/fontsan" }
|
||||||
|
|
|
@ -5,11 +5,13 @@
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::default::Default;
|
use std::default::Default;
|
||||||
use std::hash::{BuildHasherDefault, Hash, Hasher};
|
use std::hash::{BuildHasherDefault, Hash, Hasher};
|
||||||
|
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use app_units::Au;
|
use app_units::Au;
|
||||||
|
use crossbeam_channel::unbounded;
|
||||||
use fnv::FnvHasher;
|
use fnv::FnvHasher;
|
||||||
use ipc_channel::ipc::{self, IpcSender};
|
use gfx_traits::WebFontLoadFinishedCallback;
|
||||||
use log::{debug, trace};
|
use log::{debug, trace};
|
||||||
use malloc_size_of::{MallocSizeOf, MallocSizeOfOps};
|
use malloc_size_of::{MallocSizeOf, MallocSizeOfOps};
|
||||||
use malloc_size_of_derive::MallocSizeOf;
|
use malloc_size_of_derive::MallocSizeOf;
|
||||||
|
@ -49,6 +51,7 @@ pub struct FontContext<S: FontSource> {
|
||||||
cache: CachingFontSource<S>,
|
cache: CachingFontSource<S>,
|
||||||
web_fonts: CrossThreadFontStore,
|
web_fonts: CrossThreadFontStore,
|
||||||
webrender_font_store: CrossThreadWebRenderFontStore,
|
webrender_font_store: CrossThreadWebRenderFontStore,
|
||||||
|
web_fonts_still_loading: AtomicUsize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<S: FontSource> MallocSizeOf for FontContext<S> {
|
impl<S: FontSource> MallocSizeOf for FontContext<S> {
|
||||||
|
@ -66,15 +69,25 @@ impl<S: FontSource> FontContext<S> {
|
||||||
cache: CachingFontSource::new(font_source),
|
cache: CachingFontSource::new(font_source),
|
||||||
web_fonts: Arc::new(RwLock::default()),
|
web_fonts: Arc::new(RwLock::default()),
|
||||||
webrender_font_store: Arc::new(RwLock::default()),
|
webrender_font_store: Arc::new(RwLock::default()),
|
||||||
|
web_fonts_still_loading: Default::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Invalidate all caches that this [`FontContext`] holds and any in-process platform-specific
|
pub fn web_fonts_still_loading(&self) -> usize {
|
||||||
/// caches.
|
self.web_fonts_still_loading.load(Ordering::SeqCst)
|
||||||
pub fn invalidate_caches(&self) {
|
}
|
||||||
#[cfg(target_os = "macos")]
|
|
||||||
CoreTextFontCache::clear_core_text_font_cache();
|
/// Handle the situation where a web font finishes loading, specifying if the load suceeded or failed.
|
||||||
self.cache.invalidate()
|
fn handle_web_font_load_finished(
|
||||||
|
&self,
|
||||||
|
finished_callback: &WebFontLoadFinishedCallback,
|
||||||
|
succeeded: bool,
|
||||||
|
) {
|
||||||
|
self.web_fonts_still_loading.fetch_sub(1, Ordering::SeqCst);
|
||||||
|
if succeeded {
|
||||||
|
self.cache.invalidate_after_web_font_load();
|
||||||
|
}
|
||||||
|
finished_callback(succeeded);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a `FontGroup` representing fonts which can be used for layout, given the `style`.
|
/// Returns a `FontGroup` representing fonts which can be used for layout, given the `style`.
|
||||||
|
@ -218,7 +231,7 @@ impl<S: FontSource> FontContext<S> {
|
||||||
pub struct WebFontDownloadState {
|
pub struct WebFontDownloadState {
|
||||||
css_font_face_descriptors: Arc<CSSFontFaceDescriptors>,
|
css_font_face_descriptors: Arc<CSSFontFaceDescriptors>,
|
||||||
remaining_sources: Vec<Source>,
|
remaining_sources: Vec<Source>,
|
||||||
result_sender: IpcSender<()>,
|
finished_callback: WebFontLoadFinishedCallback,
|
||||||
core_resource_thread: CoreResourceThread,
|
core_resource_thread: CoreResourceThread,
|
||||||
local_fonts: Arc<HashMap<Atom, Option<FontTemplateRef>>>,
|
local_fonts: Arc<HashMap<Atom, Option<FontTemplateRef>>>,
|
||||||
}
|
}
|
||||||
|
@ -229,7 +242,7 @@ pub trait FontContextWebFontMethods {
|
||||||
stylesheet: &Stylesheet,
|
stylesheet: &Stylesheet,
|
||||||
guard: &SharedRwLockReadGuard,
|
guard: &SharedRwLockReadGuard,
|
||||||
device: &Device,
|
device: &Device,
|
||||||
font_cache_sender: &IpcSender<()>,
|
finished_callback: WebFontLoadFinishedCallback,
|
||||||
synchronous: bool,
|
synchronous: bool,
|
||||||
) -> usize;
|
) -> usize;
|
||||||
fn process_next_web_font_source(&self, web_font_download_state: WebFontDownloadState);
|
fn process_next_web_font_source(&self, web_font_download_state: WebFontDownloadState);
|
||||||
|
@ -241,14 +254,20 @@ impl<S: FontSource + Send + 'static> FontContextWebFontMethods for Arc<FontConte
|
||||||
stylesheet: &Stylesheet,
|
stylesheet: &Stylesheet,
|
||||||
guard: &SharedRwLockReadGuard,
|
guard: &SharedRwLockReadGuard,
|
||||||
device: &Device,
|
device: &Device,
|
||||||
font_cache_sender: &IpcSender<()>,
|
finished_callback: WebFontLoadFinishedCallback,
|
||||||
synchronous: bool,
|
synchronous: bool,
|
||||||
) -> usize {
|
) -> usize {
|
||||||
let (result_sender, receiver) = if synchronous {
|
let (finished_callback, synchronous_receiver) = if synchronous {
|
||||||
let (sender, receiver) = ipc::channel().unwrap();
|
let (sender, receiver) = unbounded();
|
||||||
(Some(sender), Some(receiver))
|
let finished_callback = move |_succeeded: bool| {
|
||||||
|
let _ = sender.send(());
|
||||||
|
};
|
||||||
|
(
|
||||||
|
Arc::new(finished_callback) as WebFontLoadFinishedCallback,
|
||||||
|
Some(receiver),
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
(None, None)
|
(finished_callback, None)
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut number_loading = 0;
|
let mut number_loading = 0;
|
||||||
|
@ -293,20 +312,21 @@ impl<S: FontSource + Send + 'static> FontContextWebFontMethods for Arc<FontConte
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let result_sender = result_sender.as_ref().unwrap_or(font_cache_sender).clone();
|
|
||||||
self.process_next_web_font_source(WebFontDownloadState {
|
self.process_next_web_font_source(WebFontDownloadState {
|
||||||
css_font_face_descriptors: Arc::new(rule.into()),
|
css_font_face_descriptors: Arc::new(rule.into()),
|
||||||
remaining_sources: sources,
|
remaining_sources: sources,
|
||||||
result_sender,
|
finished_callback: finished_callback.clone(),
|
||||||
core_resource_thread: self.resource_threads.lock().clone(),
|
core_resource_thread: self.resource_threads.lock().clone(),
|
||||||
local_fonts: Arc::new(local_fonts),
|
local_fonts: Arc::new(local_fonts),
|
||||||
});
|
});
|
||||||
|
|
||||||
// Either increment the count of loading web fonts, or wait for a synchronous load.
|
|
||||||
if let Some(ref receiver) = receiver {
|
|
||||||
receiver.recv().unwrap();
|
|
||||||
}
|
|
||||||
number_loading += 1;
|
number_loading += 1;
|
||||||
|
self.web_fonts_still_loading.fetch_add(1, Ordering::SeqCst);
|
||||||
|
|
||||||
|
// If the load is synchronous wait for it to be signalled.
|
||||||
|
if let Some(ref synchronous_receiver) = synchronous_receiver {
|
||||||
|
synchronous_receiver.recv().unwrap();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
number_loading
|
number_loading
|
||||||
|
@ -314,7 +334,7 @@ impl<S: FontSource + Send + 'static> FontContextWebFontMethods for Arc<FontConte
|
||||||
|
|
||||||
fn process_next_web_font_source(&self, mut state: WebFontDownloadState) {
|
fn process_next_web_font_source(&self, mut state: WebFontDownloadState) {
|
||||||
let Some(source) = state.remaining_sources.pop() else {
|
let Some(source) = state.remaining_sources.pop() else {
|
||||||
state.result_sender.send(()).unwrap();
|
self.handle_web_font_load_finished(&state.finished_callback, false);
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -344,7 +364,7 @@ impl<S: FontSource + Send + 'static> FontContextWebFontMethods for Arc<FontConte
|
||||||
.entry(web_font_family_name.clone())
|
.entry(web_font_family_name.clone())
|
||||||
.or_default()
|
.or_default()
|
||||||
.add_template(new_template);
|
.add_template(new_template);
|
||||||
drop(state.result_sender.send(()));
|
self.handle_web_font_load_finished(&state.finished_callback, true);
|
||||||
} else {
|
} else {
|
||||||
this.process_next_web_font_source(state);
|
this.process_next_web_font_source(state);
|
||||||
}
|
}
|
||||||
|
@ -452,8 +472,8 @@ impl<FCT: FontSource + Send + 'static> RemoteWebFontDownloader<FCT> {
|
||||||
.or_default()
|
.or_default()
|
||||||
.add_template(new_template);
|
.add_template(new_template);
|
||||||
|
|
||||||
// Signal the Document that we have finished trying to load this web font.
|
self.font_context
|
||||||
drop(state.result_sender.send(()));
|
.handle_web_font_load_finished(&state.finished_callback, true);
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -519,9 +539,7 @@ impl<FCT: FontSource> CachingFontSource<FCT> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn invalidate(&self) {
|
fn invalidate_after_web_font_load(&self) {
|
||||||
self.fonts.write().clear();
|
|
||||||
self.templates.write().clear();
|
|
||||||
self.resolved_font_groups.write().clear();
|
self.resolved_font_groups.write().clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
* 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/. */
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::Arc;
|
||||||
|
|
||||||
use base::id::PipelineId;
|
use base::id::PipelineId;
|
||||||
use fnv::FnvHashMap;
|
use fnv::FnvHashMap;
|
||||||
|
@ -11,7 +11,7 @@ use gfx::font_context::FontContext;
|
||||||
use net_traits::image_cache::{
|
use net_traits::image_cache::{
|
||||||
ImageCache, ImageCacheResult, ImageOrMetadataAvailable, UsePlaceholder,
|
ImageCache, ImageCacheResult, ImageOrMetadataAvailable, UsePlaceholder,
|
||||||
};
|
};
|
||||||
use parking_lot::RwLock;
|
use parking_lot::{Mutex, RwLock};
|
||||||
use script_layout_interface::{PendingImage, PendingImageState};
|
use script_layout_interface::{PendingImage, PendingImageState};
|
||||||
use servo_url::{ImmutableOrigin, ServoUrl};
|
use servo_url::{ImmutableOrigin, ServoUrl};
|
||||||
use style::context::SharedStyleContext;
|
use style::context::SharedStyleContext;
|
||||||
|
@ -43,7 +43,7 @@ pub struct LayoutContext<'a> {
|
||||||
impl<'a> Drop for LayoutContext<'a> {
|
impl<'a> Drop for LayoutContext<'a> {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
if !std::thread::panicking() {
|
if !std::thread::panicking() {
|
||||||
assert!(self.pending_images.lock().unwrap().is_empty());
|
assert!(self.pending_images.lock().is_empty());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -79,7 +79,7 @@ impl<'a> LayoutContext<'a> {
|
||||||
id,
|
id,
|
||||||
origin: self.origin.clone(),
|
origin: self.origin.clone(),
|
||||||
};
|
};
|
||||||
self.pending_images.lock().unwrap().push(image);
|
self.pending_images.lock().push(image);
|
||||||
None
|
None
|
||||||
},
|
},
|
||||||
// Not yet requested - request image or metadata from the cache
|
// Not yet requested - request image or metadata from the cache
|
||||||
|
@ -90,7 +90,7 @@ impl<'a> LayoutContext<'a> {
|
||||||
id,
|
id,
|
||||||
origin: self.origin.clone(),
|
origin: self.origin.clone(),
|
||||||
};
|
};
|
||||||
self.pending_images.lock().unwrap().push(image);
|
self.pending_images.lock().push(image);
|
||||||
None
|
None
|
||||||
},
|
},
|
||||||
// Image failed to load, so just return nothing
|
// Image failed to load, so just return nothing
|
||||||
|
|
|
@ -13,7 +13,6 @@ use std::cell::{Cell, RefCell};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::ops::{Deref, DerefMut};
|
use std::ops::{Deref, DerefMut};
|
||||||
use std::process;
|
use std::process;
|
||||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
use app_units::Au;
|
use app_units::Au;
|
||||||
|
@ -27,9 +26,9 @@ use fxhash::{FxHashMap, FxHashSet};
|
||||||
use gfx::font;
|
use gfx::font;
|
||||||
use gfx::font_cache_thread::FontCacheThread;
|
use gfx::font_cache_thread::FontCacheThread;
|
||||||
use gfx::font_context::{FontContext, FontContextWebFontMethods};
|
use gfx::font_context::{FontContext, FontContextWebFontMethods};
|
||||||
|
use gfx_traits::WebFontLoadFinishedCallback;
|
||||||
use histogram::Histogram;
|
use histogram::Histogram;
|
||||||
use ipc_channel::ipc::{self, IpcSender};
|
use ipc_channel::ipc::IpcSender;
|
||||||
use ipc_channel::router::ROUTER;
|
|
||||||
use layout::construct::ConstructionResult;
|
use layout::construct::ConstructionResult;
|
||||||
use layout::context::{LayoutContext, RegisteredPainter, RegisteredPainters};
|
use layout::context::{LayoutContext, RegisteredPainter, RegisteredPainters};
|
||||||
use layout::display_list::items::{DisplayList, ScrollOffsetMap, WebRenderImageInfo};
|
use layout::display_list::items::{DisplayList, ScrollOffsetMap, WebRenderImageInfo};
|
||||||
|
@ -119,9 +118,6 @@ pub struct LayoutThread {
|
||||||
/// Is the current reflow of an iframe, as opposed to a root window?
|
/// Is the current reflow of an iframe, as opposed to a root window?
|
||||||
is_iframe: bool,
|
is_iframe: bool,
|
||||||
|
|
||||||
/// The channel on which the font cache can send messages to us.
|
|
||||||
font_cache_sender: IpcSender<()>,
|
|
||||||
|
|
||||||
/// The channel on which messages can be sent to the constellation.
|
/// The channel on which messages can be sent to the constellation.
|
||||||
constellation_chan: IpcSender<ConstellationMsg>,
|
constellation_chan: IpcSender<ConstellationMsg>,
|
||||||
|
|
||||||
|
@ -147,9 +143,6 @@ pub struct LayoutThread {
|
||||||
/// This can be used to easily check for invalid stale data.
|
/// This can be used to easily check for invalid stale data.
|
||||||
generation: Cell<u32>,
|
generation: Cell<u32>,
|
||||||
|
|
||||||
/// The number of Web fonts that have been requested but not yet loaded.
|
|
||||||
outstanding_web_fonts: Arc<AtomicUsize>,
|
|
||||||
|
|
||||||
/// The root of the flow tree.
|
/// The root of the flow tree.
|
||||||
root_flow: RefCell<Option<FlowRef>>,
|
root_flow: RefCell<Option<FlowRef>>,
|
||||||
|
|
||||||
|
@ -252,12 +245,18 @@ impl Drop for ScriptReflowResult {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Layout for LayoutThread {
|
impl Layout for LayoutThread {
|
||||||
fn handle_constellation_msg(&mut self, msg: script_traits::LayoutControlMsg) {
|
fn handle_constellation_message(
|
||||||
self.handle_request(Request::FromPipeline(msg));
|
&mut self,
|
||||||
}
|
constellation_message: script_traits::LayoutControlMsg,
|
||||||
|
) {
|
||||||
fn handle_font_cache_msg(&mut self) {
|
match constellation_message {
|
||||||
self.handle_request(Request::FromFontCache);
|
LayoutControlMsg::SetScrollStates(new_scroll_states) => {
|
||||||
|
self.set_scroll_states(new_scroll_states);
|
||||||
|
},
|
||||||
|
LayoutControlMsg::PaintMetric(epoch, paint_time) => {
|
||||||
|
self.paint_time_metrics.maybe_set_metric(epoch, paint_time);
|
||||||
|
},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn device(&self) -> &Device {
|
fn device(&self) -> &Device {
|
||||||
|
@ -265,7 +264,7 @@ impl Layout for LayoutThread {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn waiting_for_web_fonts_to_load(&self) -> bool {
|
fn waiting_for_web_fonts_to_load(&self) -> bool {
|
||||||
self.outstanding_web_fonts.load(Ordering::SeqCst) != 0
|
self.font_context.web_fonts_still_loading() != 0
|
||||||
}
|
}
|
||||||
|
|
||||||
fn current_epoch(&self) -> Epoch {
|
fn current_epoch(&self) -> Epoch {
|
||||||
|
@ -548,11 +547,6 @@ impl Layout for LayoutThread {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
enum Request {
|
|
||||||
FromPipeline(LayoutControlMsg),
|
|
||||||
FromFontCache,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl LayoutThread {
|
impl LayoutThread {
|
||||||
fn root_flow_for_query(&self) -> Option<FlowRef> {
|
fn root_flow_for_query(&self) -> Option<FlowRef> {
|
||||||
self.root_flow.borrow().clone()
|
self.root_flow.borrow().clone()
|
||||||
|
@ -584,17 +578,6 @@ impl LayoutThread {
|
||||||
Box::new(LayoutFontMetricsProvider),
|
Box::new(LayoutFontMetricsProvider),
|
||||||
);
|
);
|
||||||
|
|
||||||
// Ask the router to proxy IPC messages from the font cache thread to layout.
|
|
||||||
let (ipc_font_cache_sender, ipc_font_cache_receiver) = ipc::channel().unwrap();
|
|
||||||
let cloned_script_chan = script_chan.clone();
|
|
||||||
ROUTER.add_route(
|
|
||||||
ipc_font_cache_receiver.to_opaque(),
|
|
||||||
Box::new(move |_message| {
|
|
||||||
let _ =
|
|
||||||
cloned_script_chan.send(ConstellationControlMsg::ForLayoutFromFontCache(id));
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
LayoutThread {
|
LayoutThread {
|
||||||
id,
|
id,
|
||||||
url,
|
url,
|
||||||
|
@ -606,10 +589,8 @@ impl LayoutThread {
|
||||||
image_cache,
|
image_cache,
|
||||||
font_context,
|
font_context,
|
||||||
first_reflow: Cell::new(true),
|
first_reflow: Cell::new(true),
|
||||||
font_cache_sender: ipc_font_cache_sender,
|
|
||||||
parallel_flag: true,
|
parallel_flag: true,
|
||||||
generation: Cell::new(0),
|
generation: Cell::new(0),
|
||||||
outstanding_web_fonts: Arc::new(AtomicUsize::new(0)),
|
|
||||||
root_flow: RefCell::new(None),
|
root_flow: RefCell::new(None),
|
||||||
// Epoch starts at 1 because of the initial display list for epoch 0 that we send to WR
|
// Epoch starts at 1 because of the initial display list for epoch 0 that we send to WR
|
||||||
epoch: Cell::new(Epoch(1)),
|
epoch: Cell::new(Epoch(1)),
|
||||||
|
@ -685,53 +666,41 @@ impl LayoutThread {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Receives and dispatches messages from the script and constellation threads
|
|
||||||
fn handle_request(&mut self, request: Request) {
|
|
||||||
match request {
|
|
||||||
Request::FromFontCache => {
|
|
||||||
self.outstanding_web_fonts.fetch_sub(1, Ordering::SeqCst);
|
|
||||||
self.handle_web_font_loaded();
|
|
||||||
},
|
|
||||||
Request::FromPipeline(LayoutControlMsg::ExitNow) => self.exit_now(),
|
|
||||||
Request::FromPipeline(LayoutControlMsg::SetScrollStates(new_scroll_states)) => {
|
|
||||||
self.set_scroll_states(new_scroll_states);
|
|
||||||
},
|
|
||||||
Request::FromPipeline(LayoutControlMsg::PaintMetric(epoch, paint_time)) => {
|
|
||||||
self.paint_time_metrics.maybe_set_metric(epoch, paint_time);
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
fn load_all_web_fonts_from_stylesheet_with_guard(
|
fn load_all_web_fonts_from_stylesheet_with_guard(
|
||||||
&self,
|
&self,
|
||||||
stylesheet: &Stylesheet,
|
stylesheet: &Stylesheet,
|
||||||
guard: &SharedRwLockReadGuard,
|
guard: &SharedRwLockReadGuard,
|
||||||
) {
|
) {
|
||||||
// Find all font-face rules and notify the font cache of them.
|
if !stylesheet.is_effective_for_device(self.stylist.device(), guard) {
|
||||||
// GWTODO: Need to handle unloading web fonts.
|
return;
|
||||||
if stylesheet.is_effective_for_device(self.stylist.device(), guard) {
|
|
||||||
let newly_loading_font_count = self.font_context.add_all_web_fonts_from_stylesheet(
|
|
||||||
stylesheet,
|
|
||||||
guard,
|
|
||||||
self.stylist.device(),
|
|
||||||
&self.font_cache_sender,
|
|
||||||
self.debug.load_webfonts_synchronously,
|
|
||||||
);
|
|
||||||
|
|
||||||
if !self.debug.load_webfonts_synchronously {
|
|
||||||
self.outstanding_web_fonts
|
|
||||||
.fetch_add(newly_loading_font_count, Ordering::SeqCst);
|
|
||||||
} else if newly_loading_font_count > 0 {
|
|
||||||
self.handle_web_font_loaded();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
fn handle_web_font_loaded(&self) {
|
let locked_script_channel = Mutex::new(self.script_chan.clone());
|
||||||
self.font_context.invalidate_caches();
|
let pipeline_id = self.id;
|
||||||
self.script_chan
|
let web_font_finished_loading_callback = move |succeeded: bool| {
|
||||||
.send(ConstellationControlMsg::WebFontLoaded(self.id))
|
if succeeded {
|
||||||
.unwrap();
|
let _ = locked_script_channel
|
||||||
|
.lock()
|
||||||
|
.unwrap()
|
||||||
|
.send(ConstellationControlMsg::WebFontLoaded(pipeline_id));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Find all font-face rules and notify the FontContext of them.
|
||||||
|
// GWTODO: Need to handle unloading web fonts.
|
||||||
|
let newly_loading_font_count = self.font_context.add_all_web_fonts_from_stylesheet(
|
||||||
|
stylesheet,
|
||||||
|
guard,
|
||||||
|
self.stylist.device(),
|
||||||
|
Arc::new(web_font_finished_loading_callback) as WebFontLoadFinishedCallback,
|
||||||
|
self.debug.load_webfonts_synchronously,
|
||||||
|
);
|
||||||
|
|
||||||
|
if self.debug.load_webfonts_synchronously && newly_loading_font_count > 0 {
|
||||||
|
let _ = self
|
||||||
|
.script_chan
|
||||||
|
.send(ConstellationControlMsg::WebFontLoaded(self.id));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn try_get_layout_root<'dom>(&self, node: impl LayoutNode<'dom>) -> Option<FlowRef> {
|
fn try_get_layout_root<'dom>(&self, node: impl LayoutNode<'dom>) -> Option<FlowRef> {
|
||||||
|
|
|
@ -13,8 +13,7 @@ use std::collections::HashMap;
|
||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
use std::ops::{Deref, DerefMut};
|
use std::ops::{Deref, DerefMut};
|
||||||
use std::process;
|
use std::process;
|
||||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
use std::sync::Arc;
|
||||||
use std::sync::{Arc, Mutex};
|
|
||||||
|
|
||||||
use app_units::Au;
|
use app_units::Au;
|
||||||
use base::id::{BrowsingContextId, PipelineId};
|
use base::id::{BrowsingContextId, PipelineId};
|
||||||
|
@ -26,8 +25,8 @@ use fnv::FnvHashMap;
|
||||||
use fxhash::FxHashMap;
|
use fxhash::FxHashMap;
|
||||||
use gfx::font_cache_thread::FontCacheThread;
|
use gfx::font_cache_thread::FontCacheThread;
|
||||||
use gfx::font_context::{FontContext, FontContextWebFontMethods};
|
use gfx::font_context::{FontContext, FontContextWebFontMethods};
|
||||||
use ipc_channel::ipc::{self, IpcSender};
|
use gfx_traits::WebFontLoadFinishedCallback;
|
||||||
use ipc_channel::router::ROUTER;
|
use ipc_channel::ipc::IpcSender;
|
||||||
use layout::context::LayoutContext;
|
use layout::context::LayoutContext;
|
||||||
use layout::display_list::{DisplayList, WebRenderImageInfo};
|
use layout::display_list::{DisplayList, WebRenderImageInfo};
|
||||||
use layout::query::{
|
use layout::query::{
|
||||||
|
@ -43,7 +42,7 @@ use malloc_size_of::{MallocSizeOf, MallocSizeOfOps};
|
||||||
use metrics::{PaintTimeMetrics, ProfilerMetadataFactory};
|
use metrics::{PaintTimeMetrics, ProfilerMetadataFactory};
|
||||||
use net_traits::image_cache::{ImageCache, UsePlaceholder};
|
use net_traits::image_cache::{ImageCache, UsePlaceholder};
|
||||||
use net_traits::ResourceThreads;
|
use net_traits::ResourceThreads;
|
||||||
use parking_lot::RwLock;
|
use parking_lot::{Mutex, RwLock};
|
||||||
use profile_traits::mem::{Report, ReportKind};
|
use profile_traits::mem::{Report, ReportKind};
|
||||||
use profile_traits::path;
|
use profile_traits::path;
|
||||||
use profile_traits::time::{
|
use profile_traits::time::{
|
||||||
|
@ -107,9 +106,6 @@ pub struct LayoutThread {
|
||||||
/// Is the current reflow of an iframe, as opposed to a root window?
|
/// Is the current reflow of an iframe, as opposed to a root window?
|
||||||
is_iframe: bool,
|
is_iframe: bool,
|
||||||
|
|
||||||
/// The channel on which the font cache can send messages to us.
|
|
||||||
font_cache_sender: IpcSender<()>,
|
|
||||||
|
|
||||||
/// The channel on which messages can be sent to the constellation.
|
/// The channel on which messages can be sent to the constellation.
|
||||||
constellation_chan: IpcSender<ConstellationMsg>,
|
constellation_chan: IpcSender<ConstellationMsg>,
|
||||||
|
|
||||||
|
@ -132,9 +128,6 @@ pub struct LayoutThread {
|
||||||
/// This can be used to easily check for invalid stale data.
|
/// This can be used to easily check for invalid stale data.
|
||||||
generation: Cell<u32>,
|
generation: Cell<u32>,
|
||||||
|
|
||||||
/// The number of Web fonts that have been requested but not yet loaded.
|
|
||||||
outstanding_web_fonts: Arc<AtomicUsize>,
|
|
||||||
|
|
||||||
/// The box tree.
|
/// The box tree.
|
||||||
box_tree: RefCell<Option<Arc<BoxTree>>>,
|
box_tree: RefCell<Option<Arc<BoxTree>>>,
|
||||||
|
|
||||||
|
@ -228,12 +221,18 @@ impl Drop for ScriptReflowResult {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Layout for LayoutThread {
|
impl Layout for LayoutThread {
|
||||||
fn handle_constellation_msg(&mut self, msg: script_traits::LayoutControlMsg) {
|
fn handle_constellation_message(
|
||||||
self.handle_request(Request::FromPipeline(msg));
|
&mut self,
|
||||||
}
|
constellation_message: script_traits::LayoutControlMsg,
|
||||||
|
) {
|
||||||
fn handle_font_cache_msg(&mut self) {
|
match constellation_message {
|
||||||
self.handle_request(Request::FromFontCache);
|
LayoutControlMsg::SetScrollStates(new_scroll_states) => {
|
||||||
|
self.set_scroll_states(new_scroll_states);
|
||||||
|
},
|
||||||
|
LayoutControlMsg::PaintMetric(epoch, paint_time) => {
|
||||||
|
self.paint_time_metrics.maybe_set_metric(epoch, paint_time);
|
||||||
|
},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn device(&self) -> &Device {
|
fn device(&self) -> &Device {
|
||||||
|
@ -241,7 +240,7 @@ impl Layout for LayoutThread {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn waiting_for_web_fonts_to_load(&self) -> bool {
|
fn waiting_for_web_fonts_to_load(&self) -> bool {
|
||||||
self.outstanding_web_fonts.load(Ordering::SeqCst) != 0
|
self.font_context.web_fonts_still_loading() != 0
|
||||||
}
|
}
|
||||||
|
|
||||||
fn current_epoch(&self) -> Epoch {
|
fn current_epoch(&self) -> Epoch {
|
||||||
|
@ -469,11 +468,6 @@ impl Layout for LayoutThread {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
enum Request {
|
|
||||||
FromPipeline(LayoutControlMsg),
|
|
||||||
FromFontCache,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl LayoutThread {
|
impl LayoutThread {
|
||||||
fn new(
|
fn new(
|
||||||
id: PipelineId,
|
id: PipelineId,
|
||||||
|
@ -503,17 +497,6 @@ impl LayoutThread {
|
||||||
Box::new(LayoutFontMetricsProvider(font_context.clone())),
|
Box::new(LayoutFontMetricsProvider(font_context.clone())),
|
||||||
);
|
);
|
||||||
|
|
||||||
// Ask the router to proxy IPC messages from the font cache thread to layout.
|
|
||||||
let (ipc_font_cache_sender, ipc_font_cache_receiver) = ipc::channel().unwrap();
|
|
||||||
let cloned_script_chan = script_chan.clone();
|
|
||||||
ROUTER.add_route(
|
|
||||||
ipc_font_cache_receiver.to_opaque(),
|
|
||||||
Box::new(move |_message| {
|
|
||||||
let _ =
|
|
||||||
cloned_script_chan.send(ConstellationControlMsg::ForLayoutFromFontCache(id));
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
LayoutThread {
|
LayoutThread {
|
||||||
id,
|
id,
|
||||||
url,
|
url,
|
||||||
|
@ -525,9 +508,7 @@ impl LayoutThread {
|
||||||
image_cache,
|
image_cache,
|
||||||
font_context,
|
font_context,
|
||||||
first_reflow: Cell::new(true),
|
first_reflow: Cell::new(true),
|
||||||
font_cache_sender: ipc_font_cache_sender,
|
|
||||||
generation: Cell::new(0),
|
generation: Cell::new(0),
|
||||||
outstanding_web_fonts: Arc::new(AtomicUsize::new(0)),
|
|
||||||
box_tree: Default::default(),
|
box_tree: Default::default(),
|
||||||
fragment_tree: Default::default(),
|
fragment_tree: Default::default(),
|
||||||
// Epoch starts at 1 because of the initial display list for epoch 0 that we send to WR
|
// Epoch starts at 1 because of the initial display list for epoch 0 that we send to WR
|
||||||
|
@ -601,55 +582,42 @@ impl LayoutThread {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Receives and dispatches messages from the script and constellation threads
|
|
||||||
fn handle_request(&mut self, request: Request) {
|
|
||||||
match request {
|
|
||||||
Request::FromPipeline(LayoutControlMsg::SetScrollStates(new_scroll_states)) => {
|
|
||||||
self.set_scroll_states(new_scroll_states);
|
|
||||||
},
|
|
||||||
Request::FromPipeline(LayoutControlMsg::ExitNow) => {},
|
|
||||||
Request::FromPipeline(LayoutControlMsg::PaintMetric(epoch, paint_time)) => {
|
|
||||||
self.paint_time_metrics.maybe_set_metric(epoch, paint_time);
|
|
||||||
},
|
|
||||||
Request::FromFontCache => {
|
|
||||||
self.outstanding_web_fonts.fetch_sub(1, Ordering::SeqCst);
|
|
||||||
self.handle_web_font_loaded();
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
fn load_all_web_fonts_from_stylesheet_with_guard(
|
fn load_all_web_fonts_from_stylesheet_with_guard(
|
||||||
&self,
|
&self,
|
||||||
stylesheet: &Stylesheet,
|
stylesheet: &Stylesheet,
|
||||||
guard: &SharedRwLockReadGuard,
|
guard: &SharedRwLockReadGuard,
|
||||||
) {
|
) {
|
||||||
|
if !stylesheet.is_effective_for_device(self.stylist.device(), guard) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let locked_script_channel = Mutex::new(self.script_chan.clone());
|
||||||
|
let pipeline_id = self.id;
|
||||||
|
let web_font_finished_loading_callback = move |succeeded: bool| {
|
||||||
|
if succeeded {
|
||||||
|
let _ = locked_script_channel
|
||||||
|
.lock()
|
||||||
|
.send(ConstellationControlMsg::WebFontLoaded(pipeline_id));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// Find all font-face rules and notify the font cache of them.
|
// Find all font-face rules and notify the font cache of them.
|
||||||
// GWTODO: Need to handle unloading web fonts.
|
// GWTODO: Need to handle unloading web fonts.
|
||||||
if stylesheet.is_effective_for_device(self.stylist.device(), guard) {
|
let newly_loading_font_count = self.font_context.add_all_web_fonts_from_stylesheet(
|
||||||
let newly_loading_font_count = self.font_context.add_all_web_fonts_from_stylesheet(
|
stylesheet,
|
||||||
stylesheet,
|
guard,
|
||||||
guard,
|
self.stylist.device(),
|
||||||
self.stylist.device(),
|
Arc::new(web_font_finished_loading_callback) as WebFontLoadFinishedCallback,
|
||||||
&self.font_cache_sender,
|
self.debug.load_webfonts_synchronously,
|
||||||
self.debug.load_webfonts_synchronously,
|
);
|
||||||
);
|
|
||||||
|
|
||||||
if !self.debug.load_webfonts_synchronously {
|
if self.debug.load_webfonts_synchronously && newly_loading_font_count > 0 {
|
||||||
self.outstanding_web_fonts
|
let _ = self
|
||||||
.fetch_add(newly_loading_font_count, Ordering::SeqCst);
|
.script_chan
|
||||||
} else if newly_loading_font_count > 0 {
|
.send(ConstellationControlMsg::WebFontLoaded(self.id));
|
||||||
self.handle_web_font_loaded();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_web_font_loaded(&self) {
|
|
||||||
self.font_context.invalidate_caches();
|
|
||||||
self.script_chan
|
|
||||||
.send(ConstellationControlMsg::WebFontLoaded(self.id))
|
|
||||||
.unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The high-level routine that performs layout.
|
/// The high-level routine that performs layout.
|
||||||
fn handle_reflow(&mut self, data: &mut ScriptReflowResult) {
|
fn handle_reflow(&mut self, data: &mut ScriptReflowResult) {
|
||||||
let document = unsafe { ServoLayoutNode::new(&data.document) };
|
let document = unsafe { ServoLayoutNode::new(&data.document) };
|
||||||
|
@ -845,7 +813,7 @@ impl LayoutThread {
|
||||||
self.first_reflow.set(false);
|
self.first_reflow.set(false);
|
||||||
|
|
||||||
data.result.borrow_mut().as_mut().unwrap().pending_images =
|
data.result.borrow_mut().as_mut().unwrap().pending_images =
|
||||||
std::mem::take(&mut *layout_context.pending_images.lock().unwrap());
|
std::mem::take(&mut *layout_context.pending_images.lock());
|
||||||
if let ReflowGoal::UpdateScrollNode(scroll_state) = data.reflow_goal {
|
if let ReflowGoal::UpdateScrollNode(scroll_state) = data.reflow_goal {
|
||||||
self.update_scroll_node_state(&scroll_state);
|
self.update_scroll_node_state(&scroll_state);
|
||||||
}
|
}
|
||||||
|
|
|
@ -2124,7 +2124,6 @@ impl ScriptThread {
|
||||||
MediaSessionAction(..) => None,
|
MediaSessionAction(..) => None,
|
||||||
SetWebGPUPort(..) => None,
|
SetWebGPUPort(..) => None,
|
||||||
ForLayoutFromConstellation(_, id) => Some(id),
|
ForLayoutFromConstellation(_, id) => Some(id),
|
||||||
ForLayoutFromFontCache(id) => Some(id),
|
|
||||||
},
|
},
|
||||||
MixedMessage::FromDevtools(_) => None,
|
MixedMessage::FromDevtools(_) => None,
|
||||||
MixedMessage::FromScript(ref inner_msg) => match *inner_msg {
|
MixedMessage::FromScript(ref inner_msg) => match *inner_msg {
|
||||||
|
@ -2358,28 +2357,21 @@ impl ScriptThread {
|
||||||
panic!("should have handled {:?} already", msg)
|
panic!("should have handled {:?} already", msg)
|
||||||
},
|
},
|
||||||
ConstellationControlMsg::ForLayoutFromConstellation(msg, pipeline_id) => {
|
ConstellationControlMsg::ForLayoutFromConstellation(msg, pipeline_id) => {
|
||||||
self.handle_layout_message(msg, pipeline_id)
|
self.handle_layout_message_from_constellation(msg, pipeline_id)
|
||||||
},
|
|
||||||
ConstellationControlMsg::ForLayoutFromFontCache(pipeline_id) => {
|
|
||||||
self.handle_font_cache(pipeline_id)
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_layout_message(&self, msg: LayoutControlMsg, pipeline_id: PipelineId) {
|
fn handle_layout_message_from_constellation(
|
||||||
|
&self,
|
||||||
|
msg: LayoutControlMsg,
|
||||||
|
pipeline_id: PipelineId,
|
||||||
|
) {
|
||||||
let Some(window) = self.documents.borrow().find_window(pipeline_id) else {
|
let Some(window) = self.documents.borrow().find_window(pipeline_id) else {
|
||||||
warn!("Received layout message pipeline {pipeline_id} closed: {msg:?}.");
|
warn!("Received layout message pipeline {pipeline_id} closed: {msg:?}.");
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
window.layout_mut().handle_constellation_msg(msg);
|
window.layout_mut().handle_constellation_message(msg);
|
||||||
}
|
|
||||||
|
|
||||||
fn handle_font_cache(&self, pipeline_id: PipelineId) {
|
|
||||||
let Some(window) = self.documents.borrow().find_window(pipeline_id) else {
|
|
||||||
warn!("Received font cache message pipeline {pipeline_id} closed.");
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
window.layout_mut().handle_font_cache_msg();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_msg_from_webgpu_server(&self, msg: WebGPUMsg) {
|
fn handle_msg_from_webgpu_server(&self, msg: WebGPUMsg) {
|
||||||
|
|
|
@ -4,6 +4,8 @@
|
||||||
|
|
||||||
#![deny(unsafe_code)]
|
#![deny(unsafe_code)]
|
||||||
|
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
use malloc_size_of_derive::MallocSizeOf;
|
use malloc_size_of_derive::MallocSizeOf;
|
||||||
use range::{int_range_index, RangeIndex};
|
use range::{int_range_index, RangeIndex};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
@ -14,3 +16,5 @@ int_range_index! {
|
||||||
/// the middle of a glyph.
|
/// the middle of a glyph.
|
||||||
struct ByteIndex(isize)
|
struct ByteIndex(isize)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub type WebFontLoadFinishedCallback = Arc<dyn Fn(bool) + Send + Sync + 'static>;
|
||||||
|
|
|
@ -113,8 +113,6 @@ impl UntrustedNodeAddress {
|
||||||
/// Messages sent to layout from the constellation and/or compositor.
|
/// Messages sent to layout from the constellation and/or compositor.
|
||||||
#[derive(Debug, Deserialize, Serialize)]
|
#[derive(Debug, Deserialize, Serialize)]
|
||||||
pub enum LayoutControlMsg {
|
pub enum LayoutControlMsg {
|
||||||
/// Requests that this layout clean up before exit.
|
|
||||||
ExitNow,
|
|
||||||
/// Tells layout about the new scrolling offsets of each scrollable stacking context.
|
/// Tells layout about the new scrolling offsets of each scrollable stacking context.
|
||||||
SetScrollStates(Vec<ScrollState>),
|
SetScrollStates(Vec<ScrollState>),
|
||||||
/// Send the paint time for a specific epoch to layout.
|
/// Send the paint time for a specific epoch to layout.
|
||||||
|
@ -395,8 +393,6 @@ pub enum ConstellationControlMsg {
|
||||||
SetWebGPUPort(IpcReceiver<WebGPUMsg>),
|
SetWebGPUPort(IpcReceiver<WebGPUMsg>),
|
||||||
/// A mesage for a layout from the constellation.
|
/// A mesage for a layout from the constellation.
|
||||||
ForLayoutFromConstellation(LayoutControlMsg, PipelineId),
|
ForLayoutFromConstellation(LayoutControlMsg, PipelineId),
|
||||||
/// A message for a layout from the font cache.
|
|
||||||
ForLayoutFromFontCache(PipelineId),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Debug for ConstellationControlMsg {
|
impl fmt::Debug for ConstellationControlMsg {
|
||||||
|
@ -436,7 +432,6 @@ impl fmt::Debug for ConstellationControlMsg {
|
||||||
MediaSessionAction(..) => "MediaSessionAction",
|
MediaSessionAction(..) => "MediaSessionAction",
|
||||||
SetWebGPUPort(..) => "SetWebGPUPort",
|
SetWebGPUPort(..) => "SetWebGPUPort",
|
||||||
ForLayoutFromConstellation(..) => "ForLayoutFromConstellation",
|
ForLayoutFromConstellation(..) => "ForLayoutFromConstellation",
|
||||||
ForLayoutFromFontCache(..) => "ForLayoutFromFontCache",
|
|
||||||
};
|
};
|
||||||
write!(formatter, "ConstellationControlMsg::{}", variant)
|
write!(formatter, "ConstellationControlMsg::{}", variant)
|
||||||
}
|
}
|
||||||
|
|
|
@ -179,10 +179,7 @@ pub trait LayoutFactory: Send + Sync {
|
||||||
|
|
||||||
pub trait Layout {
|
pub trait Layout {
|
||||||
/// Handle a single message from the Constellation.
|
/// Handle a single message from the Constellation.
|
||||||
fn handle_constellation_msg(&mut self, msg: LayoutControlMsg);
|
fn handle_constellation_message(&mut self, msg: LayoutControlMsg);
|
||||||
|
|
||||||
/// Handle a a single mesasge from the FontCacheThread.
|
|
||||||
fn handle_font_cache_msg(&mut self);
|
|
||||||
|
|
||||||
/// Get a reference to this Layout's Stylo `Device` used to handle media queries and
|
/// Get a reference to this Layout's Stylo `Device` used to handle media queries and
|
||||||
/// resolve font metrics.
|
/// resolve font metrics.
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue