Implement document.scrollingElement (#35994)

<!-- Please describe your changes on the following line: -->
This implements `document.scrollingElement`
(https://drafts.csswg.org/cssom-view/#dom-document-scrollingelement).

---
<!-- Thank you for contributing to Servo! Please replace each `[ ]` by
`[X]` when the step is complete, and replace `___` with appropriate
data: -->
- [x] `./mach build -d` does not report any errors
- [x] `./mach test-tidy` does not report any errors
- [x] These changes fix #35700
- [x] There are tests for these changes

<!-- Also, please make sure that "Allow edits from maintainers" checkbox
is checked, so that we can help you if you get stuck somewhere along the
way.-->

<!-- Pull requests that do not address these steps are welcome, but they
will require additional verification as part of the review process. -->

---------

Signed-off-by: JimmyDdotEXE <50691404+JimmyDdotEXE@users.noreply.github.com>
This commit is contained in:
Jimmy D. Buckets 2025-05-05 08:50:42 -04:00 committed by GitHub
parent 7ea5951e34
commit 1f6050f931
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
37 changed files with 195 additions and 272 deletions

View file

@ -6369,6 +6369,30 @@ impl DocumentMethods<crate::DomTypeHolder> for Document {
)
}
/// <https://drafts.csswg.org/cssom-view/#dom-document-scrollingelement>
fn GetScrollingElement(&self, can_gc: CanGc) -> Option<DomRoot<Element>> {
// Step 1. If the Document is in quirks mode, follow these steps:
if self.quirks_mode() == QuirksMode::Quirks {
// Step 1.1. If the body element exists,
if let Some(ref body) = self.GetBody() {
let e = body.upcast::<Element>();
// and it is not potentially scrollable, return the body element and abort these steps.
// For this purpose, a value of overflow:clip on the the body elements parent element
// must be treated as overflow:hidden.
if !e.is_potentially_scrollable_body_for_scrolling_element(can_gc) {
return Some(DomRoot::from_ref(e));
}
}
// Step 1.2. Return null and abort these steps.
return None;
}
// Step 2. If there is a root element, return the root element and abort these steps.
// Step 3. Return null.
self.GetDocumentElement()
}
// https://html.spec.whatwg.org/multipage/#dom-document-open
fn Open(
&self,

View file

@ -50,6 +50,7 @@ use style::selector_parser::{
use style::shared_lock::{Locked, SharedRwLock};
use style::stylesheets::layer_rule::LayerOrder;
use style::stylesheets::{CssRuleType, UrlExtraData};
use style::values::computed::Overflow;
use style::values::generics::NonNegative;
use style::values::generics::position::PreferredRatio;
use style::values::generics::ratio::Ratio;
@ -455,8 +456,25 @@ impl Element {
.is_some_and(|s| !s.get_box().clone_display().is_none())
}
// https://drafts.csswg.org/cssom-view/#potentially-scrollable
fn is_potentially_scrollable_body(&self, can_gc: CanGc) -> bool {
/// <https://drafts.csswg.org/cssom-view/#potentially-scrollable>
pub(crate) fn is_potentially_scrollable_body(&self, can_gc: CanGc) -> bool {
self.is_potentially_scrollable_body_shared_logic(false, can_gc)
}
/// <https://drafts.csswg.org/cssom-view/#potentially-scrollable>
pub(crate) fn is_potentially_scrollable_body_for_scrolling_element(
&self,
can_gc: CanGc,
) -> bool {
self.is_potentially_scrollable_body_shared_logic(true, can_gc)
}
/// <https://drafts.csswg.org/cssom-view/#potentially-scrollable>
fn is_potentially_scrollable_body_shared_logic(
&self,
treat_overflow_clip_on_parent_as_hidden: bool,
can_gc: CanGc,
) -> bool {
let node = self.upcast::<Node>();
debug_assert!(
node.owner_doc().GetBody().as_deref() == self.downcast::<HTMLElement>(),
@ -474,9 +492,21 @@ impl Element {
// overflow-y properties is neither visible nor clip."
if let Some(parent) = node.GetParentElement() {
if let Some(style) = parent.style(can_gc) {
if !style.get_box().clone_overflow_x().is_scrollable() &&
!style.get_box().clone_overflow_y().is_scrollable()
{
let mut overflow_x = style.get_box().clone_overflow_x();
let mut overflow_y = style.get_box().clone_overflow_y();
// This fulfills the 'treat parent element overflow:clip as overflow:hidden' stipulation
// from the document.scrollingElement specification.
if treat_overflow_clip_on_parent_as_hidden {
if overflow_x == Overflow::Clip {
overflow_x = Overflow::Hidden;
}
if overflow_y == Overflow::Clip {
overflow_y = Overflow::Hidden;
}
}
if !overflow_x.is_scrollable() && !overflow_y.is_scrollable() {
return false;
}
};

View file

@ -162,7 +162,7 @@ DOMInterfaces = {
'Document': {
'additionalTraits': ["crate::interfaces::DocumentHelpers"],
'canGc': ['Close', 'CreateElement', 'CreateElementNS', 'ImportNode', 'SetTitle', 'Write', 'Writeln', 'CreateEvent', 'CreateRange', 'Open', 'Open_', 'CreateComment', 'CreateAttribute', 'CreateAttributeNS', 'CreateDocumentFragment', 'CreateTextNode', 'CreateCDATASection', 'CreateProcessingInstruction', 'Prepend', 'Append', 'ReplaceChildren', 'SetBgColor', 'SetFgColor', 'Fonts', 'ElementFromPoint', 'ElementsFromPoint', 'ExitFullscreen', 'CreateExpression', 'CreateNSResolver', 'Evaluate', 'StyleSheets', 'Implementation', 'GetElementsByTagName', 'GetElementsByTagNameNS', 'GetElementsByClassName', 'AdoptNode', 'CreateNodeIterator', 'SetBody', 'GetElementsByName', 'Images', 'Embeds', 'Plugins', 'Links', 'Forms', 'Scripts', 'Anchors', 'Applets', 'Children', 'GetSelection'],
'canGc': ['Close', 'CreateElement', 'CreateElementNS', 'ImportNode', 'SetTitle', 'Write', 'Writeln', 'CreateEvent', 'CreateRange', 'Open', 'Open_', 'CreateComment', 'CreateAttribute', 'CreateAttributeNS', 'CreateDocumentFragment', 'CreateTextNode', 'CreateCDATASection', 'CreateProcessingInstruction', 'Prepend', 'Append', 'ReplaceChildren', 'SetBgColor', 'SetFgColor', 'Fonts', 'ElementFromPoint', 'ElementsFromPoint', 'GetScrollingElement', 'ExitFullscreen', 'CreateExpression', 'CreateNSResolver', 'Evaluate', 'StyleSheets', 'Implementation', 'GetElementsByTagName', 'GetElementsByTagNameNS', 'GetElementsByClassName', 'AdoptNode', 'CreateNodeIterator', 'SetBody', 'GetElementsByName', 'Images', 'Embeds', 'Plugins', 'Links', 'Forms', 'Scripts', 'Anchors', 'Applets', 'Children', 'GetSelection'],
},
'DissimilarOriginWindow': {

View file

@ -204,6 +204,16 @@ partial interface Document {
Document includes DocumentOrShadowRoot;
// https://drafts.csswg.org/cssom-view/#extensions-to-the-document-interface
partial interface Document {
// CaretPosition? caretPositionFromPoint(double x, double y, optional CaretPositionFromPointOptions options = {});
readonly attribute Element? scrollingElement;
};
// dictionary CaretPositionFromPointOptions {
// sequence<ShadowRoot> shadowRoots = [];
// };
// https://w3c.github.io/selection-api/#dom-document
partial interface Document {
Selection? getSelection();