mirror of
https://github.com/servo/servo.git
synced 2025-08-12 17:05:33 +01:00
script/layout: Ensure a StackingContextTree before IntersectionObserver geometry queries (#38473)
IntersectionObserver needs to be able to query node geometry without forcing a layout. A previous layout could have run without needing a `StackingContextTree`. In that case the layout-less query should finish building the `StackingContextTree` before doing the query. Add a new type of layout API which requests that layout finishes building the StackingContextTree. This change also slightly simplifies and corrects the naming of `Element` APIs around client box queries. Testing: This should fix intermittent failures in WPT tests. Fixes: #38380. Fixes: #38390. Closes: #38400. Signed-off-by: Martin Robinson <mrobinson@igalia.com>
This commit is contained in:
parent
757dbc0eda
commit
44a11a7c6c
12 changed files with 78 additions and 59 deletions
|
@ -1056,7 +1056,7 @@ impl Document {
|
|||
// inside other scrollable containers. Ideally this should use an implementation of
|
||||
// `scrollIntoView` when that is available:
|
||||
// See https://github.com/servo/servo/issues/24059.
|
||||
let rect = element.upcast::<Node>().bounding_content_box_or_zero();
|
||||
let rect = element.upcast::<Node>().content_box().unwrap_or_default();
|
||||
|
||||
// In order to align with element edges, we snap to unscaled pixel boundaries, since
|
||||
// the paint thread currently does the same for drawing elements. This is important
|
||||
|
@ -1356,7 +1356,7 @@ impl Document {
|
|||
|
||||
// Notify the embedder to display an input method.
|
||||
if let Some(kind) = elem.input_method_type() {
|
||||
let rect = elem.upcast::<Node>().bounding_content_box_or_zero();
|
||||
let rect = elem.upcast::<Node>().content_box().unwrap_or_default();
|
||||
let rect = Rect::new(
|
||||
Point2D::new(rect.origin.x.to_px(), rect.origin.y.to_px()),
|
||||
Size2D::new(rect.size.width.to_px(), rect.size.height.to_px()),
|
||||
|
|
|
@ -1005,7 +1005,7 @@ impl Element {
|
|||
block: ScrollLogicalPosition,
|
||||
inline: ScrollLogicalPosition,
|
||||
) -> ScrollPosition {
|
||||
let target_bounding_box = self.upcast::<Node>().bounding_content_box_or_zero();
|
||||
let target_bounding_box = self.upcast::<Node>().content_box().unwrap_or_default();
|
||||
|
||||
let device_pixel_ratio = self
|
||||
.upcast::<Node>()
|
||||
|
@ -1071,7 +1071,7 @@ impl Element {
|
|||
} else {
|
||||
// Handle element-specific scrolling
|
||||
// Scrolling box bounds and current scroll position
|
||||
let scrolling_box = scrolling_node.bounding_content_box_or_zero();
|
||||
let scrolling_box = scrolling_node.content_box().unwrap_or_default();
|
||||
let scrolling_left = scrolling_box.origin.x.to_nearest_pixel(device_pixel_ratio) as f64;
|
||||
let scrolling_top = scrolling_box.origin.y.to_nearest_pixel(device_pixel_ratio) as f64;
|
||||
let scrolling_width = scrolling_box
|
||||
|
@ -1125,7 +1125,7 @@ impl Element {
|
|||
if matches!(position, Position::Relative | Position::Absolute) {
|
||||
// If this element establishes a positioning context,
|
||||
// Get its bounding box to calculate the offset
|
||||
let positioning_box = node.bounding_content_box_or_zero();
|
||||
let positioning_box = node.content_box().unwrap_or_default();
|
||||
let positioning_left = positioning_box
|
||||
.origin
|
||||
.x
|
||||
|
@ -3458,7 +3458,7 @@ impl ElementMethods<crate::DomTypeHolder> for Element {
|
|||
// https://drafts.csswg.org/cssom-view/#dom-element-getboundingclientrect
|
||||
fn GetBoundingClientRect(&self, can_gc: CanGc) -> DomRoot<DOMRect> {
|
||||
let win = self.owner_window();
|
||||
let rect = self.upcast::<Node>().bounding_content_box_or_zero();
|
||||
let rect = self.upcast::<Node>().content_box().unwrap_or_default();
|
||||
DOMRect::new(
|
||||
win.upcast(),
|
||||
rect.origin.x.to_f64_px(),
|
||||
|
|
|
@ -329,7 +329,7 @@ impl Activatable for HTMLAnchorElement {
|
|||
if let Some(element) = target.downcast::<Element>() {
|
||||
if target.is::<HTMLImageElement>() && element.has_attribute(&local_name!("ismap")) {
|
||||
let target_node = element.upcast::<Node>();
|
||||
let rect = target_node.bounding_content_box_or_zero();
|
||||
let rect = target_node.content_box().unwrap_or_default();
|
||||
ismap_suffix = Some(format!(
|
||||
"?{},{}",
|
||||
mouse_event.ClientX().to_f32().unwrap() - rect.origin.x.to_f32_px(),
|
||||
|
|
|
@ -1657,10 +1657,9 @@ impl HTMLImageElementMethods<crate::DomTypeHolder> for HTMLImageElement {
|
|||
// https://html.spec.whatwg.org/multipage/#dom-img-width
|
||||
fn Width(&self) -> u32 {
|
||||
let node = self.upcast::<Node>();
|
||||
match node.bounding_content_box() {
|
||||
Some(rect) => rect.size.width.to_px() as u32,
|
||||
None => self.NaturalWidth(),
|
||||
}
|
||||
node.content_box()
|
||||
.map(|rect| rect.size.width.to_px() as u32)
|
||||
.unwrap_or_else(|| self.NaturalWidth())
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/#dom-img-width
|
||||
|
@ -1671,10 +1670,9 @@ impl HTMLImageElementMethods<crate::DomTypeHolder> for HTMLImageElement {
|
|||
// https://html.spec.whatwg.org/multipage/#dom-img-height
|
||||
fn Height(&self) -> u32 {
|
||||
let node = self.upcast::<Node>();
|
||||
match node.bounding_content_box() {
|
||||
Some(rect) => rect.size.height.to_px() as u32,
|
||||
None => self.NaturalHeight(),
|
||||
}
|
||||
node.content_box()
|
||||
.map(|rect| rect.size.height.to_px() as u32)
|
||||
.unwrap_or_else(|| self.NaturalHeight())
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/#dom-img-height
|
||||
|
|
|
@ -2736,7 +2736,7 @@ impl HTMLInputElement {
|
|||
let (ipc_sender, ipc_receiver) =
|
||||
ipc::channel::<Option<RgbColor>>().expect("Failed to create IPC channel!");
|
||||
let document = self.owner_document();
|
||||
let rect = self.upcast::<Node>().bounding_content_box_or_zero();
|
||||
let rect = self.upcast::<Node>().content_box().unwrap_or_default();
|
||||
let rect = Rect::new(
|
||||
Point2D::new(rect.origin.x.to_px(), rect.origin.y.to_px()),
|
||||
Size2D::new(rect.size.width.to_px(), rect.size.height.to_px()),
|
||||
|
|
|
@ -377,7 +377,7 @@ impl HTMLSelectElement {
|
|||
})
|
||||
.collect();
|
||||
|
||||
let rect = self.upcast::<Node>().bounding_content_box_or_zero();
|
||||
let rect = self.upcast::<Node>().content_box().unwrap_or_default();
|
||||
let rect = Rect::new(
|
||||
Point2D::new(rect.origin.x.to_px(), rect.origin.y.to_px()),
|
||||
Size2D::new(rect.size.width.to_px(), rect.size.height.to_px()),
|
||||
|
|
|
@ -408,6 +408,7 @@ impl IntersectionObserver {
|
|||
///
|
||||
/// <https://w3c.github.io/IntersectionObserver/#intersectionobserver-root-intersection-rectangle>
|
||||
pub(crate) fn root_intersection_rectangle(&self, document: &Document) -> Option<Rect<Au>> {
|
||||
let window = document.window();
|
||||
let intersection_rectangle = match &self.root {
|
||||
// Handle if root is an element.
|
||||
Some(ElementOrDocument::Element(element)) => {
|
||||
|
@ -419,7 +420,7 @@ impl IntersectionObserver {
|
|||
|
||||
// > Otherwise, it’s the result of getting the bounding box for the intersection root.
|
||||
// TODO: replace this once getBoundingBox() is implemented correctly.
|
||||
DomRoot::upcast::<Node>(element.clone()).bounding_content_box_no_reflow()
|
||||
window.content_box_query_without_reflow(&DomRoot::upcast::<Node>(element.clone()))
|
||||
},
|
||||
// Handle if root is a Document, which includes implicit root and explicit Document root.
|
||||
_ => {
|
||||
|
@ -503,7 +504,9 @@ impl IntersectionObserver {
|
|||
// This is what we are currently using for getBoundingBox(). However, it is not correct,
|
||||
// mainly because it is not considering transform and scroll offset.
|
||||
// TODO: replace this once getBoundingBox() is implemented correctly.
|
||||
let maybe_target_rect = target.upcast::<Node>().bounding_content_box_no_reflow();
|
||||
let maybe_target_rect = document
|
||||
.window()
|
||||
.content_box_query_without_reflow(target.upcast::<Node>());
|
||||
|
||||
// Following the implementation of Gecko, we will skip further processing if these
|
||||
// information not available. This would also handle display none element.
|
||||
|
|
|
@ -942,20 +942,10 @@ impl Node {
|
|||
TrustedNodeAddress(self as *const Node as *const libc::c_void)
|
||||
}
|
||||
|
||||
/// Returns the rendered bounding content box if the element is rendered,
|
||||
/// and none otherwise.
|
||||
pub(crate) fn bounding_content_box(&self) -> Option<Rect<Au>> {
|
||||
pub(crate) fn content_box(&self) -> Option<Rect<Au>> {
|
||||
self.owner_window().content_box_query(self)
|
||||
}
|
||||
|
||||
pub(crate) fn bounding_content_box_or_zero(&self) -> Rect<Au> {
|
||||
self.bounding_content_box().unwrap_or_else(Rect::zero)
|
||||
}
|
||||
|
||||
pub(crate) fn bounding_content_box_no_reflow(&self) -> Option<Rect<Au>> {
|
||||
self.owner_window().content_box_query_unchecked(self)
|
||||
}
|
||||
|
||||
pub(crate) fn content_boxes(&self) -> Vec<Rect<Au>> {
|
||||
self.owner_window().content_boxes_query(self)
|
||||
}
|
||||
|
|
|
@ -2434,16 +2434,19 @@ impl Window {
|
|||
)
|
||||
}
|
||||
|
||||
// Query content box without considering any reflow
|
||||
pub(crate) fn content_box_query_unchecked(&self, node: &Node) -> Option<UntypedRect<Au>> {
|
||||
self.layout
|
||||
.borrow()
|
||||
.query_content_box(node.to_trusted_node_address())
|
||||
/// Do the same kind of query as `Self::content_box_query`, but do not force a reflow.
|
||||
/// This is used for things like `IntersectionObserver` which should observe the value
|
||||
/// from the most recent reflow, but do not need it to reflect the current state of
|
||||
/// the DOM / style.
|
||||
pub(crate) fn content_box_query_without_reflow(&self, node: &Node) -> Option<UntypedRect<Au>> {
|
||||
let layout = self.layout.borrow();
|
||||
layout.ensure_stacking_context_tree(self.viewport_details.get());
|
||||
layout.query_content_box(node.to_trusted_node_address())
|
||||
}
|
||||
|
||||
pub(crate) fn content_box_query(&self, node: &Node) -> Option<UntypedRect<Au>> {
|
||||
self.layout_reflow(QueryMsg::ContentBox);
|
||||
self.content_box_query_unchecked(node)
|
||||
self.content_box_query_without_reflow(node)
|
||||
}
|
||||
|
||||
pub(crate) fn content_boxes_query(&self, node: &Node) -> Vec<UntypedRect<Au>> {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue