| Index: third_party/WebKit/Source/core/editing/commands/InsertIncrementalTextCommand.cpp
|
| diff --git a/third_party/WebKit/Source/core/editing/commands/InsertIncrementalTextCommand.cpp b/third_party/WebKit/Source/core/editing/commands/InsertIncrementalTextCommand.cpp
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..9a00edf68096817f6bc857a7133a6e748babd9ff
|
| --- /dev/null
|
| +++ b/third_party/WebKit/Source/core/editing/commands/InsertIncrementalTextCommand.cpp
|
| @@ -0,0 +1,593 @@
|
| +// Copyright (c) 2016 The Chromium Authors. All rights reserved.
|
| +// Use of this source code is governed by a BSD-style license that can be
|
| +// found in the LICENSE file.
|
| +
|
| +#include "core/editing/commands/InsertIncrementalTextCommand.h"
|
| +
|
| +#include "core/dom/Document.h"
|
| +#include "core/dom/Element.h"
|
| +#include "core/dom/Text.h"
|
| +#include "core/editing/EditingUtilities.h"
|
| +#include "core/editing/Editor.h"
|
| +#include "core/editing/PlainTextRange.h"
|
| +#include "core/editing/VisibleUnits.h"
|
| +#include "core/frame/LocalFrame.h"
|
| +#include "core/html/HTMLSpanElement.h"
|
| +
|
| +namespace blink {
|
| +
|
| +InsertIncrementalTextCommand::InsertIncrementalTextCommand(
|
| + Document& document,
|
| + const String& text,
|
| + bool selectInsertedText,
|
| + RebalanceType rebalanceType)
|
| + : CompositeEditCommand(document),
|
| + m_text(text),
|
| + m_selectInsertedText(selectInsertedText),
|
| + m_rebalanceType(rebalanceType) {}
|
| +
|
| +String InsertIncrementalTextCommand::textDataForInputEvent() const {
|
| + return m_text;
|
| +}
|
| +
|
| +Position InsertIncrementalTextCommand::positionInsideTextNode(
|
| + const Position& p,
|
| + EditingState* editingState) {
|
| + Position pos = p;
|
| + if (isTabHTMLSpanElementTextNode(pos.anchorNode())) {
|
| + Text* textNode = document().createEditingTextNode("");
|
| + insertNodeAtTabSpanPosition(textNode, pos, editingState);
|
| + if (editingState->isAborted())
|
| + return Position();
|
| + return Position::firstPositionInNode(textNode);
|
| + }
|
| +
|
| + // Prepare for text input by looking at the specified position.
|
| + // It may be necessary to insert a text node to receive characters.
|
| + if (!pos.computeContainerNode()->isTextNode()) {
|
| + Text* textNode = document().createEditingTextNode("");
|
| + insertNodeAt(textNode, pos, editingState);
|
| + if (editingState->isAborted())
|
| + return Position();
|
| + return Position::firstPositionInNode(textNode);
|
| + }
|
| +
|
| + return pos;
|
| +}
|
| +
|
| +void InsertIncrementalTextCommand::setEndingSelectionWithoutValidation(
|
| + const Position& startPosition,
|
| + const Position& endPosition) {
|
| + // We could have inserted a part of composed character sequence,
|
| + // so we are basically treating ending selection as a range to avoid
|
| + // validation. <http://bugs.webkit.org/show_bug.cgi?id=15781>
|
| + setEndingSelection(SelectionInDOMTree::Builder()
|
| + .collapse(startPosition)
|
| + .extend(endPosition)
|
| + .setIsDirectional(endingSelection().isDirectional())
|
| + .build());
|
| +}
|
| +
|
| +// This avoids the expense of a full fledged delete operation, and avoids a
|
| +// layout that typically results from text removal.
|
| +bool InsertIncrementalTextCommand::performTrivialReplace(
|
| + const String& text,
|
| + bool selectInsertedText) {
|
| + if (!endingSelection().isRange())
|
| + return false;
|
| +
|
| + if (text.contains('\t') || text.contains(' ') || text.contains('\n'))
|
| + return false;
|
| +
|
| + Position start = endingSelection().start();
|
| + Position endPosition = replaceSelectedTextInNode(text);
|
| + if (endPosition.isNull())
|
| + return false;
|
| +
|
| + setEndingSelectionWithoutValidation(start, endPosition);
|
| + if (selectInsertedText)
|
| + return true;
|
| + setEndingSelection(SelectionInDOMTree::Builder()
|
| + .collapse(endingSelection().end())
|
| + .setIsDirectional(endingSelection().isDirectional())
|
| + .build());
|
| + return true;
|
| +}
|
| +
|
| +bool InsertIncrementalTextCommand::performOverwrite(const String& text,
|
| + bool selectInsertedText) {
|
| + Position start = endingSelection().start();
|
| + if (start.isNull() || !start.isOffsetInAnchor() ||
|
| + !start.computeContainerNode()->isTextNode())
|
| + return false;
|
| + Text* textNode = toText(start.computeContainerNode());
|
| + if (!textNode)
|
| + return false;
|
| +
|
| + unsigned count = std::min(text.length(),
|
| + textNode->length() - start.offsetInContainerNode());
|
| + if (!count)
|
| + return false;
|
| +
|
| + replaceTextInNode(textNode, start.offsetInContainerNode(), count, text);
|
| +
|
| + Position endPosition =
|
| + Position(textNode, start.offsetInContainerNode() + text.length());
|
| + setEndingSelectionWithoutValidation(start, endPosition);
|
| + if (selectInsertedText || endingSelection().isNone())
|
| + return true;
|
| + setEndingSelection(SelectionInDOMTree::Builder()
|
| + .collapse(endingSelection().end())
|
| + .setIsDirectional(endingSelection().isDirectional())
|
| + .build());
|
| + return true;
|
| +}
|
| +
|
| +static size_t computeCommonPrefixLength(const String& str1,
|
| + const String& str2) {
|
| + const size_t maxCommonPrefixLength = std::min(str1.length(), str2.length());
|
| + for (size_t index = 0; index < maxCommonPrefixLength; ++index) {
|
| + if (str1[index] != str2[index])
|
| + return index;
|
| + }
|
| + return maxCommonPrefixLength;
|
| +}
|
| +
|
| +static size_t computeCommonSuffixLength(const String& str1,
|
| + const String& str2) {
|
| + const size_t length1 = str1.length();
|
| + const size_t length2 = str2.length();
|
| + const size_t maxCommonSuffixLength = std::min(length1, length2);
|
| + for (size_t index = 0; index < maxCommonSuffixLength; ++index) {
|
| + if (str1[length1 - index - 1] != str2[length2 - index - 1])
|
| + return index;
|
| + }
|
| + return maxCommonSuffixLength;
|
| +}
|
| +
|
| +// If current position is at grapheme boundary, return 0; otherwise, return the
|
| +// distance to its nearest left grapheme boundary.
|
| +static size_t computeDistanceToLeftGraphemeBoundary(const Position& position) {
|
| + const Position& adjustedPosition = previousPositionOf(
|
| + nextPositionOf(position, PositionMoveType::GraphemeCluster),
|
| + PositionMoveType::GraphemeCluster);
|
| + DCHECK_EQ(position.anchorNode(), adjustedPosition.anchorNode());
|
| + DCHECK_GE(position.computeOffsetInContainerNode(),
|
| + adjustedPosition.computeOffsetInContainerNode());
|
| + return static_cast<size_t>(position.computeOffsetInContainerNode() -
|
| + adjustedPosition.computeOffsetInContainerNode());
|
| +}
|
| +
|
| +static size_t computeCommonGraphemeClusterPrefixLength(
|
| + const String& oldText,
|
| + const String& newText,
|
| + const Element* rootEditableElement) {
|
| + const size_t commonPrefixLength = computeCommonPrefixLength(oldText, newText);
|
| +
|
| + // For grapheme cluster, we should adjust it for grapheme boundary.
|
| + const EphemeralRange& range =
|
| + PlainTextRange(0, commonPrefixLength).createRange(*rootEditableElement);
|
| + if (range.isNull())
|
| + return 0;
|
| + const Position& position = range.endPosition();
|
| + const size_t diff = computeDistanceToLeftGraphemeBoundary(position);
|
| + DCHECK_GE(commonPrefixLength, diff);
|
| + return commonPrefixLength - diff;
|
| +}
|
| +
|
| +// If current position is at grapheme boundary, return 0; otherwise, return the
|
| +// distance to its nearest right grapheme boundary.
|
| +static size_t computeDistanceToRightGraphemeBoundary(const Position& position) {
|
| + const Position& adjustedPosition = nextPositionOf(
|
| + previousPositionOf(position, PositionMoveType::GraphemeCluster),
|
| + PositionMoveType::GraphemeCluster);
|
| + DCHECK_EQ(position.anchorNode(), adjustedPosition.anchorNode());
|
| + DCHECK_GE(adjustedPosition.computeOffsetInContainerNode(),
|
| + position.computeOffsetInContainerNode());
|
| + return static_cast<size_t>(adjustedPosition.computeOffsetInContainerNode() -
|
| + position.computeOffsetInContainerNode());
|
| +}
|
| +
|
| +static size_t computeCommonGraphemeClusterSuffixLength(
|
| + const String& oldText,
|
| + const String& newText,
|
| + const Element* rootEditableElement) {
|
| + const size_t commonSuffixLength = computeCommonSuffixLength(oldText, newText);
|
| +
|
| + // For grapheme cluster, we should adjust it for grapheme boundary.
|
| + const EphemeralRange& range =
|
| + PlainTextRange(0, oldText.length() - commonSuffixLength)
|
| + .createRange(*rootEditableElement);
|
| + if (range.isNull())
|
| + return 0;
|
| + const Position& position = range.endPosition();
|
| + const size_t diff = computeDistanceToRightGraphemeBoundary(position);
|
| + DCHECK_GE(commonSuffixLength, diff);
|
| + return commonSuffixLength - diff;
|
| +}
|
| +
|
| +static PlainTextRange getSelectionOffsets(LocalFrame* frame) {
|
| + EphemeralRange range = firstEphemeralRangeOf(frame->selection().selection());
|
| + if (range.isNull())
|
| + return PlainTextRange();
|
| + ContainerNode* editable =
|
| + frame->selection().rootEditableElementOrTreeScopeRootNode();
|
| + DCHECK(editable);
|
| +
|
| + return PlainTextRange::create(*editable, range);
|
| +}
|
| +
|
| +static const VisibleSelection createSelectionForIncrementalInsertion(
|
| + const size_t start,
|
| + const size_t end,
|
| + const bool isDirectional,
|
| + LocalFrame* frame) {
|
| + Element* element = frame->selection().selection().rootEditableElement();
|
| + DCHECK(element);
|
| +
|
| + const EphemeralRange& startRange =
|
| + PlainTextRange(0, static_cast<int>(start)).createRange(*element);
|
| + DCHECK(startRange.isNotNull());
|
| + const Position& startPosition = startRange.endPosition();
|
| +
|
| + const EphemeralRange& endRange =
|
| + PlainTextRange(0, static_cast<int>(end)).createRange(*element);
|
| + DCHECK(endRange.isNotNull());
|
| + const Position& endPosition = endRange.endPosition();
|
| +
|
| + VisibleSelection selection =
|
| + createVisibleSelection(SelectionInDOMTree::Builder()
|
| + .setBaseAndExtent(startPosition, endPosition)
|
| + .build());
|
| + selection.setIsDirectional(isDirectional);
|
| +
|
| + return selection;
|
| +}
|
| +
|
| +void InsertIncrementalTextCommand::setSelection(const size_t start,
|
| + const size_t end,
|
| + LocalFrame* frame) {
|
| + const VisibleSelection selection = createSelectionForIncrementalInsertion(
|
| + start, end, endingSelection().isDirectional(), frame);
|
| + setStartingSelection(selection);
|
| + setEndingSelectionWithoutValidation(selection.start(), selection.end());
|
| +
|
| + document().frame()->selection().setSelection(selection);
|
| +}
|
| +
|
| +void InsertIncrementalTextCommand::doApply(EditingState* editingState) {
|
| + // LOG(ERROR) << "hyb:\n";
|
| + // LOG(ERROR) << "hyb: InsertIncrementalTextCommand::doApply";
|
| + DCHECK_EQ(m_text.find('\n'), kNotFound);
|
| + if (!endingSelection().isNonOrphanedCaretOrRange())
|
| + return;
|
| + /*
|
| + LOG(ERROR)<<"hyb:
|
| + endingSelection().start()"<<endingSelection().start().computeOffsetInContainerNode();
|
| + LOG(ERROR)<<"hyb:
|
| + endingSelection().end()"<<endingSelection().end().computeOffsetInContainerNode();
|
| + LOG(ERROR)<<"hyb:
|
| + startingSelection().start()"<<startingSelection().start().computeOffsetInContainerNode();
|
| + LOG(ERROR)<<"hyb:
|
| + startingSelection().end()"<<startingSelection().end().computeOffsetInContainerNode();
|
| + LOG(ERROR)<<"hyb: frame
|
| + start:"<<document().frame()->selection().selection().start().computeOffsetInContainerNode();
|
| + LOG(ERROR)<<"hyb: frame
|
| + end:"<<document().frame()->selection().selection().end().computeOffsetInContainerNode();
|
| + LOG(ERROR)<<"hyb: m_text:"<<m_text.utf8().data();
|
| +
|
| + LOG(ERROR)<<"hyb: endingSelection().end node()"
|
| + <<endingSelection().end().computeContainerNode();
|
| + LOG(ERROR)<<"hyb: frame end node:"<<
|
| + document().frame()->selection().selection().end().computeContainerNode();
|
| +
|
| + */
|
| +
|
| + LocalFrame* frame = document().frame();
|
| + DCHECK(frame);
|
| + const Element* element = endingSelection().rootEditableElement();
|
| + DCHECK(element);
|
| +
|
| + const String& newText = m_text;
|
| + String oldText = frame->selectedText();
|
| + // LOG(ERROR) << "hyb: -------oldText:" << oldText.utf8().data();
|
| + // LOG(ERROR) << "hyb: -------newText:" << newText.utf8().data();
|
| +
|
| + //...........!!!!!
|
| + // need some comment!!!
|
| + if (element->tagName() == String("INPUT"))
|
| + oldText = emptyString();
|
| +
|
| + /*
|
| + LOG(ERROR) << "hyb: " <<
|
| + element->getIdAttribute().getString().utf8().data();
|
| + LOG(ERROR) << "hyb: "
|
| + << element->getNameAttribute().getString().utf8().data();
|
| + LOG(ERROR) << "hyb: "
|
| + << element->getClassAttribute().getString().utf8().data();
|
| +
|
| + LOG(ERROR) << "hyb: -------oldText:" << oldText.utf8().data();
|
| + */
|
| +
|
| + const size_t newTextLength = newText.length();
|
| + const size_t commonPrefixLength =
|
| + computeCommonGraphemeClusterPrefixLength(oldText, newText, element);
|
| + // We should ignore common prefix when finding common suffix.
|
| + const size_t commonSuffixLength = computeCommonGraphemeClusterSuffixLength(
|
| + oldText.right(oldText.length() - commonPrefixLength),
|
| + newText.right(newTextLength - commonPrefixLength), element);
|
| + // LOG(ERROR)<<"hyb: common: "<<commonPrefixLength<<" "<<commonSuffixLength;
|
| +
|
| + const String textToInsert =
|
| + newText.substring(commonPrefixLength, newTextLength - commonPrefixLength -
|
| + commonSuffixLength);
|
| + // LOG(ERROR) << "hyb: textToInsert:" << textToInsert.utf8().data();
|
| +
|
| + PlainTextRange selectionOffsets = getSelectionOffsets(frame);
|
| + const size_t selecitonStart = selectionOffsets.start();
|
| + const size_t selectionEnd = selectionOffsets.end();
|
| + const size_t insertionStart = selecitonStart + commonPrefixLength;
|
| + const size_t insertionEnd = selectionEnd - commonSuffixLength;
|
| +
|
| + // LOG(ERROR) << "hyb: ..." << selecitonStart << " " << selectionEnd << " "
|
| + //<< insertionStart << " " << insertionEnd;
|
| + DCHECK_LE(insertionStart, insertionEnd);
|
| +
|
| + const VisibleSelection selectionForInsertion =
|
| + createSelectionForIncrementalInsertion(insertionStart, insertionEnd,
|
| + endingSelection().isDirectional(),
|
| + frame);
|
| +
|
| + const bool changeSelection = selectionForInsertion != endingSelection();
|
| + // LOG(ERROR)<<"hyb:
|
| + // selectionForInsertion().start()"<<selectionForInsertion.start().computeOffsetInContainerNode();
|
| + // LOG(ERROR)<<"hyb:
|
| + // selectionForInsertion().end()"<<selectionForInsertion.end().computeOffsetInContainerNode();
|
| +
|
| + setStartingSelection(selectionForInsertion);
|
| + setEndingSelectionWithoutValidation(selectionForInsertion.start(),
|
| + selectionForInsertion.end());
|
| +
|
| + // ...
|
| + // document().updateStyleAndLayoutIgnorePendingStylesheets(); // crash without
|
| + // this line???
|
| +
|
| + // Delete the current selection.
|
| + // FIXME: This delete operation blows away the typing style.
|
| + if (endingSelection().isRange()) {
|
| + if (performTrivialReplace(textToInsert, m_selectInsertedText)) {
|
| + // LOG(ERROR) << "hyb: 1.1";
|
| + if (changeSelection)
|
| + setSelection(selecitonStart, selecitonStart + newTextLength, frame);
|
| + document().updateStyleAndLayoutIgnorePendingStylesheets();
|
| +
|
| + // document().updateStyleAndLayoutIgnorePendingStylesheets();//???
|
| + /*
|
| + LOG(ERROR)<<"hyb:
|
| + endingSelection().start()"<<endingSelection().start().computeOffsetInContainerNode();
|
| + LOG(ERROR)<<"hyb:
|
| + endingSelection().end()"<<endingSelection().end().computeOffsetInContainerNode();
|
| +
|
| + PlainTextRange selectionOffsets = getSelectionOffsets(frame);
|
| + const size_t selecitonStart = selectionOffsets.start();
|
| + const size_t selectionEnd = selectionOffsets.end();
|
| + LOG(ERROR)<<"hyb: ..."<<selecitonStart<<" "<<selectionEnd;
|
| +
|
| + LOG(ERROR)<<"hyb: frame end node:"<<
|
| + document().frame()->selection().selection().end().computeContainerNode();
|
| + */
|
| + return;
|
| + }
|
| + document().updateStyleAndLayoutIgnorePendingStylesheets();
|
| + bool endOfSelectionWasAtStartOfBlock =
|
| + isStartOfBlock(endingSelection().visibleEnd());
|
| + deleteSelection(editingState, false, true, false, false);
|
| + if (editingState->isAborted()) {
|
| + // LOG(ERROR) << "hyb: 1.2";
|
| + return;
|
| + }
|
| + // deleteSelection eventually makes a new endingSelection out of a Position.
|
| + // If that Position doesn't have a layoutObject (e.g. it is on a <frameset>
|
| + // in the DOM), the VisibleSelection cannot be canonicalized to anything
|
| + // other than NoSelection. The rest of this function requires a real
|
| + // endingSelection, so bail out.
|
| + if (endingSelection().isNone()) {
|
| + // LOG(ERROR) << "hyb: 1.3";
|
| + return;
|
| + }
|
| + if (endOfSelectionWasAtStartOfBlock) {
|
| + if (EditingStyle* typingStyle = frame->selection().typingStyle())
|
| + typingStyle->removeBlockProperties();
|
| + }
|
| + } else if (frame->editor().isOverwriteModeEnabled()) {
|
| + if (performOverwrite(textToInsert, m_selectInsertedText)) {
|
| + if (changeSelection)
|
| + setSelection(selecitonStart, selecitonStart + newTextLength, frame);
|
| + document().updateStyleAndLayoutIgnorePendingStylesheets();
|
| + // LOG(ERROR) << "hyb: 1.4";
|
| + return;
|
| + }
|
| + }
|
| +
|
| + // LOG(ERROR) << "hyb: 222";
|
| + document().updateStyleAndLayoutIgnorePendingStylesheets();
|
| +
|
| + Position startPosition(endingSelection().start());
|
| +
|
| + Position placeholder;
|
| + // We want to remove preserved newlines and brs that will collapse (and thus
|
| + // become unnecessary) when content is inserted just before them.
|
| + // FIXME: We shouldn't really have to do this, but removing placeholders is a
|
| + // workaround for 9661.
|
| + // If the caret is just before a placeholder, downstream will normalize the
|
| + // caret to it.
|
| + Position downstream(mostForwardCaretPosition(startPosition));
|
| + if (lineBreakExistsAtPosition(downstream)) {
|
| + // FIXME: This doesn't handle placeholders at the end of anonymous blocks.
|
| + VisiblePosition caret = createVisiblePosition(startPosition);
|
| + if (isEndOfBlock(caret) && isStartOfParagraph(caret))
|
| + placeholder = downstream;
|
| + // Don't remove the placeholder yet, otherwise the block we're inserting
|
| + // into would collapse before we get a chance to insert into it. We check
|
| + // for a placeholder now, though, because doing so requires the creation of
|
| + // a VisiblePosition, and if we did that post-insertion it would force a
|
| + // layout.
|
| + }
|
| +
|
| + // Insert the character at the leftmost candidate.
|
| + startPosition = mostBackwardCaretPosition(startPosition);
|
| +
|
| + // It is possible for the node that contains startPosition to contain only
|
| + // unrendered whitespace, and so deleteInsignificantText could remove it.
|
| + // Save the position before the node in case that happens.
|
| + DCHECK(startPosition.computeContainerNode()) << startPosition;
|
| + Position positionBeforeStartNode(
|
| + Position::inParentBeforeNode(*startPosition.computeContainerNode()));
|
| + deleteInsignificantText(startPosition,
|
| + mostForwardCaretPosition(startPosition));
|
| + if (!startPosition.isConnected())
|
| + startPosition = positionBeforeStartNode;
|
| + if (!isVisuallyEquivalentCandidate(startPosition))
|
| + startPosition = mostForwardCaretPosition(startPosition);
|
| +
|
| + startPosition =
|
| + positionAvoidingSpecialElementBoundary(startPosition, editingState);
|
| + if (editingState->isAborted())
|
| + return;
|
| +
|
| + // LOG(ERROR) << "hyb: 333";
|
| + Position endPosition;
|
| +
|
| + if (textToInsert == "\t" && isRichlyEditablePosition(startPosition)) {
|
| + endPosition = insertTab(startPosition, editingState);
|
| + if (editingState->isAborted())
|
| + return;
|
| + startPosition =
|
| + previousPositionOf(endPosition, PositionMoveType::GraphemeCluster);
|
| + if (placeholder.isNotNull())
|
| + removePlaceholderAt(placeholder);
|
| + } else {
|
| + // Make sure the document is set up to receive textToInsert
|
| + startPosition = positionInsideTextNode(startPosition, editingState);
|
| + if (editingState->isAborted())
|
| + return;
|
| + DCHECK(startPosition.isOffsetInAnchor()) << startPosition;
|
| + DCHECK(startPosition.computeContainerNode()) << startPosition;
|
| + DCHECK(startPosition.computeContainerNode()->isTextNode()) << startPosition;
|
| + if (placeholder.isNotNull())
|
| + removePlaceholderAt(placeholder);
|
| + Text* textNode = toText(startPosition.computeContainerNode());
|
| + const unsigned offset = startPosition.offsetInContainerNode();
|
| +
|
| + insertTextIntoNode(textNode, offset, textToInsert);
|
| + endPosition = Position(textNode, offset + textToInsert.length());
|
| +
|
| + if (m_rebalanceType == RebalanceLeadingAndTrailingWhitespaces) {
|
| + // The insertion may require adjusting adjacent whitespace, if it is
|
| + // present.
|
| + rebalanceWhitespaceAt(endPosition);
|
| + // Rebalancing on both sides isn't necessary if we've inserted only
|
| + // spaces.
|
| + if (!shouldRebalanceLeadingWhitespaceFor(textToInsert))
|
| + rebalanceWhitespaceAt(startPosition);
|
| + } else {
|
| + DCHECK_EQ(m_rebalanceType, RebalanceAllWhitespaces);
|
| + if (canRebalance(startPosition) && canRebalance(endPosition)) {
|
| + rebalanceWhitespaceOnTextSubstring(
|
| + textNode, startPosition.offsetInContainerNode(),
|
| + endPosition.offsetInContainerNode());
|
| + }
|
| + }
|
| + }
|
| +
|
| + LOG(ERROR) << "hyb: 555";
|
| + document().updateStyleAndLayoutIgnorePendingStylesheets(); //??
|
| +
|
| + LOG(ERROR) << "hyb: 666";
|
| + setEndingSelectionWithoutValidation(startPosition, endPosition);
|
| +
|
| + // document().updateStyleAndLayoutIgnorePendingStylesheets(); //???
|
| +
|
| + /*
|
| + LOG(ERROR) << "hyb: endingSelection().start()"
|
| + << endingSelection().start().computeOffsetInContainerNode();
|
| + LOG(ERROR) << "hyb: endingSelection().end()"
|
| + << endingSelection().end().computeOffsetInContainerNode();
|
| + */
|
| +
|
| + // Handle the case where there is a typing style.
|
| + if (EditingStyle* typingStyle = frame->selection().typingStyle()) {
|
| + typingStyle->prepareToApplyAt(endPosition,
|
| + EditingStyle::PreserveWritingDirection);
|
| + if (!typingStyle->isEmpty()) {
|
| + applyStyle(typingStyle, editingState);
|
| + if (editingState->isAborted())
|
| + return;
|
| + }
|
| + }
|
| +
|
| + LOG(ERROR) << "hyb: 777"; ///
|
| + // totally test...
|
| + if (changeSelection)
|
| + setSelection(selecitonStart, selecitonStart + newTextLength, frame);
|
| +
|
| + // document().updateStyleAndLayoutIgnorePendingStylesheets(); //???
|
| +
|
| + if (!m_selectInsertedText) {
|
| + SelectionInDOMTree::Builder builder;
|
| + builder.setAffinity(endingSelection().affinity());
|
| + builder.setIsDirectional(endingSelection().isDirectional());
|
| + if (endingSelection().end().isNotNull())
|
| + builder.collapse(endingSelection().end());
|
| + setEndingSelection(builder.build());
|
| + }
|
| +
|
| + LOG(ERROR) << "hyb: 888"; ////
|
| +}
|
| +
|
| +Position InsertIncrementalTextCommand::insertTab(const Position& pos,
|
| + EditingState* editingState) {
|
| + document().updateStyleAndLayoutIgnorePendingStylesheets();
|
| +
|
| + Position insertPos = createVisiblePosition(pos).deepEquivalent();
|
| + if (insertPos.isNull())
|
| + return pos;
|
| +
|
| + Node* node = insertPos.computeContainerNode();
|
| + unsigned offset = node->isTextNode() ? insertPos.offsetInContainerNode() : 0;
|
| +
|
| + // keep tabs coalesced in tab span
|
| + if (isTabHTMLSpanElementTextNode(node)) {
|
| + Text* textNode = toText(node);
|
| + insertTextIntoNode(textNode, offset, "\t");
|
| + return Position(textNode, offset + 1);
|
| + }
|
| +
|
| + // create new tab span
|
| + HTMLSpanElement* spanElement = createTabSpanElement(document());
|
| +
|
| + // place it
|
| + if (!node->isTextNode()) {
|
| + insertNodeAt(spanElement, insertPos, editingState);
|
| + } else {
|
| + Text* textNode = toText(node);
|
| + if (offset >= textNode->length()) {
|
| + insertNodeAfter(spanElement, textNode, editingState);
|
| + } else {
|
| + // split node to make room for the span
|
| + // NOTE: splitTextNode uses textNode for the
|
| + // second node in the split, so we need to
|
| + // insert the span before it.
|
| + if (offset > 0)
|
| + splitTextNode(textNode, offset);
|
| + insertNodeBefore(spanElement, textNode, editingState);
|
| + }
|
| + }
|
| + if (editingState->isAborted())
|
| + return Position();
|
| +
|
| + // return the position following the new tab
|
| + return Position::lastPositionInNode(spanElement);
|
| +}
|
| +
|
| +} // namespace blink
|
|
|