mirror of
https://github.com/servo/servo.git
synced 2025-06-06 16:45:39 +00:00
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:
parent
7d33e72bfc
commit
54b5c7b632
25 changed files with 233 additions and 270 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -1042,7 +1042,6 @@ dependencies = [
|
||||||
"euclid",
|
"euclid",
|
||||||
"fnv",
|
"fnv",
|
||||||
"gleam",
|
"gleam",
|
||||||
"image",
|
|
||||||
"ipc-channel",
|
"ipc-channel",
|
||||||
"libc",
|
"libc",
|
||||||
"log",
|
"log",
|
||||||
|
|
|
@ -26,7 +26,6 @@ embedder_traits = { workspace = true }
|
||||||
euclid = { workspace = true }
|
euclid = { workspace = true }
|
||||||
fnv = { workspace = true }
|
fnv = { workspace = true }
|
||||||
gleam = { workspace = true }
|
gleam = { workspace = true }
|
||||||
image = { workspace = true }
|
|
||||||
ipc-channel = { workspace = true }
|
ipc-channel = { workspace = true }
|
||||||
libc = { workspace = true }
|
libc = { workspace = true }
|
||||||
log = { workspace = true }
|
log = { workspace = true }
|
||||||
|
|
|
@ -22,11 +22,10 @@ use compositing_traits::{
|
||||||
use crossbeam_channel::Sender;
|
use crossbeam_channel::Sender;
|
||||||
use embedder_traits::{
|
use embedder_traits::{
|
||||||
Cursor, InputEvent, MouseButton, MouseButtonAction, MouseButtonEvent, MouseMoveEvent,
|
Cursor, InputEvent, MouseButton, MouseButtonAction, MouseButtonEvent, MouseMoveEvent,
|
||||||
TouchAction, TouchEvent, TouchEventType, TouchId,
|
ShutdownState, TouchAction, TouchEvent, TouchEventType, TouchId,
|
||||||
};
|
};
|
||||||
use euclid::{Point2D, Rect, Scale, Size2D, Transform3D, Vector2D};
|
use euclid::{Point2D, Rect, Scale, Size2D, Transform3D, Vector2D};
|
||||||
use fnv::{FnvHashMap, FnvHashSet};
|
use fnv::{FnvHashMap, FnvHashSet};
|
||||||
use image::{DynamicImage, ImageFormat};
|
|
||||||
use ipc_channel::ipc::{self, IpcSharedMemory};
|
use ipc_channel::ipc::{self, IpcSharedMemory};
|
||||||
use libc::c_void;
|
use libc::c_void;
|
||||||
use log::{debug, error, info, trace, warn};
|
use log::{debug, error, info, trace, warn};
|
||||||
|
@ -37,6 +36,7 @@ use script_traits::{
|
||||||
AnimationState, AnimationTickType, EventResult, ScriptThreadMessage, ScrollState,
|
AnimationState, AnimationTickType, EventResult, ScriptThreadMessage, ScrollState,
|
||||||
WindowSizeData, WindowSizeType,
|
WindowSizeData, WindowSizeType,
|
||||||
};
|
};
|
||||||
|
use servo_config::opts;
|
||||||
use servo_geometry::DeviceIndependentPixel;
|
use servo_geometry::DeviceIndependentPixel;
|
||||||
use style_traits::{CSSPixel, PinchZoomFactor};
|
use style_traits::{CSSPixel, PinchZoomFactor};
|
||||||
use webrender::{CaptureBits, RenderApi, Transaction};
|
use webrender::{CaptureBits, RenderApi, Transaction};
|
||||||
|
@ -102,8 +102,8 @@ pub struct ServoRenderer {
|
||||||
webviews: WebViewManager<WebView>,
|
webviews: WebViewManager<WebView>,
|
||||||
|
|
||||||
/// Tracks whether we are in the process of shutting down, or have shut down and should close
|
/// Tracks whether we are in the process of shutting down, or have shut down and should close
|
||||||
/// the compositor.
|
/// the compositor. This is shared with the `Servo` instance.
|
||||||
shutdown_state: ShutdownState,
|
shutdown_state: Rc<Cell<ShutdownState>>,
|
||||||
|
|
||||||
/// The port on which we receive messages.
|
/// The port on which we receive messages.
|
||||||
compositor_receiver: CompositorReceiver,
|
compositor_receiver: CompositorReceiver,
|
||||||
|
@ -120,9 +120,6 @@ pub struct ServoRenderer {
|
||||||
/// The GL bindings for webrender
|
/// The GL bindings for webrender
|
||||||
webrender_gl: Rc<dyn gleam::gl::Gl>,
|
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
|
/// The string representing the version of Servo that is running. This is used to tag
|
||||||
/// WebRender capture output.
|
/// WebRender capture output.
|
||||||
version_string: String,
|
version_string: String,
|
||||||
|
@ -153,9 +150,6 @@ pub struct IOCompositor {
|
||||||
/// "Desktop-style" zoom that resizes the viewport to fit the window.
|
/// "Desktop-style" zoom that resizes the viewport to fit the window.
|
||||||
page_zoom: Scale<f32, CSSPixel, DeviceIndependentPixel>,
|
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.
|
/// Tracks whether or not the view needs to be repainted.
|
||||||
needs_repaint: Cell<RepaintReason>,
|
needs_repaint: Cell<RepaintReason>,
|
||||||
|
|
||||||
|
@ -250,13 +244,6 @@ bitflags! {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
|
||||||
pub enum ShutdownState {
|
|
||||||
NotShuttingDown,
|
|
||||||
ShuttingDown,
|
|
||||||
FinishedShuttingDown,
|
|
||||||
}
|
|
||||||
|
|
||||||
struct PipelineDetails {
|
struct PipelineDetails {
|
||||||
/// The pipeline associated with this PipelineDetails object.
|
/// The pipeline associated with this PipelineDetails object.
|
||||||
pipeline: Option<CompositionPipeline>,
|
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 {
|
impl IOCompositor {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
window: Rc<dyn WindowMethods>,
|
window: Rc<dyn WindowMethods>,
|
||||||
state: InitialCompositorState,
|
state: InitialCompositorState,
|
||||||
composite_target: CompositeTarget,
|
|
||||||
exit_after_load: bool,
|
|
||||||
convert_mouse_to_touch: bool,
|
convert_mouse_to_touch: bool,
|
||||||
version_string: String,
|
version_string: String,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let compositor = IOCompositor {
|
let compositor = IOCompositor {
|
||||||
global: ServoRenderer {
|
global: ServoRenderer {
|
||||||
shutdown_state: ShutdownState::NotShuttingDown,
|
shutdown_state: state.shutdown_state,
|
||||||
webviews: WebViewManager::default(),
|
webviews: WebViewManager::default(),
|
||||||
compositor_receiver: state.receiver,
|
compositor_receiver: state.receiver,
|
||||||
constellation_sender: state.constellation_chan,
|
constellation_sender: state.constellation_chan,
|
||||||
time_profiler_chan: state.time_profiler_chan,
|
time_profiler_chan: state.time_profiler_chan,
|
||||||
webrender_api: state.webrender_api,
|
webrender_api: state.webrender_api,
|
||||||
webrender_gl: state.webrender_gl,
|
webrender_gl: state.webrender_gl,
|
||||||
exit_after_load,
|
|
||||||
version_string,
|
version_string,
|
||||||
#[cfg(feature = "webxr")]
|
#[cfg(feature = "webxr")]
|
||||||
webxr_main_thread: state.webxr_main_thread,
|
webxr_main_thread: state.webxr_main_thread,
|
||||||
|
@ -366,7 +337,6 @@ impl IOCompositor {
|
||||||
needs_repaint: Cell::default(),
|
needs_repaint: Cell::default(),
|
||||||
touch_handler: TouchHandler::new(),
|
touch_handler: TouchHandler::new(),
|
||||||
pending_scroll_zoom_events: Vec::new(),
|
pending_scroll_zoom_events: Vec::new(),
|
||||||
composite_target,
|
|
||||||
page_zoom: Scale::new(1.0),
|
page_zoom: Scale::new(1.0),
|
||||||
viewport_zoom: PinchZoomFactor::new(1.0),
|
viewport_zoom: PinchZoomFactor::new(1.0),
|
||||||
min_viewport_zoom: Some(PinchZoomFactor::new(1.0)),
|
min_viewport_zoom: Some(PinchZoomFactor::new(1.0)),
|
||||||
|
@ -394,7 +364,7 @@ impl IOCompositor {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn shutdown_state(&self) -> ShutdownState {
|
pub fn shutdown_state(&self) -> ShutdownState {
|
||||||
self.global.shutdown_state
|
self.global.shutdown_state.get()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn deinit(&mut self) {
|
pub fn deinit(&mut self) {
|
||||||
|
@ -441,27 +411,7 @@ impl IOCompositor {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn start_shutting_down(&mut self) {
|
pub fn finish_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");
|
|
||||||
|
|
||||||
// Drain compositor port, sometimes messages contain channels that are blocking
|
// Drain compositor port, sometimes messages contain channels that are blocking
|
||||||
// another thread from finishing (i.e. SetFrameTree).
|
// another thread from finishing (i.e. SetFrameTree).
|
||||||
while self
|
while self
|
||||||
|
@ -478,14 +428,12 @@ impl IOCompositor {
|
||||||
.send(profile_time::ProfilerMsg::Exit(sender));
|
.send(profile_time::ProfilerMsg::Exit(sender));
|
||||||
let _ = receiver.recv();
|
let _ = receiver.recv();
|
||||||
}
|
}
|
||||||
|
|
||||||
self.global.shutdown_state = ShutdownState::FinishedShuttingDown;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_browser_message(&mut self, msg: CompositorMsg) {
|
fn handle_browser_message(&mut self, msg: CompositorMsg) {
|
||||||
trace_msg_from_constellation!(msg, "{msg:?}");
|
trace_msg_from_constellation!(msg, "{msg:?}");
|
||||||
|
|
||||||
match self.global.shutdown_state {
|
match self.shutdown_state() {
|
||||||
ShutdownState::NotShuttingDown => {},
|
ShutdownState::NotShuttingDown => {},
|
||||||
ShutdownState::ShuttingDown => {
|
ShutdownState::ShuttingDown => {
|
||||||
self.handle_browser_message_while_shutting_down(msg);
|
self.handle_browser_message_while_shutting_down(msg);
|
||||||
|
@ -498,11 +446,6 @@ impl IOCompositor {
|
||||||
}
|
}
|
||||||
|
|
||||||
match msg {
|
match msg {
|
||||||
CompositorMsg::ShutdownComplete => {
|
|
||||||
error!("Received `ShutdownComplete` while not shutting down.");
|
|
||||||
self.finish_shutting_down();
|
|
||||||
},
|
|
||||||
|
|
||||||
CompositorMsg::ChangeRunningAnimationsState(pipeline_id, animation_state) => {
|
CompositorMsg::ChangeRunningAnimationsState(pipeline_id, animation_state) => {
|
||||||
self.change_running_animations_state(pipeline_id, animation_state);
|
self.change_running_animations_state(pipeline_id, animation_state);
|
||||||
},
|
},
|
||||||
|
@ -521,7 +464,7 @@ impl IOCompositor {
|
||||||
},
|
},
|
||||||
|
|
||||||
CompositorMsg::CreatePng(page_rect, reply) => {
|
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 {
|
if let Err(ref e) = res {
|
||||||
info!("Error retrieving PNG: {:?}", e);
|
info!("Error retrieving PNG: {:?}", e);
|
||||||
}
|
}
|
||||||
|
@ -570,10 +513,7 @@ impl IOCompositor {
|
||||||
},
|
},
|
||||||
|
|
||||||
CompositorMsg::LoadComplete(_) => {
|
CompositorMsg::LoadComplete(_) => {
|
||||||
// If we're painting in headless mode, schedule a recomposite.
|
if opts::get().wait_for_stable_image {
|
||||||
if matches!(self.composite_target, CompositeTarget::PngFile(_)) ||
|
|
||||||
self.global.exit_after_load
|
|
||||||
{
|
|
||||||
self.set_needs_repaint(RepaintReason::ReadyForScreenshot);
|
self.set_needs_repaint(RepaintReason::ReadyForScreenshot);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -850,9 +790,6 @@ impl IOCompositor {
|
||||||
/// compositor no longer does any WebRender frame generation.
|
/// compositor no longer does any WebRender frame generation.
|
||||||
fn handle_browser_message_while_shutting_down(&mut self, msg: CompositorMsg) {
|
fn handle_browser_message_while_shutting_down(&mut self, msg: CompositorMsg) {
|
||||||
match msg {
|
match msg {
|
||||||
CompositorMsg::ShutdownComplete => {
|
|
||||||
self.finish_shutting_down();
|
|
||||||
},
|
|
||||||
CompositorMsg::PipelineExited(pipeline_id, sender) => {
|
CompositorMsg::PipelineExited(pipeline_id, sender) => {
|
||||||
debug!("Compositor got pipeline exited: {:?}", pipeline_id);
|
debug!("Compositor got pipeline exited: {:?}", pipeline_id);
|
||||||
self.remove_pipeline_root_layer(pipeline_id);
|
self.remove_pipeline_root_layer(pipeline_id);
|
||||||
|
@ -1299,7 +1236,7 @@ impl IOCompositor {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn on_rendering_context_resized(&mut self) -> bool {
|
pub fn on_rendering_context_resized(&mut self) -> bool {
|
||||||
if self.global.shutdown_state != ShutdownState::NotShuttingDown {
|
if self.shutdown_state() != ShutdownState::NotShuttingDown {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1352,7 +1289,7 @@ impl IOCompositor {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn on_input_event(&mut self, event: InputEvent) {
|
pub fn on_input_event(&mut self, event: InputEvent) {
|
||||||
if self.global.shutdown_state != ShutdownState::NotShuttingDown {
|
if self.shutdown_state() != ShutdownState::NotShuttingDown {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1465,7 +1402,7 @@ impl IOCompositor {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn on_touch_event(&mut self, event: TouchEvent) {
|
pub fn on_touch_event(&mut self, event: TouchEvent) {
|
||||||
if self.global.shutdown_state != ShutdownState::NotShuttingDown {
|
if self.shutdown_state() != ShutdownState::NotShuttingDown {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1605,7 +1542,7 @@ impl IOCompositor {
|
||||||
cursor: DeviceIntPoint,
|
cursor: DeviceIntPoint,
|
||||||
event_type: TouchEventType,
|
event_type: TouchEventType,
|
||||||
) {
|
) {
|
||||||
if self.global.shutdown_state != ShutdownState::NotShuttingDown {
|
if self.shutdown_state() != ShutdownState::NotShuttingDown {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1822,9 +1759,6 @@ impl IOCompositor {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn hidpi_factor(&self) -> Scale<f32, DeviceIndependentPixel, DevicePixel> {
|
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
|
self.embedder_coordinates.hidpi_factor
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1839,7 +1773,7 @@ impl IOCompositor {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn on_zoom_reset_window_event(&mut self) {
|
pub fn on_zoom_reset_window_event(&mut self) {
|
||||||
if self.global.shutdown_state != ShutdownState::NotShuttingDown {
|
if self.shutdown_state() != ShutdownState::NotShuttingDown {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1848,7 +1782,7 @@ impl IOCompositor {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn on_zoom_window_event(&mut self, magnification: f32) {
|
pub fn on_zoom_window_event(&mut self, magnification: f32) {
|
||||||
if self.global.shutdown_state != ShutdownState::NotShuttingDown {
|
if self.shutdown_state() != ShutdownState::NotShuttingDown {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1871,7 +1805,7 @@ impl IOCompositor {
|
||||||
|
|
||||||
/// Simulate a pinch zoom
|
/// Simulate a pinch zoom
|
||||||
pub fn on_pinch_zoom_window_event(&mut self, magnification: f32) {
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1985,10 +1919,12 @@ impl IOCompositor {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn composite(&mut self) {
|
/// Render the WebRender scene to the active `RenderingContext`. If successful, trigger
|
||||||
if let Err(error) = self.composite_specific_target(self.composite_target.clone(), None) {
|
/// the next round of animations.
|
||||||
warn!("Unable to composite: {error:?}");
|
pub fn render(&mut self) -> bool {
|
||||||
return;
|
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,
|
// 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.
|
// Queue up any subsequent paints for animations.
|
||||||
self.process_animations(true);
|
self.process_animations(true);
|
||||||
|
|
||||||
if matches!(self.composite_target, CompositeTarget::PngFile(_)) ||
|
true
|
||||||
self.global.exit_after_load
|
}
|
||||||
{
|
|
||||||
println!("Shutting down the Constellation after generating an output file or exit flag specified");
|
/// Render the WebRender scene to the shared memory, without updating other state of this
|
||||||
self.start_shutting_down();
|
/// [`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(
|
#[cfg_attr(
|
||||||
feature = "tracing",
|
feature = "tracing",
|
||||||
tracing::instrument(skip_all, fields(servo_profiling = true), level = "trace")
|
tracing::instrument(skip_all, fields(servo_profiling = true), level = "trace")
|
||||||
)]
|
)]
|
||||||
fn composite_specific_target(
|
fn render_inner(&mut self) -> Result<(), UnableToComposite> {
|
||||||
&mut self,
|
|
||||||
target: CompositeTarget,
|
|
||||||
page_rect: Option<Rect<f32, CSSPixel>>,
|
|
||||||
) -> Result<Option<Image>, UnableToComposite> {
|
|
||||||
let size = self.embedder_coordinates.framebuffer.to_u32();
|
|
||||||
if let Err(err) = self.rendering_context.make_current() {
|
if let Err(err) = self.rendering_context.make_current() {
|
||||||
warn!("Failed to make the rendering context current: {:?}", err);
|
warn!("Failed to make the rendering context current: {:?}", err);
|
||||||
}
|
}
|
||||||
|
@ -2029,12 +1991,7 @@ impl IOCompositor {
|
||||||
webrender.update();
|
webrender.update();
|
||||||
}
|
}
|
||||||
|
|
||||||
let wait_for_stable_image = matches!(
|
if opts::get().wait_for_stable_image {
|
||||||
target,
|
|
||||||
CompositeTarget::SharedMemory | CompositeTarget::PngFile(_)
|
|
||||||
) || self.global.exit_after_load;
|
|
||||||
|
|
||||||
if wait_for_stable_image {
|
|
||||||
// The current image may be ready to output. However, if there are animations active,
|
// 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
|
// tick those instead and continue waiting for the image output to be stable AND
|
||||||
// all active animations to complete.
|
// all active animations to complete.
|
||||||
|
@ -2071,64 +2028,7 @@ impl IOCompositor {
|
||||||
);
|
);
|
||||||
|
|
||||||
self.send_pending_paint_metrics_messages_after_composite();
|
self.send_pending_paint_metrics_messages_after_composite();
|
||||||
|
Ok(())
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Send all pending paint metrics messages after a composite operation, which may advance
|
/// Send all pending paint metrics messages after a composite operation, which may advance
|
||||||
|
@ -2257,7 +2157,7 @@ impl IOCompositor {
|
||||||
for msg in compositor_messages {
|
for msg in compositor_messages {
|
||||||
self.handle_browser_message(msg);
|
self.handle_browser_message(msg);
|
||||||
|
|
||||||
if self.global.shutdown_state == ShutdownState::FinishedShuttingDown {
|
if self.shutdown_state() == ShutdownState::FinishedShuttingDown {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2268,7 +2168,7 @@ impl IOCompositor {
|
||||||
tracing::instrument(skip_all, fields(servo_profiling = true), level = "trace")
|
tracing::instrument(skip_all, fields(servo_profiling = true), level = "trace")
|
||||||
)]
|
)]
|
||||||
pub fn perform_updates(&mut self) -> bool {
|
pub fn perform_updates(&mut self) -> bool {
|
||||||
if self.global.shutdown_state == ShutdownState::FinishedShuttingDown {
|
if self.shutdown_state() == ShutdownState::FinishedShuttingDown {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2292,7 +2192,7 @@ impl IOCompositor {
|
||||||
if !self.pending_scroll_zoom_events.is_empty() {
|
if !self.pending_scroll_zoom_events.is_empty() {
|
||||||
self.process_pending_scroll_events()
|
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> {
|
pub fn pinch_zoom_level(&self) -> Scale<f32, DevicePixel, DevicePixel> {
|
||||||
|
|
|
@ -4,16 +4,18 @@
|
||||||
|
|
||||||
#![deny(unsafe_code)]
|
#![deny(unsafe_code)]
|
||||||
|
|
||||||
|
use std::cell::Cell;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
use compositing_traits::{CompositorProxy, CompositorReceiver, ConstellationMsg};
|
use compositing_traits::{CompositorProxy, CompositorReceiver, ConstellationMsg};
|
||||||
use crossbeam_channel::Sender;
|
use crossbeam_channel::Sender;
|
||||||
|
use embedder_traits::ShutdownState;
|
||||||
use profile_traits::{mem, time};
|
use profile_traits::{mem, time};
|
||||||
use webrender::RenderApi;
|
use webrender::RenderApi;
|
||||||
use webrender_api::DocumentId;
|
use webrender_api::DocumentId;
|
||||||
use webrender_traits::rendering_context::RenderingContext;
|
use webrender_traits::rendering_context::RenderingContext;
|
||||||
|
|
||||||
pub use crate::compositor::{CompositeTarget, IOCompositor, ShutdownState};
|
pub use crate::compositor::IOCompositor;
|
||||||
|
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
mod tracing;
|
mod tracing;
|
||||||
|
@ -35,6 +37,9 @@ pub struct InitialCompositorState {
|
||||||
pub time_profiler_chan: time::ProfilerChan,
|
pub time_profiler_chan: time::ProfilerChan,
|
||||||
/// A channel to the memory profiler thread.
|
/// A channel to the memory profiler thread.
|
||||||
pub mem_profiler_chan: mem::ProfilerChan,
|
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
|
/// Instance of webrender API
|
||||||
pub webrender: webrender::Renderer,
|
pub webrender: webrender::Renderer,
|
||||||
pub webrender_document: DocumentId,
|
pub webrender_document: DocumentId,
|
||||||
|
|
|
@ -30,7 +30,6 @@ mod from_constellation {
|
||||||
impl LogTarget for compositing_traits::CompositorMsg {
|
impl LogTarget for compositing_traits::CompositorMsg {
|
||||||
fn log_target(&self) -> &'static str {
|
fn log_target(&self) -> &'static str {
|
||||||
match self {
|
match self {
|
||||||
Self::ShutdownComplete => target!("ShutdownComplete"),
|
|
||||||
Self::ChangeRunningAnimationsState(..) => target!("ChangeRunningAnimationsState"),
|
Self::ChangeRunningAnimationsState(..) => target!("ChangeRunningAnimationsState"),
|
||||||
Self::CreateOrUpdateWebView(..) => target!("CreateOrUpdateWebView"),
|
Self::CreateOrUpdateWebView(..) => target!("CreateOrUpdateWebView"),
|
||||||
Self::RemoveWebView(..) => target!("RemoveWebView"),
|
Self::RemoveWebView(..) => target!("RemoveWebView"),
|
||||||
|
|
|
@ -15,6 +15,11 @@ use servo_url::ServoUrl;
|
||||||
/// Global flags for Servo, currently set on the command line.
|
/// Global flags for Servo, currently set on the command line.
|
||||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||||
pub struct Opts {
|
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.
|
/// Whether or not the legacy layout system is enabled.
|
||||||
pub legacy_layout: bool,
|
pub legacy_layout: bool,
|
||||||
|
|
||||||
|
@ -44,8 +49,6 @@ pub struct Opts {
|
||||||
|
|
||||||
pub user_stylesheets: Vec<(Vec<u8>, ServoUrl)>,
|
pub user_stylesheets: Vec<(Vec<u8>, ServoUrl)>,
|
||||||
|
|
||||||
pub output_file: Option<String>,
|
|
||||||
|
|
||||||
/// True to exit on thread failure instead of displaying about:failure.
|
/// True to exit on thread failure instead of displaying about:failure.
|
||||||
pub hard_fail: bool,
|
pub hard_fail: bool,
|
||||||
|
|
||||||
|
@ -74,9 +77,6 @@ pub struct Opts {
|
||||||
/// used for testing the hardening of the constellation.
|
/// used for testing the hardening of the constellation.
|
||||||
pub random_pipeline_closure_seed: Option<usize>,
|
pub random_pipeline_closure_seed: Option<usize>,
|
||||||
|
|
||||||
/// True to exit after the page load (`-x`).
|
|
||||||
pub exit_after_load: bool,
|
|
||||||
|
|
||||||
/// Load shaders from disk.
|
/// Load shaders from disk.
|
||||||
pub shaders_dir: Option<PathBuf>,
|
pub shaders_dir: Option<PathBuf>,
|
||||||
|
|
||||||
|
@ -194,6 +194,7 @@ pub enum OutputOptions {
|
||||||
impl Default for Opts {
|
impl Default for Opts {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
wait_for_stable_image: false,
|
||||||
legacy_layout: false,
|
legacy_layout: false,
|
||||||
time_profiling: None,
|
time_profiling: None,
|
||||||
time_profiler_trace_path: None,
|
time_profiler_trace_path: None,
|
||||||
|
@ -201,7 +202,6 @@ impl Default for Opts {
|
||||||
nonincremental_layout: false,
|
nonincremental_layout: false,
|
||||||
userscripts: None,
|
userscripts: None,
|
||||||
user_stylesheets: Vec::new(),
|
user_stylesheets: Vec::new(),
|
||||||
output_file: None,
|
|
||||||
hard_fail: true,
|
hard_fail: true,
|
||||||
webdriver_port: None,
|
webdriver_port: None,
|
||||||
multiprocess: false,
|
multiprocess: false,
|
||||||
|
@ -210,7 +210,6 @@ impl Default for Opts {
|
||||||
random_pipeline_closure_seed: None,
|
random_pipeline_closure_seed: None,
|
||||||
sandbox: false,
|
sandbox: false,
|
||||||
debug: Default::default(),
|
debug: Default::default(),
|
||||||
exit_after_load: false,
|
|
||||||
config_dir: None,
|
config_dir: None,
|
||||||
shaders_dir: None,
|
shaders_dir: None,
|
||||||
certificate_path: None,
|
certificate_path: None,
|
||||||
|
|
|
@ -2698,8 +2698,8 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
debug!("Asking compositor to complete shutdown.");
|
debug!("Asking embedding layer to complete shutdown.");
|
||||||
self.compositor_proxy.send(CompositorMsg::ShutdownComplete);
|
self.embedder_proxy.send(EmbedderMsg::ShutdownComplete);
|
||||||
|
|
||||||
debug!("Shutting-down IPC router thread in constellation.");
|
debug!("Shutting-down IPC router thread in constellation.");
|
||||||
ROUTER.shutdown();
|
ROUTER.shutdown();
|
||||||
|
|
|
@ -247,6 +247,7 @@ mod from_script {
|
||||||
Self::RequestDevtoolsConnection(..) => target_variant!("RequestDevtoolsConnection"),
|
Self::RequestDevtoolsConnection(..) => target_variant!("RequestDevtoolsConnection"),
|
||||||
Self::PlayGamepadHapticEffect(..) => target_variant!("PlayGamepadHapticEffect"),
|
Self::PlayGamepadHapticEffect(..) => target_variant!("PlayGamepadHapticEffect"),
|
||||||
Self::StopGamepadHapticEffect(..) => target_variant!("StopGamepadHapticEffect"),
|
Self::StopGamepadHapticEffect(..) => target_variant!("StopGamepadHapticEffect"),
|
||||||
|
Self::ShutdownComplete => target_variant!("ShutdownComplete"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -65,7 +65,7 @@ use script_traits::{
|
||||||
use selectors::attr::CaseSensitivity;
|
use selectors::attr::CaseSensitivity;
|
||||||
use servo_arc::Arc as ServoArc;
|
use servo_arc::Arc as ServoArc;
|
||||||
use servo_atoms::Atom;
|
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_geometry::{f32_rect_to_au_rect, DeviceIndependentIntRect, MaxRect};
|
||||||
use servo_url::{ImmutableOrigin, MutableOrigin, ServoUrl};
|
use servo_url::{ImmutableOrigin, MutableOrigin, ServoUrl};
|
||||||
use style::dom::OpaqueNode;
|
use style::dom::OpaqueNode;
|
||||||
|
@ -368,9 +368,6 @@ pub(crate) struct Window {
|
||||||
/// Emits notifications when there is a relayout.
|
/// Emits notifications when there is a relayout.
|
||||||
relayout_event: bool,
|
relayout_event: bool,
|
||||||
|
|
||||||
/// True if it is safe to write to the image.
|
|
||||||
prepare_for_screenshot: bool,
|
|
||||||
|
|
||||||
/// Unminify Css.
|
/// Unminify Css.
|
||||||
unminify_css: bool,
|
unminify_css: bool,
|
||||||
|
|
||||||
|
@ -2071,7 +2068,7 @@ impl Window {
|
||||||
// When all these conditions are met, notify the constellation
|
// When all these conditions are met, notify the constellation
|
||||||
// that this pipeline is ready to write the image (from the script thread
|
// that this pipeline is ready to write the image (from the script thread
|
||||||
// perspective at least).
|
// 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.
|
// Checks if the html element has reftest-wait attribute present.
|
||||||
// See http://testthewebforward.org/docs/reftests.html
|
// See http://testthewebforward.org/docs/reftests.html
|
||||||
// and https://web-platform-tests.org/writing-tests/crashtest.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
|
/// If writing a screenshot, synchronously update the layout epoch that it set
|
||||||
/// in the constellation.
|
/// in the constellation.
|
||||||
pub(crate) fn update_constellation_epoch(&self) {
|
pub(crate) fn update_constellation_epoch(&self) {
|
||||||
if !self.prepare_for_screenshot {
|
if !opts::get().wait_for_stable_image {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2772,7 +2769,6 @@ impl Window {
|
||||||
webrender_document: DocumentId,
|
webrender_document: DocumentId,
|
||||||
compositor_api: CrossProcessCompositorApi,
|
compositor_api: CrossProcessCompositorApi,
|
||||||
relayout_event: bool,
|
relayout_event: bool,
|
||||||
prepare_for_screenshot: bool,
|
|
||||||
unminify_js: bool,
|
unminify_js: bool,
|
||||||
unminify_css: bool,
|
unminify_css: bool,
|
||||||
local_script_source: Option<String>,
|
local_script_source: Option<String>,
|
||||||
|
@ -2862,7 +2858,6 @@ impl Window {
|
||||||
compositor_api,
|
compositor_api,
|
||||||
has_sent_idle_message: Cell::new(false),
|
has_sent_idle_message: Cell::new(false),
|
||||||
relayout_event,
|
relayout_event,
|
||||||
prepare_for_screenshot,
|
|
||||||
unminify_css,
|
unminify_css,
|
||||||
userscripts_path,
|
userscripts_path,
|
||||||
player_context,
|
player_context,
|
||||||
|
|
|
@ -298,9 +298,6 @@ pub struct ScriptThread {
|
||||||
/// Emits notifications when there is a relayout.
|
/// Emits notifications when there is a relayout.
|
||||||
relayout_event: bool,
|
relayout_event: bool,
|
||||||
|
|
||||||
/// True if it is safe to write to the image.
|
|
||||||
prepare_for_screenshot: bool,
|
|
||||||
|
|
||||||
/// Unminify Javascript.
|
/// Unminify Javascript.
|
||||||
unminify_js: bool,
|
unminify_js: bool,
|
||||||
|
|
||||||
|
@ -835,10 +832,6 @@ impl ScriptThread {
|
||||||
system_font_service: Arc<SystemFontServiceProxy>,
|
system_font_service: Arc<SystemFontServiceProxy>,
|
||||||
user_agent: Cow<'static, str>,
|
user_agent: Cow<'static, str>,
|
||||||
) -> ScriptThread {
|
) -> 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 (self_sender, self_receiver) = unbounded();
|
||||||
let runtime = Runtime::new(Some(SendableTaskSource {
|
let runtime = Runtime::new(Some(SendableTaskSource {
|
||||||
sender: ScriptEventLoopSender::MainThread(self_sender.clone()),
|
sender: ScriptEventLoopSender::MainThread(self_sender.clone()),
|
||||||
|
@ -898,6 +891,7 @@ impl ScriptThread {
|
||||||
webgpu_receiver: RefCell::new(crossbeam_channel::never()),
|
webgpu_receiver: RefCell::new(crossbeam_channel::never()),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let opts = opts::get();
|
||||||
let senders = ScriptThreadSenders {
|
let senders = ScriptThreadSenders {
|
||||||
self_sender,
|
self_sender,
|
||||||
#[cfg(feature = "bluetooth")]
|
#[cfg(feature = "bluetooth")]
|
||||||
|
@ -946,7 +940,6 @@ impl ScriptThread {
|
||||||
profile_script_events: opts.debug.profile_script_events,
|
profile_script_events: opts.debug.profile_script_events,
|
||||||
print_pwm: opts.print_pwm,
|
print_pwm: opts.print_pwm,
|
||||||
relayout_event: opts.debug.relayout_event,
|
relayout_event: opts.debug.relayout_event,
|
||||||
prepare_for_screenshot,
|
|
||||||
unminify_js: opts.unminify_js,
|
unminify_js: opts.unminify_js,
|
||||||
local_script_source: opts.local_script_source.clone(),
|
local_script_source: opts.local_script_source.clone(),
|
||||||
unminify_css: opts.unminify_css,
|
unminify_css: opts.unminify_css,
|
||||||
|
@ -3099,7 +3092,6 @@ impl ScriptThread {
|
||||||
self.webrender_document,
|
self.webrender_document,
|
||||||
self.compositor_api.clone(),
|
self.compositor_api.clone(),
|
||||||
self.relayout_event,
|
self.relayout_event,
|
||||||
self.prepare_for_screenshot,
|
|
||||||
self.unminify_js,
|
self.unminify_js,
|
||||||
self.unminify_css,
|
self.unminify_css,
|
||||||
self.local_script_source.clone(),
|
self.local_script_source.clone(),
|
||||||
|
|
|
@ -111,7 +111,6 @@ impl ApplicationHandler<WakerEvent> for App {
|
||||||
}),
|
}),
|
||||||
window_delegate.clone(),
|
window_delegate.clone(),
|
||||||
Default::default(),
|
Default::default(),
|
||||||
compositing::CompositeTarget::ContextFbo,
|
|
||||||
);
|
);
|
||||||
servo.setup_logging();
|
servo.setup_logging();
|
||||||
|
|
||||||
|
|
|
@ -24,7 +24,7 @@ mod webview;
|
||||||
mod webview_delegate;
|
mod webview_delegate;
|
||||||
|
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
use std::cell::RefCell;
|
use std::cell::{Cell, RefCell};
|
||||||
use std::cmp::max;
|
use std::cmp::max;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
@ -43,7 +43,7 @@ use canvas::WebGLComm;
|
||||||
use canvas_traits::webgl::{GlType, WebGLThreads};
|
use canvas_traits::webgl::{GlType, WebGLThreads};
|
||||||
use clipboard_delegate::StringRequest;
|
use clipboard_delegate::StringRequest;
|
||||||
use compositing::windowing::{EmbedderMethods, WindowMethods};
|
use compositing::windowing::{EmbedderMethods, WindowMethods};
|
||||||
use compositing::{CompositeTarget, IOCompositor, InitialCompositorState, ShutdownState};
|
use compositing::{IOCompositor, InitialCompositorState};
|
||||||
use compositing_traits::{CompositorMsg, CompositorProxy, CompositorReceiver, ConstellationMsg};
|
use compositing_traits::{CompositorMsg, CompositorProxy, CompositorReceiver, ConstellationMsg};
|
||||||
#[cfg(all(
|
#[cfg(all(
|
||||||
not(target_os = "windows"),
|
not(target_os = "windows"),
|
||||||
|
@ -79,7 +79,7 @@ use ipc_channel::router::ROUTER;
|
||||||
pub use keyboard_types::*;
|
pub use keyboard_types::*;
|
||||||
#[cfg(feature = "layout_2013")]
|
#[cfg(feature = "layout_2013")]
|
||||||
pub use layout_thread_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 media::{GlApi, NativeDisplay, WindowGLContext};
|
||||||
use net::protocols::ProtocolRegistry;
|
use net::protocols::ProtocolRegistry;
|
||||||
use net::resource_thread::new_resource_threads;
|
use net::resource_thread::new_resource_threads;
|
||||||
|
@ -201,6 +201,9 @@ pub struct Servo {
|
||||||
compositor: Rc<RefCell<IOCompositor>>,
|
compositor: Rc<RefCell<IOCompositor>>,
|
||||||
constellation_proxy: ConstellationProxy,
|
constellation_proxy: ConstellationProxy,
|
||||||
embedder_receiver: Receiver<EmbedderMsg>,
|
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
|
/// 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.
|
/// as `Weak` references so that the embedding application can control their lifetime.
|
||||||
/// When accessed, `Servo` will be reponsible for cleaning up the invalid `Weak`
|
/// When accessed, `Servo` will be reponsible for cleaning up the invalid `Weak`
|
||||||
|
@ -261,7 +264,6 @@ impl Servo {
|
||||||
mut embedder: Box<dyn EmbedderMethods>,
|
mut embedder: Box<dyn EmbedderMethods>,
|
||||||
window: Rc<dyn WindowMethods>,
|
window: Rc<dyn WindowMethods>,
|
||||||
user_agent: Option<String>,
|
user_agent: Option<String>,
|
||||||
composite_target: CompositeTarget,
|
|
||||||
) -> Self {
|
) -> Self {
|
||||||
// Global configuration options, parsed from the command line.
|
// Global configuration options, parsed from the command line.
|
||||||
opts::set_options(opts);
|
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
|
// The compositor coordinates with the client window to create the final
|
||||||
// rendered page and display it somewhere.
|
// rendered page and display it somewhere.
|
||||||
|
let shutdown_state = Rc::new(Cell::new(ShutdownState::NotShuttingDown));
|
||||||
let compositor = IOCompositor::new(
|
let compositor = IOCompositor::new(
|
||||||
window,
|
window,
|
||||||
InitialCompositorState {
|
InitialCompositorState {
|
||||||
|
@ -529,18 +526,18 @@ impl Servo {
|
||||||
webrender_gl,
|
webrender_gl,
|
||||||
#[cfg(feature = "webxr")]
|
#[cfg(feature = "webxr")]
|
||||||
webxr_main_thread,
|
webxr_main_thread,
|
||||||
|
shutdown_state: shutdown_state.clone(),
|
||||||
},
|
},
|
||||||
composite_target,
|
|
||||||
opts.exit_after_load,
|
|
||||||
opts.debug.convert_mouse_to_touch,
|
opts.debug.convert_mouse_to_touch,
|
||||||
embedder.get_version_string().unwrap_or_default(),
|
embedder.get_version_string().unwrap_or_default(),
|
||||||
);
|
);
|
||||||
|
|
||||||
Servo {
|
Self {
|
||||||
delegate: RefCell::new(Rc::new(DefaultServoDelegate)),
|
delegate: RefCell::new(Rc::new(DefaultServoDelegate)),
|
||||||
compositor: Rc::new(RefCell::new(compositor)),
|
compositor: Rc::new(RefCell::new(compositor)),
|
||||||
constellation_proxy: ConstellationProxy::new(constellation_chan),
|
constellation_proxy: ConstellationProxy::new(constellation_chan),
|
||||||
embedder_receiver,
|
embedder_receiver,
|
||||||
|
shutdown_state,
|
||||||
webviews: Default::default(),
|
webviews: Default::default(),
|
||||||
_js_engine_setup: js_engine_setup,
|
_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
|
/// 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.
|
/// has finished shutting down and you should not spin the event loop any longer.
|
||||||
pub fn spin_event_loop(&self) -> bool {
|
pub fn spin_event_loop(&self) -> bool {
|
||||||
if self.compositor.borrow().shutdown_state() == ShutdownState::FinishedShuttingDown {
|
if self.shutdown_state.get() == ShutdownState::FinishedShuttingDown {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
self.compositor.borrow_mut().receive_messages();
|
self.compositor.borrow_mut().receive_messages();
|
||||||
|
|
||||||
// Only handle incoming embedder messages if the compositor hasn't already started shutting down.
|
// 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() {
|
while let Ok(message) = self.embedder_receiver.try_recv() {
|
||||||
self.handle_embedder_message(message)
|
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.send_new_frame_ready_messages();
|
||||||
self.clean_up_destroyed_webview_handles();
|
self.clean_up_destroyed_webview_handles();
|
||||||
|
|
||||||
if self.compositor.borrow().shutdown_state() == ShutdownState::FinishedShuttingDown {
|
if self.shutdown_state.get() == ShutdownState::FinishedShuttingDown {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -641,7 +640,20 @@ impl Servo {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn start_shutting_down(&self) {
|
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) {
|
pub fn deinit(&self) {
|
||||||
|
@ -675,6 +687,7 @@ impl Servo {
|
||||||
|
|
||||||
fn handle_embedder_message(&self, message: EmbedderMsg) {
|
fn handle_embedder_message(&self, message: EmbedderMsg) {
|
||||||
match message {
|
match message {
|
||||||
|
EmbedderMsg::ShutdownComplete => self.finish_shutting_down(),
|
||||||
EmbedderMsg::Status(webview_id, status_text) => {
|
EmbedderMsg::Status(webview_id, status_text) => {
|
||||||
if let Some(webview) = self.get_webview_handle(webview_id) {
|
if let Some(webview) = self.get_webview_handle(webview_id) {
|
||||||
webview.set_status_text(status_text);
|
webview.set_status_text(status_text);
|
||||||
|
|
|
@ -436,7 +436,10 @@ impl WebView {
|
||||||
.send(ConstellationMsg::SendError(Some(self.id()), message));
|
.send(ConstellationMsg::SendError(Some(self.id()), message));
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn paint(&self) {
|
/// Paint the contents of this [`WebView`] into its `RenderingContext`. This will
|
||||||
self.inner().compositor.borrow_mut().composite();
|
/// 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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -58,10 +58,6 @@ impl CompositorReceiver {
|
||||||
|
|
||||||
/// Messages from (or via) the constellation thread to the compositor.
|
/// Messages from (or via) the constellation thread to the compositor.
|
||||||
pub enum CompositorMsg {
|
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.
|
/// Alerts the compositor that the given pipeline has changed whether it is running animations.
|
||||||
ChangeRunningAnimationsState(PipelineId, AnimationState),
|
ChangeRunningAnimationsState(PipelineId, AnimationState),
|
||||||
/// Create or update a webview, given its frame tree.
|
/// Create or update a webview, given its frame tree.
|
||||||
|
@ -118,7 +114,6 @@ pub struct CompositionPipeline {
|
||||||
impl Debug for CompositorMsg {
|
impl Debug for CompositorMsg {
|
||||||
fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
|
fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
|
||||||
match *self {
|
match *self {
|
||||||
CompositorMsg::ShutdownComplete => write!(f, "ShutdownComplete"),
|
|
||||||
CompositorMsg::ChangeRunningAnimationsState(_, state) => {
|
CompositorMsg::ChangeRunningAnimationsState(_, state) => {
|
||||||
write!(f, "ChangeRunningAnimationsState({:?})", state)
|
write!(f, "ChangeRunningAnimationsState({:?})", state)
|
||||||
},
|
},
|
||||||
|
|
|
@ -22,6 +22,15 @@ use webrender_api::units::{DeviceIntPoint, DeviceIntRect, DeviceIntSize};
|
||||||
|
|
||||||
pub use crate::input_events::*;
|
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
|
/// A cursor for the window. This is different from a CSS cursor (see
|
||||||
/// `CursorKind`) in that it has no `Auto` value.
|
/// `CursorKind`) in that it has no `Auto` value.
|
||||||
#[repr(u8)]
|
#[repr(u8)]
|
||||||
|
@ -257,6 +266,10 @@ pub enum EmbedderMsg {
|
||||||
PlayGamepadHapticEffect(WebViewId, usize, GamepadHapticEffectType, IpcSender<bool>),
|
PlayGamepadHapticEffect(WebViewId, usize, GamepadHapticEffectType, IpcSender<bool>),
|
||||||
/// Request to stop a haptic effect on a connected gamepad.
|
/// Request to stop a haptic effect on a connected gamepad.
|
||||||
StopGamepadHapticEffect(WebViewId, usize, IpcSender<bool>),
|
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 {
|
impl Debug for EmbedderMsg {
|
||||||
|
@ -302,6 +315,7 @@ impl Debug for EmbedderMsg {
|
||||||
EmbedderMsg::ShowContextMenu(..) => write!(f, "ShowContextMenu"),
|
EmbedderMsg::ShowContextMenu(..) => write!(f, "ShowContextMenu"),
|
||||||
EmbedderMsg::PlayGamepadHapticEffect(..) => write!(f, "PlayGamepadHapticEffect"),
|
EmbedderMsg::PlayGamepadHapticEffect(..) => write!(f, "PlayGamepadHapticEffect"),
|
||||||
EmbedderMsg::StopGamepadHapticEffect(..) => write!(f, "StopGamepadHapticEffect"),
|
EmbedderMsg::StopGamepadHapticEffect(..) => write!(f, "StopGamepadHapticEffect"),
|
||||||
|
EmbedderMsg::ShutdownComplete => write!(f, "ShutdownComplete"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -66,6 +66,7 @@ keyboard-types = { workspace = true }
|
||||||
log = { workspace = true }
|
log = { workspace = true }
|
||||||
getopts = { workspace = true }
|
getopts = { workspace = true }
|
||||||
hitrace = { workspace = true, optional = true }
|
hitrace = { workspace = true, optional = true }
|
||||||
|
image = { workspace = true }
|
||||||
mime_guess = { workspace = true }
|
mime_guess = { workspace = true }
|
||||||
url = { workspace = true }
|
url = { workspace = true }
|
||||||
raw-window-handle = { workspace = true }
|
raw-window-handle = { workspace = true }
|
||||||
|
@ -125,9 +126,6 @@ tinyfiledialogs = "3.0"
|
||||||
egui-file-dialog = "0.9.0"
|
egui-file-dialog = "0.9.0"
|
||||||
winit = "0.30.9"
|
winit = "0.30.9"
|
||||||
|
|
||||||
[target.'cfg(any(all(target_os = "linux", not(target_env = "ohos")), target_os = "windows"))'.dependencies]
|
|
||||||
image = { workspace = true }
|
|
||||||
|
|
||||||
[target.'cfg(any(all(target_os = "linux", not(target_env = "ohos")), target_os = "macos"))'.dependencies]
|
[target.'cfg(any(all(target_os = "linux", not(target_env = "ohos")), target_os = "macos"))'.dependencies]
|
||||||
sig = "1.0"
|
sig = "1.0"
|
||||||
|
|
||||||
|
|
|
@ -12,7 +12,6 @@ use std::{env, fs};
|
||||||
|
|
||||||
use log::{info, trace, warn};
|
use log::{info, trace, warn};
|
||||||
use servo::compositing::windowing::{AnimationState, WindowMethods};
|
use servo::compositing::windowing::{AnimationState, WindowMethods};
|
||||||
use servo::compositing::CompositeTarget;
|
|
||||||
use servo::config::opts::Opts;
|
use servo::config::opts::Opts;
|
||||||
use servo::config::prefs::Preferences;
|
use servo::config::prefs::Preferences;
|
||||||
use servo::servo_config::pref;
|
use servo::servo_config::pref;
|
||||||
|
@ -99,11 +98,7 @@ impl App {
|
||||||
assert_eq!(headless, event_loop.is_none());
|
assert_eq!(headless, event_loop.is_none());
|
||||||
let window = match event_loop {
|
let window = match event_loop {
|
||||||
Some(event_loop) => {
|
Some(event_loop) => {
|
||||||
let window = headed_window::Window::new(
|
let window = headed_window::Window::new(&self.servoshell_preferences, event_loop);
|
||||||
&self.opts,
|
|
||||||
&self.servoshell_preferences,
|
|
||||||
event_loop,
|
|
||||||
);
|
|
||||||
self.minibrowser = Some(Minibrowser::new(
|
self.minibrowser = Some(Minibrowser::new(
|
||||||
window.offscreen_rendering_context(),
|
window.offscreen_rendering_context(),
|
||||||
event_loop,
|
event_loop,
|
||||||
|
@ -158,11 +153,14 @@ impl App {
|
||||||
embedder,
|
embedder,
|
||||||
Rc::new(UpcastedWindow(window.clone())),
|
Rc::new(UpcastedWindow(window.clone())),
|
||||||
self.servoshell_preferences.user_agent.clone(),
|
self.servoshell_preferences.user_agent.clone(),
|
||||||
CompositeTarget::ContextFbo,
|
|
||||||
);
|
);
|
||||||
servo.setup_logging();
|
servo.setup_logging();
|
||||||
|
|
||||||
let running_state = Rc::new(RunningAppState::new(servo, window.clone(), headless));
|
let running_state = Rc::new(RunningAppState::new(
|
||||||
|
servo,
|
||||||
|
window.clone(),
|
||||||
|
self.servoshell_preferences.clone(),
|
||||||
|
));
|
||||||
running_state.new_toplevel_webview(self.initial_url.clone().into_url());
|
running_state.new_toplevel_webview(self.initial_url.clone().into_url());
|
||||||
|
|
||||||
if let Some(ref mut minibrowser) = self.minibrowser {
|
if let Some(ref mut minibrowser) = self.minibrowser {
|
||||||
|
|
|
@ -9,6 +9,7 @@ use std::rc::Rc;
|
||||||
use std::thread;
|
use std::thread;
|
||||||
|
|
||||||
use euclid::Vector2D;
|
use euclid::Vector2D;
|
||||||
|
use image::DynamicImage;
|
||||||
use keyboard_types::{Key, KeyboardEvent, Modifiers, ShortcutMatcher};
|
use keyboard_types::{Key, KeyboardEvent, Modifiers, ShortcutMatcher};
|
||||||
use log::{error, info};
|
use log::{error, info};
|
||||||
use servo::base::id::WebViewId;
|
use servo::base::id::WebViewId;
|
||||||
|
@ -30,6 +31,7 @@ use super::dialog::Dialog;
|
||||||
use super::gamepad::GamepadSupport;
|
use super::gamepad::GamepadSupport;
|
||||||
use super::keyutils::CMD_OR_CONTROL;
|
use super::keyutils::CMD_OR_CONTROL;
|
||||||
use super::window_trait::{WindowPortsMethods, LINE_HEIGHT};
|
use super::window_trait::{WindowPortsMethods, LINE_HEIGHT};
|
||||||
|
use crate::prefs::ServoShellPreferences;
|
||||||
|
|
||||||
pub(crate) enum AppState {
|
pub(crate) enum AppState {
|
||||||
Initializing,
|
Initializing,
|
||||||
|
@ -42,13 +44,13 @@ pub(crate) struct RunningAppState {
|
||||||
/// `inner` so that we can keep a reference to Servo in order to spin the event loop,
|
/// `inner` so that we can keep a reference to Servo in order to spin the event loop,
|
||||||
/// which will in turn call delegates doing a mutable borrow on `inner`.
|
/// which will in turn call delegates doing a mutable borrow on `inner`.
|
||||||
servo: Servo,
|
servo: Servo,
|
||||||
|
/// The preferences for this run of servoshell. This is not mutable, so doesn't need to
|
||||||
|
/// be stored inside the [`RunningAppStateInner`].
|
||||||
|
servoshell_preferences: ServoShellPreferences,
|
||||||
inner: RefCell<RunningAppStateInner>,
|
inner: RefCell<RunningAppStateInner>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct RunningAppStateInner {
|
pub struct RunningAppStateInner {
|
||||||
/// Whether or not this is a headless servoshell window.
|
|
||||||
headless: bool,
|
|
||||||
|
|
||||||
/// List of top-level browsing contexts.
|
/// List of top-level browsing contexts.
|
||||||
/// Modified by EmbedderMsg::WebViewOpened and EmbedderMsg::WebViewClosed,
|
/// Modified by EmbedderMsg::WebViewOpened and EmbedderMsg::WebViewClosed,
|
||||||
/// and we exit if it ever becomes empty.
|
/// and we exit if it ever becomes empty.
|
||||||
|
@ -88,13 +90,13 @@ impl RunningAppState {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
servo: Servo,
|
servo: Servo,
|
||||||
window: Rc<dyn WindowPortsMethods>,
|
window: Rc<dyn WindowPortsMethods>,
|
||||||
headless: bool,
|
servoshell_preferences: ServoShellPreferences,
|
||||||
) -> RunningAppState {
|
) -> RunningAppState {
|
||||||
servo.set_delegate(Rc::new(ServoShellServoDelegate));
|
servo.set_delegate(Rc::new(ServoShellServoDelegate));
|
||||||
RunningAppState {
|
RunningAppState {
|
||||||
servo,
|
servo,
|
||||||
|
servoshell_preferences,
|
||||||
inner: RefCell::new(RunningAppStateInner {
|
inner: RefCell::new(RunningAppStateInner {
|
||||||
headless,
|
|
||||||
webviews: HashMap::default(),
|
webviews: HashMap::default(),
|
||||||
creation_order: Default::default(),
|
creation_order: Default::default(),
|
||||||
focused_webview_id: None,
|
focused_webview_id: None,
|
||||||
|
@ -125,6 +127,36 @@ impl RunningAppState {
|
||||||
&self.servo
|
&self.servo
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn save_output_image_if_necessary(&self) {
|
||||||
|
let Some(output_path) = self.servoshell_preferences.output_image_path.as_ref() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let inner = self.inner();
|
||||||
|
let viewport_rect = inner
|
||||||
|
.window
|
||||||
|
.get_coordinates()
|
||||||
|
.viewport
|
||||||
|
.to_rect()
|
||||||
|
.to_untyped()
|
||||||
|
.to_u32();
|
||||||
|
let Some(image) = inner
|
||||||
|
.window
|
||||||
|
.rendering_context()
|
||||||
|
.read_to_image(viewport_rect)
|
||||||
|
else {
|
||||||
|
error!("Failed to read output image.");
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Err(error) = DynamicImage::ImageRgba8(image).save(output_path) {
|
||||||
|
error!("Failed to save {output_path}: {error}.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Repaint the Servo view is necessary, returning true if anything was actually
|
||||||
|
/// painted or false otherwise. Something may not be painted if Servo is waiting
|
||||||
|
/// for a stable image to paint.
|
||||||
pub(crate) fn repaint_servo_if_necessary(&self) {
|
pub(crate) fn repaint_servo_if_necessary(&self) {
|
||||||
if !self.inner().need_repaint {
|
if !self.inner().need_repaint {
|
||||||
return;
|
return;
|
||||||
|
@ -132,10 +164,21 @@ impl RunningAppState {
|
||||||
let Some(webview) = self.focused_webview() else {
|
let Some(webview) = self.focused_webview() else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
if !webview.paint() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
webview.paint();
|
// This needs to be done before presenting(), because `ReneringContext::read_to_image` reads
|
||||||
self.inner().window.rendering_context().present();
|
// from the back buffer.
|
||||||
self.inner_mut().need_repaint = false;
|
self.save_output_image_if_necessary();
|
||||||
|
|
||||||
|
let mut inner_mut = self.inner_mut();
|
||||||
|
inner_mut.window.rendering_context().present();
|
||||||
|
inner_mut.need_repaint = false;
|
||||||
|
|
||||||
|
if self.servoshell_preferences.exit_after_stable_image {
|
||||||
|
self.servo().start_shutting_down();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Spins the internal application event loop.
|
/// Spins the internal application event loop.
|
||||||
|
@ -370,7 +413,7 @@ impl WebViewDelegate for RunningAppState {
|
||||||
definition: PromptDefinition,
|
definition: PromptDefinition,
|
||||||
_origin: PromptOrigin,
|
_origin: PromptOrigin,
|
||||||
) {
|
) {
|
||||||
if self.inner().headless {
|
if self.servoshell_preferences.headless {
|
||||||
let _ = match definition {
|
let _ = match definition {
|
||||||
PromptDefinition::Alert(_message, sender) => sender.send(()),
|
PromptDefinition::Alert(_message, sender) => sender.send(()),
|
||||||
PromptDefinition::OkCancel(_message, sender) => sender.send(PromptResult::Primary),
|
PromptDefinition::OkCancel(_message, sender) => sender.send(PromptResult::Primary),
|
||||||
|
@ -401,7 +444,7 @@ impl WebViewDelegate for RunningAppState {
|
||||||
webview: WebView,
|
webview: WebView,
|
||||||
authentication_request: AuthenticationRequest,
|
authentication_request: AuthenticationRequest,
|
||||||
) {
|
) {
|
||||||
if self.inner().headless {
|
if self.servoshell_preferences.headless {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -493,7 +536,7 @@ impl WebViewDelegate for RunningAppState {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn request_permission(&self, _webview: servo::WebView, request: PermissionRequest) {
|
fn request_permission(&self, _webview: servo::WebView, request: PermissionRequest) {
|
||||||
if !self.inner().headless {
|
if !self.servoshell_preferences.headless {
|
||||||
prompt_user(request);
|
prompt_user(request);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,7 +29,8 @@ pub fn main() {
|
||||||
crate::init_tracing(servoshell_preferences.tracing_filter.as_deref());
|
crate::init_tracing(servoshell_preferences.tracing_filter.as_deref());
|
||||||
|
|
||||||
let clean_shutdown = servoshell_preferences.clean_shutdown;
|
let clean_shutdown = servoshell_preferences.clean_shutdown;
|
||||||
let event_loop = EventsLoop::new(servoshell_preferences.headless, opts.output_file.is_some())
|
let has_output_file = servoshell_preferences.output_image_path.is_some();
|
||||||
|
let event_loop = EventsLoop::new(servoshell_preferences.headless, has_output_file)
|
||||||
.expect("Failed to create events loop");
|
.expect("Failed to create events loop");
|
||||||
|
|
||||||
{
|
{
|
||||||
|
|
|
@ -17,7 +17,6 @@ use raw_window_handle::{HasDisplayHandle, HasWindowHandle};
|
||||||
use servo::compositing::windowing::{
|
use servo::compositing::windowing::{
|
||||||
AnimationState, EmbedderCoordinates, WebRenderDebugOption, WindowMethods,
|
AnimationState, EmbedderCoordinates, WebRenderDebugOption, WindowMethods,
|
||||||
};
|
};
|
||||||
use servo::config::opts::Opts;
|
|
||||||
use servo::servo_config::pref;
|
use servo::servo_config::pref;
|
||||||
use servo::servo_geometry::DeviceIndependentPixel;
|
use servo::servo_geometry::DeviceIndependentPixel;
|
||||||
use servo::webrender_api::units::{DeviceIntPoint, DeviceIntRect, DeviceIntSize, DevicePixel};
|
use servo::webrender_api::units::{DeviceIntPoint, DeviceIntRect, DeviceIntSize, DevicePixel};
|
||||||
|
@ -78,24 +77,17 @@ pub struct Window {
|
||||||
|
|
||||||
impl Window {
|
impl Window {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
opts: &Opts,
|
|
||||||
servoshell_preferences: &ServoShellPreferences,
|
servoshell_preferences: &ServoShellPreferences,
|
||||||
event_loop: &ActiveEventLoop,
|
event_loop: &ActiveEventLoop,
|
||||||
) -> Window {
|
) -> Window {
|
||||||
// If there's no chrome, start off with the window invisible. It will be set to visible in
|
|
||||||
// `load_end()`. This avoids an ugly flash of unstyled content (especially important since
|
|
||||||
// unstyled content is white and chrome often has a transparent background). See issue
|
|
||||||
// #9996.
|
|
||||||
let no_native_titlebar = servoshell_preferences.no_native_titlebar;
|
let no_native_titlebar = servoshell_preferences.no_native_titlebar;
|
||||||
let visible = opts.output_file.is_none() && !servoshell_preferences.no_native_titlebar;
|
|
||||||
|
|
||||||
let window_size = servoshell_preferences.initial_window_size;
|
let window_size = servoshell_preferences.initial_window_size;
|
||||||
let window_attr = winit::window::Window::default_attributes()
|
let window_attr = winit::window::Window::default_attributes()
|
||||||
.with_title("Servo".to_string())
|
.with_title("Servo".to_string())
|
||||||
.with_decorations(!no_native_titlebar)
|
.with_decorations(!no_native_titlebar)
|
||||||
.with_transparent(no_native_titlebar)
|
.with_transparent(no_native_titlebar)
|
||||||
.with_inner_size(LogicalSize::new(window_size.width, window_size.height))
|
.with_inner_size(LogicalSize::new(window_size.width, window_size.height))
|
||||||
.with_visible(visible);
|
.with_visible(true);
|
||||||
|
|
||||||
#[allow(deprecated)]
|
#[allow(deprecated)]
|
||||||
let winit_window = event_loop
|
let winit_window = event_loop
|
||||||
|
|
|
@ -7,7 +7,6 @@ use std::mem;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
use raw_window_handle::{DisplayHandle, RawDisplayHandle, RawWindowHandle, WindowHandle};
|
use raw_window_handle::{DisplayHandle, RawDisplayHandle, RawWindowHandle, WindowHandle};
|
||||||
use servo::compositing::CompositeTarget;
|
|
||||||
pub use servo::webrender_api::units::DeviceIntRect;
|
pub use servo::webrender_api::units::DeviceIntRect;
|
||||||
/// The EventLoopWaker::wake function will be called from any thread.
|
/// The EventLoopWaker::wake function will be called from any thread.
|
||||||
/// It will be called to notify embedder that some events are available,
|
/// It will be called to notify embedder that some events are available,
|
||||||
|
@ -97,7 +96,6 @@ pub fn init(
|
||||||
embedder_callbacks,
|
embedder_callbacks,
|
||||||
window_callbacks.clone(),
|
window_callbacks.clone(),
|
||||||
None,
|
None,
|
||||||
CompositeTarget::ContextFbo,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
APP.with(|app| {
|
APP.with(|app| {
|
||||||
|
|
|
@ -12,7 +12,6 @@ use raw_window_handle::{
|
||||||
DisplayHandle, OhosDisplayHandle, OhosNdkWindowHandle, RawDisplayHandle, RawWindowHandle,
|
DisplayHandle, OhosDisplayHandle, OhosNdkWindowHandle, RawDisplayHandle, RawWindowHandle,
|
||||||
WindowHandle,
|
WindowHandle,
|
||||||
};
|
};
|
||||||
use servo::compositing::CompositeTarget;
|
|
||||||
/// The EventLoopWaker::wake function will be called from any thread.
|
/// The EventLoopWaker::wake function will be called from any thread.
|
||||||
/// It will be called to notify embedder that some events are available,
|
/// It will be called to notify embedder that some events are available,
|
||||||
/// and that perform_updates need to be called
|
/// and that perform_updates need to be called
|
||||||
|
@ -113,7 +112,6 @@ pub fn init(
|
||||||
embedder_callbacks,
|
embedder_callbacks,
|
||||||
window_callbacks.clone(),
|
window_callbacks.clone(),
|
||||||
None, /* user_agent */
|
None, /* user_agent */
|
||||||
CompositeTarget::ContextFbo,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
let app_state = RunningAppState::new(
|
let app_state = RunningAppState::new(
|
||||||
|
|
|
@ -19,6 +19,7 @@ use servo::servo_url::ServoUrl;
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
#[cfg_attr(any(target_os = "android", target_env = "ohos"), allow(dead_code))]
|
#[cfg_attr(any(target_os = "android", target_env = "ohos"), allow(dead_code))]
|
||||||
|
#[derive(Clone)]
|
||||||
pub(crate) struct ServoShellPreferences {
|
pub(crate) struct ServoShellPreferences {
|
||||||
/// The user agent to use for servoshell.
|
/// The user agent to use for servoshell.
|
||||||
pub user_agent: Option<String>,
|
pub user_agent: Option<String>,
|
||||||
|
@ -48,6 +49,11 @@ pub(crate) struct ServoShellPreferences {
|
||||||
/// An override for the screen resolution. This is useful for testing behavior on different screen sizes,
|
/// An override for the screen resolution. This is useful for testing behavior on different screen sizes,
|
||||||
/// such as the screen of a mobile device.
|
/// such as the screen of a mobile device.
|
||||||
pub screen_size_override: Option<Size2D<u32, DeviceIndependentPixel>>,
|
pub screen_size_override: Option<Size2D<u32, DeviceIndependentPixel>>,
|
||||||
|
/// If not-None, the path to a file to output the default WebView's rendered output
|
||||||
|
/// after waiting for a stable image, this implies `Self::exit_after_load`.
|
||||||
|
pub output_image_path: Option<String>,
|
||||||
|
/// Whether or not to exit after Servo detects a stable output image in all WebViews.
|
||||||
|
pub exit_after_stable_image: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for ServoShellPreferences {
|
impl Default for ServoShellPreferences {
|
||||||
|
@ -64,6 +70,8 @@ impl Default for ServoShellPreferences {
|
||||||
tracing_filter: None,
|
tracing_filter: None,
|
||||||
url: None,
|
url: None,
|
||||||
user_agent: None,
|
user_agent: None,
|
||||||
|
output_image_path: None,
|
||||||
|
exit_after_stable_image: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -168,7 +176,13 @@ pub(crate) fn parse_command_line_arguments(args: Vec<String>) -> ArgumentParsing
|
||||||
|
|
||||||
let mut opts = Options::new();
|
let mut opts = Options::new();
|
||||||
opts.optflag("", "legacy-layout", "Use the legacy layout engine");
|
opts.optflag("", "legacy-layout", "Use the legacy layout engine");
|
||||||
opts.optopt("o", "output", "Output file", "output.png");
|
opts.optopt(
|
||||||
|
"o",
|
||||||
|
"output",
|
||||||
|
"Path to an output image. The format of the image is determined by the extension. \
|
||||||
|
Supports all formats that `rust-image` does.",
|
||||||
|
"output.png",
|
||||||
|
);
|
||||||
opts.optopt("s", "size", "Size of tiles", "512");
|
opts.optopt("s", "size", "Size of tiles", "512");
|
||||||
opts.optflagopt(
|
opts.optflagopt(
|
||||||
"p",
|
"p",
|
||||||
|
@ -190,7 +204,11 @@ pub(crate) fn parse_command_line_arguments(args: Vec<String>) -> ArgumentParsing
|
||||||
"Memory profiler flag and output interval",
|
"Memory profiler flag and output interval",
|
||||||
"10",
|
"10",
|
||||||
);
|
);
|
||||||
opts.optflag("x", "exit", "Exit after load flag");
|
opts.optflag(
|
||||||
|
"x",
|
||||||
|
"exit",
|
||||||
|
"Exit after Servo has loaded the page and detected a stable output image",
|
||||||
|
);
|
||||||
opts.optopt(
|
opts.optopt(
|
||||||
"y",
|
"y",
|
||||||
"layout-threads",
|
"layout-threads",
|
||||||
|
@ -546,8 +564,8 @@ pub(crate) fn parse_command_line_arguments(args: Vec<String>) -> ArgumentParsing
|
||||||
});
|
});
|
||||||
|
|
||||||
// If an output file is specified the device pixel ratio is always 1.
|
// If an output file is specified the device pixel ratio is always 1.
|
||||||
let output_file = opt_match.opt_str("o");
|
let output_image_path = opt_match.opt_str("o");
|
||||||
if output_file.is_some() {
|
if output_image_path.is_some() {
|
||||||
device_pixel_ratio_override = Some(1.0);
|
device_pixel_ratio_override = Some(1.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -564,6 +582,8 @@ pub(crate) fn parse_command_line_arguments(args: Vec<String>) -> ArgumentParsing
|
||||||
preferences.js_ion_enabled = false;
|
preferences.js_ion_enabled = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let exit_after_load = opt_match.opt_present("x") || output_image_path.is_some();
|
||||||
|
let wait_for_stable_image = exit_after_load || webdriver_port.is_some();
|
||||||
let servoshell_preferences = ServoShellPreferences {
|
let servoshell_preferences = ServoShellPreferences {
|
||||||
user_agent: opt_match.opt_str("u"),
|
user_agent: opt_match.opt_str("u"),
|
||||||
url,
|
url,
|
||||||
|
@ -574,6 +594,8 @@ pub(crate) fn parse_command_line_arguments(args: Vec<String>) -> ArgumentParsing
|
||||||
tracing_filter,
|
tracing_filter,
|
||||||
initial_window_size,
|
initial_window_size,
|
||||||
screen_size_override,
|
screen_size_override,
|
||||||
|
output_image_path,
|
||||||
|
exit_after_stable_image: exit_after_load,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -584,6 +606,7 @@ pub(crate) fn parse_command_line_arguments(args: Vec<String>) -> ArgumentParsing
|
||||||
|
|
||||||
let opts = Opts {
|
let opts = Opts {
|
||||||
debug: debug_options.clone(),
|
debug: debug_options.clone(),
|
||||||
|
wait_for_stable_image,
|
||||||
legacy_layout,
|
legacy_layout,
|
||||||
time_profiling,
|
time_profiling,
|
||||||
time_profiler_trace_path: opt_match.opt_str("profiler-trace-path"),
|
time_profiler_trace_path: opt_match.opt_str("profiler-trace-path"),
|
||||||
|
@ -591,7 +614,6 @@ pub(crate) fn parse_command_line_arguments(args: Vec<String>) -> ArgumentParsing
|
||||||
nonincremental_layout,
|
nonincremental_layout,
|
||||||
userscripts: opt_match.opt_default("userscripts", ""),
|
userscripts: opt_match.opt_default("userscripts", ""),
|
||||||
user_stylesheets,
|
user_stylesheets,
|
||||||
output_file,
|
|
||||||
hard_fail: opt_match.opt_present("f") && !opt_match.opt_present("F"),
|
hard_fail: opt_match.opt_present("f") && !opt_match.opt_present("F"),
|
||||||
webdriver_port,
|
webdriver_port,
|
||||||
multiprocess: opt_match.opt_present("M"),
|
multiprocess: opt_match.opt_present("M"),
|
||||||
|
@ -599,7 +621,6 @@ pub(crate) fn parse_command_line_arguments(args: Vec<String>) -> ArgumentParsing
|
||||||
sandbox: opt_match.opt_present("S"),
|
sandbox: opt_match.opt_present("S"),
|
||||||
random_pipeline_closure_probability,
|
random_pipeline_closure_probability,
|
||||||
random_pipeline_closure_seed,
|
random_pipeline_closure_seed,
|
||||||
exit_after_load: opt_match.opt_present("x"),
|
|
||||||
config_dir,
|
config_dir,
|
||||||
shaders_dir: opt_match.opt_str("shaders").map(Into::into),
|
shaders_dir: opt_match.opt_str("shaders").map(Into::into),
|
||||||
certificate_path: opt_match.opt_str("certificate-path"),
|
certificate_path: opt_match.opt_str("certificate-path"),
|
||||||
|
|
2
tests/wpt/meta/MANIFEST.json
vendored
2
tests/wpt/meta/MANIFEST.json
vendored
|
@ -506147,7 +506147,7 @@
|
||||||
[]
|
[]
|
||||||
],
|
],
|
||||||
"executorservo.py": [
|
"executorservo.py": [
|
||||||
"e3369d24ebc6d6aac1b4632ef673a36602417745",
|
"2710b1b844c8e93c251c91c8809697d357bd4f06",
|
||||||
[]
|
[]
|
||||||
],
|
],
|
||||||
"executorservodriver.py": [
|
"executorservodriver.py": [
|
||||||
|
|
|
@ -226,6 +226,7 @@ class ServoRefTestExecutor(ServoExecutor):
|
||||||
|
|
||||||
def screenshot(self, test, viewport_size, dpi, page_ranges):
|
def screenshot(self, test, viewport_size, dpi, page_ranges):
|
||||||
with TempFilename(self.tempdir) as output_path:
|
with TempFilename(self.tempdir) as output_path:
|
||||||
|
output_path = f"{output_path}.png"
|
||||||
extra_args = ["--exit",
|
extra_args = ["--exit",
|
||||||
"--output=%s" % output_path,
|
"--output=%s" % output_path,
|
||||||
"--window-size", viewport_size or "800x600"]
|
"--window-size", viewport_size or "800x600"]
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue