Open Bug 816238 Opened 12 years ago Updated 2 years ago

Range.getClientRects() should include rects for all descendants in the range

Categories

(Core :: DOM: CSS Object Model, defect, P5)

defect

Tracking

()

UNCONFIRMED

People

(Reporter: dtrebbien, Unassigned)

Details

(Keywords: testcase)

Attachments

(2 files, 1 obsolete file)

Attached file Test case
Range.getBoundingClientRect() does not consider overflow content of children that is visible as a result of the parent element having overflow:visible. Horizontally and vertically overflown content is excluded.

Attached is a test case. In Firefox 17.0, the alert dialog shows the message "bcr.width = 14, bcr.height = 54", which I would expect for the bounding client rect of the inner box, not the bounding client rect of a Range selecting the inner box.

In Safari 6.0.2, Safari 5.0.5, and Chrome 23.0.1271.91, the alert message is "bcr.width = 195, bcr.height = 200", which seems correct based on measuring the content in a screenshot.
Attachment #686244 - Attachment mime type: text/plain → text/html
The CSSOM spec says it's "border boxes" (does not include overflow)
http://dvcs.w3.org/hg/csswg/raw-file/tip/cssom-view/Overview.html#extensions-to-the-range-interface
http://dvcs.w3.org/hg/csswg/raw-file/tip/cssom-view/Overview.html#the-getclientrects-and-getboundingclientrect-methods

"For each element selected by the range, whose parent is not selected by the range"
that would be the <div id="innerBox"> in your example.
"For each Text node selected or partially selected by the range"
there are no partially selected text nodes in your example.

AFAICT, Firefox is correct per the spec.  Opera does the same.
IE10 seem to match webkit.
Keywords: testcase
Whiteboard: INVALID?
The way I interpret the spec, a Range selecting an element also selects the child elements and Text nodes.  Therefore, a Range around the inner box also selects all of the Text node and BR element children of the inner box.

DOM Level 2 Traversal and Range mentions the example of a user making a selection in a text editor or word processor by pressing down the mouse at one point in a document, moving the mouse to another point, and releasing the mouse.  http://www.w3.org/TR/DOM-Level-2-Traversal-Range/ranges.html
If I use my mouse to try to select the inner box in Firefox, some of the text overflowing the border box is also selected.

