blob: c33d170db135b538733d97bc5186addddb031682 [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/PendingSelection.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/HTMLTextFormControlElement.h"
#include "core/layout/LayoutView.h"
namespace blink {
PendingSelection::PendingSelection(FrameSelection& frameSelection)
: m_frameSelection(&frameSelection)
, m_hasPendingSelection(false)
{
}
const VisibleSelection& PendingSelection::visibleSelection() const
{
return m_frameSelection->selection();
}
template <typename Strategy>
static bool isSelectionInDocument(const VisibleSelectionTemplate<Strategy>& visibleSelection, const Document& document)
{
const PositionTemplate<Strategy> start = visibleSelection.start();
if (start.isNotNull() && (!start.inDocument() || start.document() != document))
return false;
const PositionTemplate<Strategy> end = visibleSelection.end();
if (end.isNotNull() && (!end.inDocument() || end.document() != document))
return false;
const PositionTemplate<Strategy> extent = visibleSelection.extent();
if (extent.isNotNull() && (!extent.inDocument() || extent.document() != document))
return false;
return true;
}
template <typename Strategy>
VisibleSelectionTemplate<Strategy> PendingSelection::calcVisibleSelectionAlgorithm(const VisibleSelectionTemplate<Strategy>& originalSelection) const
{
const PositionTemplate<Strategy> start = originalSelection.start();
const PositionTemplate<Strategy> end = originalSelection.end();
SelectionType selectionType = originalSelection.selectionType();
const TextAffinity affinity = originalSelection.affinity();
bool paintBlockCursor = m_frameSelection->shouldShowBlockCursor() && selectionType == SelectionType::CaretSelection && !isLogicalEndOfLine(createVisiblePosition(end, affinity));
VisibleSelectionTemplate<Strategy> selection;
if (enclosingTextFormControl(start.computeContainerNode())) {
// TODO(yosin) We should use |PositionMoveType::Character| to avoid
// ending paint at middle of character.
PositionTemplate<Strategy> endPosition = paintBlockCursor ? nextPositionOf(originalSelection.extent(), PositionMoveType::CodePoint) : end;
selection.setWithoutValidation(start, endPosition);
return selection;
}
const VisiblePositionTemplate<Strategy> visibleStart = createVisiblePosition(start, selectionType == SelectionType::RangeSelection ? TextAffinity::Downstream : affinity);
if (paintBlockCursor) {
VisiblePositionTemplate<Strategy> visibleExtent = createVisiblePosition(end, affinity);
visibleExtent = nextPositionOf(visibleExtent, CanSkipOverEditingBoundary);
return VisibleSelectionTemplate<Strategy>(visibleStart, visibleExtent);
}
const VisiblePositionTemplate<Strategy> visibleEnd = createVisiblePosition(end, selectionType == SelectionType::RangeSelection ? TextAffinity::Upstream : affinity);
return VisibleSelectionTemplate<Strategy>(visibleStart, visibleEnd);
}
template <typename Strategy>
void PendingSelection::commitAlgorithm(LayoutView& layoutView)
{
if (!hasPendingSelection())
return;
ASSERT(!layoutView.needsLayout());
m_hasPendingSelection = false;
const VisibleSelectionTemplate<Strategy> originalSelection = m_frameSelection->visibleSelection<Strategy>();
// 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 VisibleSelectionTemplate<Strategy> selection = calcVisibleSelectionAlgorithm<Strategy>(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'.
PositionTemplate<Strategy> startPos = selection.start();
PositionTemplate<Strategy> candidate = mostForwardCaretPosition(startPos);
if (isVisuallyEquivalentCandidate(candidate))
startPos = candidate;
PositionTemplate<Strategy> 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;
ASSERT(layoutView == startLayoutObject->view() && layoutView == endLayoutObject->view());
layoutView.setSelection(startLayoutObject, startPos.computeEditingOffset(), endLayoutObject, endPos.computeEditingOffset());
}
void PendingSelection::commit(LayoutView& layoutView)
{
if (RuntimeEnabledFeatures::selectionForComposedTreeEnabled())
return commitAlgorithm<EditingInComposedTreeStrategy>(layoutView);
commitAlgorithm<EditingStrategy>(layoutView);
}
DEFINE_TRACE(PendingSelection)
{
visitor->trace(m_frameSelection);
}
} // namespace blink