Chromium Code Reviews| 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 |