Index: third_party/WebKit/Source/core/editing/LayoutSelection.cpp |
diff --git a/third_party/WebKit/Source/core/editing/LayoutSelection.cpp b/third_party/WebKit/Source/core/editing/LayoutSelection.cpp |
index 540146afea59317b2f41c95f2de8b3f74415c3f7..1dccdebda0a3ee1b88a8e1343b95427310c1fd8c 100644 |
--- a/third_party/WebKit/Source/core/editing/LayoutSelection.cpp |
+++ b/third_party/WebKit/Source/core/editing/LayoutSelection.cpp |
@@ -28,6 +28,7 @@ |
#include "core/editing/VisibleUnits.h" |
#include "core/html/TextControlElement.h" |
#include "core/layout/LayoutView.h" |
+#include "core/paint/PaintLayer.h" |
namespace blink { |
@@ -111,6 +112,231 @@ SelectionInFlatTree LayoutSelection::CalcVisibleSelection( |
return builder.Build(); |
} |
+static LayoutObject* LayoutObjectAfterPosition(LayoutObject* object, |
+ unsigned offset) { |
+ if (!object) |
+ return nullptr; |
+ |
+ LayoutObject* child = object->ChildAt(offset); |
+ return child ? child : object->NextInPreOrderAfterChildren(); |
+} |
+ |
+// When exploring the LayoutTree looking for the nodes involved in the |
+// Selection, sometimes it's required to change the traversing direction because |
+// the "start" position is below the "end" one. |
+static inline LayoutObject* GetNextOrPrevLayoutObjectBasedOnDirection( |
+ const LayoutObject* o, |
+ const LayoutObject* stop, |
+ bool& continue_exploring, |
+ bool& exploring_backwards) { |
+ LayoutObject* next; |
+ if (exploring_backwards) { |
+ next = o->PreviousInPreOrder(); |
+ continue_exploring = next && !(next)->IsLayoutView(); |
+ } else { |
+ next = o->NextInPreOrder(); |
+ continue_exploring = next && next != stop; |
+ exploring_backwards = !next && (next != stop); |
+ if (exploring_backwards) { |
+ next = stop->PreviousInPreOrder(); |
+ continue_exploring = next && !next->IsLayoutView(); |
+ } |
+ } |
+ |
+ return next; |
+} |
+ |
+void LayoutSelection::SetSelection( |
+ LayoutObject* start, |
+ int start_pos, |
+ LayoutObject* end, |
+ int end_pos, |
+ SelectionPaintInvalidationMode block_paint_invalidation_mode) { |
+ // This code makes no assumptions as to if the layout tree is up to date or |
+ // not and will not try to update it. Currently clearSelection calls this |
+ // (intentionally) without updating the layout tree as it doesn't care. |
+ // Other callers may want to force recalc style before calling this. |
+ |
+ // Make sure both our start and end objects are defined. |
+ // Check www.msnbc.com and try clicking around to find the case where this |
+ // happened. |
+ if ((start && !end) || (end && !start)) |
+ return; |
+ |
+ // Just return if the selection hasn't changed. |
+ if (selection_start_ == start && selection_start_pos_ == start_pos && |
+ selection_end_ == end && selection_end_pos_ == end_pos) |
+ return; |
+ |
+ // Record the old selected objects. These will be used later when we compare |
+ // against the new selected objects. |
+ int old_start_pos = selection_start_pos_; |
+ int old_end_pos = selection_end_pos_; |
+ |
+ // Objects each have a single selection rect to examine. |
+ typedef HashMap<LayoutObject*, SelectionState> SelectedObjectMap; |
+ SelectedObjectMap old_selected_objects; |
+ // FIXME: |newSelectedObjects| doesn't really need to store the |
+ // SelectionState, it's just more convenient to have it use the same data |
+ // structure as |oldSelectedObjects|. |
+ SelectedObjectMap new_selected_objects; |
+ |
+ // 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. |
+ typedef HashMap<LayoutBlock*, SelectionState> SelectedBlockMap; |
+ SelectedBlockMap old_selected_blocks; |
+ // FIXME: |newSelectedBlocks| doesn't really need to store the SelectionState, |
+ // it's just more convenient to have it use the same data structure as |
+ // |oldSelectedBlocks|. |
+ SelectedBlockMap new_selected_blocks; |
+ |
+ LayoutObject* os = selection_start_; |
+ LayoutObject* stop = |
+ LayoutObjectAfterPosition(selection_end_, selection_end_pos_); |
+ bool exploring_backwards = false; |
+ bool continue_exploring = os && (os != stop); |
+ while (continue_exploring) { |
+ if ((os->CanBeSelectionLeaf() || os == selection_start_ || |
+ os == selection_end_) && |
+ os->GetSelectionState() != SelectionNone) { |
+ // Blocks are responsible for painting line gaps and margin gaps. They |
+ // must be examined as well. |
+ old_selected_objects.Set(os, os->GetSelectionState()); |
+ if (block_paint_invalidation_mode == kPaintInvalidationNewXOROld) { |
+ LayoutBlock* cb = os->ContainingBlock(); |
+ while (cb && !cb->IsLayoutView()) { |
+ SelectedBlockMap::AddResult result = |
+ old_selected_blocks.insert(cb, cb->GetSelectionState()); |
+ if (!result.is_new_entry) |
+ break; |
+ cb = cb->ContainingBlock(); |
+ } |
+ } |
+ } |
+ |
+ os = GetNextOrPrevLayoutObjectBasedOnDirection(os, stop, continue_exploring, |
+ exploring_backwards); |
+ } |
+ |
+ // Now clear the selection. |
+ SelectedObjectMap::iterator old_objects_end = old_selected_objects.end(); |
+ for (SelectedObjectMap::iterator i = old_selected_objects.begin(); |
+ i != old_objects_end; ++i) |
+ i->key->SetSelectionStateIfNeeded(SelectionNone); |
+ |
+ // set selection start and end |
+ selection_start_ = start; |
+ selection_start_pos_ = start_pos; |
+ selection_end_ = end; |
+ selection_end_pos_ = end_pos; |
+ |
+ // Update the selection status of all objects between m_selectionStart and |
+ // m_selectionEnd |
+ if (start && start == end) { |
+ start->SetSelectionStateIfNeeded(SelectionBoth); |
+ } else { |
+ if (start) |
+ start->SetSelectionStateIfNeeded(SelectionStart); |
+ if (end) |
+ end->SetSelectionStateIfNeeded(SelectionEnd); |
+ } |
+ |
+ LayoutObject* o = start; |
+ stop = LayoutObjectAfterPosition(end, end_pos); |
+ |
+ while (o && o != stop) { |
+ if (o != start && o != end && o->CanBeSelectionLeaf()) |
+ o->SetSelectionStateIfNeeded(SelectionInside); |
+ o = o->NextInPreOrder(); |
+ } |
+ |
+ // Now that the selection state has been updated for the new objects, walk |
+ // them again and put them in the new objects list. |
+ o = start; |
+ exploring_backwards = false; |
+ continue_exploring = o && (o != stop); |
+ while (continue_exploring) { |
+ if ((o->CanBeSelectionLeaf() || o == start || o == end) && |
+ o->GetSelectionState() != SelectionNone) { |
+ new_selected_objects.Set(o, o->GetSelectionState()); |
+ LayoutBlock* cb = o->ContainingBlock(); |
+ while (cb && !cb->IsLayoutView()) { |
+ SelectedBlockMap::AddResult result = |
+ new_selected_blocks.insert(cb, cb->GetSelectionState()); |
+ if (!result.is_new_entry) |
+ break; |
+ cb = cb->ContainingBlock(); |
+ } |
+ } |
+ |
+ o = GetNextOrPrevLayoutObjectBasedOnDirection(o, stop, continue_exploring, |
+ exploring_backwards); |
+ } |
+ |
+ // TODO(yoichio): DCHECK(frame_selection_->,,,->GetFrameView()); |
+ if (!frame_selection_->GetDocument().GetLayoutView()->GetFrameView()) |
+ return; |
+ |
+ // Have any of the old selected objects changed compared to the new selection? |
+ for (SelectedObjectMap::iterator i = old_selected_objects.begin(); |
+ i != old_objects_end; ++i) { |
+ LayoutObject* obj = i->key; |
+ SelectionState new_selection_state = obj->GetSelectionState(); |
+ SelectionState old_selection_state = i->value; |
+ if (new_selection_state != old_selection_state || |
+ (selection_start_ == obj && old_start_pos != selection_start_pos_) || |
+ (selection_end_ == obj && old_end_pos != selection_end_pos_)) { |
+ obj->SetShouldInvalidateSelection(); |
+ new_selected_objects.erase(obj); |
+ } |
+ } |
+ |
+ // Any new objects that remain were not found in the old objects dict, and so |
+ // they need to be updated. |
+ SelectedObjectMap::iterator new_objects_end = new_selected_objects.end(); |
+ for (SelectedObjectMap::iterator i = new_selected_objects.begin(); |
+ i != new_objects_end; ++i) |
+ i->key->SetShouldInvalidateSelection(); |
+ |
+ // Have any of the old blocks changed? |
+ SelectedBlockMap::iterator old_blocks_end = old_selected_blocks.end(); |
+ for (SelectedBlockMap::iterator i = old_selected_blocks.begin(); |
+ i != old_blocks_end; ++i) { |
+ LayoutBlock* block = i->key; |
+ SelectionState new_selection_state = block->GetSelectionState(); |
+ SelectionState old_selection_state = i->value; |
+ if (new_selection_state != old_selection_state) { |
+ block->SetShouldInvalidateSelection(); |
+ new_selected_blocks.erase(block); |
+ } |
+ } |
+ |
+ // Any new blocks that remain were not found in the old blocks dict, and so |
+ // they need to be updated. |
+ SelectedBlockMap::iterator new_blocks_end = new_selected_blocks.end(); |
+ for (SelectedBlockMap::iterator i = new_selected_blocks.begin(); |
+ i != new_blocks_end; ++i) |
+ i->key->SetShouldInvalidateSelection(); |
+} |
+ |
+void LayoutSelection::SelectionStartEnd(int& start_pos, int& end_pos) { |
+ Commit(*frame_selection_->GetDocument().GetLayoutView()); |
+ start_pos = selection_start_pos_; |
+ end_pos = selection_end_pos_; |
+} |
+ |
+void LayoutSelection::ClearSelection() { |
+ // For querying Layer::compositingState() |
+ // This is correct, since destroying layout objects needs to cause eager paint |
+ // invalidations. |
+ DisableCompositingQueryAsserts disabler; |
+ |
+ SetSelection(0, -1, 0, -1, kPaintInvalidationNewMinusOld); |
+} |
+ |
void LayoutSelection::Commit(LayoutView& layout_view) { |
if (!HasPendingSelection()) |
return; |
@@ -176,6 +402,66 @@ void LayoutSelection::OnDocumentShutdown() { |
selection_end_pos_ = -1; |
} |
+static LayoutRect SelectionRectForLayoutObject(const LayoutObject* object) { |
+ if (!object->IsRooted()) |
+ return LayoutRect(); |
+ |
+ if (!object->CanUpdateSelectionOnRootLineBoxes()) |
+ return LayoutRect(); |
+ |
+ return object->SelectionRectInViewCoordinates(); |
+} |
+ |
+IntRect LayoutSelection::SelectionBounds() { |
+ // Now create a single bounding box rect that encloses the whole selection. |
+ LayoutRect sel_rect; |
+ |
+ typedef HashSet<const LayoutBlock*> VisitedContainingBlockSet; |
+ VisitedContainingBlockSet visited_containing_blocks; |
+ |
+ Commit(*frame_selection_->GetDocument().GetLayoutView()); |
+ LayoutObject* os = selection_start_; |
+ LayoutObject* stop = |
+ LayoutObjectAfterPosition(selection_end_, selection_end_pos_); |
+ while (os && os != stop) { |
+ if ((os->CanBeSelectionLeaf() || os == selection_start_ || |
+ os == selection_end_) && |
+ os->GetSelectionState() != SelectionNone) { |
+ // Blocks are responsible for painting line gaps and margin gaps. They |
+ // must be examined as well. |
+ sel_rect.Unite(SelectionRectForLayoutObject(os)); |
+ const LayoutBlock* cb = os->ContainingBlock(); |
+ while (cb && !cb->IsLayoutView()) { |
+ sel_rect.Unite(SelectionRectForLayoutObject(cb)); |
+ VisitedContainingBlockSet::AddResult add_result = |
+ visited_containing_blocks.insert(cb); |
+ if (!add_result.is_new_entry) |
+ break; |
+ cb = cb->ContainingBlock(); |
+ } |
+ } |
+ |
+ os = os->NextInPreOrder(); |
+ } |
+ |
+ return PixelSnappedIntRect(sel_rect); |
+} |
+ |
+void LayoutSelection::InvalidatePaintForSelection() { |
+ LayoutObject* end = |
+ LayoutObjectAfterPosition(selection_end_, selection_end_pos_); |
+ for (LayoutObject* o = selection_start_; o && o != end; |
+ o = o->NextInPreOrder()) { |
+ if (!o->CanBeSelectionLeaf() && o != selection_start_ && |
+ o != selection_end_) |
+ continue; |
+ if (o->GetSelectionState() == SelectionNone) |
+ continue; |
+ |
+ o->SetShouldInvalidateSelection(); |
+ } |
+} |
+ |
DEFINE_TRACE(LayoutSelection) { |
visitor->Trace(frame_selection_); |
} |