compositing: Move image output and shutdown management out of the compositor (#35538)

This is a step toward the renderer-per-WebView goal. It moves various
details out of `IOCompositor`.

- Image output: This is moved to servoshell as now applications can
  access the image contents of a `WebView` via
  `RenderingContext::read_to_image`. Most options for this are moved to
  `ServoShellPreferences` apart from `wait_for_stable_image` as this
  requires a specific kind of coordination in the `ScriptThread` that is
  also very expensive. Instead, paint is now simply delayed until a
  stable image is reached and `WebView::paint()` returns a boolean.
  Maybe this can be revisited in the future.
- Shutdown: Shutdown is now managed by libservo itself. Shutdown state
  is shared between the compositor and `Servo` instance. In the future,
  this sharing might be unecessary.
- `CompositeTarget` has been removed entirely. This no longer needs to
   be passed when creating a Servo instance.

Signed-off-by: Martin Robinson <mrobinson@igalia.com>
Co-authored-by: Ngo Iok Ui (Wu Yu Wei) <yuweiwu@pm.me>
This commit is contained in:
Martin Robinson 2025-02-20 19:27:49 +01:00 committed by GitHub
parent 7d33e72bfc
commit 54b5c7b632
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
25 changed files with 233 additions and 270 deletions

View file

@ -26,7 +26,6 @@ embedder_traits = { workspace = true }
euclid = { workspace = true }
fnv = { workspace = true }
gleam = { workspace = true }
image = { workspace = true }
ipc-channel = { workspace = true }
libc = { workspace = true }
log = { workspace = true }

View file

@ -22,11 +22,10 @@ use compositing_traits::{
use crossbeam_channel::Sender;
use embedder_traits::{
Cursor, InputEvent, MouseButton, MouseButtonAction, MouseButtonEvent, MouseMoveEvent,
TouchAction, TouchEvent, TouchEventType, TouchId,
ShutdownState, TouchAction, TouchEvent, TouchEventType, TouchId,
};
use euclid::{Point2D, Rect, Scale, Size2D, Transform3D, Vector2D};
use fnv::{FnvHashMap, FnvHashSet};
use image::{DynamicImage, ImageFormat};
use ipc_channel::ipc::{self, IpcSharedMemory};
use libc::c_void;
use log::{debug, error, info, trace, warn};
@ -37,6 +36,7 @@ use script_traits::{
AnimationState, AnimationTickType, EventResult, ScriptThreadMessage, ScrollState,
WindowSizeData, WindowSizeType,
};
use servo_config::opts;
use servo_geometry::DeviceIndependentPixel;
use style_traits::{CSSPixel, PinchZoomFactor};
use webrender::{CaptureBits, RenderApi, Transaction};
@ -102,8 +102,8 @@ pub struct ServoRenderer {
webviews: WebViewManager<WebView>,
/// Tracks whether we are in the process of shutting down, or have shut down and should close
/// the compositor.
shutdown_state: ShutdownState,
/// the compositor. This is shared with the `Servo` instance.
shutdown_state: Rc<Cell<ShutdownState>>,
/// The port on which we receive messages.
compositor_receiver: CompositorReceiver,
@ -120,9 +120,6 @@ pub struct ServoRenderer {
/// The GL bindings for webrender
webrender_gl: Rc<dyn gleam::gl::Gl>,
/// True to exit after page load ('-x').
exit_after_load: bool,
/// The string representing the version of Servo that is running. This is used to tag
/// WebRender capture output.
version_string: String,
@ -153,9 +150,6 @@ pub struct IOCompositor {
/// "Desktop-style" zoom that resizes the viewport to fit the window.
page_zoom: Scale<f32, CSSPixel, DeviceIndependentPixel>,
/// The type of composition to perform
composite_target: CompositeTarget,
/// Tracks whether or not the view needs to be repainted.
needs_repaint: Cell<RepaintReason>,
@ -250,13 +244,6 @@ bitflags! {
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum ShutdownState {
NotShuttingDown,
ShuttingDown,
FinishedShuttingDown,
}
struct PipelineDetails {
/// The pipeline associated with this PipelineDetails object.
pipeline: Option<CompositionPipeline>,
@ -324,38 +311,22 @@ impl PipelineDetails {
}
}
#[derive(Clone, Debug, PartialEq)]
pub enum CompositeTarget {
/// Draw to a OpenGL framebuffer object that will then be used by the compositor to composite
/// to [`RenderingContext::framebuffer_object`]
ContextFbo,
/// Draw to an uncompressed image in shared memory.
SharedMemory,
/// Draw to a PNG file on disk, then exit the browser (for reftests).
PngFile(Rc<String>),
}
impl IOCompositor {
pub fn new(
window: Rc<dyn WindowMethods>,
state: InitialCompositorState,
composite_target: CompositeTarget,
exit_after_load: bool,
convert_mouse_to_touch: bool,
version_string: String,
) -> Self {
let compositor = IOCompositor {
global: ServoRenderer {
shutdown_state: ShutdownState::NotShuttingDown,
shutdown_state: state.shutdown_state,
webviews: WebViewManager::default(),
compositor_receiver: state.receiver,
constellation_sender: state.constellation_chan,
time_profiler_chan: state.time_profiler_chan,
webrender_api: state.webrender_api,
webrender_gl: state.webrender_gl,
exit_after_load,
version_string,
#[cfg(feature = "webxr")]
webxr_main_thread: state.webxr_main_thread,
@ -366,7 +337,6 @@ impl IOCompositor {
needs_repaint: Cell::default(),
touch_handler: TouchHandler::new(),
pending_scroll_zoom_events: Vec::new(),
composite_target,
page_zoom: Scale::new(1.0),
viewport_zoom: PinchZoomFactor::new(1.0),
min_viewport_zoom: Some(PinchZoomFactor::new(1.0)),
@ -394,7 +364,7 @@ impl IOCompositor {
}
pub fn shutdown_state(&self) -> ShutdownState {
self.global.shutdown_state
self.global.shutdown_state.get()
}
pub fn deinit(&mut self) {
@ -441,27 +411,7 @@ impl IOCompositor {
}
}
pub fn start_shutting_down(&mut self) {
if self.global.shutdown_state != ShutdownState::NotShuttingDown {
warn!("Requested shutdown while already shutting down");
return;
}
debug!("Compositor sending Exit message to Constellation");
if let Err(e) = self
.global
.constellation_sender
.send(ConstellationMsg::Exit)
{
warn!("Sending exit message to constellation failed ({:?}).", e);
}
self.global.shutdown_state = ShutdownState::ShuttingDown;
}
fn finish_shutting_down(&mut self) {
debug!("Compositor received message that constellation shutdown is complete");
pub fn finish_shutting_down(&mut self) {
// Drain compositor port, sometimes messages contain channels that are blocking
// another thread from finishing (i.e. SetFrameTree).
while self
@ -478,14 +428,12 @@ impl IOCompositor {
.send(profile_time::ProfilerMsg::Exit(sender));
let _ = receiver.recv();
}
self.global.shutdown_state = ShutdownState::FinishedShuttingDown;
}
fn handle_browser_message(&mut self, msg: CompositorMsg) {
trace_msg_from_constellation!(msg, "{msg:?}");
match self.global.shutdown_state {
match self.shutdown_state() {
ShutdownState::NotShuttingDown => {},
ShutdownState::ShuttingDown => {
self.handle_browser_message_while_shutting_down(msg);
@ -498,11 +446,6 @@ impl IOCompositor {
}
match msg {
CompositorMsg::ShutdownComplete => {
error!("Received `ShutdownComplete` while not shutting down.");
self.finish_shutting_down();
},
CompositorMsg::ChangeRunningAnimationsState(pipeline_id, animation_state) => {
self.change_running_animations_state(pipeline_id, animation_state);
},
@ -521,7 +464,7 @@ impl IOCompositor {
},
CompositorMsg::CreatePng(page_rect, reply) => {
let res = self.composite_specific_target(CompositeTarget::SharedMemory, page_rect);
let res = self.render_to_shared_memory(page_rect);
if let Err(ref e) = res {
info!("Error retrieving PNG: {:?}", e);
}
@ -570,10 +513,7 @@ impl IOCompositor {
},
CompositorMsg::LoadComplete(_) => {
// If we're painting in headless mode, schedule a recomposite.
if matches!(self.composite_target, CompositeTarget::PngFile(_)) ||
self.global.exit_after_load
{
if opts::get().wait_for_stable_image {
self.set_needs_repaint(RepaintReason::ReadyForScreenshot);
}
},
@ -850,9 +790,6 @@ impl IOCompositor {
/// compositor no longer does any WebRender frame generation.
fn handle_browser_message_while_shutting_down(&mut self, msg: CompositorMsg) {
match msg {
CompositorMsg::ShutdownComplete => {
self.finish_shutting_down();
},
CompositorMsg::PipelineExited(pipeline_id, sender) => {
debug!("Compositor got pipeline exited: {:?}", pipeline_id);
self.remove_pipeline_root_layer(pipeline_id);
@ -1299,7 +1236,7 @@ impl IOCompositor {
}
pub fn on_rendering_context_resized(&mut self) -> bool {
if self.global.shutdown_state != ShutdownState::NotShuttingDown {
if self.shutdown_state() != ShutdownState::NotShuttingDown {
return false;
}
@ -1352,7 +1289,7 @@ impl IOCompositor {
}
pub fn on_input_event(&mut self, event: InputEvent) {
if self.global.shutdown_state != ShutdownState::NotShuttingDown {
if self.shutdown_state() != ShutdownState::NotShuttingDown {
return;
}
@ -1465,7 +1402,7 @@ impl IOCompositor {
}
pub fn on_touch_event(&mut self, event: TouchEvent) {
if self.global.shutdown_state != ShutdownState::NotShuttingDown {
if self.shutdown_state() != ShutdownState::NotShuttingDown {
return;
}
@ -1605,7 +1542,7 @@ impl IOCompositor {
cursor: DeviceIntPoint,
event_type: TouchEventType,
) {
if self.global.shutdown_state != ShutdownState::NotShuttingDown {
if self.shutdown_state() != ShutdownState::NotShuttingDown {
return;
}
@ -1822,9 +1759,6 @@ impl IOCompositor {
}
fn hidpi_factor(&self) -> Scale<f32, DeviceIndependentPixel, DevicePixel> {
if matches!(self.composite_target, CompositeTarget::PngFile(_)) {
return Scale::new(1.0);
}
self.embedder_coordinates.hidpi_factor
}
@ -1839,7 +1773,7 @@ impl IOCompositor {
}
pub fn on_zoom_reset_window_event(&mut self) {
if self.global.shutdown_state != ShutdownState::NotShuttingDown {
if self.shutdown_state() != ShutdownState::NotShuttingDown {
return;
}
@ -1848,7 +1782,7 @@ impl IOCompositor {
}
pub fn on_zoom_window_event(&mut self, magnification: f32) {
if self.global.shutdown_state != ShutdownState::NotShuttingDown {
if self.shutdown_state() != ShutdownState::NotShuttingDown {
return;
}
@ -1871,7 +1805,7 @@ impl IOCompositor {
/// Simulate a pinch zoom
pub fn on_pinch_zoom_window_event(&mut self, magnification: f32) {
if self.global.shutdown_state != ShutdownState::NotShuttingDown {
if self.shutdown_state() != ShutdownState::NotShuttingDown {
return;
}
@ -1985,10 +1919,12 @@ impl IOCompositor {
}
}
pub fn composite(&mut self) {
if let Err(error) = self.composite_specific_target(self.composite_target.clone(), None) {
warn!("Unable to composite: {error:?}");
return;
/// Render the WebRender scene to the active `RenderingContext`. If successful, trigger
/// the next round of animations.
pub fn render(&mut self) -> bool {
if let Err(error) = self.render_inner() {
warn!("Unable to render: {error:?}");
return false;
}
// We've painted the default target, which means that from the embedder's perspective,
@ -1998,28 +1934,54 @@ impl IOCompositor {
// Queue up any subsequent paints for animations.
self.process_animations(true);
if matches!(self.composite_target, CompositeTarget::PngFile(_)) ||
self.global.exit_after_load
{
println!("Shutting down the Constellation after generating an output file or exit flag specified");
self.start_shutting_down();
}
true
}
/// Render the WebRender scene to the shared memory, without updating other state of this
/// [`IOCompositor`]. If succesful return the output image in shared memory.
fn render_to_shared_memory(
&mut self,
page_rect: Option<Rect<f32, CSSPixel>>,
) -> Result<Option<Image>, UnableToComposite> {
self.render_inner()?;
let size = self.embedder_coordinates.framebuffer.to_u32();
let (x, y, width, height) = if let Some(rect) = page_rect {
let rect = self.device_pixels_per_page_pixel().transform_rect(&rect);
let x = rect.origin.x as i32;
// We need to convert to the bottom-left origin coordinate
// system used by OpenGL
let y = (size.height as f32 - rect.origin.y - rect.size.height) as i32;
let w = rect.size.width as u32;
let h = rect.size.height as u32;
(x, y, w, h)
} else {
(0, 0, size.width, size.height)
};
Ok(self
.rendering_context
.read_to_image(Rect::new(
Point2D::new(x as u32, y as u32),
Size2D::new(width, height),
))
.map(|image| Image {
width: image.width(),
height: image.height(),
format: PixelFormat::RGBA8,
bytes: ipc::IpcSharedMemory::from_bytes(&image),
id: None,
cors_status: CorsStatus::Safe,
}))
}
/// Composite to the given target if any, or the current target otherwise.
/// Returns Ok if composition was performed or Err if it was not possible to composite for some
/// reason. When the target is [CompositeTarget::SharedMemory], the image is read back from the
/// GPU and returned as Ok(Some(png::Image)), otherwise we return Ok(None).
#[cfg_attr(
feature = "tracing",
tracing::instrument(skip_all, fields(servo_profiling = true), level = "trace")
)]
fn composite_specific_target(
&mut self,
target: CompositeTarget,
page_rect: Option<Rect<f32, CSSPixel>>,
) -> Result<Option<Image>, UnableToComposite> {
let size = self.embedder_coordinates.framebuffer.to_u32();
fn render_inner(&mut self) -> Result<(), UnableToComposite> {
if let Err(err) = self.rendering_context.make_current() {
warn!("Failed to make the rendering context current: {:?}", err);
}
@ -2029,12 +1991,7 @@ impl IOCompositor {
webrender.update();
}
let wait_for_stable_image = matches!(
target,
CompositeTarget::SharedMemory | CompositeTarget::PngFile(_)
) || self.global.exit_after_load;
if wait_for_stable_image {
if opts::get().wait_for_stable_image {
// The current image may be ready to output. However, if there are animations active,
// tick those instead and continue waiting for the image output to be stable AND
// all active animations to complete.
@ -2071,64 +2028,7 @@ impl IOCompositor {
);
self.send_pending_paint_metrics_messages_after_composite();
let (x, y, width, height) = if let Some(rect) = page_rect {
let rect = self.device_pixels_per_page_pixel().transform_rect(&rect);
let x = rect.origin.x as i32;
// We need to convert to the bottom-left origin coordinate
// system used by OpenGL
let y = (size.height as f32 - rect.origin.y - rect.size.height) as i32;
let w = rect.size.width as u32;
let h = rect.size.height as u32;
(x, y, w, h)
} else {
(0, 0, size.width, size.height)
};
let rv = match target {
CompositeTarget::ContextFbo => None,
CompositeTarget::SharedMemory => self
.rendering_context
.read_to_image(Rect::new(
Point2D::new(x as u32, y as u32),
Size2D::new(width, height),
))
.map(|image| Image {
width: image.width(),
height: image.height(),
format: PixelFormat::RGBA8,
bytes: ipc::IpcSharedMemory::from_bytes(&image),
id: None,
cors_status: CorsStatus::Safe,
}),
CompositeTarget::PngFile(path) => {
time_profile!(
ProfilerCategory::ImageSaving,
None,
self.global.time_profiler_chan.clone(),
|| match File::create(&*path) {
Ok(mut file) => {
if let Some(image) = self.rendering_context.read_to_image(Rect::new(
Point2D::new(x as u32, y as u32),
Size2D::new(width, height),
)) {
let dynamic_image = DynamicImage::ImageRgba8(image);
if let Err(e) = dynamic_image.write_to(&mut file, ImageFormat::Png)
{
error!("Failed to save {} ({}).", path, e);
}
}
},
Err(e) => error!("Failed to create {} ({}).", path, e),
},
);
None
},
};
Ok(rv)
Ok(())
}
/// Send all pending paint metrics messages after a composite operation, which may advance
@ -2257,7 +2157,7 @@ impl IOCompositor {
for msg in compositor_messages {
self.handle_browser_message(msg);
if self.global.shutdown_state == ShutdownState::FinishedShuttingDown {
if self.shutdown_state() == ShutdownState::FinishedShuttingDown {
return;
}
}
@ -2268,7 +2168,7 @@ impl IOCompositor {
tracing::instrument(skip_all, fields(servo_profiling = true), level = "trace")
)]
pub fn perform_updates(&mut self) -> bool {
if self.global.shutdown_state == ShutdownState::FinishedShuttingDown {
if self.shutdown_state() == ShutdownState::FinishedShuttingDown {
return false;
}
@ -2292,7 +2192,7 @@ impl IOCompositor {
if !self.pending_scroll_zoom_events.is_empty() {
self.process_pending_scroll_events()
}
self.global.shutdown_state != ShutdownState::FinishedShuttingDown
self.shutdown_state() != ShutdownState::FinishedShuttingDown
}
pub fn pinch_zoom_level(&self) -> Scale<f32, DevicePixel, DevicePixel> {

View file

@ -4,16 +4,18 @@
#![deny(unsafe_code)]
use std::cell::Cell;
use std::rc::Rc;
use compositing_traits::{CompositorProxy, CompositorReceiver, ConstellationMsg};
use crossbeam_channel::Sender;
use embedder_traits::ShutdownState;
use profile_traits::{mem, time};
use webrender::RenderApi;
use webrender_api::DocumentId;
use webrender_traits::rendering_context::RenderingContext;
pub use crate::compositor::{CompositeTarget, IOCompositor, ShutdownState};
pub use crate::compositor::IOCompositor;
#[macro_use]
mod tracing;
@ -35,6 +37,9 @@ pub struct InitialCompositorState {
pub time_profiler_chan: time::ProfilerChan,
/// A channel to the memory profiler thread.
pub mem_profiler_chan: mem::ProfilerChan,
/// A shared state which tracks whether Servo has started or has finished
/// shutting down.
pub shutdown_state: Rc<Cell<ShutdownState>>,
/// Instance of webrender API
pub webrender: webrender::Renderer,
pub webrender_document: DocumentId,

View file

@ -30,7 +30,6 @@ mod from_constellation {
impl LogTarget for compositing_traits::CompositorMsg {
fn log_target(&self) -> &'static str {
match self {
Self::ShutdownComplete => target!("ShutdownComplete"),
Self::ChangeRunningAnimationsState(..) => target!("ChangeRunningAnimationsState"),
Self::CreateOrUpdateWebView(..) => target!("CreateOrUpdateWebView"),
Self::RemoveWebView(..) => target!("RemoveWebView"),

View file

@ -15,6 +15,11 @@ use servo_url::ServoUrl;
/// Global flags for Servo, currently set on the command line.
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct Opts {
/// Whether or not Servo should wait for web content to go into an idle state, therefore
/// likely producing a stable output image. This is useful for taking screenshots of pages
/// after they have loaded.
pub wait_for_stable_image: bool,
/// Whether or not the legacy layout system is enabled.
pub legacy_layout: bool,
@ -44,8 +49,6 @@ pub struct Opts {
pub user_stylesheets: Vec<(Vec<u8>, ServoUrl)>,
pub output_file: Option<String>,
/// True to exit on thread failure instead of displaying about:failure.
pub hard_fail: bool,
@ -74,9 +77,6 @@ pub struct Opts {
/// used for testing the hardening of the constellation.
pub random_pipeline_closure_seed: Option<usize>,
/// True to exit after the page load (`-x`).
pub exit_after_load: bool,
/// Load shaders from disk.
pub shaders_dir: Option<PathBuf>,
@ -194,6 +194,7 @@ pub enum OutputOptions {
impl Default for Opts {
fn default() -> Self {
Self {
wait_for_stable_image: false,
legacy_layout: false,
time_profiling: None,
time_profiler_trace_path: None,
@ -201,7 +202,6 @@ impl Default for Opts {
nonincremental_layout: false,
userscripts: None,
user_stylesheets: Vec::new(),
output_file: None,
hard_fail: true,
webdriver_port: None,
multiprocess: false,
@ -210,7 +210,6 @@ impl Default for Opts {
random_pipeline_closure_seed: None,
sandbox: false,
debug: Default::default(),
exit_after_load: false,
config_dir: None,
shaders_dir: None,
certificate_path: None,

View file

@ -2698,8 +2698,8 @@ where
}
}
debug!("Asking compositor to complete shutdown.");
self.compositor_proxy.send(CompositorMsg::ShutdownComplete);
debug!("Asking embedding layer to complete shutdown.");
self.embedder_proxy.send(EmbedderMsg::ShutdownComplete);
debug!("Shutting-down IPC router thread in constellation.");
ROUTER.shutdown();

View file

@ -247,6 +247,7 @@ mod from_script {
Self::RequestDevtoolsConnection(..) => target_variant!("RequestDevtoolsConnection"),
Self::PlayGamepadHapticEffect(..) => target_variant!("PlayGamepadHapticEffect"),
Self::StopGamepadHapticEffect(..) => target_variant!("StopGamepadHapticEffect"),
Self::ShutdownComplete => target_variant!("ShutdownComplete"),
}
}
}

View file

@ -65,7 +65,7 @@ use script_traits::{
use selectors::attr::CaseSensitivity;
use servo_arc::Arc as ServoArc;
use servo_atoms::Atom;
use servo_config::pref;
use servo_config::{opts, pref};
use servo_geometry::{f32_rect_to_au_rect, DeviceIndependentIntRect, MaxRect};
use servo_url::{ImmutableOrigin, MutableOrigin, ServoUrl};
use style::dom::OpaqueNode;
@ -368,9 +368,6 @@ pub(crate) struct Window {
/// Emits notifications when there is a relayout.
relayout_event: bool,
/// True if it is safe to write to the image.
prepare_for_screenshot: bool,
/// Unminify Css.
unminify_css: bool,
@ -2071,7 +2068,7 @@ impl Window {
// When all these conditions are met, notify the constellation
// that this pipeline is ready to write the image (from the script thread
// perspective at least).
if self.prepare_for_screenshot && updating_the_rendering {
if opts::get().wait_for_stable_image && updating_the_rendering {
// Checks if the html element has reftest-wait attribute present.
// See http://testthewebforward.org/docs/reftests.html
// and https://web-platform-tests.org/writing-tests/crashtest.html
@ -2166,7 +2163,7 @@ impl Window {
/// If writing a screenshot, synchronously update the layout epoch that it set
/// in the constellation.
pub(crate) fn update_constellation_epoch(&self) {
if !self.prepare_for_screenshot {
if !opts::get().wait_for_stable_image {
return;
}
@ -2772,7 +2769,6 @@ impl Window {
webrender_document: DocumentId,
compositor_api: CrossProcessCompositorApi,
relayout_event: bool,
prepare_for_screenshot: bool,
unminify_js: bool,
unminify_css: bool,
local_script_source: Option<String>,
@ -2862,7 +2858,6 @@ impl Window {
compositor_api,
has_sent_idle_message: Cell::new(false),
relayout_event,
prepare_for_screenshot,
unminify_css,
userscripts_path,
player_context,

View file

@ -298,9 +298,6 @@ pub struct ScriptThread {
/// Emits notifications when there is a relayout.
relayout_event: bool,
/// True if it is safe to write to the image.
prepare_for_screenshot: bool,
/// Unminify Javascript.
unminify_js: bool,
@ -835,10 +832,6 @@ impl ScriptThread {
system_font_service: Arc<SystemFontServiceProxy>,
user_agent: Cow<'static, str>,
) -> ScriptThread {
let opts = opts::get();
let prepare_for_screenshot =
opts.output_file.is_some() || opts.exit_after_load || opts.webdriver_port.is_some();
let (self_sender, self_receiver) = unbounded();
let runtime = Runtime::new(Some(SendableTaskSource {
sender: ScriptEventLoopSender::MainThread(self_sender.clone()),
@ -898,6 +891,7 @@ impl ScriptThread {
webgpu_receiver: RefCell::new(crossbeam_channel::never()),
};
let opts = opts::get();
let senders = ScriptThreadSenders {
self_sender,
#[cfg(feature = "bluetooth")]
@ -946,7 +940,6 @@ impl ScriptThread {
profile_script_events: opts.debug.profile_script_events,
print_pwm: opts.print_pwm,
relayout_event: opts.debug.relayout_event,
prepare_for_screenshot,
unminify_js: opts.unminify_js,
local_script_source: opts.local_script_source.clone(),
unminify_css: opts.unminify_css,
@ -3099,7 +3092,6 @@ impl ScriptThread {
self.webrender_document,
self.compositor_api.clone(),
self.relayout_event,
self.prepare_for_screenshot,
self.unminify_js,
self.unminify_css,
self.local_script_source.clone(),

View file

@ -111,7 +111,6 @@ impl ApplicationHandler<WakerEvent> for App {
}),
window_delegate.clone(),
Default::default(),
compositing::CompositeTarget::ContextFbo,
);
servo.setup_logging();

View file

@ -24,7 +24,7 @@ mod webview;
mod webview_delegate;
use std::borrow::Cow;
use std::cell::RefCell;
use std::cell::{Cell, RefCell};
use std::cmp::max;
use std::collections::HashMap;
use std::path::PathBuf;
@ -43,7 +43,7 @@ use canvas::WebGLComm;
use canvas_traits::webgl::{GlType, WebGLThreads};
use clipboard_delegate::StringRequest;
use compositing::windowing::{EmbedderMethods, WindowMethods};
use compositing::{CompositeTarget, IOCompositor, InitialCompositorState, ShutdownState};
use compositing::{IOCompositor, InitialCompositorState};
use compositing_traits::{CompositorMsg, CompositorProxy, CompositorReceiver, ConstellationMsg};
#[cfg(all(
not(target_os = "windows"),
@ -79,7 +79,7 @@ use ipc_channel::router::ROUTER;
pub use keyboard_types::*;
#[cfg(feature = "layout_2013")]
pub use layout_thread_2013;
use log::{warn, Log, Metadata, Record};
use log::{debug, warn, Log, Metadata, Record};
use media::{GlApi, NativeDisplay, WindowGLContext};
use net::protocols::ProtocolRegistry;
use net::resource_thread::new_resource_threads;
@ -201,6 +201,9 @@ pub struct Servo {
compositor: Rc<RefCell<IOCompositor>>,
constellation_proxy: ConstellationProxy,
embedder_receiver: Receiver<EmbedderMsg>,
/// Tracks whether we are in the process of shutting down, or have shut down.
/// This is shared with `WebView`s and the `ServoRenderer`.
shutdown_state: Rc<Cell<ShutdownState>>,
/// A map [`WebView`]s that are managed by this [`Servo`] instance. These are stored
/// as `Weak` references so that the embedding application can control their lifetime.
/// When accessed, `Servo` will be reponsible for cleaning up the invalid `Weak`
@ -261,7 +264,6 @@ impl Servo {
mut embedder: Box<dyn EmbedderMethods>,
window: Rc<dyn WindowMethods>,
user_agent: Option<String>,
composite_target: CompositeTarget,
) -> Self {
// Global configuration options, parsed from the command line.
opts::set_options(opts);
@ -506,14 +508,9 @@ impl Servo {
}
}
let composite_target = if let Some(path) = opts.output_file.clone() {
CompositeTarget::PngFile(path.into())
} else {
composite_target
};
// The compositor coordinates with the client window to create the final
// rendered page and display it somewhere.
let shutdown_state = Rc::new(Cell::new(ShutdownState::NotShuttingDown));
let compositor = IOCompositor::new(
window,
InitialCompositorState {
@ -529,18 +526,18 @@ impl Servo {
webrender_gl,
#[cfg(feature = "webxr")]
webxr_main_thread,
shutdown_state: shutdown_state.clone(),
},
composite_target,
opts.exit_after_load,
opts.debug.convert_mouse_to_touch,
embedder.get_version_string().unwrap_or_default(),
);
Servo {
Self {
delegate: RefCell::new(Rc::new(DefaultServoDelegate)),
compositor: Rc::new(RefCell::new(compositor)),
constellation_proxy: ConstellationProxy::new(constellation_chan),
embedder_receiver,
shutdown_state,
webviews: Default::default(),
_js_engine_setup: js_engine_setup,
}
@ -569,16 +566,18 @@ impl Servo {
/// The return value of this method indicates whether or not Servo, false indicates that Servo
/// has finished shutting down and you should not spin the event loop any longer.
pub fn spin_event_loop(&self) -> bool {
if self.compositor.borrow().shutdown_state() == ShutdownState::FinishedShuttingDown {
if self.shutdown_state.get() == ShutdownState::FinishedShuttingDown {
return false;
}
self.compositor.borrow_mut().receive_messages();
// Only handle incoming embedder messages if the compositor hasn't already started shutting down.
if self.compositor.borrow().shutdown_state() == ShutdownState::NotShuttingDown {
while let Ok(message) = self.embedder_receiver.try_recv() {
self.handle_embedder_message(message)
while let Ok(message) = self.embedder_receiver.try_recv() {
self.handle_embedder_message(message);
if self.shutdown_state.get() == ShutdownState::FinishedShuttingDown {
break;
}
}
@ -591,7 +590,7 @@ impl Servo {
self.send_new_frame_ready_messages();
self.clean_up_destroyed_webview_handles();
if self.compositor.borrow().shutdown_state() == ShutdownState::FinishedShuttingDown {
if self.shutdown_state.get() == ShutdownState::FinishedShuttingDown {
return false;
}
@ -641,7 +640,20 @@ impl Servo {
}
pub fn start_shutting_down(&self) {
self.compositor.borrow_mut().start_shutting_down();
if self.shutdown_state.get() != ShutdownState::NotShuttingDown {
warn!("Requested shutdown while already shutting down");
return;
}
debug!("Sending Exit message to Constellation");
self.constellation_proxy.send(ConstellationMsg::Exit);
self.shutdown_state.set(ShutdownState::ShuttingDown);
}
fn finish_shutting_down(&self) {
debug!("Servo received message that Constellation shutdown is complete");
self.shutdown_state.set(ShutdownState::FinishedShuttingDown);
self.compositor.borrow_mut().finish_shutting_down();
}
pub fn deinit(&self) {
@ -675,6 +687,7 @@ impl Servo {
fn handle_embedder_message(&self, message: EmbedderMsg) {
match message {
EmbedderMsg::ShutdownComplete => self.finish_shutting_down(),
EmbedderMsg::Status(webview_id, status_text) => {
if let Some(webview) = self.get_webview_handle(webview_id) {
webview.set_status_text(status_text);

View file

@ -436,7 +436,10 @@ impl WebView {
.send(ConstellationMsg::SendError(Some(self.id()), message));
}
pub fn paint(&self) {
self.inner().compositor.borrow_mut().composite();
/// Paint the contents of this [`WebView`] into its `RenderingContext`. This will
/// always paint, unless the `Opts::wait_for_stable_image` option is enabled. In
/// that case, this might do nothing. Returns true if a paint was actually performed.
pub fn paint(&self) -> bool {
self.inner().compositor.borrow_mut().render()
}
}

View file

@ -58,10 +58,6 @@ impl CompositorReceiver {
/// Messages from (or via) the constellation thread to the compositor.
pub enum CompositorMsg {
/// Informs the compositor that the constellation has completed shutdown.
/// Required because the constellation can have pending calls to make
/// (e.g. SetFrameTree) at the time that we send it an ExitMsg.
ShutdownComplete,
/// Alerts the compositor that the given pipeline has changed whether it is running animations.
ChangeRunningAnimationsState(PipelineId, AnimationState),
/// Create or update a webview, given its frame tree.
@ -118,7 +114,6 @@ pub struct CompositionPipeline {
impl Debug for CompositorMsg {
fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
match *self {
CompositorMsg::ShutdownComplete => write!(f, "ShutdownComplete"),
CompositorMsg::ChangeRunningAnimationsState(_, state) => {
write!(f, "ChangeRunningAnimationsState({:?})", state)
},

View file

@ -22,6 +22,15 @@ use webrender_api::units::{DeviceIntPoint, DeviceIntRect, DeviceIntSize};
pub use crate::input_events::*;
/// Tracks whether Servo isn't shutting down, is in the process of shutting down,
/// or has finished shutting down.
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum ShutdownState {
NotShuttingDown,
ShuttingDown,
FinishedShuttingDown,
}
/// A cursor for the window. This is different from a CSS cursor (see
/// `CursorKind`) in that it has no `Auto` value.
#[repr(u8)]
@ -257,6 +266,10 @@ pub enum EmbedderMsg {
PlayGamepadHapticEffect(WebViewId, usize, GamepadHapticEffectType, IpcSender<bool>),
/// Request to stop a haptic effect on a connected gamepad.
StopGamepadHapticEffect(WebViewId, usize, IpcSender<bool>),
/// Informs the embedder that the constellation has completed shutdown.
/// Required because the constellation can have pending calls to make
/// (e.g. SetFrameTree) at the time that we send it an ExitMsg.
ShutdownComplete,
}
impl Debug for EmbedderMsg {
@ -302,6 +315,7 @@ impl Debug for EmbedderMsg {
EmbedderMsg::ShowContextMenu(..) => write!(f, "ShowContextMenu"),
EmbedderMsg::PlayGamepadHapticEffect(..) => write!(f, "PlayGamepadHapticEffect"),
EmbedderMsg::StopGamepadHapticEffect(..) => write!(f, "StopGamepadHapticEffect"),
EmbedderMsg::ShutdownComplete => write!(f, "ShutdownComplete"),
}
}
}