| /* |
| * Copyright (C) 1999 Lars Knoll (knoll@kde.org) |
| * Copyright (C) 2004, 2005, 2006, 2007, 2008, 2009 Apple Inc. All rights |
| * reserved. |
| * |
| * This library is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU Library General Public |
| * License as published by the Free Software Foundation; either |
| * version 2 of the License, or (at your option) any later version. |
| * |
| * This library is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| * Library General Public License for more details. |
| * |
| * You should have received a copy of the GNU Library General Public License |
| * along with this library; see the file COPYING.LIB. If not, write to |
| * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, |
| * Boston, MA 02110-1301, USA. |
| */ |
| |
| #include "core/editing/LayoutSelection.h" |
| |
| #include "core/dom/Document.h" |
| #include "core/editing/EditingUtilities.h" |
| #include "core/editing/FrameSelection.h" |
| #include "core/editing/VisiblePosition.h" |
| #include "core/editing/VisibleUnits.h" |
| #include "core/html/TextControlElement.h" |
| #include "core/layout/LayoutView.h" |
| |
| namespace blink { |
| |
| LayoutSelection::LayoutSelection(FrameSelection& frameSelection) |
| : m_frameSelection(&frameSelection), m_hasPendingSelection(false) {} |
| |
| const VisibleSelection& LayoutSelection::visibleSelection() const { |
| return m_frameSelection->computeVisibleSelectionInDOMTree(); |
| } |
| |
| static bool isSelectionInDocument( |
| const VisibleSelectionInFlatTree& visibleSelection, |
| const Document& document) { |
| const PositionInFlatTree& start = visibleSelection.start(); |
| if (start.isNotNull() && |
| (!start.isConnected() || start.document() != document)) |
| return false; |
| const PositionInFlatTree& end = visibleSelection.end(); |
| if (end.isNotNull() && (!end.isConnected() || end.document() != document)) |
| return false; |
| const PositionInFlatTree extent = visibleSelection.extent(); |
| if (extent.isNotNull() && |
| (!extent.isConnected() || extent.document() != document)) |
| return false; |
| return true; |
| } |
| |
| SelectionInFlatTree LayoutSelection::calcVisibleSelection( |
| const VisibleSelectionInFlatTree& originalSelection) const { |
| const PositionInFlatTree& start = originalSelection.start(); |
| const PositionInFlatTree& end = originalSelection.end(); |
| SelectionType selectionType = originalSelection.getSelectionType(); |
| const TextAffinity affinity = originalSelection.affinity(); |
| |
| bool paintBlockCursor = |
| m_frameSelection->shouldShowBlockCursor() && |
| selectionType == SelectionType::CaretSelection && |
| !isLogicalEndOfLine(createVisiblePosition(end, affinity)); |
| if (enclosingTextControl(start.computeContainerNode())) { |
| // TODO(yosin) We should use |PositionMoveType::CodePoint| to avoid |
| // ending paint at middle of character. |
| PositionInFlatTree endPosition = |
| paintBlockCursor ? nextPositionOf(originalSelection.extent(), |
| PositionMoveType::CodeUnit) |
| : end; |
| return SelectionInFlatTree::Builder() |
| .setBaseAndExtent(start, endPosition) |
| .build(); |
| } |
| |
| const VisiblePositionInFlatTree& visibleStart = createVisiblePosition( |
| start, selectionType == SelectionType::RangeSelection |
| ? TextAffinity::Downstream |
| : affinity); |
| if (visibleStart.isNull()) |
| return SelectionInFlatTree(); |
| if (paintBlockCursor) { |
| const VisiblePositionInFlatTree visibleExtent = nextPositionOf( |
| createVisiblePosition(end, affinity), CanSkipOverEditingBoundary); |
| if (visibleExtent.isNull()) |
| return SelectionInFlatTree(); |
| SelectionInFlatTree::Builder builder; |
| builder.collapse(visibleStart.toPositionWithAffinity()); |
| builder.extend(visibleExtent.deepEquivalent()); |
| return builder.build(); |
| } |
| const VisiblePositionInFlatTree visibleEnd = |
| createVisiblePosition(end, selectionType == SelectionType::RangeSelection |
| ? TextAffinity::Upstream |
| : affinity); |
| if (visibleEnd.isNull()) |
| return SelectionInFlatTree(); |
| SelectionInFlatTree::Builder builder; |
| builder.collapse(visibleStart.toPositionWithAffinity()); |
| builder.extend(visibleEnd.deepEquivalent()); |
| return builder.build(); |
| } |
| |
| void LayoutSelection::commit(LayoutView& layoutView) { |
| if (!hasPendingSelection()) |
| return; |
| DCHECK(!layoutView.needsLayout()); |
| m_hasPendingSelection = false; |
| |
| const VisibleSelectionInFlatTree& originalSelection = |
| m_frameSelection->computeVisibleSelectionInFlatTree(); |
| |
| // Skip if pending VisibilePositions became invalid before we reach here. |
| if (!isSelectionInDocument(originalSelection, layoutView.document())) |
| return; |
| |
| // Construct a new VisibleSolution, since visibleSelection() is not |
| // necessarily valid, and the following steps assume a valid selection. See |
| // <https://bugs.webkit.org/show_bug.cgi?id=69563> and |
| // <rdar://problem/10232866>. |
| const VisibleSelectionInFlatTree& selection = |
| createVisibleSelection(calcVisibleSelection(originalSelection)); |
| |
| if (!selection.isRange()) { |
| layoutView.clearSelection(); |
| return; |
| } |
| |
| // Use the rightmost candidate for the start of the selection, and the |
| // leftmost candidate for the end of the selection. Example: foo <a>bar</a>. |
| // Imagine that a line wrap occurs after 'foo', and that 'bar' is selected. |
| // If we pass [foo, 3] as the start of the selection, the selection painting |
| // code will think that content on the line containing 'foo' is selected |
| // and will fill the gap before 'bar'. |
| PositionInFlatTree startPos = selection.start(); |
| PositionInFlatTree candidate = mostForwardCaretPosition(startPos); |
| if (isVisuallyEquivalentCandidate(candidate)) |
| startPos = candidate; |
| PositionInFlatTree endPos = selection.end(); |
| candidate = mostBackwardCaretPosition(endPos); |
| if (isVisuallyEquivalentCandidate(candidate)) |
| endPos = candidate; |
| |
| // We can get into a state where the selection endpoints map to the same |
| // |VisiblePosition| when a selection is deleted because we don't yet notify |
| // the |FrameSelection| of text removal. |
| if (startPos.isNull() || endPos.isNull() || |
| selection.visibleStart().deepEquivalent() == |
| selection.visibleEnd().deepEquivalent()) |
| return; |
| LayoutObject* startLayoutObject = startPos.anchorNode()->layoutObject(); |
| LayoutObject* endLayoutObject = endPos.anchorNode()->layoutObject(); |
| if (!startLayoutObject || !endLayoutObject) |
| return; |
| DCHECK(layoutView == startLayoutObject->view()); |
| DCHECK(layoutView == endLayoutObject->view()); |
| layoutView.setSelection(startLayoutObject, startPos.computeEditingOffset(), |
| endLayoutObject, endPos.computeEditingOffset()); |
| } |
| |
| DEFINE_TRACE(LayoutSelection) { |
| visitor->trace(m_frameSelection); |
| } |
| |
| } // namespace blink |