1/*
2 * Copyright (C) 2004, 2008, 2009, 2010, 2014-2015 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 "FrameSelection.h"
28
29#include "AXObjectCache.h"
30#include "CharacterData.h"
31#include "DeleteSelectionCommand.h"
32#include "Document.h"
33#include "Editing.h"
34#include "Editor.h"
35#include "EditorClient.h"
36#include "Element.h"
37#include "ElementIterator.h"
38#include "Event.h"
39#include "EventNames.h"
40#include "FloatQuad.h"
41#include "FocusController.h"
42#include "Frame.h"
43#include "FrameTree.h"
44#include "FrameView.h"
45#include "GraphicsContext.h"
46#include "HTMLBodyElement.h"
47#include "HTMLFormElement.h"
48#include "HTMLFrameElement.h"
49#include "HTMLIFrameElement.h"
50#include "HTMLNames.h"
51#include "HTMLSelectElement.h"
52#include "HitTestRequest.h"
53#include "HitTestResult.h"
54#include "InlineTextBox.h"
55#include "Page.h"
56#include "RenderLayer.h"
57#include "RenderText.h"
58#include "RenderTextControl.h"
59#include "RenderTheme.h"
60#include "RenderView.h"
61#include "RenderWidget.h"
62#include "RenderedPosition.h"
63#include "Settings.h"
64#include "SpatialNavigation.h"
65#include "StyleProperties.h"
66#include "TypingCommand.h"
67#include "VisibleUnits.h"
68#include <stdio.h>
69#include <wtf/text/CString.h>
70
71#if PLATFORM(IOS_FAMILY)
72#include "Chrome.h"
73#include "ChromeClient.h"
74#include "Color.h"
75#include "RenderObject.h"
76#include "RenderStyle.h"
77#endif
78
79namespace WebCore {
80
81using namespace HTMLNames;
82
83static inline LayoutUnit NoXPosForVerticalArrowNavigation()
84{
85 return LayoutUnit::min();
86}
87
88CaretBase::CaretBase(CaretVisibility visibility)
89 : m_caretRectNeedsUpdate(true)
90 , m_caretVisibility(visibility)
91{
92}
93
94DragCaretController::DragCaretController()
95 : CaretBase(Visible)
96{
97}
98
99bool DragCaretController::isContentRichlyEditable() const
100{
101 return isRichlyEditablePosition(m_position.deepEquivalent());
102}
103
104IntRect DragCaretController::caretRectInRootViewCoordinates() const
105{
106 if (!hasCaret())
107 return { };
108
109 if (auto* document = m_position.deepEquivalent().document()) {
110 if (auto* documentView = document->view())
111 return documentView->contentsToRootView(m_position.absoluteCaretBounds());
112 }
113
114 return { };
115}
116
117IntRect DragCaretController::editableElementRectInRootViewCoordinates() const
118{
119 if (!hasCaret())
120 return { };
121
122 RefPtr<ContainerNode> editableContainer;
123 if (auto* formControl = enclosingTextFormControl(m_position.deepEquivalent()))
124 editableContainer = formControl;
125 else
126 editableContainer = highestEditableRoot(m_position.deepEquivalent());
127
128 if (!editableContainer)
129 return { };
130
131 auto* renderer = editableContainer->renderer();
132 if (!renderer)
133 return { };
134
135 if (auto* view = editableContainer->document().view())
136 return view->contentsToRootView(renderer->absoluteBoundingBoxRect());
137
138 return { };
139}
140
141static inline bool shouldAlwaysUseDirectionalSelection(Frame* frame)
142{
143 return !frame || frame->editor().behavior().shouldConsiderSelectionAsDirectional();
144}
145
146FrameSelection::FrameSelection(Frame* frame)
147 : m_frame(frame)
148 , m_xPosForVerticalArrowNavigation(NoXPosForVerticalArrowNavigation())
149 , m_granularity(CharacterGranularity)
150#if ENABLE(TEXT_CARET)
151 , m_caretBlinkTimer(*this, &FrameSelection::caretBlinkTimerFired)
152#endif
153 , m_appearanceUpdateTimer(*this, &FrameSelection::appearanceUpdateTimerFired)
154 , m_caretInsidePositionFixed(false)
155 , m_absCaretBoundsDirty(true)
156 , m_caretPaint(true)
157 , m_isCaretBlinkingSuspended(false)
158 , m_focused(frame && frame->page() && frame->page()->focusController().focusedFrame() == frame)
159 , m_shouldShowBlockCursor(false)
160 , m_pendingSelectionUpdate(false)
161 , m_alwaysAlignCursorOnScrollWhenRevealingSelection(false)
162#if PLATFORM(IOS_FAMILY)
163 , m_updateAppearanceEnabled(false)
164 , m_caretBlinks(true)
165#endif
166{
167 if (shouldAlwaysUseDirectionalSelection(m_frame))
168 m_selection.setIsDirectional(true);
169}
170
171Element* FrameSelection::rootEditableElementOrDocumentElement() const
172{
173 Element* selectionRoot = m_selection.rootEditableElement();
174 return selectionRoot ? selectionRoot : m_frame->document()->documentElement();
175}
176
177void FrameSelection::moveTo(const VisiblePosition &pos, EUserTriggered userTriggered, CursorAlignOnScroll align)
178{
179 setSelection(VisibleSelection(pos.deepEquivalent(), pos.deepEquivalent(), pos.affinity(), m_selection.isDirectional()),
180 defaultSetSelectionOptions(userTriggered), AXTextStateChangeIntent(), align);
181}
182
183void FrameSelection::moveTo(const VisiblePosition &base, const VisiblePosition &extent, EUserTriggered userTriggered)
184{
185 const bool selectionHasDirection = true;
186 setSelection(VisibleSelection(base.deepEquivalent(), extent.deepEquivalent(), base.affinity(), selectionHasDirection), defaultSetSelectionOptions(userTriggered));
187}
188
189void FrameSelection::moveTo(const Position &pos, EAffinity affinity, EUserTriggered userTriggered)
190{
191 setSelection(VisibleSelection(pos, affinity, m_selection.isDirectional()), defaultSetSelectionOptions(userTriggered));
192}
193
194void FrameSelection::moveTo(const Range* range)
195{
196 VisibleSelection selection = range ? VisibleSelection(range->startPosition(), range->endPosition()) : VisibleSelection();
197 setSelection(selection);
198}
199
200void FrameSelection::moveTo(const Position &base, const Position &extent, EAffinity affinity, EUserTriggered userTriggered)
201{
202 const bool selectionHasDirection = true;
203 setSelection(VisibleSelection(base, extent, affinity, selectionHasDirection), defaultSetSelectionOptions(userTriggered));
204}
205
206void FrameSelection::moveWithoutValidationTo(const Position& base, const Position& extent, bool selectionHasDirection, bool shouldSetFocus, SelectionRevealMode revealMode, const AXTextStateChangeIntent& intent)
207{
208 VisibleSelection newSelection;
209 newSelection.setWithoutValidation(base, extent);
210 newSelection.setIsDirectional(selectionHasDirection);
211 AXTextStateChangeIntent newIntent = intent.type == AXTextStateChangeTypeUnknown ? AXTextStateChangeIntent(AXTextStateChangeTypeSelectionMove, AXTextSelection { AXTextSelectionDirectionDiscontiguous, AXTextSelectionGranularityUnknown, false }) : intent;
212 auto options = defaultSetSelectionOptions();
213 if (!shouldSetFocus)
214 options.add(DoNotSetFocus);
215 switch (revealMode) {
216 case SelectionRevealMode::DoNotReveal:
217 break;
218 case SelectionRevealMode::Reveal:
219 options.add(RevealSelection);
220 break;
221 case SelectionRevealMode::RevealUpToMainFrame:
222 options.add(RevealSelectionUpToMainFrame);
223 break;
224 }
225 setSelection(newSelection, options, newIntent);
226}
227
228void DragCaretController::setCaretPosition(const VisiblePosition& position)
229{
230 if (Node* node = m_position.deepEquivalent().deprecatedNode())
231 invalidateCaretRect(node);
232 m_position = position;
233 setCaretRectNeedsUpdate();
234 Document* document = nullptr;
235 if (Node* node = m_position.deepEquivalent().deprecatedNode()) {
236 invalidateCaretRect(node);
237 document = &node->document();
238 }
239 if (m_position.isNull() || m_position.isOrphan())
240 clearCaretRect();
241 else
242 updateCaretRect(document, m_position);
243}
244
245static void adjustEndpointsAtBidiBoundary(VisiblePosition& visibleBase, VisiblePosition& visibleExtent)
246{
247 RenderedPosition base(visibleBase);
248 RenderedPosition extent(visibleExtent);
249
250 if (base.isNull() || extent.isNull() || base.isEquivalent(extent))
251 return;
252
253 if (base.atLeftBoundaryOfBidiRun()) {
254 if (!extent.atRightBoundaryOfBidiRun(base.bidiLevelOnRight())
255 && base.isEquivalent(extent.leftBoundaryOfBidiRun(base.bidiLevelOnRight()))) {
256 visibleBase = base.positionAtLeftBoundaryOfBiDiRun();
257 return;
258 }
259 return;
260 }
261
262 if (base.atRightBoundaryOfBidiRun()) {
263 if (!extent.atLeftBoundaryOfBidiRun(base.bidiLevelOnLeft())
264 && base.isEquivalent(extent.rightBoundaryOfBidiRun(base.bidiLevelOnLeft()))) {
265 visibleBase = base.positionAtRightBoundaryOfBiDiRun();
266 return;
267 }
268 return;
269 }
270
271 if (extent.atLeftBoundaryOfBidiRun() && extent.isEquivalent(base.leftBoundaryOfBidiRun(extent.bidiLevelOnRight()))) {
272 visibleExtent = extent.positionAtLeftBoundaryOfBiDiRun();
273 return;
274 }
275
276 if (extent.atRightBoundaryOfBidiRun() && extent.isEquivalent(base.rightBoundaryOfBidiRun(extent.bidiLevelOnLeft()))) {
277 visibleExtent = extent.positionAtRightBoundaryOfBiDiRun();
278 return;
279 }
280}
281
282void FrameSelection::setSelectionByMouseIfDifferent(const VisibleSelection& passedNewSelection, TextGranularity granularity,
283 EndPointsAdjustmentMode endpointsAdjustmentMode)
284{
285 VisibleSelection newSelection = passedNewSelection;
286 bool isDirectional = shouldAlwaysUseDirectionalSelection(m_frame) || newSelection.isDirectional();
287
288 VisiblePosition base = m_originalBase.isNotNull() ? m_originalBase : newSelection.visibleBase();
289 VisiblePosition newBase = base;
290 VisiblePosition extent = newSelection.visibleExtent();
291 VisiblePosition newExtent = extent;
292 if (endpointsAdjustmentMode == AdjustEndpointsAtBidiBoundary)
293 adjustEndpointsAtBidiBoundary(newBase, newExtent);
294
295 if (newBase != base || newExtent != extent) {
296 m_originalBase = base;
297 newSelection.setBase(newBase);
298 newSelection.setExtent(newExtent);
299 } else if (m_originalBase.isNotNull()) {
300 if (m_selection.base() == newSelection.base())
301 newSelection.setBase(m_originalBase);
302 m_originalBase.clear();
303 }
304
305 newSelection.setIsDirectional(isDirectional); // Adjusting base and extent will make newSelection always directional
306 if (m_selection == newSelection || !shouldChangeSelection(newSelection))
307 return;
308
309
310 AXTextStateChangeIntent intent;
311 if (AXObjectCache::accessibilityEnabled() && newSelection.isCaret())
312 intent = AXTextStateChangeIntent(AXTextStateChangeTypeSelectionMove, AXTextSelection { AXTextSelectionDirectionDiscontiguous, AXTextSelectionGranularityUnknown, false });
313 else
314 intent = AXTextStateChangeIntent();
315 setSelection(newSelection, defaultSetSelectionOptions() | FireSelectEvent, intent, AlignCursorOnScrollIfNeeded, granularity);
316}
317
318bool FrameSelection::setSelectionWithoutUpdatingAppearance(const VisibleSelection& newSelectionPossiblyWithoutDirection, OptionSet<SetSelectionOption> options, CursorAlignOnScroll align, TextGranularity granularity)
319{
320 bool closeTyping = options.contains(CloseTyping);
321 bool shouldClearTypingStyle = options.contains(ClearTypingStyle);
322
323 VisibleSelection newSelection = newSelectionPossiblyWithoutDirection;
324 if (shouldAlwaysUseDirectionalSelection(m_frame))
325 newSelection.setIsDirectional(true);
326
327 if (!m_frame) {
328 m_selection = newSelection;
329 return false;
330 }
331
332 // <http://bugs.webkit.org/show_bug.cgi?id=23464>: Infinite recursion at FrameSelection::setSelection
333 // if document->frame() == m_frame we can get into an infinite loop
334 if (Document* newSelectionDocument = newSelection.base().document()) {
335 if (RefPtr<Frame> newSelectionFrame = newSelectionDocument->frame()) {
336 if (newSelectionFrame != m_frame && newSelectionDocument != m_frame->document()) {
337 newSelectionFrame->selection().setSelection(newSelection, options, AXTextStateChangeIntent(), align, granularity);
338 // It's possible that during the above set selection, this FrameSelection has been modified by
339 // selectFrameElementInParentIfFullySelected, but that the selection is no longer valid since
340 // the frame is about to be destroyed. If this is the case, clear our selection.
341 if (newSelectionFrame->hasOneRef() && m_selection.isNoneOrOrphaned())
342 clear();
343 return false;
344 }
345 }
346 }
347
348 m_granularity = granularity;
349
350 if (closeTyping)
351 TypingCommand::closeTyping(m_frame);
352
353 if (shouldClearTypingStyle)
354 clearTypingStyle();
355
356 VisibleSelection oldSelection = m_selection;
357 bool didMutateSelection = oldSelection != newSelection;
358 if (didMutateSelection)
359 m_frame->editor().selectionWillChange();
360
361 m_selection = newSelection;
362
363 // Selection offsets should increase when LF is inserted before the caret in InsertLineBreakCommand. See <https://webkit.org/b/56061>.
364 if (HTMLTextFormControlElement* textControl = enclosingTextFormControl(newSelection.start()))
365 textControl->selectionChanged(options.contains(FireSelectEvent));
366
367 if (!didMutateSelection)
368 return false;
369
370 setCaretRectNeedsUpdate();
371
372 if (!newSelection.isNone() && !(options & DoNotSetFocus))
373 setFocusedElementIfNeeded();
374
375 // Always clear the x position used for vertical arrow navigation.
376 // It will be restored by the vertical arrow navigation code if necessary.
377 m_xPosForVerticalArrowNavigation = NoXPosForVerticalArrowNavigation();
378 selectFrameElementInParentIfFullySelected();
379 m_frame->editor().respondToChangedSelection(oldSelection, options);
380 m_frame->document()->enqueueDocumentEvent(Event::create(eventNames().selectionchangeEvent, Event::CanBubble::No, Event::IsCancelable::No));
381
382 return true;
383}
384
385void FrameSelection::setSelection(const VisibleSelection& selection, OptionSet<SetSelectionOption> options, AXTextStateChangeIntent intent, CursorAlignOnScroll align, TextGranularity granularity)
386{
387 RefPtr<Frame> protectedFrame(m_frame);
388 if (!setSelectionWithoutUpdatingAppearance(selection, options, align, granularity))
389 return;
390
391 Document* document = m_frame->document();
392 if (!document)
393 return;
394
395 if (options & RevealSelectionUpToMainFrame)
396 m_selectionRevealMode = SelectionRevealMode::RevealUpToMainFrame;
397 else if (options & RevealSelection)
398 m_selectionRevealMode = SelectionRevealMode::Reveal;
399 else
400 m_selectionRevealMode = SelectionRevealMode::DoNotReveal;
401 m_alwaysAlignCursorOnScrollWhenRevealingSelection = align == AlignCursorOnScrollAlways;
402
403 m_selectionRevealIntent = intent;
404 m_pendingSelectionUpdate = true;
405
406 if (document->hasPendingStyleRecalc())
407 return;
408
409 FrameView* frameView = document->view();
410 if (frameView && frameView->layoutContext().isLayoutPending())
411 return;
412
413 updateAndRevealSelection(intent);
414
415 if (options & IsUserTriggered) {
416 if (auto* client = m_frame->editor().client())
417 client->didEndUserTriggeredSelectionChanges();
418 }
419}
420
421static void updateSelectionByUpdatingLayoutOrStyle(Frame& frame)
422{
423#if ENABLE(TEXT_CARET)
424 frame.document()->updateLayoutIgnorePendingStylesheets();
425#else
426 frame.document()->updateStyleIfNeeded();
427#endif
428}
429
430void FrameSelection::setNeedsSelectionUpdate(RevealSelectionAfterUpdate revealMode)
431{
432 m_selectionRevealIntent = AXTextStateChangeIntent();
433 if (revealMode == RevealSelectionAfterUpdate::Forced)
434 m_selectionRevealMode = SelectionRevealMode::Reveal;
435 m_pendingSelectionUpdate = true;
436 if (RenderView* view = m_frame->contentRenderer())
437 view->selection().clear();
438}
439
440void FrameSelection::updateAndRevealSelection(const AXTextStateChangeIntent& intent)
441{
442 if (!m_pendingSelectionUpdate)
443 return;
444
445 m_pendingSelectionUpdate = false;
446
447 updateAppearance();
448
449 if (m_selectionRevealMode != SelectionRevealMode::DoNotReveal) {
450 ScrollAlignment alignment;
451
452 if (m_frame->editor().behavior().shouldCenterAlignWhenSelectionIsRevealed())
453 alignment = m_alwaysAlignCursorOnScrollWhenRevealingSelection ? ScrollAlignment::alignCenterAlways : ScrollAlignment::alignCenterIfNeeded;
454 else
455 alignment = m_alwaysAlignCursorOnScrollWhenRevealingSelection ? ScrollAlignment::alignTopAlways : ScrollAlignment::alignToEdgeIfNeeded;
456
457 revealSelection(m_selectionRevealMode, alignment, RevealExtent);
458 }
459
460 notifyAccessibilityForSelectionChange(intent);
461}
462
463void FrameSelection::updateDataDetectorsForSelection()
464{
465#if ENABLE(TELEPHONE_NUMBER_DETECTION) && !PLATFORM(IOS_FAMILY)
466 m_frame->editor().scanSelectionForTelephoneNumbers();
467#endif
468}
469
470static bool removingNodeRemovesPosition(Node& node, const Position& position)
471{
472 if (!position.anchorNode())
473 return false;
474
475 if (position.anchorNode() == &node)
476 return true;
477
478 if (!is<Element>(node))
479 return false;
480
481 return downcast<Element>(node).containsIncludingShadowDOM(position.anchorNode());
482}
483
484void DragCaretController::nodeWillBeRemoved(Node& node)
485{
486 if (!hasCaret() || !node.isConnected())
487 return;
488
489 if (!removingNodeRemovesPosition(node, m_position.deepEquivalent()))
490 return;
491
492 if (RenderView* view = node.document().renderView())
493 view->selection().clear();
494
495 clear();
496}
497
498void FrameSelection::nodeWillBeRemoved(Node& node)
499{
500 // There can't be a selection inside a fragment, so if a fragment's node is being removed,
501 // the selection in the document that created the fragment needs no adjustment.
502 if (isNone() || !node.isConnected())
503 return;
504
505 respondToNodeModification(node, removingNodeRemovesPosition(node, m_selection.base()), removingNodeRemovesPosition(node, m_selection.extent()),
506 removingNodeRemovesPosition(node, m_selection.start()), removingNodeRemovesPosition(node, m_selection.end()));
507}
508
509void FrameSelection::respondToNodeModification(Node& node, bool baseRemoved, bool extentRemoved, bool startRemoved, bool endRemoved)
510{
511 bool clearRenderTreeSelection = false;
512 bool clearDOMTreeSelection = false;
513
514 if (startRemoved || endRemoved) {
515 Position start = m_selection.start();
516 Position end = m_selection.end();
517 if (startRemoved)
518 updatePositionForNodeRemoval(start, node);
519 if (endRemoved)
520 updatePositionForNodeRemoval(end, node);
521
522 if (start.isNotNull() && end.isNotNull()) {
523 if (m_selection.isBaseFirst())
524 m_selection.setWithoutValidation(start, end);
525 else
526 m_selection.setWithoutValidation(end, start);
527 } else
528 clearDOMTreeSelection = true;
529
530 clearRenderTreeSelection = true;
531 } else if (baseRemoved || extentRemoved) {
532 // The base and/or extent are about to be removed, but the start and end aren't.
533 // Change the base and extent to the start and end, but don't re-validate the
534 // selection, since doing so could move the start and end into the node
535 // that is about to be removed.
536 if (m_selection.isBaseFirst())
537 m_selection.setWithoutValidation(m_selection.start(), m_selection.end());
538 else
539 m_selection.setWithoutValidation(m_selection.end(), m_selection.start());
540 } else if (isRange()) {
541 if (RefPtr<Range> range = m_selection.firstRange()) {
542 auto compareNodeResult = range->compareNode(node);
543 if (!compareNodeResult.hasException()) {
544 auto compareResult = compareNodeResult.releaseReturnValue();
545 if (compareResult == Range::NODE_BEFORE_AND_AFTER || compareResult == Range::NODE_INSIDE) {
546 // If we did nothing here, when this node's renderer was destroyed, the rect that it
547 // occupied would be invalidated, but, selection gaps that change as a result of
548 // the removal wouldn't be invalidated.
549 // FIXME: Don't do so much unnecessary invalidation.
550 clearRenderTreeSelection = true;
551 }
552 }
553 }
554 }
555
556 if (clearRenderTreeSelection) {
557 if (auto* renderView = node.document().renderView()) {
558 renderView->selection().clear();
559
560 // Trigger a selection update so the selection will be set again.
561 m_selectionRevealIntent = AXTextStateChangeIntent();
562 m_pendingSelectionUpdate = true;
563 renderView->frameView().scheduleSelectionUpdate();
564 }
565 }
566
567 if (clearDOMTreeSelection)
568 setSelection(VisibleSelection(), DoNotSetFocus);
569}
570
571static void updatePositionAfterAdoptingTextReplacement(Position& position, CharacterData* node, unsigned offset, unsigned oldLength, unsigned newLength)
572{
573 if (!position.anchorNode() || position.anchorNode() != node || position.anchorType() != Position::PositionIsOffsetInAnchor)
574 return;
575
576 // See: http://www.w3.org/TR/DOM-Level-2-Traversal-Range/ranges.html#Level-2-Range-Mutation
577 ASSERT(position.offsetInContainerNode() >= 0);
578 unsigned positionOffset = static_cast<unsigned>(position.offsetInContainerNode());
579 // Replacing text can be viewed as a deletion followed by insertion.
580 if (positionOffset >= offset && positionOffset <= offset + oldLength)
581 position.moveToOffset(offset);
582
583 // Adjust the offset if the position is after the end of the deleted contents
584 // (positionOffset > offset + oldLength) to avoid having a stale offset.
585 if (positionOffset > offset + oldLength)
586 position.moveToOffset(positionOffset - oldLength + newLength);
587
588 ASSERT(static_cast<unsigned>(position.offsetInContainerNode()) <= node->length());
589}
590
591void FrameSelection::textWasReplaced(CharacterData* node, unsigned offset, unsigned oldLength, unsigned newLength)
592{
593 // The fragment check is a performance optimization. See http://trac.webkit.org/changeset/30062.
594 if (isNone() || !node || !node->isConnected())
595 return;
596
597 Position base = m_selection.base();
598 Position extent = m_selection.extent();
599 Position start = m_selection.start();
600 Position end = m_selection.end();
601 updatePositionAfterAdoptingTextReplacement(base, node, offset, oldLength, newLength);
602 updatePositionAfterAdoptingTextReplacement(extent, node, offset, oldLength, newLength);
603 updatePositionAfterAdoptingTextReplacement(start, node, offset, oldLength, newLength);
604 updatePositionAfterAdoptingTextReplacement(end, node, offset, oldLength, newLength);
605
606 if (base != m_selection.base() || extent != m_selection.extent() || start != m_selection.start() || end != m_selection.end()) {
607 VisibleSelection newSelection;
608 if (base != extent)
609 newSelection.setWithoutValidation(base, extent);
610 else if (m_selection.isDirectional() && !m_selection.isBaseFirst())
611 newSelection.setWithoutValidation(end, start);
612 else
613 newSelection.setWithoutValidation(start, end);
614
615 setSelection(newSelection, DoNotSetFocus);
616 }
617}
618
619TextDirection FrameSelection::directionOfEnclosingBlock()
620{
621 return WebCore::directionOfEnclosingBlock(m_selection.extent());
622}
623
624TextDirection FrameSelection::directionOfSelection()
625{
626 InlineBox* startBox = nullptr;
627 InlineBox* endBox = nullptr;
628 int unusedOffset;
629 // Cache the VisiblePositions because visibleStart() and visibleEnd()
630 // can cause layout, which has the potential to invalidate lineboxes.
631 VisiblePosition startPosition = m_selection.visibleStart();
632 VisiblePosition endPosition = m_selection.visibleEnd();
633 if (startPosition.isNotNull())
634 startPosition.getInlineBoxAndOffset(startBox, unusedOffset);
635 if (endPosition.isNotNull())
636 endPosition.getInlineBoxAndOffset(endBox, unusedOffset);
637 if (startBox && endBox && startBox->direction() == endBox->direction())
638 return startBox->direction();
639
640 return directionOfEnclosingBlock();
641}
642
643void FrameSelection::willBeModified(EAlteration alter, SelectionDirection direction)
644{
645 if (alter != AlterationExtend)
646 return;
647
648 Position start = m_selection.start();
649 Position end = m_selection.end();
650
651 bool baseIsStart = true;
652
653 if (m_selection.isDirectional()) {
654 // Make base and extent match start and end so we extend the user-visible selection.
655 // This only matters for cases where base and extend point to different positions than
656 // start and end (e.g. after a double-click to select a word).
657 if (m_selection.isBaseFirst())
658 baseIsStart = true;
659 else
660 baseIsStart = false;
661 } else {
662 switch (direction) {
663 case DirectionRight:
664 if (directionOfSelection() == TextDirection::LTR)
665 baseIsStart = true;
666 else
667 baseIsStart = false;
668 break;
669 case DirectionForward:
670 baseIsStart = true;
671 break;
672 case DirectionLeft:
673 if (directionOfSelection() == TextDirection::LTR)
674 baseIsStart = false;
675 else
676 baseIsStart = true;
677 break;
678 case DirectionBackward:
679 baseIsStart = false;
680 break;
681 }
682 }
683 if (baseIsStart) {
684 m_selection.setBase(start);
685 m_selection.setExtent(end);
686 } else {
687 m_selection.setBase(end);
688 m_selection.setExtent(start);
689 }
690}
691
692VisiblePosition FrameSelection::positionForPlatform(bool isGetStart) const
693{
694 // FIXME: VisibleSelection should be fixed to ensure as an invariant that
695 // base/extent always point to the same nodes as start/end, but which points
696 // to which depends on the value of isBaseFirst. Then this can be changed
697 // to just return m_sel.extent().
698 if (m_frame && m_frame->editor().behavior().shouldAlwaysExtendSelectionFromExtentEndpoint())
699 return m_selection.isBaseFirst() ? m_selection.visibleEnd() : m_selection.visibleStart();
700
701 return isGetStart ? m_selection.visibleStart() : m_selection.visibleEnd();
702}
703
704VisiblePosition FrameSelection::startForPlatform() const
705{
706 return positionForPlatform(true);
707}
708
709VisiblePosition FrameSelection::endForPlatform() const
710{
711 return positionForPlatform(false);
712}
713
714VisiblePosition FrameSelection::nextWordPositionForPlatform(const VisiblePosition &originalPosition)
715{
716 VisiblePosition positionAfterCurrentWord = nextWordPosition(originalPosition);
717
718 if (m_frame && m_frame->editor().behavior().shouldSkipSpaceWhenMovingRight()) {
719 // In order to skip spaces when moving right, we advance one
720 // word further and then move one word back. Given the
721 // semantics of previousWordPosition() this will put us at the
722 // beginning of the word following.
723 VisiblePosition positionAfterSpacingAndFollowingWord = nextWordPosition(positionAfterCurrentWord);
724 if (positionAfterSpacingAndFollowingWord != positionAfterCurrentWord)
725 positionAfterCurrentWord = previousWordPosition(positionAfterSpacingAndFollowingWord);
726
727 bool movingBackwardsMovedPositionToStartOfCurrentWord = positionAfterCurrentWord == previousWordPosition(nextWordPosition(originalPosition));
728 if (movingBackwardsMovedPositionToStartOfCurrentWord)
729 positionAfterCurrentWord = positionAfterSpacingAndFollowingWord;
730 }
731 return positionAfterCurrentWord;
732}
733
734#if ENABLE(USERSELECT_ALL)
735static void adjustPositionForUserSelectAll(VisiblePosition& pos, bool isForward)
736{
737 if (Node* rootUserSelectAll = Position::rootUserSelectAllForNode(pos.deepEquivalent().anchorNode()))
738 pos = isForward ? positionAfterNode(rootUserSelectAll).downstream(CanCrossEditingBoundary) : positionBeforeNode(rootUserSelectAll).upstream(CanCrossEditingBoundary);
739}
740#endif
741
742VisiblePosition FrameSelection::modifyExtendingRight(TextGranularity granularity)
743{
744 VisiblePosition pos(m_selection.extent(), m_selection.affinity());
745
746 // The difference between modifyExtendingRight and modifyExtendingForward is:
747 // modifyExtendingForward always extends forward logically.
748 // modifyExtendingRight behaves the same as modifyExtendingForward except for extending character or word,
749 // it extends forward logically if the enclosing block is TextDirection::LTR,
750 // but it extends backward logically if the enclosing block is TextDirection::RTL.
751 switch (granularity) {
752 case CharacterGranularity:
753 if (directionOfEnclosingBlock() == TextDirection::LTR)
754 pos = pos.next(CannotCrossEditingBoundary);
755 else
756 pos = pos.previous(CannotCrossEditingBoundary);
757 break;
758 case WordGranularity:
759 if (directionOfEnclosingBlock() == TextDirection::LTR)
760 pos = nextWordPositionForPlatform(pos);
761 else
762 pos = previousWordPosition(pos);
763 break;
764 case LineBoundary:
765 if (directionOfEnclosingBlock() == TextDirection::LTR)
766 pos = modifyExtendingForward(granularity);
767 else
768 pos = modifyExtendingBackward(granularity);
769 break;
770 case SentenceGranularity:
771 case LineGranularity:
772 case ParagraphGranularity:
773 case SentenceBoundary:
774 case ParagraphBoundary:
775 case DocumentBoundary:
776 // FIXME: implement all of the above?
777 pos = modifyExtendingForward(granularity);
778 break;
779 case DocumentGranularity:
780 ASSERT_NOT_REACHED();
781 break;
782 }
783#if ENABLE(USERSELECT_ALL)
784 adjustPositionForUserSelectAll(pos, directionOfEnclosingBlock() == TextDirection::LTR);
785#endif
786 return pos;
787}
788
789VisiblePosition FrameSelection::modifyExtendingForward(TextGranularity granularity)
790{
791 VisiblePosition pos(m_selection.extent(), m_selection.affinity());
792 switch (granularity) {
793 case CharacterGranularity:
794 pos = pos.next(CannotCrossEditingBoundary);
795 break;
796 case WordGranularity:
797 pos = nextWordPositionForPlatform(pos);
798 break;
799 case SentenceGranularity:
800 pos = nextSentencePosition(pos);
801 break;
802 case LineGranularity:
803 pos = nextLinePosition(pos, lineDirectionPointForBlockDirectionNavigation(EXTENT));
804 break;
805 case ParagraphGranularity:
806 pos = nextParagraphPosition(pos, lineDirectionPointForBlockDirectionNavigation(EXTENT));
807 break;
808 case DocumentGranularity:
809 ASSERT_NOT_REACHED();
810 break;
811 case SentenceBoundary:
812 pos = endOfSentence(endForPlatform());
813 break;
814 case LineBoundary:
815 pos = logicalEndOfLine(endForPlatform());
816 break;
817 case ParagraphBoundary:
818 pos = endOfParagraph(endForPlatform());
819 break;
820 case DocumentBoundary:
821 pos = endForPlatform();
822 if (isEditablePosition(pos.deepEquivalent()))
823 pos = endOfEditableContent(pos);
824 else
825 pos = endOfDocument(pos);
826 break;
827 }
828#if ENABLE(USERSELECT_ALL)
829 adjustPositionForUserSelectAll(pos, directionOfEnclosingBlock() == TextDirection::LTR);
830#endif
831 return pos;
832}
833
834VisiblePosition FrameSelection::modifyMovingRight(TextGranularity granularity, bool* reachedBoundary)
835{
836 if (reachedBoundary)
837 *reachedBoundary = false;
838 VisiblePosition pos;
839 switch (granularity) {
840 case CharacterGranularity:
841 if (isRange()) {
842 if (directionOfSelection() == TextDirection::LTR)
843 pos = VisiblePosition(m_selection.end(), m_selection.affinity());
844 else
845 pos = VisiblePosition(m_selection.start(), m_selection.affinity());
846 } else
847 pos = VisiblePosition(m_selection.extent(), m_selection.affinity()).right(true, reachedBoundary);
848 break;
849 case WordGranularity: {
850 bool skipsSpaceWhenMovingRight = m_frame && m_frame->editor().behavior().shouldSkipSpaceWhenMovingRight();
851 VisiblePosition currentPosition(m_selection.extent(), m_selection.affinity());
852 pos = rightWordPosition(currentPosition, skipsSpaceWhenMovingRight);
853 if (reachedBoundary)
854 *reachedBoundary = pos == currentPosition;
855 break;
856 }
857 case SentenceGranularity:
858 case LineGranularity:
859 case ParagraphGranularity:
860 case SentenceBoundary:
861 case ParagraphBoundary:
862 case DocumentBoundary:
863 // FIXME: Implement all of the above.
864 pos = modifyMovingForward(granularity, reachedBoundary);
865 break;
866 case LineBoundary:
867 pos = rightBoundaryOfLine(startForPlatform(), directionOfEnclosingBlock(), reachedBoundary);
868 break;
869 case DocumentGranularity:
870 ASSERT_NOT_REACHED();
871 break;
872 }
873 return pos;
874}
875
876VisiblePosition FrameSelection::modifyMovingForward(TextGranularity granularity, bool* reachedBoundary)
877{
878 if (reachedBoundary)
879 *reachedBoundary = false;
880 VisiblePosition currentPosition;
881 switch (granularity) {
882 case WordGranularity:
883 case SentenceGranularity:
884 currentPosition = VisiblePosition(m_selection.extent(), m_selection.affinity());
885 break;
886 case LineGranularity:
887 case ParagraphGranularity:
888 case SentenceBoundary:
889 case ParagraphBoundary:
890 case DocumentBoundary:
891 currentPosition = endForPlatform();
892 break;
893 default:
894 break;
895 }
896 VisiblePosition pos;
897 // FIXME: Stay in editable content for the less common granularities.
898 switch (granularity) {
899 case CharacterGranularity:
900 if (isRange())
901 pos = VisiblePosition(m_selection.end(), m_selection.affinity());
902 else
903 pos = VisiblePosition(m_selection.extent(), m_selection.affinity()).next(CannotCrossEditingBoundary, reachedBoundary);
904 break;
905 case WordGranularity:
906 pos = nextWordPositionForPlatform(currentPosition);
907 break;
908 case SentenceGranularity:
909 pos = nextSentencePosition(currentPosition);
910 break;
911 case LineGranularity: {
912 // down-arrowing from a range selection that ends at the start of a line needs
913 // to leave the selection at that line start (no need to call nextLinePosition!)
914 pos = currentPosition;
915 if (!isRange() || !isStartOfLine(pos))
916 pos = nextLinePosition(pos, lineDirectionPointForBlockDirectionNavigation(START));
917 break;
918 }
919 case ParagraphGranularity:
920 pos = nextParagraphPosition(currentPosition, lineDirectionPointForBlockDirectionNavigation(START));
921 break;
922 case DocumentGranularity:
923 ASSERT_NOT_REACHED();
924 break;
925 case SentenceBoundary:
926 pos = endOfSentence(currentPosition);
927 break;
928 case LineBoundary:
929 pos = logicalEndOfLine(endForPlatform(), reachedBoundary);
930 break;
931 case ParagraphBoundary:
932 pos = endOfParagraph(currentPosition);
933 break;
934 case DocumentBoundary:
935 pos = currentPosition;
936 if (isEditablePosition(pos.deepEquivalent()))
937 pos = endOfEditableContent(pos);
938 else
939 pos = endOfDocument(pos);
940 break;
941 }
942 switch (granularity) {
943 case WordGranularity:
944 case SentenceGranularity:
945 case LineGranularity:
946 case ParagraphGranularity:
947 case SentenceBoundary:
948 case ParagraphBoundary:
949 case DocumentBoundary:
950 if (reachedBoundary)
951 *reachedBoundary = pos == currentPosition;
952 break;
953 default:
954 break;
955 }
956 return pos;
957}
958
959VisiblePosition FrameSelection::modifyExtendingLeft(TextGranularity granularity)
960{
961 VisiblePosition pos(m_selection.extent(), m_selection.affinity());
962
963 // The difference between modifyExtendingLeft and modifyExtendingBackward is:
964 // modifyExtendingBackward always extends backward logically.
965 // modifyExtendingLeft behaves the same as modifyExtendingBackward except for extending character or word,
966 // it extends backward logically if the enclosing block is TextDirection::LTR,
967 // but it extends forward logically if the enclosing block is TextDirection::RTL.
968 switch (granularity) {
969 case CharacterGranularity:
970 if (directionOfEnclosingBlock() == TextDirection::LTR)
971 pos = pos.previous(CannotCrossEditingBoundary);
972 else
973 pos = pos.next(CannotCrossEditingBoundary);
974 break;
975 case WordGranularity:
976 if (directionOfEnclosingBlock() == TextDirection::LTR)
977 pos = previousWordPosition(pos);
978 else
979 pos = nextWordPositionForPlatform(pos);
980 break;
981 case LineBoundary:
982 if (directionOfEnclosingBlock() == TextDirection::LTR)
983 pos = modifyExtendingBackward(granularity);
984 else
985 pos = modifyExtendingForward(granularity);
986 break;
987 case SentenceGranularity:
988 case LineGranularity:
989 case ParagraphGranularity:
990 case SentenceBoundary:
991 case ParagraphBoundary:
992 case DocumentBoundary:
993 pos = modifyExtendingBackward(granularity);
994 break;
995 case DocumentGranularity:
996 ASSERT_NOT_REACHED();
997 break;
998 }
999#if ENABLE(USERSELECT_ALL)
1000 adjustPositionForUserSelectAll(pos, !(directionOfEnclosingBlock() == TextDirection::LTR));
1001#endif
1002 return pos;
1003}
1004
1005VisiblePosition FrameSelection::modifyExtendingBackward(TextGranularity granularity)
1006{
1007 VisiblePosition pos(m_selection.extent(), m_selection.affinity());
1008
1009 // Extending a selection backward by word or character from just after a table selects
1010 // the table. This "makes sense" from the user perspective, esp. when deleting.
1011 // It was done here instead of in VisiblePosition because we want VPs to iterate
1012 // over everything.
1013 switch (granularity) {
1014 case CharacterGranularity:
1015 pos = pos.previous(CannotCrossEditingBoundary);
1016 break;
1017 case WordGranularity:
1018 pos = previousWordPosition(pos);
1019 break;
1020 case SentenceGranularity:
1021 pos = previousSentencePosition(pos);
1022 break;
1023 case LineGranularity:
1024 pos = previousLinePosition(pos, lineDirectionPointForBlockDirectionNavigation(EXTENT));
1025 break;
1026 case ParagraphGranularity:
1027 pos = previousParagraphPosition(pos, lineDirectionPointForBlockDirectionNavigation(EXTENT));
1028 break;
1029 case SentenceBoundary:
1030 pos = startOfSentence(startForPlatform());
1031 break;
1032 case LineBoundary:
1033 pos = logicalStartOfLine(startForPlatform());
1034 break;
1035 case ParagraphBoundary:
1036 pos = startOfParagraph(startForPlatform());
1037 break;
1038 case DocumentBoundary:
1039 pos = startForPlatform();
1040 if (isEditablePosition(pos.deepEquivalent()))
1041 pos = startOfEditableContent(pos);
1042 else
1043 pos = startOfDocument(pos);
1044 break;
1045 case DocumentGranularity:
1046 ASSERT_NOT_REACHED();
1047 break;
1048 }
1049#if ENABLE(USERSELECT_ALL)
1050 adjustPositionForUserSelectAll(pos, !(directionOfEnclosingBlock() == TextDirection::LTR));
1051#endif
1052 return pos;
1053}
1054
1055VisiblePosition FrameSelection::modifyMovingLeft(TextGranularity granularity, bool* reachedBoundary)
1056{
1057 if (reachedBoundary)
1058 *reachedBoundary = false;
1059 VisiblePosition pos;
1060 switch (granularity) {
1061 case CharacterGranularity:
1062 if (isRange())
1063 if (directionOfSelection() == TextDirection::LTR)
1064 pos = VisiblePosition(m_selection.start(), m_selection.affinity());
1065 else
1066 pos = VisiblePosition(m_selection.end(), m_selection.affinity());
1067 else
1068 pos = VisiblePosition(m_selection.extent(), m_selection.affinity()).left(true, reachedBoundary);
1069 break;
1070 case WordGranularity: {
1071 bool skipsSpaceWhenMovingRight = m_frame && m_frame->editor().behavior().shouldSkipSpaceWhenMovingRight();
1072 VisiblePosition currentPosition(m_selection.extent(), m_selection.affinity());
1073 pos = leftWordPosition(currentPosition, skipsSpaceWhenMovingRight);
1074 if (reachedBoundary)
1075 *reachedBoundary = pos == currentPosition;
1076 break;
1077 }
1078 case SentenceGranularity:
1079 case LineGranularity:
1080 case ParagraphGranularity:
1081 case SentenceBoundary:
1082 case ParagraphBoundary:
1083 case DocumentBoundary:
1084 // FIXME: Implement all of the above.
1085 pos = modifyMovingBackward(granularity, reachedBoundary);
1086 break;
1087 case LineBoundary:
1088 pos = leftBoundaryOfLine(startForPlatform(), directionOfEnclosingBlock(), reachedBoundary);
1089 break;
1090 case DocumentGranularity:
1091 ASSERT_NOT_REACHED();
1092 break;
1093 }
1094 return pos;
1095}
1096
1097VisiblePosition FrameSelection::modifyMovingBackward(TextGranularity granularity, bool* reachedBoundary)
1098{
1099 if (reachedBoundary)
1100 *reachedBoundary = false;
1101 VisiblePosition currentPosition;
1102 switch (granularity) {
1103 case WordGranularity:
1104 case SentenceGranularity:
1105 currentPosition = VisiblePosition(m_selection.extent(), m_selection.affinity());
1106 break;
1107 case LineGranularity:
1108 case ParagraphGranularity:
1109 case SentenceBoundary:
1110 case ParagraphBoundary:
1111 case DocumentBoundary:
1112 currentPosition = startForPlatform();
1113 break;
1114 default:
1115 break;
1116 }
1117 VisiblePosition pos;
1118 switch (granularity) {
1119 case CharacterGranularity:
1120 if (isRange())
1121 pos = VisiblePosition(m_selection.start(), m_selection.affinity());
1122 else
1123 pos = VisiblePosition(m_selection.extent(), m_selection.affinity()).previous(CannotCrossEditingBoundary, reachedBoundary);
1124 break;
1125 case WordGranularity:
1126 pos = previousWordPosition(currentPosition);
1127 break;
1128 case SentenceGranularity:
1129 pos = previousSentencePosition(currentPosition);
1130 break;
1131 case LineGranularity:
1132 pos = previousLinePosition(currentPosition, lineDirectionPointForBlockDirectionNavigation(START));
1133 break;
1134 case ParagraphGranularity:
1135 pos = previousParagraphPosition(currentPosition, lineDirectionPointForBlockDirectionNavigation(START));
1136 break;
1137 case SentenceBoundary:
1138 pos = startOfSentence(currentPosition);
1139 break;
1140 case LineBoundary:
1141 pos = logicalStartOfLine(startForPlatform(), reachedBoundary);
1142 break;
1143 case ParagraphBoundary:
1144 pos = startOfParagraph(currentPosition);
1145 break;
1146 case DocumentBoundary:
1147 pos = currentPosition;
1148 if (isEditablePosition(pos.deepEquivalent()))
1149 pos = startOfEditableContent(pos);
1150 else
1151 pos = startOfDocument(pos);
1152 break;
1153 case DocumentGranularity:
1154 ASSERT_NOT_REACHED();
1155 break;
1156 }
1157 switch (granularity) {
1158 case WordGranularity:
1159 case SentenceGranularity:
1160 case LineGranularity:
1161 case ParagraphGranularity:
1162 case SentenceBoundary:
1163 case ParagraphBoundary:
1164 case DocumentBoundary:
1165 if (reachedBoundary)
1166 *reachedBoundary = pos == currentPosition;
1167 break;
1168 default:
1169 break;
1170 }
1171 return pos;
1172}
1173
1174static bool isBoundary(TextGranularity granularity)
1175{
1176 return granularity == LineBoundary || granularity == ParagraphBoundary || granularity == DocumentBoundary;
1177}
1178
1179AXTextStateChangeIntent FrameSelection::textSelectionIntent(EAlteration alter, SelectionDirection direction, TextGranularity granularity)
1180{
1181 AXTextStateChangeIntent intent = AXTextStateChangeIntent();
1182 bool flip = false;
1183 if (alter == FrameSelection::AlterationMove) {
1184 intent.type = AXTextStateChangeTypeSelectionMove;
1185 flip = isRange() && directionOfSelection() == TextDirection::RTL;
1186 } else
1187 intent.type = AXTextStateChangeTypeSelectionExtend;
1188 switch (granularity) {
1189 case CharacterGranularity:
1190 intent.selection.granularity = AXTextSelectionGranularityCharacter;
1191 break;
1192 case WordGranularity:
1193 intent.selection.granularity = AXTextSelectionGranularityWord;
1194 break;
1195 case SentenceGranularity:
1196 case SentenceBoundary:
1197 intent.selection.granularity = AXTextSelectionGranularitySentence;
1198 break;
1199 case LineGranularity:
1200 case LineBoundary:
1201 intent.selection.granularity = AXTextSelectionGranularityLine;
1202 break;
1203 case ParagraphGranularity:
1204 case ParagraphBoundary:
1205 intent.selection.granularity = AXTextSelectionGranularityParagraph;
1206 break;
1207 case DocumentGranularity:
1208 case DocumentBoundary:
1209 intent.selection.granularity = AXTextSelectionGranularityDocument;
1210 break;
1211 }
1212 bool boundary = false;
1213 switch (granularity) {
1214 case CharacterGranularity:
1215 case WordGranularity:
1216 case SentenceGranularity:
1217 case LineGranularity:
1218 case ParagraphGranularity:
1219 case DocumentGranularity:
1220 break;
1221 case SentenceBoundary:
1222 case LineBoundary:
1223 case ParagraphBoundary:
1224 case DocumentBoundary:
1225 boundary = true;
1226 break;
1227 }
1228 switch (direction) {
1229 case DirectionRight:
1230 case DirectionForward:
1231 if (boundary)
1232 intent.selection.direction = flip ? AXTextSelectionDirectionBeginning : AXTextSelectionDirectionEnd;
1233 else
1234 intent.selection.direction = flip ? AXTextSelectionDirectionPrevious : AXTextSelectionDirectionNext;
1235 break;
1236 case DirectionLeft:
1237 case DirectionBackward:
1238 if (boundary)
1239 intent.selection.direction = flip ? AXTextSelectionDirectionEnd : AXTextSelectionDirectionBeginning;
1240 else
1241 intent.selection.direction = flip ? AXTextSelectionDirectionNext : AXTextSelectionDirectionPrevious;
1242 break;
1243 }
1244 return intent;
1245}
1246
1247static AXTextSelection textSelectionWithDirectionAndGranularity(SelectionDirection direction, TextGranularity granularity)
1248{
1249 // FIXME: Account for BIDI in DirectionRight & DirectionLeft. (In a RTL block, Right would map to Previous/Beginning and Left to Next/End.)
1250 AXTextSelectionDirection intentDirection = AXTextSelectionDirectionUnknown;
1251 switch (direction) {
1252 case DirectionForward:
1253 intentDirection = AXTextSelectionDirectionNext;
1254 break;
1255 case DirectionRight:
1256 intentDirection = AXTextSelectionDirectionNext;
1257 break;
1258 case DirectionBackward:
1259 intentDirection = AXTextSelectionDirectionPrevious;
1260 break;
1261 case DirectionLeft:
1262 intentDirection = AXTextSelectionDirectionPrevious;
1263 break;
1264 }
1265 AXTextSelectionGranularity intentGranularity = AXTextSelectionGranularityUnknown;
1266 switch (granularity) {
1267 case CharacterGranularity:
1268 intentGranularity = AXTextSelectionGranularityCharacter;
1269 break;
1270 case WordGranularity:
1271 intentGranularity = AXTextSelectionGranularityWord;
1272 break;
1273 case SentenceGranularity:
1274 case SentenceBoundary: // FIXME: Boundary should affect direction.
1275 intentGranularity = AXTextSelectionGranularitySentence;
1276 break;
1277 case LineGranularity:
1278 intentGranularity = AXTextSelectionGranularityLine;
1279 break;
1280 case ParagraphGranularity:
1281 case ParagraphBoundary: // FIXME: Boundary should affect direction.
1282 intentGranularity = AXTextSelectionGranularityParagraph;
1283 break;
1284 case DocumentGranularity:
1285 case DocumentBoundary: // FIXME: Boundary should affect direction.
1286 intentGranularity = AXTextSelectionGranularityDocument;
1287 break;
1288 case LineBoundary:
1289 intentGranularity = AXTextSelectionGranularityLine;
1290 switch (direction) {
1291 case DirectionForward:
1292 intentDirection = AXTextSelectionDirectionEnd;
1293 break;
1294 case DirectionRight:
1295 intentDirection = AXTextSelectionDirectionEnd;
1296 break;
1297 case DirectionBackward:
1298 intentDirection = AXTextSelectionDirectionBeginning;
1299 break;
1300 case DirectionLeft:
1301 intentDirection = AXTextSelectionDirectionBeginning;
1302 break;
1303 }
1304 break;
1305 }
1306 return { intentDirection, intentGranularity, false };
1307}
1308
1309bool FrameSelection::modify(EAlteration alter, SelectionDirection direction, TextGranularity granularity, EUserTriggered userTriggered)
1310{
1311 if (userTriggered == UserTriggered) {
1312 FrameSelection trialFrameSelection;
1313 trialFrameSelection.setSelection(m_selection);
1314 trialFrameSelection.modify(alter, direction, granularity, NotUserTriggered);
1315
1316 bool change = shouldChangeSelection(trialFrameSelection.selection());
1317 if (!change)
1318 return false;
1319
1320 if (trialFrameSelection.selection().isRange() && m_selection.isCaret() && !dispatchSelectStart())
1321 return false;
1322 }
1323
1324 willBeModified(alter, direction);
1325
1326 bool reachedBoundary = false;
1327 bool wasRange = m_selection.isRange();
1328 Position originalStartPosition = m_selection.start();
1329 VisiblePosition position;
1330 switch (direction) {
1331 case DirectionRight:
1332 if (alter == AlterationMove)
1333 position = modifyMovingRight(granularity, &reachedBoundary);
1334 else
1335 position = modifyExtendingRight(granularity);
1336 break;
1337 case DirectionForward:
1338 if (alter == AlterationExtend)
1339 position = modifyExtendingForward(granularity);
1340 else
1341 position = modifyMovingForward(granularity, &reachedBoundary);
1342 break;
1343 case DirectionLeft:
1344 if (alter == AlterationMove)
1345 position = modifyMovingLeft(granularity, &reachedBoundary);
1346 else
1347 position = modifyExtendingLeft(granularity);
1348 break;
1349 case DirectionBackward:
1350 if (alter == AlterationExtend)
1351 position = modifyExtendingBackward(granularity);
1352 else
1353 position = modifyMovingBackward(granularity, &reachedBoundary);
1354 break;
1355 }
1356
1357 if (reachedBoundary && !isRange() && userTriggered == UserTriggered && m_frame && AXObjectCache::accessibilityEnabled()) {
1358 notifyAccessibilityForSelectionChange({ AXTextStateChangeTypeSelectionBoundary, textSelectionWithDirectionAndGranularity(direction, granularity) });
1359 return true;
1360 }
1361
1362 if (position.isNull())
1363 return false;
1364
1365 if (isSpatialNavigationEnabled(m_frame))
1366 if (!wasRange && alter == AlterationMove && position == originalStartPosition)
1367 return false;
1368
1369 if (m_frame && AXObjectCache::accessibilityEnabled()) {
1370 if (AXObjectCache* cache = m_frame->document()->existingAXObjectCache())
1371 cache->setTextSelectionIntent(textSelectionIntent(alter, direction, granularity));
1372 }
1373
1374 // Some of the above operations set an xPosForVerticalArrowNavigation.
1375 // Setting a selection will clear it, so save it to possibly restore later.
1376 // Note: the START position type is arbitrary because it is unused, it would be
1377 // the requested position type if there were no xPosForVerticalArrowNavigation set.
1378 LayoutUnit x = lineDirectionPointForBlockDirectionNavigation(START);
1379 m_selection.setIsDirectional(shouldAlwaysUseDirectionalSelection(m_frame) || alter == AlterationExtend);
1380
1381 switch (alter) {
1382 case AlterationMove:
1383 moveTo(position, userTriggered);
1384 break;
1385 case AlterationExtend:
1386
1387 if (!m_selection.isCaret()
1388 && (granularity == WordGranularity || granularity == ParagraphGranularity || granularity == LineGranularity)
1389 && m_frame && !m_frame->editor().behavior().shouldExtendSelectionByWordOrLineAcrossCaret()) {
1390 // Don't let the selection go across the base position directly. Needed to match mac
1391 // behavior when, for instance, word-selecting backwards starting with the caret in
1392 // the middle of a word and then word-selecting forward, leaving the caret in the
1393 // same place where it was, instead of directly selecting to the end of the word.
1394 VisibleSelection newSelection = m_selection;
1395 newSelection.setExtent(position);
1396 if (m_selection.isBaseFirst() != newSelection.isBaseFirst())
1397 position = m_selection.base();
1398 }
1399
1400 // Standard Mac behavior when extending to a boundary is grow the selection rather than leaving the
1401 // base in place and moving the extent. Matches NSTextView.
1402 if (!m_frame || !m_frame->editor().behavior().shouldAlwaysGrowSelectionWhenExtendingToBoundary() || m_selection.isCaret() || !isBoundary(granularity))
1403 setExtent(position, userTriggered);
1404 else {
1405 TextDirection textDirection = directionOfEnclosingBlock();
1406 if (direction == DirectionForward || (textDirection == TextDirection::LTR && direction == DirectionRight) || (textDirection == TextDirection::RTL && direction == DirectionLeft))
1407 setEnd(position, userTriggered);
1408 else
1409 setStart(position, userTriggered);
1410 }
1411 break;
1412 }
1413
1414 if (granularity == LineGranularity || granularity == ParagraphGranularity)
1415 m_xPosForVerticalArrowNavigation = x;
1416
1417 if (userTriggered == UserTriggered)
1418 m_granularity = CharacterGranularity;
1419
1420 setCaretRectNeedsUpdate();
1421
1422 return true;
1423}
1424
1425// FIXME: Maybe baseline would be better?
1426static bool absoluteCaretY(const VisiblePosition &c, int &y)
1427{
1428 IntRect rect = c.absoluteCaretBounds();
1429 if (rect.isEmpty())
1430 return false;
1431 y = rect.y() + rect.height() / 2;
1432 return true;
1433}
1434
1435bool FrameSelection::modify(EAlteration alter, unsigned verticalDistance, VerticalDirection direction, EUserTriggered userTriggered, CursorAlignOnScroll align)
1436{
1437 if (!verticalDistance)
1438 return false;
1439
1440 if (userTriggered == UserTriggered) {
1441 FrameSelection trialFrameSelection;
1442 trialFrameSelection.setSelection(m_selection);
1443 trialFrameSelection.modify(alter, verticalDistance, direction, NotUserTriggered);
1444
1445 bool change = shouldChangeSelection(trialFrameSelection.selection());
1446 if (!change)
1447 return false;
1448 }
1449
1450 willBeModified(alter, direction == DirectionUp ? DirectionBackward : DirectionForward);
1451
1452 VisiblePosition pos;
1453 LayoutUnit xPos;
1454 switch (alter) {
1455 case AlterationMove:
1456 pos = VisiblePosition(direction == DirectionUp ? m_selection.start() : m_selection.end(), m_selection.affinity());
1457 xPos = lineDirectionPointForBlockDirectionNavigation(direction == DirectionUp ? START : END);
1458 m_selection.setAffinity(direction == DirectionUp ? UPSTREAM : DOWNSTREAM);
1459 break;
1460 case AlterationExtend:
1461 pos = VisiblePosition(m_selection.extent(), m_selection.affinity());
1462 xPos = lineDirectionPointForBlockDirectionNavigation(EXTENT);
1463 m_selection.setAffinity(DOWNSTREAM);
1464 break;
1465 }
1466
1467 int startY;
1468 if (!absoluteCaretY(pos, startY))
1469 return false;
1470 if (direction == DirectionUp)
1471 startY = -startY;
1472 int lastY = startY;
1473
1474 VisiblePosition result;
1475 VisiblePosition next;
1476 for (VisiblePosition p = pos; ; p = next) {
1477 if (direction == DirectionUp)
1478 next = previousLinePosition(p, xPos);
1479 else
1480 next = nextLinePosition(p, xPos);
1481
1482 if (next.isNull() || next == p)
1483 break;
1484 int nextY;
1485 if (!absoluteCaretY(next, nextY))
1486 break;
1487 if (direction == DirectionUp)
1488 nextY = -nextY;
1489 if (nextY - startY > static_cast<int>(verticalDistance))
1490 break;
1491 if (nextY >= lastY) {
1492 lastY = nextY;
1493 result = next;
1494 }
1495 }
1496
1497 if (result.isNull())
1498 return false;
1499
1500 switch (alter) {
1501 case AlterationMove:
1502 moveTo(result, userTriggered, align);
1503 break;
1504 case AlterationExtend:
1505 setExtent(result, userTriggered);
1506 break;
1507 }
1508
1509 if (userTriggered == UserTriggered)
1510 m_granularity = CharacterGranularity;
1511
1512 m_selection.setIsDirectional(shouldAlwaysUseDirectionalSelection(m_frame) || alter == AlterationExtend);
1513
1514 return true;
1515}
1516
1517LayoutUnit FrameSelection::lineDirectionPointForBlockDirectionNavigation(EPositionType type)
1518{
1519 LayoutUnit x;
1520
1521 if (isNone())
1522 return x;
1523
1524 Position pos;
1525 switch (type) {
1526 case START:
1527 pos = m_selection.start();
1528 break;
1529 case END:
1530 pos = m_selection.end();
1531 break;
1532 case BASE:
1533 pos = m_selection.base();
1534 break;
1535 case EXTENT:
1536 pos = m_selection.extent();
1537 break;
1538 }
1539
1540 Frame* frame = pos.anchorNode()->document().frame();
1541 if (!frame)
1542 return x;
1543
1544 if (m_xPosForVerticalArrowNavigation == NoXPosForVerticalArrowNavigation()) {
1545 VisiblePosition visiblePosition(pos, m_selection.affinity());
1546 // VisiblePosition creation can fail here if a node containing the selection becomes visibility:hidden
1547 // after the selection is created and before this function is called.
1548 x = visiblePosition.isNotNull() ? visiblePosition.lineDirectionPointForBlockDirectionNavigation() : 0;
1549 m_xPosForVerticalArrowNavigation = x;
1550 } else
1551 x = m_xPosForVerticalArrowNavigation;
1552
1553 return x;
1554}
1555
1556void FrameSelection::clear()
1557{
1558 m_granularity = CharacterGranularity;
1559 setSelection(VisibleSelection());
1560}
1561
1562void FrameSelection::prepareForDestruction()
1563{
1564 m_granularity = CharacterGranularity;
1565
1566#if ENABLE(TEXT_CARET)
1567 m_caretBlinkTimer.stop();
1568#endif
1569
1570 if (auto* view = m_frame->contentRenderer())
1571 view->selection().clear();
1572
1573 setSelectionWithoutUpdatingAppearance(VisibleSelection(), defaultSetSelectionOptions(), AlignCursorOnScrollIfNeeded, CharacterGranularity);
1574 m_previousCaretNode = nullptr;
1575 m_typingStyle = nullptr;
1576 m_appearanceUpdateTimer.stop();
1577}
1578
1579void FrameSelection::setStart(const VisiblePosition &pos, EUserTriggered trigger)
1580{
1581 if (m_selection.isBaseFirst())
1582 setBase(pos, trigger);
1583 else
1584 setExtent(pos, trigger);
1585}
1586
1587void FrameSelection::setEnd(const VisiblePosition &pos, EUserTriggered trigger)
1588{
1589 if (m_selection.isBaseFirst())
1590 setExtent(pos, trigger);
1591 else
1592 setBase(pos, trigger);
1593}
1594
1595void FrameSelection::setBase(const VisiblePosition &pos, EUserTriggered userTriggered)
1596{
1597 const bool selectionHasDirection = true;
1598 setSelection(VisibleSelection(pos.deepEquivalent(), m_selection.extent(), pos.affinity(), selectionHasDirection), defaultSetSelectionOptions(userTriggered));
1599}
1600
1601void FrameSelection::setExtent(const VisiblePosition &pos, EUserTriggered userTriggered)
1602{
1603 const bool selectionHasDirection = true;
1604 setSelection(VisibleSelection(m_selection.base(), pos.deepEquivalent(), pos.affinity(), selectionHasDirection), defaultSetSelectionOptions(userTriggered));
1605}
1606
1607void FrameSelection::setBase(const Position &pos, EAffinity affinity, EUserTriggered userTriggered)
1608{
1609 const bool selectionHasDirection = true;
1610 setSelection(VisibleSelection(pos, m_selection.extent(), affinity, selectionHasDirection), defaultSetSelectionOptions(userTriggered));
1611}
1612
1613void FrameSelection::setExtent(const Position &pos, EAffinity affinity, EUserTriggered userTriggered)
1614{
1615 const bool selectionHasDirection = true;
1616 setSelection(VisibleSelection(m_selection.base(), pos, affinity, selectionHasDirection), defaultSetSelectionOptions(userTriggered));
1617}
1618
1619void CaretBase::clearCaretRect()
1620{
1621 m_caretLocalRect = LayoutRect();
1622}
1623
1624bool CaretBase::updateCaretRect(Document* document, const VisiblePosition& caretPosition)
1625{
1626 document->updateLayoutIgnorePendingStylesheets();
1627 m_caretRectNeedsUpdate = false;
1628 RenderBlock* renderer;
1629 m_caretLocalRect = localCaretRectInRendererForCaretPainting(caretPosition, renderer);
1630 return !m_caretLocalRect.isEmpty();
1631}
1632
1633RenderBlock* FrameSelection::caretRendererWithoutUpdatingLayout() const
1634{
1635 return rendererForCaretPainting(m_selection.start().deprecatedNode());
1636}
1637
1638RenderBlock* DragCaretController::caretRenderer() const
1639{
1640 return rendererForCaretPainting(m_position.deepEquivalent().deprecatedNode());
1641}
1642
1643static bool isNonOrphanedCaret(const VisibleSelection& selection)
1644{
1645 return selection.isCaret() && !selection.start().isOrphan() && !selection.end().isOrphan();
1646}
1647
1648IntRect FrameSelection::absoluteCaretBounds(bool* insideFixed)
1649{
1650 if (!m_frame)
1651 return IntRect();
1652 updateSelectionByUpdatingLayoutOrStyle(*m_frame);
1653 recomputeCaretRect();
1654 if (insideFixed)
1655 *insideFixed = m_caretInsidePositionFixed;
1656 return m_absCaretBounds;
1657}
1658
1659static void repaintCaretForLocalRect(Node* node, const LayoutRect& rect)
1660{
1661 if (auto* caretPainter = rendererForCaretPainting(node))
1662 caretPainter->repaintRectangle(rect);
1663}
1664
1665bool FrameSelection::recomputeCaretRect()
1666{
1667 if (!shouldUpdateCaretRect())
1668 return false;
1669
1670 if (!m_frame)
1671 return false;
1672
1673 FrameView* v = m_frame->document()->view();
1674 if (!v)
1675 return false;
1676
1677 LayoutRect oldRect = localCaretRectWithoutUpdate();
1678
1679 RefPtr<Node> caretNode = m_previousCaretNode;
1680 if (shouldUpdateCaretRect()) {
1681 if (!isNonOrphanedCaret(m_selection))
1682 clearCaretRect();
1683 else {
1684 VisiblePosition visibleStart = m_selection.visibleStart();
1685 if (updateCaretRect(m_frame->document(), visibleStart)) {
1686 caretNode = visibleStart.deepEquivalent().deprecatedNode();
1687 m_absCaretBoundsDirty = true;
1688 }
1689 }
1690 }
1691 LayoutRect newRect = localCaretRectWithoutUpdate();
1692
1693 if (caretNode == m_previousCaretNode && oldRect == newRect && !m_absCaretBoundsDirty)
1694 return false;
1695
1696 IntRect oldAbsCaretBounds = m_absCaretBounds;
1697 bool isInsideFixed;
1698 m_absCaretBounds = absoluteBoundsForLocalCaretRect(rendererForCaretPainting(caretNode.get()), newRect, &isInsideFixed);
1699 m_caretInsidePositionFixed = isInsideFixed;
1700
1701 if (m_absCaretBoundsDirty && m_selection.isCaret()) // We should be able to always assert this condition.
1702 ASSERT(m_absCaretBounds == m_selection.visibleStart().absoluteCaretBounds());
1703
1704 m_absCaretBoundsDirty = false;
1705
1706 if (caretNode == m_previousCaretNode && oldAbsCaretBounds == m_absCaretBounds)
1707 return false;
1708
1709#if ENABLE(TEXT_CARET)
1710 if (RenderView* view = m_frame->document()->renderView()) {
1711 bool previousOrNewCaretNodeIsContentEditable = m_selection.isContentEditable() || (m_previousCaretNode && m_previousCaretNode->isContentEditable());
1712 if (shouldRepaintCaret(view, previousOrNewCaretNodeIsContentEditable)) {
1713 if (m_previousCaretNode)
1714 repaintCaretForLocalRect(m_previousCaretNode.get(), oldRect);
1715 m_previousCaretNode = caretNode;
1716 repaintCaretForLocalRect(caretNode.get(), newRect);
1717 }
1718 }
1719#endif
1720 return true;
1721}
1722
1723bool CaretBase::shouldRepaintCaret(const RenderView* view, bool isContentEditable) const
1724{
1725 ASSERT(view);
1726 Frame* frame = &view->frameView().frame(); // The frame where the selection started.
1727 bool caretBrowsing = frame && frame->settings().caretBrowsingEnabled();
1728 return (caretBrowsing || isContentEditable);
1729}
1730
1731void FrameSelection::invalidateCaretRect()
1732{
1733 if (!isCaret())
1734 return;
1735
1736 CaretBase::invalidateCaretRect(m_selection.start().deprecatedNode(), recomputeCaretRect());
1737}
1738
1739void CaretBase::invalidateCaretRect(Node* node, bool caretRectChanged)
1740{
1741 // EDIT FIXME: This is an unfortunate hack.
1742 // Basically, we can't trust this layout position since we
1743 // can't guarantee that the check to see if we are in unrendered
1744 // content will work at this point. We may have to wait for
1745 // a layout and re-render of the document to happen. So, resetting this
1746 // flag will cause another caret layout to happen the first time
1747 // that we try to paint the caret after this call. That one will work since
1748 // it happens after the document has accounted for any editing
1749 // changes which may have been done.
1750 // And, we need to leave this layout here so the caret moves right
1751 // away after clicking.
1752 m_caretRectNeedsUpdate = true;
1753
1754 if (caretRectChanged)
1755 return;
1756
1757 if (RenderView* view = node->document().renderView()) {
1758 if (shouldRepaintCaret(view, isEditableNode(*node)))
1759 repaintCaretForLocalRect(node, localCaretRectWithoutUpdate());
1760 }
1761}
1762
1763void FrameSelection::paintCaret(GraphicsContext& context, const LayoutPoint& paintOffset, const LayoutRect& clipRect)
1764{
1765 if (m_selection.isCaret() && m_caretPaint)
1766 CaretBase::paintCaret(m_selection.start().deprecatedNode(), context, paintOffset, clipRect);
1767}
1768
1769void CaretBase::paintCaret(Node* node, GraphicsContext& context, const LayoutPoint& paintOffset, const LayoutRect& clipRect) const
1770{
1771#if ENABLE(TEXT_CARET)
1772 if (m_caretVisibility == Hidden)
1773 return;
1774
1775 LayoutRect drawingRect = localCaretRectWithoutUpdate();
1776 if (auto* renderer = rendererForCaretPainting(node))
1777 renderer->flipForWritingMode(drawingRect);
1778 drawingRect.moveBy(roundedIntPoint(paintOffset));
1779 LayoutRect caret = intersection(drawingRect, clipRect);
1780 if (caret.isEmpty())
1781 return;
1782
1783 Color caretColor = Color::black;
1784 Element* element = is<Element>(*node) ? downcast<Element>(node) : node->parentElement();
1785 if (element && element->renderer()) {
1786 auto computeCaretColor = [] (const RenderStyle& elementStyle, const RenderStyle* rootEditableStyle) {
1787 // CSS value "auto" is treated as an invalid color.
1788 if (!elementStyle.caretColor().isValid() && rootEditableStyle) {
1789 auto rootEditableBackgroundColor = rootEditableStyle->visitedDependentColorWithColorFilter(CSSPropertyBackgroundColor);
1790 auto elementBackgroundColor = elementStyle.visitedDependentColorWithColorFilter(CSSPropertyBackgroundColor);
1791 auto disappearsIntoBackground = rootEditableBackgroundColor.blend(elementBackgroundColor) == rootEditableBackgroundColor;
1792 if (disappearsIntoBackground)
1793 return rootEditableStyle->visitedDependentColorWithColorFilter(CSSPropertyCaretColor);
1794 }
1795 return elementStyle.visitedDependentColorWithColorFilter(CSSPropertyCaretColor);
1796 };
1797 auto* rootEditableElement = node->rootEditableElement();
1798 auto* rootEditableStyle = rootEditableElement && rootEditableElement->renderer() ? &rootEditableElement->renderer()->style() : nullptr;
1799 caretColor = computeCaretColor(element->renderer()->style(), rootEditableStyle);
1800 }
1801
1802 context.fillRect(caret, caretColor);
1803#else
1804 UNUSED_PARAM(node);
1805 UNUSED_PARAM(context);
1806 UNUSED_PARAM(paintOffset);
1807 UNUSED_PARAM(clipRect);
1808#endif
1809}
1810
1811void FrameSelection::debugRenderer(RenderObject* renderer, bool selected) const
1812{
1813 if (is<Element>(*renderer->node())) {
1814 Element& element = downcast<Element>(*renderer->node());
1815 fprintf(stderr, "%s%s\n", selected ? "==> " : " ", element.localName().string().utf8().data());
1816 } else if (is<RenderText>(*renderer)) {
1817 RenderText& textRenderer = downcast<RenderText>(*renderer);
1818 if (textRenderer.text().isEmpty() || !textRenderer.firstTextBox()) {
1819 fprintf(stderr, "%s#text (empty)\n", selected ? "==> " : " ");
1820 return;
1821 }
1822
1823 static const int max = 36;
1824 String text = textRenderer.text();
1825 int textLength = text.length();
1826 if (selected) {
1827 int offset = 0;
1828 if (renderer->node() == m_selection.start().containerNode())
1829 offset = m_selection.start().computeOffsetInContainerNode();
1830 else if (renderer->node() == m_selection.end().containerNode())
1831 offset = m_selection.end().computeOffsetInContainerNode();
1832
1833 int pos;
1834 InlineTextBox* box = textRenderer.findNextInlineTextBox(offset, pos);
1835 text = text.substring(box->start(), box->len());
1836
1837 String show;
1838 int mid = max / 2;
1839 int caret = 0;
1840
1841 // text is shorter than max
1842 if (textLength < max) {
1843 show = text;
1844 caret = pos;
1845 } else if (pos - mid < 0) {
1846 // too few characters to left
1847 show = text.left(max - 3) + "...";
1848 caret = pos;
1849 } else if (pos - mid >= 0 && pos + mid <= textLength) {
1850 // enough characters on each side
1851 show = "..." + text.substring(pos - mid + 3, max - 6) + "...";
1852 caret = mid;
1853 } else {
1854 // too few characters on right
1855 show = "..." + text.right(max - 3);
1856 caret = pos - (textLength - show.length());
1857 }
1858
1859 show.replace('\n', ' ');
1860 show.replace('\r', ' ');
1861 fprintf(stderr, "==> #text : \"%s\" at offset %d\n", show.utf8().data(), pos);
1862 fprintf(stderr, " ");
1863 for (int i = 0; i < caret; i++)
1864 fprintf(stderr, " ");
1865 fprintf(stderr, "^\n");
1866 } else {
1867 if ((int)text.length() > max)
1868 text = text.left(max - 3) + "...";
1869 else
1870 text = text.left(max);
1871 fprintf(stderr, " #text : \"%s\"\n", text.utf8().data());
1872 }
1873 }
1874}
1875
1876bool FrameSelection::contains(const LayoutPoint& point) const
1877{
1878 // Treat a collapsed selection like no selection.
1879 if (!isRange())
1880 return false;
1881
1882 auto* document = m_frame->document();
1883 if (!document)
1884 return false;
1885
1886 HitTestResult result(point);
1887 document->hitTest(HitTestRequest(), result);
1888 Node* innerNode = result.innerNode();
1889 if (!innerNode || !innerNode->renderer())
1890 return false;
1891
1892 VisiblePosition visiblePos(innerNode->renderer()->positionForPoint(result.localPoint(), nullptr));
1893 if (visiblePos.isNull())
1894 return false;
1895
1896 if (m_selection.visibleStart().isNull() || m_selection.visibleEnd().isNull())
1897 return false;
1898
1899 Position start(m_selection.visibleStart().deepEquivalent());
1900 Position end(m_selection.visibleEnd().deepEquivalent());
1901 Position p(visiblePos.deepEquivalent());
1902
1903 return comparePositions(start, p) <= 0 && comparePositions(p, end) <= 0;
1904}
1905
1906// Workaround for the fact that it's hard to delete a frame.
1907// Call this after doing user-triggered selections to make it easy to delete the frame you entirely selected.
1908// Can't do this implicitly as part of every setSelection call because in some contexts it might not be good
1909// for the focus to move to another frame. So instead we call it from places where we are selecting with the
1910// mouse or the keyboard after setting the selection.
1911void FrameSelection::selectFrameElementInParentIfFullySelected()
1912{
1913 // Find the parent frame; if there is none, then we have nothing to do.
1914 Frame* parent = m_frame->tree().parent();
1915 if (!parent)
1916 return;
1917 Page* page = m_frame->page();
1918 if (!page)
1919 return;
1920
1921 // Check if the selection contains the entire frame contents; if not, then there is nothing to do.
1922 if (!isRange())
1923 return;
1924 if (!isStartOfDocument(selection().visibleStart()))
1925 return;
1926 if (!isEndOfDocument(selection().visibleEnd()))
1927 return;
1928
1929 // Get to the <iframe> or <frame> (or even <object>) element in the parent frame.
1930 Element* ownerElement = m_frame->ownerElement();
1931 if (!ownerElement)
1932 return;
1933 ContainerNode* ownerElementParent = ownerElement->parentNode();
1934 if (!ownerElementParent)
1935 return;
1936
1937 // This method's purpose is it to make it easier to select iframes (in order to delete them). Don't do anything if the iframe isn't deletable.
1938 if (!ownerElementParent->hasEditableStyle())
1939 return;
1940
1941 // Create compute positions before and after the element.
1942 unsigned ownerElementNodeIndex = ownerElement->computeNodeIndex();
1943 VisiblePosition beforeOwnerElement(VisiblePosition(Position(ownerElementParent, ownerElementNodeIndex, Position::PositionIsOffsetInAnchor)));
1944 VisiblePosition afterOwnerElement(VisiblePosition(Position(ownerElementParent, ownerElementNodeIndex + 1, Position::PositionIsOffsetInAnchor), VP_UPSTREAM_IF_POSSIBLE));
1945
1946 // Focus on the parent frame, and then select from before this element to after.
1947 VisibleSelection newSelection(beforeOwnerElement, afterOwnerElement);
1948 if (parent->selection().shouldChangeSelection(newSelection)) {
1949 page->focusController().setFocusedFrame(parent);
1950 parent->selection().setSelection(newSelection);
1951 }
1952}
1953
1954void FrameSelection::selectAll()
1955{
1956 Document* document = m_frame->document();
1957
1958 Element* focusedElement = document->focusedElement();
1959 if (is<HTMLSelectElement>(focusedElement)) {
1960 HTMLSelectElement& selectElement = downcast<HTMLSelectElement>(*focusedElement);
1961 if (selectElement.canSelectAll()) {
1962 selectElement.selectAll();
1963 return;
1964 }
1965 }
1966
1967 RefPtr<Node> root;
1968 Node* selectStartTarget = nullptr;
1969 if (m_selection.isContentEditable()) {
1970 root = highestEditableRoot(m_selection.start());
1971 if (Node* shadowRoot = m_selection.nonBoundaryShadowTreeRootNode())
1972 selectStartTarget = shadowRoot->shadowHost();
1973 else
1974 selectStartTarget = root.get();
1975 } else {
1976 if (m_selection.isNone() && focusedElement) {
1977 if (focusedElement->isTextField()) {
1978 downcast<HTMLTextFormControlElement>(*focusedElement).select();
1979 return;
1980 }
1981 root = focusedElement->nonBoundaryShadowTreeRootNode();
1982 } else
1983 root = m_selection.nonBoundaryShadowTreeRootNode();
1984
1985 if (root)
1986 selectStartTarget = root->shadowHost();
1987 else {
1988 root = document->documentElement();
1989 selectStartTarget = document->bodyOrFrameset();
1990 }
1991 }
1992 if (!root)
1993 return;
1994
1995 if (selectStartTarget) {
1996 auto event = Event::create(eventNames().selectstartEvent, Event::CanBubble::Yes, Event::IsCancelable::Yes);
1997 selectStartTarget->dispatchEvent(event);
1998 if (event->defaultPrevented())
1999 return;
2000 }
2001
2002 VisibleSelection newSelection(VisibleSelection::selectionFromContentsOfNode(root.get()));
2003
2004 if (shouldChangeSelection(newSelection)) {
2005 AXTextStateChangeIntent intent(AXTextStateChangeTypeSelectionExtend, AXTextSelection { AXTextSelectionDirectionDiscontiguous, AXTextSelectionGranularityAll, false });
2006 setSelection(newSelection, defaultSetSelectionOptions() | FireSelectEvent, intent);
2007 }
2008}
2009
2010bool FrameSelection::setSelectedRange(Range* range, EAffinity affinity, ShouldCloseTyping closeTyping, EUserTriggered userTriggered)
2011{
2012 if (!range)
2013 return false;
2014 ASSERT(&range->startContainer().document() == &range->endContainer().document());
2015
2016 VisibleSelection newSelection(*range, affinity);
2017
2018#if PLATFORM(IOS_FAMILY)
2019 // FIXME: Why do we need this check only in iOS?
2020 if (newSelection.isNone())
2021 return false;
2022#endif
2023
2024 OptionSet<SetSelectionOption> selectionOptions { ClearTypingStyle };
2025 if (closeTyping == ShouldCloseTyping::Yes)
2026 selectionOptions.add(CloseTyping);
2027
2028 if (userTriggered == UserTriggered) {
2029 FrameSelection trialFrameSelection;
2030
2031 trialFrameSelection.setSelection(newSelection, selectionOptions);
2032
2033 if (!shouldChangeSelection(trialFrameSelection.selection()))
2034 return false;
2035
2036 selectionOptions.add(IsUserTriggered);
2037 }
2038
2039 setSelection(newSelection, selectionOptions);
2040 return true;
2041}
2042
2043void FrameSelection::focusedOrActiveStateChanged()
2044{
2045 bool activeAndFocused = isFocusedAndActive();
2046 Ref<Document> document(*m_frame->document());
2047
2048 document->updateStyleIfNeeded();
2049
2050#if USE(UIKIT_EDITING)
2051 // Caret blinking (blinks | does not blink)
2052 if (activeAndFocused)
2053 setSelectionFromNone();
2054 setCaretVisible(activeAndFocused);
2055#else
2056 // Because RenderObject::selectionBackgroundColor() and
2057 // RenderObject::selectionForegroundColor() check if the frame is active,
2058 // we have to update places those colors were painted.
2059 if (RenderView* view = document->renderView())
2060 view->selection().repaint();
2061
2062 // Caret appears in the active frame.
2063 if (activeAndFocused)
2064 setSelectionFromNone();
2065 setCaretVisibility(activeAndFocused ? Visible : Hidden);
2066
2067 // Because StyleResolver::checkOneSelector() and
2068 // RenderTheme::isFocused() check if the frame is active, we have to
2069 // update style and theme state that depended on those.
2070 if (Element* element = document->focusedElement()) {
2071 element->invalidateStyleForSubtree();
2072 if (RenderObject* renderer = element->renderer())
2073 if (renderer && renderer->style().hasAppearance())
2074 renderer->theme().stateChanged(*renderer, ControlStates::FocusState);
2075 }
2076#endif
2077}
2078
2079void FrameSelection::pageActivationChanged()
2080{
2081 focusedOrActiveStateChanged();
2082}
2083
2084void FrameSelection::setFocused(bool flag)
2085{
2086 if (m_focused == flag)
2087 return;
2088 m_focused = flag;
2089
2090 focusedOrActiveStateChanged();
2091}
2092
2093bool FrameSelection::isFocusedAndActive() const
2094{
2095 return m_focused && m_frame->page() && m_frame->page()->focusController().isActive();
2096}
2097
2098#if ENABLE(TEXT_CARET)
2099inline static bool shouldStopBlinkingDueToTypingCommand(Frame* frame)
2100{
2101 return frame->editor().lastEditCommand() && frame->editor().lastEditCommand()->shouldStopCaretBlinking();
2102}
2103#endif
2104
2105void FrameSelection::updateAppearance()
2106{
2107#if PLATFORM(IOS_FAMILY)
2108 if (!m_updateAppearanceEnabled)
2109 return;
2110#endif
2111
2112 // Paint a block cursor instead of a caret in overtype mode unless the caret is at the end of a line (in this case
2113 // the FrameSelection will paint a blinking caret as usual).
2114 VisibleSelection oldSelection = selection();
2115
2116#if ENABLE(TEXT_CARET)
2117 bool paintBlockCursor = m_shouldShowBlockCursor && m_selection.isCaret() && !isLogicalEndOfLine(m_selection.visibleEnd());
2118 bool caretRectChangedOrCleared = recomputeCaretRect();
2119
2120 bool caretBrowsing = m_frame->settings().caretBrowsingEnabled();
2121 bool shouldBlink = !paintBlockCursor && caretIsVisible() && isCaret() && (oldSelection.isContentEditable() || caretBrowsing);
2122
2123 // If the caret moved, stop the blink timer so we can restart with a
2124 // black caret in the new location.
2125 if (caretRectChangedOrCleared || !shouldBlink || shouldStopBlinkingDueToTypingCommand(m_frame))
2126 m_caretBlinkTimer.stop();
2127
2128 // Start blinking with a black caret. Be sure not to restart if we're
2129 // already blinking in the right location.
2130 if (shouldBlink && !m_caretBlinkTimer.isActive()) {
2131 if (Seconds blinkInterval = RenderTheme::singleton().caretBlinkInterval())
2132 m_caretBlinkTimer.startRepeating(blinkInterval);
2133
2134 if (!m_caretPaint) {
2135 m_caretPaint = true;
2136 invalidateCaretRect();
2137 }
2138 }
2139#endif
2140
2141 RenderView* view = m_frame->contentRenderer();
2142 if (!view)
2143 return;
2144
2145 // Construct a new VisibleSolution, since m_selection is not necessarily valid, and the following steps
2146 // assume a valid selection. See <https://bugs.webkit.org/show_bug.cgi?id=69563> and <rdar://problem/10232866>.
2147#if ENABLE(TEXT_CARET)
2148 VisiblePosition endVisiblePosition = paintBlockCursor ? modifyExtendingForward(CharacterGranularity) : oldSelection.visibleEnd();
2149 VisibleSelection selection(oldSelection.visibleStart(), endVisiblePosition);
2150#else
2151 VisibleSelection selection(oldSelection.visibleStart(), oldSelection.visibleEnd());
2152#endif
2153
2154 if (!selection.isRange()) {
2155 view->selection().clear();
2156 return;
2157 }
2158
2159 // Use the rightmost candidate for the start of the selection, and the leftmost candidate for the end of the selection.
2160 // Example: foo <a>bar</a>. Imagine that a line wrap occurs after 'foo', and that 'bar' is selected. If we pass [foo, 3]
2161 // as the start of the selection, the selection painting code will think that content on the line containing 'foo' is selected
2162 // and will fill the gap before 'bar'.
2163 Position startPos = selection.start();
2164 Position candidate = startPos.downstream();
2165 if (candidate.isCandidate())
2166 startPos = candidate;
2167 Position endPos = selection.end();
2168 candidate = endPos.upstream();
2169 if (candidate.isCandidate())
2170 endPos = candidate;
2171
2172 // We can get into a state where the selection endpoints map to the same VisiblePosition when a selection is deleted
2173 // because we don't yet notify the FrameSelection of text removal.
2174 if (startPos.isNotNull() && endPos.isNotNull() && selection.visibleStart() != selection.visibleEnd()) {
2175 RenderObject* startRenderer = startPos.deprecatedNode()->renderer();
2176 int startOffset = startPos.deprecatedEditingOffset();
2177 RenderObject* endRenderer = endPos.deprecatedNode()->renderer();
2178 int endOffset = endPos.deprecatedEditingOffset();
2179 ASSERT(startOffset >= 0 && endOffset >= 0);
2180 view->selection().set({ startRenderer, endRenderer, static_cast<unsigned>(startOffset), static_cast<unsigned>(endOffset) });
2181 }
2182}
2183
2184void FrameSelection::setCaretVisibility(CaretVisibility visibility)
2185{
2186 if (caretVisibility() == visibility)
2187 return;
2188
2189 // FIXME: We shouldn't trigger a synchronous layout here.
2190 if (m_frame)
2191 updateSelectionByUpdatingLayoutOrStyle(*m_frame);
2192
2193#if ENABLE(TEXT_CARET)
2194 if (m_caretPaint) {
2195 m_caretPaint = false;
2196 invalidateCaretRect();
2197 }
2198 CaretBase::setCaretVisibility(visibility);
2199#endif
2200
2201 updateAppearance();
2202}
2203
2204void FrameSelection::caretBlinkTimerFired()
2205{
2206#if ENABLE(TEXT_CARET)
2207 ASSERT(caretIsVisible());
2208 ASSERT(isCaret());
2209 bool caretPaint = m_caretPaint;
2210 if (isCaretBlinkingSuspended() && caretPaint)
2211 return;
2212 m_caretPaint = !caretPaint;
2213 invalidateCaretRect();
2214#endif
2215}
2216
2217// Helper function that tells whether a particular node is an element that has an entire
2218// Frame and FrameView, a <frame>, <iframe>, or <object>.
2219static bool isFrameElement(const Node* n)
2220{
2221 if (!n)
2222 return false;
2223 RenderObject* renderer = n->renderer();
2224 if (!is<RenderWidget>(renderer))
2225 return false;
2226 Widget* widget = downcast<RenderWidget>(*renderer).widget();
2227 return widget && widget->isFrameView();
2228}
2229
2230void FrameSelection::setFocusedElementIfNeeded()
2231{
2232 if (isNone() || !isFocused())
2233 return;
2234
2235 bool caretBrowsing = m_frame->settings().caretBrowsingEnabled();
2236 if (caretBrowsing) {
2237 if (Element* anchor = enclosingAnchorElement(m_selection.base())) {
2238 m_frame->page()->focusController().setFocusedElement(anchor, *m_frame);
2239 return;
2240 }
2241 }
2242
2243 if (Element* target = m_selection.rootEditableElement()) {
2244 // Walk up the DOM tree to search for an element to focus.
2245 while (target) {
2246 // We don't want to set focus on a subframe when selecting in a parent frame,
2247 // so add the !isFrameElement check here. There's probably a better way to make this
2248 // work in the long term, but this is the safest fix at this time.
2249 if (target->isMouseFocusable() && !isFrameElement(target)) {
2250 m_frame->page()->focusController().setFocusedElement(target, *m_frame);
2251 return;
2252 }
2253 target = target->parentOrShadowHostElement();
2254 }
2255 m_frame->document()->setFocusedElement(nullptr);
2256 }
2257
2258 if (caretBrowsing)
2259 m_frame->page()->focusController().setFocusedElement(nullptr, *m_frame);
2260}
2261
2262void DragCaretController::paintDragCaret(Frame* frame, GraphicsContext& p, const LayoutPoint& paintOffset, const LayoutRect& clipRect) const
2263{
2264#if ENABLE(TEXT_CARET)
2265 if (m_position.deepEquivalent().deprecatedNode()->document().frame() == frame)
2266 paintCaret(m_position.deepEquivalent().deprecatedNode(), p, paintOffset, clipRect);
2267#else
2268 UNUSED_PARAM(frame);
2269 UNUSED_PARAM(p);
2270 UNUSED_PARAM(paintOffset);
2271 UNUSED_PARAM(clipRect);
2272#endif
2273}
2274
2275RefPtr<MutableStyleProperties> FrameSelection::copyTypingStyle() const
2276{
2277 if (!m_typingStyle || !m_typingStyle->style())
2278 return nullptr;
2279 return m_typingStyle->style()->mutableCopy();
2280}
2281
2282bool FrameSelection::shouldDeleteSelection(const VisibleSelection& selection) const
2283{
2284#if PLATFORM(IOS_FAMILY)
2285 if (m_frame->selectionChangeCallbacksDisabled())
2286 return true;
2287#endif
2288 return m_frame->editor().client()->shouldDeleteRange(selection.toNormalizedRange().get());
2289}
2290
2291FloatRect FrameSelection::selectionBounds(bool clipToVisibleContent) const
2292{
2293 if (!m_frame->document())
2294 return LayoutRect();
2295
2296 updateSelectionByUpdatingLayoutOrStyle(*m_frame);
2297 auto* renderView = m_frame->contentRenderer();
2298 if (!renderView)
2299 return LayoutRect();
2300
2301 auto& selection = renderView->selection();
2302 auto selectionRect = clipToVisibleContent ? selection.boundsClippedToVisibleContent() : selection.bounds();
2303 return clipToVisibleContent ? intersection(selectionRect, renderView->frameView().visibleContentRect(ScrollableArea::LegacyIOSDocumentVisibleRect)) : selectionRect;
2304}
2305
2306void FrameSelection::getClippedVisibleTextRectangles(Vector<FloatRect>& rectangles, TextRectangleHeight textRectHeight) const
2307{
2308 RenderView* root = m_frame->contentRenderer();
2309 if (!root)
2310 return;
2311
2312 Vector<FloatRect> textRects;
2313 getTextRectangles(textRects, textRectHeight);
2314
2315 FloatRect visibleContentRect = m_frame->view()->visibleContentRect(ScrollableArea::LegacyIOSDocumentVisibleRect);
2316
2317 for (const auto& rect : textRects) {
2318 FloatRect intersectionRect = intersection(rect, visibleContentRect);
2319 if (!intersectionRect.isEmpty())
2320 rectangles.append(intersectionRect);
2321 }
2322}
2323
2324void FrameSelection::getTextRectangles(Vector<FloatRect>& rectangles, TextRectangleHeight textRectHeight) const
2325{
2326 RefPtr<Range> range = toNormalizedRange();
2327 if (!range)
2328 return;
2329
2330 Vector<FloatQuad> quads;
2331 range->absoluteTextQuads(quads, textRectHeight == TextRectangleHeight::SelectionHeight);
2332
2333 for (const auto& quad : quads)
2334 rectangles.append(quad.boundingBox());
2335}
2336
2337// Scans logically forward from "start", including any child frames.
2338static HTMLFormElement* scanForForm(Element* start)
2339{
2340 if (!start)
2341 return nullptr;
2342
2343 auto descendants = descendantsOfType<HTMLElement>(start->document());
2344 for (auto it = descendants.from(*start), end = descendants.end(); it != end; ++it) {
2345 HTMLElement& element = *it;
2346 if (is<HTMLFormElement>(element))
2347 return &downcast<HTMLFormElement>(element);
2348 if (is<HTMLFormControlElement>(element))
2349 return downcast<HTMLFormControlElement>(element).form();
2350 if (is<HTMLFrameElementBase>(element)) {
2351 Document* contentDocument = downcast<HTMLFrameElementBase>(element).contentDocument();
2352 if (!contentDocument)
2353 continue;
2354 if (HTMLFormElement* frameResult = scanForForm(contentDocument->documentElement()))
2355 return frameResult;
2356 }
2357 }
2358 return nullptr;
2359}
2360
2361// We look for either the form containing the current focus, or for one immediately after it
2362HTMLFormElement* FrameSelection::currentForm() const
2363{
2364 // Start looking either at the active (first responder) node, or where the selection is.
2365 Element* start = m_frame->document()->focusedElement();
2366 if (!start)
2367 start = m_selection.start().element();
2368 if (!start)
2369 return nullptr;
2370
2371 if (auto form = lineageOfType<HTMLFormElement>(*start).first())
2372 return form;
2373 if (auto formControl = lineageOfType<HTMLFormControlElement>(*start).first())
2374 return formControl->form();
2375
2376 // Try walking forward in the node tree to find a form element.
2377 return scanForForm(start);
2378}
2379
2380void FrameSelection::revealSelection(SelectionRevealMode revealMode, const ScrollAlignment& alignment, RevealExtentOption revealExtentOption)
2381{
2382 if (revealMode == SelectionRevealMode::DoNotReveal)
2383 return;
2384
2385 LayoutRect rect;
2386 bool insideFixed = false;
2387 switch (m_selection.selectionType()) {
2388 case VisibleSelection::NoSelection:
2389 return;
2390 case VisibleSelection::CaretSelection:
2391 rect = absoluteCaretBounds(&insideFixed);
2392 break;
2393 case VisibleSelection::RangeSelection:
2394 rect = revealExtentOption == RevealExtent ? VisiblePosition(m_selection.extent()).absoluteCaretBounds() : enclosingIntRect(selectionBounds(false));
2395 break;
2396 }
2397
2398 Position start = m_selection.start();
2399 ASSERT(start.deprecatedNode());
2400 if (start.deprecatedNode() && start.deprecatedNode()->renderer()) {
2401#if PLATFORM(IOS_FAMILY)
2402 if (RenderLayer* layer = start.deprecatedNode()->renderer()->enclosingLayer()) {
2403 if (!m_scrollingSuppressCount) {
2404 layer->setAdjustForIOSCaretWhenScrolling(true);
2405 layer->scrollRectToVisible(rect, insideFixed, { revealMode, alignment, alignment, ShouldAllowCrossOriginScrolling::Yes });
2406 layer->setAdjustForIOSCaretWhenScrolling(false);
2407 updateAppearance();
2408 if (m_frame->page())
2409 m_frame->page()->chrome().client().notifyRevealedSelectionByScrollingFrame(*m_frame);
2410 }
2411 }
2412#else
2413 // FIXME: This code only handles scrolling the startContainer's layer, but
2414 // the selection rect could intersect more than just that.
2415 // See <rdar://problem/4799899>.
2416 if (start.deprecatedNode()->renderer()->scrollRectToVisible(rect, insideFixed, { revealMode, alignment, alignment, ShouldAllowCrossOriginScrolling::Yes }))
2417 updateAppearance();
2418#endif
2419 }
2420}
2421
2422void FrameSelection::setSelectionFromNone()
2423{
2424 // Put a caret inside the body if the entire frame is editable (either the
2425 // entire WebView is editable or designMode is on for this document).
2426
2427 Document* document = m_frame->document();
2428#if !PLATFORM(IOS_FAMILY)
2429 bool caretBrowsing = m_frame->settings().caretBrowsingEnabled();
2430 if (!isNone() || !(document->hasEditableStyle() || caretBrowsing))
2431 return;
2432#else
2433 if (!document || !(isNone() || isStartOfDocument(VisiblePosition(m_selection.start(), m_selection.affinity()))) || !document->hasEditableStyle())
2434 return;
2435#endif
2436
2437 if (auto* body = document->body())
2438 setSelection(VisibleSelection(firstPositionInOrBeforeNode(body), DOWNSTREAM));
2439}
2440
2441bool FrameSelection::shouldChangeSelection(const VisibleSelection& newSelection) const
2442{
2443#if PLATFORM(IOS_FAMILY)
2444 if (m_frame->selectionChangeCallbacksDisabled())
2445 return true;
2446#endif
2447 return m_frame->editor().shouldChangeSelection(selection(), newSelection, newSelection.affinity(), false);
2448}
2449
2450bool FrameSelection::dispatchSelectStart()
2451{
2452 Node* selectStartTarget = m_selection.extent().containerNode();
2453 if (!selectStartTarget)
2454 return true;
2455
2456 auto event = Event::create(eventNames().selectstartEvent, Event::CanBubble::Yes, Event::IsCancelable::Yes);
2457 selectStartTarget->dispatchEvent(event);
2458 return !event->defaultPrevented();
2459}
2460
2461void FrameSelection::setShouldShowBlockCursor(bool shouldShowBlockCursor)
2462{
2463 m_shouldShowBlockCursor = shouldShowBlockCursor;
2464
2465 m_frame->document()->updateLayoutIgnorePendingStylesheets();
2466
2467 updateAppearance();
2468}
2469
2470void FrameSelection::updateAppearanceAfterLayout()
2471{
2472 m_appearanceUpdateTimer.stop();
2473 updateAppearanceAfterLayoutOrStyleChange();
2474}
2475
2476void FrameSelection::scheduleAppearanceUpdateAfterStyleChange()
2477{
2478 m_appearanceUpdateTimer.startOneShot(0_s);
2479}
2480
2481void FrameSelection::appearanceUpdateTimerFired()
2482{
2483 Ref<Frame> protectedFrame(*m_frame);
2484 updateAppearanceAfterLayoutOrStyleChange();
2485}
2486
2487void FrameSelection::updateAppearanceAfterLayoutOrStyleChange()
2488{
2489 if (auto* client = m_frame->editor().client())
2490 client->updateEditorStateAfterLayoutIfEditabilityChanged();
2491
2492 setCaretRectNeedsUpdate();
2493 updateAndRevealSelection(m_selectionRevealIntent);
2494 updateDataDetectorsForSelection();
2495}
2496
2497#if ENABLE(TREE_DEBUGGING)
2498
2499void FrameSelection::formatForDebugger(char* buffer, unsigned length) const
2500{
2501 m_selection.formatForDebugger(buffer, length);
2502}
2503
2504void FrameSelection::showTreeForThis() const
2505{
2506 m_selection.showTreeForThis();
2507}
2508
2509#endif
2510
2511#if PLATFORM(IOS_FAMILY)
2512void FrameSelection::expandSelectionToElementContainingCaretSelection()
2513{
2514 RefPtr<Range> range = elementRangeContainingCaretSelection();
2515 if (!range)
2516 return;
2517 VisibleSelection selection(*range, DOWNSTREAM);
2518 setSelection(selection);
2519}
2520
2521RefPtr<Range> FrameSelection::elementRangeContainingCaretSelection() const
2522{
2523 if (m_selection.isNone())
2524 return nullptr;
2525
2526 VisibleSelection selection = m_selection;
2527 if (selection.isNone())
2528 return nullptr;
2529
2530 VisiblePosition visiblePos(selection.start(), VP_DEFAULT_AFFINITY);
2531 if (visiblePos.isNull())
2532 return nullptr;
2533
2534 Node* node = visiblePos.deepEquivalent().deprecatedNode();
2535 Element* element = deprecatedEnclosingBlockFlowElement(node);
2536 if (!element)
2537 return nullptr;
2538
2539 Position startPos = createLegacyEditingPosition(element, 0);
2540 Position endPos = createLegacyEditingPosition(element, element->countChildNodes());
2541
2542 VisiblePosition startVisiblePos(startPos, VP_DEFAULT_AFFINITY);
2543 VisiblePosition endVisiblePos(endPos, VP_DEFAULT_AFFINITY);
2544 if (startVisiblePos.isNull() || endVisiblePos.isNull())
2545 return nullptr;
2546
2547 selection.setBase(startVisiblePos);
2548 selection.setExtent(endVisiblePos);
2549
2550 return selection.toNormalizedRange();
2551}
2552
2553void FrameSelection::expandSelectionToWordContainingCaretSelection()
2554{
2555 VisibleSelection selection(wordSelectionContainingCaretSelection(m_selection));
2556 if (selection.isCaretOrRange())
2557 setSelection(selection);
2558}
2559
2560RefPtr<Range> FrameSelection::wordRangeContainingCaretSelection()
2561{
2562 return wordSelectionContainingCaretSelection(m_selection).toNormalizedRange();
2563}
2564
2565void FrameSelection::expandSelectionToStartOfWordContainingCaretSelection()
2566{
2567 if (m_selection.isNone() || isStartOfDocument(m_selection.start()))
2568 return;
2569
2570 VisiblePosition s1(m_selection.start());
2571 VisiblePosition e1(m_selection.end());
2572
2573 VisibleSelection expanded(wordSelectionContainingCaretSelection(m_selection));
2574 VisiblePosition s2(expanded.start());
2575
2576 // Don't allow the start to become greater after the expansion.
2577 if (s2.isNull() || s2 > s1)
2578 s2 = s1;
2579
2580 moveTo(s2, e1);
2581}
2582
2583UChar FrameSelection::characterInRelationToCaretSelection(int amount) const
2584{
2585 if (m_selection.isNone())
2586 return 0;
2587
2588 VisibleSelection selection = m_selection;
2589 ASSERT(selection.isCaretOrRange());
2590
2591 VisiblePosition visiblePosition(selection.start(), VP_DEFAULT_AFFINITY);
2592
2593 if (amount < 0) {
2594 int count = abs(amount);
2595 for (int i = 0; i < count; i++)
2596 visiblePosition = visiblePosition.previous();
2597 return visiblePosition.characterBefore();
2598 }
2599 for (int i = 0; i < amount; i++)
2600 visiblePosition = visiblePosition.next();
2601 return visiblePosition.characterAfter();
2602}
2603
2604UChar FrameSelection::characterBeforeCaretSelection() const
2605{
2606 if (m_selection.isNone())
2607 return 0;
2608
2609 VisibleSelection selection = m_selection;
2610 ASSERT(selection.isCaretOrRange());
2611
2612 VisiblePosition visiblePosition(selection.start(), VP_DEFAULT_AFFINITY);
2613 return visiblePosition.characterBefore();
2614}
2615
2616UChar FrameSelection::characterAfterCaretSelection() const
2617{
2618 if (m_selection.isNone())
2619 return 0;
2620
2621 VisibleSelection selection = m_selection;
2622 ASSERT(selection.isCaretOrRange());
2623
2624 VisiblePosition visiblePosition(selection.end(), VP_DEFAULT_AFFINITY);
2625 return visiblePosition.characterAfter();
2626}
2627
2628int FrameSelection::wordOffsetInRange(const Range *range) const
2629{
2630 if (!range)
2631 return -1;
2632
2633 VisibleSelection selection = m_selection;
2634 if (!selection.isCaret())
2635 return -1;
2636
2637 // FIXME: This will only work in cases where the selection remains in
2638 // the same node after it is expanded. Improve to handle more complicated
2639 // cases.
2640 int result = selection.start().deprecatedEditingOffset() - range->startOffset();
2641 if (result < 0)
2642 result = 0;
2643 return result;
2644}
2645
2646bool FrameSelection::spaceFollowsWordInRange(const Range *range) const
2647{
2648 if (!range)
2649 return false;
2650 Node& node = range->endContainer();
2651 int endOffset = range->endOffset();
2652 VisiblePosition pos(createLegacyEditingPosition(&node, endOffset), VP_DEFAULT_AFFINITY);
2653 return isSpaceOrNewline(pos.characterAfter());
2654}
2655
2656bool FrameSelection::selectionAtDocumentStart() const
2657{
2658 VisibleSelection selection = m_selection;
2659 if (selection.isNone())
2660 return false;
2661
2662 Position startPos(selection.start());
2663 VisiblePosition pos(createLegacyEditingPosition(startPos.deprecatedNode(), startPos.deprecatedEditingOffset()), VP_DEFAULT_AFFINITY);
2664 if (pos.isNull())
2665 return false;
2666
2667 return isStartOfDocument(pos);
2668}
2669
2670bool FrameSelection::selectionAtSentenceStart() const
2671{
2672 VisibleSelection selection = m_selection;
2673 if (selection.isNone())
2674 return false;
2675
2676 return actualSelectionAtSentenceStart(selection);
2677}
2678
2679bool FrameSelection::selectionAtWordStart() const
2680{
2681 VisibleSelection selection = m_selection;
2682 if (selection.isNone())
2683 return false;
2684
2685 Position startPos(selection.start());
2686 VisiblePosition pos(createLegacyEditingPosition(startPos.deprecatedNode(), startPos.deprecatedEditingOffset()), VP_DEFAULT_AFFINITY);
2687 if (pos.isNull())
2688 return false;
2689
2690 if (isStartOfParagraph(pos))
2691 return true;
2692
2693 bool result = true;
2694 unsigned previousCount = 0;
2695 for (pos = pos.previous(); !pos.isNull(); pos = pos.previous()) {
2696 previousCount++;
2697 if (isStartOfParagraph(pos)) {
2698 if (previousCount == 1)
2699 result = false;
2700 break;
2701 }
2702 UChar c(pos.characterAfter());
2703 if (c) {
2704 result = isSpaceOrNewline(c) || c == noBreakSpace || (u_ispunct(c) && c != ',' && c != '-' && c != '\'');
2705 break;
2706 }
2707 }
2708
2709 return result;
2710}
2711
2712RefPtr<Range> FrameSelection::rangeByMovingCurrentSelection(int amount) const
2713{
2714 return rangeByAlteringCurrentSelection(AlterationMove, amount);
2715}
2716
2717RefPtr<Range> FrameSelection::rangeByExtendingCurrentSelection(int amount) const
2718{
2719 return rangeByAlteringCurrentSelection(AlterationExtend, amount);
2720}
2721
2722void FrameSelection::selectRangeOnElement(unsigned location, unsigned length, Node& node)
2723{
2724 RefPtr<Range> resultRange = m_frame->document()->createRange();
2725 resultRange->setStart(node, location);
2726 resultRange->setEnd(node, location + length);
2727 VisibleSelection selection = VisibleSelection(*resultRange, SEL_DEFAULT_AFFINITY);
2728 // FIXME: The second argument was "true" which implicitly converted to option "FireSelectEvent". Is this correct?
2729 setSelection(selection, { FireSelectEvent });
2730}
2731
2732VisibleSelection FrameSelection::wordSelectionContainingCaretSelection(const VisibleSelection& selection)
2733{
2734 if (selection.isNone())
2735 return VisibleSelection();
2736
2737 ASSERT(selection.isCaretOrRange());
2738 FrameSelection frameSelection;
2739 frameSelection.setSelection(selection);
2740
2741 Position startPosBeforeExpansion(selection.start());
2742 Position endPosBeforeExpansion(selection.end());
2743 VisiblePosition startVisiblePosBeforeExpansion(startPosBeforeExpansion, VP_DEFAULT_AFFINITY);
2744 VisiblePosition endVisiblePosBeforeExpansion(endPosBeforeExpansion, VP_DEFAULT_AFFINITY);
2745 if (endVisiblePosBeforeExpansion.isNull())
2746 return VisibleSelection();
2747
2748 if (isEndOfParagraph(endVisiblePosBeforeExpansion)) {
2749 UChar c(endVisiblePosBeforeExpansion.characterBefore());
2750 if (isSpaceOrNewline(c) || c == noBreakSpace) {
2751 // End of paragraph with space.
2752 return VisibleSelection();
2753 }
2754 }
2755
2756 // If at end of paragraph, move backwards one character.
2757 // This has the effect of selecting the word on the line (which is
2758 // what we want, rather than selecting past the end of the line).
2759 if (isEndOfParagraph(endVisiblePosBeforeExpansion) && !isStartOfParagraph(endVisiblePosBeforeExpansion))
2760 frameSelection.modify(FrameSelection::AlterationMove, DirectionBackward, CharacterGranularity);
2761
2762 VisibleSelection newSelection = frameSelection.selection();
2763 newSelection.expandUsingGranularity(WordGranularity);
2764 frameSelection.setSelection(newSelection, defaultSetSelectionOptions(), AXTextStateChangeIntent(), AlignCursorOnScrollIfNeeded, frameSelection.granularity());
2765
2766 Position startPos(frameSelection.selection().start());
2767 Position endPos(frameSelection.selection().end());
2768
2769 // Expansion cannot be allowed to change selection so that it is no longer
2770 // touches (or contains) the original, unexpanded selection.
2771 // Enforce this on the way into these additional calculations to give them
2772 // the best chance to yield a suitable answer.
2773 if (startPos > startPosBeforeExpansion)
2774 startPos = startPosBeforeExpansion;
2775 if (endPos < endPosBeforeExpansion)
2776 endPos = endPosBeforeExpansion;
2777
2778 VisiblePosition startVisiblePos(startPos, VP_DEFAULT_AFFINITY);
2779 VisiblePosition endVisiblePos(endPos, VP_DEFAULT_AFFINITY);
2780
2781 if (startVisiblePos.isNull() || endVisiblePos.isNull()) {
2782 // Start or end is nil
2783 return VisibleSelection();
2784 }
2785
2786 if (isEndOfLine(endVisiblePosBeforeExpansion)) {
2787 VisiblePosition previous(endVisiblePos.previous());
2788 if (previous == endVisiblePos) {
2789 // Empty document
2790 return VisibleSelection();
2791 }
2792 UChar c(previous.characterAfter());
2793 if (isSpaceOrNewline(c) || c == noBreakSpace) {
2794 // Space at end of line
2795 return VisibleSelection();
2796 }
2797 }
2798
2799 // Expansion has selected past end of line.
2800 // Try repositioning backwards.
2801 if (isEndOfLine(startVisiblePos) && isStartOfLine(endVisiblePos)) {
2802 VisiblePosition previous(startVisiblePos.previous());
2803 if (isEndOfLine(previous)) {
2804 // On empty line
2805 return VisibleSelection();
2806 }
2807 UChar c(previous.characterAfter());
2808 if (isSpaceOrNewline(c) || c == noBreakSpace) {
2809 // Space at end of line
2810 return VisibleSelection();
2811 }
2812 frameSelection.moveTo(startVisiblePos);
2813 frameSelection.modify(FrameSelection::AlterationExtend, DirectionBackward, WordGranularity);
2814 startPos = frameSelection.selection().start();
2815 endPos = frameSelection.selection().end();
2816 startVisiblePos = VisiblePosition(startPos, VP_DEFAULT_AFFINITY);
2817 endVisiblePos = VisiblePosition(endPos, VP_DEFAULT_AFFINITY);
2818 if (startVisiblePos.isNull() || endVisiblePos.isNull()) {
2819 // Start or end is nil
2820 return VisibleSelection();
2821 }
2822 }
2823
2824 // Now loop backwards until we find a non-space.
2825 while (endVisiblePos != startVisiblePos) {
2826 VisiblePosition previous(endVisiblePos.previous());
2827 UChar c(previous.characterAfter());
2828 if (!isSpaceOrNewline(c) && c != noBreakSpace)
2829 break;
2830 endVisiblePos = previous;
2831 }
2832
2833 // Expansion cannot be allowed to change selection so that it is no longer
2834 // touches (or contains) the original, unexpanded selection.
2835 // Enforce this on the way out of the function to preserve the invariant.
2836 if (startVisiblePos > startVisiblePosBeforeExpansion)
2837 startVisiblePos = startVisiblePosBeforeExpansion;
2838 if (endVisiblePos < endVisiblePosBeforeExpansion)
2839 endVisiblePos = endVisiblePosBeforeExpansion;
2840
2841 return VisibleSelection(startVisiblePos, endVisiblePos);
2842}
2843
2844bool FrameSelection::actualSelectionAtSentenceStart(const VisibleSelection& sel) const
2845{
2846 Position startPos(sel.start());
2847 VisiblePosition pos(createLegacyEditingPosition(startPos.deprecatedNode(), startPos.deprecatedEditingOffset()), VP_DEFAULT_AFFINITY);
2848 if (pos.isNull())
2849 return false;
2850
2851 if (isStartOfParagraph(pos))
2852 return true;
2853
2854 bool result = true;
2855 bool sawSpace = false;
2856 unsigned previousCount = 0;
2857 for (pos = pos.previous(); !pos.isNull(); pos = pos.previous()) {
2858 previousCount++;
2859 if (isStartOfParagraph(pos)) {
2860 if (previousCount == 1 || (previousCount == 2 && sawSpace))
2861 result = false;
2862 break;
2863 }
2864 UChar c(pos.characterAfter());
2865 if (c) {
2866 if (isSpaceOrNewline(c) || c == noBreakSpace) {
2867 sawSpace = true;
2868 }
2869 else {
2870 result = (c == '.' || c == '!' || c == '?');
2871 break;
2872 }
2873 }
2874 }
2875
2876 return result;
2877}
2878
2879RefPtr<Range> FrameSelection::rangeByAlteringCurrentSelection(EAlteration alteration, int amount) const
2880{
2881 if (m_selection.isNone())
2882 return nullptr;
2883
2884 if (!amount)
2885 return toNormalizedRange();
2886
2887 FrameSelection frameSelection;
2888 frameSelection.setSelection(m_selection);
2889 SelectionDirection direction = amount > 0 ? DirectionForward : DirectionBackward;
2890 for (int i = 0; i < abs(amount); i++)
2891 frameSelection.modify(alteration, direction, CharacterGranularity);
2892 return frameSelection.toNormalizedRange();
2893}
2894
2895void FrameSelection::clearCurrentSelection()
2896{
2897 setSelection(VisibleSelection());
2898}
2899
2900void FrameSelection::setCaretBlinks(bool caretBlinks)
2901{
2902 if (m_caretBlinks == caretBlinks)
2903 return;
2904#if ENABLE(TEXT_CARET)
2905 m_frame->document()->updateLayoutIgnorePendingStylesheets();
2906 if (m_caretPaint) {
2907 m_caretPaint = false;
2908 invalidateCaretRect();
2909 }
2910#endif
2911 if (caretBlinks)
2912 setFocusedElementIfNeeded();
2913 m_caretBlinks = caretBlinks;
2914 updateAppearance();
2915}
2916
2917void FrameSelection::setCaretColor(const Color& caretColor)
2918{
2919 if (m_caretColor != caretColor) {
2920 m_caretColor = caretColor;
2921 if (caretIsVisible() && m_caretBlinks && isCaret())
2922 invalidateCaretRect();
2923 }
2924}
2925#endif // PLATFORM(IOS_FAMILY)
2926
2927}
2928
2929#if ENABLE(TREE_DEBUGGING)
2930
2931void showTree(const WebCore::FrameSelection& sel)
2932{
2933 sel.showTreeForThis();
2934}
2935
2936void showTree(const WebCore::FrameSelection* sel)
2937{
2938 if (sel)
2939 sel->showTreeForThis();
2940}
2941
2942#endif
2943