Bug 25757 - VisibleSelection cannot represent all selection types
Summary: VisibleSelection cannot represent all selection types
Status: NEW
Alias: None
Product: WebKit
Classification: Unclassified
Component: HTML Editing (show other bugs)
Version: 528+ (Nightly build)
Hardware: PC Windows Vista
: P2 Normal
Assignee: Benjamin (Ben) Kalman
URL: http://willhirsch.co.uk/stuff/textare...
Keywords: HasReduction
: 41986 (view as bug list)
Depends on:
Blocks:
 
Reported: 2009-05-13 13:37 PDT by Will Hirsch
Modified: 2022-11-22 02:22 PST (History)
9 users (show)

See Also:


Attachments
demo (367 bytes, text/html)
2010-10-27 22:51 PDT, Ryosuke Niwa
no flags Details
RTL version of the demo (520 bytes, text/html;charset=<windows-1255>)
2011-02-16 09:49 PST, Yair Yogev
no flags Details
RTL version of the demo (621 bytes, text/html)
2011-02-16 10:00 PST, Yair Yogev
no flags Details

Note You need to log in before you can comment on or make changes to this bug.
Description Will Hirsch 2009-05-13 13:37:16 PDT
1. Go to the example URL.
2. Click somewhere at the far right of the middle wrapped line to place the caret after the space at the end of the middle wrapped line.
3. Optionally, move down one line with the down key to move the caret to the end of the third wrapped line.
4. Hold down shift, and press and hold/repeatedly press the up key.

You'll notice that you can't highlight all the way to the top. I think it's related to the algorithm which remembers your horizontal caret position when you move to a line which is so short the caret has to move to the left.
Comment 1 Will Hirsch 2010-10-27 11:55:30 PDT
Still reproduces in Safari 5.0.2 (7533.18.5) and Chrome 8.0.552.11 dev
Comment 2 Ryosuke Niwa 2010-10-27 22:51:23 PDT
Created attachment 72146 [details]
demo

Reproduction steps:
1. Open the attached file
2. Shift+Up Twice

Expected result:
The last two lines are selected.

Actual result:
Only the last line is selected.
Comment 3 Yair Yogev 2011-01-25 02:54:43 PST
i can reproduce the issue with this test case under Windows only if i expand the width of the text area so that there are only 3 lines

the one at Bug 41986 (which i am guessing is the same bug) reproduces the issue reliably though
Comment 4 Ryosuke Niwa 2011-01-26 21:22:55 PST
*** Bug 41986 has been marked as a duplicate of this bug. ***
Comment 5 Fabrizio 2011-01-27 13:04:03 PST
Feel free to assign to me as I've been looking into this.
Comment 6 Benjamin (Ben) Kalman 2011-01-30 23:59:33 PST
@Fabrizio you're looking into it?  Oops, I didn't notice, I've been looking at it today too.  Well it's all yours :-)

But for the record, I think the problem is with the UPSTREAM/DOWNSTREAM affinity and how it's used in VisiblePosition::getInlineBoxAndOffset.

Imagine that the text is "foo baaaaaaaa" and gets wrapped as

foo
baaaaaaaa

Placing the cursor at the end of "baaaaaaaa" and pressing shift+up moves the selection from

foo
baaaaaaaa|

... to

foo{
baaaaaaaa}

... but internally (within VisibleSelection) it's just seen as "foo {baaaaaaaa}" *, which is actually

foo
{baaaaaaaa}

... because the start of the selection ends up with DOWNSTREAM affinity**.

The exact same thing happens next time that shift+up is pressed because:
 - the same line is used (VisiblePosition::getInlineBoxAndOffset).
 - the same x offset is used due to the cached m_xPosForVerticalArrowNavigation being used.

I've verified that this is indeed the cause of the problem, by (in SelectionController::modifyExtendingBackward) changing
  VisiblePosition pos(m_selection.extent(), m_selection.affinity())
to
  VisiblePosition pos(m_selection.extent(), UPSTREAM)
but I don't think that that is necessarily the most "correct" solution.  What would be "correct" is that m_selection.affinity() somehow knows that last operation was to extend the selection backwards, and that the affinity should be UPSTREAM in this case... but that doesn't really make sense, since what does the affinity of a whole selection really mean?

So perhaps the shortest path to a fix would be to change that to UPSTREAM.  And for the record, the change doesn't cause any layout tests to fail.


* rather than "foo{ baaaaaaaa}" due to visible equivalence, I believe.
** or at least, it does when then re-queried; it seems that the start of a selection always has DOWNSTREAM, and the end always has UPSTREAM.
Comment 7 Ryosuke Niwa 2011-01-31 00:09:32 PST
(In reply to comment #6)
> ... because the start of the selection ends up with DOWNSTREAM affinity**.

If this is really the case, then this is the bug and needs to be fixed.

> I've verified that this is indeed the cause of the problem, by (in SelectionController::modifyExtendingBackward) changing
>   VisiblePosition pos(m_selection.extent(), m_selection.affinity())
> to
>   VisiblePosition pos(m_selection.extent(), UPSTREAM)
> but I don't think that that is necessarily the most "correct" solution.  What would be "correct" is that m_selection.affinity() somehow knows that last operation was to extend the selection backwards, and that the affinity should be UPSTREAM in this case... but that doesn't really make sense, since what does the affinity of a whole selection really mean?

The correct fix is to make m_selection.affinity() return UPSTREAM there.

> So perhaps the shortest path to a fix would be to change that to UPSTREAM.  And for the record, the change doesn't cause any layout tests to fail.

No. That may make sense for this specific case but it won't make sense for other cases.  I bet this is an issue with the trial SelectionController in SelectionController::modify.  http://trac.webkit.org/browser/trunk/Source/WebCore/editing/SelectionController.cpp#L699.  Levi will probably know how to fix this.
Comment 8 Fabrizio 2011-01-31 04:17:18 PST
@Benjamin no worries!  I've been stepping through the SelectionController code a few days and changing some code around, but wasn't really getting at the root of the problem.  

I'll step through it again and see if I understand what you and Ryosuke have described but, by all means, post your patch(es) if you already have the fix.   I'm interested in seeing how this is supposed to work.  I can work on an easier bug in the meantime.
Comment 9 Levi Weintraub 2011-01-31 10:40:42 PST
> > I've verified that this is indeed the cause of the problem, by (in SelectionController::modifyExtendingBackward) changing
> >   VisiblePosition pos(m_selection.extent(), m_selection.affinity())
> > to
> >   VisiblePosition pos(m_selection.extent(), UPSTREAM)
> > but I don't think that that is necessarily the most "correct" solution.  What would be "correct" is that m_selection.affinity() somehow knows that last operation was to extend the selection backwards, and that the affinity should be UPSTREAM in this case... but that doesn't really make sense, since what does the affinity of a whole selection really mean?
> 
> The correct fix is to make m_selection.affinity() return UPSTREAM there.

That is correct.

> > So perhaps the shortest path to a fix would be to change that to UPSTREAM.  And for the record, the change doesn't cause any layout tests to fail.
> 
> No. That may make sense for this specific case but it won't make sense for other cases.  I bet this is an issue with the trial SelectionController in SelectionController::modify.  http://trac.webkit.org/browser/trunk/Source/WebCore/editing/SelectionController.cpp#L699.  Levi will probably know how to fix this.

Unfortunately this isn't a trial selection controller problem, but your statement that no layout tests fail by simply changing this to UPSTREAM worries me, as this would likely cause a number of unintended consequences! 

I'll take a look at this, and hope to check in a test that will, indeed, cause such a change to fail so we don't end up making a mistake like this in the future :)
Comment 10 Levi Weintraub 2011-01-31 11:59:03 PST
Ahh, more bugs from my early days. Apologies...

The source of this bug is VisibleSelection. VisibleSelection, in an effort to avoid the overhead and relayout issues of VisiblePositions, uses a set of Positions and one affinity. The single affinity is only used for Caret selections, which does not cover all cases, this one included.

VisibleSelection needs *at least* two affinities, one each for start and end, so it can keep track of a selection that covers both a downstream and upstream Position like the one we're trying to make here.
Comment 11 Benjamin (Ben) Kalman 2011-01-31 17:19:59 PST
(In reply to comment #10)
> Ahh, more bugs from my early days. Apologies...
> 
> The source of this bug is VisibleSelection. VisibleSelection, in an effort to avoid the overhead and relayout issues of VisiblePositions, uses a set of Positions and one affinity. The single affinity is only used for Caret selections, which does not cover all cases, this one included.

This sort of thing is what I meant by "what does the affinity of a whole selection really mean".

> 
> VisibleSelection needs *at least* two affinities, one each for start and end, so it can keep track of a selection that covers both a downstream and upstream Position like the one we're trying to make here.

Yep.  When I said that the start of a selection always has DOWNSTREAM and the start UPSTREAM I was referring to the visibleStart and visibleEnd methods which return DOWNSTREAM vs UPSTREAM respectively.  Perhaps this is what should be changed.
Comment 12 Levi Weintraub 2011-01-31 17:32:02 PST
> Yep.  When I said that the start of a selection always has DOWNSTREAM and the start UPSTREAM I was referring to the visibleStart and visibleEnd methods which return DOWNSTREAM vs UPSTREAM respectively.  Perhaps this is what should be changed.

This is an attempt to ensure we use the "smallest" possible selection that represents the underlying positions for editing. If we change visibleStart to always return the upstream VisiblePosition, we'll be switching this to being greedy, which will cause unintended consequences in other editing code.

I believe VisibleSelection needs to track multiple affinities that it derives based on the VisiblePositions passed in, but I'm not sure without looking more if we need 2 or 4 to cover all cases on all platforms.
Comment 13 Benjamin (Ben) Kalman 2011-01-31 17:39:33 PST
(In reply to comment #12)
> > Yep.  When I said that the start of a selection always has DOWNSTREAM and the start UPSTREAM I was referring to the visibleStart and visibleEnd methods which return DOWNSTREAM vs UPSTREAM respectively.  Perhaps this is what should be changed.
> 
> This is an attempt to ensure we use the "smallest" possible selection that represents the underlying positions for editing. If we change visibleStart to always return the upstream VisiblePosition, we'll be switching this to being greedy, which will cause unintended consequences in other editing code.

Oh, I wasn't suggesting this.  More like what you said below, or replacing setAffinity with setStartAffinity and getEndAffinity.  From what I (eclipse) can tell, the only place it's called is SelectionController:modify anyway.

> 
> I believe VisibleSelection needs to track multiple affinities that it derives based on the VisiblePositions passed in, but I'm not sure without looking more if we need 2 or 4 to cover all cases on all platforms.
Comment 14 Yair Yogev 2011-02-16 09:49:15 PST
Created attachment 82653 [details]
RTL version of the demo

Here is an RTL demo, (hopefully) showing the same bug.
Comment 15 Yair Yogev 2011-02-16 10:00:08 PST
Created attachment 82656 [details]
RTL version of the demo

same file, without Bugzilla destroying the encoding...
Comment 16 Ahmad Saleem 2022-11-22 02:22:15 PST
I am not able to reproduce this bug in Safari Technology Preview 158 and in both attached test case, doing Shift+Arrow Up will select both lines rather than just one line.

Appreciate if someone can reconfirm my testing results, so we can mark this as "RESOLVED CONFIGURATION CHANGED". Thanks!