Caret positioning is incorrect in an empty contenteditable element with ::before pseudo
Categories
(Core :: Layout: Block and Inline, defect, P2)
Tracking
()
Webcompat Priority | P2 |
Tracking | Status | |
---|---|---|
firefox61 | --- | wontfix |
firefox62 | --- | fix-optional |
firefox63 | --- | affected |
People
(Reporter: andyearnshaw, Assigned: emilio)
References
Details
Attachments
(6 files, 3 obsolete files)
User Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/30.0.1588.0 Safari/537.36 Steps to reproduce: Create a single element, with the contenteditable attribute set to "true". Add a ::before pseudo-element rule for the element, with a character in the content property value or use a fixed width. In the rendered page, focus the editable element, paying attention to the position of the caret before typing the first character. Actual results: The caret is initially positioned to the leftmost point of the element. When the first character is typed, the position of the character will be placed after the pseudo element, causing the caret to jump to the correct position. Note that Chrome 30 dev (perhaps WebKit) also suffers from this problem, but Opera positions the caret perfectly. Expected results: The caret should initially be positioned after the pseudo-element. Pseudo-elements aren't an editable part of the content and so it is confusing to see the caret positioned before the pseudo element when any typing places content after the pseudo element. Once the element has content, it's not possible to get the caret back to its original (incorrect) position.
Updated•11 years ago
|
Comment 1•11 years ago
|
||
Confirmed with 2013-08-19-03-02-05-mozilla-central-firefox-26.0a1.en-US.linux-x86_64
I've confirmed with "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:23.0) Gecko/20100101 Firefox/23.0" Here's a JSFiddle: http://jsfiddle.net/rgthree/zTj8q/
(In reply to rgthree from comment #2) > I've confirmed with "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:23.0) > Gecko/20100101 Firefox/23.0" > > Here's a JSFiddle: http://jsfiddle.net/rgthree/zTj8q/ Actually, my issue in the jsfiddle linked above is very similar, except when the pseudo content (::before or ::after) is positioned absolutely, the caret is ~10px above the contenteditable until the user starts typing. I'm going to assume it's the same bug as here.
Comment 4•11 years ago
|
||
(In reply to comment #3) > (In reply to rgthree from comment #2) > > I've confirmed with "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:23.0) > > Gecko/20100101 Firefox/23.0" > > > > Here's a JSFiddle: http://jsfiddle.net/rgthree/zTj8q/ > > Actually, my issue in the jsfiddle linked above is very similar, except when > the pseudo content (::before or ::after) is positioned absolutely, the caret is > ~10px above the contenteditable until the user starts typing. I'm going to > assume it's the same bug as here. In general we don't handle generated content (such as ::before and ::after) very well at all.
Comment 5•11 years ago
|
||
Here’s one more JSFiddle that demonstrates this same issue along with a very closely related issue regarding the use of [contenteditable] with a ::before pseudoelement: http://jsfiddle.net/m5zDU/ Follow the instructions there and you’ll see that it’s pretty easy to accidentally move the input caret into and within the pseudoelement’s text. Happy to file it as a separate issue... just let me know.
Comment 6•10 years ago
|
||
How is this still unconfirmed? Almost a year now... I've had numerous problems with the content editable divs, with both chrome and firefox, but at least chrome has finally fixed this issue.
Comment 7•10 years ago
|
||
Hi, we see problems with this bug as well. You can look at our live demos at http://www.cortical.io/context-browser.html Does anybody here have a work around, short of getting a real fix? Screenshot: https://www.evernote.com/shard/s128/sh/79590667-2fea-4929-a874-8345a8eaaaff/615fa9af0042929aa3c46ce1b99374b9/deep/0/safari-firefox-difference.png
Updated•10 years ago
|
This isn't an editor issue. This is a layout problem. The 'inline-block' bit is key to this. If you change it to just inline the testcase works.
The issue here is that nsBlockFrame passes through calls to GetCaretBaseline to its children, while nsInlineFrame does not. If you use inline you get an nsInlineFrame, but if you use inline-block you get an nsBlockFrame. The underlying text frame has not been reflowed so it doesn't have any metric yet, so if we pass the call through to the text frame we get a bogus baseline value. Making nsBlockFrame not pass the call through if the line is inline fixes this issue, but I don't claim it's the correct fix. Some guidance would be appreciated here. I'm a long way from home.
Comment 10•10 years ago
|
||
Comment on attachment 8481880 [details] [diff] [review] (Probably wrong) Patch Review of attachment 8481880 [details] [diff] [review]: ----------------------------------------------------------------- I think the right thing to do would be to pass this down to the child even if it's an inline frame. The problem might be the fact that the text frame doesn't have the right metrics. You said something about it not having been reflown? That's totally unexpected, I think nsCaret should not call GetCaretBaseline on any frame before a reflow has been performed. Is that what's really happening? Note that I would expect the reflow to happen, and since this is an empty text frame I would expect this code <http://hg.mozilla.org/mozilla-central/annotate/c360f3d1c00d/layout/generic/nsTextFrame.cpp#l8037> to compute the correct metrics for the frame. Does that not happen here?
Comment on attachment 8481880 [details] [diff] [review] (Probably wrong) Patch So I poked at this a little more and we're bailing out of nsTextFrame::ReflowText at http://hg.mozilla.org/mozilla-central/annotate/c360f3d1c00d/layout/generic/nsTextFrame.cpp#l7799 because there's no text. That means we never get to http://hg.mozilla.org/mozilla-central/annotate/c360f3d1c00d/layout/generic/nsTextFrame.cpp#l8036 where we get the ascent data we need for GetCaretBaseline.
Right, I agree. We need to add some code to ClearMetrics that obtains a real ascent. It would basically duplicate the code from PropertyProvider::InitFontGroupAndFontMetrics to get the nsFontMetrics.
I'll take this.
Fixing the textframe metrics gets the vertical position right but that isn't enough. The other part of the bug is that the horizontal position is wrong. Since the block has no normal content to associate the caret with, we set the caret to be associated with offset 0 in the <div>'s block frame. The horizontal position of the caret is then computed by nsFrame::GetPointFromOffset, which just returns the left edge of the frame's content-rect. What we really want here is the position on the block's first line after any ::before content. But that only works if the ::before content is inline outside. If it was display:block then we'd want the caret position to be below the ::before content, on an empty line that doesn't otherwise exist. So there's actually a large can of worms here to fix this bug properly. In fact it's not at all clear what do in cases like <style>div::before { content"hello"; display:block; background:yellow; }</style> <div style="border:2px solid black"></div> The intuitive idea "draw the caret where text would be inserted" doesn't work in cases like that where there's nowhere for the caret to go. We either have to change the layout to make room for the caret or draw it somewhere that looks broken.
Comment 16•10 years ago
|
||
Comment on attachment 8483223 [details] [diff] [review] fix vertical position Review of attachment 8483223 [details] [diff] [review]: ----------------------------------------------------------------- This looks fine for the vertical metrics... are you going to try and do something about horizontal position here as well, or spin off a separate bug for that? As you note, there are some examples where the "right" answer is not at all clear, but it'd be nice to fix the inline-::before case, at least.
I'm not going to do anything about the horizontal metrics for now. The patch here at least makes us behave the same as Chrome, which is a definite improvement. I'm unenthusiastic about trying to fix the inline case on its own without a broader plan.
Comment 18•10 years ago
|
||
Fair enough, but let's have a new bug on file for that, then.
To fix test regressions, we have to change nsLineLayout::IsZeroBSize to ignore collapsed-away text frames with non-zero height. There's also a test that checks the height of an empty text node via getClientRects. I think changing this test is OK; I'm not aware of any spec that says the height must be zero.
Comment 21•10 years ago
|
||
Comment on attachment 8486313 [details] [diff] [review] fix v2 LGTM, but dbaron should probably have a look as well.
Comment on attachment 8486313 [details] [diff] [review] fix v2 I think this will change the quirks mode inline box model handling when line-height is normal, for cases like: <p><span style="background:yellow"><span style="font-size: 80%">hello </span> </span></p> (In quirks mode, the yellow background should be smaller than it is in standards mode. And note the space before both </span>s.) Looking at the code from: https://hg.mozilla.org/mozilla-central/file/30920bcb070a/layout/generic/nsLineLayout.cpp#l2016 I think we'll hit canUpdate = true (because vertical-align is not top or bottom, and line-height is normal) and then hit: 2044 if (blockStart < minBCoord) minBCoord = blockStart; 2045 if (blockEnd > maxBCoord) maxBCoord = blockEnd; This, in turn, will change the inline sizing adjustment further down. If my theory that the patch breaks that case is correct, could you: * add a reftest for that case, and * fix it by making lines 2044-2045 (quoted above) conditional on not being an empty text frame (just like your check in IsZeroBSize r=dbaron with that
I don't think that theory is correct. For the whitespace-only textframe (or a completely empty text frame in its place, if you adjust the test that way), canUpdate will not be set, because PFD_ISNONWHITESPACETEXTFRAME will not be set, because TEXT_HAS_NONCOLLAPSED_CHARACTERS will not be set along any path that calls nsTextFrame::ClearMetrics. Since canUpdate is not set. Do you agree?
Ah, right; I think I'd misread the conditions around setting canUpdate. Sounds good.
ready to land?
Probably, I just have a list of higher-priority stuff to deal with.
Comment 28•9 years ago
|
||
Any update on landing the resolution to Firefox ? (last message was 8 months ago)
Updated•9 years ago
|
There were test failures last time I ran this through try.
Comment 30•8 years ago
|
||
focus in contentEditable empty inline element, cursor position error
Comment 31•8 years ago
|
||
http://codepen.io/mantou132/pen/EyJmBr
Comment 32•6 years ago
|
||
5 years later and this bug persists.
Comment 33•6 years ago
|
||
use this function with index = 0 ` function moveCaretTo(element, index) { const range = document.createRange(); const selection = window.getSelection(); range.setStart(element, index); range.collapse(true); selection.removeAllRanges(); selection.addRange(range); } `
Comment 34•6 years ago
|
||
Interesting that selection works and range doesn't. This allowed me to figure out what's going on. Apparently the startNode seems to be off by 1, falling into the node following the contenteditable. The workaround is to go to the previous element and re-focus. Proof of concept showcasing both the bug and workaround: https://codepen.io/cheng-lee/pen/LBVvOv
Comment 35•6 years ago
|
||
David, do you want to take a look or maybe find someone who could fix this in 63? It's clearly been around a while, but now we have this suggestion for a workaround.
While it would be good to get this landed, I don't see any reason to prioritize it right now.
I also don't see why the existence of a workaround increases the bug's priority.
Comment 38•6 years ago
|
||
It should be prioritized right now because it's affecting a lot of my work, and the work of others, and the existence of a hacky javascript "workaround" is not acceptable. We use contenteditable divs in a lot of our projects, including our main control panel (Froala Editor).
Comment 39•6 years ago
|
||
The workaround doesn't work.
Comment 40•6 years ago
|
||
My caretproblems in firefox was gone as I added a white-space: pre-wrap to my contenteditable element.
Comment 41•6 years ago
|
||
@kontakt I can confirm this workaround works in Firefox v63 (64bit, Windows 7 Pro) I used this CSS to only affect white space wrapping on *empty contenteditable elements: [contenteditable="true"]:empty { white-space: pre-wrap; }
Assignee | ||
Comment 42•6 years ago
|
||
FWIW I rebased this patch to see where it stands: https://treeherder.mozilla.org/#/jobs?repo=try&revision=051656ace072f5955771720bd35ea34273bc7576
Comment 43•5 years ago
|
||
HTML: ``` <div contenteditable="true" placeholder="Enter text here here here here here here here here..."></div> ``` CSS: ``` [contenteditable=true]:empty::before{ content: attr(placeholder); display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } div[contenteditable=true] { border: 1px dashed #AAA; width: 290px; padding: 5px; white-space: pre-wrap; } ``` https://codepen.io/anon/pen/BvNpYw
Comment 44•5 years ago
|
||
I was hoping that the workaround would work for my use case (simulating an _ellipsis'd_ `::placeholder` in a contenteditable `<div>`) but it did not. (https://bugzilla.mozilla.org/attachment.cgi?id=9030512) HTML: ``` <div contenteditable="true" placeholder="Enter text here here here here here here here here..."></div> ``` CSS: ``` [contenteditable=true]:empty::before{ content: attr(placeholder); display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } div[contenteditable=true] { border: 1px dashed #AAA; width: 290px; padding: 5px; white-space: pre-wrap; } ``` https://codepen.io/anon/pen/BvNpYw
Comment 45•5 years ago
|
||
@Steven Luscher I found a work using display: flex
on the contenteditable
element:
https://codepen.io/anon/pen/omqGgL
It may have some side-effects though which I haven't thought about yet.
Updated•4 years ago
|
Comment 46•4 years ago
|
||
So on webcompat we got this testcase.
https://jsfiddle.net/7r13ebp8/
1. Open link and click on text field. 2. See where caret is positioned.
In Chrome, it is at the start of placeholder text, on the same line.
In Firefox, it is above, in top-left corner. But when you start typing something, your text appears on the same line. Or if you remove placeholder text, then caret is on the same line too.
The interop stories is not great, but there's no unique scenario for sure. I will repeat my comment from the webcompat issue.
https://github.com/webcompat/web-bugs/issues/48056#issuecomment-590129491
And this is in Safari… which has the same behavior than firefox.
So it's not obvious there is a webcompat issue.
but we could argue that it is more logical to put the caret at the beginning of the placeholder.
When we start typing the line, safari has also a caret which goes from the top of the box to the baseline, while firefox and chrome have a caret which is has big has the characters.
Another difference is if you remove the text after typing it.
- In firefox, the area stays blank with small cursor blinking at the start of the line. If unfocused the line stays blank, caret disappears.
- In chrome, the caret blinks, and the placeholder text reappears, when unfocused, just the caret disappears.
- in safari, same thing than firefox, but with a caret from top of box to the baseline.
Assignee | ||
Comment 47•4 years ago
|
||
While I'm looking at this code... This fixes the case described in
comment 46.
Updated•4 years ago
|
Comment 48•4 years ago
|
||
Comment on attachment 9145036 [details]
Bug 904846 - Fix caret baseline on a line with only inlines to account for line-height properly. r=jfkthame
Revision D73465 was moved to bug 1634743. Setting attachment 9145036 [details] to obsolete.
Updated•3 years ago
|
Updated•2 years ago
|
Updated•2 years ago
|
Comment 49•2 years ago
|
||
The severity field for this bug is relatively low, S3. However, the bug has 18 votes.
:emilio, could you consider increasing the bug severity?
For more information, please visit auto_nag documentation.
Comment 51•4 months ago
|
||
(In reply to Steven Luscher from comment #44)
I was hoping that the workaround would work for my use case (simulating an
ellipsis'd::placeholder
in a contenteditable<div>
) but it did not.
(https://bugzilla.mozilla.org/attachment.cgi?id=9030512)HTML:
<div contenteditable="true" placeholder="Enter text here here here here here here here here..."></div>
CSS:
[contenteditable=true]:empty::before{ content: attr(placeholder); display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } div[contenteditable=true] { border: 1px dashed #AAA; width: 290px; padding: 5px; white-space: pre-wrap; }
FWIW, changing overflow:hidden
to overflow:clip
will fix the caret position - tho its not clear why, according to the mdn doc:
Specifying a value other than visible (the default) or clip for overflow creates a new block formatting context. This is necessary for technical reasons; if a float intersects with a scrolling element, it would forcibly rewrap the content after each scroll step, leading to a slow scrolling experience.
I would imagine it might be related to the formatting context somehow...
Comment 52•3 days ago
|
||
As we don't know of any actual sites being affected by this, only codepens, I'm going to remove the webcompat priority for now.
Comment 53•1 day ago
|
||
(In reply to Thomas Wisniewski [:twisniewski] from comment #52)
As we don't know of any actual sites being affected by this, only codepens, I'm going to remove the webcompat priority for now.
I've spent the last couple of days baffled about a particular bug in a private project that is causing content written inside a ContentEditable code block to be written in reverse.
By pure luck this particular 11 year old bug report was bumped up 2 days ago and I just realised this is the root cause of my issue as it makes the caret get stuck on the leftmost position which causes the input to be reversed.
Imagine my dissapointment when i realised this issue (which is now so old that you could register it to middle school) was only bumped up because its priority has been removed. I implore you to please take cases similar to mine and number of votes on this bug report to consideration and re-evaluate the severity and priority of this issue.
Comment 54•1 day ago
|
||
(In reply to Thomas Wisniewski [:twisniewski] from comment #52)
As we don't know of any actual sites being affected by this, only codepens, I'm going to remove the webcompat priority for now.
Tons of websites/web-apps affected. Every contentEditable
based editor is affected, and there are so many web components relying on contentEditable
"wysiwyg"-like abilities. My own famous Tagify component which is used by tons of people is affected as well: https://github.com/yairEO/tagify
Many SaaS projects are behind a pay-wall and or are aimed for B2B only and are heavily relied on complex web components (unlike simple text-based websites) and this bug probably affect quite of the SaaS products (I've worked on many by many clients of mine)
Comment 55•1 day ago
|
||
Thanks for the insight folks, it's exactly what we need to know to help prioritize these kinds of bug fixes. I'll bump the priority back up now that we know it's definitely causing more pain than we were aware of.
Emilio, would you mind taking another look at this?
Updated•1 day ago
|
Assignee | ||
Comment 56•23 hours ago
|
||
(In reply to Ata Hakcil from comment #53)
By pure luck this particular 11 year old bug report was bumped up 2 days ago and I just realised this is the root cause of my issue as it makes the caret get stuck on the leftmost position which causes the input to be reversed.
WDYM with "causes the input to be reversed"? This is only about the caret position. If you have a repro for that I'm happy to poke.
In any case I think the right thing to do is looking at whether https://hg.mozilla.org/try/rev/6716fc2c18434337eb6c153a380c9ffccc6b6986 still fixes the issue, and whether we can get it landed.
Comment 57•23 hours ago
|
||
Thanks for updating the priority!
(In reply to Emilio Cobos Álvarez (:emilio) from comment #56)
(In reply to Ata Hakcil from comment #53)
By pure luck this particular 11 year old bug report was bumped up 2 days ago and I just realised this is the root cause of my issue as it makes the caret get stuck on the leftmost position which causes the input to be reversed.
WDYM with "causes the input to be reversed"? This is only about the caret position. If you have a repro for that I'm happy to poke.
We have a ContentEditable code block as the main component of an editor page. There is an event handler that reads the code block content until the caret and may inject additional content to the contentEditable code block such as autocomplete suggestions at the caret position.
Because of the nature of this component and the dynamic contents inside, we have to prevent the default event handler for keyboard input and insert the new character at the caret position ourselves.
Caret being stuck at the leftmost position means when we try to read the code block contents until the caret, we always read empty string, and it also makes the characters typed by the user to be added to the beginning of the block where caret is stuck.
Assignee | ||
Comment 58•23 hours ago
|
||
https://treeherder.mozilla.org/jobs?repo=try&revision=00570165f1d0893c25db1bf1b5453188fc291da4 is the rebased patch again.
(In reply to Ata Hakcil from comment #57)
We have a ContentEditable code block as the main component of an editor page. There is an event handler that reads the code block content until the caret and may inject additional content to the contentEditable code block such as autocomplete suggestions at the caret position.
Because of the nature of this component and the dynamic contents inside, we have to prevent the default event handler for keyboard input and insert the new character at the caret position ourselves.
Caret being stuck at the leftmost position means when we try to read the code block contents until the caret, we always read empty string, and it also makes the characters typed by the user to be added to the beginning of the block where caret is stuck.
That seems totally unrelated to this bug, assuming I'm understanding correctly. Presumably you're reading the caret position using Selection.focusNode
/ Selection.focusOffset
or so? That seems nothing to do with this bug, this bug is about the caret being mispositioned at paint time. If you could build a test-case for that issue and file a new bug, it'd be appreciated. Feel free to cc me, post it here.
Comment 59•22 hours ago
|
||
(In reply to Emilio Cobos Álvarez (:emilio) from comment #58)
https://treeherder.mozilla.org/jobs?repo=try&revision=00570165f1d0893c25db1bf1b5453188fc291da4 is the rebased patch again.
(In reply to Ata Hakcil from comment #57)
We have a ContentEditable code block as the main component of an editor page. There is an event handler that reads the code block content until the caret and may inject additional content to the contentEditable code block such as autocomplete suggestions at the caret position.
Because of the nature of this component and the dynamic contents inside, we have to prevent the default event handler for keyboard input and insert the new character at the caret position ourselves.
Caret being stuck at the leftmost position means when we try to read the code block contents until the caret, we always read empty string, and it also makes the characters typed by the user to be added to the beginning of the block where caret is stuck.
That seems totally unrelated to this bug, assuming I'm understanding correctly. Presumably you're reading the caret position using
Selection.focusNode
/Selection.focusOffset
or so? That seems nothing to do with this bug, this bug is about the caret being mispositioned at paint time. If you could build a test-case for that issue and file a new bug, it'd be appreciated. Feel free to cc me, post it here.
Unfortunately I can't come up with a clean POC to demonstrate this as I can't get everything to go precisely wrong with just vanilla JS the way it is going on my current project, but I've taken the following screen recording from a more minimal environment that shows (unless I'm completely misreading what is going on in the background) that my issue is also related to caret being mispositioned at the paint time in same way, although I'm not 100% certain.
When I have time, I'll check if the suggested solution also fixes this problem and if not I'll isolate the issue further and create a new bug report.
Assignee | ||
Comment 60•20 hours ago
|
||
The behavior of the test-cases on the bug is identical to chromium now. That's because empty text nodes on ::before were suppressed in bug 1865668.
Assignee | ||
Comment 61•20 hours ago
|
||
Updated•20 hours ago
|
Assignee | ||
Comment 62•17 hours ago
|
||
Assignee | ||
Comment 63•17 hours ago
|
||
This passes try and includes a test, but the test fails.
Description
•