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