| 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
|
|
|