From 6be7612d165ab15b5df383fb378633b707a6f782 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20W=C3=BClker?= Date: Wed, 19 Mar 2025 09:33:11 +0100 Subject: [PATCH] script: Implement `Range::getClientRects` and `Range::getBoundingClientRect` (#35993) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add doc comments to boundary point Signed-off-by: Simon Wülker * Allow querying content box of text fragments Signed-off-by: Simon Wülker * Implement Range::getBoundingClientRect Signed-off-by: Simon Wülker * Update WPT expectations Signed-off-by: Simon Wülker --------- Signed-off-by: Simon Wülker --- .../fragment_tree/fragment_tree.rs | 1 + components/script/dom/abstractrange.rs | 3 + components/script/dom/range.rs | 67 ++++++++++++++++++- .../script_bindings/codegen/Bindings.conf | 2 +- .../script_bindings/webidls/Range.webidl | 5 +- .../meta/css/cssom-view/DOMRectList.html.ini | 3 - .../getBoundingClientRect-svg.html.ini | 9 --- .../meta/css/cssom-view/idlharness.html.ini | 12 ---- 8 files changed, 73 insertions(+), 29 deletions(-) delete mode 100644 tests/wpt/meta/css/cssom-view/DOMRectList.html.ini delete mode 100644 tests/wpt/meta/css/cssom-view/getBoundingClientRect-svg.html.ini diff --git a/components/layout_2020/fragment_tree/fragment_tree.rs b/components/layout_2020/fragment_tree/fragment_tree.rs index a620d0b1f9f..cc9c6abba18 100644 --- a/components/layout_2020/fragment_tree/fragment_tree.rs +++ b/components/layout_2020/fragment_tree/fragment_tree.rs @@ -155,6 +155,7 @@ impl FragmentTree { } }, Fragment::Positioning(fragment) => fragment.borrow().rect.cast_unit(), + Fragment::Text(text_fragment) => text_fragment.borrow().rect, _ => return None, }; diff --git a/components/script/dom/abstractrange.rs b/components/script/dom/abstractrange.rs index c31750cf7dc..e3f58e1e826 100644 --- a/components/script/dom/abstractrange.rs +++ b/components/script/dom/abstractrange.rs @@ -94,10 +94,13 @@ impl AbstractRangeMethods for AbstractRange { } } +/// #[derive(DenyPublicFields, JSTraceable, MallocSizeOf)] #[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)] pub(crate) struct BoundaryPoint { + /// node: MutDom, + /// offset: Cell, } diff --git a/components/script/dom/range.rs b/components/script/dom/range.rs index e25e9f870ba..7eb022635fb 100644 --- a/components/script/dom/range.rs +++ b/components/script/dom/range.rs @@ -4,6 +4,7 @@ use std::cell::UnsafeCell; use std::cmp::{Ordering, PartialOrd}; +use std::iter; use dom_struct::dom_struct; use js::jsapi::JSTracer; @@ -29,9 +30,11 @@ use crate::dom::bindings::weakref::{WeakRef, WeakRefVec}; use crate::dom::characterdata::CharacterData; use crate::dom::document::Document; use crate::dom::documentfragment::DocumentFragment; +use crate::dom::domrect::DOMRect; +use crate::dom::domrectlist::DOMRectList; use crate::dom::element::Element; use crate::dom::htmlscriptelement::HTMLScriptElement; -use crate::dom::node::{Node, ShadowIncluding, UnbindContext}; +use crate::dom::node::{Node, NodeTraits, ShadowIncluding, UnbindContext}; use crate::dom::selection::Selection; use crate::dom::text::Text; use crate::dom::window::Window; @@ -324,6 +327,23 @@ impl Range { pub(crate) fn collapsed(&self) -> bool { self.abstract_range().Collapsed() } + + fn client_rects( + &self, + can_gc: CanGc, + ) -> impl Iterator> { + // FIXME: For text nodes that are only partially selected, this should return the client + // rect of the selected part, not the whole text node. + let start = self.start_container(); + let end = self.end_container(); + let document = start.owner_doc(); + let end_clone = end.clone(); + start + .following_nodes(document.upcast::()) + .take_while(move |node| node != &end) + .chain(iter::once(end_clone)) + .flat_map(move |node| node.content_boxes(can_gc)) + } } impl RangeMethods for Range { @@ -1105,6 +1125,51 @@ impl RangeMethods for Range { // Step 5. Ok(fragment_node) } + + /// + fn GetClientRects(&self, can_gc: CanGc) -> DomRoot { + let start = self.start_container(); + let window = start.owner_window(); + + let client_rects = self + .client_rects(can_gc) + .map(|rect| { + DOMRect::new( + window.upcast(), + rect.origin.x.to_f64_px(), + rect.origin.y.to_f64_px(), + rect.size.width.to_f64_px(), + rect.size.height.to_f64_px(), + can_gc, + ) + }) + .collect(); + + DOMRectList::new(&window, client_rects, can_gc) + } + + /// + fn GetBoundingClientRect(&self, can_gc: CanGc) -> DomRoot { + let window = self.start_container().owner_window(); + + // Step 1. Let list be the result of invoking getClientRects() on the same range this method was invoked on. + let list = self.client_rects(can_gc); + + // Step 2. If list is empty return a DOMRect object whose x, y, width and height members are zero. + // Step 3. If all rectangles in list have zero width or height, return the first rectangle in list. + // Step 4. Otherwise, return a DOMRect object describing the smallest rectangle that includes all + // of the rectangles in list of which the height or width is not zero. + let bounding_rect = list.fold(euclid::Rect::zero(), |acc, rect| acc.union(&rect)); + + DOMRect::new( + window.upcast(), + bounding_rect.origin.x.to_f64_px(), + bounding_rect.origin.y.to_f64_px(), + bounding_rect.size.width.to_f64_px(), + bounding_rect.size.height.to_f64_px(), + can_gc, + ) + } } pub(crate) struct WeakRangeVec { diff --git a/components/script_bindings/codegen/Bindings.conf b/components/script_bindings/codegen/Bindings.conf index 8fb49121aed..a6adf7e45df 100644 --- a/components/script_bindings/codegen/Bindings.conf +++ b/components/script_bindings/codegen/Bindings.conf @@ -469,7 +469,7 @@ DOMInterfaces = { }, 'Range': { - 'canGc': ['CloneContents', 'CloneRange', 'CreateContextualFragment', 'ExtractContents', 'SurroundContents', 'InsertNode'], + 'canGc': ['CloneContents', 'CloneRange', 'CreateContextualFragment', 'ExtractContents', 'SurroundContents', 'InsertNode', 'GetClientRects', 'GetBoundingClientRect'], 'weakReferenceable': True, }, diff --git a/components/script_bindings/webidls/Range.webidl b/components/script_bindings/webidls/Range.webidl index 7ba168ddb05..b3980639b2c 100644 --- a/components/script_bindings/webidls/Range.webidl +++ b/components/script_bindings/webidls/Range.webidl @@ -73,7 +73,6 @@ partial interface Range { // http://dev.w3.org/csswg/cssom-view/#extensions-to-the-range-interface partial interface Range { - // sequence getClientRects(); - // [NewObject] - // DOMRect getBoundingClientRect(); + DOMRectList getClientRects(); + [NewObject] DOMRect getBoundingClientRect(); }; diff --git a/tests/wpt/meta/css/cssom-view/DOMRectList.html.ini b/tests/wpt/meta/css/cssom-view/DOMRectList.html.ini deleted file mode 100644 index 7994572555c..00000000000 --- a/tests/wpt/meta/css/cssom-view/DOMRectList.html.ini +++ /dev/null @@ -1,3 +0,0 @@ -[DOMRectList.html] - [Range getClientRects()] - expected: FAIL diff --git a/tests/wpt/meta/css/cssom-view/getBoundingClientRect-svg.html.ini b/tests/wpt/meta/css/cssom-view/getBoundingClientRect-svg.html.ini deleted file mode 100644 index 22b5ce5cd27..00000000000 --- a/tests/wpt/meta/css/cssom-view/getBoundingClientRect-svg.html.ini +++ /dev/null @@ -1,9 +0,0 @@ -[getBoundingClientRect-svg.html] - [Element.getBoundingClientRect() and Range.getBoudingClientRect() should match for an SVG ] - expected: FAIL - - [Element.getBoundingClientRect() and Range.getBoudingClientRect() should match for an SVG with a transform] - expected: FAIL - - [Element.getBoundingClientRect() and Range.getBoudingClientRect() should match for an SVG with a rotate] - expected: FAIL diff --git a/tests/wpt/meta/css/cssom-view/idlharness.html.ini b/tests/wpt/meta/css/cssom-view/idlharness.html.ini index 2ce851386f3..24583c6b095 100644 --- a/tests/wpt/meta/css/cssom-view/idlharness.html.ini +++ b/tests/wpt/meta/css/cssom-view/idlharness.html.ini @@ -26,9 +26,6 @@ [Element interface: document.createElement("div") must inherit property "getBoxQuads(BoxQuadOptions)" with the proper type] expected: FAIL - [Range interface: new Range() must inherit property "getBoundingClientRect()" with the proper type] - expected: FAIL - [Element interface: calling convertRectFromNode(DOMRectReadOnly, GeometryNode, ConvertCoordinateOptions) on document.createElementNS("x", "y") with too few arguments must throw TypeError] expected: FAIL @@ -62,9 +59,6 @@ [CaretPosition interface: attribute offset] expected: FAIL - [Range interface: operation getBoundingClientRect()] - expected: FAIL - [Partial dictionary MouseEventInit: member names are unique] expected: FAIL @@ -98,9 +92,6 @@ [CaretPosition interface: existence and properties of interface prototype object] expected: FAIL - [Range interface: operation getClientRects()] - expected: FAIL - [Element interface: document.createElementNS("x", "y") must inherit property "convertRectFromNode(DOMRectReadOnly, GeometryNode, ConvertCoordinateOptions)" with the proper type] expected: FAIL @@ -272,9 +263,6 @@ [Element interface: document.createElement("img") must inherit property "convertPointFromNode(DOMPointInit, GeometryNode, ConvertCoordinateOptions)" with the proper type] expected: FAIL - [Range interface: new Range() must inherit property "getClientRects()" with the proper type] - expected: FAIL - [Window interface: window must inherit property "screenLeft" with the proper type] expected: FAIL