Lazily initialize canvas paint thread in constellation (#37765)

This PR moves canvas paint thread initialization to constellation. This
allows us to lazily initialize it on first create canvas request (like
we do for webgpu). If we didn't started canvas paint thread we also do
not need to wait for it's teardown.

Per https://chromestatus.com/metrics/webfeature/timeline/popularity/201
~30% of websites still use 2d canvas.

Testing: Existing WPT tests

Signed-off-by: sagudev <16504129+sagudev@users.noreply.github.com>
This commit is contained in:
sagudev 2025-06-28 19:29:11 +02:00 committed by GitHub
parent ee7cb80213
commit 5311beb34a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 34 additions and 32 deletions

2
Cargo.lock generated
View file

@ -1454,6 +1454,7 @@ dependencies = [
"backtrace",
"base",
"bluetooth_traits",
"canvas",
"canvas_traits",
"compositing_traits",
"constellation_traits",
@ -4846,7 +4847,6 @@ dependencies = [
"bincode",
"bluetooth",
"bluetooth_traits",
"canvas",
"canvas_traits",
"compositing",
"compositing_traits",

View file

@ -23,6 +23,7 @@ background_hang_monitor_api = { workspace = true }
backtrace = { workspace = true }
base = { workspace = true }
bluetooth_traits = { workspace = true, optional = true }
canvas = { path = "../canvas" }
canvas_traits = { workspace = true }
compositing_traits = { workspace = true }
constellation_traits = { workspace = true }

View file

@ -85,6 +85,7 @@
//! See <https://github.com/servo/servo/issues/14704>
use std::borrow::ToOwned;
use std::cell::OnceCell;
use std::collections::hash_map::Entry;
use std::collections::{HashMap, HashSet, VecDeque};
use std::marker::PhantomData;
@ -105,6 +106,7 @@ use base::id::{
};
#[cfg(feature = "bluetooth")]
use bluetooth_traits::BluetoothRequest;
use canvas::canvas_paint_thread::CanvasPaintThread;
use canvas_traits::ConstellationCanvasMsg;
use canvas_traits::canvas::{CanvasId, CanvasMsg};
use canvas_traits::webgl::WebGLThreads;
@ -425,10 +427,8 @@ pub struct Constellation<STF, SWF> {
/// The XR device registry
webxr_registry: Option<webxr_api::Registry>,
/// A channel through which messages can be sent to the canvas paint thread.
canvas_sender: Sender<ConstellationCanvasMsg>,
canvas_ipc_sender: IpcSender<CanvasMsg>,
/// Lazily initialized channels for canvas paint thread.
canvas: OnceCell<(Sender<ConstellationCanvasMsg>, IpcSender<CanvasMsg>)>,
/// Navigation requests from script awaiting approval from the embedder.
pending_approval_navigations: PendingApprovalNavigations,
@ -584,8 +584,6 @@ where
random_pipeline_closure_probability: Option<f32>,
random_pipeline_closure_seed: Option<usize>,
hard_fail: bool,
canvas_create_sender: Sender<ConstellationCanvasMsg>,
canvas_ipc_sender: IpcSender<CanvasMsg>,
) -> Sender<EmbedderToConstellationMessage> {
let (compositor_sender, compositor_receiver) = unbounded();
@ -707,8 +705,7 @@ where
}),
webgl_threads: state.webgl_threads,
webxr_registry: state.webxr_registry,
canvas_sender: canvas_create_sender,
canvas_ipc_sender,
canvas: OnceCell::new(),
pending_approval_navigations: HashMap::new(),
pressed_mouse_buttons: 0,
active_keyboard_modifiers: Modifiers::empty(),
@ -2665,14 +2662,16 @@ where
}
}
let canvas_exit_receiver = if let Some((canvas_sender, _)) = self.canvas.get() {
debug!("Exiting Canvas Paint thread.");
let (canvas_exit_sender, canvas_exit_receiver) = unbounded();
if let Err(e) = self
.canvas_sender
.send(ConstellationCanvasMsg::Exit(canvas_exit_sender))
{
if let Err(e) = canvas_sender.send(ConstellationCanvasMsg::Exit(canvas_exit_sender)) {
warn!("Exit Canvas Paint thread failed ({})", e);
}
Some(canvas_exit_receiver)
} else {
None
};
debug!("Exiting WebGPU threads.");
#[cfg(feature = "webgpu")]
@ -2714,7 +2713,9 @@ where
// Wait for the canvas thread to exit before shutting down the font service, as
// canvas might still be using the system font service before shutting down.
if let Some(canvas_exit_receiver) = canvas_exit_receiver {
let _ = canvas_exit_receiver.recv();
}
debug!("Exiting the system font service thread.");
self.system_font_service.exit();
@ -4493,8 +4494,11 @@ where
response_sender: IpcSender<(IpcSender<CanvasMsg>, CanvasId, ImageKey)>,
) {
let (canvas_data_sender, canvas_data_receiver) = unbounded();
let (canvas_sender, canvas_ipc_sender) = self
.canvas
.get_or_init(|| self.create_canvas_paint_thread());
if let Err(e) = self.canvas_sender.send(ConstellationCanvasMsg::Create {
if let Err(e) = canvas_sender.send(ConstellationCanvasMsg::Create {
sender: canvas_data_sender,
size,
}) {
@ -4504,8 +4508,7 @@ where
Ok(canvas_data) => canvas_data,
Err(e) => return warn!("Create canvas paint thread id response failed ({})", e),
};
if let Err(e) = response_sender.send((self.canvas_ipc_sender.clone(), canvas_id, image_key))
{
if let Err(e) = response_sender.send((canvas_ipc_sender.clone(), canvas_id, image_key)) {
warn!("Create canvas paint thread response failed ({})", e);
}
}
@ -5745,4 +5748,12 @@ where
warn!("Could not sent paint metric event to pipeline: {pipeline_id:?}: {error:?}");
}
}
fn create_canvas_paint_thread(&self) -> (Sender<ConstellationCanvasMsg>, IpcSender<CanvasMsg>) {
CanvasPaintThread::start(
self.compositor_proxy.cross_process_compositor_api.clone(),
self.system_font_service.clone(),
self.public_resource_threads.clone(),
)
}
}

