diff --git a/Cargo.lock b/Cargo.lock index e14b4f7ffcc..4de24621e66 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1426,6 +1426,7 @@ dependencies = [ "profile_traits", "raw-window-handle", "serde", + "servo_geometry", "servo_malloc_size_of", "smallvec", "strum_macros", diff --git a/components/compositing/compositor.rs b/components/compositing/compositor.rs index 765dcce08cb..897a9b55155 100644 --- a/components/compositing/compositor.rs +++ b/components/compositing/compositor.rs @@ -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>, + /// 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> { diff --git a/components/compositing/webview_renderer.rs b/components/compositing/webview_renderer.rs index e23d7fc55d0..f0e7ab003b5 100644 --- a/components/compositing/webview_renderer.rs +++ b/components/compositing/webview_renderer.rs @@ -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>, /// WebRender is not ready between `SendDisplayList` and `WebRenderFrameReady` messages. pub webrender_frame_ready: Cell, - /// 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, } @@ -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 { + self.page_zoom } + pub(crate) fn set_page_zoom( + &mut self, + new_page_zoom: Scale, + ) { + 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 { - 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 { diff --git a/components/layout/display_list/background.rs b/components/layout/display_list/background.rs index f1099fdab2b..625f2f9494c 100644 --- a/components/layout/display_list/background.rs +++ b/components/layout/display_list/background.rs @@ -65,7 +65,11 @@ impl<'a> BackgroundPainter<'a> { if &BackgroundAttachment::Fixed == get_cyclic(&background.background_attachment.0, layer_index) { - return builder.compositor_info.viewport_size.into(); + return builder + .compositor_info + .viewport_details + .layout_size() + .into(); } match get_cyclic(&background.background_clip.0, layer_index) { @@ -149,7 +153,11 @@ impl<'a> BackgroundPainter<'a> { Origin::PaddingBox => *fragment_builder.padding_rect(), Origin::BorderBox => fragment_builder.border_rect, }, - BackgroundAttachment::Fixed => builder.compositor_info.viewport_size.into(), + BackgroundAttachment::Fixed => builder + .compositor_info + .viewport_details + .layout_size() + .into(), } } } diff --git a/components/layout/display_list/stacking_context.rs b/components/layout/display_list/stacking_context.rs index ab2205d8ba9..3dce9b4a019 100644 --- a/components/layout/display_list/stacking_context.rs +++ b/components/layout/display_list/stacking_context.rs @@ -14,6 +14,7 @@ use compositing_traits::display_list::{ AxesScrollSensitivity, CompositorDisplayListInfo, ReferenceFrameNodeInfo, ScrollableNodeInfo, SpatialTreeNodeInfo, StickyNodeInfo, }; +use embedder_traits::ViewportDetails; use euclid::SideOffsets2D; use euclid::default::{Point2D, Rect, Size2D}; use log::warn; @@ -120,7 +121,7 @@ impl StackingContextTree { /// pipeline id. pub fn new( fragment_tree: &FragmentTree, - viewport_size: LayoutSize, + viewport_details: ViewportDetails, pipeline_id: wr::PipelineId, first_reflow: bool, debug: &DebugOptions, @@ -131,8 +132,9 @@ impl StackingContextTree { scrollable_overflow.size.height.to_f32_px(), )); + let viewport_size = viewport_details.layout_size(); let compositor_info = CompositorDisplayListInfo::new( - viewport_size, + viewport_details, scrollable_overflow, pipeline_id, // This epoch is set when the WebRender display list is built. For now use a dummy value. @@ -145,7 +147,7 @@ impl StackingContextTree { let cb_for_non_fixed_descendants = ContainingBlock::new( fragment_tree.initial_containing_block, root_scroll_node_id, - Some(compositor_info.viewport_size), + Some(viewport_size), ClipId::INVALID, ); let cb_for_fixed_descendants = ContainingBlock::new( @@ -1504,7 +1506,10 @@ impl BoxFragment { Some(size) => size, None => { // This is a direct descendant of a reference frame. - &stacking_context_tree.compositor_info.viewport_size + &stacking_context_tree + .compositor_info + .viewport_details + .layout_size() }, }; diff --git a/components/layout/layout_impl.rs b/components/layout/layout_impl.rs index 360a0f20f53..5d70bccd8dc 100644 --- a/components/layout/layout_impl.rs +++ b/components/layout/layout_impl.rs @@ -75,7 +75,7 @@ use style::{Zero, driver}; use style_traits::{CSSPixel, SpeculativePainter}; use stylo_atoms::Atom; use url::Url; -use webrender_api::units::{DevicePixel, DevicePoint, LayoutSize, LayoutVector2D}; +use webrender_api::units::{DevicePixel, DevicePoint, LayoutVector2D}; use webrender_api::{ExternalScrollId, HitTestFlags}; use crate::context::{CachedImageOrError, ImageResolver, LayoutContext}; @@ -960,12 +960,6 @@ impl LayoutThread { return; } - let viewport_size = self.stylist.device().au_viewport_size(); - let viewport_size = LayoutSize::new( - viewport_size.width.to_f32_px(), - viewport_size.height.to_f32_px(), - ); - let mut stacking_context_tree = self.stacking_context_tree.borrow_mut(); let old_scroll_offsets = stacking_context_tree .as_ref() @@ -976,7 +970,7 @@ impl LayoutThread { // applicable spatial and clip nodes. let mut new_stacking_context_tree = StackingContextTree::new( fragment_tree, - viewport_size, + reflow_request.viewport_details, self.id.into(), !self.have_ever_generated_display_list.get(), &self.debug, diff --git a/components/shared/compositing/Cargo.toml b/components/shared/compositing/Cargo.toml index 2384f722c5b..11ac6223766 100644 --- a/components/shared/compositing/Cargo.toml +++ b/components/shared/compositing/Cargo.toml @@ -32,6 +32,7 @@ malloc_size_of_derive = { workspace = true } profile_traits = { path = '../profile' } raw-window-handle = { version = "0.6" } serde = { workspace = true } +servo_geometry = { path = "../../geometry" } smallvec = { workspace = true } strum_macros = { workspace = true } stylo = { workspace = true } diff --git a/components/shared/compositing/display_list.rs b/components/shared/compositing/display_list.rs index ed4cc24cccc..b85fc84956e 100644 --- a/components/shared/compositing/display_list.rs +++ b/components/shared/compositing/display_list.rs @@ -9,7 +9,7 @@ use std::collections::HashMap; use base::id::ScrollTreeNodeId; use base::print_tree::PrintTree; use bitflags::bitflags; -use embedder_traits::Cursor; +use embedder_traits::{Cursor, ViewportDetails}; use euclid::SideOffsets2D; use malloc_size_of_derive::MallocSizeOf; use serde::{Deserialize, Serialize}; @@ -486,8 +486,9 @@ pub struct CompositorDisplayListInfo { /// The WebRender [PipelineId] of this display list. pub pipeline_id: PipelineId, - /// The size of the viewport that this display list renders into. - pub viewport_size: LayoutSize, + /// The [`ViewportDetails`] that describe the viewport in the script/layout thread at + /// the time this display list was created. + pub viewport_details: ViewportDetails, /// The size of this display list's content. pub content_size: LayoutSize, @@ -526,7 +527,7 @@ impl CompositorDisplayListInfo { /// Create a new CompositorDisplayListInfo with the root reference frame /// and scroll frame already added to the scroll tree. pub fn new( - viewport_size: LayoutSize, + viewport_details: ViewportDetails, content_size: LayoutSize, pipeline_id: PipelineId, epoch: Epoch, @@ -548,7 +549,10 @@ impl CompositorDisplayListInfo { SpatialTreeNodeInfo::Scroll(ScrollableNodeInfo { external_id: ExternalScrollId(0, pipeline_id), content_rect: LayoutRect::from_origin_and_size(LayoutPoint::zero(), content_size), - clip_rect: LayoutRect::from_origin_and_size(LayoutPoint::zero(), viewport_size), + clip_rect: LayoutRect::from_origin_and_size( + LayoutPoint::zero(), + viewport_details.layout_size(), + ), scroll_sensitivity: viewport_scroll_sensitivity, offset: LayoutVector2D::zero(), }), @@ -556,7 +560,7 @@ impl CompositorDisplayListInfo { CompositorDisplayListInfo { pipeline_id, - viewport_size, + viewport_details, content_size, epoch, hit_test_info: Default::default(), diff --git a/components/shared/compositing/viewport_description.rs b/components/shared/compositing/viewport_description.rs index 83b29371f84..e4136872437 100644 --- a/components/shared/compositing/viewport_description.rs +++ b/components/shared/compositing/viewport_description.rs @@ -7,17 +7,19 @@ use std::collections::HashMap; use std::str::FromStr; -use euclid::default::Scale; +use euclid::Scale; use serde::{Deserialize, Serialize}; +use servo_geometry::DeviceIndependentPixel; +use style_traits::CSSPixel; /// Default viewport constraints /// /// -pub const MIN_ZOOM: f32 = 0.1; +pub const MIN_PAGE_ZOOM: Scale = Scale::new(0.1); /// -pub const MAX_ZOOM: f32 = 10.0; +pub const MAX_PAGE_ZOOM: Scale = Scale::new(10.0); /// -pub const DEFAULT_ZOOM: f32 = 1.0; +pub const DEFAULT_PAGE_ZOOM: Scale = Scale::new(1.0); /// const SEPARATORS: [char; 2] = [',', ';']; // Comma (0x2c) and Semicolon (0x3b) @@ -35,15 +37,15 @@ pub struct ViewportDescription { // TODO: height Needs to be implemented /// /// the zoom level when the page is first loaded - pub initial_scale: Scale, + pub initial_scale: Scale, /// /// how much zoom out is allowed on the page. - pub minimum_scale: Scale, + pub minimum_scale: Scale, /// /// how much zoom in is allowed on the page - pub maximum_scale: Scale, + pub maximum_scale: Scale, /// /// whether zoom in and zoom out actions are allowed on the page @@ -85,9 +87,9 @@ impl TryFrom<&str> for UserScalable { impl Default for ViewportDescription { fn default() -> Self { ViewportDescription { - initial_scale: Scale::new(DEFAULT_ZOOM), - minimum_scale: Scale::new(MIN_ZOOM), - maximum_scale: Scale::new(MAX_ZOOM), + initial_scale: DEFAULT_PAGE_ZOOM, + minimum_scale: MIN_PAGE_ZOOM, + maximum_scale: MAX_PAGE_ZOOM, user_scalable: UserScalable::Yes, } } @@ -126,7 +128,9 @@ impl ViewportDescription { } /// Parses a viewport zoom value. - fn parse_viewport_value_as_zoom(value: &str) -> Option> { + fn parse_viewport_value_as_zoom( + value: &str, + ) -> Option> { value .to_lowercase() .as_str() diff --git a/components/shared/embedder/lib.rs b/components/shared/embedder/lib.rs index c7f0ca46984..82c49176f63 100644 --- a/components/shared/embedder/lib.rs +++ b/components/shared/embedder/lib.rs @@ -38,7 +38,7 @@ use style::queries::values::PrefersColorScheme; use style_traits::CSSPixel; use url::Url; use uuid::Uuid; -use webrender_api::units::{DeviceIntPoint, DeviceIntRect, DeviceIntSize, DevicePixel}; +use webrender_api::units::{DeviceIntPoint, DeviceIntRect, DeviceIntSize, DevicePixel, LayoutSize}; pub use crate::input_events::*; pub use crate::webdriver::*; @@ -319,6 +319,14 @@ pub struct ViewportDetails { pub hidpi_scale_factor: Scale, } +impl ViewportDetails { + /// Convert this [`ViewportDetails`] size to a [`LayoutSize`]. This is the same numerical + /// value as [`Self::size`], because a `LayoutPixel` is the same as a `CSSPixel`. + pub fn layout_size(&self) -> LayoutSize { + Size2D::from_untyped(self.size.to_untyped()) + } +} + /// Unlike [`ScreenGeometry`], the data is in device-independent pixels /// to be used by DOM APIs #[derive(Default, Deserialize, Serialize)]