1 | /* |
2 | * Copyright (C) 2004, 2005, 2006, 2007, 2008, 2009 Apple Inc. All rights reserved. |
3 | * |
4 | * Redistribution and use in source and binary forms, with or without |
5 | * modification, are permitted provided that the following conditions |
6 | * are met: |
7 | * 1. Redistributions of source code must retain the above copyright |
8 | * notice, this list of conditions and the following disclaimer. |
9 | * 2. Redistributions in binary form must reproduce the above copyright |
10 | * notice, this list of conditions and the following disclaimer in the |
11 | * documentation and/or other materials provided with the distribution. |
12 | * |
13 | * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY |
14 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
15 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
16 | * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR |
17 | * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
18 | * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
19 | * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
20 | * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY |
21 | * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
22 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
23 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
24 | */ |
25 | |
26 | #include "config.h" |
27 | #include "VisibleUnits.h" |
28 | |
29 | #include "Document.h" |
30 | #include "Editing.h" |
31 | #include "HTMLBRElement.h" |
32 | #include "HTMLElement.h" |
33 | #include "HTMLNames.h" |
34 | #include "InlineTextBox.h" |
35 | #include "NodeTraversal.h" |
36 | #include "RenderBlockFlow.h" |
37 | #include "RenderObject.h" |
38 | #include "RenderedPosition.h" |
39 | #include "Text.h" |
40 | #include "TextBoundaries.h" |
41 | #include "TextIterator.h" |
42 | #include "VisibleSelection.h" |
43 | #include <unicode/ubrk.h> |
44 | #include <wtf/text/TextBreakIterator.h> |
45 | |
46 | namespace WebCore { |
47 | |
48 | using namespace HTMLNames; |
49 | using namespace WTF::Unicode; |
50 | |
51 | static Node* previousLeafWithSameEditability(Node* node, EditableType editableType) |
52 | { |
53 | bool editable = hasEditableStyle(*node, editableType); |
54 | node = previousLeafNode(node); |
55 | while (node) { |
56 | if (editable == hasEditableStyle(*node, editableType)) |
57 | return node; |
58 | node = previousLeafNode(node); |
59 | } |
60 | return nullptr; |
61 | } |
62 | |
63 | static Node* nextLeafWithSameEditability(Node* node, EditableType editableType) |
64 | { |
65 | if (!node) |
66 | return nullptr; |
67 | |
68 | bool editable = hasEditableStyle(*node, editableType); |
69 | node = nextLeafNode(node); |
70 | while (node) { |
71 | if (editable == hasEditableStyle(*node, editableType)) |
72 | return node; |
73 | node = nextLeafNode(node); |
74 | } |
75 | return nullptr; |
76 | } |
77 | |
78 | // FIXME: consolidate with code in previousLinePosition. |
79 | static Position previousRootInlineBoxCandidatePosition(Node* node, const VisiblePosition& visiblePosition, EditableType editableType) |
80 | { |
81 | auto* highestRoot = highestEditableRoot(visiblePosition.deepEquivalent(), editableType); |
82 | Node* previousNode = previousLeafWithSameEditability(node, editableType); |
83 | |
84 | while (previousNode && (!previousNode->renderer() || inSameLine(firstPositionInOrBeforeNode(previousNode), visiblePosition))) |
85 | previousNode = previousLeafWithSameEditability(previousNode, editableType); |
86 | |
87 | while (previousNode && !previousNode->isShadowRoot()) { |
88 | if (highestEditableRoot(firstPositionInOrBeforeNode(previousNode), editableType) != highestRoot) |
89 | break; |
90 | |
91 | Position pos = previousNode->hasTagName(brTag) ? positionBeforeNode(previousNode) : |
92 | createLegacyEditingPosition(previousNode, caretMaxOffset(*previousNode)); |
93 | |
94 | if (pos.isCandidate()) |
95 | return pos; |
96 | |
97 | previousNode = previousLeafWithSameEditability(previousNode, editableType); |
98 | } |
99 | return Position(); |
100 | } |
101 | |
102 | static Position nextRootInlineBoxCandidatePosition(Node* node, const VisiblePosition& visiblePosition, EditableType editableType) |
103 | { |
104 | auto* highestRoot = highestEditableRoot(visiblePosition.deepEquivalent(), editableType); |
105 | Node* nextNode = nextLeafWithSameEditability(node, editableType); |
106 | while (nextNode && (!nextNode->renderer() || inSameLine(firstPositionInOrBeforeNode(nextNode), visiblePosition))) |
107 | nextNode = nextLeafWithSameEditability(nextNode, ContentIsEditable); |
108 | |
109 | while (nextNode && !nextNode->isShadowRoot()) { |
110 | if (highestEditableRoot(firstPositionInOrBeforeNode(nextNode), editableType) != highestRoot) |
111 | break; |
112 | |
113 | Position pos; |
114 | pos = createLegacyEditingPosition(nextNode, caretMinOffset(*nextNode)); |
115 | |
116 | if (pos.isCandidate()) |
117 | return pos; |
118 | |
119 | nextNode = nextLeafWithSameEditability(nextNode, editableType); |
120 | } |
121 | return Position(); |
122 | } |
123 | |
124 | class CachedLogicallyOrderedLeafBoxes { |
125 | public: |
126 | CachedLogicallyOrderedLeafBoxes(); |
127 | |
128 | const InlineBox* previousTextOrLineBreakBox(const RootInlineBox*, const InlineBox*); |
129 | const InlineBox* nextTextOrLineBreakBox(const RootInlineBox*, const InlineBox*); |
130 | |
131 | size_t size() const { return m_leafBoxes.size(); } |
132 | const InlineBox* firstBox() const { return m_leafBoxes[0]; } |
133 | |
134 | private: |
135 | const Vector<InlineBox*>& collectBoxes(const RootInlineBox*); |
136 | int boxIndexInLeaves(const InlineBox*) const; |
137 | |
138 | const RootInlineBox* m_rootInlineBox { nullptr }; |
139 | Vector<InlineBox*> m_leafBoxes; |
140 | }; |
141 | |
142 | CachedLogicallyOrderedLeafBoxes::CachedLogicallyOrderedLeafBoxes() |
143 | { |
144 | } |
145 | |
146 | const InlineBox* CachedLogicallyOrderedLeafBoxes::previousTextOrLineBreakBox(const RootInlineBox* root, const InlineBox* box) |
147 | { |
148 | if (!root) |
149 | return nullptr; |
150 | |
151 | collectBoxes(root); |
152 | |
153 | // If box is null, root is box's previous RootInlineBox, and previousBox is the last logical box in root. |
154 | int boxIndex = m_leafBoxes.size() - 1; |
155 | if (box) |
156 | boxIndex = boxIndexInLeaves(box) - 1; |
157 | |
158 | for (int i = boxIndex; i >= 0; --i) { |
159 | InlineBox* box = m_leafBoxes[i]; |
160 | if (box->isInlineTextBox() || box->renderer().isBR()) |
161 | return box; |
162 | } |
163 | |
164 | return nullptr; |
165 | } |
166 | |
167 | const InlineBox* CachedLogicallyOrderedLeafBoxes::nextTextOrLineBreakBox(const RootInlineBox* root, const InlineBox* box) |
168 | { |
169 | if (!root) |
170 | return nullptr; |
171 | |
172 | collectBoxes(root); |
173 | |
174 | // If box is null, root is box's next RootInlineBox, and nextBox is the first logical box in root. |
175 | // Otherwise, root is box's RootInlineBox, and nextBox is the next logical box in the same line. |
176 | size_t nextBoxIndex = 0; |
177 | if (box) |
178 | nextBoxIndex = boxIndexInLeaves(box) + 1; |
179 | |
180 | for (size_t i = nextBoxIndex; i < m_leafBoxes.size(); ++i) { |
181 | InlineBox* box = m_leafBoxes[i]; |
182 | if (box->isInlineTextBox() || box->renderer().isBR()) |
183 | return box; |
184 | } |
185 | |
186 | return nullptr; |
187 | } |
188 | |
189 | const Vector<InlineBox*>& CachedLogicallyOrderedLeafBoxes::collectBoxes(const RootInlineBox* root) |
190 | { |
191 | if (m_rootInlineBox != root) { |
192 | m_rootInlineBox = root; |
193 | m_leafBoxes.clear(); |
194 | root->collectLeafBoxesInLogicalOrder(m_leafBoxes); |
195 | } |
196 | return m_leafBoxes; |
197 | } |
198 | |
199 | int CachedLogicallyOrderedLeafBoxes::boxIndexInLeaves(const InlineBox* box) const |
200 | { |
201 | for (size_t i = 0; i < m_leafBoxes.size(); ++i) { |
202 | if (box == m_leafBoxes[i]) |
203 | return i; |
204 | } |
205 | return 0; |
206 | } |
207 | |
208 | static const InlineBox* logicallyPreviousBox(const VisiblePosition& visiblePosition, const InlineBox* textBox, |
209 | bool& previousBoxInDifferentLine, CachedLogicallyOrderedLeafBoxes& leafBoxes) |
210 | { |
211 | const InlineBox* startBox = textBox; |
212 | |
213 | const InlineBox* previousBox = leafBoxes.previousTextOrLineBreakBox(&startBox->root(), textBox); |
214 | if (previousBox) |
215 | return previousBox; |
216 | |
217 | previousBox = leafBoxes.previousTextOrLineBreakBox(startBox->root().prevRootBox(), 0); |
218 | if (previousBox) |
219 | return previousBox; |
220 | |
221 | while (1) { |
222 | Node* startNode = startBox->renderer().nonPseudoNode(); |
223 | if (!startNode) |
224 | break; |
225 | |
226 | Position position = previousRootInlineBoxCandidatePosition(startNode, visiblePosition, ContentIsEditable); |
227 | if (position.isNull()) |
228 | break; |
229 | |
230 | RenderedPosition renderedPosition(position, DOWNSTREAM); |
231 | RootInlineBox* previousRoot = renderedPosition.rootBox(); |
232 | if (!previousRoot) |
233 | break; |
234 | |
235 | previousBox = leafBoxes.previousTextOrLineBreakBox(previousRoot, 0); |
236 | if (previousBox) { |
237 | previousBoxInDifferentLine = true; |
238 | return previousBox; |
239 | } |
240 | |
241 | if (!leafBoxes.size()) |
242 | break; |
243 | startBox = leafBoxes.firstBox(); |
244 | } |
245 | return 0; |
246 | } |
247 | |
248 | |
249 | static const InlineBox* logicallyNextBox(const VisiblePosition& visiblePosition, const InlineBox* textBox, |
250 | bool& nextBoxInDifferentLine, CachedLogicallyOrderedLeafBoxes& leafBoxes) |
251 | { |
252 | const InlineBox* startBox = textBox; |
253 | |
254 | const InlineBox* nextBox = leafBoxes.nextTextOrLineBreakBox(&startBox->root(), textBox); |
255 | if (nextBox) |
256 | return nextBox; |
257 | |
258 | nextBox = leafBoxes.nextTextOrLineBreakBox(startBox->root().nextRootBox(), 0); |
259 | if (nextBox) |
260 | return nextBox; |
261 | |
262 | while (1) { |
263 | Node* startNode = startBox->renderer().nonPseudoNode(); |
264 | if (!startNode) |
265 | break; |
266 | |
267 | Position position = nextRootInlineBoxCandidatePosition(startNode, visiblePosition, ContentIsEditable); |
268 | if (position.isNull()) |
269 | break; |
270 | |
271 | RenderedPosition renderedPosition(position, DOWNSTREAM); |
272 | RootInlineBox* nextRoot = renderedPosition.rootBox(); |
273 | if (!nextRoot) |
274 | break; |
275 | |
276 | nextBox = leafBoxes.nextTextOrLineBreakBox(nextRoot, 0); |
277 | if (nextBox) { |
278 | nextBoxInDifferentLine = true; |
279 | return nextBox; |
280 | } |
281 | |
282 | if (!leafBoxes.size()) |
283 | break; |
284 | startBox = leafBoxes.firstBox(); |
285 | } |
286 | return 0; |
287 | } |
288 | |
289 | static UBreakIterator* wordBreakIteratorForMinOffsetBoundary(const VisiblePosition& visiblePosition, const InlineTextBox* textBox, |
290 | int& previousBoxLength, bool& previousBoxInDifferentLine, Vector<UChar, 1024>& string, CachedLogicallyOrderedLeafBoxes& leafBoxes) |
291 | { |
292 | previousBoxInDifferentLine = false; |
293 | |
294 | const InlineBox* previousBox = logicallyPreviousBox(visiblePosition, textBox, previousBoxInDifferentLine, leafBoxes); |
295 | while (previousBox && !is<InlineTextBox>(previousBox)) { |
296 | ASSERT(previousBox->renderer().isBR()); |
297 | previousBoxInDifferentLine = true; |
298 | previousBox = logicallyPreviousBox(visiblePosition, previousBox, previousBoxInDifferentLine, leafBoxes); |
299 | } |
300 | |
301 | string.clear(); |
302 | |
303 | if (is<InlineTextBox>(previousBox)) { |
304 | const auto& previousTextBox = downcast<InlineTextBox>(*previousBox); |
305 | previousBoxLength = previousTextBox.len(); |
306 | append(string, StringView(previousTextBox.renderer().text()).substring(previousTextBox.start(), previousBoxLength)); |
307 | } |
308 | append(string, StringView(textBox->renderer().text()).substring(textBox->start(), textBox->len())); |
309 | |
310 | return wordBreakIterator(StringView(string.data(), string.size())); |
311 | } |
312 | |
313 | static UBreakIterator* wordBreakIteratorForMaxOffsetBoundary(const VisiblePosition& visiblePosition, const InlineTextBox* textBox, |
314 | bool& nextBoxInDifferentLine, Vector<UChar, 1024>& string, CachedLogicallyOrderedLeafBoxes& leafBoxes) |
315 | { |
316 | nextBoxInDifferentLine = false; |
317 | |
318 | const InlineBox* nextBox = logicallyNextBox(visiblePosition, textBox, nextBoxInDifferentLine, leafBoxes); |
319 | while (nextBox && !is<InlineTextBox>(nextBox)) { |
320 | ASSERT(nextBox->renderer().isBR()); |
321 | nextBoxInDifferentLine = true; |
322 | nextBox = logicallyNextBox(visiblePosition, nextBox, nextBoxInDifferentLine, leafBoxes); |
323 | } |
324 | |
325 | string.clear(); |
326 | append(string, StringView(textBox->renderer().text()).substring(textBox->start(), textBox->len())); |
327 | if (is<InlineTextBox>(nextBox)) { |
328 | const auto& nextTextBox = downcast<InlineTextBox>(*nextBox); |
329 | append(string, StringView(nextTextBox.renderer().text()).substring(nextTextBox.start(), nextTextBox.len())); |
330 | } |
331 | |
332 | return wordBreakIterator(StringView(string.data(), string.size())); |
333 | } |
334 | |
335 | static bool isLogicalStartOfWord(UBreakIterator* iter, int position, bool hardLineBreak) |
336 | { |
337 | bool boundary = hardLineBreak ? true : ubrk_isBoundary(iter, position); |
338 | if (!boundary) |
339 | return false; |
340 | |
341 | ubrk_following(iter, position); |
342 | // isWordTextBreak returns true after moving across a word and false after moving across a punctuation/space. |
343 | return isWordTextBreak(iter); |
344 | } |
345 | |
346 | static bool islogicalEndOfWord(UBreakIterator* iter, int position, bool hardLineBreak) |
347 | { |
348 | bool boundary = ubrk_isBoundary(iter, position); |
349 | return (hardLineBreak || boundary) && isWordTextBreak(iter); |
350 | } |
351 | |
352 | enum CursorMovementDirection { MoveLeft, MoveRight }; |
353 | |
354 | static VisiblePosition visualWordPosition(const VisiblePosition& visiblePosition, CursorMovementDirection direction, |
355 | bool skipsSpaceWhenMovingRight) |
356 | { |
357 | if (visiblePosition.isNull()) |
358 | return VisiblePosition(); |
359 | |
360 | TextDirection blockDirection = directionOfEnclosingBlock(visiblePosition.deepEquivalent()); |
361 | InlineBox* previouslyVisitedBox = nullptr; |
362 | VisiblePosition current = visiblePosition; |
363 | Optional<VisiblePosition> previousPosition; |
364 | UBreakIterator* iter = nullptr; |
365 | |
366 | CachedLogicallyOrderedLeafBoxes leafBoxes; |
367 | Vector<UChar, 1024> string; |
368 | |
369 | while (1) { |
370 | VisiblePosition adjacentCharacterPosition = direction == MoveRight ? current.right(true) : current.left(true); |
371 | if (adjacentCharacterPosition == current || adjacentCharacterPosition.isNull()) |
372 | return VisiblePosition(); |
373 | // FIXME: This is a workaround for webkit.org/b/167138. |
374 | if (previousPosition && adjacentCharacterPosition == previousPosition.value()) |
375 | return VisiblePosition(); |
376 | |
377 | InlineBox* box; |
378 | int offsetInBox; |
379 | adjacentCharacterPosition.deepEquivalent().getInlineBoxAndOffset(UPSTREAM, box, offsetInBox); |
380 | |
381 | if (!box) |
382 | break; |
383 | if (!is<InlineTextBox>(*box)) { |
384 | current = adjacentCharacterPosition; |
385 | continue; |
386 | } |
387 | |
388 | InlineTextBox& textBox = downcast<InlineTextBox>(*box); |
389 | int previousBoxLength = 0; |
390 | bool previousBoxInDifferentLine = false; |
391 | bool nextBoxInDifferentLine = false; |
392 | bool movingIntoNewBox = previouslyVisitedBox != box; |
393 | |
394 | if (offsetInBox == box->caretMinOffset()) |
395 | iter = wordBreakIteratorForMinOffsetBoundary(adjacentCharacterPosition, &textBox, previousBoxLength, previousBoxInDifferentLine, string, leafBoxes); |
396 | else if (offsetInBox == box->caretMaxOffset()) |
397 | iter = wordBreakIteratorForMaxOffsetBoundary(adjacentCharacterPosition, &textBox, nextBoxInDifferentLine, string, leafBoxes); |
398 | else if (movingIntoNewBox) { |
399 | iter = wordBreakIterator(StringView(textBox.renderer().text()).substring(textBox.start(), textBox.len())); |
400 | previouslyVisitedBox = box; |
401 | } |
402 | |
403 | if (!iter) |
404 | break; |
405 | |
406 | ubrk_first(iter); |
407 | int offsetInIterator = offsetInBox - textBox.start() + previousBoxLength; |
408 | |
409 | bool isWordBreak; |
410 | bool boxHasSameDirectionalityAsBlock = box->direction() == blockDirection; |
411 | bool movingBackward = (direction == MoveLeft && box->direction() == TextDirection::LTR) || (direction == MoveRight && box->direction() == TextDirection::RTL); |
412 | if ((skipsSpaceWhenMovingRight && boxHasSameDirectionalityAsBlock) |
413 | || (!skipsSpaceWhenMovingRight && movingBackward)) { |
414 | bool logicalStartInRenderer = offsetInBox == static_cast<int>(textBox.start()) && previousBoxInDifferentLine; |
415 | isWordBreak = isLogicalStartOfWord(iter, offsetInIterator, logicalStartInRenderer); |
416 | if (isWordBreak && offsetInBox == box->caretMaxOffset() && nextBoxInDifferentLine) |
417 | isWordBreak = false; |
418 | } else { |
419 | bool logicalEndInRenderer = offsetInBox == static_cast<int>(textBox.start() + textBox.len()) && nextBoxInDifferentLine; |
420 | isWordBreak = islogicalEndOfWord(iter, offsetInIterator, logicalEndInRenderer); |
421 | if (isWordBreak && offsetInBox == box->caretMinOffset() && previousBoxInDifferentLine) |
422 | isWordBreak = false; |
423 | } |
424 | |
425 | if (isWordBreak) |
426 | return adjacentCharacterPosition; |
427 | |
428 | previousPosition = current; |
429 | current = adjacentCharacterPosition; |
430 | } |
431 | return VisiblePosition(); |
432 | } |
433 | |
434 | VisiblePosition leftWordPosition(const VisiblePosition& visiblePosition, bool skipsSpaceWhenMovingRight) |
435 | { |
436 | VisiblePosition leftWordBreak = visualWordPosition(visiblePosition, MoveLeft, skipsSpaceWhenMovingRight); |
437 | leftWordBreak = visiblePosition.honorEditingBoundaryAtOrBefore(leftWordBreak); |
438 | |
439 | // FIXME: How should we handle a non-editable position? |
440 | if (leftWordBreak.isNull() && isEditablePosition(visiblePosition.deepEquivalent())) { |
441 | TextDirection blockDirection = directionOfEnclosingBlock(visiblePosition.deepEquivalent()); |
442 | leftWordBreak = blockDirection == TextDirection::LTR ? startOfEditableContent(visiblePosition) : endOfEditableContent(visiblePosition); |
443 | } |
444 | return leftWordBreak; |
445 | } |
446 | |
447 | VisiblePosition rightWordPosition(const VisiblePosition& visiblePosition, bool skipsSpaceWhenMovingRight) |
448 | { |
449 | VisiblePosition rightWordBreak = visualWordPosition(visiblePosition, MoveRight, skipsSpaceWhenMovingRight); |
450 | rightWordBreak = visiblePosition.honorEditingBoundaryAtOrBefore(rightWordBreak); |
451 | |
452 | // FIXME: How should we handle a non-editable position? |
453 | if (rightWordBreak.isNull() && isEditablePosition(visiblePosition.deepEquivalent())) { |
454 | TextDirection blockDirection = directionOfEnclosingBlock(visiblePosition.deepEquivalent()); |
455 | rightWordBreak = blockDirection == TextDirection::LTR ? endOfEditableContent(visiblePosition) : startOfEditableContent(visiblePosition); |
456 | } |
457 | return rightWordBreak; |
458 | } |
459 | |
460 | |
461 | static void prepend(Vector<UChar, 1024>& buffer, StringView string) |
462 | { |
463 | unsigned oldSize = buffer.size(); |
464 | unsigned length = string.length(); |
465 | buffer.grow(oldSize + length); |
466 | memmove(buffer.data() + length, buffer.data(), oldSize * sizeof(UChar)); |
467 | for (unsigned i = 0; i < length; ++i) |
468 | buffer[i] = string[i]; |
469 | } |
470 | |
471 | static void prependRepeatedCharacter(Vector<UChar, 1024>& buffer, UChar character, unsigned count) |
472 | { |
473 | unsigned oldSize = buffer.size(); |
474 | buffer.grow(oldSize + count); |
475 | memmove(buffer.data() + count, buffer.data(), oldSize * sizeof(UChar)); |
476 | for (unsigned i = 0; i < count; ++i) |
477 | buffer[i] = character; |
478 | } |
479 | |
480 | static void appendRepeatedCharacter(Vector<UChar, 1024>& buffer, UChar character, unsigned count) |
481 | { |
482 | unsigned oldSize = buffer.size(); |
483 | buffer.grow(oldSize + count); |
484 | for (unsigned i = 0; i < count; ++i) |
485 | buffer[oldSize + i] = character; |
486 | } |
487 | |
488 | unsigned suffixLengthForRange(const Range& forwardsScanRange, Vector<UChar, 1024>& string) |
489 | { |
490 | unsigned suffixLength = 0; |
491 | TextIterator forwardsIterator(&forwardsScanRange); |
492 | while (!forwardsIterator.atEnd()) { |
493 | StringView text = forwardsIterator.text(); |
494 | unsigned i = endOfFirstWordBoundaryContext(text); |
495 | append(string, text.substring(0, i)); |
496 | suffixLength += i; |
497 | if (i < text.length()) |
498 | break; |
499 | forwardsIterator.advance(); |
500 | } |
501 | return suffixLength; |
502 | } |
503 | |
504 | unsigned prefixLengthForRange(const Range& backwardsScanRange, Vector<UChar, 1024>& string) |
505 | { |
506 | unsigned prefixLength = 0; |
507 | SimplifiedBackwardsTextIterator backwardsIterator(backwardsScanRange); |
508 | while (!backwardsIterator.atEnd()) { |
509 | StringView text = backwardsIterator.text(); |
510 | int i = startOfLastWordBoundaryContext(text); |
511 | prepend(string, text.substring(i)); |
512 | prefixLength += text.length() - i; |
513 | if (i > 0) |
514 | break; |
515 | backwardsIterator.advance(); |
516 | } |
517 | return prefixLength; |
518 | } |
519 | |
520 | unsigned backwardSearchForBoundaryWithTextIterator(SimplifiedBackwardsTextIterator& it, Vector<UChar, 1024>& string, unsigned suffixLength, BoundarySearchFunction searchFunction) |
521 | { |
522 | unsigned next = 0; |
523 | bool needMoreContext = false; |
524 | while (!it.atEnd()) { |
525 | bool inTextSecurityMode = it.node() && it.node()->renderer() && it.node()->renderer()->style().textSecurity() != TextSecurity::None; |
526 | // iterate to get chunks until the searchFunction returns a non-zero value. |
527 | if (!inTextSecurityMode) |
528 | prepend(string, it.text()); |
529 | else { |
530 | // Treat bullets used in the text security mode as regular characters when looking for boundaries |
531 | prependRepeatedCharacter(string, 'x', it.text().length()); |
532 | } |
533 | if (string.size() > suffixLength) { |
534 | next = searchFunction(StringView(string.data(), string.size()), string.size() - suffixLength, MayHaveMoreContext, needMoreContext); |
535 | if (next > 1) // FIXME: This is a work around for https://webkit.org/b/115070. We need to provide more contexts in general case. |
536 | break; |
537 | } |
538 | it.advance(); |
539 | } |
540 | if (needMoreContext && string.size() > suffixLength) { |
541 | // The last search returned the beginning of the buffer and asked for more context, |
542 | // but there is no earlier text. Force a search with what's available. |
543 | next = searchFunction(StringView(string.data(), string.size()), string.size() - suffixLength, DontHaveMoreContext, needMoreContext); |
544 | ASSERT(!needMoreContext); |
545 | } |
546 | |
547 | return next; |
548 | } |
549 | |
550 | unsigned forwardSearchForBoundaryWithTextIterator(TextIterator& it, Vector<UChar, 1024>& string, unsigned prefixLength, BoundarySearchFunction searchFunction) |
551 | { |
552 | unsigned next = 0; |
553 | bool needMoreContext = false; |
554 | while (!it.atEnd()) { |
555 | bool inTextSecurityMode = it.node() && it.node()->renderer() && it.node()->renderer()->style().textSecurity() != TextSecurity::None; |
556 | // Keep asking the iterator for chunks until the search function |
557 | // returns an end value not equal to the length of the string passed to it. |
558 | if (!inTextSecurityMode) |
559 | append(string, it.text()); |
560 | else { |
561 | // Treat bullets used in the text security mode as regular characters when looking for boundaries |
562 | appendRepeatedCharacter(string, 'x', it.text().length()); |
563 | } |
564 | if (string.size() > prefixLength) { |
565 | next = searchFunction(StringView(string.data(), string.size()), prefixLength, MayHaveMoreContext, needMoreContext); |
566 | if (next != string.size()) |
567 | break; |
568 | } |
569 | it.advance(); |
570 | } |
571 | if (needMoreContext && string.size() > prefixLength) { |
572 | // The last search returned the end of the buffer and asked for more context, |
573 | // but there is no further text. Force a search with what's available. |
574 | next = searchFunction(StringView(string.data(), string.size()), prefixLength, DontHaveMoreContext, needMoreContext); |
575 | ASSERT(!needMoreContext); |
576 | } |
577 | |
578 | return next; |
579 | } |
580 | |
581 | enum class NeedsContextAtParagraphStart { Yes, No }; |
582 | static VisiblePosition previousBoundary(const VisiblePosition& c, BoundarySearchFunction searchFunction, |
583 | NeedsContextAtParagraphStart needsContextAtParagraphStart = NeedsContextAtParagraphStart::No) |
584 | { |
585 | Position pos = c.deepEquivalent(); |
586 | Node* boundary = pos.parentEditingBoundary(); |
587 | if (!boundary) |
588 | return VisiblePosition(); |
589 | |
590 | Document& boundaryDocument = boundary->document(); |
591 | Position start = createLegacyEditingPosition(boundary, 0).parentAnchoredEquivalent(); |
592 | Position end = pos.parentAnchoredEquivalent(); |
593 | |
594 | if (start.isNull() || end.isNull()) |
595 | return VisiblePosition(); |
596 | |
597 | Ref<Range> searchRange = Range::create(boundaryDocument); |
598 | |
599 | Vector<UChar, 1024> string; |
600 | unsigned suffixLength = 0; |
601 | |
602 | if (needsContextAtParagraphStart == NeedsContextAtParagraphStart::Yes && isStartOfParagraph(c)) { |
603 | auto forwardsScanRange = boundaryDocument.createRange(); |
604 | auto endOfCurrentParagraph = endOfParagraph(c); |
605 | auto result = forwardsScanRange->setEnd(endOfCurrentParagraph.deepEquivalent()); |
606 | if (result.hasException()) |
607 | return { }; |
608 | result = forwardsScanRange->setStart(start); |
609 | if (result.hasException()) |
610 | return { }; |
611 | for (TextIterator forwardsIterator(forwardsScanRange.ptr()); !forwardsIterator.atEnd(); forwardsIterator.advance()) |
612 | append(string, forwardsIterator.text()); |
613 | suffixLength = string.size(); |
614 | } else if (requiresContextForWordBoundary(c.characterBefore())) { |
615 | auto forwardsScanRange = boundaryDocument.createRange(); |
616 | auto result = forwardsScanRange->setEndAfter(*boundary); |
617 | if (result.hasException()) |
618 | return { }; |
619 | result = forwardsScanRange->setStart(*end.deprecatedNode(), end.deprecatedEditingOffset()); |
620 | if (result.hasException()) |
621 | return { }; |
622 | suffixLength = suffixLengthForRange(forwardsScanRange, string); |
623 | } |
624 | |
625 | auto result = searchRange->setStart(*start.deprecatedNode(), start.deprecatedEditingOffset()); |
626 | if (result.hasException()) |
627 | return { }; |
628 | result = searchRange->setEnd(*end.deprecatedNode(), end.deprecatedEditingOffset()); |
629 | if (result.hasException()) |
630 | return { }; |
631 | |
632 | SimplifiedBackwardsTextIterator it(searchRange); |
633 | unsigned next = backwardSearchForBoundaryWithTextIterator(it, string, suffixLength, searchFunction); |
634 | |
635 | if (!next) |
636 | return VisiblePosition(it.atEnd() ? searchRange->startPosition() : pos, DOWNSTREAM); |
637 | |
638 | Node& node = it.atEnd() ? searchRange->startContainer() : it.range()->startContainer(); |
639 | if ((!suffixLength && node.isTextNode() && static_cast<int>(next) <= node.maxCharacterOffset()) || (node.renderer() && node.renderer()->isBR() && !next)) { |
640 | // The next variable contains a usable index into a text node |
641 | return VisiblePosition(createLegacyEditingPosition(&node, next), DOWNSTREAM); |
642 | } |
643 | |
644 | // Use the character iterator to translate the next value into a DOM position. |
645 | BackwardsCharacterIterator charIt(searchRange); |
646 | if (next < string.size() - suffixLength) |
647 | charIt.advance(string.size() - suffixLength - next); |
648 | // FIXME: charIt can get out of shadow host. |
649 | return VisiblePosition(charIt.range()->endPosition(), DOWNSTREAM); |
650 | } |
651 | |
652 | static VisiblePosition nextBoundary(const VisiblePosition& c, BoundarySearchFunction searchFunction) |
653 | { |
654 | Position pos = c.deepEquivalent(); |
655 | Node* boundary = pos.parentEditingBoundary(); |
656 | if (!boundary) |
657 | return VisiblePosition(); |
658 | |
659 | Document& boundaryDocument = boundary->document(); |
660 | Ref<Range> searchRange = boundaryDocument.createRange(); |
661 | Position start(pos.parentAnchoredEquivalent()); |
662 | |
663 | Vector<UChar, 1024> string; |
664 | unsigned prefixLength = 0; |
665 | |
666 | if (requiresContextForWordBoundary(c.characterAfter())) { |
667 | auto backwardsScanRange = boundaryDocument.createRange(); |
668 | if (start.deprecatedNode()) |
669 | backwardsScanRange->setEnd(*start.deprecatedNode(), start.deprecatedEditingOffset()); |
670 | prefixLength = prefixLengthForRange(backwardsScanRange, string); |
671 | } |
672 | |
673 | searchRange->selectNodeContents(*boundary); |
674 | if (start.deprecatedNode()) |
675 | searchRange->setStart(*start.deprecatedNode(), start.deprecatedEditingOffset()); |
676 | TextIterator it(searchRange.ptr(), TextIteratorEmitsCharactersBetweenAllVisiblePositions); |
677 | unsigned next = forwardSearchForBoundaryWithTextIterator(it, string, prefixLength, searchFunction); |
678 | |
679 | if (it.atEnd() && next == string.size()) |
680 | pos = searchRange->endPosition(); |
681 | else if (next > prefixLength) { |
682 | // Use the character iterator to translate the next value into a DOM position. |
683 | CharacterIterator charIt(searchRange, TextIteratorEmitsCharactersBetweenAllVisiblePositions); |
684 | charIt.advance(next - prefixLength - 1); |
685 | RefPtr<Range> characterRange = charIt.range(); |
686 | pos = characterRange->endPosition(); |
687 | |
688 | if (charIt.text()[0] == '\n') { |
689 | // FIXME: workaround for collapsed range (where only start position is correct) emitted for some emitted newlines (see rdar://5192593) |
690 | VisiblePosition visPos = VisiblePosition(pos); |
691 | if (visPos == VisiblePosition(characterRange->startPosition())) { |
692 | charIt.advance(1); |
693 | pos = charIt.range()->startPosition(); |
694 | } |
695 | } |
696 | } |
697 | |
698 | // generate VisiblePosition, use UPSTREAM affinity if possible |
699 | return VisiblePosition(pos, VP_UPSTREAM_IF_POSSIBLE); |
700 | } |
701 | |
702 | // --------- |
703 | |
704 | unsigned startWordBoundary(StringView text, unsigned offset, BoundarySearchContextAvailability mayHaveMoreContext, bool& needMoreContext) |
705 | { |
706 | ASSERT(offset); |
707 | if (mayHaveMoreContext && !startOfLastWordBoundaryContext(text.substring(0, offset))) { |
708 | needMoreContext = true; |
709 | return 0; |
710 | } |
711 | needMoreContext = false; |
712 | int start, end; |
713 | U16_BACK_1(text, 0, offset); |
714 | findWordBoundary(text, offset, &start, &end); |
715 | return start; |
716 | } |
717 | |
718 | VisiblePosition startOfWord(const VisiblePosition& c, EWordSide side) |
719 | { |
720 | // FIXME: This returns a null VP for c at the start of the document |
721 | // and side == LeftWordIfOnBoundary |
722 | VisiblePosition p = c; |
723 | if (side == RightWordIfOnBoundary) { |
724 | // at paragraph end, the startofWord is the current position |
725 | if (isEndOfParagraph(c)) |
726 | return c; |
727 | |
728 | p = c.next(); |
729 | if (p.isNull()) |
730 | return c; |
731 | } |
732 | return previousBoundary(p, startWordBoundary); |
733 | } |
734 | |
735 | unsigned endWordBoundary(StringView text, unsigned offset, BoundarySearchContextAvailability mayHaveMoreContext, bool& needMoreContext) |
736 | { |
737 | ASSERT(offset <= text.length()); |
738 | if (mayHaveMoreContext && endOfFirstWordBoundaryContext(text.substring(offset)) == text.length() - offset) { |
739 | needMoreContext = true; |
740 | return text.length(); |
741 | } |
742 | needMoreContext = false; |
743 | int end; |
744 | findEndWordBoundary(text, offset, &end); |
745 | return end; |
746 | } |
747 | |
748 | VisiblePosition endOfWord(const VisiblePosition& c, EWordSide side) |
749 | { |
750 | VisiblePosition p = c; |
751 | if (side == LeftWordIfOnBoundary) { |
752 | if (isStartOfParagraph(c)) |
753 | return c; |
754 | |
755 | p = c.previous(); |
756 | if (p.isNull()) |
757 | return c; |
758 | } else if (isEndOfParagraph(c)) |
759 | return c; |
760 | |
761 | return nextBoundary(p, endWordBoundary); |
762 | } |
763 | |
764 | static unsigned previousWordPositionBoundary(StringView text, unsigned offset, BoundarySearchContextAvailability mayHaveMoreContext, bool& needMoreContext) |
765 | { |
766 | if (mayHaveMoreContext && !startOfLastWordBoundaryContext(text.substring(0, offset))) { |
767 | needMoreContext = true; |
768 | return 0; |
769 | } |
770 | needMoreContext = false; |
771 | return findNextWordFromIndex(text, offset, false); |
772 | } |
773 | |
774 | VisiblePosition previousWordPosition(const VisiblePosition& position) |
775 | { |
776 | return position.honorEditingBoundaryAtOrBefore(previousBoundary(position, previousWordPositionBoundary)); |
777 | } |
778 | |
779 | static unsigned nextWordPositionBoundary(StringView text, unsigned offset, BoundarySearchContextAvailability mayHaveMoreContext, bool& needMoreContext) |
780 | { |
781 | if (mayHaveMoreContext && endOfFirstWordBoundaryContext(text.substring(offset)) == text.length() - offset) { |
782 | needMoreContext = true; |
783 | return text.length(); |
784 | } |
785 | needMoreContext = false; |
786 | return findNextWordFromIndex(text, offset, true); |
787 | } |
788 | |
789 | VisiblePosition nextWordPosition(const VisiblePosition& position) |
790 | { |
791 | return position.honorEditingBoundaryAtOrAfter(nextBoundary(position, nextWordPositionBoundary)); |
792 | } |
793 | |
794 | bool isStartOfWord(const VisiblePosition& p) |
795 | { |
796 | return p.isNotNull() && p == startOfWord(p, RightWordIfOnBoundary); |
797 | } |
798 | |
799 | // --------- |
800 | |
801 | enum LineEndpointComputationMode { UseLogicalOrdering, UseInlineBoxOrdering }; |
802 | static VisiblePosition startPositionForLine(const VisiblePosition& c, LineEndpointComputationMode mode) |
803 | { |
804 | if (c.isNull()) |
805 | return VisiblePosition(); |
806 | |
807 | RootInlineBox* rootBox = RenderedPosition(c).rootBox(); |
808 | if (!rootBox) { |
809 | // There are VisiblePositions at offset 0 in blocks without |
810 | // RootInlineBoxes, like empty editable blocks and bordered blocks. |
811 | Position p = c.deepEquivalent(); |
812 | if (p.deprecatedNode()->renderer() && p.deprecatedNode()->renderer()->isRenderBlock() && !p.deprecatedEditingOffset()) |
813 | return c; |
814 | |
815 | return VisiblePosition(); |
816 | } |
817 | |
818 | Node* startNode; |
819 | InlineBox* startBox; |
820 | if (mode == UseLogicalOrdering) { |
821 | startNode = rootBox->getLogicalStartBoxWithNode(startBox); |
822 | if (!startNode) |
823 | return VisiblePosition(); |
824 | } else { |
825 | // Generated content (e.g. list markers and CSS :before and :after pseudoelements) have no corresponding DOM element, |
826 | // and so cannot be represented by a VisiblePosition. Use whatever follows instead. |
827 | startBox = rootBox->firstLeafChild(); |
828 | while (true) { |
829 | if (!startBox) |
830 | return VisiblePosition(); |
831 | |
832 | startNode = startBox->renderer().nonPseudoNode(); |
833 | if (startNode) |
834 | break; |
835 | |
836 | startBox = startBox->nextLeafChild(); |
837 | } |
838 | } |
839 | |
840 | return is<Text>(*startNode) ? Position(downcast<Text>(startNode), downcast<InlineTextBox>(*startBox).start()) |
841 | : positionBeforeNode(startNode); |
842 | } |
843 | |
844 | static VisiblePosition startOfLine(const VisiblePosition& c, LineEndpointComputationMode mode, bool* reachedBoundary) |
845 | { |
846 | if (reachedBoundary) |
847 | *reachedBoundary = false; |
848 | // TODO: this is the current behavior that might need to be fixed. |
849 | // Please refer to https://bugs.webkit.org/show_bug.cgi?id=49107 for detail. |
850 | VisiblePosition visPos = startPositionForLine(c, mode); |
851 | |
852 | if (mode == UseLogicalOrdering) { |
853 | if (Node* editableRoot = highestEditableRoot(c.deepEquivalent())) { |
854 | if (!editableRoot->contains(visPos.deepEquivalent().containerNode())) { |
855 | VisiblePosition newPosition = firstPositionInNode(editableRoot); |
856 | if (reachedBoundary) |
857 | *reachedBoundary = c == newPosition; |
858 | return newPosition; |
859 | } |
860 | } |
861 | } |
862 | |
863 | return c.honorEditingBoundaryAtOrBefore(visPos, reachedBoundary); |
864 | } |
865 | |
866 | // FIXME: Rename this function to reflect the fact it ignores bidi levels. |
867 | VisiblePosition startOfLine(const VisiblePosition& currentPosition) |
868 | { |
869 | return startOfLine(currentPosition, UseInlineBoxOrdering, nullptr); |
870 | } |
871 | |
872 | VisiblePosition logicalStartOfLine(const VisiblePosition& currentPosition, bool* reachedBoundary) |
873 | { |
874 | return startOfLine(currentPosition, UseLogicalOrdering, reachedBoundary); |
875 | } |
876 | |
877 | static VisiblePosition endPositionForLine(const VisiblePosition& c, LineEndpointComputationMode mode) |
878 | { |
879 | if (c.isNull()) |
880 | return VisiblePosition(); |
881 | |
882 | RootInlineBox* rootBox = RenderedPosition(c).rootBox(); |
883 | if (!rootBox) { |
884 | // There are VisiblePositions at offset 0 in blocks without |
885 | // RootInlineBoxes, like empty editable blocks and bordered blocks. |
886 | Position p = c.deepEquivalent(); |
887 | if (p.deprecatedNode()->renderer() && p.deprecatedNode()->renderer()->isRenderBlock() && !p.deprecatedEditingOffset()) |
888 | return c; |
889 | return VisiblePosition(); |
890 | } |
891 | |
892 | Node* endNode; |
893 | InlineBox* endBox; |
894 | if (mode == UseLogicalOrdering) { |
895 | endNode = rootBox->getLogicalEndBoxWithNode(endBox); |
896 | if (!endNode) |
897 | return VisiblePosition(); |
898 | } else { |
899 | // Generated content (e.g. list markers and CSS :before and :after pseudoelements) have no corresponding DOM element, |
900 | // and so cannot be represented by a VisiblePosition. Use whatever precedes instead. |
901 | endBox = rootBox->lastLeafChild(); |
902 | while (true) { |
903 | if (!endBox) |
904 | return VisiblePosition(); |
905 | |
906 | endNode = endBox->renderer().nonPseudoNode(); |
907 | if (endNode) |
908 | break; |
909 | |
910 | endBox = endBox->prevLeafChild(); |
911 | } |
912 | } |
913 | |
914 | Position pos; |
915 | if (is<HTMLBRElement>(*endNode)) |
916 | pos = positionBeforeNode(endNode); |
917 | else if (is<InlineTextBox>(*endBox) && is<Text>(*endNode)) { |
918 | auto& endTextBox = downcast<InlineTextBox>(*endBox); |
919 | int endOffset = endTextBox.start(); |
920 | if (!endTextBox.isLineBreak()) |
921 | endOffset += endTextBox.len(); |
922 | pos = Position(downcast<Text>(endNode), endOffset); |
923 | } else |
924 | pos = positionAfterNode(endNode); |
925 | |
926 | return VisiblePosition(pos, VP_UPSTREAM_IF_POSSIBLE); |
927 | } |
928 | |
929 | static bool inSameLogicalLine(const VisiblePosition& a, const VisiblePosition& b) |
930 | { |
931 | return a.isNotNull() && logicalStartOfLine(a) == logicalStartOfLine(b); |
932 | } |
933 | |
934 | static VisiblePosition endOfLine(const VisiblePosition& c, LineEndpointComputationMode mode, bool* reachedBoundary) |
935 | { |
936 | if (reachedBoundary) |
937 | *reachedBoundary = false; |
938 | // TODO: this is the current behavior that might need to be fixed. |
939 | // Please refer to https://bugs.webkit.org/show_bug.cgi?id=49107 for detail. |
940 | VisiblePosition visPos = endPositionForLine(c, mode); |
941 | |
942 | if (mode == UseLogicalOrdering) { |
943 | // Make sure the end of line is at the same line as the given input position. For a wrapping line, the logical end |
944 | // position for the not-last-2-lines might incorrectly hand back the logical beginning of the next line. |
945 | // For example, <div contenteditable dir="rtl" style="line-break:before-white-space">abcdefg abcdefg abcdefg |
946 | // a abcdefg abcdefg abcdefg abcdefg abcdefg abcdefg abcdefg abcdefg abcdefg abcdefg </div> |
947 | // In this case, use the previous position of the computed logical end position. |
948 | if (!inSameLogicalLine(c, visPos)) |
949 | visPos = visPos.previous(); |
950 | |
951 | if (Node* editableRoot = highestEditableRoot(c.deepEquivalent())) { |
952 | if (!editableRoot->contains(visPos.deepEquivalent().containerNode())) { |
953 | VisiblePosition newPosition = lastPositionInNode(editableRoot); |
954 | if (reachedBoundary) |
955 | *reachedBoundary = c == newPosition; |
956 | return newPosition; |
957 | } |
958 | } |
959 | |
960 | return c.honorEditingBoundaryAtOrAfter(visPos, reachedBoundary); |
961 | } |
962 | |
963 | // Make sure the end of line is at the same line as the given input position. Else use the previous position to |
964 | // obtain end of line. This condition happens when the input position is before the space character at the end |
965 | // of a soft-wrapped non-editable line. In this scenario, endPositionForLine would incorrectly hand back a position |
966 | // in the next line instead. This fix is to account for the discrepancy between lines with webkit-line-break:after-white-space style |
967 | // versus lines without that style, which would break before a space by default. |
968 | if (!inSameLine(c, visPos)) { |
969 | visPos = c.previous(); |
970 | if (visPos.isNull()) |
971 | return VisiblePosition(); |
972 | visPos = endPositionForLine(visPos, UseInlineBoxOrdering); |
973 | } |
974 | |
975 | return c.honorEditingBoundaryAtOrAfter(visPos, reachedBoundary); |
976 | } |
977 | |
978 | // FIXME: Rename this function to reflect the fact it ignores bidi levels. |
979 | VisiblePosition endOfLine(const VisiblePosition& currentPosition) |
980 | { |
981 | return endOfLine(currentPosition, UseInlineBoxOrdering, nullptr); |
982 | } |
983 | |
984 | VisiblePosition logicalEndOfLine(const VisiblePosition& currentPosition, bool* reachedBoundary) |
985 | { |
986 | return endOfLine(currentPosition, UseLogicalOrdering, reachedBoundary); |
987 | } |
988 | |
989 | bool inSameLine(const VisiblePosition& a, const VisiblePosition& b) |
990 | { |
991 | return a.isNotNull() && startOfLine(a) == startOfLine(b); |
992 | } |
993 | |
994 | bool isStartOfLine(const VisiblePosition& p) |
995 | { |
996 | return p.isNotNull() && p == startOfLine(p); |
997 | } |
998 | |
999 | bool isEndOfLine(const VisiblePosition& p) |
1000 | { |
1001 | return p.isNotNull() && p == endOfLine(p); |
1002 | } |
1003 | |
1004 | bool isLogicalEndOfLine(const VisiblePosition &p) |
1005 | { |
1006 | return p.isNotNull() && p == logicalEndOfLine(p); |
1007 | } |
1008 | |
1009 | static inline IntPoint absoluteLineDirectionPointToLocalPointInBlock(RootInlineBox& root, int lineDirectionPoint) |
1010 | { |
1011 | RenderBlockFlow& containingBlock = root.blockFlow(); |
1012 | FloatPoint absoluteBlockPoint = containingBlock.localToAbsolute(FloatPoint()) - toFloatSize(containingBlock.scrollPosition()); |
1013 | |
1014 | if (containingBlock.isHorizontalWritingMode()) |
1015 | return IntPoint(lineDirectionPoint - absoluteBlockPoint.x(), root.blockDirectionPointInLine()); |
1016 | |
1017 | return IntPoint(root.blockDirectionPointInLine(), lineDirectionPoint - absoluteBlockPoint.y()); |
1018 | } |
1019 | |
1020 | static Element* rootEditableOrDocumentElement(Node& node, EditableType editableType) |
1021 | { |
1022 | if (hasEditableStyle(node, editableType)) |
1023 | return editableRootForPosition(firstPositionInOrBeforeNode(&node), editableType); |
1024 | return node.document().documentElement(); |
1025 | } |
1026 | |
1027 | VisiblePosition previousLinePosition(const VisiblePosition& visiblePosition, int lineDirectionPoint, EditableType editableType) |
1028 | { |
1029 | Position p = visiblePosition.deepEquivalent(); |
1030 | Node* node = p.deprecatedNode(); |
1031 | |
1032 | if (!node) |
1033 | return VisiblePosition(); |
1034 | |
1035 | node->document().updateLayoutIgnorePendingStylesheets(); |
1036 | |
1037 | RenderObject* renderer = node->renderer(); |
1038 | if (!renderer) |
1039 | return VisiblePosition(); |
1040 | |
1041 | RootInlineBox* root = nullptr; |
1042 | InlineBox* box; |
1043 | int ignoredCaretOffset; |
1044 | visiblePosition.getInlineBoxAndOffset(box, ignoredCaretOffset); |
1045 | if (box) { |
1046 | root = box->root().prevRootBox(); |
1047 | // We want to skip zero height boxes. |
1048 | // This could happen in case it is a TrailingFloatsRootInlineBox. |
1049 | if (!root || !root->logicalHeight() || !root->firstLeafChild()) |
1050 | root = nullptr; |
1051 | } |
1052 | |
1053 | if (!root) { |
1054 | Position position = previousRootInlineBoxCandidatePosition(node, visiblePosition, editableType); |
1055 | if (position.isNotNull()) { |
1056 | RenderedPosition renderedPosition(position); |
1057 | root = renderedPosition.rootBox(); |
1058 | if (!root) |
1059 | return position; |
1060 | } |
1061 | } |
1062 | |
1063 | if (root) { |
1064 | // FIXME: Can be wrong for multi-column layout and with transforms. |
1065 | IntPoint pointInLine = absoluteLineDirectionPointToLocalPointInBlock(*root, lineDirectionPoint); |
1066 | RenderObject& renderer = root->closestLeafChildForPoint(pointInLine, isEditablePosition(p))->renderer(); |
1067 | Node* node = renderer.node(); |
1068 | if (node && editingIgnoresContent(*node)) |
1069 | return positionInParentBeforeNode(node); |
1070 | return renderer.positionForPoint(pointInLine, nullptr); |
1071 | } |
1072 | |
1073 | // Could not find a previous line. This means we must already be on the first line. |
1074 | // Move to the start of the content in this block, which effectively moves us |
1075 | // to the start of the line we're on. |
1076 | Element* rootElement = rootEditableOrDocumentElement(*node, editableType); |
1077 | if (!rootElement) |
1078 | return VisiblePosition(); |
1079 | return VisiblePosition(firstPositionInNode(rootElement), DOWNSTREAM); |
1080 | } |
1081 | |
1082 | VisiblePosition nextLinePosition(const VisiblePosition& visiblePosition, int lineDirectionPoint, EditableType editableType) |
1083 | { |
1084 | Position p = visiblePosition.deepEquivalent(); |
1085 | Node* node = p.deprecatedNode(); |
1086 | |
1087 | if (!node) |
1088 | return VisiblePosition(); |
1089 | |
1090 | node->document().updateLayoutIgnorePendingStylesheets(); |
1091 | |
1092 | RenderObject* renderer = node->renderer(); |
1093 | if (!renderer) |
1094 | return VisiblePosition(); |
1095 | |
1096 | RootInlineBox* root = nullptr; |
1097 | InlineBox* box; |
1098 | int ignoredCaretOffset; |
1099 | visiblePosition.getInlineBoxAndOffset(box, ignoredCaretOffset); |
1100 | if (box) { |
1101 | root = box->root().nextRootBox(); |
1102 | // We want to skip zero height boxes. |
1103 | // This could happen in case it is a TrailingFloatsRootInlineBox. |
1104 | if (!root || !root->logicalHeight() || !root->firstLeafChild()) |
1105 | root = nullptr; |
1106 | } |
1107 | |
1108 | if (!root) { |
1109 | // FIXME: We need do the same in previousLinePosition. |
1110 | Node* child = node->traverseToChildAt(p.deprecatedEditingOffset()); |
1111 | node = child ? child : node->lastDescendant(); |
1112 | Position position = nextRootInlineBoxCandidatePosition(node, visiblePosition, editableType); |
1113 | if (position.isNotNull()) { |
1114 | RenderedPosition renderedPosition(position); |
1115 | root = renderedPosition.rootBox(); |
1116 | if (!root) |
1117 | return position; |
1118 | } |
1119 | } |
1120 | |
1121 | if (root) { |
1122 | // FIXME: Can be wrong for multi-column layout and with transforms. |
1123 | IntPoint pointInLine = absoluteLineDirectionPointToLocalPointInBlock(*root, lineDirectionPoint); |
1124 | RenderObject& renderer = root->closestLeafChildForPoint(pointInLine, isEditablePosition(p))->renderer(); |
1125 | Node* node = renderer.node(); |
1126 | if (node && editingIgnoresContent(*node)) |
1127 | return positionInParentBeforeNode(node); |
1128 | return renderer.positionForPoint(pointInLine, nullptr); |
1129 | } |
1130 | |
1131 | // Could not find a next line. This means we must already be on the last line. |
1132 | // Move to the end of the content in this block, which effectively moves us |
1133 | // to the end of the line we're on. |
1134 | Element* rootElement = rootEditableOrDocumentElement(*node, editableType); |
1135 | if (!rootElement) |
1136 | return VisiblePosition(); |
1137 | return VisiblePosition(lastPositionInNode(rootElement), DOWNSTREAM); |
1138 | } |
1139 | |
1140 | // --------- |
1141 | |
1142 | unsigned startSentenceBoundary(StringView text, unsigned, BoundarySearchContextAvailability, bool&) |
1143 | { |
1144 | // FIXME: The following function can return -1; we don't handle that. |
1145 | return ubrk_preceding(sentenceBreakIterator(text), text.length()); |
1146 | } |
1147 | |
1148 | VisiblePosition startOfSentence(const VisiblePosition& position) |
1149 | { |
1150 | return previousBoundary(position, startSentenceBoundary, NeedsContextAtParagraphStart::Yes); |
1151 | } |
1152 | |
1153 | unsigned endSentenceBoundary(StringView text, unsigned, BoundarySearchContextAvailability, bool&) |
1154 | { |
1155 | return ubrk_next(sentenceBreakIterator(text)); |
1156 | } |
1157 | |
1158 | VisiblePosition endOfSentence(const VisiblePosition& position) |
1159 | { |
1160 | // FIXME: This includes the space after the punctuation that marks the end of the sentence. |
1161 | return nextBoundary(position, endSentenceBoundary); |
1162 | } |
1163 | |
1164 | static unsigned previousSentencePositionBoundary(StringView text, unsigned, BoundarySearchContextAvailability, bool&) |
1165 | { |
1166 | // FIXME: This is identical to startSentenceBoundary. I'm pretty sure that's not right. |
1167 | // FIXME: The following function can return -1; we don't handle that. |
1168 | return ubrk_preceding(sentenceBreakIterator(text), text.length()); |
1169 | } |
1170 | |
1171 | VisiblePosition previousSentencePosition(const VisiblePosition& position) |
1172 | { |
1173 | return position.honorEditingBoundaryAtOrBefore(previousBoundary(position, previousSentencePositionBoundary)); |
1174 | } |
1175 | |
1176 | static unsigned nextSentencePositionBoundary(StringView text, unsigned, BoundarySearchContextAvailability, bool&) |
1177 | { |
1178 | // FIXME: This is identical to endSentenceBoundary. |
1179 | // That isn't right. This function needs to move to the equivalent position in the following sentence. |
1180 | return ubrk_following(sentenceBreakIterator(text), 0); |
1181 | } |
1182 | |
1183 | VisiblePosition nextSentencePosition(const VisiblePosition& position) |
1184 | { |
1185 | return position.honorEditingBoundaryAtOrAfter(nextBoundary(position, nextSentencePositionBoundary)); |
1186 | } |
1187 | |
1188 | Node* findStartOfParagraph(Node* startNode, Node* highestRoot, Node* startBlock, int& offset, Position::AnchorType& type, EditingBoundaryCrossingRule boundaryCrossingRule) |
1189 | { |
1190 | Node* node = startNode; |
1191 | Node* n = startNode; |
1192 | while (n) { |
1193 | #if ENABLE(USERSELECT_ALL) |
1194 | if (boundaryCrossingRule == CannotCrossEditingBoundary && !Position::nodeIsUserSelectAll(n) && n->hasEditableStyle() != startNode->hasEditableStyle()) |
1195 | #else |
1196 | if (boundaryCrossingRule == CannotCrossEditingBoundary && n->hasEditableStyle() != startNode->hasEditableStyle()) |
1197 | #endif |
1198 | break; |
1199 | if (boundaryCrossingRule == CanSkipOverEditingBoundary) { |
1200 | while (n && n->hasEditableStyle() != startNode->hasEditableStyle()) |
1201 | n = NodeTraversal::previousPostOrder(*n, startBlock); |
1202 | if (!n || !n->isDescendantOf(highestRoot)) |
1203 | break; |
1204 | } |
1205 | RenderObject* r = n->renderer(); |
1206 | if (!r) { |
1207 | n = NodeTraversal::previousPostOrder(*n, startBlock); |
1208 | continue; |
1209 | } |
1210 | const RenderStyle& style = r->style(); |
1211 | if (style.visibility() != Visibility::Visible) { |
1212 | n = NodeTraversal::previousPostOrder(*n, startBlock); |
1213 | continue; |
1214 | } |
1215 | |
1216 | if (r->isBR() || isBlock(n)) |
1217 | break; |
1218 | |
1219 | if (is<RenderText>(*r) && downcast<RenderText>(*r).hasRenderedText()) { |
1220 | ASSERT_WITH_SECURITY_IMPLICATION(is<Text>(*n)); |
1221 | type = Position::PositionIsOffsetInAnchor; |
1222 | if (style.preserveNewline()) { |
1223 | StringImpl& text = downcast<RenderText>(*r).text(); |
1224 | int i = text.length(); |
1225 | int o = offset; |
1226 | if (n == startNode && o < i) |
1227 | i = std::max(0, o); |
1228 | while (--i >= 0) { |
1229 | if (text[i] == '\n') { |
1230 | offset = i + 1; |
1231 | return n; |
1232 | } |
1233 | } |
1234 | } |
1235 | node = n; |
1236 | offset = 0; |
1237 | n = NodeTraversal::previousPostOrder(*n, startBlock); |
1238 | } else if (editingIgnoresContent(*n) || isRenderedTable(n)) { |
1239 | node = n; |
1240 | type = Position::PositionIsBeforeAnchor; |
1241 | n = n->previousSibling() ? n->previousSibling() : NodeTraversal::previousPostOrder(*n, startBlock); |
1242 | } else |
1243 | n = NodeTraversal::previousPostOrder(*n, startBlock); |
1244 | } |
1245 | |
1246 | return node; |
1247 | } |
1248 | |
1249 | Node* findEndOfParagraph(Node* startNode, Node* highestRoot, Node* stayInsideBlock, int& offset, Position::AnchorType& type, EditingBoundaryCrossingRule boundaryCrossingRule) |
1250 | { |
1251 | Node* node = startNode; |
1252 | Node* n = startNode; |
1253 | while (n) { |
1254 | #if ENABLE(USERSELECT_ALL) |
1255 | if (boundaryCrossingRule == CannotCrossEditingBoundary && !Position::nodeIsUserSelectAll(n) && n->hasEditableStyle() != startNode->hasEditableStyle()) |
1256 | #else |
1257 | if (boundaryCrossingRule == CannotCrossEditingBoundary && n->hasEditableStyle() != startNode->hasEditableStyle()) |
1258 | #endif |
1259 | break; |
1260 | if (boundaryCrossingRule == CanSkipOverEditingBoundary) { |
1261 | while (n && n->hasEditableStyle() != startNode->hasEditableStyle()) |
1262 | n = NodeTraversal::next(*n, stayInsideBlock); |
1263 | if (!n || !n->isDescendantOf(highestRoot)) |
1264 | break; |
1265 | } |
1266 | |
1267 | RenderObject* r = n->renderer(); |
1268 | if (!r) { |
1269 | n = NodeTraversal::next(*n, stayInsideBlock); |
1270 | continue; |
1271 | } |
1272 | const RenderStyle& style = r->style(); |
1273 | if (style.visibility() != Visibility::Visible) { |
1274 | n = NodeTraversal::next(*n, stayInsideBlock); |
1275 | continue; |
1276 | } |
1277 | |
1278 | // FIXME: This is wrong when startNode is a block. We should return a position after the block. |
1279 | if (r->isBR() || isBlock(n)) |
1280 | break; |
1281 | |
1282 | // FIXME: We avoid returning a position where the renderer can't accept the caret. |
1283 | if (is<RenderText>(*r) && downcast<RenderText>(*r).hasRenderedText()) { |
1284 | ASSERT_WITH_SECURITY_IMPLICATION(is<Text>(*n)); |
1285 | type = Position::PositionIsOffsetInAnchor; |
1286 | if (style.preserveNewline()) { |
1287 | StringImpl& text = downcast<RenderText>(*r).text(); |
1288 | int o = n == startNode ? offset : 0; |
1289 | int length = text.length(); |
1290 | for (int i = o; i < length; ++i) { |
1291 | if (text[i] == '\n') { |
1292 | offset = i; |
1293 | return n; |
1294 | } |
1295 | } |
1296 | } |
1297 | node = n; |
1298 | offset = r->caretMaxOffset(); |
1299 | n = NodeTraversal::next(*n, stayInsideBlock); |
1300 | } else if (editingIgnoresContent(*n) || isRenderedTable(n)) { |
1301 | node = n; |
1302 | type = Position::PositionIsAfterAnchor; |
1303 | n = NodeTraversal::nextSkippingChildren(*n, stayInsideBlock); |
1304 | } else |
1305 | n = NodeTraversal::next(*n, stayInsideBlock); |
1306 | } |
1307 | return node; |
1308 | } |
1309 | |
1310 | VisiblePosition startOfParagraph(const VisiblePosition& c, EditingBoundaryCrossingRule boundaryCrossingRule) |
1311 | { |
1312 | Position p = c.deepEquivalent(); |
1313 | auto* startNode = p.deprecatedNode(); |
1314 | |
1315 | if (!startNode) |
1316 | return VisiblePosition(); |
1317 | |
1318 | if (isRenderedAsNonInlineTableImageOrHR(startNode)) |
1319 | return positionBeforeNode(startNode); |
1320 | |
1321 | Node* startBlock = enclosingBlock(startNode); |
1322 | |
1323 | auto* highestRoot = highestEditableRoot(p); |
1324 | int offset = p.deprecatedEditingOffset(); |
1325 | Position::AnchorType type = p.anchorType(); |
1326 | |
1327 | auto* node = findStartOfParagraph(startNode, highestRoot, startBlock, offset, type, boundaryCrossingRule); |
1328 | |
1329 | if (is<Text>(node)) |
1330 | return VisiblePosition(Position(downcast<Text>(node), offset), DOWNSTREAM); |
1331 | |
1332 | if (type == Position::PositionIsOffsetInAnchor) { |
1333 | ASSERT(type == Position::PositionIsOffsetInAnchor || !offset); |
1334 | return VisiblePosition(Position(node, offset, type), DOWNSTREAM); |
1335 | } |
1336 | |
1337 | return VisiblePosition(Position(node, type), DOWNSTREAM); |
1338 | } |
1339 | |
1340 | VisiblePosition endOfParagraph(const VisiblePosition& c, EditingBoundaryCrossingRule boundaryCrossingRule) |
1341 | { |
1342 | if (c.isNull()) |
1343 | return VisiblePosition(); |
1344 | |
1345 | Position p = c.deepEquivalent(); |
1346 | auto* startNode = p.deprecatedNode(); |
1347 | |
1348 | if (isRenderedAsNonInlineTableImageOrHR(startNode)) |
1349 | return positionAfterNode(startNode); |
1350 | |
1351 | auto* startBlock = enclosingBlock(startNode); |
1352 | auto* stayInsideBlock = startBlock; |
1353 | |
1354 | auto* highestRoot = highestEditableRoot(p); |
1355 | int offset = p.deprecatedEditingOffset(); |
1356 | Position::AnchorType type = p.anchorType(); |
1357 | |
1358 | auto* node = findEndOfParagraph(startNode, highestRoot, stayInsideBlock, offset, type, boundaryCrossingRule); |
1359 | |
1360 | if (is<Text>(node)) |
1361 | return VisiblePosition(Position(downcast<Text>(node), offset), DOWNSTREAM); |
1362 | |
1363 | if (type == Position::PositionIsOffsetInAnchor) |
1364 | return VisiblePosition(Position(node, offset, type), DOWNSTREAM); |
1365 | |
1366 | return VisiblePosition(Position(node, type), DOWNSTREAM); |
1367 | } |
1368 | |
1369 | // FIXME: isStartOfParagraph(startOfNextParagraph(pos)) is not always true |
1370 | VisiblePosition startOfNextParagraph(const VisiblePosition& visiblePosition) |
1371 | { |
1372 | VisiblePosition paragraphEnd(endOfParagraph(visiblePosition, CanSkipOverEditingBoundary)); |
1373 | VisiblePosition afterParagraphEnd(paragraphEnd.next(CannotCrossEditingBoundary)); |
1374 | // The position after the last position in the last cell of a table |
1375 | // is not the start of the next paragraph. |
1376 | if (isFirstPositionAfterTable(afterParagraphEnd)) |
1377 | return afterParagraphEnd.next(CannotCrossEditingBoundary); |
1378 | return afterParagraphEnd; |
1379 | } |
1380 | |
1381 | bool inSameParagraph(const VisiblePosition& a, const VisiblePosition& b, EditingBoundaryCrossingRule boundaryCrossingRule) |
1382 | { |
1383 | return a.isNotNull() && startOfParagraph(a, boundaryCrossingRule) == startOfParagraph(b, boundaryCrossingRule); |
1384 | } |
1385 | |
1386 | bool isStartOfParagraph(const VisiblePosition& pos, EditingBoundaryCrossingRule boundaryCrossingRule) |
1387 | { |
1388 | return pos.isNotNull() && pos == startOfParagraph(pos, boundaryCrossingRule); |
1389 | } |
1390 | |
1391 | bool isEndOfParagraph(const VisiblePosition& pos, EditingBoundaryCrossingRule boundaryCrossingRule) |
1392 | { |
1393 | return pos.isNotNull() && pos == endOfParagraph(pos, boundaryCrossingRule); |
1394 | } |
1395 | |
1396 | bool isBlankParagraph(const VisiblePosition& position) |
1397 | { |
1398 | return isStartOfParagraph(position) && startOfParagraph(position.next()) != startOfParagraph(position); |
1399 | } |
1400 | |
1401 | VisiblePosition previousParagraphPosition(const VisiblePosition& p, int x) |
1402 | { |
1403 | VisiblePosition pos = p; |
1404 | do { |
1405 | VisiblePosition n = previousLinePosition(pos, x); |
1406 | if (n.isNull() || n == pos) |
1407 | break; |
1408 | pos = n; |
1409 | } while (inSameParagraph(p, pos)); |
1410 | return pos; |
1411 | } |
1412 | |
1413 | VisiblePosition nextParagraphPosition(const VisiblePosition& p, int x) |
1414 | { |
1415 | VisiblePosition pos = p; |
1416 | do { |
1417 | VisiblePosition n = nextLinePosition(pos, x); |
1418 | if (n.isNull() || n == pos) |
1419 | break; |
1420 | pos = n; |
1421 | } while (inSameParagraph(p, pos)); |
1422 | return pos; |
1423 | } |
1424 | |
1425 | // --------- |
1426 | |
1427 | VisiblePosition startOfBlock(const VisiblePosition& visiblePosition, EditingBoundaryCrossingRule rule) |
1428 | { |
1429 | Position position = visiblePosition.deepEquivalent(); |
1430 | Node* startBlock; |
1431 | if (!position.containerNode() || !(startBlock = enclosingBlock(position.containerNode(), rule))) |
1432 | return VisiblePosition(); |
1433 | return firstPositionInNode(startBlock); |
1434 | } |
1435 | |
1436 | VisiblePosition endOfBlock(const VisiblePosition& visiblePosition, EditingBoundaryCrossingRule rule) |
1437 | { |
1438 | Position position = visiblePosition.deepEquivalent(); |
1439 | Node* endBlock; |
1440 | if (!position.containerNode() || !(endBlock = enclosingBlock(position.containerNode(), rule))) |
1441 | return VisiblePosition(); |
1442 | return lastPositionInNode(endBlock); |
1443 | } |
1444 | |
1445 | bool inSameBlock(const VisiblePosition& a, const VisiblePosition& b) |
1446 | { |
1447 | return !a.isNull() && enclosingBlock(a.deepEquivalent().containerNode()) == enclosingBlock(b.deepEquivalent().containerNode()); |
1448 | } |
1449 | |
1450 | bool isStartOfBlock(const VisiblePosition& pos) |
1451 | { |
1452 | return pos.isNotNull() && pos == startOfBlock(pos, CanCrossEditingBoundary); |
1453 | } |
1454 | |
1455 | bool isEndOfBlock(const VisiblePosition& pos) |
1456 | { |
1457 | return pos.isNotNull() && pos == endOfBlock(pos, CanCrossEditingBoundary); |
1458 | } |
1459 | |
1460 | // --------- |
1461 | |
1462 | VisiblePosition startOfDocument(const Node* node) |
1463 | { |
1464 | if (!node || !node->document().documentElement()) |
1465 | return VisiblePosition(); |
1466 | |
1467 | // The canonicalization of the position at (documentElement, 0) can turn the visible |
1468 | // position to null, even when there's a valid candidate to be had, because the root HTML element |
1469 | // is not content editable. So we construct directly from the valid candidate. |
1470 | Position firstCandidate = nextCandidate(createLegacyEditingPosition(node->document().documentElement(), 0)); |
1471 | if (firstCandidate.isNull()) |
1472 | return VisiblePosition(); |
1473 | return VisiblePosition(firstCandidate); |
1474 | } |
1475 | |
1476 | VisiblePosition startOfDocument(const VisiblePosition& c) |
1477 | { |
1478 | return startOfDocument(c.deepEquivalent().deprecatedNode()); |
1479 | } |
1480 | |
1481 | VisiblePosition endOfDocument(const Node* node) |
1482 | { |
1483 | if (!node || !node->document().documentElement()) |
1484 | return VisiblePosition(); |
1485 | |
1486 | // (As above, in startOfDocument.) The canonicalization can reject valid visible positions |
1487 | // when descending from the root element, so we construct the visible position directly from a |
1488 | // valid candidate. |
1489 | Position lastPosition = createLegacyEditingPosition(node->document().documentElement(), node->document().documentElement()->countChildNodes()); |
1490 | Position lastCandidate = previousCandidate(lastPosition); |
1491 | if (lastCandidate.isNull()) |
1492 | return VisiblePosition(); |
1493 | return VisiblePosition(lastCandidate); |
1494 | } |
1495 | |
1496 | VisiblePosition endOfDocument(const VisiblePosition& c) |
1497 | { |
1498 | return endOfDocument(c.deepEquivalent().deprecatedNode()); |
1499 | } |
1500 | |
1501 | bool inSameDocument(const VisiblePosition& a, const VisiblePosition& b) |
1502 | { |
1503 | Position ap = a.deepEquivalent(); |
1504 | Node* an = ap.deprecatedNode(); |
1505 | if (!an) |
1506 | return false; |
1507 | Position bp = b.deepEquivalent(); |
1508 | Node* bn = bp.deprecatedNode(); |
1509 | if (an == bn) |
1510 | return true; |
1511 | |
1512 | return &an->document() == &bn->document(); |
1513 | } |
1514 | |
1515 | bool isStartOfDocument(const VisiblePosition& p) |
1516 | { |
1517 | return p.isNotNull() && p.previous(CanCrossEditingBoundary).isNull(); |
1518 | } |
1519 | |
1520 | bool isEndOfDocument(const VisiblePosition& p) |
1521 | { |
1522 | return p.isNotNull() && p.next(CanCrossEditingBoundary).isNull(); |
1523 | } |
1524 | |
1525 | // --------- |
1526 | |
1527 | VisiblePosition startOfEditableContent(const VisiblePosition& visiblePosition) |
1528 | { |
1529 | auto* highestRoot = highestEditableRoot(visiblePosition.deepEquivalent()); |
1530 | if (!highestRoot) |
1531 | return { }; |
1532 | |
1533 | return firstPositionInNode(highestRoot); |
1534 | } |
1535 | |
1536 | VisiblePosition endOfEditableContent(const VisiblePosition& visiblePosition) |
1537 | { |
1538 | auto* highestRoot = highestEditableRoot(visiblePosition.deepEquivalent()); |
1539 | if (!highestRoot) |
1540 | return { }; |
1541 | |
1542 | return lastPositionInNode(highestRoot); |
1543 | } |
1544 | |
1545 | bool isEndOfEditableOrNonEditableContent(const VisiblePosition& p) |
1546 | { |
1547 | return p.isNotNull() && p.next().isNull(); |
1548 | } |
1549 | |
1550 | VisiblePosition leftBoundaryOfLine(const VisiblePosition& c, TextDirection direction, bool* reachedBoundary) |
1551 | { |
1552 | return direction == TextDirection::LTR ? logicalStartOfLine(c, reachedBoundary) : logicalEndOfLine(c, reachedBoundary); |
1553 | } |
1554 | |
1555 | VisiblePosition rightBoundaryOfLine(const VisiblePosition& c, TextDirection direction, bool* reachedBoundary) |
1556 | { |
1557 | return direction == TextDirection::LTR ? logicalEndOfLine(c, reachedBoundary) : logicalStartOfLine(c, reachedBoundary); |
1558 | } |
1559 | |
1560 | static bool directionIsDownstream(SelectionDirection direction) |
1561 | { |
1562 | if (direction == DirectionBackward) |
1563 | return false; |
1564 | else if (direction == DirectionForward) |
1565 | return true; |
1566 | |
1567 | // FIXME: this code doesn't take into account the original direction of the element. |
1568 | // I'm not fixing this now because I'm afraid there is some code in UIKit relying on |
1569 | // this wrong behavior. |
1570 | return direction == DirectionRight; |
1571 | } |
1572 | |
1573 | bool atBoundaryOfGranularity(const VisiblePosition& vp, TextGranularity granularity, SelectionDirection direction) |
1574 | { |
1575 | if (granularity == CharacterGranularity) |
1576 | return true; |
1577 | |
1578 | VisiblePosition boundary; |
1579 | |
1580 | bool useDownstream = directionIsDownstream(direction); |
1581 | |
1582 | switch (granularity) { |
1583 | case WordGranularity: |
1584 | // visible_units claims erroneously that the start and the end |
1585 | // of a paragraph are the end and start of a word, respectively. |
1586 | if ((useDownstream && isStartOfParagraph(vp)) || (!useDownstream && isEndOfParagraph(vp))) |
1587 | return false; |
1588 | |
1589 | // Note that "Left" and "Right" in this context apparently mean "upstream/previous" and "downstream/next". |
1590 | boundary = useDownstream ? endOfWord(vp, LeftWordIfOnBoundary) : startOfWord(vp, RightWordIfOnBoundary); |
1591 | break; |
1592 | |
1593 | case SentenceGranularity: |
1594 | boundary = useDownstream ? endOfSentence(vp) : startOfSentence(vp); |
1595 | break; |
1596 | |
1597 | case LineGranularity: |
1598 | // Affinity has to be set to get right boundary of the line. |
1599 | boundary = vp; |
1600 | boundary.setAffinity(useDownstream ? VP_UPSTREAM_IF_POSSIBLE : DOWNSTREAM); |
1601 | boundary = useDownstream ? endOfLine(boundary) : startOfLine(boundary); |
1602 | break; |
1603 | |
1604 | case ParagraphGranularity: |
1605 | boundary = useDownstream ? endOfParagraph(vp) : startOfParagraph(vp); |
1606 | break; |
1607 | |
1608 | case DocumentGranularity: |
1609 | boundary = useDownstream ? endOfDocument(vp) : startOfDocument(vp); |
1610 | break; |
1611 | |
1612 | default: |
1613 | ASSERT_NOT_REACHED(); |
1614 | break; |
1615 | } |
1616 | |
1617 | return vp == boundary; |
1618 | } |
1619 | |
1620 | bool withinTextUnitOfGranularity(const VisiblePosition& vp, TextGranularity granularity, SelectionDirection direction) |
1621 | { |
1622 | if (granularity == CharacterGranularity || granularity == DocumentGranularity) |
1623 | return true; |
1624 | |
1625 | bool useDownstream = directionIsDownstream(direction); |
1626 | |
1627 | VisiblePosition prevBoundary; |
1628 | VisiblePosition nextBoundary; |
1629 | |
1630 | switch (granularity) { |
1631 | case WordGranularity: |
1632 | // Note that "Left" and "Right" in this context apparently mean "upstream/previous" and "downstream/next". |
1633 | prevBoundary = startOfWord(vp, (useDownstream ? RightWordIfOnBoundary : LeftWordIfOnBoundary)); |
1634 | nextBoundary = endOfWord(vp, (useDownstream ? RightWordIfOnBoundary : LeftWordIfOnBoundary)); |
1635 | |
1636 | // Workaround for <rdar://problem/7259611> Word boundary code on iPhone gives different results than desktop |
1637 | if (endOfWord(prevBoundary, RightWordIfOnBoundary) != nextBoundary) |
1638 | return false; |
1639 | |
1640 | break; |
1641 | |
1642 | case SentenceGranularity: |
1643 | prevBoundary = startOfSentence(vp); |
1644 | nextBoundary = endOfSentence(vp); |
1645 | break; |
1646 | |
1647 | case LineGranularity: |
1648 | prevBoundary = startOfLine(vp); |
1649 | nextBoundary = endOfLine(vp); |
1650 | |
1651 | if (prevBoundary == nextBoundary) { |
1652 | nextBoundary = nextLinePosition(nextBoundary, 0); |
1653 | nextBoundary.setAffinity(UPSTREAM); |
1654 | if (!inSameLine(prevBoundary, nextBoundary)) |
1655 | nextBoundary = vp.next(); |
1656 | } |
1657 | break; |
1658 | |
1659 | case ParagraphGranularity: |
1660 | prevBoundary = startOfParagraph(vp); |
1661 | nextBoundary = endOfParagraph(vp); |
1662 | break; |
1663 | |
1664 | default: |
1665 | ASSERT_NOT_REACHED(); |
1666 | break; |
1667 | } |
1668 | |
1669 | if (prevBoundary == nextBoundary) |
1670 | return false; |
1671 | |
1672 | if (vp == prevBoundary) |
1673 | return useDownstream; |
1674 | |
1675 | if (vp == nextBoundary) |
1676 | return !useDownstream; |
1677 | |
1678 | return (prevBoundary < vp && vp < nextBoundary); |
1679 | } |
1680 | |
1681 | static VisiblePosition nextCharacterBoundaryInDirection(const VisiblePosition& vp, SelectionDirection direction, EditingBoundaryCrossingRule rule) |
1682 | { |
1683 | return directionIsDownstream(direction) ? vp.next(rule) : vp.previous(rule); |
1684 | } |
1685 | |
1686 | static VisiblePosition nextWordBoundaryInDirection(const VisiblePosition& vp, SelectionDirection direction) |
1687 | { |
1688 | bool useDownstream = directionIsDownstream(direction); |
1689 | bool withinUnitOfGranularity = withinTextUnitOfGranularity(vp, WordGranularity, direction); |
1690 | VisiblePosition result; |
1691 | |
1692 | if (useDownstream) { |
1693 | if (withinUnitOfGranularity) |
1694 | result = endOfWord(vp, RightWordIfOnBoundary); |
1695 | else { |
1696 | VisiblePosition start = startOfWord(vp, RightWordIfOnBoundary); |
1697 | if (start > vp && start != endOfWord(start)) |
1698 | result = start; |
1699 | else { |
1700 | // Do same thing as backwards traveling below. |
1701 | start = vp; |
1702 | while (true) { |
1703 | result = startOfWord(nextWordPosition(start), RightWordIfOnBoundary); |
1704 | |
1705 | if (result == start) |
1706 | break; |
1707 | |
1708 | // We failed to find a word boundary. |
1709 | if (result.isNull() || result < start) |
1710 | return VisiblePosition(); |
1711 | |
1712 | // We consider successs also the case where start is before element and result is after. |
1713 | // This covers moving past images like words. |
1714 | if (result != endOfWord(result) |
1715 | || (result.deepEquivalent().anchorNode() == start.deepEquivalent().anchorNode() |
1716 | && result.deepEquivalent().anchorType() == Position::PositionIsAfterAnchor |
1717 | && start.deepEquivalent().anchorType() == Position::PositionIsBeforeAnchor)) |
1718 | break; |
1719 | |
1720 | start = result; |
1721 | } |
1722 | } |
1723 | } |
1724 | } else { |
1725 | if (withinUnitOfGranularity) |
1726 | result = startOfWord(vp, LeftWordIfOnBoundary); |
1727 | else { |
1728 | // This is complicated because: |
1729 | // When given "Blah blah.|", endOfWord is "Blah blah|.", and previousWordPosition is "Blah| blah." |
1730 | // When given "Blah blah. |", endOfWord is "Blah blah.| ", and previousWordPosition is "Blah |blah. ". |
1731 | VisiblePosition end = endOfWord(vp, LeftWordIfOnBoundary); |
1732 | if (end < vp && end != startOfWord(end)) |
1733 | result = end; |
1734 | else { |
1735 | end = vp; |
1736 | while (true) { |
1737 | result = endOfWord(previousWordPosition(end), RightWordIfOnBoundary); |
1738 | |
1739 | if (result == end) |
1740 | break; |
1741 | |
1742 | if (result.isNull() || result > end) |
1743 | return VisiblePosition(); |
1744 | |
1745 | if (result != startOfWord(result)) |
1746 | break; |
1747 | |
1748 | end = result; |
1749 | } |
1750 | } |
1751 | } |
1752 | } |
1753 | |
1754 | if (result == vp) |
1755 | return VisiblePosition(); |
1756 | |
1757 | return result; |
1758 | } |
1759 | |
1760 | static VisiblePosition nextSentenceBoundaryInDirection(const VisiblePosition& vp, SelectionDirection direction) |
1761 | { |
1762 | bool useDownstream = directionIsDownstream(direction); |
1763 | bool withinUnitOfGranularity = withinTextUnitOfGranularity(vp, SentenceGranularity, direction); |
1764 | VisiblePosition result; |
1765 | |
1766 | if (withinUnitOfGranularity) |
1767 | result = useDownstream ? endOfSentence(vp) : startOfSentence(vp); |
1768 | else { |
1769 | result = useDownstream ? nextSentencePosition(vp) : previousSentencePosition(vp); |
1770 | if (result.isNull() || result == vp) |
1771 | return VisiblePosition(); |
1772 | |
1773 | result = useDownstream ? startOfSentence(vp) : endOfSentence(vp); |
1774 | } |
1775 | |
1776 | if (result == vp) |
1777 | return VisiblePosition(); |
1778 | |
1779 | ASSERT(useDownstream ? (result > vp) : (result < vp)); |
1780 | |
1781 | return result; |
1782 | } |
1783 | |
1784 | static VisiblePosition nextLineBoundaryInDirection(const VisiblePosition& vp, SelectionDirection direction) |
1785 | { |
1786 | bool useDownstream = directionIsDownstream(direction); |
1787 | VisiblePosition result = vp; |
1788 | |
1789 | if (useDownstream) { |
1790 | result.setAffinity(DOWNSTREAM); |
1791 | result = isEndOfLine(result) ? startOfLine(nextLinePosition(result, result.lineDirectionPointForBlockDirectionNavigation())) : endOfLine(result); |
1792 | } else { |
1793 | result.setAffinity(VP_UPSTREAM_IF_POSSIBLE); |
1794 | result = isStartOfLine(result) ? endOfLine(previousLinePosition(result, result.lineDirectionPointForBlockDirectionNavigation())) : startOfLine(result); |
1795 | } |
1796 | |
1797 | return result; |
1798 | } |
1799 | |
1800 | static VisiblePosition nextParagraphBoundaryInDirection(const VisiblePosition& vp, SelectionDirection direction) |
1801 | { |
1802 | bool useDownstream = directionIsDownstream(direction); |
1803 | bool withinUnitOfGranularity = withinTextUnitOfGranularity(vp, ParagraphGranularity, direction); |
1804 | VisiblePosition result; |
1805 | |
1806 | if (!withinUnitOfGranularity) |
1807 | result = useDownstream ? startOfParagraph(nextParagraphPosition(vp, vp.lineDirectionPointForBlockDirectionNavigation())) : endOfParagraph(previousParagraphPosition(vp, vp.lineDirectionPointForBlockDirectionNavigation())); |
1808 | else |
1809 | result = useDownstream ? endOfParagraph(vp) : startOfParagraph(vp); |
1810 | |
1811 | return result; |
1812 | } |
1813 | |
1814 | static VisiblePosition nextDocumentBoundaryInDirection(const VisiblePosition& vp, SelectionDirection direction) |
1815 | { |
1816 | return directionIsDownstream(direction) ? endOfDocument(vp) : startOfDocument(vp); |
1817 | } |
1818 | |
1819 | VisiblePosition positionOfNextBoundaryOfGranularity(const VisiblePosition& vp, TextGranularity granularity, SelectionDirection direction) |
1820 | { |
1821 | switch (granularity) { |
1822 | case CharacterGranularity: |
1823 | return nextCharacterBoundaryInDirection(vp, direction, CanCrossEditingBoundary); |
1824 | case WordGranularity: |
1825 | return nextWordBoundaryInDirection(vp, direction); |
1826 | case SentenceGranularity: |
1827 | return nextSentenceBoundaryInDirection(vp, direction); |
1828 | case LineGranularity: |
1829 | return nextLineBoundaryInDirection(vp, direction); |
1830 | case ParagraphGranularity: |
1831 | return nextParagraphBoundaryInDirection(vp, direction); |
1832 | case DocumentGranularity: |
1833 | return nextDocumentBoundaryInDirection(vp, direction); |
1834 | default: |
1835 | ASSERT_NOT_REACHED(); |
1836 | return VisiblePosition(); |
1837 | } |
1838 | } |
1839 | |
1840 | RefPtr<Range> enclosingTextUnitOfGranularity(const VisiblePosition& vp, TextGranularity granularity, SelectionDirection direction) |
1841 | { |
1842 | // This is particularly inefficient. We could easily obtain the answer with the boundaries computed below. |
1843 | if (!withinTextUnitOfGranularity(vp, granularity, direction)) |
1844 | return nullptr; |
1845 | |
1846 | VisiblePosition prevBoundary; |
1847 | VisiblePosition nextBoundary; |
1848 | bool useDownstream = directionIsDownstream(direction); |
1849 | |
1850 | switch (granularity) { |
1851 | case CharacterGranularity: |
1852 | prevBoundary = vp; |
1853 | nextBoundary = prevBoundary.next(); |
1854 | break; |
1855 | |
1856 | case WordGranularity: |
1857 | // NB: "Left" and "Right" in this context apparently mean "upstream/previous" and "downstream/next". |
1858 | if (useDownstream) { |
1859 | prevBoundary = startOfWord(vp, RightWordIfOnBoundary); |
1860 | nextBoundary = endOfWord(vp, RightWordIfOnBoundary); |
1861 | } else { |
1862 | prevBoundary = startOfWord(vp, LeftWordIfOnBoundary); |
1863 | nextBoundary = endOfWord(vp, LeftWordIfOnBoundary); |
1864 | } |
1865 | break; |
1866 | |
1867 | case SentenceGranularity: |
1868 | prevBoundary = startOfSentence(vp); |
1869 | nextBoundary = endOfSentence(vp); |
1870 | break; |
1871 | |
1872 | case LineGranularity: |
1873 | prevBoundary = startOfLine(vp); |
1874 | nextBoundary = endOfLine(vp); |
1875 | |
1876 | if (prevBoundary == nextBoundary) { |
1877 | nextBoundary = nextLinePosition(nextBoundary, 0); |
1878 | nextBoundary.setAffinity(UPSTREAM); |
1879 | if (!inSameLine(prevBoundary, nextBoundary)) |
1880 | nextBoundary = vp.next(); |
1881 | } |
1882 | break; |
1883 | |
1884 | case ParagraphGranularity: |
1885 | prevBoundary = startOfParagraph(vp); |
1886 | nextBoundary = endOfParagraph(vp); |
1887 | break; |
1888 | |
1889 | case DocumentGranularity: |
1890 | prevBoundary = startOfDocument(vp); |
1891 | nextBoundary = endOfDocument(vp); |
1892 | break; |
1893 | |
1894 | default: |
1895 | ASSERT_NOT_REACHED(); |
1896 | return nullptr; |
1897 | } |
1898 | |
1899 | if (prevBoundary.isNull() || nextBoundary.isNull()) |
1900 | return nullptr; |
1901 | |
1902 | if (vp < prevBoundary || vp > nextBoundary) |
1903 | return nullptr; |
1904 | |
1905 | return Range::create(prevBoundary.deepEquivalent().deprecatedNode()->document(), prevBoundary, nextBoundary); |
1906 | } |
1907 | |
1908 | int distanceBetweenPositions(const VisiblePosition& vp, const VisiblePosition& other) |
1909 | { |
1910 | if (vp.isNull() || other.isNull()) |
1911 | return 0; |
1912 | |
1913 | bool thisIsStart = (vp < other); |
1914 | |
1915 | // Start must come first in the Range constructor. |
1916 | auto range = Range::create(vp.deepEquivalent().deprecatedNode()->document(), |
1917 | (thisIsStart ? vp : other), |
1918 | (thisIsStart ? other : vp)); |
1919 | int distance = TextIterator::rangeLength(range.ptr()); |
1920 | |
1921 | return (thisIsStart ? -distance : distance); |
1922 | } |
1923 | |
1924 | void charactersAroundPosition(const VisiblePosition& position, UChar32& oneAfter, UChar32& oneBefore, UChar32& twoBefore) |
1925 | { |
1926 | const int maxCharacters = 3; |
1927 | UChar32 characters[maxCharacters] = { 0 }; |
1928 | |
1929 | if (position.isNull() || isStartOfDocument(position)) |
1930 | return; |
1931 | |
1932 | VisiblePosition startPosition = position; |
1933 | VisiblePosition endPosition = position; |
1934 | |
1935 | VisiblePosition nextPosition = nextCharacterBoundaryInDirection(position, DirectionForward, CannotCrossEditingBoundary); |
1936 | if (nextPosition.isNotNull()) |
1937 | endPosition = nextPosition; |
1938 | |
1939 | VisiblePosition previousPosition = nextCharacterBoundaryInDirection(position, DirectionBackward, CannotCrossEditingBoundary); |
1940 | if (previousPosition.isNotNull()) { |
1941 | startPosition = previousPosition; |
1942 | previousPosition = nextCharacterBoundaryInDirection(previousPosition, DirectionBackward, CannotCrossEditingBoundary); |
1943 | if (previousPosition.isNotNull()) |
1944 | startPosition = previousPosition; |
1945 | } |
1946 | |
1947 | if (startPosition != endPosition) { |
1948 | String characterString = plainText(Range::create(position.deepEquivalent().anchorNode()->document(), startPosition, endPosition).ptr()).replace(noBreakSpace, ' '); |
1949 | for (int i = characterString.length() - 1, index = 0; i >= 0 && index < maxCharacters; --i) { |
1950 | if (!index && nextPosition.isNull()) |
1951 | index++; |
1952 | characters[index++] = characterString[i]; |
1953 | } |
1954 | } |
1955 | oneAfter = characters[0]; |
1956 | oneBefore = characters[1]; |
1957 | twoBefore = characters[2]; |
1958 | } |
1959 | |
1960 | RefPtr<Range> wordRangeFromPosition(const VisiblePosition& position) |
1961 | { |
1962 | // The selection could be in a non visible element and we don't have a VisiblePosition. |
1963 | if (position.isNull()) |
1964 | return nullptr; |
1965 | |
1966 | RefPtr<Range> range = enclosingTextUnitOfGranularity(position, WordGranularity, DirectionBackward); |
1967 | |
1968 | if (!range) { |
1969 | // We could be at the start of a word, try forward. |
1970 | range = enclosingTextUnitOfGranularity(position, WordGranularity, DirectionForward); |
1971 | } |
1972 | if (range) |
1973 | return range; |
1974 | |
1975 | VisiblePosition currentPosition = position; |
1976 | do { |
1977 | currentPosition = positionOfNextBoundaryOfGranularity(currentPosition, WordGranularity, DirectionBackward); |
1978 | } while (currentPosition.isNotNull() && !atBoundaryOfGranularity(currentPosition, WordGranularity, DirectionBackward)); |
1979 | |
1980 | // If the position is an empty paragraph and at the end of the document |
1981 | // the word iterator could not pass the paragraph boundary, therefore iterating to |
1982 | // the previous line is required. |
1983 | if (currentPosition.isNull() && isEndOfDocument(position)) { |
1984 | VisiblePosition previousLinePosition = positionOfNextBoundaryOfGranularity(position, LineGranularity, DirectionBackward); |
1985 | if (previousLinePosition.isNotNull()) { |
1986 | currentPosition = positionOfNextBoundaryOfGranularity(previousLinePosition, WordGranularity, DirectionBackward); |
1987 | if (currentPosition.isNull()) |
1988 | currentPosition = previousLinePosition; |
1989 | } |
1990 | } |
1991 | |
1992 | if (currentPosition.isNull()) |
1993 | currentPosition = positionOfNextBoundaryOfGranularity(position, WordGranularity, DirectionForward); |
1994 | |
1995 | if (currentPosition.isNotNull()) { |
1996 | range = Range::create(position.deepEquivalent().deprecatedNode()->document(), currentPosition, position); |
1997 | ASSERT(range); |
1998 | } |
1999 | return range; |
2000 | } |
2001 | |
2002 | VisiblePosition closestWordBoundaryForPosition(const VisiblePosition& position) |
2003 | { |
2004 | VisiblePosition result; |
2005 | |
2006 | // move the position at the end of the word |
2007 | if (atBoundaryOfGranularity(position, LineGranularity, DirectionForward)) { |
2008 | // Don't cross line boundaries. |
2009 | result = position; |
2010 | } else if (withinTextUnitOfGranularity(position, WordGranularity, DirectionForward)) { |
2011 | // The position lies within a word. |
2012 | RefPtr<Range> wordRange = enclosingTextUnitOfGranularity(position, WordGranularity, DirectionForward); |
2013 | |
2014 | result = wordRange->startPosition(); |
2015 | if (distanceBetweenPositions(position, result) > 1) |
2016 | result = wordRange->endPosition(); |
2017 | } else if (atBoundaryOfGranularity(position, WordGranularity, DirectionBackward)) { |
2018 | // The position is at the end of a word. |
2019 | result = position; |
2020 | } else { |
2021 | // The position is not within a word. |
2022 | // Go to the next boundary. |
2023 | result = positionOfNextBoundaryOfGranularity(position, WordGranularity, DirectionForward); |
2024 | |
2025 | // If there is no such boundary we go to the end of the element. |
2026 | if (result.isNull()) |
2027 | result = endOfEditableContent(position); |
2028 | } |
2029 | return result; |
2030 | } |
2031 | |
2032 | RefPtr<Range> rangeExpandedByCharactersInDirectionAtWordBoundary(const VisiblePosition& position, int numberOfCharactersToExpand, SelectionDirection direction) |
2033 | { |
2034 | Position start = position.deepEquivalent(); |
2035 | Position end = position.deepEquivalent(); |
2036 | for (int i = 0; i < numberOfCharactersToExpand; ++i) { |
2037 | if (direction == DirectionBackward) |
2038 | start = start.previous(Character); |
2039 | else |
2040 | end = end.next(Character); |
2041 | } |
2042 | |
2043 | if (direction == DirectionBackward && !atBoundaryOfGranularity(start, WordGranularity, DirectionBackward)) |
2044 | start = startOfWord(start).deepEquivalent(); |
2045 | if (direction == DirectionForward && !atBoundaryOfGranularity(end, WordGranularity, DirectionForward)) |
2046 | end = endOfWord(end).deepEquivalent(); |
2047 | |
2048 | return makeRange(start, end); |
2049 | } |
2050 | |
2051 | RefPtr<Range> rangeExpandedAroundPositionByCharacters(const VisiblePosition& position, int numberOfCharactersToExpand) |
2052 | { |
2053 | Position start = position.deepEquivalent(); |
2054 | Position end = position.deepEquivalent(); |
2055 | for (int i = 0; i < numberOfCharactersToExpand; ++i) { |
2056 | start = start.previous(Character); |
2057 | end = end.next(Character); |
2058 | } |
2059 | |
2060 | return makeRange(start, end); |
2061 | } |
2062 | |
2063 | } |
2064 | |