Index: third_party/WebKit/Source/core/editing/SelectionEditor.cpp |
diff --git a/third_party/WebKit/Source/core/editing/SelectionEditor.cpp b/third_party/WebKit/Source/core/editing/SelectionEditor.cpp |
index 7c833994c93ed98299585562a2afc71afda45e92..9390a928775707a2dae2524c22deaa666109b6e8 100644 |
--- a/third_party/WebKit/Source/core/editing/SelectionEditor.cpp |
+++ b/third_party/WebKit/Source/core/editing/SelectionEditor.cpp |
@@ -25,6 +25,8 @@ |
#include "core/editing/SelectionEditor.h" |
+#include "core/dom/NodeWithIndex.h" |
+#include "core/dom/Text.h" |
#include "core/editing/EditingUtilities.h" |
#include "core/editing/Editor.h" |
#include "core/editing/SelectionAdjuster.h" |
@@ -32,20 +34,30 @@ |
namespace blink { |
-SelectionEditor::SelectionEditor(LocalFrame& frame) |
- : m_frame(frame), m_observingVisibleSelection(false) { |
+SelectionEditor::SelectionEditor(LocalFrame& frame) : m_frame(frame) { |
clearVisibleSelection(); |
} |
SelectionEditor::~SelectionEditor() {} |
+void SelectionEditor::assertSelectionValid() const { |
+#if DCHECK_IS_ON() |
+ // Since We don't track dom tree version during attribute changes, we can't |
+ // use it for validity of |m_selection|. |
+ const_cast<SelectionEditor*>(this)->m_selection.m_domTreeVersion = |
+ document().domTreeVersion(); |
+#endif |
+ m_selection.assertValidFor(document()); |
+} |
+ |
void SelectionEditor::clearVisibleSelection() { |
- m_selection = VisibleSelection(); |
- m_selectionInFlatTree = VisibleSelectionInFlatTree(); |
+ m_selection = SelectionInDOMTree(); |
+ m_cachedVisibleSelectionInDOMTree = VisibleSelection(); |
+ m_cachedVisibleSelectionInFlatTree = VisibleSelectionInFlatTree(); |
+ m_cacheIsDirty = false; |
if (!shouldAlwaysUseDirectionalSelection()) |
return; |
- m_selection.setIsDirectional(true); |
- m_selectionInFlatTree.setIsDirectional(true); |
+ m_selection.m_isDirectional = true; |
} |
void SelectionEditor::dispose() { |
@@ -55,88 +67,183 @@ void SelectionEditor::dispose() { |
} |
const Document& SelectionEditor::document() const { |
- DCHECK(m_document); |
- return *m_document; |
+ DCHECK(lifecycleContext()); |
+ return *lifecycleContext(); |
+} |
+ |
+Document& SelectionEditor::document() { |
+ DCHECK(lifecycleContext()); |
+ return *lifecycleContext(); |
} |
template <> |
const VisibleSelection& SelectionEditor::visibleSelection<EditingStrategy>() |
const { |
- DCHECK_EQ(frame()->document(), document()); |
- DCHECK_EQ(frame(), document().frame()); |
- if (m_selection.isNone()) |
- return m_selection; |
- DCHECK_EQ(m_selection.base().document(), document()); |
- return m_selection; |
+ return computeVisibleSelectionInDOMTree(); |
} |
template <> |
const VisibleSelectionInFlatTree& |
SelectionEditor::visibleSelection<EditingInFlatTreeStrategy>() const { |
+ return computeVisibleSelectionInFlatTree(); |
+} |
+ |
+const VisibleSelection& SelectionEditor::computeVisibleSelectionInDOMTree() |
+ const { |
DCHECK_EQ(frame()->document(), document()); |
DCHECK_EQ(frame(), document().frame()); |
- if (m_selectionInFlatTree.isNone()) |
- return m_selectionInFlatTree; |
- DCHECK_EQ(m_selectionInFlatTree.base().document(), document()); |
- return m_selectionInFlatTree; |
+ const_cast<SelectionEditor*>(this)->updateCachedVisibleSelectionIfNeeded(); |
+ if (m_cachedVisibleSelectionInDOMTree.isNone()) |
+ return m_cachedVisibleSelectionInDOMTree; |
+ DCHECK_EQ(m_cachedVisibleSelectionInDOMTree.base().document(), document()); |
+ return m_cachedVisibleSelectionInDOMTree; |
} |
-void SelectionEditor::setVisibleSelection( |
- const VisibleSelection& newSelection, |
- FrameSelection::SetSelectionOptions options) { |
- DCHECK(newSelection.isValidFor(document())) << newSelection; |
- resetLogicalRange(); |
- clearDocumentCachedRange(); |
+const VisibleSelectionInFlatTree& |
+SelectionEditor::computeVisibleSelectionInFlatTree() const { |
+ DCHECK_EQ(frame()->document(), document()); |
+ DCHECK_EQ(frame(), document().frame()); |
+ const_cast<SelectionEditor*>(this)->updateCachedVisibleSelectionIfNeeded(); |
tkent
2017/02/10 08:47:41
Can we make updateCachedVisibleSelectionIfNeeded()
yosin_UTC9
2017/02/10 10:13:20
Done.
|
+ if (m_cachedVisibleSelectionInFlatTree.isNone()) |
+ return m_cachedVisibleSelectionInFlatTree; |
+ DCHECK_EQ(m_cachedVisibleSelectionInFlatTree.base().document(), document()); |
+ return m_cachedVisibleSelectionInFlatTree; |
+} |
- m_selection = newSelection; |
- if (options & FrameSelection::DoNotAdjustInFlatTree) { |
- m_selectionInFlatTree.setWithoutValidation( |
- toPositionInFlatTree(m_selection.base()), |
- toPositionInFlatTree(m_selection.extent())); |
- return; |
- } |
+const SelectionInDOMTree& SelectionEditor::selectionInDOMTree() const { |
+ assertSelectionValid(); |
+ return m_selection; |
+} |
- SelectionAdjuster::adjustSelectionInFlatTree(&m_selectionInFlatTree, |
- m_selection); |
+bool SelectionEditor::hasEditableStyle() const { |
+ return computeVisibleSelectionInDOMTree().hasEditableStyle(); |
} |
-void SelectionEditor::setVisibleSelection( |
- const VisibleSelectionInFlatTree& newSelection, |
- FrameSelection::SetSelectionOptions options) { |
- DCHECK(newSelection.isValidFor(document())) << newSelection; |
- DCHECK(!(options & FrameSelection::DoNotAdjustInFlatTree)); |
- resetLogicalRange(); |
- clearDocumentCachedRange(); |
+bool SelectionEditor::isContentEditable() const { |
+ return computeVisibleSelectionInDOMTree().isContentEditable(); |
+} |
- m_selectionInFlatTree = newSelection; |
- SelectionAdjuster::adjustSelectionInDOMTree(&m_selection, |
- m_selectionInFlatTree); |
+bool SelectionEditor::isContentRichlyEditable() const { |
+ return computeVisibleSelectionInDOMTree().isContentRichlyEditable(); |
} |
-void SelectionEditor::setWithoutValidation(const Position& base, |
- const Position& extent) { |
+void SelectionEditor::markCacheDirty() { |
+ m_cachedVisibleSelectionInFlatTree = VisibleSelectionInFlatTree(); |
+ m_cachedVisibleSelectionInDOMTree = VisibleSelection(); |
+ m_cacheIsDirty = true; |
+} |
+ |
+void SelectionEditor::setSelection(const SelectionInDOMTree& newSelection) { |
+ newSelection.assertValidFor(document()); |
+ if (m_selection == newSelection) |
+ return; |
resetLogicalRange(); |
- if (base.isNotNull()) |
- DCHECK_EQ(base.document(), document()); |
- if (extent.isNotNull()) |
- DCHECK_EQ(extent.document(), document()); |
clearDocumentCachedRange(); |
+ markCacheDirty(); |
+ m_selection = newSelection; |
+} |
+ |
+void SelectionEditor::didChangeChildren(const ContainerNode&) { |
+ markCacheDirty(); |
+ didFinishDOMMutation(); |
+} |
+ |
+static Position positionAfterPreviousSiblingOf(const Node& node) { |
+ Node* const previousSibling = NodeTraversal::previousSibling(node); |
+ if (!previousSibling) { |
+ // Returns |BeforeChildren| of a parent of |node| since |node| is the |
+ // first child. |
+ return Position::firstPositionInNode(NodeTraversal::parent(node)); |
+ } |
+ if (previousSibling->isCharacterDataNode()) { |
+ return Position(previousSibling, |
+ toCharacterData(previousSibling)->length()); |
+ } |
+ return Position::afterNode(previousSibling); |
+} |
- m_selection.setWithoutValidation(base, extent); |
- m_selectionInFlatTree.setWithoutValidation(toPositionInFlatTree(base), |
- toPositionInFlatTree(extent)); |
+static Position computePositionForNodeRemoval(const Position& position, |
+ Node& nodeToBeRemoved) { |
+ Node* const anchorNode = position.anchorNode(); |
+ if (anchorNode == nodeToBeRemoved) |
+ return positionAfterPreviousSiblingOf(nodeToBeRemoved); |
+ if (position.isOffsetInAnchor() && |
+ anchorNode == nodeToBeRemoved.parentNode()) { |
+ const int positionOffset = position.offsetInContainerNode(); |
+ if (positionOffset == 0) |
+ return position; |
+ // |nodeToBeRemoved| is sibling or itself of |position|. |
+ int offset = 0; |
+ for (Node& child : |
+ NodeTraversal::childrenOf(*nodeToBeRemoved.parentNode())) { |
+ if (nodeToBeRemoved == child) { |
+ // |position| is after |nodeToBeRemoved|. |
+ return Position(anchorNode, positionOffset - 1); |
+ } |
+ if (positionOffset == offset) { |
+ // |position| is before |nodeToBeRemoved|. |
+ return position; |
+ } |
+ ++offset; |
+ } |
+ NOTREACHED() << position << " should be handled."; |
+ } |
+ if (!nodeToBeRemoved.isContainerNode()) |
+ return position; |
+ if (toContainerNode(nodeToBeRemoved) |
+ .containsIncludingHostElements(*anchorNode)) |
+ return positionAfterPreviousSiblingOf(nodeToBeRemoved); |
+ return position; |
+} |
+ |
+void SelectionEditor::didFinishTextChange(const Position& newBase, |
+ const Position& newExtent) { |
+ if (newBase == m_selection.m_base && newExtent == m_selection.m_extent) { |
+ didFinishDOMMutation(); |
+ return; |
+ } |
+ m_selection.m_base = newBase; |
+ m_selection.m_extent = newExtent; |
+ markCacheDirty(); |
+ didFinishDOMMutation(); |
+} |
+ |
+void SelectionEditor::didFinishDOMMutation() { |
+ assertSelectionValid(); |
+} |
+ |
+void SelectionEditor::nodeWillBeRemoved(Node& nodeToBeRemoved) { |
+ if (m_selection.isNone()) |
+ return; |
+ const Position oldBase = m_selection.m_base; |
+ const Position oldExtent = m_selection.m_extent; |
+ const Position& newBase = |
+ computePositionForNodeRemoval(oldBase, nodeToBeRemoved); |
+ const Position& newExtent = |
+ computePositionForNodeRemoval(oldExtent, nodeToBeRemoved); |
+ if (newBase == oldBase && newExtent == oldExtent) |
+ return; |
+ m_selection = SelectionInDOMTree::Builder() |
+ .setBaseAndExtent(newBase, newExtent) |
+ .build(); |
+ markCacheDirty(); |
} |
void SelectionEditor::documentAttached(Document* document) { |
DCHECK(document); |
- DCHECK(!m_document) << m_document; |
- m_document = document; |
+ DCHECK(!lifecycleContext()) << lifecycleContext(); |
+ m_styleVersion = static_cast<uint64_t>(-1); |
+ clearVisibleSelection(); |
+ setContext(document); |
} |
-void SelectionEditor::documentDetached(const Document& document) { |
- DCHECK_EQ(m_document, &document); |
+void SelectionEditor::contextDestroyed(Document*) { |
dispose(); |
- m_document = nullptr; |
+ m_styleVersion = static_cast<uint64_t>(-1); |
+ m_selection = SelectionInDOMTree(); |
+ m_cachedVisibleSelectionInDOMTree = VisibleSelection(); |
+ m_cachedVisibleSelectionInFlatTree = VisibleSelectionInFlatTree(); |
+ m_cacheIsDirty = false; |
} |
void SelectionEditor::resetLogicalRange() { |
@@ -157,7 +264,7 @@ void SelectionEditor::setLogicalRange(Range* range) { |
Range* SelectionEditor::firstRange() const { |
if (m_logicalRange) |
return m_logicalRange->cloneRange(); |
- return firstRangeOf(m_selection); |
+ return firstRangeOf(computeVisibleSelectionInDOMTree()); |
} |
bool SelectionEditor::shouldAlwaysUseDirectionalSelection() const { |
@@ -165,10 +272,37 @@ bool SelectionEditor::shouldAlwaysUseDirectionalSelection() const { |
} |
void SelectionEditor::updateIfNeeded() { |
- DCHECK(m_selection.isValidFor(document())) << m_selection; |
- DCHECK(m_selectionInFlatTree.isValidFor(document())) << m_selection; |
- m_selection.updateIfNeeded(); |
- m_selectionInFlatTree.updateIfNeeded(); |
+ // TODO(yosin): We should unify |SelectionEditor::updateIfNeeded()| and |
+ // |updateCachedVisibleSelectionIfNeeded()| |
+ updateCachedVisibleSelectionIfNeeded(); |
+} |
+ |
+bool SelectionEditor::needsUpdateVisibleSelection() const { |
+ return m_cacheIsDirty || m_styleVersion != document().styleVersion(); |
+} |
+ |
+void SelectionEditor::updateCachedVisibleSelectionIfNeeded() { |
+ // Note: Since we |FrameCaret::updateApperance()| is called from |
+ // |FrameView::performPostLayoutTasks()|, we check lifecycle against |
+ // |AfterPerformLayout| instead of |LayoutClean|. |
+ DCHECK_GE(document().lifecycle().state(), |
+ DocumentLifecycle::AfterPerformLayout); |
+ assertSelectionValid(); |
+ if (!needsUpdateVisibleSelection()) |
+ return; |
+ |
+ m_cachedVisibleSelectionInDOMTree = createVisibleSelection(m_selection); |
+ m_cachedVisibleSelectionInFlatTree = createVisibleSelection( |
+ SelectionInFlatTree::Builder() |
+ .setBaseAndExtent(toPositionInFlatTree(m_selection.base()), |
+ toPositionInFlatTree(m_selection.extent())) |
+ .setAffinity(m_selection.affinity()) |
+ .setHasTrailingWhitespace(m_selection.hasTrailingWhitespace()) |
+ .setGranularity(m_selection.granularity()) |
+ .setIsDirectional(m_selection.isDirectional()) |
+ .build()); |
+ m_styleVersion = document().styleVersion(); |
+ m_cacheIsDirty = false; |
} |
void SelectionEditor::cacheRangeOfDocument(Range* range) { |
@@ -184,12 +318,13 @@ void SelectionEditor::clearDocumentCachedRange() { |
} |
DEFINE_TRACE(SelectionEditor) { |
- visitor->trace(m_document); |
visitor->trace(m_frame); |
visitor->trace(m_selection); |
- visitor->trace(m_selectionInFlatTree); |
+ visitor->trace(m_cachedVisibleSelectionInDOMTree); |
+ visitor->trace(m_cachedVisibleSelectionInFlatTree); |
visitor->trace(m_logicalRange); |
visitor->trace(m_cachedRange); |
+ SynchronousMutationObserver::trace(visitor); |
} |
} // namespace blink |