mirror of
https://github.com/servo/servo.git
synced 2025-08-29 17:18:23 +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
|
@ -302,6 +302,14 @@ impl BoxFragment {
|
||||||
rect.translate(self.cumulative_containing_block_rect.origin.to_vector())
|
rect.translate(self.cumulative_containing_block_rect.origin.to_vector())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn cumulative_content_box_rect(&self) -> PhysicalRect<Au> {
|
||||||
|
self.offset_by_containing_block(&self.margin_rect())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn cumulative_padding_box_rect(&self) -> PhysicalRect<Au> {
|
||||||
|
self.offset_by_containing_block(&self.padding_rect())
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn cumulative_border_box_rect(&self) -> PhysicalRect<Au> {
|
pub(crate) fn cumulative_border_box_rect(&self) -> PhysicalRect<Au> {
|
||||||
self.offset_by_containing_block(&self.border_rect())
|
self.offset_by_containing_block(&self.border_rect())
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,7 @@ use base::id::PipelineId;
|
||||||
use base::print_tree::PrintTree;
|
use base::print_tree::PrintTree;
|
||||||
use euclid::{Point2D, Rect, Size2D, UnknownUnit};
|
use euclid::{Point2D, Rect, Size2D, UnknownUnit};
|
||||||
use fonts::{ByteIndex, FontMetrics, GlyphStore};
|
use fonts::{ByteIndex, FontMetrics, GlyphStore};
|
||||||
|
use layout_api::BoxAreaType;
|
||||||
use malloc_size_of_derive::MallocSizeOf;
|
use malloc_size_of_derive::MallocSizeOf;
|
||||||
use range::Range as ServoRange;
|
use range::Range as ServoRange;
|
||||||
use servo_arc::Arc as ServoArc;
|
use servo_arc::Arc as ServoArc;
|
||||||
|
@ -202,11 +203,13 @@ impl Fragment {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn cumulative_border_box_rect(&self) -> Option<PhysicalRect<Au>> {
|
pub(crate) fn cumulative_box_area_rect(&self, area: BoxAreaType) -> Option<PhysicalRect<Au>> {
|
||||||
match self {
|
match self {
|
||||||
Fragment::Box(fragment) | Fragment::Float(fragment) => {
|
Fragment::Box(fragment) | Fragment::Float(fragment) => Some(match area {
|
||||||
Some(fragment.borrow().cumulative_border_box_rect())
|
BoxAreaType::Content => fragment.borrow().cumulative_content_box_rect(),
|
||||||
},
|
BoxAreaType::Padding => fragment.borrow().cumulative_padding_box_rect(),
|
||||||
|
BoxAreaType::Border => fragment.borrow().cumulative_border_box_rect(),
|
||||||
|
}),
|
||||||
Fragment::Positioning(fragment) => {
|
Fragment::Positioning(fragment) => {
|
||||||
let fragment = fragment.borrow();
|
let fragment = fragment.borrow();
|
||||||
Some(fragment.offset_by_containing_block(&fragment.rect))
|
Some(fragment.offset_by_containing_block(&fragment.rect))
|
||||||
|
|
|
@ -28,9 +28,9 @@ use fonts_traits::StylesheetWebFontLoadFinishedCallback;
|
||||||
use fxhash::FxHashMap;
|
use fxhash::FxHashMap;
|
||||||
use layout_api::wrapper_traits::LayoutNode;
|
use layout_api::wrapper_traits::LayoutNode;
|
||||||
use layout_api::{
|
use layout_api::{
|
||||||
IFrameSizes, Layout, LayoutConfig, LayoutDamage, LayoutFactory, OffsetParentResponse,
|
BoxAreaType, IFrameSizes, Layout, LayoutConfig, LayoutDamage, LayoutFactory,
|
||||||
PropertyRegistration, QueryMsg, ReflowGoal, ReflowPhasesRun, ReflowRequest,
|
OffsetParentResponse, PropertyRegistration, QueryMsg, ReflowGoal, ReflowPhasesRun,
|
||||||
ReflowRequestRestyle, ReflowResult, RegisterPropertyError, TrustedNodeAddress,
|
ReflowRequest, ReflowRequestRestyle, ReflowResult, RegisterPropertyError, TrustedNodeAddress,
|
||||||
};
|
};
|
||||||
use log::{debug, error, warn};
|
use log::{debug, error, warn};
|
||||||
use malloc_size_of::{MallocConditionalSizeOf, MallocSizeOf, MallocSizeOfOps};
|
use malloc_size_of::{MallocConditionalSizeOf, MallocSizeOf, MallocSizeOfOps};
|
||||||
|
@ -90,8 +90,8 @@ use webrender_api::units::{DevicePixel, LayoutVector2D};
|
||||||
use crate::context::{CachedImageOrError, ImageResolver, LayoutContext};
|
use crate::context::{CachedImageOrError, ImageResolver, LayoutContext};
|
||||||
use crate::display_list::{DisplayListBuilder, HitTest, StackingContextTree};
|
use crate::display_list::{DisplayListBuilder, HitTest, StackingContextTree};
|
||||||
use crate::query::{
|
use crate::query::{
|
||||||
get_the_text_steps, process_client_rect_request, process_content_box_request,
|
get_the_text_steps, process_box_area_request, process_box_areas_request,
|
||||||
process_content_boxes_request, process_node_scroll_area_request, process_offset_parent_query,
|
process_client_rect_request, process_node_scroll_area_request, process_offset_parent_query,
|
||||||
process_resolved_font_style_query, process_resolved_style_request, process_text_index_request,
|
process_resolved_font_style_query, process_resolved_style_request, process_text_index_request,
|
||||||
};
|
};
|
||||||
use crate::traversal::{RecalcStyle, compute_damage_and_repair_style};
|
use crate::traversal::{RecalcStyle, compute_damage_and_repair_style};
|
||||||
|
@ -262,14 +262,17 @@ impl Layout for LayoutThread {
|
||||||
.remove_all_web_fonts_from_stylesheet(&stylesheet);
|
.remove_all_web_fonts_from_stylesheet(&stylesheet);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return the union of this node's content boxes in the coordinate space of the Document.
|
/// Return the union of this node's areas in the coordinate space of the Document. This is used
|
||||||
/// to implement `getBoundingClientRect()`.
|
/// to implement `getBoundingClientRect()` and support many other API where the such query is
|
||||||
|
/// required.
|
||||||
///
|
///
|
||||||
/// Part of <https://drafts.csswg.org/cssom-view-1/#element-get-the-bounding-box>
|
/// Part of <https://drafts.csswg.org/cssom-view-1/#element-get-the-bounding-box>.
|
||||||
/// TODO(stevennovaryo): Rename and parameterize the function, allowing padding area
|
|
||||||
/// query and possibly, query without consideration of transform.
|
|
||||||
#[servo_tracing::instrument(skip_all)]
|
#[servo_tracing::instrument(skip_all)]
|
||||||
fn query_content_box(&self, node: TrustedNodeAddress) -> Option<UntypedRect<Au>> {
|
fn query_box_area(
|
||||||
|
&self,
|
||||||
|
node: TrustedNodeAddress,
|
||||||
|
area: BoxAreaType,
|
||||||
|
) -> Option<UntypedRect<Au>> {
|
||||||
// If we have not built a fragment tree yet, there is no way we have layout information for
|
// If we have not built a fragment tree yet, there is no way we have layout information for
|
||||||
// this query, which can be run without forcing a layout (for IntersectionObserver).
|
// this query, which can be run without forcing a layout (for IntersectionObserver).
|
||||||
if self.fragment_tree.borrow().is_none() {
|
if self.fragment_tree.borrow().is_none() {
|
||||||
|
@ -280,16 +283,16 @@ impl Layout for LayoutThread {
|
||||||
let stacking_context_tree = self.stacking_context_tree.borrow();
|
let stacking_context_tree = self.stacking_context_tree.borrow();
|
||||||
let stacking_context_tree = stacking_context_tree
|
let stacking_context_tree = stacking_context_tree
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.expect("Should always have a StackingContextTree for content box queries");
|
.expect("Should always have a StackingContextTree for box area queries");
|
||||||
process_content_box_request(stacking_context_tree, node.to_threadsafe())
|
process_box_area_request(stacking_context_tree, node.to_threadsafe(), area)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get a `Vec` of bounding boxes of this node's `Fragement`s in the coordinate space of the
|
/// Get a `Vec` of bounding boxes of this node's `Fragment`s specific area in the coordinate space of
|
||||||
/// Document. This is used to implement `getClientRects()`.
|
/// the Document. This is used to implement `getClientRects()`.
|
||||||
///
|
///
|
||||||
/// See <https://drafts.csswg.org/cssom-view/#dom-element-getclientrects>.
|
/// See <https://drafts.csswg.org/cssom-view/#dom-element-getclientrects>.
|
||||||
#[servo_tracing::instrument(skip_all)]
|
#[servo_tracing::instrument(skip_all)]
|
||||||
fn query_content_boxes(&self, node: TrustedNodeAddress) -> Vec<UntypedRect<Au>> {
|
fn query_box_areas(&self, node: TrustedNodeAddress, area: BoxAreaType) -> Vec<UntypedRect<Au>> {
|
||||||
// If we have not built a fragment tree yet, there is no way we have layout information for
|
// If we have not built a fragment tree yet, there is no way we have layout information for
|
||||||
// this query, which can be run without forcing a layout (for IntersectionObserver).
|
// this query, which can be run without forcing a layout (for IntersectionObserver).
|
||||||
if self.fragment_tree.borrow().is_none() {
|
if self.fragment_tree.borrow().is_none() {
|
||||||
|
@ -300,8 +303,8 @@ impl Layout for LayoutThread {
|
||||||
let stacking_context_tree = self.stacking_context_tree.borrow();
|
let stacking_context_tree = self.stacking_context_tree.borrow();
|
||||||
let stacking_context_tree = stacking_context_tree
|
let stacking_context_tree = stacking_context_tree
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.expect("Should always have a StackingContextTree for content box queries");
|
.expect("Should always have a StackingContextTree for box area queries");
|
||||||
process_content_boxes_request(stacking_context_tree, node.to_threadsafe())
|
process_box_areas_request(stacking_context_tree, node.to_threadsafe(), area)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[servo_tracing::instrument(skip_all)]
|
#[servo_tracing::instrument(skip_all)]
|
||||||
|
@ -1597,8 +1600,8 @@ impl ReflowPhases {
|
||||||
QueryMsg::NodesFromPointQuery => {
|
QueryMsg::NodesFromPointQuery => {
|
||||||
Self::StackingContextTreeConstruction | Self::DisplayListConstruction
|
Self::StackingContextTreeConstruction | Self::DisplayListConstruction
|
||||||
},
|
},
|
||||||
QueryMsg::ContentBox |
|
QueryMsg::BoxArea |
|
||||||
QueryMsg::ContentBoxes |
|
QueryMsg::BoxAreas |
|
||||||
QueryMsg::ResolvedStyleQuery |
|
QueryMsg::ResolvedStyleQuery |
|
||||||
QueryMsg::ScrollingAreaOrOffsetQuery |
|
QueryMsg::ScrollingAreaOrOffsetQuery |
|
||||||
QueryMsg::ElementsFromPoint => Self::StackingContextTreeConstruction,
|
QueryMsg::ElementsFromPoint => Self::StackingContextTreeConstruction,
|
||||||
|
|
|
@ -11,7 +11,7 @@ use euclid::default::{Point2D, Rect};
|
||||||
use euclid::{SideOffsets2D, Size2D};
|
use euclid::{SideOffsets2D, Size2D};
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use layout_api::wrapper_traits::{LayoutNode, ThreadSafeLayoutElement, ThreadSafeLayoutNode};
|
use layout_api::wrapper_traits::{LayoutNode, ThreadSafeLayoutElement, ThreadSafeLayoutNode};
|
||||||
use layout_api::{LayoutElementType, LayoutNodeType, OffsetParentResponse};
|
use layout_api::{BoxAreaType, LayoutElementType, LayoutNodeType, OffsetParentResponse};
|
||||||
use script::layout_dom::{ServoLayoutNode, ServoThreadSafeLayoutNode};
|
use script::layout_dom::{ServoLayoutNode, ServoThreadSafeLayoutNode};
|
||||||
use servo_arc::Arc as ServoArc;
|
use servo_arc::Arc as ServoArc;
|
||||||
use servo_geometry::{FastLayoutTransform, au_rect_to_f32_rect, f32_rect_to_au_rect};
|
use servo_geometry::{FastLayoutTransform, au_rect_to_f32_rect, f32_rect_to_au_rect};
|
||||||
|
@ -67,14 +67,15 @@ fn root_transform_for_layout_node(
|
||||||
Some(scroll_tree.cumulative_node_to_root_transform(&scroll_tree_node_id))
|
Some(scroll_tree.cumulative_node_to_root_transform(&scroll_tree_node_id))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn process_content_box_request(
|
pub(crate) fn process_box_area_request(
|
||||||
stacking_context_tree: &StackingContextTree,
|
stacking_context_tree: &StackingContextTree,
|
||||||
node: ServoThreadSafeLayoutNode<'_>,
|
node: ServoThreadSafeLayoutNode<'_>,
|
||||||
|
area: BoxAreaType,
|
||||||
) -> Option<Rect<Au>> {
|
) -> Option<Rect<Au>> {
|
||||||
let rects: Vec<_> = node
|
let rects: Vec<_> = node
|
||||||
.fragments_for_pseudo(None)
|
.fragments_for_pseudo(None)
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(Fragment::cumulative_border_box_rect)
|
.filter_map(|node| node.cumulative_box_area_rect(area))
|
||||||
.collect();
|
.collect();
|
||||||
if rects.is_empty() {
|
if rects.is_empty() {
|
||||||
return None;
|
return None;
|
||||||
|
@ -92,23 +93,24 @@ pub(crate) fn process_content_box_request(
|
||||||
transform_au_rectangle(rect_union, transform)
|
transform_au_rectangle(rect_union, transform)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn process_content_boxes_request(
|
pub(crate) fn process_box_areas_request(
|
||||||
stacking_context_tree: &StackingContextTree,
|
stacking_context_tree: &StackingContextTree,
|
||||||
node: ServoThreadSafeLayoutNode<'_>,
|
node: ServoThreadSafeLayoutNode<'_>,
|
||||||
|
area: BoxAreaType,
|
||||||
) -> Vec<Rect<Au>> {
|
) -> Vec<Rect<Au>> {
|
||||||
let fragments = node.fragments_for_pseudo(None);
|
let fragments = node.fragments_for_pseudo(None);
|
||||||
let content_boxes = fragments
|
let box_areas = fragments
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(Fragment::cumulative_border_box_rect)
|
.filter_map(|node| node.cumulative_box_area_rect(area))
|
||||||
.map(|rect| rect.to_untyped());
|
.map(|rect| rect.to_untyped());
|
||||||
|
|
||||||
let Some(transform) =
|
let Some(transform) =
|
||||||
root_transform_for_layout_node(&stacking_context_tree.compositor_info.scroll_tree, node)
|
root_transform_for_layout_node(&stacking_context_tree.compositor_info.scroll_tree, node)
|
||||||
else {
|
else {
|
||||||
return content_boxes.collect();
|
return box_areas.collect();
|
||||||
};
|
};
|
||||||
|
|
||||||
content_boxes
|
box_areas
|
||||||
.filter_map(|rect| transform_au_rectangle(rect, transform))
|
.filter_map(|rect| transform_au_rectangle(rect, transform))
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
@ -575,7 +577,7 @@ pub fn process_offset_parent_query(node: ServoLayoutNode<'_>) -> Option<OffsetPa
|
||||||
.fragments_for_pseudo(None)
|
.fragments_for_pseudo(None)
|
||||||
.first()
|
.first()
|
||||||
.cloned()?;
|
.cloned()?;
|
||||||
let mut border_box = fragment.cumulative_border_box_rect()?;
|
let mut border_box = fragment.cumulative_box_area_rect(BoxAreaType::Border)?;
|
||||||
|
|
||||||
// 2. If the offsetParent of the element is null return the x-coordinate of the left
|
// 2. If the offsetParent of the element is null return the x-coordinate of the left
|
||||||
// border edge of the first CSS layout box associated with the element, relative to
|
// border edge of the first CSS layout box associated with the element, relative to
|
||||||
|
|
|
@ -1029,7 +1029,7 @@ impl Document {
|
||||||
// inside other scrollable containers. Ideally this should use an implementation of
|
// inside other scrollable containers. Ideally this should use an implementation of
|
||||||
// `scrollIntoView` when that is available:
|
// `scrollIntoView` when that is available:
|
||||||
// See https://github.com/servo/servo/issues/24059.
|
// 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
|
// 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
|
// 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.
|
// Notify the embedder to display an input method.
|
||||||
if let Some(kind) = elem.input_method_type() {
|
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(
|
let rect = Rect::new(
|
||||||
Point2D::new(rect.origin.x.to_px(), rect.origin.y.to_px()),
|
Point2D::new(rect.origin.x.to_px(), rect.origin.y.to_px()),
|
||||||
Size2D::new(rect.size.width.to_px(), rect.size.height.to_px()),
|
Size2D::new(rect.size.width.to_px(), rect.size.height.to_px()),
|
||||||
|
|
|
@ -1007,7 +1007,7 @@ impl Element {
|
||||||
block: ScrollLogicalPosition,
|
block: ScrollLogicalPosition,
|
||||||
inline: ScrollLogicalPosition,
|
inline: ScrollLogicalPosition,
|
||||||
) -> ScrollPosition {
|
) -> 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
|
let device_pixel_ratio = self
|
||||||
.upcast::<Node>()
|
.upcast::<Node>()
|
||||||
|
@ -1073,7 +1073,7 @@ impl Element {
|
||||||
} else {
|
} else {
|
||||||
// Handle element-specific scrolling
|
// Handle element-specific scrolling
|
||||||
// Scrolling box bounds and current scroll position
|
// 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_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_top = scrolling_box.origin.y.to_nearest_pixel(device_pixel_ratio) as f64;
|
||||||
let scrolling_width = scrolling_box
|
let scrolling_width = scrolling_box
|
||||||
|
@ -1127,7 +1127,7 @@ impl Element {
|
||||||
if matches!(position, Position::Relative | Position::Absolute) {
|
if matches!(position, Position::Relative | Position::Absolute) {
|
||||||
// If this element establishes a positioning context,
|
// If this element establishes a positioning context,
|
||||||
// Get its bounding box to calculate the offset
|
// 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
|
let positioning_left = positioning_box
|
||||||
.origin
|
.origin
|
||||||
.x
|
.x
|
||||||
|
@ -3449,7 +3449,7 @@ impl ElementMethods<crate::DomTypeHolder> for Element {
|
||||||
// https://drafts.csswg.org/cssom-view/#dom-element-getclientrects
|
// https://drafts.csswg.org/cssom-view/#dom-element-getclientrects
|
||||||
fn GetClientRects(&self, can_gc: CanGc) -> DomRoot<DOMRectList> {
|
fn GetClientRects(&self, can_gc: CanGc) -> DomRoot<DOMRectList> {
|
||||||
let win = self.owner_window();
|
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
|
let rects: Vec<DomRoot<DOMRect>> = raw_rects
|
||||||
.iter()
|
.iter()
|
||||||
.map(|rect| {
|
.map(|rect| {
|
||||||
|
@ -3469,7 +3469,7 @@ impl ElementMethods<crate::DomTypeHolder> for Element {
|
||||||
// https://drafts.csswg.org/cssom-view/#dom-element-getboundingclientrect
|
// https://drafts.csswg.org/cssom-view/#dom-element-getboundingclientrect
|
||||||
fn GetBoundingClientRect(&self, can_gc: CanGc) -> DomRoot<DOMRect> {
|
fn GetBoundingClientRect(&self, can_gc: CanGc) -> DomRoot<DOMRect> {
|
||||||
let win = self.owner_window();
|
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(
|
DOMRect::new(
|
||||||
win.upcast(),
|
win.upcast(),
|
||||||
rect.origin.x.to_f64_px(),
|
rect.origin.x.to_f64_px(),
|
||||||
|
|
|
@ -330,7 +330,7 @@ impl Activatable for HTMLAnchorElement {
|
||||||
if let Some(element) = target.downcast::<Element>() {
|
if let Some(element) = target.downcast::<Element>() {
|
||||||
if target.is::<HTMLImageElement>() && element.has_attribute(&local_name!("ismap")) {
|
if target.is::<HTMLImageElement>() && element.has_attribute(&local_name!("ismap")) {
|
||||||
let target_node = element.upcast::<Node>();
|
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!(
|
ismap_suffix = Some(format!(
|
||||||
"?{},{}",
|
"?{},{}",
|
||||||
mouse_event.ClientX().to_f32().unwrap() - rect.origin.x.to_f32_px(),
|
mouse_event.ClientX().to_f32().unwrap() - rect.origin.x.to_f32_px(),
|
||||||
|
|
|
@ -2789,7 +2789,7 @@ impl HTMLInputElement {
|
||||||
let (ipc_sender, ipc_receiver) =
|
let (ipc_sender, ipc_receiver) =
|
||||||
ipc::channel::<Option<RgbColor>>().expect("Failed to create IPC channel!");
|
ipc::channel::<Option<RgbColor>>().expect("Failed to create IPC channel!");
|
||||||
let document = self.owner_document();
|
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(
|
let rect = Rect::new(
|
||||||
Point2D::new(rect.origin.x.to_px(), rect.origin.y.to_px()),
|
Point2D::new(rect.origin.x.to_px(), rect.origin.y.to_px()),
|
||||||
Size2D::new(rect.size.width.to_px(), rect.size.height.to_px()),
|
Size2D::new(rect.size.width.to_px(), rect.size.height.to_px()),
|
||||||
|
|
|
@ -377,7 +377,7 @@ impl HTMLSelectElement {
|
||||||
})
|
})
|
||||||
.collect();
|
.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(
|
let rect = Rect::new(
|
||||||
Point2D::new(rect.origin.x.to_px(), rect.origin.y.to_px()),
|
Point2D::new(rect.origin.x.to_px(), rect.origin.y.to_px()),
|
||||||
Size2D::new(rect.size.width.to_px(), rect.size.height.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 dom_struct::dom_struct;
|
||||||
use euclid::default::{Rect, SideOffsets2D, Size2D};
|
use euclid::default::{Rect, SideOffsets2D, Size2D};
|
||||||
use js::rust::{HandleObject, MutableHandleValue};
|
use js::rust::{HandleObject, MutableHandleValue};
|
||||||
|
use layout_api::BoxAreaType;
|
||||||
use style::context::QuirksMode;
|
use style::context::QuirksMode;
|
||||||
use style::parser::{Parse, ParserContext};
|
use style::parser::{Parse, ParserContext};
|
||||||
use style::stylesheets::{CssRuleType, Origin};
|
use style::stylesheets::{CssRuleType, Origin};
|
||||||
|
use style::values::computed::Overflow;
|
||||||
use style::values::specified::intersection_observer::IntersectionObserverMargin;
|
use style::values::specified::intersection_observer::IntersectionObserverMargin;
|
||||||
use style_traits::{ParsingMode, ToCss};
|
use style_traits::{ParsingMode, ToCss};
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
@ -413,14 +415,22 @@ impl IntersectionObserver {
|
||||||
// Handle if root is an element.
|
// Handle if root is an element.
|
||||||
Some(ElementOrDocument::Element(element)) => {
|
Some(ElementOrDocument::Element(element)) => {
|
||||||
// TODO: recheck scrollbar approach and clip-path clipping from Chromium implementation.
|
// TODO: recheck scrollbar approach and clip-path clipping from Chromium implementation.
|
||||||
|
if element.style().is_some_and(|style| {
|
||||||
// > Otherwise, if the intersection root has a content clip,
|
style.clone_overflow_x() != Overflow::Visible ||
|
||||||
// > it’s the element’s padding area.
|
style.clone_overflow_y() != Overflow::Visible
|
||||||
// TODO(stevennovaryo): check for content clip
|
}) {
|
||||||
|
// > Otherwise, if the intersection root has a content clip, it’s the element’s padding area.
|
||||||
// > Otherwise, it’s the result of getting the bounding box for the intersection root.
|
window.box_area_query_without_reflow(
|
||||||
// TODO: replace this once getBoundingBox() is implemented correctly.
|
&DomRoot::upcast::<Node>(element.clone()),
|
||||||
window.content_box_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.
|
// Handle if root is a Document, which includes implicit root and explicit Document root.
|
||||||
_ => {
|
_ => {
|
||||||
|
@ -498,20 +508,15 @@ impl IntersectionObserver {
|
||||||
|
|
||||||
// Step 7
|
// Step 7
|
||||||
// > Set targetRect to the DOMRectReadOnly obtained by getting the bounding box for target.
|
// > 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
|
let maybe_target_rect = document
|
||||||
.window()
|
.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
|
// Following the implementation of Gecko, we will skip further processing if these
|
||||||
// information not available. This would also handle display none element.
|
// 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();
|
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
|
// TODO(stevennovaryo): we should probably also consider adding visibity check, ideally
|
||||||
// it would require new query from LayoutThread.
|
// it would require new query from LayoutThread.
|
||||||
|
|
|
@ -26,8 +26,8 @@ use js::jsapi::JSObject;
|
||||||
use js::rust::HandleObject;
|
use js::rust::HandleObject;
|
||||||
use keyboard_types::Modifiers;
|
use keyboard_types::Modifiers;
|
||||||
use layout_api::{
|
use layout_api::{
|
||||||
GenericLayoutData, HTMLCanvasData, HTMLMediaData, LayoutElementType, LayoutNodeType, QueryMsg,
|
BoxAreaType, GenericLayoutData, HTMLCanvasData, HTMLMediaData, LayoutElementType,
|
||||||
SVGElementData, StyleData, TrustedNodeAddress,
|
LayoutNodeType, QueryMsg, SVGElementData, StyleData, TrustedNodeAddress,
|
||||||
};
|
};
|
||||||
use libc::{self, c_void, uintptr_t};
|
use libc::{self, c_void, uintptr_t};
|
||||||
use malloc_size_of::{MallocSizeOf, MallocSizeOfOps};
|
use malloc_size_of::{MallocSizeOf, MallocSizeOfOps};
|
||||||
|
@ -943,11 +943,18 @@ impl Node {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn content_box(&self) -> Option<Rect<Au>> {
|
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>> {
|
pub(crate) fn border_box(&self) -> Option<Rect<Au>> {
|
||||||
self.owner_window().content_boxes_query(self)
|
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> {
|
pub(crate) fn client_rect(&self) -> Rect<i32> {
|
||||||
|
|
|
@ -340,7 +340,7 @@ impl Range {
|
||||||
.following_nodes(document.upcast::<Node>())
|
.following_nodes(document.upcast::<Node>())
|
||||||
.take_while(move |node| node != &end)
|
.take_while(move |node| node != &end)
|
||||||
.chain(iter::once(end_clone))
|
.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>
|
/// <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.
|
// but the spec will expand to cover all fragments.
|
||||||
target
|
target
|
||||||
.upcast::<Node>()
|
.upcast::<Node>()
|
||||||
.content_boxes()
|
.border_boxes()
|
||||||
.pop()
|
.pop()
|
||||||
.unwrap_or_else(Rect::zero)
|
.unwrap_or_else(Rect::zero)
|
||||||
},
|
},
|
||||||
|
|
|
@ -53,9 +53,9 @@ use js::rust::{
|
||||||
MutableHandleValue,
|
MutableHandleValue,
|
||||||
};
|
};
|
||||||
use layout_api::{
|
use layout_api::{
|
||||||
ElementsFromPointFlags, ElementsFromPointResult, FragmentType, Layout, PendingImage,
|
BoxAreaType, ElementsFromPointFlags, ElementsFromPointResult, FragmentType, Layout,
|
||||||
PendingImageState, PendingRasterizationImage, QueryMsg, ReflowGoal, ReflowPhasesRun,
|
PendingImage, PendingImageState, PendingRasterizationImage, QueryMsg, ReflowGoal,
|
||||||
ReflowRequest, ReflowRequestRestyle, RestyleReason, TrustedNodeAddress,
|
ReflowPhasesRun, ReflowRequest, ReflowRequestRestyle, RestyleReason, TrustedNodeAddress,
|
||||||
combine_id_with_fragment_type,
|
combine_id_with_fragment_type,
|
||||||
};
|
};
|
||||||
use malloc_size_of::MallocSizeOf;
|
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
|
/// 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
|
/// from the most recent reflow, but do not need it to reflect the current state of
|
||||||
/// the DOM / style.
|
/// 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();
|
let layout = self.layout.borrow();
|
||||||
layout.ensure_stacking_context_tree(self.viewport_details.get());
|
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>> {
|
pub(crate) fn box_area_query(&self, node: &Node, area: BoxAreaType) -> Option<UntypedRect<Au>> {
|
||||||
self.layout_reflow(QueryMsg::ContentBox);
|
self.layout_reflow(QueryMsg::BoxArea);
|
||||||
self.content_box_query_without_reflow(node)
|
self.box_area_query_without_reflow(node, area)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn content_boxes_query(&self, node: &Node) -> Vec<UntypedRect<Au>> {
|
pub(crate) fn box_areas_query(&self, node: &Node, area: BoxAreaType) -> Vec<UntypedRect<Au>> {
|
||||||
self.layout_reflow(QueryMsg::ContentBoxes);
|
self.layout_reflow(QueryMsg::BoxAreas);
|
||||||
self.layout
|
self.layout
|
||||||
.borrow()
|
.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> {
|
pub(crate) fn client_rect_query(&self, node: &Node) -> UntypedRect<i32> {
|
||||||
|
|
|
@ -291,8 +291,8 @@ pub trait Layout {
|
||||||
/// Returns true if this layout needs to produce a new display list for rendering updates.
|
/// Returns true if this layout needs to produce a new display list for rendering updates.
|
||||||
fn needs_new_display_list(&self) -> bool;
|
fn needs_new_display_list(&self) -> bool;
|
||||||
|
|
||||||
fn query_content_box(&self, node: TrustedNodeAddress) -> Option<Rect<Au>>;
|
fn query_box_area(&self, node: TrustedNodeAddress, area: BoxAreaType) -> Option<Rect<Au>>;
|
||||||
fn query_content_boxes(&self, node: TrustedNodeAddress) -> Vec<Rect<Au>>;
|
fn query_box_areas(&self, node: TrustedNodeAddress, area: BoxAreaType) -> Vec<Rect<Au>>;
|
||||||
fn query_client_rect(&self, node: TrustedNodeAddress) -> Rect<i32>;
|
fn query_client_rect(&self, node: TrustedNodeAddress) -> Rect<i32>;
|
||||||
fn query_element_inner_outer_text(&self, node: TrustedNodeAddress) -> String;
|
fn query_element_inner_outer_text(&self, node: TrustedNodeAddress) -> String;
|
||||||
fn query_offset_parent(&self, node: TrustedNodeAddress) -> OffsetParentResponse;
|
fn query_offset_parent(&self, node: TrustedNodeAddress) -> OffsetParentResponse;
|
||||||
|
@ -336,6 +336,16 @@ pub trait ScriptThreadFactory {
|
||||||
load_data: LoadData,
|
load_data: LoadData,
|
||||||
) -> JoinHandle<()>;
|
) -> JoinHandle<()>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Type of the area of CSS box for query.
|
||||||
|
/// See <https://www.w3.org/TR/css-box-3/#box-model>.
|
||||||
|
#[derive(Copy, Clone)]
|
||||||
|
pub enum BoxAreaType {
|
||||||
|
Content,
|
||||||
|
Padding,
|
||||||
|
Border,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Default)]
|
#[derive(Clone, Default)]
|
||||||
pub struct OffsetParentResponse {
|
pub struct OffsetParentResponse {
|
||||||
pub node_address: Option<UntrustedNodeAddress>,
|
pub node_address: Option<UntrustedNodeAddress>,
|
||||||
|
@ -344,9 +354,9 @@ pub struct OffsetParentResponse {
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
#[derive(Debug, PartialEq)]
|
||||||
pub enum QueryMsg {
|
pub enum QueryMsg {
|
||||||
|
BoxArea,
|
||||||
|
BoxAreas,
|
||||||
ClientRectQuery,
|
ClientRectQuery,
|
||||||
ContentBox,
|
|
||||||
ContentBoxes,
|
|
||||||
ElementInnerOuterTextQuery,
|
ElementInnerOuterTextQuery,
|
||||||
ElementsFromPoint,
|
ElementsFromPoint,
|
||||||
InnerWindowDimensionsQuery,
|
InnerWindowDimensionsQuery,
|
||||||
|
|
|
@ -2,8 +2,5 @@
|
||||||
[Not in containing block and not intersecting.]
|
[Not in containing block and not intersecting.]
|
||||||
expected: FAIL
|
expected: FAIL
|
||||||
|
|
||||||
[In containing block and intersecting.]
|
[Not in containing block and intersecting.]
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[In containing block and not intersecting.]
|
|
||||||
expected: FAIL
|
expected: FAIL
|
||||||
|
|
|
@ -1,6 +1,3 @@
|
||||||
[initial-observation-with-threshold.html]
|
[initial-observation-with-threshold.html]
|
||||||
[First rAF]
|
[First rAF]
|
||||||
expected: FAIL
|
expected: FAIL
|
||||||
|
|
||||||
[root.scrollTop = 20]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
|
@ -1,6 +0,0 @@
|
||||||
[isIntersecting-change-events.html]
|
|
||||||
[Set scrollTop=100 and check for one new notification.]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[Add 4th target.]
|
|
||||||
expected: FAIL
|
|
|
@ -1,10 +1,4 @@
|
||||||
[remove-element.html]
|
[remove-element.html]
|
||||||
[First rAF]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[root.scrollTop = 150]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[root.scrollTop = 150 after reinserting target.]
|
[root.scrollTop = 150 after reinserting target.]
|
||||||
expected: FAIL
|
expected: FAIL
|
||||||
|
|
||||||
|
|
|
@ -1,12 +0,0 @@
|
||||||
[root-margin-root-element.html]
|
|
||||||
[First rAF]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[root.scrollTop = 50, putting target into root margin]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[root.scrollTop = 0]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[root.scrollTop = 50 with root scrolled out of view.]
|
|
||||||
expected: FAIL
|
|
|
@ -1,12 +0,0 @@
|
||||||
[same-document-root.html]
|
|
||||||
[First rAF]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[root.scrollTop = 150 with root scrolled into view.]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[root.scrollTop = 0]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[root.scrollTop = 150 with root scrolled out of view.]
|
|
||||||
expected: FAIL
|
|
Loading…
Add table
Add a link
Reference in a new issue