mirror of
https://github.com/servo/servo.git
synced 2025-09-27 15:20:09 +01:00
script/layout: Implement HTMLElement.scrollParent
(#39110)
This new API allows getting the element which establishes an element's scroll container. This will be used to properly implement `scrollIntoView`. There is still work to do for this API and `offsetParent` to properly handle ancestors which are closed-shadow-hidden from the original query element. In addition, fix an issue where inline boxes were establishing scrolling containers (they shouldn't do that). Testing: There are tests for this change. Fixes: #39096. Signed-off-by: Martin Robinson <mrobinson@igalia.com> Co-authored-by: Oriol Brufau <obrufau@igalia.com>
This commit is contained in:
parent
5c7ea4bdee
commit
2c7866eb24
13 changed files with 173 additions and 49 deletions
|
@ -29,7 +29,8 @@ use layout_api::wrapper_traits::LayoutNode;
|
|||
use layout_api::{
|
||||
BoxAreaType, IFrameSizes, Layout, LayoutConfig, LayoutDamage, LayoutFactory,
|
||||
OffsetParentResponse, PropertyRegistration, QueryMsg, ReflowGoal, ReflowPhasesRun,
|
||||
ReflowRequest, ReflowRequestRestyle, ReflowResult, RegisterPropertyError, TrustedNodeAddress,
|
||||
ReflowRequest, ReflowRequestRestyle, ReflowResult, RegisterPropertyError, ScrollParentResponse,
|
||||
TrustedNodeAddress,
|
||||
};
|
||||
use log::{debug, error, warn};
|
||||
use malloc_size_of::{MallocConditionalSizeOf, MallocSizeOf, MallocSizeOfOps};
|
||||
|
@ -91,7 +92,8 @@ use crate::display_list::{DisplayListBuilder, HitTest, StackingContextTree};
|
|||
use crate::query::{
|
||||
get_the_text_steps, process_box_area_request, process_box_areas_request,
|
||||
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_scroll_parent_query,
|
||||
process_text_index_request,
|
||||
};
|
||||
use crate::traversal::{RecalcStyle, compute_damage_and_repair_style};
|
||||
use crate::{BoxTree, FragmentTree};
|
||||
|
@ -323,6 +325,12 @@ impl Layout for LayoutThread {
|
|||
process_offset_parent_query(node).unwrap_or_default()
|
||||
}
|
||||
|
||||
#[servo_tracing::instrument(skip_all)]
|
||||
fn query_scroll_parent(&self, node: TrustedNodeAddress) -> Option<ScrollParentResponse> {
|
||||
let node = unsafe { ServoLayoutNode::new(&node) };
|
||||
process_scroll_parent_query(node)
|
||||
}
|
||||
|
||||
#[servo_tracing::instrument(skip_all)]
|
||||
fn query_resolved_style(
|
||||
&self,
|
||||
|
@ -1608,6 +1616,7 @@ impl ReflowPhases {
|
|||
QueryMsg::ElementInnerOuterTextQuery |
|
||||
QueryMsg::InnerWindowDimensionsQuery |
|
||||
QueryMsg::OffsetParentQuery |
|
||||
QueryMsg::ScrollParentQuery |
|
||||
QueryMsg::ResolvedFontStyleQuery |
|
||||
QueryMsg::TextIndexQuery |
|
||||
QueryMsg::StyleQuery => Self::empty(),
|
||||
|
|
|
@ -11,7 +11,9 @@ use euclid::default::{Point2D, Rect};
|
|||
use euclid::{SideOffsets2D, Size2D};
|
||||
use itertools::Itertools;
|
||||
use layout_api::wrapper_traits::{LayoutNode, ThreadSafeLayoutElement, ThreadSafeLayoutNode};
|
||||
use layout_api::{BoxAreaType, LayoutElementType, LayoutNodeType, OffsetParentResponse};
|
||||
use layout_api::{
|
||||
BoxAreaType, LayoutElementType, LayoutNodeType, OffsetParentResponse, ScrollParentResponse,
|
||||
};
|
||||
use script::layout_dom::{ServoLayoutNode, ServoThreadSafeLayoutNode};
|
||||
use servo_arc::Arc as ServoArc;
|
||||
use servo_geometry::{FastLayoutTransform, au_rect_to_f32_rect, f32_rect_to_au_rect};
|
||||
|
@ -47,6 +49,7 @@ use crate::flow::inline::construct::{TextTransformation, WhitespaceCollapse, cap
|
|||
use crate::fragment_tree::{
|
||||
BoxFragment, Fragment, FragmentFlags, FragmentTree, SpecificLayoutInfo,
|
||||
};
|
||||
use crate::style_ext::ComputedValuesExt;
|
||||
use crate::taffy::SpecificTaffyGridInfo;
|
||||
|
||||
/// Get a scroll node that would represents this [`ServoLayoutNode`]'s transform and
|
||||
|
@ -638,6 +641,95 @@ pub fn process_offset_parent_query(node: ServoLayoutNode<'_>) -> Option<OffsetPa
|
|||
})
|
||||
}
|
||||
|
||||
/// This is an implementation of
|
||||
/// <https://drafts.csswg.org/cssom-view/#dom-htmlelement-scrollparent>.
|
||||
#[inline]
|
||||
pub(crate) fn process_scroll_parent_query(
|
||||
node: ServoLayoutNode<'_>,
|
||||
) -> Option<ScrollParentResponse> {
|
||||
let layout_data = node.to_threadsafe().inner_layout_data()?;
|
||||
|
||||
// 1. If any of the following holds true, return null and terminate this algorithm:
|
||||
// - The element does not have an associated box.
|
||||
let layout_box = layout_data.self_box.borrow();
|
||||
let layout_box = layout_box.as_ref()?;
|
||||
|
||||
let (mut current_position_value, flags) = layout_box
|
||||
.with_base_flat(|base| vec![(base.style.clone_position(), base.base_fragment_info.flags)])
|
||||
.first()
|
||||
.cloned()?;
|
||||
|
||||
// - The element is the root element.
|
||||
// - The element is the body element.
|
||||
// - The element’s computed value of the position property is fixed and no ancestor
|
||||
// establishes a fixed position containing block.
|
||||
if flags.intersects(
|
||||
FragmentFlags::IS_ROOT_ELEMENT | FragmentFlags::IS_BODY_ELEMENT_OF_HTML_ELEMENT_ROOT,
|
||||
) {
|
||||
return None;
|
||||
}
|
||||
|
||||
// 2. Let ancestor be the containing block of the element in the flat tree and repeat these substeps:
|
||||
// - If ancestor is the initial containing block, return the scrollingElement for the
|
||||
// element’s document if it is not closed-shadow-hidden from the element, otherwise
|
||||
// return null.
|
||||
// - If ancestor is not closed-shadow-hidden from the element, and is a scroll
|
||||
// container, terminate this algorithm and return ancestor.
|
||||
// - If the computed value of the position property of ancestor is fixed, and no
|
||||
// ancestor establishes a fixed position containing block, terminate this algorithm
|
||||
// and return null.
|
||||
// - Let ancestor be the containing block of ancestor in the flat tree.
|
||||
//
|
||||
// Notes: We don't follow the specification exactly below, but we follow the spirit.
|
||||
//
|
||||
// TODO: Handle the situation where the ancestor is "closed-shadow-hidden" from the element.
|
||||
let mut current_ancestor = node.as_element()?;
|
||||
while let Some(ancestor) = current_ancestor.traversal_parent() {
|
||||
current_ancestor = ancestor;
|
||||
|
||||
let Some(layout_data) = ancestor.as_node().to_threadsafe().inner_layout_data() else {
|
||||
continue;
|
||||
};
|
||||
let ancestor_layout_box = layout_data.self_box.borrow();
|
||||
let Some(ancestor_layout_box) = ancestor_layout_box.as_ref() else {
|
||||
continue;
|
||||
};
|
||||
|
||||
let (ancestor_style, ancestor_flags) = ancestor_layout_box
|
||||
.with_base_flat(|base| vec![(base.style.clone(), base.base_fragment_info.flags)])
|
||||
.first()
|
||||
.cloned()?;
|
||||
|
||||
let is_containing_block = match current_position_value {
|
||||
Position::Static | Position::Relative | Position::Sticky => {
|
||||
!ancestor_style.is_inline_box(ancestor_flags)
|
||||
},
|
||||
Position::Absolute => {
|
||||
ancestor_style.establishes_containing_block_for_absolute_descendants(ancestor_flags)
|
||||
},
|
||||
Position::Fixed => {
|
||||
ancestor_style.establishes_containing_block_for_all_descendants(ancestor_flags)
|
||||
},
|
||||
};
|
||||
if !is_containing_block {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ancestor_style.establishes_scroll_container(ancestor_flags) {
|
||||
return Some(ScrollParentResponse::Element(
|
||||
ancestor.as_node().opaque().into(),
|
||||
));
|
||||
}
|
||||
|
||||
current_position_value = ancestor_style.clone_position();
|
||||
}
|
||||
|
||||
match current_position_value {
|
||||
Position::Fixed => None,
|
||||
_ => Some(ScrollParentResponse::DocumentScrollingElement),
|
||||
}
|
||||
}
|
||||
|
||||
/// <https://html.spec.whatwg.org/multipage/#get-the-text-steps>
|
||||
pub fn get_the_text_steps(node: ServoLayoutNode<'_>) -> String {
|
||||
// Step 1: If element is not being rendered or if the user agent is a non-CSS user agent, then
|
||||
|
|
|
@ -598,6 +598,14 @@ impl ComputedValuesExt for ComputedValues {
|
|||
let mut overflow_x = style_box.overflow_x;
|
||||
let mut overflow_y = style_box.overflow_y;
|
||||
|
||||
// Inline boxes should never establish scroll containers.
|
||||
if self.is_inline_box(fragment_flags) {
|
||||
return AxesOverflow {
|
||||
x: Overflow::Visible,
|
||||
y: Overflow::Visible,
|
||||
};
|
||||
}
|
||||
|
||||
// https://www.w3.org/TR/css-overflow-3/#overflow-propagation
|
||||
// The element from which the value is propagated must then have a used overflow value of visible.
|
||||
if fragment_flags.contains(FragmentFlags::PROPAGATED_OVERFLOW_TO_VIEWPORT) {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue