Diff
Added: trunk/LayoutTests/editing/text-iterator/sequential-collapsed-ranges-expected.txt (0 => 294955)
--- trunk/LayoutTests/editing/text-iterator/sequential-collapsed-ranges-expected.txt (rev 0)
+++ trunk/LayoutTests/editing/text-iterator/sequential-collapsed-ranges-expected.txt 2022-05-27 21:50:26 UTC (rev 294955)
@@ -0,0 +1,53 @@
+a b
+
+Testing first letter with narrow width...
+ Default behavior
+ 'a' (location 0 length 1)
+ IgnoresWhiteSpaceAtEndOfRun
+ 'a' (location 0 length 1)
+
+Testing first letter with wide width...
+ Default behavior
+ 'a' (location 0 length 1)
+ IgnoresWhiteSpaceAtEndOfRun
+ 'a' (location 0 length 1)
+
+Testing everything except last letter with narrow width...
+ Default behavior
+ 'a' (location 0 length 1)
+ ' ' (location 1 length 0)
+ IgnoresWhiteSpaceAtEndOfRun
+ 'a' (location 0 length 1)
+
+Testing everything except last letter with wide width...
+ Default behavior
+ 'a ' (location 0 length 2)
+ IgnoresWhiteSpaceAtEndOfRun
+ 'a ' (location 0 length 2)
+
+Testing everything with narrow width...
+ Default behavior
+ 'a' (location 0 length 1)
+ ' ' (location 1 length 0)
+ 'b' (location 2 length 1)
+ '\n' (location 3 length 0)
+ IgnoresWhiteSpaceAtEndOfRun
+ 'a' (location 0 length 1)
+ ' ' (location 1 length 0)
+ 'b' (location 1 length 1)
+ '\n' (location 3 length 0)
+
+Testing everything with wide width...
+ Default behavior
+ 'a ' (location 0 length 2)
+ 'b' (location 2 length 1)
+ '\n' (location 3 length 0)
+ IgnoresWhiteSpaceAtEndOfRun
+ 'a ' (location 0 length 2)
+ 'b' (location 2 length 1)
+ '\n' (location 3 length 0)
+
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
Added: trunk/LayoutTests/editing/text-iterator/sequential-collapsed-ranges.html (0 => 294955)
--- trunk/LayoutTests/editing/text-iterator/sequential-collapsed-ranges.html (rev 0)
+++ trunk/LayoutTests/editing/text-iterator/sequential-collapsed-ranges.html 2022-05-27 21:50:26 UTC (rev 294955)
@@ -0,0 +1,68 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script src=""
+</head>
+<body>
+<div id="test">a <span> </span> <span> </span> b</div>
+<div id="console"></div>
+<script>
+
+if (!window.internals)
+ testFailed("This test requires internals object");
+
+debug("");
+
+let container = document.getElementById("test");
+
+function test(description, rangeToSelect) {
+ let selection = window.getSelection();
+ selection.removeAllRanges();
+ selection.addRange(rangeToSelect);
+
+ debug(description);
+
+ [
+ [ ],
+ [ "IgnoresWhiteSpaceAtEndOfRun" ],
+ ].forEach((behaviors) => {
+ debug(" " + (behaviors.join("|") || "Default behavior"));
+ for (let {text, range} of internals.statesOfTextIterator(rangeToSelect, behaviors))
+ debug(` '${text.replace("\n", "\\n")}' (location ${internals.locationFromRange(container, range, behaviors)} length ${internals.lengthFromRange(container, range, behaviors)})`);
+ });
+
+ debug("");
+}
+
+let rangeOfFirstLetter = document.createRange();
+rangeOfFirstLetter.setStart(container.firstChild, 0);
+rangeOfFirstLetter.setEnd(container.firstChild, 1);
+
+container.style.width = "1ch";
+test("Testing first letter with narrow width...", rangeOfFirstLetter);
+
+container.style.width = "";
+test("Testing first letter with wide width...", rangeOfFirstLetter);
+
+let rangeUntilLastLetter = document.createRange();
+rangeUntilLastLetter.setStart(container.firstChild, 0);
+rangeUntilLastLetter.setEnd(container.lastChild, 4);
+
+container.style.width = "1ch";
+test("Testing everything except last letter with narrow width...", rangeUntilLastLetter);
+
+container.style.width = "";
+test("Testing everything except last letter with wide width...", rangeUntilLastLetter);
+
+let rangeOfNode = document.createRange();
+rangeOfNode.selectNode(container);
+
+container.style.width = "1ch";
+test("Testing everything with narrow width...", rangeOfNode);
+
+container.style.width = "";
+test("Testing everything with wide width...", rangeOfNode);
+
+</script>
+</body>
+</html>
Added: trunk/LayoutTests/editing/text-iterator/subrange-with-trailing-collapsed-whitespace-expected.txt (0 => 294955)
--- trunk/LayoutTests/editing/text-iterator/subrange-with-trailing-collapsed-whitespace-expected.txt (rev 0)
+++ trunk/LayoutTests/editing/text-iterator/subrange-with-trailing-collapsed-whitespace-expected.txt 2022-05-27 21:50:26 UTC (rev 294955)
@@ -0,0 +1,33 @@
+Lorem ipsum dolor sit amet, consectetur adipiscing elit.
+
+Testing first line...
+ Default behavior
+ 'Lorem ipsum' (location 0 length 12)
+ ' ' (location 12 length 0)
+ IgnoresWhiteSpaceAtEndOfRun
+ 'Lorem ipsum' (location 0 length 11)
+
+Testing second line...
+ Default behavior
+ 'dolor sit amet,' (location 12 length 16)
+ ' ' (location 28 length 0)
+ IgnoresWhiteSpaceAtEndOfRun
+ 'dolor sit amet,' (location 11 length 15)
+
+Testing third line...
+ Default behavior
+ 'consectetur' (location 28 length 12)
+ ' ' (location 40 length 0)
+ IgnoresWhiteSpaceAtEndOfRun
+ 'consectetur' (location 27 length 11)
+
+Testing fourth line...
+ Default behavior
+ 'adipiscing elit.' (location 40 length 16)
+ IgnoresWhiteSpaceAtEndOfRun
+ 'adipiscing elit.' (location 39 length 16)
+
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
Added: trunk/LayoutTests/editing/text-iterator/subrange-with-trailing-collapsed-whitespace.html (0 => 294955)
--- trunk/LayoutTests/editing/text-iterator/subrange-with-trailing-collapsed-whitespace.html (rev 0)
+++ trunk/LayoutTests/editing/text-iterator/subrange-with-trailing-collapsed-whitespace.html 2022-05-27 21:50:26 UTC (rev 294955)
@@ -0,0 +1,59 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script src=""
+</head>
+<body>
+<div id="test" style="width: 100px">Lorem ipsum dolor sit amet, consectetur adipiscing elit.</div>
+<div id="console"></div>
+<script>
+
+if (!window.internals)
+ testFailed("This test requires internals object");
+
+debug("");
+
+let container = document.getElementById("test");
+
+function test(description, rangeToSelect) {
+ let selection = window.getSelection();
+ selection.removeAllRanges();
+ selection.addRange(rangeToSelect);
+
+ debug(description);
+
+ [
+ [ ],
+ [ "IgnoresWhiteSpaceAtEndOfRun" ],
+ ].forEach((behaviors) => {
+ debug(" " + (behaviors.join("|") || "Default behavior"));
+ for (let {text, range} of internals.statesOfTextIterator(rangeToSelect, behaviors))
+ debug(` '${text}' (location ${internals.locationFromRange(container, range, behaviors)} length ${internals.lengthFromRange(container, range, behaviors)})`);
+ });
+
+ debug("");
+}
+
+let rangeOfFirstLine = document.createRange();
+rangeOfFirstLine.setStart(container.firstChild, 0);
+rangeOfFirstLine.setEnd(container.firstChild, 12);
+test("Testing first line...", rangeOfFirstLine);
+
+let rangeOfSecondLine = document.createRange();
+rangeOfSecondLine.setStart(container.firstChild, 12);
+rangeOfSecondLine.setEnd(container.firstChild, 28);
+test("Testing second line...", rangeOfSecondLine);
+
+let rangeOfThirdLine = document.createRange();
+rangeOfThirdLine.setStart(container.firstChild, 28);
+rangeOfThirdLine.setEnd(container.firstChild, 40);
+test("Testing third line...", rangeOfThirdLine);
+
+let rangeOfFourthLine = document.createRange();
+rangeOfFourthLine.setStart(container.firstChild, 40);
+rangeOfFourthLine.setEnd(container.firstChild, 56);
+test("Testing fourth line...", rangeOfFourthLine);
+
+</script>
+</body>
+</html>
Modified: trunk/Source/WebCore/editing/TextIterator.cpp (294954 => 294955)
--- trunk/Source/WebCore/editing/TextIterator.cpp 2022-05-27 21:37:00 UTC (rev 294954)
+++ trunk/Source/WebCore/editing/TextIterator.cpp 2022-05-27 21:50:26 UTC (rev 294955)
@@ -623,9 +623,20 @@
unsigned textRunStart = m_textRun->start();
unsigned runStart = std::max(textRunStart, start);
+ unsigned textRunEnd = textRunStart + m_textRun->length();
+ unsigned runEnd = std::min(textRunEnd, end);
+
+ // Determine what the next text run will be, but don't advance yet
+ auto nextTextRun = InlineIterator::nextTextBoxInLogicalOrder(m_textRun, m_textRunLogicalOrderCache);
+
// Check for collapsed space at the start of this run.
bool needSpace = m_lastTextNodeEndedWithCollapsedSpace || (m_textRun == firstTextRun && textRunStart == runStart && runStart);
if (needSpace && !renderer.style().isCollapsibleWhiteSpace(m_lastCharacter) && m_lastCharacter) {
+ if (runStart >= runEnd && m_behaviors.contains(TextIteratorBehavior::IgnoresWhiteSpaceAtEndOfRun)) {
+ m_textRun = nextTextRun;
+ continue;
+ }
+
if (m_lastTextNode == &textNode && runStart && rendererText[runStart - 1] == ' ') {
unsigned spaceRunStart = runStart - 1;
while (spaceRunStart && rendererText[spaceRunStart - 1] == ' ')
@@ -635,11 +646,6 @@
emitCharacter(' ', textNode, nullptr, runStart, runStart);
return;
}
- unsigned textRunEnd = textRunStart + m_textRun->length();
- unsigned runEnd = std::min(textRunEnd, end);
-
- // Determine what the next text run will be, but don't advance yet
- auto nextTextRun = InlineIterator::nextTextBoxInLogicalOrder(m_textRun, m_textRunLogicalOrderCache);
if (runStart < runEnd) {
auto isNewlineOrTab = [&](UChar character) {
Modified: trunk/Source/WebCore/editing/TextIteratorBehavior.h (294954 => 294955)
--- trunk/Source/WebCore/editing/TextIteratorBehavior.h 2022-05-27 21:37:00 UTC (rev 294954)
+++ trunk/Source/WebCore/editing/TextIteratorBehavior.h 2022-05-27 21:50:26 UTC (rev 294955)
@@ -61,6 +61,8 @@
TraversesFlatTree = 1 << 9,
EntersImageOverlays = 1 << 10,
+
+ IgnoresWhiteSpaceAtEndOfRun = 1 << 11,
};
using TextIteratorBehaviors = OptionSet<TextIteratorBehavior>;
Modified: trunk/Source/WebCore/testing/Internals.cpp (294954 => 294955)
--- trunk/Source/WebCore/testing/Internals.cpp 2022-05-27 21:37:00 UTC (rev 294954)
+++ trunk/Source/WebCore/testing/Internals.cpp 2022-05-27 21:50:26 UTC (rev 294955)
@@ -2184,19 +2184,29 @@
return { };
}
+static TextIteratorBehaviors toTextIteratorBehaviors(const Vector<String>& stringBehaviors)
+{
+ TextIteratorBehaviors behaviors;
+ for (const auto& stringBehavior : stringBehaviors) {
+ if (stringBehavior == "IgnoresWhiteSpaceAtEndOfRun"_s)
+ behaviors.add(TextIteratorBehavior::IgnoresWhiteSpaceAtEndOfRun);
+ }
+ return behaviors;
+}
+
RefPtr<Range> Internals::rangeFromLocationAndLength(Element& scope, unsigned rangeLocation, unsigned rangeLength)
{
return createLiveRange(resolveCharacterRange(makeRangeSelectingNodeContents(scope), { rangeLocation, rangeLength }));
}
-unsigned Internals::locationFromRange(Element& scope, const Range& range)
+unsigned Internals::locationFromRange(Element& scope, const Range& range, const Vector<String>& stringBehaviors)
{
- return clampTo<unsigned>(characterRange(makeBoundaryPointBeforeNodeContents(scope), makeSimpleRange(range)).location);
+ return clampTo<unsigned>(characterRange(makeBoundaryPointBeforeNodeContents(scope), makeSimpleRange(range), toTextIteratorBehaviors(stringBehaviors)).location);
}
-unsigned Internals::lengthFromRange(Element& scope, const Range& range)
+unsigned Internals::lengthFromRange(Element& scope, const Range& range, const Vector<String>& stringBehaviors)
{
- return clampTo<unsigned>(characterRange(makeBoundaryPointBeforeNodeContents(scope), makeSimpleRange(range)).length);
+ return clampTo<unsigned>(characterRange(makeBoundaryPointBeforeNodeContents(scope), makeSimpleRange(range), toTextIteratorBehaviors(stringBehaviors)).length);
}
String Internals::rangeAsText(const Range& liveRange)
@@ -2240,6 +2250,17 @@
return createLiveRange(findClosestPlainText(range, text, { }, targetOffset));
}
+Vector<Internals::TextIteratorState> Internals::statesOfTextIterator(const Range& liveRange, const Vector<String>& stringBehaviors)
+{
+ auto simpleRange = makeSimpleRange(liveRange);
+ simpleRange.start.document().updateLayout();
+
+ Vector<TextIteratorState> states;
+ for (TextIterator it(simpleRange, toTextIteratorBehaviors(stringBehaviors)); !it.atEnd(); it.advance())
+ states.append({ it.text().toString(), createLiveRange(it.range()) });
+ return states;
+}
+
#if !PLATFORM(MAC)
ExceptionOr<RefPtr<Range>> Internals::rangeForDictionaryLookupAtLocation(int, int)
{
Modified: trunk/Source/WebCore/testing/Internals.h (294954 => 294955)
--- trunk/Source/WebCore/testing/Internals.h 2022-05-27 21:37:00 UTC (rev 294954)
+++ trunk/Source/WebCore/testing/Internals.h 2022-05-27 21:50:26 UTC (rev 294955)
@@ -352,8 +352,8 @@
ExceptionOr<void> invalidateControlTints();
RefPtr<Range> rangeFromLocationAndLength(Element& scope, unsigned rangeLocation, unsigned rangeLength);
- unsigned locationFromRange(Element& scope, const Range&);
- unsigned lengthFromRange(Element& scope, const Range&);
+ unsigned locationFromRange(Element& scope, const Range&, const Vector<String>& behaviors = { });
+ unsigned lengthFromRange(Element& scope, const Range&, const Vector<String>& behaviors = { });
String rangeAsText(const Range&);
String rangeAsTextUsingBackwardsTextIterator(const Range&);
Ref<Range> subrange(Range&, unsigned rangeLocation, unsigned rangeLength);
@@ -360,6 +360,12 @@
ExceptionOr<RefPtr<Range>> rangeForDictionaryLookupAtLocation(int x, int y);
RefPtr<Range> rangeOfStringNearLocation(const Range&, const String&, unsigned);
+ struct TextIteratorState {
+ String text;
+ RefPtr<Range> range;
+ };
+ Vector<TextIteratorState> statesOfTextIterator(const Range&, const Vector<String>& behaviors = { });
+
ExceptionOr<void> setDelegatesScrolling(bool enabled);
ExceptionOr<uint64_t> lastSpellCheckRequestSequence();
Modified: trunk/Source/WebCore/testing/Internals.idl (294954 => 294955)
--- trunk/Source/WebCore/testing/Internals.idl 2022-05-27 21:37:00 UTC (rev 294954)
+++ trunk/Source/WebCore/testing/Internals.idl 2022-05-27 21:50:26 UTC (rev 294955)
@@ -291,6 +291,14 @@
[
ExportMacro=WEBCORE_TESTSUPPORT_EXPORT,
JSGenerateToJSObject,
+] dictionary TextIteratorState {
+ DOMString text;
+ Range range;
+};
+
+[
+ ExportMacro=WEBCORE_TESTSUPPORT_EXPORT,
+ JSGenerateToJSObject,
] dictionary ImageOverlayText {
required DOMString text;
required DOMPointReadOnly topLeft;
@@ -479,13 +487,14 @@
undefined scrollElementToRect(Element element, long x, long y, long w, long h);
Range? rangeFromLocationAndLength(Element scope, unsigned long rangeLocation, unsigned long rangeLength);
- unsigned long locationFromRange(Element scope, Range range);
- unsigned long lengthFromRange(Element scope, Range range);
+ unsigned long locationFromRange(Element scope, Range range, optional sequence<DOMString> behaviors = []);
+ unsigned long lengthFromRange(Element scope, Range range, optional sequence<DOMString> behaviors = []);
DOMString rangeAsText(Range range);
DOMString rangeAsTextUsingBackwardsTextIterator(Range range);
Range subrange(Range range, unsigned long rangeLocation, unsigned long rangeLength);
Range? rangeForDictionaryLookupAtLocation(long x, long y);
Range? rangeOfStringNearLocation(Range range, DOMString text, long targetOffset);
+ sequence<TextIteratorState> statesOfTextIterator(Range range, optional sequence<DOMString> behaviors = []);
undefined setDelegatesScrolling(boolean enabled);
Modified: trunk/Source/WebKit/WebProcess/WebPage/Cocoa/TextCheckingControllerProxy.mm (294954 => 294955)
--- trunk/Source/WebKit/WebProcess/WebPage/Cocoa/TextCheckingControllerProxy.mm 2022-05-27 21:37:00 UTC (rev 294954)
+++ trunk/Source/WebKit/WebProcess/WebPage/Cocoa/TextCheckingControllerProxy.mm 2022-05-27 21:50:26 UTC (rev 294955)
@@ -171,7 +171,9 @@
auto string = adoptNS([[NSMutableAttributedString alloc] init]);
- for (TextIterator it(*entireRange); !it.atEnd(); it.advance()) {
+ constexpr TextIteratorBehaviors behaviors { TextIteratorBehavior::IgnoresWhiteSpaceAtEndOfRun };
+
+ for (TextIterator it(*entireRange, behaviors); !it.atEnd(); it.advance()) {
if (!it.text().length())
continue;
[string appendAttributedString:adoptNS([[NSAttributedString alloc] initWithString:it.text().createNSStringWithoutCopying().get()]).get()];
@@ -178,8 +180,11 @@
auto range = it.range();
for (auto* marker : range.start.document().markers().markersInRange(range, DocumentMarker::PlatformTextChecking)) {
auto& data = ""
- auto subrange = resolveCharacterRange(range, { marker->startOffset(), marker->endOffset() - marker->startOffset() });
- [string addAttribute:data.key value:data.value range:characterRange(*entireRange, subrange)];
+ auto subrange = resolveCharacterRange(range, { marker->startOffset(), marker->endOffset() - marker->startOffset() }, behaviors);
+ auto attributeRange = characterRange(*entireRange, subrange, behaviors);
+ ASSERT(attributeRange.location + attributeRange.length <= [string length]);
+ if (attributeRange.location + attributeRange.length <= [string length])
+ [string addAttribute:data.key value:data.value range:WTFMove(attributeRange)];
}
}