mirror of
https://github.com/servo/servo.git
synced 2025-08-03 04:30:10 +01:00
libservo: Make zooming and HiDPI scaling work per-WebView
(#36419)
libservo: Make zooming and HiDPI scaling work per-`WebView` This change moves all zooming and HiDPI scaling to work per-`WebView` in both libservo and Compositor. This means that you can pinch zoom one `WebView` and it should now work independently of other `WebView`s. This is accomplished by making each `WebView` in the WebRender scene have its own scaling reference frame. All WebViews are now expected to manage their HiDPI scaling factor and this can be set independently of other WebViews. Perhaps in the future this will become a Servo-wide setting. This allows full removal of the `WindowMethods` trait from Servo. Testing: There are not yet any tests for the WebView API, but I hope to add those soon. Co-authored-by: Shubham Gupta <shubham13297@gmail.com> Signed-off-by: Martin Robinson <mrobinson@igalia.com> Signed-off-by: Martin Robinson <mrobinson@igalia.com> Co-authored-by: Shubham Gupta <shubham13297@gmail.com>
This commit is contained in:
parent
f1417c4e75
commit
c6dc7c83a8
17 changed files with 415 additions and 385 deletions
|
@ -21,16 +21,14 @@ use compositing_traits::rendering_context::RenderingContext;
|
|||
use compositing_traits::{
|
||||
CompositionPipeline, CompositorMsg, ImageUpdate, RendererWebView, SendableFrameTree,
|
||||
};
|
||||
use constellation_traits::{
|
||||
AnimationTickType, EmbedderToConstellationMessage, PaintMetricEvent, WindowSizeType,
|
||||
};
|
||||
use constellation_traits::{AnimationTickType, EmbedderToConstellationMessage, PaintMetricEvent};
|
||||
use crossbeam_channel::Sender;
|
||||
use dpi::PhysicalSize;
|
||||
use embedder_traits::{
|
||||
AnimationState, CompositorHitTestResult, Cursor, InputEvent, MouseButtonEvent, MouseMoveEvent,
|
||||
ScreenGeometry, ShutdownState, TouchEventType, UntrustedNodeAddress, ViewportDetails,
|
||||
ShutdownState, TouchEventType, UntrustedNodeAddress, ViewportDetails,
|
||||
};
|
||||
use euclid::{Box2D, Point2D, Rect, Scale, Size2D, Transform3D};
|
||||
use euclid::{Point2D, Rect, Scale, Size2D, Transform3D};
|
||||
use fnv::FnvHashMap;
|
||||
use ipc_channel::ipc::{self, IpcReceiver, IpcSharedMemory};
|
||||
use libc::c_void;
|
||||
|
@ -40,7 +38,7 @@ use profile_traits::time::{self as profile_time, ProfilerCategory};
|
|||
use profile_traits::time_profile;
|
||||
use servo_config::opts;
|
||||
use servo_geometry::DeviceIndependentPixel;
|
||||
use style_traits::{CSSPixel, PinchZoomFactor};
|
||||
use style_traits::CSSPixel;
|
||||
use webrender::{CaptureBits, RenderApi, Transaction};
|
||||
use webrender_api::units::{
|
||||
DeviceIntPoint, DeviceIntRect, DevicePixel, DevicePoint, DeviceRect, LayoutPoint, LayoutRect,
|
||||
|
@ -57,7 +55,7 @@ use webrender_api::{
|
|||
use crate::InitialCompositorState;
|
||||
use crate::webview::{UnknownWebView, WebView};
|
||||
use crate::webview_manager::WebViewManager;
|
||||
use crate::windowing::{WebRenderDebugOption, WindowMethods};
|
||||
use crate::windowing::WebRenderDebugOption;
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
enum UnableToComposite {
|
||||
|
@ -71,10 +69,6 @@ enum NotReadyToPaint {
|
|||
WaitingOnConstellation,
|
||||
}
|
||||
|
||||
// Default viewport constraints
|
||||
const MAX_ZOOM: f32 = 8.0;
|
||||
const MIN_ZOOM: f32 = 0.1;
|
||||
|
||||
/// Holds the state when running reftests that determines when it is
|
||||
/// safe to save the output image.
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
|
@ -134,19 +128,6 @@ pub struct IOCompositor {
|
|||
/// Our top-level browsing contexts.
|
||||
webviews: WebViewManager<WebView>,
|
||||
|
||||
/// The application window.
|
||||
pub window: Rc<dyn WindowMethods>,
|
||||
|
||||
/// "Mobile-style" zoom that does not reflow the page.
|
||||
viewport_zoom: PinchZoomFactor,
|
||||
|
||||
/// Viewport zoom constraints provided by @viewport.
|
||||
min_viewport_zoom: Option<PinchZoomFactor>,
|
||||
max_viewport_zoom: Option<PinchZoomFactor>,
|
||||
|
||||
/// "Desktop-style" zoom that resizes the viewport to fit the window.
|
||||
page_zoom: Scale<f32, CSSPixel, DeviceIndependentPixel>,
|
||||
|
||||
/// Tracks whether or not the view needs to be repainted.
|
||||
needs_repaint: Cell<RepaintReason>,
|
||||
|
||||
|
@ -160,10 +141,6 @@ pub struct IOCompositor {
|
|||
/// The surfman instance that webrender targets
|
||||
rendering_context: Rc<dyn RenderingContext>,
|
||||
|
||||
/// The HighDPI factor of the native window, its view and the screen.
|
||||
/// TODO: Eventually this should be a property of the `WebView`.
|
||||
hidpi_factor: Scale<f32, DeviceIndependentPixel, DevicePixel>,
|
||||
|
||||
/// The number of frames pending to receive from WebRender.
|
||||
pending_frames: usize,
|
||||
|
||||
|
@ -413,11 +390,7 @@ impl ServoRenderer {
|
|||
}
|
||||
|
||||
impl IOCompositor {
|
||||
pub fn new(
|
||||
window: Rc<dyn WindowMethods>,
|
||||
state: InitialCompositorState,
|
||||
convert_mouse_to_touch: bool,
|
||||
) -> Self {
|
||||
pub fn new(state: InitialCompositorState, convert_mouse_to_touch: bool) -> Self {
|
||||
let compositor = IOCompositor {
|
||||
global: Rc::new(RefCell::new(ServoRenderer {
|
||||
shutdown_state: state.shutdown_state,
|
||||
|
@ -435,13 +408,7 @@ impl IOCompositor {
|
|||
cursor_pos: DevicePoint::new(0.0, 0.0),
|
||||
})),
|
||||
webviews: WebViewManager::default(),
|
||||
hidpi_factor: window.hidpi_factor(),
|
||||
window,
|
||||
needs_repaint: Cell::default(),
|
||||
page_zoom: Scale::new(1.0),
|
||||
viewport_zoom: PinchZoomFactor::new(1.0),
|
||||
min_viewport_zoom: Some(PinchZoomFactor::new(1.0)),
|
||||
max_viewport_zoom: None,
|
||||
ready_to_save_state: ReadyState::Unknown,
|
||||
webrender: Some(state.webrender),
|
||||
rendering_context: state.rendering_context,
|
||||
|
@ -467,15 +434,8 @@ impl IOCompositor {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn default_webview_viewport_details(&self) -> ViewportDetails {
|
||||
// The division by 1 represents the page's default zoom of 100%,
|
||||
// and gives us the appropriate CSSPixel type for the viewport.
|
||||
let hidpi_scale_factor = self.window.hidpi_factor();
|
||||
let scaled_viewport_size = self.rendering_context.size2d().to_f32() / hidpi_scale_factor;
|
||||
ViewportDetails {
|
||||
size: scaled_viewport_size / Scale::new(1.0),
|
||||
hidpi_scale_factor: Scale::new(hidpi_scale_factor.0),
|
||||
}
|
||||
pub fn rendering_context_size(&self) -> Size2D<u32, DevicePixel> {
|
||||
self.rendering_context.size2d()
|
||||
}
|
||||
|
||||
pub fn webxr_running(&self) -> bool {
|
||||
|
@ -577,8 +537,8 @@ impl IOCompositor {
|
|||
webview.on_touch_event_processed(result);
|
||||
},
|
||||
|
||||
CompositorMsg::CreatePng(page_rect, reply) => {
|
||||
let res = self.render_to_shared_memory(page_rect);
|
||||
CompositorMsg::CreatePng(webview_id, page_rect, reply) => {
|
||||
let res = self.render_to_shared_memory(webview_id, page_rect);
|
||||
if let Err(ref e) = res {
|
||||
info!("Error retrieving PNG: {:?}", e);
|
||||
}
|
||||
|
@ -646,12 +606,12 @@ impl IOCompositor {
|
|||
},
|
||||
|
||||
CompositorMsg::WebDriverMouseButtonEvent(webview_id, action, button, x, y) => {
|
||||
let dppx = self.device_pixels_per_page_pixel();
|
||||
let point = dppx.transform_point(Point2D::new(x, y));
|
||||
let Some(webview) = self.webviews.get_mut(webview_id) else {
|
||||
warn!("Handling input event for unknown webview: {webview_id}");
|
||||
return;
|
||||
};
|
||||
let dppx = webview.device_pixels_per_page_pixel();
|
||||
let point = dppx.transform_point(Point2D::new(x, y));
|
||||
webview.dispatch_input_event(InputEvent::MouseButton(MouseButtonEvent {
|
||||
point,
|
||||
action,
|
||||
|
@ -660,12 +620,12 @@ impl IOCompositor {
|
|||
},
|
||||
|
||||
CompositorMsg::WebDriverMouseMoveEvent(webview_id, x, y) => {
|
||||
let dppx = self.device_pixels_per_page_pixel();
|
||||
let point = dppx.transform_point(Point2D::new(x, y));
|
||||
let Some(webview) = self.webviews.get_mut(webview_id) else {
|
||||
warn!("Handling input event for unknown webview: {webview_id}");
|
||||
return;
|
||||
};
|
||||
let dppx = webview.device_pixels_per_page_pixel();
|
||||
let point = dppx.transform_point(Point2D::new(x, y));
|
||||
webview.dispatch_input_event(InputEvent::MouseMove(MouseMoveEvent { point }));
|
||||
},
|
||||
|
||||
|
@ -897,45 +857,38 @@ impl IOCompositor {
|
|||
let _ = result_sender.send((font_keys, font_instance_keys));
|
||||
},
|
||||
CompositorMsg::GetClientWindowRect(webview_id, response_sender) => {
|
||||
let screen_geometry = self.webview_screen_geometry(webview_id);
|
||||
let rect = DeviceIntRect::from_origin_and_size(
|
||||
screen_geometry.offset,
|
||||
self.rendering_context.size2d().to_i32(),
|
||||
)
|
||||
.to_f32() /
|
||||
self.hidpi_factor;
|
||||
|
||||
if let Err(error) = response_sender.send(rect.to_i32()) {
|
||||
let client_window_rect = self
|
||||
.webviews
|
||||
.get(webview_id)
|
||||
.map(|webview| webview.client_window_rect(self.rendering_context.size2d()))
|
||||
.unwrap_or_default();
|
||||
if let Err(error) = response_sender.send(client_window_rect) {
|
||||
warn!("Sending response to get client window failed ({error:?}).");
|
||||
}
|
||||
},
|
||||
CompositorMsg::GetScreenSize(webview_id, response_sender) => {
|
||||
let screen_geometry = self.webview_screen_geometry(webview_id);
|
||||
let screen_size = screen_geometry.size.to_f32() / self.hidpi_factor;
|
||||
|
||||
if let Err(error) = response_sender.send(screen_size.to_i32()) {
|
||||
let screen_size = self
|
||||
.webviews
|
||||
.get(webview_id)
|
||||
.map(WebView::screen_size)
|
||||
.unwrap_or_default();
|
||||
if let Err(error) = response_sender.send(screen_size) {
|
||||
warn!("Sending response to get screen size failed ({error:?}).");
|
||||
}
|
||||
},
|
||||
CompositorMsg::GetAvailableScreenSize(webview_id, response_sender) => {
|
||||
let screen_geometry = self.webview_screen_geometry(webview_id);
|
||||
let available_screen_size =
|
||||
screen_geometry.available_size.to_f32() / self.hidpi_factor;
|
||||
|
||||
if let Err(error) = response_sender.send(available_screen_size.to_i32()) {
|
||||
let available_screen_size = self
|
||||
.webviews
|
||||
.get(webview_id)
|
||||
.map(WebView::available_screen_size)
|
||||
.unwrap_or_default();
|
||||
if let Err(error) = response_sender.send(available_screen_size) {
|
||||
warn!("Sending response to get screen size failed ({error:?}).");
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn webview_screen_geometry(&self, webview_id: WebViewId) -> ScreenGeometry {
|
||||
self.webviews
|
||||
.get(webview_id)
|
||||
.and_then(|webview| webview.renderer_webview.screen_geometry())
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
/// Handle messages sent to the compositor during the shutdown process. In general,
|
||||
/// the things the compositor can do in this state are limited. It's very important to
|
||||
/// answer any synchronous messages though as other threads might be waiting on the
|
||||
|
@ -1035,43 +988,50 @@ impl IOCompositor {
|
|||
let mut builder = webrender_api::DisplayListBuilder::new(root_pipeline);
|
||||
builder.begin();
|
||||
|
||||
let zoom_factor = self.device_pixels_per_page_pixel().0;
|
||||
let zoom_reference_frame = builder.push_reference_frame(
|
||||
let root_reference_frame = SpatialId::root_reference_frame(root_pipeline);
|
||||
|
||||
let viewport_size = self.rendering_context.size2d().to_f32().to_untyped();
|
||||
let viewport_rect = LayoutRect::from_origin_and_size(
|
||||
LayoutPoint::zero(),
|
||||
SpatialId::root_reference_frame(root_pipeline),
|
||||
TransformStyle::Flat,
|
||||
PropertyBinding::Value(Transform3D::scale(zoom_factor, zoom_factor, 1.)),
|
||||
ReferenceFrameKind::Transform {
|
||||
is_2d_scale_translation: true,
|
||||
should_snap: true,
|
||||
paired_with_perspective: false,
|
||||
},
|
||||
SpatialTreeItemKey::new(0, 0),
|
||||
LayoutSize::from_untyped(viewport_size),
|
||||
);
|
||||
|
||||
let scaled_viewport_size =
|
||||
self.rendering_context.size2d().to_f32().to_untyped() / zoom_factor;
|
||||
let scaled_viewport_rect = LayoutRect::from_origin_and_size(
|
||||
LayoutPoint::zero(),
|
||||
LayoutSize::from_untyped(scaled_viewport_size),
|
||||
);
|
||||
|
||||
let root_clip_id = builder.define_clip_rect(zoom_reference_frame, scaled_viewport_rect);
|
||||
let root_clip_id = builder.define_clip_rect(root_reference_frame, viewport_rect);
|
||||
let clip_chain_id = builder.define_clip_chain(None, [root_clip_id]);
|
||||
for (_, webview) in self.webviews.painting_order() {
|
||||
if let Some(pipeline_id) = webview.root_pipeline_id {
|
||||
let scaled_webview_rect = webview.rect / zoom_factor;
|
||||
builder.push_iframe(
|
||||
LayoutRect::from_untyped(&scaled_webview_rect.to_untyped()),
|
||||
LayoutRect::from_untyped(&scaled_webview_rect.to_untyped()),
|
||||
&SpaceAndClipInfo {
|
||||
spatial_id: zoom_reference_frame,
|
||||
clip_chain_id,
|
||||
},
|
||||
pipeline_id.into(),
|
||||
true,
|
||||
);
|
||||
}
|
||||
let Some(pipeline_id) = webview.root_pipeline_id else {
|
||||
continue;
|
||||
};
|
||||
|
||||
let device_pixels_per_page_pixel = webview.device_pixels_per_page_pixel().0;
|
||||
let webview_reference_frame = builder.push_reference_frame(
|
||||
LayoutPoint::zero(),
|
||||
root_reference_frame,
|
||||
TransformStyle::Flat,
|
||||
PropertyBinding::Value(Transform3D::scale(
|
||||
device_pixels_per_page_pixel,
|
||||
device_pixels_per_page_pixel,
|
||||
1.,
|
||||
)),
|
||||
ReferenceFrameKind::Transform {
|
||||
is_2d_scale_translation: true,
|
||||
should_snap: true,
|
||||
paired_with_perspective: false,
|
||||
},
|
||||
SpatialTreeItemKey::new(0, 0),
|
||||
);
|
||||
|
||||
let scaled_webview_rect = webview.rect / device_pixels_per_page_pixel;
|
||||
builder.push_iframe(
|
||||
LayoutRect::from_untyped(&scaled_webview_rect.to_untyped()),
|
||||
LayoutRect::from_untyped(&scaled_webview_rect.to_untyped()),
|
||||
&SpaceAndClipInfo {
|
||||
spatial_id: webview_reference_frame,
|
||||
clip_chain_id,
|
||||
},
|
||||
pipeline_id.into(),
|
||||
true,
|
||||
);
|
||||
}
|
||||
|
||||
let built_display_list = builder.end();
|
||||
|
@ -1113,12 +1073,15 @@ impl IOCompositor {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn add_webview(&mut self, webview: Box<dyn RendererWebView>) {
|
||||
let size = self.rendering_context.size2d().to_f32();
|
||||
pub fn add_webview(
|
||||
&mut self,
|
||||
webview: Box<dyn RendererWebView>,
|
||||
viewport_details: ViewportDetails,
|
||||
) {
|
||||
self.webviews.entry(webview.id()).or_insert(WebView::new(
|
||||
webview,
|
||||
Box2D::from_origin_and_size(Point2D::origin(), size),
|
||||
self.global.clone(),
|
||||
webview,
|
||||
viewport_details,
|
||||
));
|
||||
}
|
||||
|
||||
|
@ -1147,31 +1110,6 @@ impl IOCompositor {
|
|||
self.send_root_pipeline_display_list();
|
||||
}
|
||||
|
||||
pub fn move_resize_webview(&mut self, webview_id: WebViewId, rect: DeviceRect) {
|
||||
debug!("{webview_id}: Moving and/or resizing webview; rect={rect:?}");
|
||||
let rect_changed;
|
||||
let size_changed;
|
||||
match self.webviews.get_mut(webview_id) {
|
||||
Some(webview) => {
|
||||
rect_changed = rect != webview.rect;
|
||||
size_changed = rect.size() != webview.rect.size();
|
||||
webview.rect = rect;
|
||||
},
|
||||
None => {
|
||||
warn!("{webview_id}: MoveResizeWebView on unknown webview id");
|
||||
return;
|
||||
},
|
||||
};
|
||||
|
||||
if rect_changed {
|
||||
if size_changed {
|
||||
self.send_window_size_message_for_top_level_browser_context(rect, webview_id);
|
||||
}
|
||||
|
||||
self.send_root_pipeline_display_list();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn show_webview(
|
||||
&mut self,
|
||||
webview_id: WebViewId,
|
||||
|
@ -1228,37 +1166,46 @@ impl IOCompositor {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn send_window_size_message_for_top_level_browser_context(
|
||||
&self,
|
||||
rect: DeviceRect,
|
||||
webview_id: WebViewId,
|
||||
) {
|
||||
// The device pixel ratio used by the style system should include the scale from page pixels
|
||||
// to device pixels, but not including any pinch zoom.
|
||||
let hidpi_scale_factor = self.device_pixels_per_page_pixel_not_including_page_zoom();
|
||||
let size = rect.size().to_f32() / hidpi_scale_factor;
|
||||
let msg = EmbedderToConstellationMessage::ChangeViewportDetails(
|
||||
webview_id,
|
||||
ViewportDetails {
|
||||
size,
|
||||
hidpi_scale_factor,
|
||||
},
|
||||
WindowSizeType::Resize,
|
||||
);
|
||||
if let Err(e) = self.global.borrow().constellation_sender.send(msg) {
|
||||
warn!("Sending window resize to constellation failed ({:?}).", e);
|
||||
pub fn move_resize_webview(&mut self, webview_id: WebViewId, rect: DeviceRect) {
|
||||
if self.global.borrow().shutdown_state() != ShutdownState::NotShuttingDown {
|
||||
return;
|
||||
}
|
||||
let Some(webview) = self.webviews.get_mut(webview_id) else {
|
||||
return;
|
||||
};
|
||||
if !webview.set_rect(rect) {
|
||||
return;
|
||||
}
|
||||
|
||||
self.send_root_pipeline_display_list();
|
||||
self.set_needs_repaint(RepaintReason::Resize);
|
||||
}
|
||||
|
||||
pub fn resize_rendering_context(&mut self, new_size: PhysicalSize<u32>) -> bool {
|
||||
pub fn set_hidpi_scale_factor(
|
||||
&mut self,
|
||||
webview_id: WebViewId,
|
||||
new_scale_factor: Scale<f32, DeviceIndependentPixel, DevicePixel>,
|
||||
) {
|
||||
if self.global.borrow().shutdown_state() != ShutdownState::NotShuttingDown {
|
||||
return false;
|
||||
return;
|
||||
}
|
||||
let Some(webview) = self.webviews.get_mut(webview_id) else {
|
||||
return;
|
||||
};
|
||||
if !webview.set_hidpi_scale_factor(new_scale_factor) {
|
||||
return;
|
||||
}
|
||||
|
||||
let old_hidpi_factor = self.hidpi_factor;
|
||||
self.hidpi_factor = self.window.hidpi_factor();
|
||||
if self.hidpi_factor == old_hidpi_factor && self.rendering_context.size() == new_size {
|
||||
return false;
|
||||
self.send_root_pipeline_display_list();
|
||||
self.set_needs_repaint(RepaintReason::Resize);
|
||||
}
|
||||
|
||||
pub fn resize_rendering_context(&mut self, new_size: PhysicalSize<u32>) {
|
||||
if self.global.borrow().shutdown_state() != ShutdownState::NotShuttingDown {
|
||||
return;
|
||||
}
|
||||
if self.rendering_context.size() == new_size {
|
||||
return;
|
||||
}
|
||||
|
||||
self.rendering_context.resize(new_size);
|
||||
|
@ -1271,9 +1218,8 @@ impl IOCompositor {
|
|||
transaction.set_document_view(output_region);
|
||||
self.global.borrow_mut().send_transaction(transaction);
|
||||
|
||||
self.update_after_zoom_or_hidpi_change();
|
||||
self.send_root_pipeline_display_list();
|
||||
self.set_needs_repaint(RepaintReason::Resize);
|
||||
true
|
||||
}
|
||||
|
||||
/// If there are any animations running, dispatches appropriate messages to the constellation.
|
||||
|
@ -1294,41 +1240,25 @@ impl IOCompositor {
|
|||
}
|
||||
}
|
||||
|
||||
pub(crate) fn device_pixels_per_page_pixel(&self) -> Scale<f32, CSSPixel, DevicePixel> {
|
||||
self.device_pixels_per_page_pixel_not_including_page_zoom() * self.pinch_zoom_level()
|
||||
}
|
||||
|
||||
fn device_pixels_per_page_pixel_not_including_page_zoom(
|
||||
&self,
|
||||
) -> Scale<f32, CSSPixel, DevicePixel> {
|
||||
self.page_zoom * self.hidpi_factor
|
||||
}
|
||||
|
||||
pub fn on_zoom_reset_window_event(&mut self) {
|
||||
pub fn on_zoom_reset_window_event(&mut self, webview_id: WebViewId) {
|
||||
if self.global.borrow().shutdown_state() != ShutdownState::NotShuttingDown {
|
||||
return;
|
||||
}
|
||||
|
||||
self.page_zoom = Scale::new(1.0);
|
||||
self.update_after_zoom_or_hidpi_change();
|
||||
if let Some(webview) = self.webviews.get_mut(webview_id) {
|
||||
webview.set_page_zoom(1.0);
|
||||
}
|
||||
self.send_root_pipeline_display_list();
|
||||
}
|
||||
|
||||
pub fn on_zoom_window_event(&mut self, magnification: f32) {
|
||||
pub fn on_zoom_window_event(&mut self, webview_id: WebViewId, magnification: f32) {
|
||||
if self.global.borrow().shutdown_state() != ShutdownState::NotShuttingDown {
|
||||
return;
|
||||
}
|
||||
|
||||
self.page_zoom =
|
||||
Scale::new((self.page_zoom.get() * magnification).clamp(MIN_ZOOM, MAX_ZOOM));
|
||||
self.update_after_zoom_or_hidpi_change();
|
||||
}
|
||||
|
||||
fn update_after_zoom_or_hidpi_change(&mut self) {
|
||||
for (webview_id, webview) in self.webviews.painting_order() {
|
||||
self.send_window_size_message_for_top_level_browser_context(webview.rect, *webview_id);
|
||||
if let Some(webview) = self.webviews.get_mut(webview_id) {
|
||||
webview.set_page_zoom(magnification);
|
||||
}
|
||||
|
||||
// Update the root transform in WebRender to reflect the new zoom.
|
||||
self.send_root_pipeline_display_list();
|
||||
}
|
||||
|
||||
|
@ -1428,13 +1358,19 @@ impl IOCompositor {
|
|||
/// [`IOCompositor`]. If succesful return the output image in shared memory.
|
||||
fn render_to_shared_memory(
|
||||
&mut self,
|
||||
webview_id: WebViewId,
|
||||
page_rect: Option<Rect<f32, CSSPixel>>,
|
||||
) -> Result<Option<Image>, UnableToComposite> {
|
||||
self.render_inner()?;
|
||||
|
||||
let size = self.rendering_context.size2d().to_i32();
|
||||
let rect = if let Some(rect) = page_rect {
|
||||
let rect = self.device_pixels_per_page_pixel().transform_rect(&rect);
|
||||
let scale = self
|
||||
.webviews
|
||||
.get(webview_id)
|
||||
.map(WebView::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
|
||||
|
@ -1676,22 +1612,6 @@ impl IOCompositor {
|
|||
self.global.borrow().shutdown_state() != ShutdownState::FinishedShuttingDown
|
||||
}
|
||||
|
||||
pub fn pinch_zoom_level(&self) -> Scale<f32, DevicePixel, DevicePixel> {
|
||||
Scale::new(self.viewport_zoom.get())
|
||||
}
|
||||
|
||||
pub(crate) fn set_pinch_zoom_level(&mut self, mut zoom: f32) -> bool {
|
||||
if let Some(min) = self.min_viewport_zoom {
|
||||
zoom = f32::max(min.get(), zoom);
|
||||
}
|
||||
if let Some(max) = self.max_viewport_zoom {
|
||||
zoom = f32::min(max.get(), zoom);
|
||||
}
|
||||
|
||||
let old_zoom = std::mem::replace(&mut self.viewport_zoom, PinchZoomFactor::new(zoom));
|
||||
old_zoom != self.viewport_zoom
|
||||
}
|
||||
|
||||
pub fn toggle_webrender_debug(&mut self, option: WebRenderDebugOption) {
|
||||
let Some(webrender) = self.webrender.as_mut() else {
|
||||
return;
|
||||
|
|
|
@ -9,17 +9,21 @@ use std::rc::Rc;
|
|||
|
||||
use base::id::{PipelineId, WebViewId};
|
||||
use compositing_traits::{RendererWebView, SendableFrameTree};
|
||||
use constellation_traits::{EmbedderToConstellationMessage, ScrollState};
|
||||
use constellation_traits::{EmbedderToConstellationMessage, ScrollState, WindowSizeType};
|
||||
use embedder_traits::{
|
||||
AnimationState, CompositorHitTestResult, InputEvent, MouseButton, MouseButtonAction,
|
||||
MouseButtonEvent, MouseMoveEvent, ShutdownState, TouchEvent, TouchEventResult, TouchEventType,
|
||||
TouchId,
|
||||
TouchId, ViewportDetails,
|
||||
};
|
||||
use euclid::{Point2D, Scale, Vector2D};
|
||||
use euclid::{Box2D, Point2D, Scale, Size2D, Vector2D};
|
||||
use fnv::FnvHashSet;
|
||||
use log::{debug, warn};
|
||||
use servo_geometry::DeviceIndependentPixel;
|
||||
use style_traits::{CSSPixel, PinchZoomFactor};
|
||||
use webrender::Transaction;
|
||||
use webrender_api::units::{DeviceIntPoint, DevicePoint, DeviceRect, LayoutVector2D};
|
||||
use webrender_api::units::{
|
||||
DeviceIntPoint, DeviceIntRect, DevicePixel, DevicePoint, DeviceRect, LayoutVector2D,
|
||||
};
|
||||
use webrender_api::{
|
||||
ExternalScrollId, HitTestFlags, RenderReasons, SampledScrollOffset, ScrollLocation,
|
||||
};
|
||||
|
@ -28,6 +32,10 @@ use crate::IOCompositor;
|
|||
use crate::compositor::{PipelineDetails, ServoRenderer};
|
||||
use crate::touch::{TouchHandler, TouchMoveAction, TouchMoveAllowed, TouchSequenceState};
|
||||
|
||||
// Default viewport constraints
|
||||
const MAX_ZOOM: f32 = 8.0;
|
||||
const MIN_ZOOM: f32 = 0.1;
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
struct ScrollEvent {
|
||||
/// Scroll by this offset, or to Start or End
|
||||
|
@ -65,6 +73,16 @@ pub(crate) struct WebView {
|
|||
pending_scroll_zoom_events: Vec<ScrollZoomEvent>,
|
||||
/// Touch input state machine
|
||||
touch_handler: TouchHandler,
|
||||
/// "Desktop-style" zoom that resizes the viewport to fit the window.
|
||||
pub page_zoom: Scale<f32, CSSPixel, DeviceIndependentPixel>,
|
||||
/// "Mobile-style" zoom that does not reflow the page.
|
||||
viewport_zoom: PinchZoomFactor,
|
||||
/// Viewport zoom constraints provided by @viewport.
|
||||
min_viewport_zoom: Option<PinchZoomFactor>,
|
||||
max_viewport_zoom: Option<PinchZoomFactor>,
|
||||
/// The HiDPI scale factor for the `WebView` associated with this renderer. This is controlled
|
||||
/// by the embedding layer.
|
||||
hidpi_scale_factor: Scale<f32, DeviceIndependentPixel, DevicePixel>,
|
||||
}
|
||||
|
||||
impl Drop for WebView {
|
||||
|
@ -78,19 +96,26 @@ impl Drop for WebView {
|
|||
|
||||
impl WebView {
|
||||
pub(crate) fn new(
|
||||
renderer_webview: Box<dyn RendererWebView>,
|
||||
rect: DeviceRect,
|
||||
global: Rc<RefCell<ServoRenderer>>,
|
||||
renderer_webview: Box<dyn RendererWebView>,
|
||||
viewport_details: ViewportDetails,
|
||||
) -> Self {
|
||||
let hidpi_scale_factor = viewport_details.hidpi_scale_factor;
|
||||
let size = viewport_details.size * viewport_details.hidpi_scale_factor;
|
||||
Self {
|
||||
id: renderer_webview.id(),
|
||||
renderer_webview,
|
||||
root_pipeline_id: None,
|
||||
rect,
|
||||
rect: DeviceRect::from_origin_and_size(DevicePoint::origin(), size),
|
||||
pipelines: Default::default(),
|
||||
touch_handler: TouchHandler::new(),
|
||||
global,
|
||||
pending_scroll_zoom_events: Default::default(),
|
||||
page_zoom: Scale::new(1.0),
|
||||
viewport_zoom: PinchZoomFactor::new(1.0),
|
||||
min_viewport_zoom: Some(PinchZoomFactor::new(1.0)),
|
||||
max_viewport_zoom: None,
|
||||
hidpi_scale_factor: Scale::new(hidpi_scale_factor.0),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -265,7 +290,7 @@ impl WebView {
|
|||
}
|
||||
|
||||
/// On a Window refresh tick (e.g. vsync)
|
||||
pub fn on_vsync(&mut self) {
|
||||
pub(crate) fn on_vsync(&mut self) {
|
||||
if let Some(fling_action) = self.touch_handler.on_vsync() {
|
||||
self.on_scroll_window_event(
|
||||
ScrollLocation::Delta(fling_action.delta),
|
||||
|
@ -300,7 +325,7 @@ impl WebView {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn notify_input_event(&mut self, event: InputEvent) {
|
||||
pub(crate) fn notify_input_event(&mut self, event: InputEvent) {
|
||||
if self.global.borrow().shutdown_state() != ShutdownState::NotShuttingDown {
|
||||
return;
|
||||
}
|
||||
|
@ -365,7 +390,7 @@ impl WebView {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn on_touch_event(&mut self, event: TouchEvent) {
|
||||
pub(crate) fn on_touch_event(&mut self, event: TouchEvent) {
|
||||
if self.global.borrow().shutdown_state() != ShutdownState::NotShuttingDown {
|
||||
return;
|
||||
}
|
||||
|
@ -644,7 +669,7 @@ impl WebView {
|
|||
}));
|
||||
}
|
||||
|
||||
pub fn notify_scroll_event(
|
||||
pub(crate) fn notify_scroll_event(
|
||||
&mut self,
|
||||
scroll_location: ScrollLocation,
|
||||
cursor: DeviceIntPoint,
|
||||
|
@ -727,13 +752,12 @@ impl WebView {
|
|||
}
|
||||
}
|
||||
|
||||
let zoom_changed = compositor
|
||||
.set_pinch_zoom_level(compositor.pinch_zoom_level().get() * combined_magnification);
|
||||
let zoom_changed =
|
||||
self.set_pinch_zoom_level(self.pinch_zoom_level().get() * combined_magnification);
|
||||
let scroll_result = combined_scroll_event.and_then(|combined_event| {
|
||||
self.scroll_node_at_device_point(
|
||||
combined_event.cursor.to_f32(),
|
||||
combined_event.scroll_location,
|
||||
compositor,
|
||||
)
|
||||
});
|
||||
if !zoom_changed && scroll_result.is_none() {
|
||||
|
@ -769,11 +793,10 @@ impl WebView {
|
|||
&mut self,
|
||||
cursor: DevicePoint,
|
||||
scroll_location: ScrollLocation,
|
||||
compositor: &mut IOCompositor,
|
||||
) -> Option<(PipelineId, ExternalScrollId, LayoutVector2D)> {
|
||||
let scroll_location = match scroll_location {
|
||||
ScrollLocation::Delta(delta) => {
|
||||
let device_pixels_per_page = compositor.device_pixels_per_page_pixel();
|
||||
let device_pixels_per_page = self.device_pixels_per_page_pixel();
|
||||
let scaled_delta = (Vector2D::from_untyped(delta.to_untyped()) /
|
||||
device_pixels_per_page)
|
||||
.to_untyped();
|
||||
|
@ -818,8 +841,39 @@ impl WebView {
|
|||
None
|
||||
}
|
||||
|
||||
pub(crate) fn pinch_zoom_level(&self) -> Scale<f32, DevicePixel, DevicePixel> {
|
||||
Scale::new(self.viewport_zoom.get())
|
||||
}
|
||||
|
||||
fn set_pinch_zoom_level(&mut self, mut zoom: f32) -> bool {
|
||||
if let Some(min) = self.min_viewport_zoom {
|
||||
zoom = f32::max(min.get(), zoom);
|
||||
}
|
||||
if let Some(max) = self.max_viewport_zoom {
|
||||
zoom = f32::min(max.get(), zoom);
|
||||
}
|
||||
|
||||
let old_zoom = std::mem::replace(&mut self.viewport_zoom, PinchZoomFactor::new(zoom));
|
||||
old_zoom != self.viewport_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 device_pixels_per_page_pixel(&self) -> Scale<f32, CSSPixel, DevicePixel> {
|
||||
self.page_zoom * self.hidpi_scale_factor * self.pinch_zoom_level()
|
||||
}
|
||||
|
||||
pub(crate) fn device_pixels_per_page_pixel_not_including_pinch_zoom(
|
||||
&self,
|
||||
) -> Scale<f32, CSSPixel, DevicePixel> {
|
||||
self.page_zoom * self.hidpi_scale_factor
|
||||
}
|
||||
|
||||
/// Simulate a pinch zoom
|
||||
pub fn set_pinch_zoom(&mut self, magnification: f32) {
|
||||
pub(crate) fn set_pinch_zoom(&mut self, magnification: f32) {
|
||||
if self.global.borrow().shutdown_state() != ShutdownState::NotShuttingDown {
|
||||
return;
|
||||
}
|
||||
|
@ -828,6 +882,71 @@ impl WebView {
|
|||
self.pending_scroll_zoom_events
|
||||
.push(ScrollZoomEvent::PinchZoom(magnification));
|
||||
}
|
||||
|
||||
fn send_window_size_message(&self) {
|
||||
// The device pixel ratio used by the style system should include the scale from page pixels
|
||||
// to device pixels, but not including any pinch zoom.
|
||||
let device_pixel_ratio = self.device_pixels_per_page_pixel_not_including_pinch_zoom();
|
||||
let initial_viewport = self.rect.size().to_f32() / device_pixel_ratio;
|
||||
let msg = EmbedderToConstellationMessage::ChangeViewportDetails(
|
||||
self.id,
|
||||
ViewportDetails {
|
||||
hidpi_scale_factor: device_pixel_ratio,
|
||||
size: initial_viewport,
|
||||
},
|
||||
WindowSizeType::Resize,
|
||||
);
|
||||
if let Err(e) = self.global.borrow().constellation_sender.send(msg) {
|
||||
warn!("Sending window resize to constellation failed ({:?}).", e);
|
||||
}
|
||||
}
|
||||
|
||||
/// Set the `hidpi_scale_factor` for this renderer, returning `true` if the value actually changed.
|
||||
pub(crate) fn set_hidpi_scale_factor(
|
||||
&mut self,
|
||||
new_scale: Scale<f32, DeviceIndependentPixel, DevicePixel>,
|
||||
) -> bool {
|
||||
let old_scale_factor = std::mem::replace(&mut self.hidpi_scale_factor, new_scale);
|
||||
if self.hidpi_scale_factor == old_scale_factor {
|
||||
return false;
|
||||
}
|
||||
|
||||
self.send_window_size_message();
|
||||
true
|
||||
}
|
||||
|
||||
/// Set the `rect` for this renderer, returning `true` if the value actually changed.
|
||||
pub(crate) fn set_rect(&mut self, new_rect: DeviceRect) -> bool {
|
||||
let old_rect = std::mem::replace(&mut self.rect, new_rect);
|
||||
if old_rect.size() != self.rect.size() {
|
||||
self.send_window_size_message();
|
||||
}
|
||||
old_rect != self.rect
|
||||
}
|
||||
|
||||
pub(crate) fn client_window_rect(
|
||||
&self,
|
||||
rendering_context_size: Size2D<u32, DevicePixel>,
|
||||
) -> Box2D<i32, DeviceIndependentPixel> {
|
||||
let screen_geometry = self.renderer_webview.screen_geometry().unwrap_or_default();
|
||||
let rect = DeviceIntRect::from_origin_and_size(
|
||||
screen_geometry.offset,
|
||||
rendering_context_size.to_i32(),
|
||||
)
|
||||
.to_f32() /
|
||||
self.hidpi_scale_factor;
|
||||
rect.to_i32()
|
||||
}
|
||||
|
||||
pub(crate) fn screen_size(&self) -> Size2D<i32, DeviceIndependentPixel> {
|
||||
let screen_geometry = self.renderer_webview.screen_geometry().unwrap_or_default();
|
||||
(screen_geometry.size.to_f32() / self.hidpi_scale_factor).to_i32()
|
||||
}
|
||||
|
||||
pub(crate) fn available_screen_size(&self) -> Size2D<i32, DeviceIndependentPixel> {
|
||||
let screen_geometry = self.renderer_webview.screen_geometry().unwrap_or_default();
|
||||
(screen_geometry.available_size.to_f32() / self.hidpi_scale_factor).to_i32()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
|
|
|
@ -5,10 +5,8 @@
|
|||
//! Abstract windowing methods. The concrete implementations of these can be found in `platform/`.
|
||||
|
||||
use embedder_traits::{EventLoopWaker, MouseButton};
|
||||
use euclid::Scale;
|
||||
use net::protocols::ProtocolRegistry;
|
||||
use servo_geometry::DeviceIndependentPixel;
|
||||
use webrender_api::units::{DevicePixel, DevicePoint};
|
||||
use webrender_api::units::DevicePoint;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub enum MouseWindowEvent {
|
||||
|
@ -25,15 +23,6 @@ pub enum WebRenderDebugOption {
|
|||
RenderTargetDebug,
|
||||
}
|
||||
|
||||
// TODO: this trait assumes that the window is responsible
|
||||
// for creating the GL context, making it current, buffer
|
||||
// swapping, etc. Really that should all be done by surfman.
|
||||
pub trait WindowMethods {
|
||||
/// Get the HighDPI factor of the native window, the screen and the framebuffer.
|
||||
/// TODO(martin): Move this to `RendererWebView` when possible.
|
||||
fn hidpi_factor(&self) -> Scale<f32, DeviceIndependentPixel, DevicePixel>;
|
||||
}
|
||||
|
||||
pub trait EmbedderMethods {
|
||||
/// Returns a thread-safe object to wake up the window's event loop.
|
||||
fn create_event_loop_waker(&mut self) -> Box<dyn EventLoopWaker>;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue