mirror of
https://github.com/servo/servo.git
synced 2025-08-06 14:10:11 +01:00
layout: Consider transform for bounding box queries (#37871)
The recent changes that cached the Scroll Tree present an opportunity to calculate the queries that consider transform and scroll (dubbed as post composite queries) accurately. This PR propose a solution for this calculation by noting the lowest scroll tree nodes that would affect a fragment. To do this, each fragment would store a new attribute `spatial_tree_node` -- scroll tree node id that we could use for the query. This referencing is considered because the scroll tree node construction is managed by the fragment itself. Therefore it would ease the managing the possibly stale reference and future query cache invalidation considering the development of incremental layout. The bounding box query then could transform the bounding content rect of a fragment using the computed current transformation matrix. Fixes: https://github.com/servo/servo/issues/35768 Testing: Existing and new WPT --------- Signed-off-by: stevennovaryo <steven.novaryo@gmail.com> Signed-off-by: Jo Steven Novaryo <jo.steven.novaryo@huawei.com> Signed-off-by: Martin Robinson <mrobinson@igalia.com> Co-authored-by: Martin Robinson <mrobinson@igalia.com>
This commit is contained in:
parent
37ac4ffeb4
commit
900dd8d191
31 changed files with 304 additions and 146 deletions
|
@ -201,6 +201,7 @@ impl StackingContextTree {
|
|||
fn push_reference_frame(
|
||||
&mut self,
|
||||
origin: LayoutPoint,
|
||||
frame_origin_for_query: LayoutPoint,
|
||||
parent_scroll_node_id: &ScrollTreeNodeId,
|
||||
transform_style: wr::TransformStyle,
|
||||
transform: LayoutTransform,
|
||||
|
@ -210,6 +211,7 @@ impl StackingContextTree {
|
|||
Some(parent_scroll_node_id),
|
||||
SpatialTreeNodeInfo::ReferenceFrame(ReferenceFrameNodeInfo {
|
||||
origin,
|
||||
frame_origin_for_query,
|
||||
transform_style,
|
||||
transform,
|
||||
kind,
|
||||
|
@ -994,8 +996,11 @@ impl BoxFragment {
|
|||
return;
|
||||
}
|
||||
|
||||
let frame_origin_for_query = self.cumulative_border_box_rect().origin.to_webrender();
|
||||
|
||||
let new_spatial_id = stacking_context_tree.push_reference_frame(
|
||||
reference_frame_data.origin.to_webrender(),
|
||||
frame_origin_for_query,
|
||||
&containing_block.scroll_node_id,
|
||||
self.style.get_box().transform_style.to_webrender(),
|
||||
reference_frame_data.transform,
|
||||
|
@ -1212,6 +1217,11 @@ impl BoxFragment {
|
|||
add_fragment(StackingContextSection::Outline);
|
||||
}
|
||||
|
||||
// Spatial tree node that will affect the transform of the fragment. Note that the next frame,
|
||||
// scroll frame, does not affect the transform of the fragment but affect the transform of it
|
||||
// children.
|
||||
*self.spatial_tree_node.borrow_mut() = Some(new_scroll_node_id);
|
||||
|
||||
// We want to build the scroll frame after the background and border, because
|
||||
// they shouldn't scroll with the rest of the box content.
|
||||
if let Some(overflow_frame_data) = self.build_overflow_frame_if_necessary(
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
use app_units::{Au, MAX_AU, MIN_AU};
|
||||
use atomic_refcell::AtomicRefCell;
|
||||
use base::id::ScrollTreeNodeId;
|
||||
use base::print_tree::PrintTree;
|
||||
use malloc_size_of_derive::MallocSizeOf;
|
||||
use servo_arc::Arc as ServoArc;
|
||||
|
@ -109,6 +110,12 @@ pub(crate) struct BoxFragment {
|
|||
|
||||
/// Additional information for block-level boxes.
|
||||
pub block_level_layout_info: Option<Box<BlockLevelLayoutInfo>>,
|
||||
|
||||
/// The containing spatial tree node of this [`BoxFragment`]. This is assigned during
|
||||
/// `StackingContextTree` construction, so isn't available before that time. This is
|
||||
/// used to for determining final viewport size and position of this node and will
|
||||
/// also be used in the future for hit testing.
|
||||
pub spatial_tree_node: AtomicRefCell<Option<ScrollTreeNodeId>>,
|
||||
}
|
||||
|
||||
impl BoxFragment {
|
||||
|
@ -138,6 +145,7 @@ impl BoxFragment {
|
|||
background_mode: BackgroundMode::Normal,
|
||||
specific_layout_info,
|
||||
block_level_layout_info: None,
|
||||
spatial_tree_node: AtomicRefCell::default(),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -270,6 +278,10 @@ impl BoxFragment {
|
|||
rect.translate(self.cumulative_containing_block_rect.origin.to_vector())
|
||||
}
|
||||
|
||||
pub(crate) fn cumulative_border_box_rect(&self) -> PhysicalRect<Au> {
|
||||
self.offset_by_containing_block(&self.border_rect())
|
||||
}
|
||||
|
||||
pub(crate) fn padding_rect(&self) -> PhysicalRect<Au> {
|
||||
self.content_rect.outer_rect(self.padding)
|
||||
}
|
||||
|
|
|
@ -205,8 +205,7 @@ impl Fragment {
|
|||
pub(crate) fn cumulative_border_box_rect(&self) -> Option<PhysicalRect<Au>> {
|
||||
match self {
|
||||
Fragment::Box(fragment) | Fragment::Float(fragment) => {
|
||||
let fragment = fragment.borrow();
|
||||
Some(fragment.offset_by_containing_block(&fragment.border_rect()))
|
||||
Some(fragment.borrow().cumulative_border_box_rect())
|
||||
},
|
||||
Fragment::Positioning(fragment) => {
|
||||
let fragment = fragment.borrow();
|
||||
|
@ -325,6 +324,13 @@ impl Fragment {
|
|||
Fragment::IFrame(iframe_fragment) => iframe_fragment.borrow_mut().style = style.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn retrieve_box_fragment(&self) -> Option<&ArcRefCell<BoxFragment>> {
|
||||
match self {
|
||||
Fragment::Box(box_fragment) | Fragment::Float(box_fragment) => Some(box_fragment),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TextFragment {
|
||||
|
|
|
@ -253,16 +253,34 @@ impl Layout for LayoutThread {
|
|||
.remove_all_web_fonts_from_stylesheet(&stylesheet);
|
||||
}
|
||||
|
||||
/// Return the union of this node's content boxes in the coordinate space of the Document.
|
||||
/// to implement `getBoundingClientRect()`.
|
||||
///
|
||||
/// 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)]
|
||||
fn query_content_box(&self, node: TrustedNodeAddress) -> Option<UntypedRect<Au>> {
|
||||
let node = unsafe { ServoLayoutNode::new(&node) };
|
||||
process_content_box_request(node)
|
||||
let stacking_context_tree = self.stacking_context_tree.borrow();
|
||||
let stacking_context_tree = stacking_context_tree
|
||||
.as_ref()
|
||||
.expect("Should always have a StackingContextTree for geometry queries");
|
||||
process_content_box_request(stacking_context_tree, node)
|
||||
}
|
||||
|
||||
/// Get a `Vec` of bounding boxes of this node's `Fragement`s in the coordinate space of the
|
||||
/// Document. This is used to implement `getClientRects()`.
|
||||
///
|
||||
/// See <https://drafts.csswg.org/cssom-view/#dom-element-getclientrects>.
|
||||
#[servo_tracing::instrument(skip_all)]
|
||||
fn query_content_boxes(&self, node: TrustedNodeAddress) -> Vec<UntypedRect<Au>> {
|
||||
let node = unsafe { ServoLayoutNode::new(&node) };
|
||||
process_content_boxes_request(node)
|
||||
let stacking_context_tree = self.stacking_context_tree.borrow();
|
||||
let stacking_context_tree = stacking_context_tree
|
||||
.as_ref()
|
||||
.expect("Should always have a StackingContextTree for geometry queries");
|
||||
process_content_boxes_request(stacking_context_tree, node)
|
||||
}
|
||||
|
||||
#[servo_tracing::instrument(skip_all)]
|
||||
|
@ -1431,12 +1449,11 @@ impl ReflowPhases {
|
|||
QueryMsg::NodesFromPointQuery => {
|
||||
Self::StackingContextTreeConstruction | Self::DisplayListConstruction
|
||||
},
|
||||
QueryMsg::ResolvedStyleQuery | QueryMsg::ScrollingAreaOrOffsetQuery => {
|
||||
Self::StackingContextTreeConstruction
|
||||
},
|
||||
QueryMsg::ClientRectQuery |
|
||||
QueryMsg::ContentBox |
|
||||
QueryMsg::ContentBoxes |
|
||||
QueryMsg::ResolvedStyleQuery |
|
||||
QueryMsg::ScrollingAreaOrOffsetQuery => Self::StackingContextTreeConstruction,
|
||||
QueryMsg::ClientRectQuery |
|
||||
QueryMsg::ElementInnerOuterTextQuery |
|
||||
QueryMsg::InnerWindowDimensionsQuery |
|
||||
QueryMsg::OffsetParentQuery |
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
use std::rc::Rc;
|
||||
|
||||
use app_units::Au;
|
||||
use compositing_traits::display_list::ScrollTree;
|
||||
use euclid::default::{Point2D, Rect};
|
||||
use euclid::{SideOffsets2D, Size2D};
|
||||
use itertools::Itertools;
|
||||
|
@ -13,6 +14,7 @@ use layout_api::wrapper_traits::{LayoutNode, ThreadSafeLayoutElement, ThreadSafe
|
|||
use layout_api::{LayoutElementType, LayoutNodeType, OffsetParentResponse};
|
||||
use script::layout_dom::ServoLayoutNode;
|
||||
use servo_arc::Arc as ServoArc;
|
||||
use servo_geometry::{au_rect_to_f32_rect, f32_rect_to_au_rect};
|
||||
use servo_url::ServoUrl;
|
||||
use style::computed_values::display::T as Display;
|
||||
use style::computed_values::position::T as Position;
|
||||
|
@ -37,8 +39,10 @@ use style::values::specified::GenericGridTemplateComponent;
|
|||
use style::values::specified::box_::DisplayInside;
|
||||
use style::values::specified::text::TextTransformCase;
|
||||
use style_traits::{ParsingMode, ToCss};
|
||||
use webrender_api::units::LayoutTransform;
|
||||
|
||||
use crate::ArcRefCell;
|
||||
use crate::display_list::StackingContextTree;
|
||||
use crate::dom::NodeExt;
|
||||
use crate::flow::inline::construct::{TextTransformation, WhitespaceCollapse, capitalize_string};
|
||||
use crate::fragment_tree::{
|
||||
|
@ -46,7 +50,28 @@ use crate::fragment_tree::{
|
|||
};
|
||||
use crate::taffy::SpecificTaffyGridInfo;
|
||||
|
||||
pub fn process_content_box_request(node: ServoLayoutNode<'_>) -> Option<Rect<Au>> {
|
||||
/// Get a scroll node that would represents this [`ServoLayoutNode`]'s transform and
|
||||
/// calculate its cumlative transform from its root scroll node to the scroll node.
|
||||
fn root_transform_for_layout_node(
|
||||
scroll_tree: &ScrollTree,
|
||||
node: ServoLayoutNode<'_>,
|
||||
) -> Option<LayoutTransform> {
|
||||
let fragments = node.fragments_for_pseudo(None);
|
||||
let box_fragment = fragments
|
||||
.first()
|
||||
.and_then(Fragment::retrieve_box_fragment)?
|
||||
.borrow();
|
||||
let scroll_tree_node_id = box_fragment
|
||||
.spatial_tree_node
|
||||
.borrow()
|
||||
.expect("Should always have a scroll tree node when querying bounding box.");
|
||||
Some(scroll_tree.cumulative_node_transform(&scroll_tree_node_id))
|
||||
}
|
||||
|
||||
pub(crate) fn process_content_box_request(
|
||||
stacking_context_tree: &StackingContextTree,
|
||||
node: ServoLayoutNode<'_>,
|
||||
) -> Option<Rect<Au>> {
|
||||
let rects: Vec<_> = node
|
||||
.fragments_for_pseudo(None)
|
||||
.iter()
|
||||
|
@ -55,17 +80,37 @@ pub fn process_content_box_request(node: ServoLayoutNode<'_>) -> Option<Rect<Au>
|
|||
if rects.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(rects.iter().fold(Rect::zero(), |unioned_rect, rect| {
|
||||
let rect_union = rects.iter().fold(Rect::zero(), |unioned_rect, rect| {
|
||||
rect.to_untyped().union(&unioned_rect)
|
||||
}))
|
||||
});
|
||||
|
||||
let Some(transform) =
|
||||
root_transform_for_layout_node(&stacking_context_tree.compositor_info.scroll_tree, node)
|
||||
else {
|
||||
return Some(rect_union);
|
||||
};
|
||||
|
||||
Some(transform_au_rectangle(rect_union, transform))
|
||||
}
|
||||
|
||||
pub fn process_content_boxes_request(node: ServoLayoutNode<'_>) -> Vec<Rect<Au>> {
|
||||
node.fragments_for_pseudo(None)
|
||||
pub(crate) fn process_content_boxes_request(
|
||||
stacking_context_tree: &StackingContextTree,
|
||||
node: ServoLayoutNode<'_>,
|
||||
) -> Vec<Rect<Au>> {
|
||||
let fragments = node.fragments_for_pseudo(None);
|
||||
let content_boxes = fragments
|
||||
.iter()
|
||||
.filter_map(Fragment::cumulative_border_box_rect)
|
||||
.map(|rect| rect.to_untyped())
|
||||
.map(|rect| rect.to_untyped());
|
||||
|
||||
let Some(transform) =
|
||||
root_transform_for_layout_node(&stacking_context_tree.compositor_info.scroll_tree, node)
|
||||
else {
|
||||
return content_boxes.collect();
|
||||
};
|
||||
|
||||
content_boxes
|
||||
.map(|rect| transform_au_rectangle(rect, transform))
|
||||
.collect()
|
||||
}
|
||||
|
||||
|
@ -1104,3 +1149,10 @@ where
|
|||
|
||||
Some(computed_values.clone_font())
|
||||
}
|
||||
|
||||
fn transform_au_rectangle(rect_to_transform: Rect<Au>, transform: LayoutTransform) -> Rect<Au> {
|
||||
transform
|
||||
.outer_transformed_rect(&au_rect_to_f32_rect(rect_to_transform).cast_unit())
|
||||
.map(|transformed_rect| f32_rect_to_au_rect(transformed_rect.to_untyped()))
|
||||
.unwrap_or(rect_to_transform)
|
||||
}
|
||||
|
|
|
@ -397,7 +397,7 @@ pub const TEST_WEBVIEW_ID: WebViewId = WebViewId(TEST_BROWSING_CONTEXT_ID);
|
|||
/// An id for a ScrollTreeNode in the ScrollTree. This contains both the index
|
||||
/// to the node in the tree's array of nodes as well as the corresponding SpatialId
|
||||
/// for the SpatialNode in the WebRender display list.
|
||||
#[derive(Clone, Copy, Debug, Default, Deserialize, PartialEq, Serialize)]
|
||||
#[derive(Clone, Copy, Debug, Default, Deserialize, MallocSizeOf, PartialEq, Serialize)]
|
||||
pub struct ScrollTreeNodeId {
|
||||
/// The index of this scroll tree node in the tree's array of nodes.
|
||||
pub index: usize,
|
||||
|
|
|
@ -10,7 +10,7 @@ use base::id::ScrollTreeNodeId;
|
|||
use base::print_tree::PrintTree;
|
||||
use bitflags::bitflags;
|
||||
use embedder_traits::{Cursor, ViewportDetails};
|
||||
use euclid::SideOffsets2D;
|
||||
use euclid::{SideOffsets2D, Transform3D};
|
||||
use malloc_size_of_derive::MallocSizeOf;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use style::values::specified::Overflow;
|
||||
|
@ -88,6 +88,8 @@ pub struct StickyNodeInfo {
|
|||
#[derive(Debug, Deserialize, Serialize)]
|
||||
pub struct ReferenceFrameNodeInfo {
|
||||
pub origin: LayoutPoint,
|
||||
/// Origin of this frame relative to the document for bounding box queries.
|
||||
pub frame_origin_for_query: LayoutPoint,
|
||||
pub transform_style: TransformStyle,
|
||||
pub transform: LayoutTransform,
|
||||
pub kind: ReferenceFrameKind,
|
||||
|
@ -422,6 +424,53 @@ impl ScrollTree {
|
|||
_ => None,
|
||||
})
|
||||
}
|
||||
|
||||
/// Traverse a scroll node to its root to calculate the transform.
|
||||
///
|
||||
/// TODO(stevennovaryo): Add caching mechanism for this.
|
||||
pub fn cumulative_node_transform(&self, node_id: &ScrollTreeNodeId) -> LayoutTransform {
|
||||
let current_node = self.get_node(node_id);
|
||||
|
||||
let change_basis =
|
||||
|transform: &Transform3D<f32, LayoutPixel, LayoutPixel>, x: f32, y: f32, z: f32| {
|
||||
let pre_translation = Transform3D::translation(x, y, z);
|
||||
let post_translation = Transform3D::translation(-x, -y, -z);
|
||||
post_translation.then(transform).then(&pre_translation)
|
||||
};
|
||||
|
||||
// FIXME(stevennovaryo): Ideally we should optimize the computation of simpler
|
||||
// transformation like translate as it could be done
|
||||
// in smaller amount of operation compared to a normal
|
||||
// matrix multiplication.
|
||||
let node_transform = match ¤t_node.info {
|
||||
// To apply a transformation we need to make sure the rectangle's
|
||||
// coordinate space is the same as reference frame's coordinate space.
|
||||
// TODO(stevennovaryo): contrary to how Firefox are handling the coordinate space,
|
||||
// we are ignoring zoom in transforming the coordinate
|
||||
// space, and we might need to consider zoom here if it was
|
||||
// implemented completely.
|
||||
SpatialTreeNodeInfo::ReferenceFrame(info) => change_basis(
|
||||
&info.transform,
|
||||
info.frame_origin_for_query.x,
|
||||
info.frame_origin_for_query.y,
|
||||
0.0,
|
||||
),
|
||||
SpatialTreeNodeInfo::Scroll(info) => {
|
||||
Transform3D::translation(-info.offset.x, -info.offset.y, 0.0)
|
||||
},
|
||||
// TODO(stevennovaryo): Need to consider sticky frame accurately.
|
||||
SpatialTreeNodeInfo::Sticky(_) => Default::default(),
|
||||
};
|
||||
|
||||
match current_node.parent {
|
||||
// If a node is not a root, accumulate the transforms.
|
||||
Some(parent_id) => {
|
||||
let ancestors_transform = self.cumulative_node_transform(&parent_id);
|
||||
node_transform.then(&ancestors_transform)
|
||||
},
|
||||
None => node_transform,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// In order to pretty print the [ScrollTree] structure, we are converting
|
||||
|
@ -539,6 +588,7 @@ impl CompositorDisplayListInfo {
|
|||
None,
|
||||
SpatialTreeNodeInfo::ReferenceFrame(ReferenceFrameNodeInfo {
|
||||
origin: Default::default(),
|
||||
frame_origin_for_query: Default::default(),
|
||||
transform_style: TransformStyle::Flat,
|
||||
transform: LayoutTransform::identity(),
|
||||
kind: ReferenceFrameKind::default(),
|
||||
|
|
7
tests/wpt/meta/MANIFEST.json
vendored
7
tests/wpt/meta/MANIFEST.json
vendored
|
@ -626509,6 +626509,13 @@
|
|||
{}
|
||||
]
|
||||
],
|
||||
"getBoundingClientRect-scroll.html": [
|
||||
"4bcfe5e3de7bbc052732c8737345e5ca8acb6304",
|
||||
[
|
||||
null,
|
||||
{}
|
||||
]
|
||||
],
|
||||
"getBoundingClientRect-shy.html": [
|
||||
"55349e93969af0d527da4bcb589804239164bafc",
|
||||
[
|
||||
|
|
|
@ -1,7 +1,4 @@
|
|||
[position-sticky-transforms-translate.html]
|
||||
[Translation transform can move sticky element past sticking point]
|
||||
expected: FAIL
|
||||
|
||||
[Stuck elements can still be moved via translations]
|
||||
expected: FAIL
|
||||
|
||||
|
|
|
@ -8,15 +8,6 @@
|
|||
[Intermediate DOM nodes cause rendering context to end (fixed)]
|
||||
expected: FAIL
|
||||
|
||||
[Perspective applies to direct DOM normal-flow children]
|
||||
expected: FAIL
|
||||
|
||||
[Perspective applies to direct DOM abs-pos children]
|
||||
expected: FAIL
|
||||
|
||||
[Perspective applies to direct DOM fixed-pos children]
|
||||
expected: FAIL
|
||||
|
||||
[Perspective does not apply to DOM normal-flow grandchildren]
|
||||
expected: FAIL
|
||||
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
[transform-getBoundingClientRect-001.html]
|
||||
[correct getBoundingClientRect() result within set of transforms]
|
||||
expected: FAIL
|
|
@ -1,3 +0,0 @@
|
|||
[GetBoundingRect.html]
|
||||
[getBoundingClientRect]
|
||||
expected: FAIL
|
|
@ -1,6 +1,3 @@
|
|||
[elementsFromPoint-iframes.html]
|
||||
[elementsFromPoint on the root document for points in iframe elements]
|
||||
expected: FAIL
|
||||
|
||||
[elementsFromPoint on inner documents]
|
||||
expected: FAIL
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
[elementsFromPoint-simple.html]
|
||||
[elementsFromPoint for each corner of a div with a 3d transform]
|
||||
expected: FAIL
|
|
@ -1,12 +1,3 @@
|
|||
[bounding-box.html]
|
||||
[First rAF.]
|
||||
expected: FAIL
|
||||
|
||||
[target.style.transform = 'translateY(195px)']
|
||||
expected: FAIL
|
||||
|
||||
[target.style.transform = 'translateY(300px)']
|
||||
expected: FAIL
|
||||
|
||||
[target.style.zoom = 2]
|
||||
expected: FAIL
|
||||
|
|
|
@ -1,12 +0,0 @@
|
|||
[edge-inclusive-intersection.html]
|
||||
[First rAF.]
|
||||
expected: FAIL
|
||||
|
||||
[Set transform=translateY(200px) on target.]
|
||||
expected: FAIL
|
||||
|
||||
[Set transform=translateY(201px) on target.]
|
||||
expected: FAIL
|
||||
|
||||
[Set transform=translateY(185px) on target.]
|
||||
expected: FAIL
|
|
@ -1,6 +1,3 @@
|
|||
[isIntersecting-threshold.html]
|
||||
[Scrolled to half way through target element]
|
||||
expected: FAIL
|
||||
|
||||
[Scrolled to target element completely off screen]
|
||||
expected: FAIL
|
||||
|
|
|
@ -1,9 +0,0 @@
|
|||
[multiple-targets.html]
|
||||
[document.scrollingElement.scrollTop = 150]
|
||||
expected: FAIL
|
||||
|
||||
[document.scrollingElement.scrollTop = 10000]
|
||||
expected: FAIL
|
||||
|
||||
[document.scrollingElement.scrollTop = 0]
|
||||
expected: FAIL
|
|
@ -1,21 +0,0 @@
|
|||
[multiple-thresholds.html]
|
||||
[document.scrollingElement.scrollTop = 120]
|
||||
expected: FAIL
|
||||
|
||||
[document.scrollingElement.scrollTop = 160]
|
||||
expected: FAIL
|
||||
|
||||
[document.scrollingElement.scrollTop = 200]
|
||||
expected: FAIL
|
||||
|
||||
[document.scrollingElement.scrollTop = 240]
|
||||
expected: FAIL
|
||||
|
||||
[document.scrollingElement.scrollTop = window.innerHeight + 140]
|
||||
expected: FAIL
|
||||
|
||||
[document.scrollingElement.scrollTop = window.innerHeight + 160]
|
||||
expected: FAIL
|
||||
|
||||
[document.scrollingElement.scrollTop = window.innerHeight + 200]
|
||||
expected: FAIL
|
|
@ -1,3 +0,0 @@
|
|||
[observer-without-js-reference.html]
|
||||
[document.scrollingElement.scrollTop = 300]
|
||||
expected: FAIL
|
|
@ -5,8 +5,8 @@
|
|||
[root.scrollTop = 150]
|
||||
expected: FAIL
|
||||
|
||||
[root.removeChild(target).]
|
||||
expected: FAIL
|
||||
|
||||
[root.scrollTop = 150 after reinserting target.]
|
||||
expected: FAIL
|
||||
|
||||
[root.insertBefore(target, trailingSpace).]
|
||||
expected: FAIL
|
||||
|
|
|
@ -5,9 +5,6 @@
|
|||
[root.scrollTop = 50, putting target into root margin]
|
||||
expected: FAIL
|
||||
|
||||
[document.scrollingElement.scrollTop = 0.]
|
||||
expected: FAIL
|
||||
|
||||
[root.scrollTop = 0]
|
||||
expected: FAIL
|
||||
|
||||
|
|
|
@ -1,9 +0,0 @@
|
|||
[root-margin.html]
|
||||
[document.scrollingElement.scrollLeft = 100]
|
||||
expected: FAIL
|
||||
|
||||
[document.scrollingElement.scrollTop = document.documentElement.clientHeight + 200]
|
||||
expected: FAIL
|
||||
|
||||
[document.scrollingElement.scrollTop = document.documentElement.clientHeight + 300]
|
||||
expected: FAIL
|
|
@ -1,6 +0,0 @@
|
|||
[same-document-no-root.html]
|
||||
[document.scrollingElement.scrollTop = 300]
|
||||
expected: FAIL
|
||||
|
||||
[document.scrollingElement.scrollTop = 100]
|
||||
expected: FAIL
|
|
@ -5,9 +5,6 @@
|
|||
[root.scrollTop = 150 with root scrolled into view.]
|
||||
expected: FAIL
|
||||
|
||||
[document.scrollingElement.scrollTop = 0.]
|
||||
expected: FAIL
|
||||
|
||||
[root.scrollTop = 0]
|
||||
expected: FAIL
|
||||
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
[same-document-with-document-root.html]
|
||||
[document.scrollingElement.scrollTop = 300]
|
||||
expected: FAIL
|
|
@ -1,6 +0,0 @@
|
|||
[same-document-zero-size-target.html]
|
||||
[document.scrollingElement.scrollTop = 300]
|
||||
expected: FAIL
|
||||
|
||||
[document.scrollingElement.scrollTop = 100]
|
||||
expected: FAIL
|
|
@ -1,6 +0,0 @@
|
|||
[text-target.html]
|
||||
[document.scrollingElement.scrollTop = 300]
|
||||
expected: FAIL
|
||||
|
||||
[document.scrollingElement.scrollTop = 100]
|
||||
expected: FAIL
|
|
@ -1,6 +0,0 @@
|
|||
[unclipped-root.html]
|
||||
[First rAF.]
|
||||
expected: FAIL
|
||||
|
||||
[target.style.transform = 'translateY(195px)']
|
||||
expected: FAIL
|
|
@ -1,3 +0,0 @@
|
|||
[visibility-hidden.html]
|
||||
[document.scrollingElement.scrollTop = 300]
|
||||
expected: FAIL
|
130
tests/wpt/tests/css/cssom-view/getBoundingClientRect-scroll.html
vendored
Normal file
130
tests/wpt/tests/css/cssom-view/getBoundingClientRect-scroll.html
vendored
Normal file
|
@ -0,0 +1,130 @@
|
|||
<!DOCTYPE html>
|
||||
<title>getBoundingClientRect for a element inside scroll container</title>
|
||||
<link rel="author" title="Jo Steven Novaryo" href="mailto:steven.novaryo@gmail.com">
|
||||
<link rel="help" href="https://drafts.csswg.org/cssom-view/#dom-element-getboundingclientrect">
|
||||
<script src="/resources/testharness.js"></script>
|
||||
<script src="/resources/testharnessreport.js"></script>
|
||||
<html>
|
||||
<head>
|
||||
<style>
|
||||
body {
|
||||
margin: 0;
|
||||
}
|
||||
.scroll_container {
|
||||
width: 300px;
|
||||
height: 300px;
|
||||
overflow: scroll;
|
||||
scrollbar-width: none;
|
||||
}
|
||||
.dummy_overflowing_box {
|
||||
width: 5000px;
|
||||
height: 5000px;
|
||||
}
|
||||
.target_box {
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
background-color: green;
|
||||
}
|
||||
.display_none {
|
||||
display: none;
|
||||
}
|
||||
.display_content {
|
||||
display: content;
|
||||
}
|
||||
.position_absolute {
|
||||
position: absolute;
|
||||
}
|
||||
.position_fixed {
|
||||
position: fixed;
|
||||
}
|
||||
.absolute_containing_block {
|
||||
position: relative;
|
||||
}
|
||||
.fixed_containing_block {
|
||||
will-change: transform;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="scroll_container fixed_containing_block">
|
||||
<div class="scroll_container absolute_containing_block">
|
||||
<div id="target_scroll_container" class="scroll_container">
|
||||
<div id="target_fixed" class="target_box position_fixed"></div>
|
||||
<div id="target_absolute" class="target_box position_absolute"></div>
|
||||
<div id="target_none" class="target_box display_none"></div>
|
||||
<div id="target_content" class="target_box display_content">
|
||||
<div id="target_static" class="target_box"></div>
|
||||
</div>
|
||||
<div class="dummy_overflowing_box"></div>
|
||||
</div>
|
||||
<div class="dummy_overflowing_box"></div>
|
||||
</div>
|
||||
<div class="dummy_overflowing_box"></div>
|
||||
</div>
|
||||
<div id="log"></div>
|
||||
<script>
|
||||
setup(() => {
|
||||
var offset_left = 1;
|
||||
var offset_top = 2;
|
||||
for (scroll_container of document.getElementsByClassName('scroll_container')) {
|
||||
scroll_container.scrollTo(offset_left, offset_top)
|
||||
offset_left *= 4;
|
||||
offset_top *= 4;
|
||||
}
|
||||
});
|
||||
test(function() {
|
||||
var target = document.querySelector('#target_static');
|
||||
let staticRect = target.getBoundingClientRect();
|
||||
assert_equals(staticRect.left, -21, 'static positioned target left');
|
||||
assert_equals(staticRect.top, -42, 'static positioned target top');
|
||||
assert_equals(staticRect.width, 100, 'static positioned target width');
|
||||
assert_equals(staticRect.height, 100, 'static positioned target height');
|
||||
}, "getBoundingClientRect for element inside scroll container");
|
||||
|
||||
test(function() {
|
||||
var target = document.querySelector('#target_absolute');
|
||||
let absoluteRect = target.getBoundingClientRect();
|
||||
assert_equals(absoluteRect.left, -5, 'absolute positioned target left');
|
||||
assert_equals(absoluteRect.top, -10, 'absolute positioned target top');
|
||||
assert_equals(absoluteRect.width, 100, 'absolute positioned target width');
|
||||
assert_equals(absoluteRect.height, 100, 'absolute positioned target height');
|
||||
}, "getBoundingClientRect for absolute element inside scroll container");
|
||||
|
||||
test(function() {
|
||||
var target = document.querySelector('#target_fixed');
|
||||
let fixedRect = target.getBoundingClientRect();
|
||||
assert_equals(fixedRect.left, -1, 'fixed positioned target left');
|
||||
assert_equals(fixedRect.top, -2, 'fixed positioned target top');
|
||||
assert_equals(fixedRect.width, 100, 'fixed positioned target width');
|
||||
assert_equals(fixedRect.height, 100, 'fixed positioned target height');
|
||||
}, "getBoundingClientRect for fixed element inside scroll container");
|
||||
|
||||
test(function() {
|
||||
var target = document.querySelector('#target_scroll_container');
|
||||
let staticRect = target.getBoundingClientRect();
|
||||
assert_equals(staticRect.left, -5, 'scroll container target left');
|
||||
assert_equals(staticRect.top, -10, 'scroll container target top');
|
||||
assert_equals(staticRect.width, 300, 'scroll container target width');
|
||||
assert_equals(staticRect.height, 300, 'scroll container target height');
|
||||
}, "getBoundingClientRect for a scrolled scroll container");
|
||||
|
||||
test(function() {
|
||||
var target = document.querySelector('#target_none');
|
||||
let staticRect = target.getBoundingClientRect();
|
||||
assert_equals(staticRect.left, 0, 'scroll container target left');
|
||||
assert_equals(staticRect.top, 0, 'scroll container target top');
|
||||
assert_equals(staticRect.width, 0, 'scroll container target width');
|
||||
assert_equals(staticRect.height, 0, 'scroll container target height');
|
||||
}, "getBoundingClientRect for a scrolled display none box");
|
||||
|
||||
test(function() {
|
||||
var target = document.querySelector('#target_content');
|
||||
let staticRect = target.getBoundingClientRect();
|
||||
assert_equals(staticRect.left, -21, 'static positioned target left');
|
||||
assert_equals(staticRect.top, -42, 'static positioned target top');
|
||||
assert_equals(staticRect.width, 100, 'static positioned target width');
|
||||
assert_equals(staticRect.height, 100, 'static positioned target height');
|
||||
}, "getBoundingClientRect for a scrolled display content box");
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
Loading…
Add table
Add a link
Reference in a new issue