As in the given example, because the overflown content is visible, I think that the bounding client rect of the selection should not be limited to the border box of #innerBox, but also include the overflown content.  However, if I were to change overflow to hidden on #innerBox, then I think that 14 x 54 is correct.  https://bugs.webkit.org/show_bug.cgi?id=103430
(In reply to Daniel Trebbien from comment #2)
> The way I interpret the spec, a Range selecting an element also selects the
> child elements and Text nodes. 

Yes...

> Therefore, a Range around the inner box also
> selects all of the Text node and BR element children of the inner box.

... but the spec for getClientRects explicitly says "whose parent is not
selected by the range" so, in your example, all the descendants of the inner
<div> should be excluded.

> As in the given example, because the overflown content is visible, I think
> that the bounding client rect of the selection should not be limited to the
> border box of #innerBox, but also include the overflown content.

That's not what CSSOM specifies.
I think the spec is very clear that getClientRects returns border boxes,
not a hypothetical box around what is visible.

In fact, webkit also returns border boxes, it just includes too many rectangles
in the getClientRects result.  It seems to ignore the "whose parent is not
selected" part of the spec.  I've filed that as
https://bugs.webkit.org/show_bug.cgi?id=103658
(I've attached a testcase there that demonstrates the webkit bug.)
> ... but the spec for getClientRects explicitly says "whose parent is not
> selected by the range" so, in your example, all the descendants of the inner
> <div> should be excluded.

Oh, sorry. You are right. My mistake.
Status: UNCONFIRMED → RESOLVED
Closed: 12 years ago
Resolution: --- → INVALID
I was thinking about creating a patch for the WebKit bug that you created, Bug 103658, as my first foray into WebKit development.  It seems like a good first bug to work on because the patch shouldn't be that involved.

The source code behind WebKit's Range.getBoundingClientRect() is located in WebCore/dom/Range.cpp.  Upon looking at the implementation of a helper, getBorderAndTextQuads(), it appears immediately obvious that to fix this bug, I would simply need to check whether each Text node has been seen before as well.  However, now I am wondering why the WebKit developers have deliberately handled Text nodes and Elements differently.

As you pointed out, Range.getClientRects() includes the border boxes of elements selected by the range whose parents are not also selected by the range.  With regard to Text nodes, I am now confused.

The spec says:

> For each Text node selected or partially selected by the range (including
> when the boundary-points are identical), include a ClientRect object (for
> the part that is selected, not the whole line box). The bounds of these
> ClientRect objects are computed using font metrics; thus, for horizontal
> writing, the vertical dimension of each box is determined by the font ascent
> and descent, and the horizontal dimension by the text advance width.

But it does not say anything about not including ClientRects for Text nodes that are children of elements selected by the Range.

What am I missing?
> But it does not say anything about not including ClientRects for Text nodes
> that are children of elements selected by the Range.

That's a good point.  I somehow got the idea that "whose parent is not
selected by the range" also affected the recursive traversal[1] that
Element.getClientRects() does.  But you're right, it shouldn't.

So after a more careful reading of the spec, I think we should indeed
include all text nodes, and all elements that are *fully* inside the
range, recursively.  Which is what you said in comment 2 I think.

It appears that none of IE, Chrome, Firefox or Opera implements that
though, so I'm not sure I've understood it correctly.

[1]
http://dvcs.w3.org/hg/csswg/raw-file/tip/cssom-view/Overview.html#widl-Element-getClientRects-ClientRectList
Status: RESOLVED → UNCONFIRMED
OS: Mac OS X → All
Hardware: x86_64 → All
Resolution: INVALID → ---
Whiteboard: INVALID?
Version: 17 Branch → unspecified
Summary: Range.getBoundingClientRect() does not include overflown content in an element with overflow:visible → Range.getClientRects() should include rects for all descendants in the range
Our implementation lives here (if you want to take a stab at it ;-) ):
http://mxr.mozilla.org/mozilla-central/source/content/base/src/nsRange.cpp#2586
http://mxr.mozilla.org/mozilla-central/source/content/base/src/nsRange.cpp#2612
Status: UNCONFIRMED → NEW
Ever confirmed: true
Attached file A <span> with 5 text nodes (obsolete) —
This is a better test actually.
Attachment #687476 - Attachment is obsolete: true
Hmm, it appears Element.getClientRects() isn't recursive at all, which makes me
confused as to what bullet 2 of Range.getClientRects() really means.  Maybe I was
right the first time that it only address text nodes in the range that are not
a descendant of elements covered by bullet 1.
Status: NEW → UNCONFIRMED
Ever confirmed: false
> Maybe I was right the first time that it only address text nodes in the range
> that are not a descendant of elements covered by bullet 1.

I am thinking that this is correct, but the spec appears to be ambiguous on the matter.

Would you re-open https://bugs.webkit.org/show_bug.cgi?id=103658 ?  I think it is worth keeping that WebKit bug open until this ambiguity is resolved, and I can't re-open it myself.
Yeah, the spec for Element.getClientRects() and Range.getClientRects() isn't
very good.  But since Element.getClientRects() is non-recursive in all UAs
I've tested it seems logical that the text in Range.getClientRects() about
"whose parent is not selected by the range" is intended to make that the
same, basically the border boxes for the top-most nodes inside the range
whether they are elements or text nodes.  That's what Fx and Opera does and
IE10 is roughly compatible.  Webkit seems to recurse for Range.getClientRects()
which gives different results for
   range.selectNode(elem); range.getClientRects();
compared to
  elem.getClientRects();
which intuitively feels wrong.

I'll file a new bug on Webkit since the old bug is just confusing now :-)
Anne, perhaps you can clarify what the spec says about Range.getClientRects() ?
We think it's ambiguous and hard to understand.
http://dvcs.w3.org/hg/csswg/raw-file/tip/cssom-view/Overview.html#extensions-to-the-range-interface
Just a note that one of the primary use cases I can see for this API is for JavaScript libraries finding out how big content is, or whether a coordinate falls in a rect for hit detection / overlap detection / etc.  If this API is non-recursive it implies JavaScript has to do recursive traversals instead, and it's hard to see what purpose the API fulfills at that point..

So if the spec is going to be clarified I would vote for WebKit's more useful behavior here.
https://bugzilla.mozilla.org/show_bug.cgi?id=1472046

Move all DOM bugs that haven’t been updated in more than 3 years and has no one currently assigned to P5.

If you have questions, please contact :mdaly.
Priority: -- → P5

I am able to reproduce this bug in Firefox Nightly 105 compared using attached test (A descendant <span> with 5 text nodes)

*** Safari 15.6 on macOS 12.5 ***

Range.getClientRects()[0] = 0,16,20,60
Range.getClientRects()[1] = 5,21,9.6015625,18
Range.getClientRects()[2] = 14.6015625,21,19.203125,18
Range.getClientRects()[3] = 33.8046875,21,28.8046875,18
Range.getClientRects()[4] = 62.609375,21,38.40625,18
Range.getClientRects()[5] = 101.015625,21,48.0078125,18
Range.getBoundingClientRect() = 0,16,149.0234375,60

DIV.getClientRects()[0] = 0,16,20,60
DIV.getBoundingClientRect() = 0,16,20,60

*** Chrome Canary 106 ***

Range.getClientRects()[0] = 0,16,20,60
Range.getClientRects()[1] = 5,21,9.6015625,18.5
Range.getClientRects()[2] = 14.6015625,21,19.203125,18.5
Range.getClientRects()[3] = 33.8046875,21,28.8046875,18.5
Range.getClientRects()[4] = 62.609375,21,38.40625,18.5
Range.getClientRects()[5] = 101.015625,21,48.0078125,18.5
Range.getBoundingClientRect() = 0,16,149.0234375,60

DIV.getClientRects()[0] = 0,16,20,60
DIV.getBoundingClientRect() = 0,16,20,60

*** Firefox Nightly 105 ***

Range.getClientRects()[0] = 0,16,20,60
Range.getBoundingClientRect() = 0,16,20,60

DIV.getClientRects()[0] = 0,16,20,60
DIV.getBoundingClientRect() = 0,16,20,60

I think aligning with other browsers here would be great. Thanks!

Severity: normal → S3
You need to log in before you can comment on or make changes to this bug.

Attachment

General

Created:
Updated:
Size: