Bug 1958639 - Ignore anonymous boxes and NAC for paragraph selection code. r=masayuki

The inner block of a scroller for example (::-moz-scrolled-content)
shouldn't be considered a line break.

Same for scrollbar nodes, we don't want to break on scrollbars (or
native anonymous content in general), since we can't position the caret
and such there anyways.

Differential Revision: https://phabricator.services.mozilla.com/D244519
This commit is contained in:
Emilio Cobos Álvarez
2025-04-06 23:58:48 +00:00
parent acde9612b2
commit 1008751221
3 changed files with 33 additions and 11 deletions

View File

@@ -31,13 +31,13 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=578771
ce.focus(); ce.focus();
synthesizeMouse(elem, 5, 5, {clickCount: 2 }); synthesizeMouse(elem, 5, 5, {clickCount: 2 });
ok(elem.selectionStart == 0 && elem.selectionEnd == 7, is(elem.selectionStart, 0, `${elemTag} selectionStart after double-click`);
" Double-clicking on another " + elemTag + " works correctly"); is(elem.selectionEnd, 7, `${elemTag} selectionEnd after double-click`);
ce.focus(); ce.focus();
synthesizeMouse(elem, 5, 5, {clickCount: 3 }); synthesizeMouse(elem, 5, 5, {clickCount: 3 });
ok(elem.selectionStart == 0 && elem.selectionEnd == 14, is(elem.selectionStart, 0, `${elemTag} selectionStart after triple-click`);
"Triple-clicking on another " + elemTag + " works correctly"); is(elem.selectionEnd, 14, `${elemTag} selectionEnd after triple-click`);
} }
// Avoid platform selection differences // Avoid platform selection differences
SimpleTest.waitForFocus(function() { SimpleTest.waitForFocus(function() {

View File

@@ -4615,6 +4615,10 @@ nsresult nsIFrame::GetDataForTableSelection(
} }
static bool IsEditingHost(const nsIFrame* aFrame) { static bool IsEditingHost(const nsIFrame* aFrame) {
if (aFrame->Style()->GetPseudoType() ==
PseudoStyleType::mozTextControlEditingRoot) {
return true;
}
nsIContent* content = aFrame->GetContent(); nsIContent* content = aFrame->GetContent();
return content && content->IsEditingHost(); return content && content->IsEditingHost();
} }
@@ -4666,8 +4670,6 @@ bool nsIFrame::ShouldHaveLineIfEmpty() const {
break; break;
case PseudoStyleType::scrolledContent: case PseudoStyleType::scrolledContent:
return GetParent()->ShouldHaveLineIfEmpty(); return GetParent()->ShouldHaveLineIfEmpty();
case PseudoStyleType::mozTextControlEditingRoot:
return true;
case PseudoStyleType::buttonContent: case PseudoStyleType::buttonContent:
// HTML quirk. // HTML quirk.
return GetContent()->IsHTMLElement(nsGkAtoms::input); return GetContent()->IsHTMLElement(nsGkAtoms::input);
@@ -5460,6 +5462,22 @@ struct MOZ_STACK_CLASS FrameContentRange {
int32_t end; int32_t end;
}; };
static bool IsRelevantBlockFrame(const nsIFrame* aFrame) {
if (!aFrame->IsBlockOutside()) {
return false;
}
if (aFrame->Style()->IsAnonBox()) {
// This helps skipping things like ::-moz-scrolled-content on an
// inline-block.
return false;
}
if (aFrame->GetContent()->IsInNativeAnonymousSubtree()) {
// This helps skipping things like scrollbar parts.
return false;
}
return true;
}
// Retrieve the content offsets of a frame // Retrieve the content offsets of a frame
static FrameContentRange GetRangeForFrame(const nsIFrame* aFrame) { static FrameContentRange GetRangeForFrame(const nsIFrame* aFrame) {
nsIContent* content = aFrame->GetContent(); nsIContent* content = aFrame->GetContent();
@@ -5486,7 +5504,7 @@ static FrameContentRange GetRangeForFrame(const nsIFrame* aFrame) {
MOZ_ASSERT(!content->IsBeingRemoved()); MOZ_ASSERT(!content->IsBeingRemoved());
nsIContent* parent = content->GetParent(); nsIContent* parent = content->GetParent();
if (aFrame->IsBlockOutside() || !parent) { if (IsRelevantBlockFrame(aFrame) || !parent) {
return FrameContentRange(content, 0, content->GetChildCount()); return FrameContentRange(content, 0, content->GetChildCount());
} }
@@ -9385,7 +9403,7 @@ static nsContentAndOffset FindLineBreakingFrame(nsIFrame* aFrame,
// the content of the inline frames they were created from. The // the content of the inline frames they were created from. The
// first/last child of such frames is the real block frame we're // first/last child of such frames is the real block frame we're
// looking for. // looking for.
if ((aFrame->IsBlockOutside() && if ((IsRelevantBlockFrame(aFrame) &&
!aFrame->HasAnyStateBits(NS_FRAME_PART_OF_IBSPLIT)) || !aFrame->HasAnyStateBits(NS_FRAME_PART_OF_IBSPLIT)) ||
aFrame->IsBrFrame()) { aFrame->IsBrFrame()) {
nsIContent* content = aFrame->GetContent(); nsIContent* content = aFrame->GetContent();
@@ -9427,7 +9445,7 @@ nsresult nsIFrame::PeekOffsetForParagraph(PeekOffsetStruct* aPos) {
nsIFrame* frame = this; nsIFrame* frame = this;
nsContentAndOffset blockFrameOrBR; nsContentAndOffset blockFrameOrBR;
blockFrameOrBR.mContent = nullptr; blockFrameOrBR.mContent = nullptr;
bool reachedLimit = frame->IsBlockOutside() || IsEditingHost(frame); bool reachedLimit = IsRelevantBlockFrame(frame) || IsEditingHost(frame);
auto traverse = [&aPos](nsIFrame* current) { auto traverse = [&aPos](nsIFrame* current) {
return aPos->mDirection == eDirPrevious ? current->GetPrevSibling() return aPos->mDirection == eDirPrevious ? current->GetPrevSibling()
@@ -9463,7 +9481,8 @@ nsresult nsIFrame::PeekOffsetForParagraph(PeekOffsetStruct* aPos) {
break; break;
} }
frame = parent; frame = parent;
reachedLimit = frame && (frame->IsBlockOutside() || IsEditingHost(frame)); reachedLimit =
frame && (IsRelevantBlockFrame(frame) || IsEditingHost(frame));
} }
if (reachedLimit) { // no "stop frame" found if (reachedLimit) { // no "stop frame" found

View File

@@ -5,6 +5,7 @@
<style> <style>
body { margin: 0; font: 16px/1 sans-serif; } body { margin: 0; font: 16px/1 sans-serif; }
code { display: inline-block } code { display: inline-block }
#d code { overflow: auto }
</style> </style>
<p id="a">Some <code>code</code> with <span>text</span> with <code>code</code> in it</p> <p id="a">Some <code>code</code> with <span>text</span> with <code>code</code> in it</p>
@@ -15,6 +16,8 @@ some <span>more</span> code <span>with</span> pre-formatted
whitespace <span>that</span> should be selected whitespace <span>that</span> should be selected
line <span>by</span> line</pre> line <span>by</span> line</pre>
<p id="d">Some <code>code</code> with <span>text</span> with <code>code</code> in <span>it</span></p>
<script> <script>
SimpleTest.waitForExplicitFinish(); SimpleTest.waitForExplicitFinish();
@@ -25,7 +28,7 @@ line <span>by</span> line</pre>
} }
SimpleTest.waitForFocus(function () { SimpleTest.waitForFocus(function () {
for (let child of document.querySelectorAll("#a span, #a code")) for (let child of document.querySelectorAll(":is(#a, #d) :is(span, code)"))
testTripleClick(child, "Some code with text with code in it"); testTripleClick(child, "Some code with text with code in it");
{ {