mirror of
https://github.com/servo/servo.git
synced 2025-09-30 08:39:16 +01:00
layout: Parameterize content box query (#38935)
Parameterize and rename both `Layout::content_box_query` and `Layout::content_boxes_query` to support the query of rendered padding area and content area that accounts for transform and scroll. Both of these query have been misleading for a time since they are using border box, instead of content box of a Node. This PR adds a new type `layout_api::BoxAreaType` to be passed from `ScriptThread` to `LayoutThread` to query the respective area. It is then used for the query within `IntersectionObserver` to pass several WPTs. Testing: Existing WPT Coverage. --------- Signed-off-by: Jo Steven Novaryo <jo.steven.novaryo@huawei.com>
This commit is contained in:
parent
32aba08be7
commit
10ca3b6fde
21 changed files with 125 additions and 125 deletions
|
@ -1029,7 +1029,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>().content_box().unwrap_or_default();
|
||||
let rect = element.upcast::<Node>().border_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
|
||||
|
@ -1348,7 +1348,7 @@ impl Document {
|
|||
|
||||
// Notify the embedder to display an input method.
|
||||
if let Some(kind) = elem.input_method_type() {
|
||||
let rect = elem.upcast::<Node>().content_box().unwrap_or_default();
|
||||
let rect = elem.upcast::<Node>().border_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()),
|
||||
|
|
|
@ -1007,7 +1007,7 @@ impl Element {
|
|||
block: ScrollLogicalPosition,
|
||||
inline: ScrollLogicalPosition,
|
||||
) -> ScrollPosition {
|
||||
let target_bounding_box = self.upcast::<Node>().content_box().unwrap_or_default();
|
||||
let target_bounding_box = self.upcast::<Node>().border_box().unwrap_or_default();
|
||||
|
||||
let device_pixel_ratio = self
|
||||
.upcast::<Node>()
|
||||
|
@ -1073,7 +1073,7 @@ impl Element {
|
|||
} else {
|
||||
// Handle element-specific scrolling
|
||||
// Scrolling box bounds and current scroll position
|
||||
let scrolling_box = scrolling_node.content_box().unwrap_or_default();
|
||||
let scrolling_box = scrolling_node.border_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
|
||||
|
@ -1127,7 +1127,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.content_box().unwrap_or_default();
|
||||
let positioning_box = node.border_box().unwrap_or_default();
|
||||
let positioning_left = positioning_box
|
||||
.origin
|
||||
.x
|
||||
|
@ -3449,7 +3449,7 @@ impl ElementMethods<crate::DomTypeHolder> for Element {
|
|||
// https://drafts.csswg.org/cssom-view/#dom-element-getclientrects
|
||||
fn GetClientRects(&self, can_gc: CanGc) -> DomRoot<DOMRectList> {
|
||||
let win = self.owner_window();
|
||||
let raw_rects = self.upcast::<Node>().content_boxes();
|
||||
let raw_rects = self.upcast::<Node>().border_boxes();
|
||||
let rects: Vec<DomRoot<DOMRect>> = raw_rects
|
||||
.iter()
|
||||
.map(|rect| {
|
||||
|
@ -3469,7 +3469,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>().content_box().unwrap_or_default();
|
||||
let rect = self.upcast::<Node>().border_box().unwrap_or_default();
|
||||
DOMRect::new(
|
||||
win.upcast(),
|
||||
rect.origin.x.to_f64_px(),
|
||||
|
|
|
@ -330,7 +330,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.content_box().unwrap_or_default();
|
||||
let rect = target_node.border_box().unwrap_or_default();
|
||||
ismap_suffix = Some(format!(
|
||||
"?{},{}",
|
||||
mouse_event.ClientX().to_f32().unwrap() - rect.origin.x.to_f32_px(),
|
||||
|
|
|
@ -2789,7 +2789,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>().content_box().unwrap_or_default();
|
||||
let rect = self.upcast::<Node>().border_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>().content_box().unwrap_or_default();
|
||||
let rect = self.upcast::<Node>().border_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()),
|
||||
|
|
|
@ -12,9 +12,11 @@ use cssparser::{Parser, ParserInput};
|
|||
use dom_struct::dom_struct;
|
||||
use euclid::default::{Rect, SideOffsets2D, Size2D};
|
||||
use js::rust::{HandleObject, MutableHandleValue};
|
||||
use layout_api::BoxAreaType;
|
||||
use style::context::QuirksMode;
|
||||
use style::parser::{Parse, ParserContext};
|
||||
use style::stylesheets::{CssRuleType, Origin};
|
||||
use style::values::computed::Overflow;
|
||||
use style::values::specified::intersection_observer::IntersectionObserverMargin;
|
||||
use style_traits::{ParsingMode, ToCss};
|
||||
use url::Url;
|
||||
|
@ -413,14 +415,22 @@ impl IntersectionObserver {
|
|||
// Handle if root is an element.
|
||||
Some(ElementOrDocument::Element(element)) => {
|
||||
// TODO: recheck scrollbar approach and clip-path clipping from Chromium implementation.
|
||||
|
||||
// > Otherwise, if the intersection root has a content clip,
|
||||
// > it’s the element’s padding area.
|
||||
// TODO(stevennovaryo): check for content clip
|
||||
|
||||
// > Otherwise, it’s the result of getting the bounding box for the intersection root.
|
||||
// TODO: replace this once getBoundingBox() is implemented correctly.
|
||||
window.content_box_query_without_reflow(&DomRoot::upcast::<Node>(element.clone()))
|
||||
if element.style().is_some_and(|style| {
|
||||
style.clone_overflow_x() != Overflow::Visible ||
|
||||
style.clone_overflow_y() != Overflow::Visible
|
||||
}) {
|
||||
// > Otherwise, if the intersection root has a content clip, it’s the element’s padding area.
|
||||
window.box_area_query_without_reflow(
|
||||
&DomRoot::upcast::<Node>(element.clone()),
|
||||
BoxAreaType::Padding,
|
||||
)
|
||||
} else {
|
||||
// > Otherwise, it’s the result of getting the bounding box for the intersection root.
|
||||
window.box_area_query_without_reflow(
|
||||
&DomRoot::upcast::<Node>(element.clone()),
|
||||
BoxAreaType::Border,
|
||||
)
|
||||
}
|
||||
},
|
||||
// Handle if root is a Document, which includes implicit root and explicit Document root.
|
||||
_ => {
|
||||
|
@ -498,20 +508,15 @@ impl IntersectionObserver {
|
|||
|
||||
// Step 7
|
||||
// > Set targetRect to the DOMRectReadOnly obtained by getting the bounding box for target.
|
||||
// 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 = document
|
||||
.window()
|
||||
.content_box_query_without_reflow(target.upcast::<Node>());
|
||||
.box_area_query_without_reflow(target.upcast::<Node>(), BoxAreaType::Border);
|
||||
|
||||
// Following the implementation of Gecko, we will skip further processing if these
|
||||
// information not available. This would also handle display none element.
|
||||
if maybe_root_bounds.is_none() || maybe_target_rect.is_none() {
|
||||
let (Some(root_bounds), Some(target_rect)) = (maybe_root_bounds, maybe_target_rect) else {
|
||||
return IntersectionObservationOutput::default_skipped();
|
||||
}
|
||||
let root_bounds = maybe_root_bounds.unwrap();
|
||||
let target_rect = maybe_target_rect.unwrap();
|
||||
};
|
||||
|
||||
// TODO(stevennovaryo): we should probably also consider adding visibity check, ideally
|
||||
// it would require new query from LayoutThread.
|
||||
|
|
|
@ -26,8 +26,8 @@ use js::jsapi::JSObject;
|
|||
use js::rust::HandleObject;
|
||||
use keyboard_types::Modifiers;
|
||||
use layout_api::{
|
||||
GenericLayoutData, HTMLCanvasData, HTMLMediaData, LayoutElementType, LayoutNodeType, QueryMsg,
|
||||
SVGElementData, StyleData, TrustedNodeAddress,
|
||||
BoxAreaType, GenericLayoutData, HTMLCanvasData, HTMLMediaData, LayoutElementType,
|
||||
LayoutNodeType, QueryMsg, SVGElementData, StyleData, TrustedNodeAddress,
|
||||
};
|
||||
use libc::{self, c_void, uintptr_t};
|
||||
use malloc_size_of::{MallocSizeOf, MallocSizeOfOps};
|
||||
|
@ -943,11 +943,18 @@ impl Node {
|
|||
}
|
||||
|
||||
pub(crate) fn content_box(&self) -> Option<Rect<Au>> {
|
||||
self.owner_window().content_box_query(self)
|
||||
self.owner_window()
|
||||
.box_area_query(self, BoxAreaType::Content)
|
||||
}
|
||||
|
||||
pub(crate) fn content_boxes(&self) -> Vec<Rect<Au>> {
|
||||
self.owner_window().content_boxes_query(self)
|
||||
pub(crate) fn border_box(&self) -> Option<Rect<Au>> {
|
||||
self.owner_window()
|
||||
.box_area_query(self, BoxAreaType::Border)
|
||||
}
|
||||
|
||||
pub(crate) fn border_boxes(&self) -> Vec<Rect<Au>> {
|
||||
self.owner_window()
|
||||
.box_areas_query(self, BoxAreaType::Border)
|
||||
}
|
||||
|
||||
pub(crate) fn client_rect(&self) -> Rect<i32> {
|
||||
|
|
|
@ -340,7 +340,7 @@ impl Range {
|
|||
.following_nodes(document.upcast::<Node>())
|
||||
.take_while(move |node| node != &end)
|
||||
.chain(iter::once(end_clone))
|
||||
.flat_map(move |node| node.content_boxes())
|
||||
.flat_map(move |node| node.border_boxes())
|
||||
}
|
||||
|
||||
/// <https://dom.spec.whatwg.org/#concept-range-bp-set>
|
||||
|
|
|
@ -311,7 +311,7 @@ fn calculate_box_size(target: &Element, observed_box: &ResizeObserverBoxOptions)
|
|||
// but the spec will expand to cover all fragments.
|
||||
target
|
||||
.upcast::<Node>()
|
||||
.content_boxes()
|
||||
.border_boxes()
|
||||
.pop()
|
||||
.unwrap_or_else(Rect::zero)
|
||||
},
|
||||
|
|
|
@ -53,9 +53,9 @@ use js::rust::{
|
|||
MutableHandleValue,
|
||||
};
|
||||
use layout_api::{
|
||||
ElementsFromPointFlags, ElementsFromPointResult, FragmentType, Layout, PendingImage,
|
||||
PendingImageState, PendingRasterizationImage, QueryMsg, ReflowGoal, ReflowPhasesRun,
|
||||
ReflowRequest, ReflowRequestRestyle, RestyleReason, TrustedNodeAddress,
|
||||
BoxAreaType, ElementsFromPointFlags, ElementsFromPointResult, FragmentType, Layout,
|
||||
PendingImage, PendingImageState, PendingRasterizationImage, QueryMsg, ReflowGoal,
|
||||
ReflowPhasesRun, ReflowRequest, ReflowRequestRestyle, RestyleReason, TrustedNodeAddress,
|
||||
combine_id_with_fragment_type,
|
||||
};
|
||||
use malloc_size_of::MallocSizeOf;
|
||||
|
@ -2403,26 +2403,30 @@ impl Window {
|
|||
)
|
||||
}
|
||||
|
||||
/// Do the same kind of query as `Self::content_box_query`, but do not force a reflow.
|
||||
/// Do the same kind of query as `Self::box_area_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>> {
|
||||
pub(crate) fn box_area_query_without_reflow(
|
||||
&self,
|
||||
node: &Node,
|
||||
area: BoxAreaType,
|
||||
) -> 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())
|
||||
layout.query_box_area(node.to_trusted_node_address(), area)
|
||||
}
|
||||
|
||||
pub(crate) fn content_box_query(&self, node: &Node) -> Option<UntypedRect<Au>> {
|
||||
self.layout_reflow(QueryMsg::ContentBox);
|
||||
self.content_box_query_without_reflow(node)
|
||||
pub(crate) fn box_area_query(&self, node: &Node, area: BoxAreaType) -> Option<UntypedRect<Au>> {
|
||||
self.layout_reflow(QueryMsg::BoxArea);
|
||||
self.box_area_query_without_reflow(node, area)
|
||||
}
|
||||
|
||||
pub(crate) fn content_boxes_query(&self, node: &Node) -> Vec<UntypedRect<Au>> {
|
||||
self.layout_reflow(QueryMsg::ContentBoxes);
|
||||
pub(crate) fn box_areas_query(&self, node: &Node, area: BoxAreaType) -> Vec<UntypedRect<Au>> {
|
||||
self.layout_reflow(QueryMsg::BoxAreas);
|
||||
self.layout
|
||||
.borrow()
|
||||
.query_content_boxes(node.to_trusted_node_address())
|
||||
.query_box_areas(node.to_trusted_node_address(), area)
|
||||
}
|
||||
|
||||
pub(crate) fn client_rect_query(&self, node: &Node) -> UntypedRect<i32> {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue