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 3cbe4ecee2d19bcee6490b58cb16a34655a86a29..3835d0678c75ee6ef0417df6d5821adde52c828a 100644 |
--- a/third_party/WebKit/Source/core/editing/SelectionEditor.cpp |
+++ b/third_party/WebKit/Source/core/editing/SelectionEditor.cpp |
@@ -178,6 +178,199 @@ void SelectionEditor::contextDestroyed(Document*) { |
m_cacheIsDirty = false; |
} |
+static Position computePositionForChildrenRemoval(const Position& position, |
+ ContainerNode& container) { |
+ Node* node = position.computeContainerNode(); |
+ if (container.containsIncludingHostElements(*node)) |
+ return Position::firstPositionInNode(&container); |
+ return position; |
+} |
+ |
+void SelectionEditor::nodeChildrenWillBeRemoved(ContainerNode& container) { |
+ if (m_selection.isNone()) |
+ return; |
+ const Position oldBase = m_selection.m_base; |
+ const Position oldExtent = m_selection.m_extent; |
+ const Position& newBase = |
+ computePositionForChildrenRemoval(oldBase, container); |
+ const Position& newExtent = |
+ computePositionForChildrenRemoval(oldExtent, container); |
+ if (newBase == oldBase && newExtent == oldExtent) |
+ return; |
+ m_selection = SelectionInDOMTree::Builder() |
+ .setBaseAndExtent(newBase, newExtent) |
+ .build(); |
+ markCacheDirty(); |
+} |
+ |
+static Position computePositionForNodeRemoval(const Position& position, |
+ Node& nodeToBeRemoved) { |
+ Position result = position; |
+ // TODO(yosin): We should rename |updatePositionForNodeRemoval()| |
+ // to |computePositionForNodeRemoval()| to avoid using output parameter. |
+ updatePositionForNodeRemoval(result, nodeToBeRemoved); |
+ return result; |
+} |
+ |
+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(); |
+} |
+ |
+static Position updatePositionAfterAdoptingTextReplacement( |
+ const Position& position, |
+ CharacterData* node, |
+ unsigned offset, |
+ unsigned oldLength, |
+ unsigned newLength) { |
+ if (position.anchorNode() != node) |
+ return position; |
+ |
+ if (position.isBeforeAnchor()) { |
+ return updatePositionAfterAdoptingTextReplacement( |
+ Position(node, 0), node, offset, oldLength, newLength); |
+ } |
+ if (position.isAfterAnchor()) { |
+ return updatePositionAfterAdoptingTextReplacement( |
+ Position(node, oldLength), node, offset, oldLength, newLength); |
+ } |
+ |
+ // See: |
+ // http://www.w3.org/TR/DOM-Level-2-Traversal-Range/ranges.html#Level-2-Range-Mutation |
+ DCHECK_GE(position.offsetInContainerNode(), 0); |
+ unsigned positionOffset = |
+ static_cast<unsigned>(position.offsetInContainerNode()); |
+ // Replacing text can be viewed as a deletion followed by insertion. |
+ if (positionOffset >= offset && positionOffset <= offset + oldLength) |
+ positionOffset = offset; |
+ |
+ // Adjust the offset if the position is after the end of the deleted contents |
+ // (positionOffset > offset + oldLength) to avoid having a stale offset. |
+ if (positionOffset > offset + oldLength) |
+ positionOffset = positionOffset - oldLength + newLength; |
+ |
+ // Due to case folding |
+ // (http://unicode.org/Public/UCD/latest/ucd/CaseFolding.txt), LayoutText |
+ // length may be different from Text length. A correct implementation would |
+ // translate the LayoutText offset to a Text offset; this is just a safety |
+ // precaution to avoid offset values that run off the end of the Text. |
+ if (positionOffset > node->length()) |
+ positionOffset = node->length(); |
+ |
+ return Position(node, positionOffset); |
+} |
+ |
+void SelectionEditor::didUpdateCharacterData(CharacterData* node, |
+ unsigned offset, |
+ unsigned oldLength, |
+ unsigned newLength) { |
+ // The fragment check is a performance optimization. See |
+ // http://trac.webkit.org/changeset/30062. |
+ if (m_selection.isNone() || !node || !node->isConnected()) { |
+ didFinishDOMMutation(); |
+ return; |
+ } |
+ const Position& newBase = updatePositionAfterAdoptingTextReplacement( |
+ m_selection.m_base, node, offset, oldLength, newLength); |
+ const Position& newExtent = updatePositionAfterAdoptingTextReplacement( |
+ m_selection.m_extent, node, offset, oldLength, newLength); |
+ didFinishTextChange(newBase, newExtent); |
+} |
+ |
+// TODO(yosin): We should introduce |Position(const Text&, int)| to avoid |
+// |const_cast<Text*>|. |
+static Position updatePostionAfterAdoptingTextNodesMerged( |
+ const Position& position, |
+ const Text& mergedNode, |
+ const NodeWithIndex& nodeToBeRemovedWithIndex, |
+ unsigned oldLength) { |
+ Node* const anchorNode = position.anchorNode(); |
+ const Node& nodeToBeRemoved = nodeToBeRemovedWithIndex.node(); |
+ switch (position.anchorType()) { |
+ case PositionAnchorType::BeforeChildren: |
+ case PositionAnchorType::AfterChildren: |
+ return position; |
+ case PositionAnchorType::BeforeAnchor: |
+ if (anchorNode == nodeToBeRemoved) |
+ return Position(const_cast<Text*>(&mergedNode), mergedNode.length()); |
+ return position; |
+ case PositionAnchorType::AfterAnchor: |
+ if (anchorNode == nodeToBeRemoved) |
+ return Position(const_cast<Text*>(&mergedNode), mergedNode.length()); |
+ if (anchorNode == mergedNode) |
+ return Position(const_cast<Text*>(&mergedNode), oldLength); |
+ return position; |
+ case PositionAnchorType::OffsetInAnchor: { |
+ const int offset = position.offsetInContainerNode(); |
+ if (anchorNode == nodeToBeRemoved) |
+ return Position(const_cast<Text*>(&mergedNode), oldLength + offset); |
+ if (anchorNode == nodeToBeRemoved.parentNode() && |
+ offset == nodeToBeRemovedWithIndex.index()) { |
+ return Position(const_cast<Text*>(&mergedNode), oldLength); |
+ } |
+ return position; |
+ } |
+ } |
+ NOTREACHED() << position; |
+ return position; |
+} |
+ |
+void SelectionEditor::didMergeTextNodes( |
+ const Text& mergedNode, |
+ const NodeWithIndex& nodeToBeRemovedWithIndex, |
+ unsigned oldLength) { |
+ if (m_selection.isNone()) { |
+ didFinishDOMMutation(); |
+ return; |
+ } |
+ const Position& newBase = updatePostionAfterAdoptingTextNodesMerged( |
+ m_selection.m_base, mergedNode, nodeToBeRemovedWithIndex, oldLength); |
+ const Position& newExtent = updatePostionAfterAdoptingTextNodesMerged( |
+ m_selection.m_extent, mergedNode, nodeToBeRemovedWithIndex, oldLength); |
+ didFinishTextChange(newBase, newExtent); |
+} |
+ |
+static Position updatePostionAfterAdoptingTextNodeSplit( |
+ const Position& position, |
+ const Text& oldNode) { |
+ if (!position.anchorNode() || position.anchorNode() != &oldNode || |
+ !position.isOffsetInAnchor()) |
+ return position; |
+ // See: |
+ // http://www.w3.org/TR/DOM-Level-2-Traversal-Range/ranges.html#Level-2-Range-Mutation |
+ DCHECK_GE(position.offsetInContainerNode(), 0); |
+ unsigned positionOffset = |
+ static_cast<unsigned>(position.offsetInContainerNode()); |
+ unsigned oldLength = oldNode.length(); |
+ if (positionOffset <= oldLength) |
+ return position; |
+ return Position(toText(oldNode.nextSibling()), positionOffset - oldLength); |
+} |
+ |
+void SelectionEditor::didSplitTextNode(const Text& oldNode) { |
+ if (m_selection.isNone() || !oldNode.isConnected()) { |
+ didFinishDOMMutation(); |
+ return; |
+ } |
+ const Position& newBase = |
+ updatePostionAfterAdoptingTextNodeSplit(m_selection.m_base, oldNode); |
+ const Position& newExtent = |
+ updatePostionAfterAdoptingTextNodeSplit(m_selection.m_extent, oldNode); |
+ didFinishTextChange(newBase, newExtent); |
+} |
+ |
void SelectionEditor::resetLogicalRange() { |
// Non-collapsed ranges are not allowed to start at the end of a line that |
// is wrapped, they start at the beginning of the next line instead |