compositor: Request reflow when doing a page zooming (#38166)

Request a reflow when doing page zoom and only modify the scaling of the
WebView scene after the first root pipeline display list with the new
zoom is ready. In addition:

  - store zoom limits in `Scale` types
  - send `ViewportDetails` along with the display list so that we can
    detect when the root pipeline scale is ready.

Testing: This is quite hard to test as it requires verification that
contents are zoomed appropriately at the right time.
Fixes: #38091.

Signed-off-by: Martin Robinson <mrobinson@igalia.com>
This commit is contained in:
Martin Robinson 2025-07-29 12:04:37 +02:00 committed by GitHub
parent 3d2f0d1be5
commit 8d5faa9bf9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 111 additions and 44 deletions

View file

@ -227,6 +227,11 @@ pub(crate) struct PipelineDetails {
/// The paint metric status of the first contentful paint.
pub first_contentful_paint_metric: PaintMetricState,
/// The CSS pixel to device pixel scale of the viewport of this pipeline, including
/// page zoom, but not including any pinch zoom amount. This is used to detect
/// situations where the current display list is for an old scale.
pub viewport_scale: Option<Scale<f32, CSSPixel, DevicePixel>>,
/// Which parts of Servo have reported that this `Pipeline` has exited. Only when all
/// have done so will it be discarded.
pub exited: PipelineExitSource,
@ -248,6 +253,7 @@ impl PipelineDetails {
pipeline: None,
parent_pipeline_id: None,
most_recent_display_list_epoch: None,
viewport_scale: None,
animations_running: false,
animation_callbacks_running: false,
throttled: false,
@ -761,14 +767,18 @@ impl IOCompositor {
let Some(webview_renderer) = self.webview_renderers.get_mut(webview_id) else {
return warn!("Could not find WebView for incoming display list");
};
// WebRender is not ready until we receive "NewWebRenderFrameReady"
webview_renderer.webrender_frame_ready.set(false);
let old_scale = webview_renderer.device_pixels_per_page_pixel();
let pipeline_id = display_list_info.pipeline_id;
let details = webview_renderer.ensure_pipeline_details(pipeline_id.into());
details.most_recent_display_list_epoch = Some(display_list_info.epoch);
details.hit_test_items = display_list_info.hit_test_info;
details.install_new_scroll_tree(display_list_info.scroll_tree);
details.viewport_scale =
Some(display_list_info.viewport_details.hidpi_scale_factor);
let epoch = display_list_info.epoch;
let first_reflow = display_list_info.first_reflow;
@ -783,6 +793,14 @@ impl IOCompositor {
}
let mut transaction = Transaction::new();
let is_root_pipeline =
Some(pipeline_id.into()) == webview_renderer.root_pipeline_id;
if is_root_pipeline && old_scale != webview_renderer.device_pixels_per_page_pixel()
{
self.send_root_pipeline_display_list_in_transaction(&mut transaction);
}
transaction
.set_display_list(display_list_info.epoch, (pipeline_id, built_display_list));
self.update_transaction_with_all_scroll_offsets(&mut transaction);
@ -1227,9 +1245,8 @@ impl IOCompositor {
}
if let Some(webview_renderer) = self.webview_renderers.get_mut(webview_id) {
webview_renderer.set_page_zoom(1.0);
webview_renderer.set_page_zoom(Scale::new(1.0));
}
self.send_root_pipeline_display_list();
}
pub fn on_zoom_window_event(&mut self, webview_id: WebViewId, magnification: f32) {
@ -1238,9 +1255,9 @@ impl IOCompositor {
}
if let Some(webview_renderer) = self.webview_renderers.get_mut(webview_id) {
webview_renderer.set_page_zoom(magnification);
let current_page_zoom = webview_renderer.page_zoom();
webview_renderer.set_page_zoom(current_page_zoom * Scale::new(magnification));
}
self.send_root_pipeline_display_list();
}
fn details_for_pipeline(&self, pipeline_id: PipelineId) -> Option<&PipelineDetails> {

View file

@ -10,7 +10,7 @@ use std::rc::Rc;
use base::id::{PipelineId, WebViewId};
use compositing_traits::display_list::ScrollType;
use compositing_traits::viewport_description::{
DEFAULT_ZOOM, MAX_ZOOM, MIN_ZOOM, ViewportDescription,
DEFAULT_PAGE_ZOOM, MAX_PAGE_ZOOM, MIN_PAGE_ZOOM, ViewportDescription,
};
use compositing_traits::{PipelineExitSource, SendableFrameTree, WebViewTrait};
use constellation_traits::{EmbedderToConstellationMessage, WindowSizeType};
@ -100,7 +100,8 @@ pub(crate) struct WebViewRenderer {
pending_point_input_events: RefCell<VecDeque<InputEvent>>,
/// WebRender is not ready between `SendDisplayList` and `WebRenderFrameReady` messages.
pub webrender_frame_ready: Cell<bool>,
/// Viewport Description
/// A [`ViewportDescription`] for this [`WebViewRenderer`], which contains the limitations
/// and initial values for zoom derived from the `viewport` meta tag in web content.
viewport_description: Option<ViewportDescription>,
}
@ -130,8 +131,8 @@ impl WebViewRenderer {
touch_handler: TouchHandler::new(),
global,
pending_scroll_zoom_events: Default::default(),
page_zoom: Scale::new(DEFAULT_ZOOM),
pinch_zoom: PinchZoomFactor::new(DEFAULT_ZOOM),
page_zoom: DEFAULT_PAGE_ZOOM,
pinch_zoom: PinchZoomFactor::new(1.0),
hidpi_scale_factor: Scale::new(hidpi_scale_factor.0),
animating: false,
pending_point_input_events: Default::default(),
@ -964,15 +965,39 @@ impl WebViewRenderer {
old_zoom != self.pinch_zoom
}
pub(crate) fn set_page_zoom(&mut self, magnification: f32) {
self.page_zoom =
Scale::new((self.page_zoom.get() * magnification).clamp(MIN_ZOOM, MAX_ZOOM));
pub(crate) fn page_zoom(&mut self) -> Scale<f32, CSSPixel, DeviceIndependentPixel> {
self.page_zoom
}
pub(crate) fn set_page_zoom(
&mut self,
new_page_zoom: Scale<f32, CSSPixel, DeviceIndependentPixel>,
) {
let new_page_zoom = new_page_zoom.clamp(MIN_PAGE_ZOOM, MAX_PAGE_ZOOM);
let old_zoom = std::mem::replace(&mut self.page_zoom, new_page_zoom);
if old_zoom != self.page_zoom {
self.send_window_size_message();
}
}
/// The scale to use when displaying this [`WebViewRenderer`] in WebRender
/// including both viewport scale (page zoom and hidpi scale) as well as any
/// pinch zoom applied. This is based on the latest display list received,
/// as page zoom changes are applied asynchronously and the rendered view
/// should reflect the latest display list.
pub(crate) fn device_pixels_per_page_pixel(&self) -> Scale<f32, CSSPixel, DevicePixel> {
self.page_zoom * self.hidpi_scale_factor * self.pinch_zoom_level()
let viewport_scale = self
.root_pipeline_id
.and_then(|pipeline_id| self.pipelines.get(&pipeline_id))
.and_then(|pipeline| pipeline.viewport_scale)
.unwrap_or_else(|| self.page_zoom * self.hidpi_scale_factor);
viewport_scale * self.pinch_zoom_level()
}
/// The current viewport scale (hidpi scale and page zoom and not pinch
/// zoom) based on the current setting of the WebView. Note that this may
/// not be the rendered viewport zoom as that is based on the latest display
/// list and zoom changes are applied asynchronously.
pub(crate) fn device_pixels_per_page_pixel_not_including_pinch_zoom(
&self,
) -> Scale<f32, CSSPixel, DevicePixel> {