mirror of
https://github.com/servo/servo.git
synced 2025-09-29 16:19:14 +01:00
libservo: Add a WebView::take_screenshot()
API and use it for reftests
Co-authored-by: Delan Azabani <dazabani@igalia.com> Signed-off-by: Martin Robinson <mrobinson@igalia.com>
This commit is contained in:
parent
92dd54b1ec
commit
ebb12cb298
25 changed files with 481 additions and 414 deletions
|
@ -2,7 +2,7 @@
|
|||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
use std::cell::{Ref, RefCell, RefMut};
|
||||
use std::cell::{Cell, Ref, RefCell, RefMut};
|
||||
use std::collections::HashMap;
|
||||
use std::collections::hash_map::Entry;
|
||||
use std::mem;
|
||||
|
@ -11,6 +11,7 @@ use std::rc::Rc;
|
|||
|
||||
use crossbeam_channel::Receiver;
|
||||
use embedder_traits::webdriver::WebDriverSenders;
|
||||
use image::{DynamicImage, ImageFormat};
|
||||
use keyboard_types::ShortcutMatcher;
|
||||
use log::{error, info};
|
||||
use servo::base::generic_channel::GenericSender;
|
||||
|
@ -31,7 +32,6 @@ use super::dialog::Dialog;
|
|||
use super::gamepad::GamepadSupport;
|
||||
use super::keyutils::CMD_OR_CONTROL;
|
||||
use super::window_trait::WindowPortsMethods;
|
||||
use crate::output_image::save_output_image_if_necessary;
|
||||
use crate::prefs::ServoShellPreferences;
|
||||
|
||||
pub(crate) enum AppState {
|
||||
|
@ -91,6 +91,10 @@ pub struct RunningAppStateInner {
|
|||
/// List of webviews that have favicon textures which are not yet uploaded
|
||||
/// to the GPU by egui.
|
||||
pending_favicon_loads: Vec<WebViewId>,
|
||||
|
||||
/// Whether or not the application has achieved stable image output. This is used
|
||||
/// for the `exit_after_stable_image` option.
|
||||
acheived_stable_image: Rc<Cell<bool>>,
|
||||
}
|
||||
|
||||
impl Drop for RunningAppState {
|
||||
|
@ -123,6 +127,7 @@ impl RunningAppState {
|
|||
need_repaint: false,
|
||||
dialog_amount_changed: false,
|
||||
pending_favicon_loads: Default::default(),
|
||||
acheived_stable_image: Default::default(),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
@ -178,24 +183,12 @@ impl RunningAppState {
|
|||
let Some(webview) = self.focused_webview() else {
|
||||
return;
|
||||
};
|
||||
if !webview.paint() {
|
||||
return;
|
||||
}
|
||||
|
||||
// This needs to be done before presenting(), because `ReneringContext::read_to_image` reads
|
||||
// from the back buffer.
|
||||
save_output_image_if_necessary(
|
||||
&self.servoshell_preferences,
|
||||
&self.inner().window.rendering_context(),
|
||||
);
|
||||
webview.paint();
|
||||
|
||||
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.
|
||||
|
@ -220,6 +213,12 @@ impl RunningAppState {
|
|||
|
||||
self.inner_mut().dialog_amount_changed = false;
|
||||
|
||||
if self.servoshell_preferences.exit_after_stable_image &&
|
||||
self.inner().acheived_stable_image.get()
|
||||
{
|
||||
self.servo.start_shutting_down();
|
||||
}
|
||||
|
||||
PumpResult::Continue {
|
||||
need_update,
|
||||
need_window_redraw,
|
||||
|
@ -492,6 +491,44 @@ impl RunningAppState {
|
|||
pub(crate) fn take_pending_favicon_loads(&self) -> Vec<WebViewId> {
|
||||
mem::take(&mut self.inner_mut().pending_favicon_loads)
|
||||
}
|
||||
|
||||
/// If we are exiting after acheiving a stable image or we want to save the display of the
|
||||
/// [`WebView`] to an image file, request a screenshot of the [`WebView`].
|
||||
fn maybe_request_screenshot(&self, webview: WebView) {
|
||||
let output_path = self.servoshell_preferences.output_image_path.clone();
|
||||
if !self.servoshell_preferences.exit_after_stable_image && output_path.is_none() {
|
||||
return;
|
||||
}
|
||||
|
||||
// Never request more than a single screenshot for now.
|
||||
let acheived_stable_image = self.inner().acheived_stable_image.clone();
|
||||
if acheived_stable_image.get() {
|
||||
return;
|
||||
}
|
||||
|
||||
webview.take_screenshot(move |image| {
|
||||
acheived_stable_image.set(true);
|
||||
|
||||
let Some(output_path) = output_path else {
|
||||
return;
|
||||
};
|
||||
|
||||
let image = match image {
|
||||
Ok(image) => image,
|
||||
Err(error) => {
|
||||
error!("Could not take screenshot: {error:?}");
|
||||
return;
|
||||
},
|
||||
};
|
||||
|
||||
let image_format = ImageFormat::from_path(&output_path).unwrap_or(ImageFormat::Png);
|
||||
if let Err(error) =
|
||||
DynamicImage::ImageRgba8(image).save_with_format(output_path, image_format)
|
||||
{
|
||||
error!("Failed to save screenshot: {error}.");
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
struct ServoShellServoDelegate;
|
||||
|
@ -653,6 +690,7 @@ impl WebViewDelegate for RunningAppState {
|
|||
{
|
||||
let _ = sender.send(WebDriverLoadStatus::Complete);
|
||||
}
|
||||
self.maybe_request_screenshot(webview);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -471,6 +471,11 @@ impl Window {
|
|||
);
|
||||
})
|
||||
.shortcut(CMD_OR_CONTROL, 'Q', || state.servo().start_shutting_down())
|
||||
.shortcut(Modifiers::empty(), 'P', || {
|
||||
focused_webview.take_screenshot(|image| {
|
||||
println!("Done taking screenshot: {:?}", image.is_ok());
|
||||
});
|
||||
})
|
||||
.otherwise(|| handled = false);
|
||||
handled
|
||||
}
|
||||
|
|
|
@ -15,7 +15,6 @@ mod crash_handler;
|
|||
pub(crate) mod desktop;
|
||||
#[cfg(any(target_os = "android", target_env = "ohos"))]
|
||||
mod egl;
|
||||
mod output_image;
|
||||
#[cfg(not(any(target_os = "android", target_env = "ohos")))]
|
||||
mod panic_hook;
|
||||
mod parser;
|
||||
|
|
|
@ -1,39 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
use std::rc::Rc;
|
||||
|
||||
use euclid::Point2D;
|
||||
use image::{DynamicImage, ImageFormat};
|
||||
use log::error;
|
||||
use servo::RenderingContext;
|
||||
use servo::webrender_api::units::DeviceIntRect;
|
||||
|
||||
use crate::prefs::ServoShellPreferences;
|
||||
|
||||
/// This needs to be done before presenting(), because `ReneringContext::read_to_image` reads
|
||||
/// from the back buffer. This does nothing if the preference `output_image_path` is not set.
|
||||
pub(crate) fn save_output_image_if_necessary<T>(
|
||||
prefs: &ServoShellPreferences,
|
||||
rendering_context: &Rc<T>,
|
||||
) where
|
||||
T: RenderingContext + ?Sized,
|
||||
{
|
||||
let Some(output_path) = prefs.output_image_path.as_ref() else {
|
||||
return;
|
||||
};
|
||||
|
||||
let size = rendering_context.size2d().to_i32();
|
||||
let viewport_rect = DeviceIntRect::from_origin_and_size(Point2D::origin(), size);
|
||||
let Some(image) = rendering_context.read_to_image(viewport_rect) else {
|
||||
error!("Failed to read output image.");
|
||||
return;
|
||||
};
|
||||
|
||||
let image_format = ImageFormat::from_path(output_path).unwrap_or(ImageFormat::Png);
|
||||
if let Err(error) = DynamicImage::ImageRgba8(image).save_with_format(output_path, image_format)
|
||||
{
|
||||
error!("Failed to save {output_path}: {error}.");
|
||||
}
|
||||
}
|
|
@ -694,7 +694,6 @@ pub(crate) fn parse_command_line_arguments(args: Vec<String>) -> ArgumentParsing
|
|||
|
||||
let opts = Opts {
|
||||
debug: debug_options,
|
||||
wait_for_stable_image: cmd_args.exit,
|
||||
time_profiling: cmd_args.profile,
|
||||
time_profiler_trace_path: cmd_args
|
||||
.profiler_trace_path
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue