constellation: Stop assuming that the viewport is shared by all WebViews (#36312)

The `Constellation` previously held a `window_size` member, but this
assumes that all `WebView`s have the same size. This change removes that
assumption as well as making sure that all `WebView`s pass their size
and HiDIP scaling to the `Constellation` when they are created.

In addition

- `WindowSizeData` is renamed to `ViewportDetails`, as it was
holding more than just the size and it didn't necessarily correspond to
  a "window." It's used for tracking viewport data, whether for an
  `<iframe>` or the main `WebView` viewport.
- `ViewportDetails` is stored more consistently so that conceptually an
  `<iframe>` can also have its own HiDPI scaling. This isn't something
  we necessarily want, but it makes everything conceptually simpler.

The goal with this change is to work toward allowing per-`WebView` HiDPI
scaling and sizing. There are still some corresponding changes in the
compositor to make that happen, but they will in a subsequent change.

Testing: This is covered by existing tests. There should be no behavior
changes.
Fixes: This is part of #36232.

Signed-off-by: Martin Robinson <mrobinson@igalia.com>

Signed-off-by: Martin Robinson <mrobinson@igalia.com>
This commit is contained in:
Martin Robinson 2025-04-04 19:06:34 +02:00 committed by GitHub
parent 7c89e24f34
commit fb344ba4e9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
26 changed files with 272 additions and 256 deletions

View file

@ -128,7 +128,7 @@ impl DocumentOrShadowRoot {
let x = *x as f32;
let y = *y as f32;
let point = &Point2D::new(x, y);
let viewport = self.window.window_size().initial_viewport;
let viewport = self.window.viewport_details().size;
if !has_browsing_context {
return None;
@ -176,7 +176,7 @@ impl DocumentOrShadowRoot {
let x = *x as f32;
let y = *y as f32;
let point = &Point2D::new(x, y);
let viewport = self.window.window_size().initial_viewport;
let viewport = self.window.viewport_details().size;
if !has_browsing_context {
return vec![];

View file

@ -4344,7 +4344,7 @@ impl Element {
if (in_quirks_mode && doc.GetBody().as_deref() == self.downcast::<HTMLElement>()) ||
(!in_quirks_mode && *self.root_element() == *self)
{
let viewport_dimensions = doc.window().window_size().initial_viewport.round().to_i32();
let viewport_dimensions = doc.window().viewport_details().size.round().to_i32();
rect.size = Size2D::<i32>::new(viewport_dimensions.width, viewport_dimensions.height);
}

View file

@ -6,8 +6,8 @@ use std::cell::Cell;
use base::id::{BrowsingContextId, PipelineId, WebViewId};
use bitflags::bitflags;
use constellation_traits::WindowSizeData;
use dom_struct::dom_struct;
use embedder_traits::ViewportDetails;
use html5ever::{LocalName, Prefix, local_name, namespace_url, ns};
use js::rust::HandleObject;
use net_traits::ReferrerPolicy;
@ -196,12 +196,12 @@ impl HTMLIFrameElement {
history_handling,
};
let window_size = WindowSizeData {
initial_viewport: window
.get_iframe_size_if_known(browsing_context_id, can_gc)
.unwrap_or_default(),
device_pixel_ratio: window.device_pixel_ratio(),
};
let viewport_details = window
.get_iframe_viewport_details_if_known(browsing_context_id, can_gc)
.unwrap_or_else(|| ViewportDetails {
hidpi_scale_factor: window.device_pixel_ratio(),
..Default::default()
});
match pipeline_type {
PipelineType::InitialAboutBlank => {
@ -212,7 +212,7 @@ impl HTMLIFrameElement {
load_data: load_data.clone(),
old_pipeline_id,
sandbox: sandboxed,
window_size,
viewport_details,
};
window
.as_global_scope()
@ -227,7 +227,7 @@ impl HTMLIFrameElement {
webview_id,
opener: None,
load_data,
window_size,
viewport_details,
};
self.pipeline_id.set(Some(new_pipeline_id));
@ -239,7 +239,7 @@ impl HTMLIFrameElement {
load_data,
old_pipeline_id,
sandbox: sandboxed,
window_size,
viewport_details,
};
window
.as_global_scope()

View file

@ -808,8 +808,8 @@ impl HTMLImageElement {
let device_pixel_ratio = self
.owner_document()
.window()
.window_size()
.device_pixel_ratio
.viewport_details()
.hidpi_scale_factor
.get() as f64;
for (index, image_source) in img_sources.iter().enumerate() {
let current_den = image_source.descriptor.density.unwrap();

View file

@ -444,7 +444,7 @@ impl IntersectionObserver {
// > (note that this processing step can only be reached if the document is fully active).
// TODO: viewport should consider native scrollbar if exist. Recheck Servo's scrollbar approach.
document.map(|document| {
let viewport = document.window().window_size().initial_viewport;
let viewport = document.window().viewport_details().size;
Rect::from_size(Size2D::new(
Au::from_f32_px(viewport.width),
Au::from_f32_px(viewport.height),

View file

@ -21,7 +21,7 @@ use base64::Engine;
#[cfg(feature = "bluetooth")]
use bluetooth_traits::BluetoothRequest;
use canvas_traits::webgl::WebGLChan;
use constellation_traits::{ScrollState, WindowSizeData, WindowSizeType};
use constellation_traits::{ScrollState, WindowSizeType};
use crossbeam_channel::{Sender, unbounded};
use cssparser::{Parser, ParserInput, SourceLocation};
use devtools_traits::{ScriptToDevtoolsControlMsg, TimelineMarker, TimelineMarkerType};
@ -29,7 +29,7 @@ use dom_struct::dom_struct;
use embedder_traits::user_content_manager::{UserContentManager, UserScript};
use embedder_traits::{
AlertResponse, ConfirmResponse, EmbedderMsg, PromptResponse, SimpleDialog, Theme,
WebDriverJSError, WebDriverJSResult,
ViewportDetails, WebDriverJSError, WebDriverJSResult,
};
use euclid::default::{Point2D as UntypedPoint2D, Rect as UntypedRect};
use euclid::{Point2D, Rect, Scale, Size2D, Vector2D};
@ -257,7 +257,7 @@ pub(crate) struct Window {
/// Most recent unhandled resize event, if any.
#[no_trace]
unhandled_resize_event: DomRefCell<Option<(WindowSizeData, WindowSizeType)>>,
unhandled_resize_event: DomRefCell<Option<(ViewportDetails, WindowSizeType)>>,
/// Platform theme.
#[no_trace]
@ -274,9 +274,9 @@ pub(crate) struct Window {
#[ignore_malloc_size_of = "Rc<T> is hard"]
js_runtime: DomRefCell<Option<Rc<Runtime>>>,
/// The current size of the window, in pixels.
/// The [`ViewportDetails`] of this [`Window`]'s frame.
#[no_trace]
window_size: Cell<WindowSizeData>,
viewport_details: Cell<ViewportDetails>,
/// A handle for communicating messages to the bluetooth thread.
#[no_trace]
@ -1300,9 +1300,9 @@ impl WindowMethods<crate::DomTypeHolder> for Window {
// https://drafts.csswg.org/cssom-view/#dom-window-innerheight
//TODO Include Scrollbar
fn InnerHeight(&self) -> i32 {
self.window_size
self.viewport_details
.get()
.initial_viewport
.size
.height
.to_i32()
.unwrap_or(0)
@ -1311,12 +1311,7 @@ impl WindowMethods<crate::DomTypeHolder> for Window {
// https://drafts.csswg.org/cssom-view/#dom-window-innerwidth
//TODO Include Scrollbar
fn InnerWidth(&self) -> i32 {
self.window_size
.get()
.initial_viewport
.width
.to_i32()
.unwrap_or(0)
self.viewport_details.get().size.width.to_i32().unwrap_or(0)
}
// https://drafts.csswg.org/cssom-view/#dom-window-scrollx
@ -1822,7 +1817,7 @@ impl Window {
// Step 5 & 6
// TODO: Remove scrollbar dimensions.
let viewport = self.window_size.get().initial_viewport;
let viewport = self.viewport_details.get().size;
// Step 7 & 8
// TODO: Consider `block-end` and `inline-end` overflow direction.
@ -1885,7 +1880,7 @@ impl Window {
}
pub(crate) fn device_pixel_ratio(&self) -> Scale<f32, CSSPixel, DevicePixel> {
self.window_size.get().device_pixel_ratio
self.viewport_details.get().hidpi_scale_factor
}
fn client_window(&self) -> (Size2D<u32, CSSPixel>, Point2D<i32, CSSPixel>) {
@ -1994,7 +1989,7 @@ impl Window {
document: document.upcast::<Node>().to_trusted_node_address(),
dirty_root,
stylesheets_changed,
window_size: self.window_size.get(),
viewport_details: self.viewport_details.get(),
origin: self.origin().immutable().clone(),
reflow_goal,
dom_count: document.dom_count(),
@ -2050,7 +2045,7 @@ impl Window {
let size_messages = self
.Document()
.iframes_mut()
.handle_new_iframe_sizes_after_layout(results.iframe_sizes, self.device_pixel_ratio());
.handle_new_iframe_sizes_after_layout(results.iframe_sizes);
if !size_messages.is_empty() {
self.send_to_constellation(ScriptMsg::IFrameSizes(size_messages));
}
@ -2364,11 +2359,11 @@ impl Window {
/// If the given |browsing_context_id| refers to an `<iframe>` that is an element
/// in this [`Window`] and that `<iframe>` has been laid out, return its size.
/// Otherwise, return `None`.
pub(crate) fn get_iframe_size_if_known(
pub(crate) fn get_iframe_viewport_details_if_known(
&self,
browsing_context_id: BrowsingContextId,
can_gc: CanGc,
) -> Option<Size2D<f32, CSSPixel>> {
) -> Option<ViewportDetails> {
// Reflow might fail, but do a best effort to return the right size.
self.layout_reflow(QueryMsg::InnerWindowDimensionsQuery, can_gc);
self.Document()
@ -2533,12 +2528,12 @@ impl Window {
};
}
pub(crate) fn set_window_size(&self, size: WindowSizeData) {
self.window_size.set(size);
pub(crate) fn set_viewport_details(&self, size: ViewportDetails) {
self.viewport_details.set(size);
}
pub(crate) fn window_size(&self) -> WindowSizeData {
self.window_size.get()
pub(crate) fn viewport_details(&self) -> ViewportDetails {
self.viewport_details.get()
}
/// Handle a theme change request, triggering a reflow is any actual change occured.
@ -2563,13 +2558,13 @@ impl Window {
self.dom_static.windowproxy_handler
}
pub(crate) fn add_resize_event(&self, event: WindowSizeData, event_type: WindowSizeType) {
pub(crate) fn add_resize_event(&self, event: ViewportDetails, event_type: WindowSizeType) {
// Whenever we receive a new resize event we forget about all the ones that came before
// it, to avoid unnecessary relayouts
*self.unhandled_resize_event.borrow_mut() = Some((event, event_type))
}
pub(crate) fn take_unhandled_resize_event(&self) -> Option<(WindowSizeData, WindowSizeType)> {
pub(crate) fn take_unhandled_resize_event(&self) -> Option<(ViewportDetails, WindowSizeType)> {
self.unhandled_resize_event.borrow_mut().take()
}
@ -2685,7 +2680,7 @@ impl Window {
return false;
};
if self.window_size() == new_size {
if self.viewport_details() == new_size {
return false;
}
@ -2693,9 +2688,9 @@ impl Window {
debug!(
"Resizing Window for pipeline {:?} from {:?} to {new_size:?}",
self.pipeline_id(),
self.window_size(),
self.viewport_details(),
);
self.set_window_size(new_size);
self.set_viewport_details(new_size);
// http://dev.w3.org/csswg/cssom-view/#resizing-viewports
if size_type == WindowSizeType::Resize {
@ -2823,7 +2818,7 @@ impl Window {
control_chan: IpcSender<ScriptThreadMessage>,
pipeline_id: PipelineId,
parent_info: Option<PipelineId>,
window_size: WindowSizeData,
viewport_details: ViewportDetails,
origin: MutableOrigin,
creator_url: ServoUrl,
navigation_start: CrossProcessInstant,
@ -2848,7 +2843,7 @@ impl Window {
let initial_viewport = f32_rect_to_au_rect(UntypedRect::new(
Point2D::zero(),
window_size.initial_viewport.to_untyped(),
viewport_details.size.to_untyped(),
));
let win = Box::new(Self {
@ -2894,7 +2889,7 @@ impl Window {
bluetooth_extra_permission_data: BluetoothExtraPermissionData::new(),
page_clip_rect: Cell::new(MaxRect::max_rect()),
unhandled_resize_event: Default::default(),
window_size: Cell::new(window_size),
viewport_details: Cell::new(viewport_details),
current_viewport: Cell::new(initial_viewport.to_untyped()),
layout_blocker: Cell::new(LayoutBlocker::WaitingForParse),
current_state: Cell::new(WindowState::Alive),

View file

@ -326,7 +326,7 @@ impl WindowProxy {
webview_id: response.new_webview_id,
opener: Some(self.browsing_context_id),
load_data,
window_size: window.window_size(),
viewport_details: window.viewport_details(),
};
ScriptThread::process_attach_layout(new_layout_info, document.origin().clone());
// TODO: if noopener is false, copy the sessionStorage storage area of the creator origin.