| Index: third_party/WebKit/Source/core/editing/markers/DocumentMarkerController.cpp
|
| diff --git a/third_party/WebKit/Source/core/editing/markers/DocumentMarkerController.cpp b/third_party/WebKit/Source/core/editing/markers/DocumentMarkerController.cpp
|
| index e90f3213f9400715501819c4f8063d85afd4a7f9..fb31c11270409ccdd7e6766ad556876e64e5dac1 100644
|
| --- a/third_party/WebKit/Source/core/editing/markers/DocumentMarkerController.cpp
|
| +++ b/third_party/WebKit/Source/core/editing/markers/DocumentMarkerController.cpp
|
| @@ -33,6 +33,7 @@
|
| #include "core/dom/NodeTraversal.h"
|
| #include "core/dom/Range.h"
|
| #include "core/dom/Text.h"
|
| +#include "core/editing/FrameSelection.h"
|
| #include "core/editing/iterators/TextIterator.h"
|
| #include "core/editing/markers/CompositionMarker.h"
|
| #include "core/editing/markers/CompositionMarkerList.h"
|
| @@ -40,8 +41,12 @@
|
| #include "core/editing/markers/EditingMarkerList.h"
|
| #include "core/editing/markers/SpellCheckMarker.h"
|
| #include "core/editing/markers/SpellCheckMarkerList.h"
|
| +#include "core/editing/markers/SuggestionHighlightMarker.h"
|
| +#include "core/editing/markers/SuggestionHighlightMarkerList.h"
|
| +#include "core/editing/markers/SuggestionMarkerList.h"
|
| #include "core/editing/markers/TextMatchMarkerList.h"
|
| #include "core/frame/FrameView.h"
|
| +#include "core/frame/LocalFrame.h"
|
| #include "core/layout/LayoutObject.h"
|
|
|
| #ifndef NDEBUG
|
| @@ -60,6 +65,9 @@ void DocumentMarkerController::clear() {
|
| MarkerMap& markerMap = markerMapForType(type);
|
| markerMap.clear();
|
| }
|
| +
|
| + // Don't reset m_nextSuggestionMarkerID so if anything is still holding
|
| + // onto an old ID, it won't get confused by new markers
|
| }
|
|
|
| void DocumentMarkerController::addGrammarOrSpellingMarker(
|
| @@ -123,6 +131,41 @@ void DocumentMarkerController::addCompositionMarker(const EphemeralRange& range,
|
| }
|
| }
|
|
|
| +void DocumentMarkerController::addSuggestionMarker(const EphemeralRange& range,
|
| + Color underlineColor,
|
| + bool thick,
|
| + Color backgroundColor,
|
| + const Vector<String>& suggestions) {
|
| + DCHECK(!m_document->needsLayoutTreeUpdate());
|
| +
|
| + for (TextIterator markedText(range.startPosition(), range.endPosition());
|
| + !markedText.atEnd(); markedText.advance()) {
|
| + addMarker(markedText.currentContainer(),
|
| + new SuggestionMarker(
|
| + markedText.startOffsetInCurrentContainer(),
|
| + markedText.endOffsetInCurrentContainer(), underlineColor,
|
| + thick, backgroundColor, suggestions, m_nextSuggestionMarkerID++));
|
| + }
|
| +}
|
| +
|
| +void DocumentMarkerController::addSuggestionHighlightMarker(
|
| + const EphemeralRange& range,
|
| + Color underlineColor,
|
| + bool thick,
|
| + Color backgroundColor) {
|
| + DCHECK(!m_document->needsLayoutTreeUpdate());
|
| +
|
| + for (TextIterator markedText(range.startPosition(), range.endPosition());
|
| + !markedText.atEnd(); markedText.advance()) {
|
| + addMarker(markedText.currentContainer(),
|
| + new SuggestionHighlightMarker(
|
| + markedText.startOffsetInCurrentContainer(),
|
| + markedText.endOffsetInCurrentContainer(),
|
| + underlineColor,
|
| + thick, backgroundColor));
|
| + }
|
| +}
|
| +
|
| void DocumentMarkerController::prepareForDestruction() {
|
| clear();
|
| }
|
| @@ -212,7 +255,7 @@ void DocumentMarkerController::removeMarkers(
|
| int length,
|
| DocumentMarker::MarkerTypes markerTypes,
|
| RemovePartiallyOverlappingMarkerOrNot
|
| - shouldRemovePartiallyOverlappingMarker) {
|
| + shouldRemovePartiallyOverlappingMarker) {
|
| if (length <= 0)
|
| return;
|
|
|
| @@ -267,15 +310,32 @@ DocumentMarkerVector DocumentMarkerController::markers() {
|
| DocumentMarkerVector DocumentMarkerController::markersInRange(
|
| const EphemeralRange& range,
|
| DocumentMarker::MarkerTypes markerTypes) {
|
| + return markersInRangeInternal(range, markerTypes, false);
|
| +}
|
| +
|
| +DocumentMarkerVector DocumentMarkerController::markersInRangeInclusive(
|
| + const EphemeralRange& range,
|
| + DocumentMarker::MarkerTypes markerTypes) {
|
| + return markersInRangeInternal(range, markerTypes, true);
|
| +}
|
|
|
| +DocumentMarkerVector DocumentMarkerController::markersInRangeInternal(
|
| + const EphemeralRange& range,
|
| + DocumentMarker::MarkerTypes markerTypes,
|
| + bool includeSpansTouchingEndpoints) {
|
| DocumentMarkerVector foundMarkers;
|
|
|
| Node* startContainer = range.startPosition().computeContainerNode();
|
| - DCHECK(startContainer);
|
| + if (!startContainer)
|
| + return DocumentMarkerVector();
|
| +
|
| unsigned startOffset = static_cast<unsigned>(
|
| range.startPosition().computeOffsetInContainerNode());
|
| +
|
| Node* endContainer = range.endPosition().computeContainerNode();
|
| - DCHECK(endContainer);
|
| + if (!endContainer)
|
| + return DocumentMarkerVector();
|
| +
|
| unsigned endOffset =
|
| static_cast<unsigned>(range.endPosition().computeOffsetInContainerNode());
|
|
|
| @@ -283,10 +343,18 @@ DocumentMarkerVector DocumentMarkerController::markersInRange(
|
| for (DocumentMarker* marker : markersFor(&node)) {
|
| if (!markerTypes.contains(marker->type()))
|
| continue;
|
| - if (node == startContainer && marker->endOffset() <= startOffset)
|
| - continue;
|
| - if (node == endContainer && marker->startOffset() >= endOffset)
|
| - continue;
|
| + if (node == startContainer) {
|
| + if (marker->endOffset() < startOffset ||
|
| + (marker->endOffset() == startOffset &&
|
| + !includeSpansTouchingEndpoints))
|
| + continue;
|
| + }
|
| + if (node == endContainer) {
|
| + if (marker->startOffset() > endOffset ||
|
| + (marker->startOffset() == endOffset &&
|
| + !includeSpansTouchingEndpoints))
|
| + continue;
|
| + }
|
| foundMarkers.push_back(marker);
|
| }
|
| }
|
| @@ -338,6 +406,8 @@ DEFINE_TRACE(DocumentMarkerController) {
|
| visitor->trace(m_textMatches);
|
| visitor->trace(m_compositions);
|
| visitor->trace(m_document);
|
| + visitor->trace(m_suggestions);
|
| + visitor->trace(m_suggestionHighlights);
|
| SynchronousMutationObserver::trace(visitor);
|
| }
|
|
|
| @@ -369,6 +439,114 @@ void DocumentMarkerController::removeSpellingMarkersForWords(
|
| }
|
| }
|
|
|
| +void DocumentMarkerController::removeMarkersForWordsAffectedByEditing(
|
| + DocumentMarker::MarkerTypes markerTypes,
|
| + bool doNotRemoveIfSelectionAtWordBoundary) {
|
| + LOG(INFO) << "removeMarkersForWordsAffectedByEditing: " << doNotRemoveIfSelectionAtWordBoundary;
|
| + LOG(INFO) << "clearing suggestion markers: " << markerTypes.contains(DocumentMarker::Suggestion);
|
| + DCHECK(m_document->frame()->selection().isAvailable());
|
| + TRACE_EVENT0(
|
| + "blink",
|
| + "DocumentMarkerController::removeMarkersForWordsAffectedByEditing");
|
| +
|
| + Document* document = m_document->frame()->document();
|
| + DCHECK(document);
|
| +
|
| + // TODO(xiaochengh): The use of updateStyleAndLayoutIgnorePendingStylesheets
|
| + // needs to be audited. See http://crbug.com/590369 for more details.
|
| + document->updateStyleAndLayoutIgnorePendingStylesheets();
|
| +
|
| + // We want to remove the markers from a word if an editing command will change
|
| + // the word. This can happen in one of several scenarios:
|
| + // 1. Insert in the middle of a word.
|
| + // 2. Appending non whitespace at the beginning of word.
|
| + // 3. Appending non whitespace at the end of word.
|
| + // Note that, appending only whitespaces at the beginning or end of word won't
|
| + // change the word, so we don't need to remove the markers on that word. Of
|
| + // course, if current selection is a range, we potentially will edit two words
|
| + // that fall on the boundaries of selection, and remove words between the
|
| + // selection boundaries.
|
| + VisiblePosition startOfSelection = m_document->frame()
|
| + ->selection()
|
| + .computeVisibleSelectionInDOMTree()
|
| + .visibleStart();
|
| + VisiblePosition endOfSelection = m_document->frame()
|
| + ->selection()
|
| + .computeVisibleSelectionInDOMTree()
|
| + .visibleEnd();
|
| + if (startOfSelection.isNull())
|
| + return;
|
| + // First word is the word that ends after or on the start of selection.
|
| + VisiblePosition startOfFirstWord =
|
| + startOfWord(startOfSelection, LeftWordIfOnBoundary);
|
| + VisiblePosition endOfFirstWord =
|
| + endOfWord(startOfSelection, LeftWordIfOnBoundary);
|
| + // Last word is the word that begins before or on the end of selection
|
| + VisiblePosition startOfLastWord =
|
| + startOfWord(endOfSelection, RightWordIfOnBoundary);
|
| + VisiblePosition endOfLastWord =
|
| + endOfWord(endOfSelection, RightWordIfOnBoundary);
|
| +
|
| + if (startOfFirstWord.isNull()) {
|
| + startOfFirstWord = startOfWord(startOfSelection, RightWordIfOnBoundary);
|
| + endOfFirstWord = endOfWord(startOfSelection, RightWordIfOnBoundary);
|
| + }
|
| +
|
| + if (endOfLastWord.isNull()) {
|
| + startOfLastWord = startOfWord(endOfSelection, LeftWordIfOnBoundary);
|
| + endOfLastWord = endOfWord(endOfSelection, LeftWordIfOnBoundary);
|
| + }
|
| +
|
| + // If doNotRemoveIfSelectionAtWordBoundary is true, and first word ends at the
|
| + // start of selection, we choose next word as the first word.
|
| + if (doNotRemoveIfSelectionAtWordBoundary &&
|
| + endOfFirstWord.deepEquivalent() == startOfSelection.deepEquivalent()) {
|
| + startOfFirstWord = nextWordPosition(startOfFirstWord);
|
| + endOfFirstWord = endOfWord(startOfFirstWord, RightWordIfOnBoundary);
|
| + if (startOfFirstWord.deepEquivalent() == endOfSelection.deepEquivalent())
|
| + return;
|
| + }
|
| +
|
| + // If doNotRemoveIfSelectionAtWordBoundary is true, and last word begins at
|
| + // the end of selection, we choose previous word as the last word.
|
| + if (doNotRemoveIfSelectionAtWordBoundary &&
|
| + startOfLastWord.deepEquivalent() == endOfSelection.deepEquivalent()) {
|
| + startOfLastWord = previousWordPosition(startOfLastWord);
|
| + endOfLastWord = endOfWord(startOfLastWord, RightWordIfOnBoundary);
|
| + if (endOfLastWord.deepEquivalent() == startOfSelection.deepEquivalent())
|
| + return;
|
| + }
|
| +
|
| + if (startOfFirstWord.isNull() || endOfFirstWord.isNull() ||
|
| + startOfLastWord.isNull() || endOfLastWord.isNull())
|
| + return;
|
| +
|
| + const Position& removeMarkerStart = startOfFirstWord.deepEquivalent();
|
| + const Position& removeMarkerEnd = endOfLastWord.deepEquivalent();
|
| + if (removeMarkerStart > removeMarkerEnd) {
|
| + // editing/inserting/insert-br-008.html and more reach here.
|
| + // TODO(yosin): To avoid |DCHECK(removeMarkerStart <= removeMarkerEnd)|
|
| + // in |EphemeralRange| constructor, we have this if-statement. Once we
|
| + // fix |startOfWord()| and |endOfWord()|, we should remove this
|
| + // if-statement.
|
| + return;
|
| + }
|
| +
|
| + // Now we remove markers on everything between startOfFirstWord and
|
| + // endOfLastWord. However, if an autocorrection change a single word to
|
| + // multiple words, we want to remove correction mark from all the resulted
|
| + // words even we only edit one of them. For example, assuming autocorrection
|
| + // changes "avantgarde" to "avant garde", we will have CorrectionIndicator
|
| + // marker on both words and on the whitespace between them. If we then edit
|
| + // garde, we would like to remove the marker from word "avant" and whitespace
|
| + // as well. So we need to get the continous range of of marker that contains
|
| + // the word in question, and remove marker on that whole range.
|
| + const EphemeralRange wordRange(removeMarkerStart, removeMarkerEnd);
|
| + document->markers().removeMarkers(
|
| + wordRange, markerTypes,
|
| + DocumentMarkerController::RemovePartiallyOverlappingMarker);
|
| +}
|
| +
|
| void DocumentMarkerController::repaintMarkers(
|
| DocumentMarker::MarkerTypes markerTypes) {
|
| HeapHashSet<Member<Node>> nodesToRepaint;
|
| @@ -509,6 +687,10 @@ DocumentMarkerList* DocumentMarkerController::createMarkerListOfType(
|
| return new TextMatchMarkerList();
|
| case DocumentMarker::Composition:
|
| return new CompositionMarkerList();
|
| + case DocumentMarker::Suggestion:
|
| + return new SuggestionMarkerList();
|
| + case DocumentMarker::SuggestionHighlight:
|
| + return new SuggestionHighlightMarkerList();
|
| default:
|
| // MSVC thinks this method doesn't handle all enum values even though it
|
| // does. So we have to return something for the default case to avoid a
|
| @@ -553,6 +735,10 @@ DocumentMarkerController::markerMapForType(
|
| return m_textMatches;
|
| case DocumentMarker::Composition:
|
| return m_compositions;
|
| + case DocumentMarker::Suggestion:
|
| + return m_suggestions;
|
| + case DocumentMarker::SuggestionHighlight:
|
| + return m_suggestionHighlights;
|
| default:
|
| // MSVC thinks this method doesn't handle all enum values even though it
|
| // does. So we have to return something for the default case to avoid a
|
| @@ -568,6 +754,7 @@ void DocumentMarkerController::removeMarkers(
|
| RemovePartiallyOverlappingMarkerOrNot
|
| shouldRemovePartiallyOverlappingMarker) {
|
| for (; !markedText.atEnd(); markedText.advance()) {
|
| + LOG(INFO) << "removing markers from " << markedText.currentContainer() << " from offsets " << markedText.startOffsetInCurrentContainer() << " to " << markedText.endOffsetInCurrentContainer();
|
| int startOffset = markedText.startOffsetInCurrentContainer();
|
| int endOffset = markedText.endOffsetInCurrentContainer();
|
| removeMarkers(markedText.currentContainer(), startOffset,
|
|
|