blob: 540146afea59317b2f41c95f2de8b3f74415c3f7 [file] [log] [blame]
/*
* 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& frame_selection)
: frame_selection_(&frame_selection),
has_pending_selection_(false),
selection_start_(nullptr),
selection_end_(nullptr),
selection_start_pos_(-1),
selection_end_pos_(-1) {}
const VisibleSelection& LayoutSelection::GetVisibleSelection() const {
return frame_selection_->ComputeVisibleSelectionInDOMTree();
}
static bool IsSelectionInDocument(
const VisibleSelectionInFlatTree& visible_selection,
const Document& document) {
const PositionInFlatTree& start = visible_selection.Start();
if (start.IsNotNull() &&
(!start.IsConnected() || start.GetDocument() != document))
return false;
const PositionInFlatTree& end = visible_selection.end();
if (end.IsNotNull() && (!end.IsConnected() || end.GetDocument() != document))
return false;
const PositionInFlatTree extent = visible_selection.Extent();
if (extent.IsNotNull() &&
(!extent.IsConnected() || extent.GetDocument() != document))
return false;
return true;
}
SelectionInFlatTree LayoutSelection::CalcVisibleSelection(
const VisibleSelectionInFlatTree& original_selection) const {
const PositionInFlatTree& start = original_selection.Start();
const PositionInFlatTree& end = original_selection.end();
SelectionType selection_type = original_selection.GetSelectionType();
const TextAffinity affinity = original_selection.Affinity();
bool paint_block_cursor =
frame_selection_->ShouldShowBlockCursor() &&
selection_type == SelectionType::kCaretSelection &&
!IsLogicalEndOfLine(CreateVisiblePosition(end, affinity));
if (EnclosingTextControl(start.ComputeContainerNode())) {
// TODO(yosin) We should use |PositionMoveType::CodePoint| to avoid
// ending paint at middle of character.
PositionInFlatTree end_position =
paint_block_cursor ? NextPositionOf(original_selection.Extent(),
PositionMoveType::kCodeUnit)
: end;
return SelectionInFlatTree::Builder()
.SetBaseAndExtent(start, end_position)
.Build();
}
const VisiblePositionInFlatTree& visible_start = CreateVisiblePosition(
start, selection_type == SelectionType::kRangeSelection
? TextAffinity::kDownstream
: affinity);
if (visible_start.IsNull())
return SelectionInFlatTree();
if (paint_block_cursor) {
const VisiblePositionInFlatTree visible_extent = NextPositionOf(
CreateVisiblePosition(end, affinity), kCanSkipOverEditingBoundary);
if (visible_extent.IsNull())
return SelectionInFlatTree();
SelectionInFlatTree::Builder builder;
builder.Collapse(visible_start.ToPositionWithAffinity());
builder.Extend(visible_extent.DeepEquivalent());
return builder.Build();
}
const VisiblePositionInFlatTree visible_end = CreateVisiblePosition(
end, selection_type == SelectionType::kRangeSelection
? TextAffinity::kUpstream
: affinity);
if (visible_end.IsNull())
return SelectionInFlatTree();
SelectionInFlatTree::Builder builder;
builder.Collapse(visible_start.ToPositionWithAffinity());
builder.Extend(visible_end.DeepEquivalent());
return builder.Build();
}
void LayoutSelection::Commit(LayoutView& layout_view) {
if (!HasPendingSelection())
return;
DCHECK(!layout_view.NeedsLayout());
has_pending_selection_ = false;
const VisibleSelectionInFlatTree& original_selection =
frame_selection_->ComputeVisibleSelectionInFlatTree();
// Skip if pending VisibilePositions became invalid before we reach here.
if (!IsSelectionInDocument(original_selection, layout_view.GetDocument()))
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(original_selection));
if (!selection.IsRange()) {
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 start_pos = selection.Start();
PositionInFlatTree candidate = MostForwardCaretPosition(start_pos);
if (IsVisuallyEquivalentCandidate(candidate))
start_pos = candidate;
PositionInFlatTree end_pos = selection.end();
candidate = MostBackwardCaretPosition(end_pos);
if (IsVisuallyEquivalentCandidate(candidate))
end_pos = 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 (start_pos.IsNull() || end_pos.IsNull() ||
selection.VisibleStart().DeepEquivalent() ==
selection.VisibleEnd().DeepEquivalent())
return;
LayoutObject* start_layout_object = start_pos.AnchorNode()->GetLayoutObject();
LayoutObject* end_layout_object = end_pos.AnchorNode()->GetLayoutObject();
if (!start_layout_object || !end_layout_object)
return;
DCHECK(layout_view == start_layout_object->View());
DCHECK(layout_view == end_layout_object->View());
SetSelection(start_layout_object, start_pos.ComputeEditingOffset(),
end_layout_object, end_pos.ComputeEditingOffset());
}
void LayoutSelection::OnDocumentShutdown() {
has_pending_selection_ = false;
selection_start_ = nullptr;
selection_end_ = nullptr;
selection_start_pos_ = -1;
selection_end_pos_ = -1;
}
DEFINE_TRACE(LayoutSelection) {
visitor->Trace(frame_selection_);
}
} // namespace blink