mirror of
https://github.com/servo/servo.git
synced 2025-09-30 00:29:14 +01:00
script: Use HTMLElement.scrollParent
to implement Element.scrollIntoView
(#39144)
To find scrolling ancestors, we need to walk up the flat tree and only consider the elements that are in the chain of containing block ancestors of an element. `scrollParent` now does this so we can use it to properly implement `scrollIntoView`. Testing: There are WPT tests for this change. --------- Signed-off-by: Martin Robinson <mrobinson@igalia.com> Co-authored-by: Oriol Brufau <obrufau@igalia.com>
This commit is contained in:
parent
286bbe6cb1
commit
9f4f598f44
9 changed files with 141 additions and 71 deletions
|
@ -24,7 +24,7 @@ use html5ever::{LocalName, Namespace, Prefix, QualName, local_name, namespace_pr
|
|||
use js::jsapi::Heap;
|
||||
use js::jsval::JSVal;
|
||||
use js::rust::HandleObject;
|
||||
use layout_api::LayoutDamage;
|
||||
use layout_api::{LayoutDamage, ScrollContainerQueryType, ScrollContainerResponse};
|
||||
use net_traits::ReferrerPolicy;
|
||||
use net_traits::request::CorsSettings;
|
||||
use selectors::Element as SelectorsElement;
|
||||
|
@ -161,7 +161,7 @@ use crate::dom::mutationobserver::{Mutation, MutationObserver};
|
|||
use crate::dom::namednodemap::NamedNodeMap;
|
||||
use crate::dom::node::{
|
||||
BindContext, ChildrenMutation, CloneChildrenFlag, LayoutNodeHelpers, Node, NodeDamage,
|
||||
NodeFlags, NodeTraits, ShadowIncluding, UnbindContext,
|
||||
NodeFlags, NodeTraits, ShadowIncluding, UnbindContext, from_untrusted_node_address,
|
||||
};
|
||||
use crate::dom::nodelist::NodeList;
|
||||
use crate::dom::promise::Promise;
|
||||
|
@ -290,6 +290,20 @@ impl ScrollingBox {
|
|||
ScrollingBox::Viewport(document) => document.window().scroll_offset(),
|
||||
}
|
||||
}
|
||||
|
||||
fn parent(&self) -> Option<ScrollingBox> {
|
||||
match self {
|
||||
ScrollingBox::Element(element) => element.scrolling_box(),
|
||||
ScrollingBox::Viewport(_) => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn node(&self) -> &Node {
|
||||
match self {
|
||||
ScrollingBox::Element(element) => element.upcast(),
|
||||
ScrollingBox::Viewport(document) => document.upcast(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
|
@ -856,6 +870,21 @@ impl Element {
|
|||
.retain(|reg_obs| *reg_obs.observer != *observer)
|
||||
}
|
||||
|
||||
#[allow(unsafe_code)]
|
||||
fn scrolling_box(&self) -> Option<ScrollingBox> {
|
||||
self.owner_window()
|
||||
.scroll_container_query(self.upcast(), ScrollContainerQueryType::ForScrollIntoView)
|
||||
.and_then(|response| match response {
|
||||
ScrollContainerResponse::Viewport => {
|
||||
Some(ScrollingBox::Viewport(self.owner_document()))
|
||||
},
|
||||
ScrollContainerResponse::Element(parent_node_address) => {
|
||||
let node = unsafe { from_untrusted_node_address(parent_node_address) };
|
||||
Some(ScrollingBox::Element(DomRoot::downcast(node)?))
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
/// <https://drafts.csswg.org/cssom-view/#scroll-a-target-into-view>
|
||||
fn scroll_into_view_with_options(
|
||||
&self,
|
||||
|
@ -864,30 +893,11 @@ impl Element {
|
|||
inline: ScrollLogicalPosition,
|
||||
container: Option<&Element>,
|
||||
) {
|
||||
// Shadow-inclusive ancestors of this node, skipping the node itself.
|
||||
//
|
||||
// TODO: This should be the ancestors of the node in the flat tree.
|
||||
let ancestors = self
|
||||
.upcast::<Node>()
|
||||
.inclusive_ancestors(ShadowIncluding::Yes)
|
||||
.skip(1);
|
||||
|
||||
// Step 1: For each ancestor element or viewport that establishes a scrolling box `scrolling
|
||||
// box`, in order of innermost to outermost scrolling box, run these substeps:
|
||||
for node in ancestors {
|
||||
if !node.establishes_scrolling_box() {
|
||||
continue;
|
||||
}
|
||||
|
||||
// TODO: This should skip elements which are not containing blocks of the element we
|
||||
// are scrolling.
|
||||
let scrolling_box = if let Some(document) = node.downcast::<Document>() {
|
||||
ScrollingBox::Viewport(DomRoot::from_ref(document))
|
||||
} else if let Some(element) = node.downcast::<Element>() {
|
||||
ScrollingBox::Element(DomRoot::from_ref(element))
|
||||
} else {
|
||||
continue;
|
||||
};
|
||||
let mut parent_scrolling_box = self.scrolling_box();
|
||||
while let Some(scrolling_box) = parent_scrolling_box {
|
||||
parent_scrolling_box = scrolling_box.parent();
|
||||
|
||||
// Step 1.1: If the Document associated with `target` is not same origin with the
|
||||
// Document associated with the element or viewport associated with `scrolling box`,
|
||||
|
@ -906,14 +916,15 @@ impl Element {
|
|||
//
|
||||
// TODO: Handle smooth scrolling.
|
||||
if position != scrolling_box.scroll_position() {
|
||||
match scrolling_box {
|
||||
match &scrolling_box {
|
||||
// ↪ If `scrolling box` is associated with an element
|
||||
ScrollingBox::Element(element) => {
|
||||
// Perform a scroll of the element’s scrolling box to `position`,
|
||||
// with the `element` as the associated element and `behavior` as the
|
||||
// scroll behavior.
|
||||
let window = element.owner_window();
|
||||
window.scroll_an_element(&element, position.x, position.y, behavior);
|
||||
element
|
||||
.owner_window()
|
||||
.scroll_an_element(element, position.x, position.y, behavior);
|
||||
},
|
||||
// ↪ If `scrolling box` is associated with a viewport
|
||||
ScrollingBox::Viewport(document) => {
|
||||
|
@ -932,7 +943,9 @@ impl Element {
|
|||
// inclusive ancestor of `container`, abort the rest of these steps.
|
||||
if container.is_some_and(|container| {
|
||||
let container_node = container.upcast::<Node>();
|
||||
node.is_shadow_including_inclusive_ancestor_of(container_node)
|
||||
scrolling_box
|
||||
.node()
|
||||
.is_shadow_including_inclusive_ancestor_of(container_node)
|
||||
}) {
|
||||
break;
|
||||
}
|
||||
|
|
|
@ -9,7 +9,8 @@ use std::rc::Rc;
|
|||
use dom_struct::dom_struct;
|
||||
use html5ever::{LocalName, Prefix, local_name, ns};
|
||||
use js::rust::HandleObject;
|
||||
use layout_api::QueryMsg;
|
||||
use layout_api::{QueryMsg, ScrollContainerQueryType, ScrollContainerResponse};
|
||||
use script_bindings::codegen::GenericBindings::DocumentBinding::DocumentMethods;
|
||||
use style::attr::AttrValue;
|
||||
use stylo_dom::ElementState;
|
||||
|
||||
|
@ -48,7 +49,9 @@ use crate::dom::html::htmlhtmlelement::HTMLHtmlElement;
|
|||
use crate::dom::html::htmlinputelement::{HTMLInputElement, InputType};
|
||||
use crate::dom::html::htmllabelelement::HTMLLabelElement;
|
||||
use crate::dom::html::htmltextareaelement::HTMLTextAreaElement;
|
||||
use crate::dom::node::{BindContext, Node, NodeTraits, ShadowIncluding, UnbindContext};
|
||||
use crate::dom::node::{
|
||||
BindContext, Node, NodeTraits, ShadowIncluding, UnbindContext, from_untrusted_node_address,
|
||||
};
|
||||
use crate::dom::shadowroot::ShadowRoot;
|
||||
use crate::dom::text::Text;
|
||||
use crate::dom::virtualmethods::VirtualMethods;
|
||||
|
@ -445,8 +448,17 @@ impl HTMLElementMethods<crate::DomTypeHolder> for HTMLElement {
|
|||
}
|
||||
|
||||
/// <https://drafts.csswg.org/cssom-view/#dom-htmlelement-scrollparent>
|
||||
#[allow(unsafe_code)]
|
||||
fn GetScrollParent(&self) -> Option<DomRoot<Element>> {
|
||||
self.owner_window().scroll_parent_query(self.upcast())
|
||||
self.owner_window()
|
||||
.scroll_container_query(self.upcast(), ScrollContainerQueryType::ForScrollParent)
|
||||
.and_then(|response| match response {
|
||||
ScrollContainerResponse::Viewport => self.owner_document().GetScrollingElement(),
|
||||
ScrollContainerResponse::Element(parent_node_address) => {
|
||||
let node = unsafe { from_untrusted_node_address(parent_node_address) };
|
||||
DomRoot::downcast(node)
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
/// <https://drafts.csswg.org/cssom-view/#dom-htmlelement-offsetparent>
|
||||
|
|
|
@ -56,8 +56,8 @@ use js::rust::{
|
|||
use layout_api::{
|
||||
BoxAreaType, ElementsFromPointFlags, ElementsFromPointResult, FragmentType, Layout,
|
||||
PendingImage, PendingImageState, PendingRasterizationImage, QueryMsg, ReflowGoal,
|
||||
ReflowPhasesRun, ReflowRequest, ReflowRequestRestyle, RestyleReason, ScrollParentResponse,
|
||||
TrustedNodeAddress, combine_id_with_fragment_type,
|
||||
ReflowPhasesRun, ReflowRequest, ReflowRequestRestyle, RestyleReason, ScrollContainerQueryType,
|
||||
ScrollContainerResponse, TrustedNodeAddress, combine_id_with_fragment_type,
|
||||
};
|
||||
use malloc_size_of::MallocSizeOf;
|
||||
use media::WindowGLContext;
|
||||
|
@ -2599,21 +2599,15 @@ impl Window {
|
|||
(element, response.rect)
|
||||
}
|
||||
|
||||
#[allow(unsafe_code)]
|
||||
pub(crate) fn scroll_parent_query(&self, node: &Node) -> Option<DomRoot<Element>> {
|
||||
pub(crate) fn scroll_container_query(
|
||||
&self,
|
||||
node: &Node,
|
||||
query_type: ScrollContainerQueryType,
|
||||
) -> Option<ScrollContainerResponse> {
|
||||
self.layout_reflow(QueryMsg::ScrollParentQuery);
|
||||
self.layout
|
||||
.borrow()
|
||||
.query_scroll_parent(node.to_trusted_node_address())
|
||||
.and_then(|response| match response {
|
||||
ScrollParentResponse::DocumentScrollingElement => {
|
||||
self.Document().GetScrollingElement()
|
||||
},
|
||||
ScrollParentResponse::Element(parent_node_address) => {
|
||||
let node = unsafe { from_untrusted_node_address(parent_node_address) };
|
||||
DomRoot::downcast(node)
|
||||
},
|
||||
})
|
||||
.query_scroll_container(node.to_trusted_node_address(), query_type)
|
||||
}
|
||||
|
||||
pub(crate) fn text_index_query(
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue