Closed Bug 550434 Opened 14 years ago Closed 14 years ago

Clicking in an empty contenteditable element that has focus causes the caret to disappear

Categories

(Core :: DOM: Selection, defect, P2)

defect

Tracking

()

RESOLVED FIXED
mozilla1.9.3a5
Tracking Status
blocking2.0 --- final+

People

(Reporter: roc, Assigned: masayuki)

References

()

Details

(Keywords: testcase)

Attachments

(2 files)

It's not clear whether the element has focus or not. Typing doesn't work. See bug 539323 comment #8.
Thanks for the help on this.  I'd love it if we could mark this as a blocker for 3.7.
blocking2.0: --- → ?
Assignee: nobody → roc
blocking2.0: ? → final+
Priority: -- → P2
This bug affects us too. Sometimes it's possible to make a caret appear through dedicated clicking, but you still can't enter text. A workaround is to seed the contentEditable div with a "<br>" tag.
I had been trying the <br> tag for awhile, but just couldn't make it work.  I kept getting into weird situation where all of a sudden the browser would start rendering it.  I was almost able to get away with a 0-width space.  In either case the user can copy it out (e.g. Ctrl+A, Ctrl+C) and that was a deal breaker, which I couldn't find anyway around.
This is one of a few contentEditable bugs that has made it difficult/impossible to implement Google Spreadsheet's formula highlighting in Firefox.  We'd like to turn the feature on in FF 3.7 assuming enough of the issues have been addressed in the browser.  Thanks!
http://googledocs.blogspot.com/2010/05/formula-highlighting-in-spreadsheets.html
The first time that you click the element, the selection code sets the focus to the <div> node.  This includes setting up the selection to be anchored to the div element, and firing a focus event.  The focus event is caught by nsEditorEventListener::Focus.  This function calls nsFrameSelection::SetAncestorLimiter to set the selection limiter to the <div> element.

<http://mxr.mozilla.org/mozilla-central/source/editor/libeditor/base/nsEditorEventListener.cpp#925>

Inside this function, we call IsValidSelectionPoint:

<http://mxr.mozilla.org/mozilla-central/source/layout/generic/nsSelection.cpp#3249>

Note that here, the limiter is null (because we're not a text control), and the aNode parameter is set to the element currently registered to have the focus (which is the *body* element, not the div element yet).  Before IsValidSelectionPoint is called, we set the ancestor limiter to the div element <http://mxr.mozilla.org/mozilla-central/source/layout/generic/nsSelection.cpp#3243>, so, effectively the result of IsValidSelectionPoint is the result of this expression: <http://mxr.mozilla.org/mozilla-central/source/layout/generic/nsSelection.cpp#570> where limiter is the div, and aNode is the body element.  This check fails, which causes us to nuke our selection <http://mxr.mozilla.org/mozilla-central/source/layout/generic/nsSelection.cpp#3250>, which messes up the selection, and the next time when you click on the div, you won't get a caret because we don't have a selection any more.

I _think_ the correct fix here is that in the SetAncestorLimiter call from nsEditorEventListener::Focus, we skip the ancestor limiter checks.  This is actually the correct thing to do because we know that later on when the div takes focus, the ancestor limiter set is the correct one (because the ancestor limiter and the focused node are the same.)
Assignee: roc → ehsan
Status: NEW → ASSIGNED
Actually, it seems that the behavior in comment 5 only causes problems for text area elements which are empty initially.  This has started to look a lot like bug 551704.
The <br /> problem in bug 551704 is in fact the reason the work-around I described above happens to work for us. Firefox acts like the <br /> isn't there, but selection and so on works as opposed to if the field had been entirely empty.
(In reply to comment #7)
> The <br /> problem in bug 551704 is in fact the reason the work-around I
> described above happens to work for us. Firefox acts like the <br /> isn't
> there, but selection and so on works as opposed to if the field had been
> entirely empty.

Yes.  Furthermore, the existence of the <br> node has many more implications than what's apparent.  That problem really needs to be addressed.
Attached patch Patch (v1)Splinter Review
So, we need to ensure that our selection is alive and well on clicking as well as on focus.  This patch does that through the SetAncestorLimiter call.
Attachment #450478 - Flags: review?(roc)
Attachment #450478 - Flags: review?(roc) → review+
http://hg.mozilla.org/mozilla-central/rev/e9b7e557499d
Status: ASSIGNED → RESOLVED
Closed: 14 years ago
Flags: in-testsuite+
Resolution: --- → FIXED
Target Milestone: --- → mozilla1.9.3a5
Comment on attachment 450478 [details] [diff] [review]
Patch (v1)

We definitely would want this fix on our branches as well as on trunk.
Attachment #450478 - Flags: approval1.9.2.5?
Attachment #450478 - Flags: approval1.9.1.11?
I think that the patch is wrong.

When 2nd click on the editor, nsFrameSelection::HandleClick() is called with aNewFocus == <body>. Then, TakeFocus() steals the focus.

nsFrameSelection::HandleClick()
nsFrame::HandlePress()
nsFrame::HandleEvent()
nsPresShellEventCB::HandleEvent()
nsEventTargetChainItem::HandleEventTargetChain()
nsEventDispatcher::Dispatch()
PresSHell::HandleEventInternal()
PresSHell::HandlePositionedEvent()
PresShell::HandleEvent()
...

However, the actual focused element is still <div>, therefore, nsEditorEventListener::Focus() isn't called again.

I think that this shouldn't be an editor bug.
http://mxr.mozilla.org/mozilla-central/source/layout/generic/nsFrame.cpp#1971
> 1862 NS_IMETHODIMP
> 1863 nsFrame::HandlePress(nsPresContext* aPresContext, 
> 1864                      nsGUIEvent*     aEvent,
> 1865                      nsEventStatus*  aEventStatus)
> 1866 {

> 1971   nsPoint pt = nsLayoutUtils::GetEventCoordinatesRelativeTo(aEvent, this);
> 1972   ContentOffsets offsets = GetContentOffsetsFromPoint(pt);

> 2041   // Do not touch any nsFrame members after this point without adding
> 2042   // weakFrame checks.
> 2043   rv = fc->HandleClick(offsets.content, offsets.StartOffset(),
> 2044                        offsets.EndOffset(), me->isShift, control,
> 2045                        offsets.associateWithNext);

The result of GetContentOffsetsFromPoint() is strange.
(In reply to comment #12)
> I think that the patch is wrong.
> 
> When 2nd click on the editor, nsFrameSelection::HandleClick() is called with
> aNewFocus == <body>.

Actually the same thing happens when nsFrameSelection::HandleClick/Focus are called for the first time.

> Then, TakeFocus() steals the focus.

It doesn't steal the focus.  The focus remains in the div both before and after this patch.  It's the selection which was going away, and that's the problem.

> nsFrameSelection::HandleClick()
> nsFrame::HandlePress()
> nsFrame::HandleEvent()
> nsPresShellEventCB::HandleEvent()
> nsEventTargetChainItem::HandleEventTargetChain()
> nsEventDispatcher::Dispatch()
> PresSHell::HandleEventInternal()
> PresSHell::HandlePositionedEvent()
> PresShell::HandleEvent()
> ...
> 
> However, the actual focused element is still <div>, therefore,
> nsEditorEventListener::Focus() isn't called again.

nsEditorEventListener::Focus is only called for the first click, both before and after this patch.

(In reply to comment #13)
> http://mxr.mozilla.org/mozilla-central/source/layout/generic/nsFrame.cpp#1971
> > 1862 NS_IMETHODIMP
> > 1863 nsFrame::HandlePress(nsPresContext* aPresContext, 
> > 1864                      nsGUIEvent*     aEvent,
> > 1865                      nsEventStatus*  aEventStatus)
> > 1866 {
> 
> > 1971   nsPoint pt = nsLayoutUtils::GetEventCoordinatesRelativeTo(aEvent, this);
> > 1972   ContentOffsets offsets = GetContentOffsetsFromPoint(pt);
> 
> > 2041   // Do not touch any nsFrame members after this point without adding
> > 2042   // weakFrame checks.
> > 2043   rv = fc->HandleClick(offsets.content, offsets.StartOffset(),
> > 2044                        offsets.EndOffset(), me->isShift, control,
> > 2045                        offsets.associateWithNext);
> 
> The result of GetContentOffsetsFromPoint() is strange.

Yes, that was what I encountered as well.  But the exact same result happens when the div is clicked for the first time as well.  I wasn't so sure what that function is supposed to return, and stepping through it I didn't see anything particularly wrong (although I don't claim to understand all the details).  Therefore I didn't touch it.
(In reply to comment #14)
>> Then, TakeFocus() steals the focus.
> 
> It doesn't steal the focus.  The focus remains in the div both before and after
> this patch.  It's the selection which was going away, and that's the problem.

I meant it steals selection (caret) from editor (set to body). It's the cause of this bug. Your patch just reinitialize the selection/caret on mouseclick event after that.

> Yes, that was what I encountered as well.  But the exact same result happens
> when the div is clicked for the first time as well.  I wasn't so sure what that
> function is supposed to return, and stepping through it I didn't see anything
> particularly wrong (although I don't claim to understand all the details). 
> Therefore I didn't touch it.

Because after here, nsEditorEventListener::Focus() will be called and it initialize the selection, then, caret is moved to the editor. Therefore, only when we click at first time, caret doesn't disappear.
(In reply to comment #15)
> (In reply to comment #14)
> >> Then, TakeFocus() steals the focus.
> > 
> > It doesn't steal the focus.  The focus remains in the div both before and after
> > this patch.  It's the selection which was going away, and that's the problem.
> 
> I meant it steals selection (caret) from editor (set to body). It's the cause
> of this bug. Your patch just reinitialize the selection/caret on mouseclick
> event after that.

Do you mean that we want the selection to be on the body element?

> > Yes, that was what I encountered as well.  But the exact same result happens
> > when the div is clicked for the first time as well.  I wasn't so sure what that
> > function is supposed to return, and stepping through it I didn't see anything
> > particularly wrong (although I don't claim to understand all the details). 
> > Therefore I didn't touch it.
> 
> Because after here, nsEditorEventListener::Focus() will be called and it
> initialize the selection, then, caret is moved to the editor.

Yes, but this only happens the first time that the element is clicked.

> Therefore, only when we click at first time, caret doesn't disappear.

This is actually the opposite of the problem.  When you click on the div for the first time, the caret shows up correctly.  When you click on it for the second time, it goes away.  And that happens because of SetAncestorLimiter clearing the selection...
(In reply to comment #16)
> (In reply to comment #15)
> > (In reply to comment #14)
> > >> Then, TakeFocus() steals the focus.
> > > 
> > > It doesn't steal the focus.  The focus remains in the div both before and after
> > > this patch.  It's the selection which was going away, and that's the problem.
> > 
> > I meant it steals selection (caret) from editor (set to body). It's the cause
> > of this bug. Your patch just reinitialize the selection/caret on mouseclick
> > event after that.
> 
> Do you mean that we want the selection to be on the body element?

No, the selection should be in the div. I said the actual behavior.

> > Therefore, only when we click at first time, caret doesn't disappear.
> 
> This is actually the opposite of the problem.  When you click on the div for
> the first time, the caret shows up correctly.  When you click on it for the
> second time, it goes away.

??

> And that happens because of SetAncestorLimiter clearing the selection...

Really? Who calls SetAncestorLimit? At second click, nsEditorEventListener::Focus() isn't called. And I cannot break my debug build (without your patch) in SetAncestorLimiter() at second click.
I think that approach like this patch is better.
(In reply to comment #17)
> > > Therefore, only when we click at first time, caret doesn't disappear.
> > 
> > This is actually the opposite of the problem.  When you click on the div for
> > the first time, the caret shows up correctly.  When you click on it for the
> > second time, it goes away.
> 
> ??
> 
> > And that happens because of SetAncestorLimiter clearing the selection...
> 
> Really? Who calls SetAncestorLimit? At second click,
> nsEditorEventListener::Focus() isn't called. And I cannot break my debug build
> (without your patch) in SetAncestorLimiter() at second click.

My apologies.  I meant by setting mAncestorLimier here: <http://mxr.mozilla.org/mozilla-central/source/layout/generic/nsSelection.cpp#1654>.
Attachment #450771 - Flags: review?(roc)
Comment on attachment 450771 [details] [diff] [review]
Alternative approach v1.0

The general approach of this patch (special casing editable elements in GetSelectionClosestFrameForBlock) was what I tested before writing the patch that I landed here.  It seemed to work, but I was worried making this change because I wasn't feeling quite confident if this won't break anything else.

The patch definitely looks good, but I have the same lack of confidence with this as well.  Let's see what roc thinks.
I posted the patch to tryserver.
Thanks a lot! :-)
You're welcome. The patch passed the tests on Linux and Mac, is still testing on Windows.
Taking and this should be a bug of selection.
Assignee: ehsan → masayuki
Status: RESOLVED → REOPENED
Component: Editor → Selection
OS: Mac OS X → All
QA Contact: editor → selection
Hardware: x86 → All
Resolution: FIXED → ---
passed on windows too.
Status: REOPENED → RESOLVED
Closed: 14 years ago14 years ago
Component: Selection → Editor
OS: All → Mac OS X
Hardware: All → x86
Resolution: --- → FIXED
Comment on attachment 450478 [details] [diff] [review]
Patch (v1)

We shouldn't take this patch for branch.
Attachment #450478 - Flags: approval1.9.2.5?
Attachment #450478 - Flags: approval1.9.1.11?
Oops, sorry for the spam.
Status: RESOLVED → REOPENED
Component: Editor → Selection
OS: Mac OS X → All
Hardware: x86 → All
Resolution: FIXED → ---
Comment on attachment 450771 [details] [diff] [review]
Alternative approach v1.0

I like this approach
Attachment #450771 - Attachment description: Alternative apporach v1.0 → Alternative approach v1.0
Attachment #450771 - Flags: review?(roc) → review+
Thank you, Roc and Ehsan.

http://hg.mozilla.org/mozilla-central/rev/c70244e2e622
Status: REOPENED → RESOLVED
Closed: 14 years ago14 years ago
Resolution: --- → FIXED
Thank *you*!

Could you please provide a unified patch to submit it for branch consideration?
O.K. But shouldn't we watch regression reports for a week?
Thanks!!  This bug was driving me crazy and I was unable to find a JS/HTML workaround, so I really appreciate the fix!
(In reply to comment #31)
> O.K. But shouldn't we watch regression reports for a week?

Of course.  We can hold on until then.  I usually prepare the branch patch sooner though, to make sure that I don't forget about it.  YMMV.  :-)
This caused a Tp4 mem regression: <http://mzl.la/ct7vnB>.
Really? The patch doesn't use heap. And the new member |emptyBlock| is 3rd PRPackedBool, so, it shouldn't cause increasing footprint.
Agreed!
You need to log in before you can comment on or make changes to this bug.

Attachment

General

Created:
Updated:
Size: