Closed Bug 1993351 Opened 20 days ago Closed 11 days ago

Caret position gets lost (moved to end) when textarea frame is reconstructed, after deleting the first character

Categories

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

defect

Tracking

()

RESOLVED FIXED
146 Branch
Tracking Status
firefox-esr115 --- wontfix
firefox-esr140 --- affected
firefox143 --- wontfix
firefox144 --- wontfix
firefox145 --- verified
firefox146 --- verified

People

(Reporter: dholbert, Assigned: saschanaz, NeedInfo)

References

(Regression)

Details

(Keywords: regression, webcompat:platform-bug)

User Story

user-impact-score:300

Attachments

(5 files)

Attached file testcase 1

STR:

  1. Load attached testcase.
  2. Click the very start of the textarea, and press "Delete" on your keyboard to delete that first character.
    (Alternately, you can click just after that first character and press Backspace; that has the same outcome for me.)
  3. Watch the caret (vertical bar) very carefully, when the textarea changes to cyan.

ACTUAL RESULTS:
Caret jumps to the end of the textarea.

EXPECTED RESULTS:
Caret should not jump.

If I repeat the STR but I delete a character in the middle of the text, then the caret does not jump. So there's something special about the situation when the caret is at the very start here.

(I'm not sure offhand if this belongs in layout or DOM:Editor; I don't know offhand where we save the caret/selection state to restore after frame reconstruction. Starting in DOM:Editor but feel free to reclassify as-appropriate.)

DOM: Selection?

But also we should try to do better than reframing for white-space changes...

Attachment #9519082 - Attachment description: screenccast of bug reproducing when I delete first character, vs. not-reproducing when I delete a character in the middle → screencast of bug reproducing when I delete first character, vs. not-reproducing when I delete a character in the middle
See Also: → 1993374

(In reply to Emilio Cobos Álvarez [:emilio] from comment #2)

But also we should try to do better than reframing for white-space changes...

Fair point. That's somewhat orthogonal to the caret-position-getting-lost-when-we-do-have-to-reframe [for whatever reason], though, so I spun off bug 1993374 on that.

Component: DOM: Editor → DOM: Selection

Regression range:
INFO: Last good revision: 7a6d6b986a1ed263db6d19ecbd56e2f4210870cf (2020-12-10)
INFO: First bad revision: 1130661c79c222fb1acd29b7ec5dc5202cdd0d2d (2020-12-11)
INFO: Pushlog:
https://hg.mozilla.org/mozilla-central/pushloghtml?fromchange=7a6d6b986a1ed263db6d19ecbd56e2f4210870cf&tochange=1130661c79c222fb1acd29b7ec5dc5202cdd0d2d

Suspected regressor (only one to mention selection):
https://hg-edge.mozilla.org/mozilla-central/rev/2a7e04756c232eb775140500ef2d370fa608eb50
Kagami Sascha Rosylight — Bug 1681615 - Detect selection changes through the setters r=masayuki

saschanaz, could you take a look here? This is causing a papercut on google.com (see bug 1985376).

Flags: needinfo?(krosylight)
Keywords: regression
Regressed by: 1681615

Set release status flags based on info from the regressing bug 1681615

User Story: (updated)
xul.dll!mozilla::TextControlState::SelectionProperties::SetIsDirty() Line 382 (d:\firefox\obj-aarch64-pc-windows-msvc\dist\include\mozilla\TextControlState.h:382)
xul.dll!mozilla::TextControlState::SetValueWithoutTextEditor(mozilla::AutoTextControlHandlingState & aHandlingSetValue) Line 2975 (d:\firefox\dom\html\TextControlState.cpp:2975)
xul.dll!mozilla::TextControlState::SetValue(const nsTSubstring<char16_t> & aValue, const nsTSubstring<char16_t> * aOldValue, const mozilla::EnumSet<mozilla::TextControlState::ValueSetterOption,unsigned int> & aOptions) Line 2658 (d:\firefox\dom\html\TextControlState.cpp:2658)
xul.dll!mozilla::TextControlState::SetValue(const nsTSubstring<char16_t> & aValue, const mozilla::EnumSet<mozilla::TextControlState::ValueSetterOption,unsigned int> & aOptions) Line 288 (d:\firefox\obj-aarch64-pc-windows-msvc\dist\include\mozilla\TextControlState.h:288)
xul.dll!mozilla::TextControlState::UnbindFromFrame(nsTextControlFrame * aFrame) Line 2441 (d:\firefox\dom\html\TextControlState.cpp:2441)
xul.dll!mozilla::dom::HTMLTextAreaElement::UnbindFromFrame(nsTextControlFrame * aFrame) Line 218 (d:\firefox\dom\html\HTMLTextAreaElement.cpp:218)
xul.dll!nsTextControlFrame::Destroy(mozilla::FrameDestroyContext & aContext) Line 130 (d:\firefox\layout\forms\nsTextControlFrame.cpp:130)
xul.dll!nsBlockFrame::DoRemoveFrame(mozilla::FrameDestroyContext & aContext, nsIFrame * aDeletedFrame, unsigned int aFlags) Line 7285 (d:\firefox\layout\generic\nsBlockFrame.cpp:7285)
xul.dll!nsBlockFrame::RemoveFrame(mozilla::FrameDestroyContext & aContext, mozilla::FrameChildListID aListID, nsIFrame * aOldFrame) Line 6438 (d:\firefox\layout\generic\nsBlockFrame.cpp:6438)
xul.dll!nsFrameManager::RemoveFrame(mozilla::FrameDestroyContext & aContext, mozilla::FrameChildListID aListID, nsIFrame * aOldFrame) Line 123 (d:\firefox\layout\base\nsFrameManager.cpp:123)
xul.dll!nsCSSFrameConstructor::ContentWillBeRemoved(nsIContent * aChild, nsCSSFrameConstructor::RemoveFlags aFlags) Line 7560 (d:\firefox\layout\base\nsCSSFrameConstructor.cpp:7560)
xul.dll!nsCSSFrameConstructor::RecreateFramesForContent(nsIContent * aContent, nsCSSFrameConstructor::InsertionKind aInsertionKind) Line 8476 (d:\firefox\layout\base\nsCSSFrameConstructor.cpp:8476)
xul.dll!mozilla::RestyleManager::ProcessRestyledFrames(nsStyleChangeList & aChangeList) Line 1687 (d:\firefox\layout\style\RestyleManager.cpp:1687)
xul.dll!mozilla::RestyleManager::DoProcessPendingRestyles(mozilla::ServoTraversalFlags aFlags) Line 3301 (d:\firefox\layout\style\RestyleManager.cpp:3301)
xul.dll!mozilla::RestyleManager::ProcessPendingRestyles() Line 3390 (d:\firefox\layout\style\RestyleManager.cpp:3390)
xul.dll!mozilla::PresShell::DoFlushPendingNotifications(mozilla::ChangesToFlush aFlush) Line 4622 (d:\firefox\layout\base\PresShell.cpp:4622)
xul.dll!mozilla::PresShell::FlushPendingNotifications(mozilla::ChangesToFlush aType) Line 1512 (d:\firefox\obj-aarch64-pc-windows-msvc\dist\include\mozilla\PresShell.h:1512)
xul.dll!mozilla::dom::Document::DetermineProximityToViewportAndNotifyResizeObservers() Line 18576 (d:\firefox\dom\base\Document.cpp:18576)
xul.dll!nsRefreshDriver::Tick::<lambda>(mozilla::dom::Document & aDoc) Line 2515 (d:\firefox\layout\base\nsRefreshDriver.cpp:2515)
xul.dll!nsRefreshDriver::RunRenderingPhase<`lambda at D:\firefox\layout\base\nsRefreshDriver.cpp:2512:7'>::<lambda>() Line 1316 (d:\firefox\layout\base\nsRefreshDriver.cpp:1316)

The layout change calls TextControlState::UnbindFromFrame and then ultimately TextControlState::SetValueWithoutTextEditor, which internally branches based on if (!mValue.Equals(aHandlingSetValue.GetSettingValue())).

If I tap arrow-up key instead of delete in the repro, mValue doesn't change, which makes it enter the no-change branch, where it calls SetIsDirty. And later RestoreSelectionState::Run() checks the dirty state and calls mFrame->SetSelectionRange.

What's different with delete is that mValue does change, which makes in enter another branch, which does not set the mIsDirty bit because literally no selection change happened. That makes mFrame->SetSelectionRange not being called, which surprisingly... puts the cursor at the end, instead of the start? Why?

Flags: needinfo?(masayuki)
Flags: needinfo?(krosylight)
Flags: needinfo?(emilio)

Setting the cached selection as dirty when reframing (from UnbindFrame or so) makes sense to me fwiw. The DOM that's selected is going away after all. Does that work?

As for the start vs. end not sure, but we're recreating the anonymous dom so I wouldn't be surprised the handling gets a bit funky.

Flags: needinfo?(emilio) → needinfo?(krosylight)

(In reply to Kagami Rosylight [:saschanaz] (they/them) from comment #6)

That makes mFrame->SetSelectionRange not being called, which surprisingly... puts the cursor at the end, instead of the start? Why?

(In reply to Emilio Cobos Álvarez [:emilio] from comment #7)

As for the start vs. end not sure, but we're recreating the anonymous dom so I wouldn't be surprised the handling gets a bit funky.

As a literal/mechanical answer for "why does the cursor get placed at the end": I observed in a pernosco trace for the Google webcompat bug that it's because we reach this line:
https://searchfox.org/firefox-main/rev/ab5bf2401717970560597083d6ac72915bde2d89/editor/libeditor/EditorBase.cpp#3704

CollapseSelectionTo(EditorRawDOMPoint(&aTextNode, aString.Length()), error);

(That's creating a point at position aString.Length(), i.e. at the end of the string.)

That might not be precisely what you were asking, but I'm just noting it as a fact/breadcrumb in case it's useful.

(It looks like we reach that line in a "good" pernosco run, too, though, so maybe it's just a question of how we use the resulting selection... not sure.)

In any case, if it's useful, here's a pernosco trace with this bug's reduced testcase, showing calls to nsCaret::CaretPositionFor, and focused on the first such call where we're requesting the position at the end rather than the start (aSelection->mOffset is 4 rather than 0):
https://pernos.co/debug/NNSdaVADx6L57dQ7Xt-GqQ/index.html#f{m[BTWK,CwmS_,t[2Q,ReY_,f{e[BTWK,CwmK_,s{ac7hLQAAA,bDfg,uGvWnIw,oGwZmew___/

Yep, EditorBase (TextEditor instance) collapse Selection to the end when a value is set. Then, TextControlState will adjust Selection if it's required. To make it run, TextControlState::UnbindFromFrame needs to mark the cached selection dirty because it needs to set new independent Selection to the same as cached last selection before the previous frame is destroyed.

I wonder, we need to manage whether we need to restore Selection and whether the selection has been changed with different flags?

Flags: needinfo?(masayuki)

Thanks! For now let's see whether that works.

Assignee: nobody → krosylight
Flags: needinfo?(krosylight)
Severity: -- → S3
Priority: -- → P2

Set release status flags based on info from the regressing bug 1681615

Created web-platform-tests PR https://github.com/web-platform-tests/wpt/pull/55448 for changes under testing/web-platform/tests
Pushed by agoloman@mozilla.com: https://github.com/mozilla-firefox/firefox/commit/9c5a10468e47 https://hg.mozilla.org/integration/autoland/rev/f21ba25a8f44 Revert "Bug 1993351 - Set selection as dirty when unbinding from frame r=masayuki" for causing wpt failures @selection-after-whitespace-change.html.

Backed out for causing wpt failures @selection-after-whitespace-change.html.

Flags: needinfo?(krosylight)

We ignore Home in macOS, even for webdriver? Huh.

Upstream PR merged by moz-wptsync-bot

(In reply to Kagami Rosylight [:saschanaz] (they/them) from comment #18)

We ignore Home in macOS, even for webdriver? Huh.

Ah, IIRC, Home, End keys on macOS are initialized by NativeKeyBindings and we don't have shortcut key definition. Let me check it...

On Chrome it works though, is it a potential webcompat issue by itself? 🤔

Yeah, could be, but our strategy is that we should prefer the same behavior as the native apps on the platform. So, we should change when the users complain about the same behavior as the native apps.

Safari only scrolls it. Chrome does the same, but if it's single-line text control then it falls back to the line start/end navigation.

Flags: needinfo?(krosylight)
Created web-platform-tests PR https://github.com/web-platform-tests/wpt/pull/55486 for changes under testing/web-platform/tests
Status: NEW → RESOLVED
Closed: 11 days ago
Resolution: --- → FIXED
Target Milestone: --- → 146 Branch
Upstream PR merged by moz-wptsync-bot

The patch landed in nightly and beta is affected.
:saschanaz, is this bug important enough to require an uplift?

For more information, please visit BugBot documentation.

Flags: needinfo?(krosylight)

firefox-beta Uplift Approval Request

  • User impact if declined: Users of google.com get unexpected text cursor behavior
  • Code covered by automated testing: yes
  • Fix verified in Nightly: yes
  • Needs manual QE test: yes
  • Steps to reproduce for manual QE testing: See comment #0
  • Risk associated with taking this patch: low
  • Explanation of risk level: Limited behavior change in unbound frame case
  • String changes made/needed: No
  • Is Android affected?: yes
Attachment #9520868 - Flags: approval-mozilla-beta?
Flags: qe-verify+

firefox-esr140 Uplift Approval Request

  • User impact if declined: Unexpected text cursor behavior in google.com
  • Code covered by automated testing: yes
  • Fix verified in Nightly: yes
  • Needs manual QE test: yes
  • Steps to reproduce for manual QE testing: See comment #0
  • Risk associated with taking this patch: low
  • Explanation of risk level: Limited behavior change in unbound frame case
  • String changes made/needed: No
  • Is Android affected?: yes
Attachment #9520869 - Flags: approval-mozilla-esr140?
Flags: needinfo?(krosylight)
QA Whiteboard: [uplift][qa-ver-needed-c146/b145]

Verified fixed using Nightly 146.0a1 (20251019211057) on Windows 10, MacOS 15 and Ubuntu 24.04.

Attachment #9520868 - Flags: approval-mozilla-beta? → approval-mozilla-beta+

Verified fixed using Beta 145.0b5 (20251022093404)

QA Contact: pmagyari
Attachment #9520869 - Flags: approval-mozilla-esr140? → approval-mozilla-esr140+

Kagami, your patch does not apply cleanly to the ESR140 branch, could you update it please? Thanks

Flags: needinfo?(krosylight)
You need to log in before you can comment on or make changes to this bug.

Attachment

General

Created:
Updated:
Size: