use std::cell::RefCell;
use std::collections::HashMap;
use std::collections::hash_map::Keys;
use std::rc::Rc;
use base::id::{PipelineId, WebViewId};
use compositing_traits::{RendererWebView, SendableFrameTree};
use constellation_traits::{EmbedderToConstellationMessage, ScrollState, WindowSizeType};
use embedder_traits::{
AnimationState, CompositorHitTestResult, InputEvent, MouseButton, MouseButtonAction,
MouseButtonEvent, MouseMoveEvent, ShutdownState, TouchEvent, TouchEventResult, TouchEventType,
TouchId, ViewportDetails,
};
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, DeviceIntRect, DevicePixel, DevicePoint, DeviceRect, LayoutVector2D,
};
use webrender_api::{
ExternalScrollId, HitTestFlags, RenderReasons, SampledScrollOffset, ScrollLocation,
};
use crate::IOCompositor;
use crate::compositor::{PipelineDetails, ServoRenderer};
use crate::touch::{TouchHandler, TouchMoveAction, TouchMoveAllowed, TouchSequenceState};
const MAX_ZOOM: f32 = 8.0;
const MIN_ZOOM: f32 = 0.1;
#[derive(Clone, Copy)]
struct ScrollEvent {
scroll_location: ScrollLocation,
cursor: DeviceIntPoint,
event_count: u32,
}
#[derive(Clone, Copy)]
enum ScrollZoomEvent {
PinchZoom(f32),
Scroll(ScrollEvent),
}
pub(crate) struct WebView {
pub id: WebViewId,
pub renderer_webview: Box<dyn RendererWebView>,
pub root_pipeline_id: Option<PipelineId>,
pub rect: DeviceRect,
pub pipelines: HashMap<PipelineId, PipelineDetails>,
pub(crate) global: Rc<RefCell<ServoRenderer>>,
pending_scroll_zoom_events: Vec<ScrollZoomEvent>,
touch_handler: TouchHandler,
pub page_zoom: Scale<f32, CSSPixel, DeviceIndependentPixel>,
viewport_zoom: PinchZoomFactor,
min_viewport_zoom: Option<PinchZoomFactor>,
max_viewport_zoom: Option<PinchZoomFactor>,
hidpi_scale_factor: Scale<f32, DeviceIndependentPixel, DevicePixel>,
}
impl Drop for WebView {
fn drop(&mut self) {
self.global
.borrow_mut()
.pipeline_to_webview_map
.retain(|_, webview_id| self.id != *webview_id);
}
}
impl WebView {
pub(crate) fn new(
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: 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),
}
}
pub(crate) fn animations_or_animation_callbacks_running(&self) -> bool {
self.pipelines
.values()
.any(PipelineDetails::animations_or_animation_callbacks_running)
}
pub(crate) fn animation_callbacks_running(&self) -> bool {
self.pipelines
.values()
.any(PipelineDetails::animation_callbacks_running)
}
pub(crate) fn pipeline_ids(&self) -> Keys<'_, PipelineId, PipelineDetails> {
self.pipelines.keys()
}
pub(crate) fn ensure_pipeline_details(
&mut self,
pipeline_id: PipelineId,
) -> &mut PipelineDetails {
self.pipelines.entry(pipeline_id).or_insert_with(|| {
self.global
.borrow_mut()
.pipeline_to_webview_map
.insert(pipeline_id, self.id);
PipelineDetails::new(pipeline_id)
})
}
pub(crate) fn set_throttled(&mut self, pipeline_id: PipelineId, throttled: bool) {
self.ensure_pipeline_details(pipeline_id).throttled = throttled;
}
pub(crate) fn remove_pipeline(&mut self, pipeline_id: PipelineId) {
self.global
.borrow_mut()
.pipeline_to_webview_map
.remove(&pipeline_id);
self.pipelines.remove(&pipeline_id);
}
pub(crate) fn set_frame_tree(&mut self, frame_tree: &SendableFrameTree) {
let pipeline_id = frame_tree.pipeline.id;
let old_pipeline_id = std::mem::replace(&mut self.root_pipeline_id, Some(pipeline_id));
if old_pipeline_id != self.root_pipeline_id {
debug!(
"Updating webview ({:?}) from pipeline {:?} to {:?}",
3, old_pipeline_id, self.root_pipeline_id
);
}
self.set_frame_tree_on_pipeline_details(frame_tree, None);
self.reset_scroll_tree_for_unattached_pipelines(frame_tree);
self.send_scroll_positions_to_layout_for_pipeline(pipeline_id);
}
pub(crate) fn send_scroll_positions_to_layout_for_pipeline(&self, pipeline_id: PipelineId) {
let Some(details) = self.pipelines.get(&pipeline_id) else {
return;
};
let mut scroll_states = Vec::new();
details.scroll_tree.nodes.iter().for_each(|node| {
if let (Some(scroll_id), Some(scroll_offset)) = (node.external_id(), node.offset()) {
scroll_states.push(ScrollState {
scroll_id,
scroll_offset,
});
}
});
let _ = self.global.borrow().constellation_sender.send(
EmbedderToConstellationMessage::SetScrollStates(pipeline_id, scroll_states),
);
}
pub(crate) fn set_frame_tree_on_pipeline_details(
&mut self,
frame_tree: &SendableFrameTree,
parent_pipeline_id: Option<PipelineId>,
) {
let pipeline_id = frame_tree.pipeline.id;
let pipeline_details = self.ensure_pipeline_details(pipeline_id);
pipeline_details.pipeline = Some(frame_tree.pipeline.clone());
pipeline_details.parent_pipeline_id = parent_pipeline_id;
for kid in &frame_tree.children {
self.set_frame_tree_on_pipeline_details(kid, Some(pipeline_id));
}
}
pub(crate) fn reset_scroll_tree_for_unattached_pipelines(
&mut self,
frame_tree: &SendableFrameTree,
) {
fn collect_pipelines(
pipelines: &mut FnvHashSet<PipelineId>,
frame_tree: &SendableFrameTree,
) {
pipelines.insert(frame_tree.pipeline.id);
for kid in &frame_tree.children {
collect_pipelines(pipelines, kid);
}
}
let mut attached_pipelines: FnvHashSet<PipelineId> = FnvHashSet::default();
collect_pipelines(&mut attached_pipelines, frame_tree);
self.pipelines
.iter_mut()
.filter(|(id, _)| !attached_pipelines.contains(id))
.for_each(|(_, details)| {
details.scroll_tree.nodes.iter_mut().for_each(|node| {
node.set_offset(LayoutVector2D::zero());
})
})
}
pub(crate) fn change_running_animations_state(
&mut self,
pipeline_id: PipelineId,
animation_state: AnimationState,
) -> bool {
let throttled = {
let pipeline_details = self.ensure_pipeline_details(pipeline_id);
match animation_state {
AnimationState::AnimationsPresent => {
pipeline_details.animations_running = true;
},
AnimationState::AnimationCallbacksPresent => {
pipeline_details.animation_callbacks_running = true;
},
AnimationState::NoAnimationsPresent => {
pipeline_details.animations_running = false;
},
AnimationState::NoAnimationCallbacksPresent => {
pipeline_details.animation_callbacks_running = false;
},
}
pipeline_details.throttled
};
let animating = self.pipelines.values().any(PipelineDetails::animating);
self.renderer_webview.set_animating(animating);
throttled
}
pub(crate) fn tick_all_animations(&self, compositor: &IOCompositor) {
for pipeline_details in self.pipelines.values() {
pipeline_details.tick_animations(compositor)
}
}
pub(crate) fn tick_animations_for_pipeline(
&self,
pipeline_id: PipelineId,
compositor: &IOCompositor,
) {
if let Some(pipeline_details) = self.pipelines.get(&pipeline_id) {
pipeline_details.tick_animations(compositor);
}
}
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),
fling_action.cursor,
);
}
}
pub(crate) fn dispatch_input_event(&mut self, event: InputEvent) {
let Some(point) = event.point() else {
return;
};
let get_pipeline_details = |pipeline_id| self.pipelines.get(&pipeline_id);
let Some(result) = self
.global
.borrow()
.hit_test_at_point(point, get_pipeline_details)
else {
return;
};
self.global.borrow_mut().update_cursor(point, &result);
if let Err(error) = self.global.borrow().constellation_sender.send(
EmbedderToConstellationMessage::ForwardInputEvent(self.id, event, Some(result)),
) {
warn!("Sending event to constellation failed ({error:?}).");
}
}
pub(crate) fn notify_input_event(&mut self, event: InputEvent) {
if self.global.borrow().shutdown_state() != ShutdownState::NotShuttingDown {
return;
}
if let InputEvent::Touch(event) = event {
self.on_touch_event(event);
return;
}
if self.global.borrow().convert_mouse_to_touch {
match event {
InputEvent::MouseButton(event) => {
match (event.button, event.action) {
(MouseButton::Left, MouseButtonAction::Down) => self.on_touch_down(
TouchEvent::new(TouchEventType::Down, TouchId(0), event.point),
),
(MouseButton::Left, MouseButtonAction::Up) => self.on_touch_up(
TouchEvent::new(TouchEventType::Up, TouchId(0), event.point),
),
_ => {},
}
return;
},
InputEvent::MouseMove(event) => {
if let Some(state) = self.touch_handler.try_get_current_touch_sequence() {
match state.state {
TouchSequenceState::Touching | TouchSequenceState::Panning { .. } => {
self.on_touch_move(TouchEvent::new(
TouchEventType::Move,
TouchId(0),
event.point,
));
},
TouchSequenceState::MultiTouch => {
},
TouchSequenceState::Pinching => {
#[cfg(debug_assertions)]
log::error!(
"Touch handler is in Pinching state, which should be unreachable with \
-Z convert-mouse-to-touch debug option."
);
},
TouchSequenceState::PendingFling { .. } |
TouchSequenceState::Flinging { .. } |
TouchSequenceState::PendingClick(_) |
TouchSequenceState::Finished => {
},
}
}
return;
},
_ => {},
}
}
self.dispatch_input_event(event);
}
fn send_touch_event(&self, mut event: TouchEvent) -> bool {
let get_pipeline_details = |pipeline_id| self.pipelines.get(&pipeline_id);
let Some(result) = self
.global
.borrow()
.hit_test_at_point(event.point, get_pipeline_details)
else {
return false;
};
event.init_sequence_id(self.touch_handler.current_sequence_id);
let event = InputEvent::Touch(event);
if let Err(e) = self.global.borrow().constellation_sender.send(
EmbedderToConstellationMessage::ForwardInputEvent(self.id, event, Some(result)),
) {
warn!("Sending event to constellation failed ({:?}).", e);
false
} else {
true
}
}
pub(crate) fn on_touch_event(&mut self, event: TouchEvent) {
if self.global.borrow().shutdown_state() != ShutdownState::NotShuttingDown {
return;
}
match event.event_type {
TouchEventType::Down => self.on_touch_down(event),
TouchEventType::Move => self.on_touch_move(event),
TouchEventType::Up => self.on_touch_up(event),
TouchEventType::Cancel => self.on_touch_cancel(event),
}
}
fn on_touch_down(&mut self, event: TouchEvent) {
self.touch_handler.on_touch_down(event.id, event.point);
self.send_touch_event(event);
}
fn on_touch_move(&mut self, mut event: TouchEvent) {
let action: TouchMoveAction = self.touch_handler.on_touch_move(event.id, event.point);
if TouchMoveAction::NoAction != action {
if self
.touch_handler
.move_allowed(self.touch_handler.current_sequence_id)
{
event.disable_cancelable();
match action {
TouchMoveAction::Scroll(delta, point) => self.on_scroll_window_event(
ScrollLocation::Delta(LayoutVector2D::from_untyped(delta.to_untyped())),
point.cast(),
),
TouchMoveAction::Zoom(magnification, scroll_delta) => {
let cursor = Point2D::new(-1, -1); self.pending_scroll_zoom_events
.push(ScrollZoomEvent::PinchZoom(magnification));
self.pending_scroll_zoom_events
.push(ScrollZoomEvent::Scroll(ScrollEvent {
scroll_location: ScrollLocation::Delta(
LayoutVector2D::from_untyped(scroll_delta.to_untyped()),
),
cursor,
event_count: 1,
}));
},
_ => {},
}
}
if !self
.touch_handler
.is_handling_touch_move(self.touch_handler.current_sequence_id) &&
self.send_touch_event(event) &&
event.is_cancelable()
{
self.touch_handler
.set_handling_touch_move(self.touch_handler.current_sequence_id, true);
}
}
}
fn on_touch_up(&mut self, event: TouchEvent) {
self.touch_handler.on_touch_up(event.id, event.point);
self.send_touch_event(event);
}
fn on_touch_cancel(&mut self, event: TouchEvent) {
self.touch_handler.on_touch_cancel(event.id, event.point);
self.send_touch_event(event);
}
pub(crate) fn on_touch_event_processed(&mut self, result: TouchEventResult) {
match result {
TouchEventResult::DefaultPrevented(sequence_id, event_type) => {
debug!(
"Touch event {:?} in sequence {:?} prevented!",
event_type, sequence_id
);
match event_type {
TouchEventType::Down => {
self.touch_handler.prevent_click(sequence_id);
self.touch_handler.prevent_move(sequence_id);
self.touch_handler
.remove_pending_touch_move_action(sequence_id);
},
TouchEventType::Move => {
if let Some(info) = self.touch_handler.get_touch_sequence_mut(sequence_id) {
info.prevent_move = TouchMoveAllowed::Prevented;
if let TouchSequenceState::PendingFling { .. } = info.state {
info.state = TouchSequenceState::Finished;
}
self.touch_handler.set_handling_touch_move(
self.touch_handler.current_sequence_id,
false,
);
self.touch_handler
.remove_pending_touch_move_action(sequence_id);
}
},
TouchEventType::Up => {
let Some(info) =
&mut self.touch_handler.get_touch_sequence_mut(sequence_id)
else {
return;
};
match info.state {
TouchSequenceState::PendingClick(_) => {
info.state = TouchSequenceState::Finished;
self.touch_handler.remove_touch_sequence(sequence_id);
},
TouchSequenceState::Flinging { .. } => {
},
TouchSequenceState::Finished => {
self.touch_handler.remove_touch_sequence(sequence_id);
},
TouchSequenceState::Touching |
TouchSequenceState::Panning { .. } |
TouchSequenceState::Pinching |
TouchSequenceState::MultiTouch |
TouchSequenceState::PendingFling { .. } => {
},
}
},
TouchEventType::Cancel => {
self.touch_handler
.remove_pending_touch_move_action(sequence_id);
self.touch_handler.try_remove_touch_sequence(sequence_id);
},
}
},
TouchEventResult::DefaultAllowed(sequence_id, event_type) => {
debug!(
"Touch event {:?} in sequence {:?} allowed",
event_type, sequence_id
);
match event_type {
TouchEventType::Down => {},
TouchEventType::Move => {
if let Some(action) =
self.touch_handler.pending_touch_move_action(sequence_id)
{
match action {
TouchMoveAction::Scroll(delta, point) => self
.on_scroll_window_event(
ScrollLocation::Delta(LayoutVector2D::from_untyped(
delta.to_untyped(),
)),
point.cast(),
),
TouchMoveAction::Zoom(magnification, scroll_delta) => {
let cursor = Point2D::new(-1, -1);
self.pending_scroll_zoom_events
.push(ScrollZoomEvent::PinchZoom(magnification));
self.pending_scroll_zoom_events
.push(ScrollZoomEvent::Scroll(ScrollEvent {
scroll_location: ScrollLocation::Delta(
LayoutVector2D::from_untyped(
scroll_delta.to_untyped(),
),
),
cursor,
event_count: 1,
}));
},
TouchMoveAction::NoAction => {
},
}
self.touch_handler
.remove_pending_touch_move_action(sequence_id);
}
self.touch_handler
.set_handling_touch_move(self.touch_handler.current_sequence_id, false);
if let Some(info) = self.touch_handler.get_touch_sequence_mut(sequence_id) {
if info.prevent_move == TouchMoveAllowed::Pending {
info.prevent_move = TouchMoveAllowed::Allowed;
if let TouchSequenceState::PendingFling { velocity, cursor } =
info.state
{
info.state = TouchSequenceState::Flinging { velocity, cursor }
}
}
}
},
TouchEventType::Up => {
let Some(info) = self.touch_handler.get_touch_sequence_mut(sequence_id)
else {
return;
};
match info.state {
TouchSequenceState::PendingClick(point) => {
info.state = TouchSequenceState::Finished;
if !info.prevent_click {
self.simulate_mouse_click(point);
}
self.touch_handler.remove_touch_sequence(sequence_id);
},
TouchSequenceState::Flinging { .. } => {
},
TouchSequenceState::Finished => {
self.touch_handler.remove_touch_sequence(sequence_id);
},
TouchSequenceState::Panning { .. } |
TouchSequenceState::Pinching |
TouchSequenceState::PendingFling { .. } => {
},
TouchSequenceState::MultiTouch | TouchSequenceState::Touching => {
},
}
},
TouchEventType::Cancel => {
self.touch_handler
.remove_pending_touch_move_action(sequence_id);
self.touch_handler.try_remove_touch_sequence(sequence_id);
},
}
},
}
}
fn simulate_mouse_click(&mut self, point: DevicePoint) {
let button = MouseButton::Left;
self.dispatch_input_event(InputEvent::MouseMove(MouseMoveEvent { point }));
self.dispatch_input_event(InputEvent::MouseButton(MouseButtonEvent {
button,
action: MouseButtonAction::Down,
point,
}));
self.dispatch_input_event(InputEvent::MouseButton(MouseButtonEvent {
button,
action: MouseButtonAction::Up,
point,
}));
self.dispatch_input_event(InputEvent::MouseButton(MouseButtonEvent {
button,
action: MouseButtonAction::Click,
point,
}));
}
pub(crate) fn notify_scroll_event(
&mut self,
scroll_location: ScrollLocation,
cursor: DeviceIntPoint,
event_type: TouchEventType,
) {
if self.global.borrow().shutdown_state() != ShutdownState::NotShuttingDown {
return;
}
match event_type {
TouchEventType::Move => self.on_scroll_window_event(scroll_location, cursor),
TouchEventType::Up | TouchEventType::Cancel => {
self.on_scroll_window_event(scroll_location, cursor);
},
TouchEventType::Down => {
self.on_scroll_window_event(scroll_location, cursor);
},
}
}
fn on_scroll_window_event(&mut self, scroll_location: ScrollLocation, cursor: DeviceIntPoint) {
self.pending_scroll_zoom_events
.push(ScrollZoomEvent::Scroll(ScrollEvent {
scroll_location,
cursor,
event_count: 1,
}));
}
pub(crate) fn process_pending_scroll_events(&mut self, compositor: &mut IOCompositor) {
if self.pending_scroll_zoom_events.is_empty() {
return;
}
let mut combined_scroll_event: Option<ScrollEvent> = None;
let mut combined_magnification = 1.0;
for scroll_event in self.pending_scroll_zoom_events.drain(..) {
match scroll_event {
ScrollZoomEvent::PinchZoom(magnification) => {
combined_magnification *= magnification
},
ScrollZoomEvent::Scroll(scroll_event_info) => {
let combined_event = match combined_scroll_event.as_mut() {
None => {
combined_scroll_event = Some(scroll_event_info);
continue;
},
Some(combined_event) => combined_event,
};
match (
combined_event.scroll_location,
scroll_event_info.scroll_location,
) {
(ScrollLocation::Delta(old_delta), ScrollLocation::Delta(new_delta)) => {
let old_event_count = Scale::new(combined_event.event_count as f32);
combined_event.event_count += 1;
let new_event_count = Scale::new(combined_event.event_count as f32);
combined_event.scroll_location = ScrollLocation::Delta(
(old_delta * old_event_count + new_delta) / new_event_count,
);
},
(ScrollLocation::Start, _) | (ScrollLocation::End, _) => {
break;
},
(_, ScrollLocation::Start) | (_, ScrollLocation::End) => {
*combined_event = scroll_event_info;
break;
},
}
},
}
}
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,
)
});
if !zoom_changed && scroll_result.is_none() {
return;
}
let mut transaction = Transaction::new();
if zoom_changed {
compositor.send_root_pipeline_display_list_in_transaction(&mut transaction);
}
if let Some((pipeline_id, external_id, offset)) = scroll_result {
let offset = LayoutVector2D::new(-offset.x, -offset.y);
transaction.set_scroll_offsets(
external_id,
vec![SampledScrollOffset {
offset,
generation: 0,
}],
);
self.send_scroll_positions_to_layout_for_pipeline(pipeline_id);
}
compositor.generate_frame(&mut transaction, RenderReasons::APZ);
self.global.borrow_mut().send_transaction(transaction);
}
fn scroll_node_at_device_point(
&mut self,
cursor: DevicePoint,
scroll_location: ScrollLocation,
) -> Option<(PipelineId, ExternalScrollId, LayoutVector2D)> {
let scroll_location = match scroll_location {
ScrollLocation::Delta(delta) => {
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();
let calculated_delta = LayoutVector2D::from_untyped(scaled_delta);
ScrollLocation::Delta(calculated_delta)
},
ScrollLocation::Start | ScrollLocation::End => scroll_location,
};
let get_pipeline_details = |pipeline_id| self.pipelines.get(&pipeline_id);
let hit_test_results = self
.global
.borrow()
.hit_test_at_point_with_flags_and_pipeline(
cursor,
HitTestFlags::FIND_ALL,
None,
get_pipeline_details,
);
let mut previous_pipeline_id = None;
for CompositorHitTestResult {
pipeline_id,
scroll_tree_node,
..
} in hit_test_results.iter()
{
let pipeline_details = self.pipelines.get_mut(pipeline_id)?;
if previous_pipeline_id.replace(pipeline_id) != Some(pipeline_id) {
let scroll_result = pipeline_details
.scroll_tree
.scroll_node_or_ancestor(scroll_tree_node, scroll_location);
if let Some((external_id, offset)) = scroll_result {
return Some((*pipeline_id, external_id, offset));
}
}
}
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
}
pub(crate) fn set_pinch_zoom(&mut self, magnification: f32) {
if self.global.borrow().shutdown_state() != ShutdownState::NotShuttingDown {
return;
}
self.pending_scroll_zoom_events
.push(ScrollZoomEvent::PinchZoom(magnification));
}
fn send_window_size_message(&self) {
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);
}
}
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
}
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)]
pub struct UnknownWebView(pub WebViewId);