Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(556)

Unified Diff: third_party/WebKit/Source/core/editing/SelectionEditor.cpp

Issue 2680943004: Make FrameSelection to hold non-canonicalized positions (Closed)
Patch Set: 2017-02-10T16:43:07 Rebase and update test expectation for cached Document Range Created 3 years, 10 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
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

Powered by Google App Engine
This is Rietveld 408576698