Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(489)

Unified Diff: third_party/WebKit/Source/core/editing/markers/DocumentMarkerController.cpp

Issue 2650113004: [WIP] Add support for Android SuggestionSpans when editing text (Closed)
Patch Set: Uploading the latest version from my repo so I can reference it Created 3 years, 8 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
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,

Powered by Google App Engine
This is Rietveld 408576698