webdriver: Use take_screenshot() API in Take (Element) Screenshot (#39587)

WPT tests require us to reliably take screenshots when test pages are
fully loaded and testing is complete, and the WPT runner uses
[test-wait.js](a2f551eb2d/tests/wpt/tests/tools/wptrunner/wptrunner/executors/test-wait.js)
to do this in userland. when testing Servo with [--product
servo](a2f551eb2d/tests/wpt/tests/tools/wptrunner/wptrunner/executors/executorservo.py),
we use servoshell’s --output option, which backs that up with more
reliable waiting in Servo. but when testing Servo with [--product
servodriver](a2f551eb2d/tests/wpt/tests/tools/wptrunner/wptrunner/executors/executorservodriver.py),
we use the WebDriver Take Screenshot action, which currently takes the
screenshot immediately. we think this might be a source of regressions.

this patch makes the WebDriver actions Take Screenshot and Take Element
Screenshot use the same new WebView::take_screenshot() API as
servoshell’s --output option, such that those actions now wait for [a
variety of
conditions](a2f551eb2d/components/servo/webview.rs (L596-L602))
that may affect test output. it’s not clear if this is
[conformant](https://w3c.github.io/webdriver/#screen-capture), so we may
want to refine this to only wait when running tests at some point. other
changes:

- we remove the retry loop where we try to take a screenshot every
second for up to 30 seconds
- we send the result as a image::RgbaImage over crossbeam without shared
memory (it’s not cross-process)
- we now handle the zero-sized element case directly in the WebDriver
server

Testing: This should fix some flaky tests.
Fixes: #36715.
Fixes: (partially) #39180.
Fixes: (partially) #34683.

Signed-off-by: Delan Azabani <dazabani@igalia.com>
Co-authored-by: Martin Robinson <mrobinson@igalia.com>
This commit is contained in:
shuppy 2025-10-01 20:37:00 +08:00 committed by GitHub
parent cd80d2724d
commit cfa9e711c5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 106 additions and 151 deletions

View file

@ -28,11 +28,10 @@ use dpi::PhysicalSize;
use embedder_traits::{
CompositorHitTestResult, InputEvent, ScreenshotCaptureError, ShutdownState, ViewportDetails,
};
use euclid::{Point2D, Rect, Scale, Size2D, Transform3D};
use euclid::{Point2D, Scale, Size2D, Transform3D};
use image::RgbaImage;
use ipc_channel::ipc::{self, IpcSharedMemory};
use log::{debug, info, trace, warn};
use pixels::{CorsStatus, ImageFrame, ImageMetadata, PixelFormat, RasterImage};
use profile_traits::mem::{
ProcessReports, ProfilerRegistration, Report, ReportKind, perform_memory_report,
};
@ -1184,58 +1183,6 @@ impl IOCompositor {
self.needs_repaint.set(RepaintReason::empty());
}
/// Render the WebRender scene to the shared memory, without updating other state of this
/// [`IOCompositor`]. If succesful return the output image in shared memory.
pub fn render_to_shared_memory(
&mut self,
webview_id: WebViewId,
page_rect: Option<Rect<f32, CSSPixel>>,
) -> Option<RasterImage> {
self.render_inner();
let size = self.rendering_context.size2d().to_i32();
let rect = if let Some(rect) = page_rect {
let scale = self
.webview_renderers
.get(webview_id)
.map(WebViewRenderer::device_pixels_per_page_pixel)
.unwrap_or_else(|| Scale::new(1.0));
let rect = scale.transform_rect(&rect);
let x = rect.origin.x as i32;
// We need to convert to the bottom-left origin coordinate
// system used by OpenGL
// If dpi > 1, y can be computed to be -1 due to rounding issue, resulting in panic.
// https://github.com/servo/servo/issues/39306#issuecomment-3342204869
let y = 0.max((size.height as f32 - rect.origin.y - rect.size.height) as i32);
let w = rect.size.width as i32;
let h = rect.size.height as i32;
DeviceIntRect::from_origin_and_size(Point2D::new(x, y), Size2D::new(w, h))
} else {
DeviceIntRect::from_origin_and_size(Point2D::origin(), size)
};
self.rendering_context
.read_to_image(rect)
.map(|image| RasterImage {
metadata: ImageMetadata {
width: image.width(),
height: image.height(),
},
format: PixelFormat::RGBA8,
frames: vec![ImageFrame {
delay: None,
byte_range: 0..image.len(),
width: image.width(),
height: image.height(),
}],
bytes: ipc::IpcSharedMemory::from_bytes(&image),
id: None,
cors_status: CorsStatus::Safe,
})
}
#[servo_tracing::instrument(skip_all)]
fn render_inner(&mut self) {
if let Err(err) = self.rendering_context.make_current() {
@ -1584,6 +1531,16 @@ impl IOCompositor {
}
}
pub fn device_pixels_per_page_pixel(
&self,
webview_id: WebViewId,
) -> Scale<f32, CSSPixel, DevicePixel> {
self.webview_renderers
.get(webview_id)
.map(WebViewRenderer::device_pixels_per_page_pixel)
.unwrap_or_default()
}
fn webrender_document(&self) -> DocumentId {
self.global.borrow().webrender_document
}
@ -1644,10 +1601,11 @@ impl IOCompositor {
pub fn request_screenshot(
&self,
webview_id: WebViewId,
rect: Option<DeviceRect>,
callback: Box<dyn FnOnce(Result<RgbaImage, ScreenshotCaptureError>) + 'static>,
) {
self.screenshot_taker
.request_screenshot(webview_id, callback);
.request_screenshot(webview_id, rect, callback);
let _ = self.global.borrow().constellation_sender.send(
EmbedderToConstellationMessage::RequestScreenshotReadiness(webview_id),
);

View file

@ -8,16 +8,19 @@ use std::rc::Rc;
use base::Epoch;
use base::id::{PipelineId, WebViewId};
use embedder_traits::ScreenshotCaptureError;
use euclid::{Point2D, Size2D};
use image::RgbaImage;
use rustc_hash::FxHashMap;
use webrender_api::units::{DeviceIntRect, DeviceRect};
use crate::IOCompositor;
use crate::compositor::RepaintReason;
pub(crate) struct ScreenshotRequest {
webview_id: WebViewId,
phase: ScreenshotRequestPhase,
rect: Option<DeviceRect>,
callback: Box<dyn FnOnce(Result<RgbaImage, ScreenshotCaptureError>) + 'static>,
phase: ScreenshotRequestPhase,
}
/// Screenshots requests happen in three phases:
@ -63,12 +66,14 @@ impl ScreenshotTaker {
pub(crate) fn request_screenshot(
&self,
webview_id: WebViewId,
rect: Option<DeviceRect>,
callback: Box<dyn FnOnce(Result<RgbaImage, ScreenshotCaptureError>) + 'static>,
) {
self.requests.borrow_mut().push(ScreenshotRequest {
webview_id,
phase: ScreenshotRequestPhase::ConstellationRequest,
rect,
callback,
phase: ScreenshotRequestPhase::ConstellationRequest,
});
}
@ -175,9 +180,25 @@ impl ScreenshotTaker {
return None;
};
let viewport_rect = webview_renderer.rect.to_i32();
let viewport_size = viewport_rect.size();
let rect = screenshot_request.rect.map_or(viewport_rect, |rect| {
// We need to convert to the bottom-left origin coordinate
// system used by OpenGL
// If dpi > 1, y can be computed to be -1 due to rounding issue, resulting in panic.
// https://github.com/servo/servo/issues/39306#issuecomment-3342204869
let x = rect.min.x as i32;
let y = 0.max(
(viewport_size.height as f32 - rect.min.y - rect.size().height) as i32,
);
let w = rect.size().width as i32;
let h = rect.size().height as i32;
DeviceIntRect::from_origin_and_size(Point2D::new(x, y), Size2D::new(w, h))
});
let result = renderer
.rendering_context()
.read_to_image(webview_renderer.rect.to_i32())
.read_to_image(rect)
.ok_or(ScreenshotCaptureError::CouldNotReadImage);
callback(result);
None