blob: 8d87696471ccc12588e00077d133d8478233925b [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"
#include "core/paint/PaintLayer.h"
namespace blink {
SelectionPaintRange::SelectionPaintRange(LayoutObject* start_layout_object,
int start_offset,
LayoutObject* end_layout_object,
int end_offset)
: start_layout_object_(start_layout_object),
start_offset_(start_offset),
end_layout_object_(end_layout_object),
end_offset_(end_offset) {
DCHECK(start_layout_object_);
DCHECK(end_layout_object_);
if (start_layout_object_ != end_layout_object_)
return;
DCHECK_LT(start_offset_, end_offset_);
}
bool SelectionPaintRange::operator==(const SelectionPaintRange& other) const {
return start_layout_object_ == other.start_layout_object_ &&
start_offset_ == other.start_offset_ &&
end_layout_object_ == other.end_layout_object_ &&
end_offset_ == other.end_offset_;
}
LayoutObject* SelectionPaintRange::StartLayoutObject() const {
DCHECK(!IsNull());
return start_layout_object_;
}
int SelectionPaintRange::StartOffset() const {
DCHECK(!IsNull());
return start_offset_;
}
LayoutObject* SelectionPaintRange::EndLayoutObject() const {
DCHECK(!IsNull());
return end_layout_object_;
}
int SelectionPaintRange::EndOffset() const {
DCHECK(!IsNull());
return end_offset_;
}
SelectionPaintRange::Iterator::Iterator(const SelectionPaintRange* range) {
if (!range) {
current_ = nullptr;
return;
}
current_ = range->StartLayoutObject();
included_end_ = range->EndLayoutObject();
stop_ = range->EndLayoutObject()->ChildAt(range->EndOffset());
if (stop_)
return;
stop_ = range->EndLayoutObject()->NextInPreOrderAfterChildren();
}
LayoutObject* SelectionPaintRange::Iterator::operator*() const {
DCHECK(current_);
return current_;
}
SelectionPaintRange::Iterator& SelectionPaintRange::Iterator::operator++() {
DCHECK(current_);
for (current_ = current_->NextInPreOrder(); current_ && current_ != stop_;
current_ = current_->NextInPreOrder()) {
if (current_ == included_end_ || current_->CanBeSelectionLeaf())
return *this;
}
current_ = nullptr;
return *this;
}
LayoutSelection::LayoutSelection(FrameSelection& frame_selection)
: frame_selection_(&frame_selection),
has_pending_selection_(false),
paint_range_(SelectionPaintRange()) {}
static bool ShouldShowBlockCursor(const FrameSelection& frame_selection,
const VisibleSelectionInFlatTree& selection) {
if (!frame_selection.ShouldShowBlockCursor())
return false;
if (selection.GetSelectionType() != SelectionType::kCaretSelection)
return false;
if (IsLogicalEndOfLine(selection.VisibleEnd()))
return false;
return true;
}
static VisibleSelectionInFlatTree CalcSelection(
const FrameSelection& frame_selection) {
const VisibleSelectionInFlatTree& original_selection =
frame_selection.ComputeVisibleSelectionInFlatTree();
if (!ShouldShowBlockCursor(frame_selection, original_selection))
return original_selection;
const PositionInFlatTree end_position = NextPositionOf(
original_selection.Start(), PositionMoveType::kGraphemeCluster);
return CreateVisibleSelection(
SelectionInFlatTree::Builder()
.SetBaseAndExtent(original_selection.Start(), end_position)
.Build());
}
// Objects each have a single selection rect to examine.
using SelectedObjectMap = HashMap<LayoutObject*, SelectionState>;
// Blocks contain selected objects and fill gaps between them, either on the
// left, right, or in between lines and blocks.
// In order to get the visual rect right, we have to examine left, middle, and
// right rects individually, since otherwise the union of those rects might
// remain the same even when changes have occurred.
using SelectedBlockMap = HashMap<LayoutBlock*, SelectionState>;
struct SelectedMap {
STACK_ALLOCATED();
SelectedObjectMap object_map;
SelectedBlockMap block_map;
SelectedMap() = default;
SelectedMap(SelectedMap&& other) {
object_map = std::move(other.object_map);
block_map = std::move(other.block_map);
}
private:
DISALLOW_COPY_AND_ASSIGN(SelectedMap);
};
static SelectedMap CollectSelectedMap(const SelectionPaintRange& range) {
if (range.IsNull())
return SelectedMap();
SelectedMap selected_map;
for (LayoutObject* runner : range) {
if (runner->GetSelectionState() == SelectionState::kNone)
continue;
// Blocks are responsible for painting line gaps and margin gaps. They
// must be examined as well.
selected_map.object_map.Set(runner, runner->GetSelectionState());
for (LayoutBlock* containing_block = runner->ContainingBlock();
containing_block && !containing_block->IsLayoutView();
containing_block = containing_block->ContainingBlock()) {
SelectedBlockMap::AddResult result = selected_map.block_map.insert(
containing_block, containing_block->GetSelectionState());
if (!result.is_new_entry)
break;
}
}
return selected_map;
}
// Update the selection status of all LayoutObjects between |start| and |end|.
static void SetSelectionState(const SelectionPaintRange& range) {
if (range.IsNull())
return;
if (range.StartLayoutObject() == range.EndLayoutObject()) {
range.StartLayoutObject()->SetSelectionStateIfNeeded(
SelectionState::kStartAndEnd);
} else {
range.StartLayoutObject()->SetSelectionStateIfNeeded(
SelectionState::kStart);
range.EndLayoutObject()->SetSelectionStateIfNeeded(SelectionState::kEnd);
}
for (LayoutObject* runner : range) {
if (runner != range.StartLayoutObject() &&
runner != range.EndLayoutObject() && runner->CanBeSelectionLeaf())
runner->SetSelectionStateIfNeeded(SelectionState::kInside);
}
}
// Set SetSelectionState and ShouldInvalidateSelection flag of LayoutObjects
// comparing them in |new_range| and |old_range|.
static void UpdateLayoutObjectState(const SelectionPaintRange& new_range,
const SelectionPaintRange& old_range) {
const SelectedMap& old_selected_map = CollectSelectedMap(old_range);
// Now clear the selection.
for (auto layout_object : old_selected_map.object_map.Keys())
layout_object->SetSelectionStateIfNeeded(SelectionState::kNone);
SetSelectionState(new_range);
// Now that the selection state has been updated for the new objects, walk
// them again and put them in the new objects list.
// TODO(editing-dev): |new_selected_map| doesn't really need to store the
// SelectionState, it's just more convenient to have it use the same data
// structure as |old_selected_map|.
SelectedMap new_selected_map = CollectSelectedMap(new_range);
// Have any of the old selected objects changed compared to the new selection?
for (const auto& pair : old_selected_map.object_map) {
LayoutObject* obj = pair.key;
SelectionState new_selection_state = obj->GetSelectionState();
SelectionState old_selection_state = pair.value;
if (new_selection_state != old_selection_state ||
(new_range.StartLayoutObject() == obj &&
new_range.StartOffset() != old_range.StartOffset()) ||
(new_range.EndLayoutObject() == obj &&
new_range.EndOffset() != old_range.EndOffset())) {
obj->SetShouldInvalidateSelection();
new_selected_map.object_map.erase(obj);
}
}
// Any new objects that remain were not found in the old objects dict, and so
// they need to be updated.
for (auto layout_object : new_selected_map.object_map.Keys())
layout_object->SetShouldInvalidateSelection();
// Have any of the old blocks changed?
for (const auto& pair : old_selected_map.block_map) {
LayoutBlock* block = pair.key;
SelectionState new_selection_state = block->GetSelectionState();
SelectionState old_selection_state = pair.value;
if (new_selection_state != old_selection_state) {
block->SetShouldInvalidateSelection();
new_selected_map.block_map.erase(block);
}
}
// Any new blocks that remain were not found in the old blocks dict, and so
// they need to be updated.
for (auto layout_object : new_selected_map.block_map.Keys())
layout_object->SetShouldInvalidateSelection();
}
std::pair<int, int> LayoutSelection::SelectionStartEnd() {
Commit();
if (paint_range_.IsNull())
return std::make_pair(-1, -1);
return std::make_pair(paint_range_.StartOffset(), paint_range_.EndOffset());
}
void LayoutSelection::ClearSelection() {
// For querying Layer::compositingState()
// This is correct, since destroying layout objects needs to cause eager paint
// invalidations.
DisableCompositingQueryAsserts disabler;
// Just return if the selection is already empty.
if (paint_range_.IsNull())
return;
for (auto layout_object : paint_range_) {
const SelectionState old_state = layout_object->GetSelectionState();
layout_object->SetSelectionStateIfNeeded(SelectionState::kNone);
if (layout_object->GetSelectionState() == old_state)
continue;
layout_object->SetShouldInvalidateSelection();
}
// Reset selection.
paint_range_ = SelectionPaintRange();
}
static SelectionPaintRange CalcSelectionPaintRange(
const FrameSelection& frame_selection) {
const SelectionInDOMTree& selection_in_dom =
frame_selection.GetSelectionInDOMTree();
if (selection_in_dom.IsNone())
return SelectionPaintRange();
const VisibleSelectionInFlatTree& selection = CalcSelection(frame_selection);
if (!selection.IsRange() || frame_selection.IsHidden())
return SelectionPaintRange();
DCHECK(!selection.IsNone());
const PositionInFlatTree start_pos = selection.Start();
const PositionInFlatTree end_pos = selection.End();
DCHECK_LE(start_pos, end_pos);
LayoutObject* start_layout_object = start_pos.AnchorNode()->GetLayoutObject();
LayoutObject* end_layout_object = end_pos.AnchorNode()->GetLayoutObject();
DCHECK(start_layout_object);
DCHECK(end_layout_object);
DCHECK(start_layout_object->View() == end_layout_object->View());
return SelectionPaintRange(start_layout_object,
start_pos.ComputeEditingOffset(),
end_layout_object, end_pos.ComputeEditingOffset());
}
void LayoutSelection::Commit() {
if (!HasPendingSelection())
return;
has_pending_selection_ = false;
const SelectionPaintRange& new_range =
CalcSelectionPaintRange(*frame_selection_);
if (new_range.IsNull()) {
ClearSelection();
return;
}
// Just return if the selection hasn't changed.
if (paint_range_ == new_range)
return;
DCHECK(frame_selection_->GetDocument().GetLayoutView()->GetFrameView());
DCHECK(!frame_selection_->GetDocument().NeedsLayoutTreeUpdate());
UpdateLayoutObjectState(new_range, paint_range_);
paint_range_ = new_range;
}
void LayoutSelection::OnDocumentShutdown() {
has_pending_selection_ = false;
paint_range_ = SelectionPaintRange();
}
static LayoutRect SelectionRectForLayoutObject(const LayoutObject* object) {
if (!object->IsRooted())
return LayoutRect();
if (!object->CanUpdateSelectionOnRootLineBoxes())
return LayoutRect();
return object->SelectionRectInViewCoordinates();
}
IntRect LayoutSelection::SelectionBounds() {
Commit();
if (paint_range_.IsNull())
return IntRect();
// Create a single bounding box rect that encloses the whole selection.
LayoutRect selected_rect;
const SelectedMap& current_map = CollectSelectedMap(paint_range_);
for (auto layout_object : current_map.object_map.Keys())
selected_rect.Unite(SelectionRectForLayoutObject(layout_object));
for (auto layout_block : current_map.block_map.Keys())
selected_rect.Unite(SelectionRectForLayoutObject(layout_block));
return PixelSnappedIntRect(selected_rect);
}
void LayoutSelection::InvalidatePaintForSelection() {
if (paint_range_.IsNull())
return;
for (LayoutObject* runner : paint_range_) {
if (runner->GetSelectionState() == SelectionState::kNone)
continue;
runner->SetShouldInvalidateSelection();
}
}
DEFINE_TRACE(LayoutSelection) {
visitor->Trace(frame_selection_);
}
} // namespace blink