T13: push display scale factor into Servo's hidpi so Retina pages lay out at logical CSS dimensions

This commit is contained in:
2026-05-11 00:46:32 -04:00
parent 60a3b9b050
commit b70fa71a9f
9 changed files with 139 additions and 13 deletions
@@ -99,6 +99,7 @@ fn handle_request(
width,
height,
page_zoom_percent,
device_pixel_ratio,
scroll_delta_x,
scroll_delta_y,
click_x,
@@ -114,7 +115,14 @@ fn handle_request(
let session =
ensure_session(host, sessions, tab_id.clone(), &tab, &profile, width, height)?;
if apply_layout(host, session, width, height, page_zoom_percent)? {
if apply_layout(
host,
session,
width,
height,
page_zoom_percent,
device_pixel_ratio,
)? {
session.awaiting_visible_frame = true;
}
apply_permissions(host, session, &profile, site_permissions)?;
@@ -305,8 +313,26 @@ fn apply_layout(
width: u32,
height: u32,
page_zoom_percent: u16,
device_pixel_ratio: f32,
) -> Result<bool, LiveSidecarError> {
let mut changed = false;
// Push the device pixel ratio BEFORE resize. Servo's WebView
// defaults hidpi to 1.0; without this the first layout treats
// physical-pixel viewport widths as CSS-pixel widths and the page
// lays out half the size you'd expect on a Retina display. The
// sidecar-side `LiveSession::hidpi_scale_milli` keeps a u32 of
// (scale × 1000) so f32 jitter from JSON parsing doesn't churn
// the setter every frame.
let hidpi_scale_milli = encode_hidpi_scale_milli(device_pixel_ratio);
if session.hidpi_scale_milli != hidpi_scale_milli {
host.set_hidpi_scale(ely_servo_host::HidpiScaleRequest {
webview_id: session.webview_id.clone(),
scale_factor: hidpi_scale_milli_to_f32(hidpi_scale_milli),
})?;
session.hidpi_scale_milli = hidpi_scale_milli;
changed = true;
}
if session.width != width || session.height != height {
host.resize(ResizeRequest { webview_id: session.webview_id.clone(), width, height })?;
session.width = width;
@@ -326,6 +352,18 @@ fn apply_layout(
Ok(changed)
}
fn encode_hidpi_scale_milli(scale: f32) -> u32 {
if !scale.is_finite() || scale <= 0.0 {
return 1_000;
}
let scaled = (scale * 1_000.0).round();
scaled.clamp(500.0, 5_000.0) as u32
}
fn hidpi_scale_milli_to_f32(milli: u32) -> f32 {
milli as f32 / 1_000.0
}
fn apply_permissions(
host: &mut SoftwareServoHost,
session: &LiveSession,
@@ -474,6 +512,11 @@ struct LiveSession {
width: u32,
height: u32,
page_zoom_percent: u16,
/// Last hidpi factor pushed to Servo, encoded as `(scale × 1000)`.
/// Stored as a u32 so equality is cheap and stable across the
/// f32 jitter that JSON parsing can introduce. Init to 0 so the
/// first apply_layout call always pushes a real value.
hidpi_scale_milli: u32,
scroll_x: i32,
scroll_y: i32,
awaiting_visible_frame: bool,
@@ -497,6 +540,7 @@ impl LiveSession {
width: width.max(1),
height: height.max(1),
page_zoom_percent: DEFAULT_ZOOM_PERCENT,
hidpi_scale_milli: 0,
scroll_x: 0,
scroll_y: 0,
awaiting_visible_frame: false,
@@ -19,6 +19,14 @@ pub(super) enum LiveRequest {
width: u32,
height: u32,
page_zoom_percent: u16,
/// Display scale factor reported by the host's window
/// (1.0 standard, 2.0 Retina). The sidecar plumbs this into
/// Servo's `WebView::set_hidpi_scale_factor` so CSS layout
/// happens at logical-pixel dimensions instead of physical.
/// Defaults to 1.0 for backward compatibility if a client
/// (e.g. the live perf bench) omits the field.
#[serde(default = "default_device_pixel_ratio")]
device_pixel_ratio: f32,
scroll_delta_x: i32,
scroll_delta_y: i32,
click_x: Option<u32>,
@@ -35,6 +43,10 @@ pub(super) enum LiveRequest {
},
}
fn default_device_pixel_ratio() -> f32 {
1.0
}
#[derive(Deserialize)]
pub(super) struct LiveSitePermission {
pub origin: String,
+13
View File
@@ -235,6 +235,17 @@ pub struct PageZoomRequest {
pub zoom_factor: f32,
}
/// Set the WebView's hidpi (device → CSS pixel) scale. Servo's
/// builder defaults this to 1.0; on Retina hosts that produces
/// half-size layout because the page treats physical pixels as CSS
/// pixels. The embedder should mirror the platform's reported scale
/// factor on every viewport-bound change.
#[derive(Clone, Debug, PartialEq)]
pub struct HidpiScaleRequest {
pub webview_id: WebViewId,
pub scale_factor: f32,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct MouseClickRequest {
pub webview_id: WebViewId,
@@ -316,6 +327,8 @@ pub trait ServoHost {
fn set_page_zoom(&mut self, request: PageZoomRequest) -> Result<(), ServoHostError>;
fn set_hidpi_scale(&mut self, request: HidpiScaleRequest) -> Result<(), ServoHostError>;
fn click(&mut self, request: MouseClickRequest) -> Result<(), ServoHostError>;
fn hover(&mut self, request: MouseHoverRequest) -> Result<(), ServoHostError>;
+4 -4
View File
@@ -21,10 +21,10 @@ pub use error::ServoHostError;
pub use hardware_rendering_context::HardwareOffscreenContext;
pub use iosurface_handle::{IOSurfaceHandle, IOSurfaceIdentity};
pub use host::{
KeyboardTextRequest, MouseClickRequest, MouseDragRequest, MouseHoverRequest,
NavigationRequest, PageZoomRequest, PermissionDecision, PermissionRequest, RenderedFrame,
RenderedFrameSummary, ResizeRequest, ScreenshotRequest, ScrollRequest, ServoHost,
TouchTapRequest, WebViewSnapshot, WebViewState,
HidpiScaleRequest, KeyboardTextRequest, MouseClickRequest, MouseDragRequest,
MouseHoverRequest, NavigationRequest, PageZoomRequest, PermissionDecision, PermissionRequest,
RenderedFrame, RenderedFrameSummary, ResizeRequest, ScreenshotRequest, ScrollRequest,
ServoHost, TouchTapRequest, WebViewSnapshot, WebViewState,
};
#[cfg(feature = "servo-engine")]
pub use runtime::{RenderingContextKind, ServoSurfaceSize, SoftwareServoHost};
+33 -6
View File
@@ -13,17 +13,33 @@ use std::{
use dpi::PhysicalSize;
use ely_domain::{ProfileId, TabId, WebViewId};
use euclid::Scale;
use servo::{
DeviceIntPoint, DeviceIntRect, DeviceIntSize, DevicePoint, DeviceVector2D, Opts,
RenderingContext, Scroll, Servo, ServoBuilder, WebViewBuilder, WebViewPoint, WebViewVector,
DeviceIndependentPixel, DeviceIntPoint, DeviceIntRect, DeviceIntSize, DevicePixel,
DevicePoint, DeviceVector2D, Opts, RenderingContext, Scroll, Servo, ServoBuilder,
WebViewBuilder, WebViewPoint, WebViewVector,
};
/// Wrap an `f32` scale factor in Servo's typed `Scale<f32, DeviceIndependentPixel,
/// DevicePixel>`. The clamp guards against `NaN`/`inf` reaching Servo's
/// layout (which assumes a positive finite scale).
fn hidpi_scale_from_factor(
scale_factor: f32,
) -> Scale<f32, DeviceIndependentPixel, DevicePixel> {
let safe = if scale_factor.is_finite() && scale_factor > 0.0 {
scale_factor.clamp(0.5, 5.0)
} else {
1.0
};
Scale::new(safe)
}
use url::Url;
use crate::{
KeyboardTextRequest, MouseClickRequest, MouseDragRequest, MouseHoverRequest,
NavigationRequest, PageZoomRequest, PermissionDecision, PermissionRequest, RenderedFrame,
ResizeRequest, ScreenshotRequest, ScrollRequest, ServoHost, ServoHostError, TouchTapRequest,
WebViewSnapshot, WebViewState,
HidpiScaleRequest, KeyboardTextRequest, MouseClickRequest, MouseDragRequest,
MouseHoverRequest, NavigationRequest, PageZoomRequest, PermissionDecision, PermissionRequest,
RenderedFrame, ResizeRequest, ScreenshotRequest, ScrollRequest, ServoHost, ServoHostError,
TouchTapRequest, WebViewSnapshot, WebViewState,
runtime_input::{
send_keyboard_text, send_mouse_click, send_mouse_drag, send_mouse_hover, send_touch_tap,
},
@@ -254,6 +270,17 @@ impl ServoHost for SoftwareServoHost {
Ok(())
}
fn set_hidpi_scale(&mut self, request: HidpiScaleRequest) -> Result<(), ServoHostError> {
let webview = self
.webviews
.get(&request.webview_id)
.ok_or_else(|| ServoHostError::WebViewNotFound { id: request.webview_id.clone() })?;
let scale = hidpi_scale_from_factor(request.scale_factor);
webview.webview.set_hidpi_scale_factor(scale);
Ok(())
}
fn hover(&mut self, request: MouseHoverRequest) -> Result<(), ServoHostError> {
let webview = self.webview_for_input(&request.webview_id)?;
send_mouse_hover(&webview.webview, request.x, request.y);