View file

@ -69,7 +69,6 @@ base = { workspace = true }
bincode = { workspace = true }
bluetooth = { path = "../bluetooth", optional = true }
bluetooth_traits = { workspace = true, optional = true }
canvas = { path = "../canvas" }
webgl = { path = "../webgl", default-features = false }
canvas_traits = { workspace = true }
compositing = { path = "../compositing" }

View file

@ -39,7 +39,6 @@ use base::id::{PipelineNamespace, PipelineNamespaceId};
use bluetooth::BluetoothThreadFactory;
#[cfg(feature = "bluetooth")]
use bluetooth_traits::BluetoothRequest;
use canvas::canvas_paint_thread::CanvasPaintThread;
use canvas_traits::webgl::{GlType, WebGLThreads};
use clipboard_delegate::StringRequest;
pub use compositing::WebRenderDebugOption;
@ -115,7 +114,7 @@ use webview::WebViewInner;
#[cfg(feature = "webxr")]
pub use webxr;
pub use {
background_hang_monitor, base, canvas, canvas_traits, devtools, devtools_traits, euclid, fonts,
background_hang_monitor, base, canvas_traits, devtools, devtools_traits, euclid, fonts,
ipc_channel, layout_api, media, net, net_traits, profile, profile_traits, script,
script_traits, servo_config as config, servo_config, servo_geometry, servo_url, style,
style_traits, webrender_api,
@ -1095,12 +1094,6 @@ fn create_constellation(
.to_proxy(),
);
let (canvas_create_sender, canvas_ipc_sender) = CanvasPaintThread::start(
compositor_proxy.cross_process_compositor_api.clone(),
system_font_service.clone(),
public_resource_threads.clone(),
);
let initial_state = InitialConstellationState {
compositor_proxy,
embedder_proxy,
@ -1133,8 +1126,6 @@ fn create_constellation(
opts.random_pipeline_closure_probability,
opts.random_pipeline_closure_seed,
opts.hard_fail,
canvas_create_sender,
canvas_ipc_sender,
)
}