1 /*
2  * Copyright (C) 1999 Lars Knoll (knoll@kde.org)
3  *           (C) 1999 Antti Koivisto (koivisto@kde.org)
4  *           (C) 2001 Dirk Mueller (mueller@kde.org)
5  *           (C) 2006 Alexey Proskuryakov (ap@webkit.org)
6  * Copyright (C) 2004, 2005, 2006, 2007, 2008, 2009, 2010 Apple Inc. All rights
7  * reserved.
8  * Copyright (C) 2008, 2009 Torch Mobile Inc. All rights reserved.
9  * (http://www.torchmobile.com/)
10  * Copyright (C) Research In Motion Limited 2010. All rights reserved.
11  *
12  * This library is free software; you can redistribute it and/or
13  * modify it under the terms of the GNU Library General Public
14  * License as published by the Free Software Foundation; either
15  * version 2 of the License, or (at your option) any later version.
16  *
17  * This library is distributed in the hope that it will be useful,
18  * but WITHOUT ANY WARRANTY; without even the implied warranty of
19  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
20  * Library General Public License for more details.
21  *
22  * You should have received a copy of the GNU Library General Public License
23  * along with this library; see the file COPYING.LIB.  If not, write to
24  * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
25  * Boston, MA 02110-1301, USA.
26  *
27  */
28 
29 #include "third_party/blink/renderer/core/editing/markers/document_marker_controller.h"
30 
31 #include <algorithm>
32 #include "third_party/blink/renderer/core/accessibility/ax_object_cache.h"
33 #include "third_party/blink/renderer/core/dom/node.h"
34 #include "third_party/blink/renderer/core/dom/node_traversal.h"
35 #include "third_party/blink/renderer/core/dom/text.h"
36 #include "third_party/blink/renderer/core/editing/ephemeral_range.h"
37 #include "third_party/blink/renderer/core/editing/iterators/text_iterator.h"
38 #include "third_party/blink/renderer/core/editing/markers/active_suggestion_marker.h"
39 #include "third_party/blink/renderer/core/editing/markers/active_suggestion_marker_list_impl.h"
40 #include "third_party/blink/renderer/core/editing/markers/composition_marker.h"
41 #include "third_party/blink/renderer/core/editing/markers/composition_marker_list_impl.h"
42 #include "third_party/blink/renderer/core/editing/markers/grammar_marker.h"
43 #include "third_party/blink/renderer/core/editing/markers/grammar_marker_list_impl.h"
44 #include "third_party/blink/renderer/core/editing/markers/sorted_document_marker_list_editor.h"
45 #include "third_party/blink/renderer/core/editing/markers/spelling_marker.h"
46 #include "third_party/blink/renderer/core/editing/markers/spelling_marker_list_impl.h"
47 #include "third_party/blink/renderer/core/editing/markers/suggestion_marker.h"
48 #include "third_party/blink/renderer/core/editing/markers/suggestion_marker_list_impl.h"
49 #include "third_party/blink/renderer/core/editing/markers/text_fragment_marker.h"
50 #include "third_party/blink/renderer/core/editing/markers/text_fragment_marker_list_impl.h"
51 #include "third_party/blink/renderer/core/editing/markers/text_match_marker.h"
52 #include "third_party/blink/renderer/core/editing/markers/text_match_marker_list_impl.h"
53 #include "third_party/blink/renderer/core/editing/position.h"
54 #include "third_party/blink/renderer/core/editing/visible_position.h"
55 #include "third_party/blink/renderer/core/editing/visible_units.h"
56 #include "third_party/blink/renderer/core/frame/local_frame_view.h"
57 #include "third_party/blink/renderer/core/layout/layout_object.h"
58 #include "third_party/blink/renderer/core/layout/layout_view.h"
59 
60 namespace blink {
61 
62 namespace {
63 
MarkerTypeToMarkerIndex(DocumentMarker::MarkerType type)64 DocumentMarker::MarkerTypeIndex MarkerTypeToMarkerIndex(
65     DocumentMarker::MarkerType type) {
66   switch (type) {
67     case DocumentMarker::kSpelling:
68       return DocumentMarker::kSpellingMarkerIndex;
69     case DocumentMarker::kGrammar:
70       return DocumentMarker::kGrammarMarkerIndex;
71     case DocumentMarker::kTextMatch:
72       return DocumentMarker::kTextMatchMarkerIndex;
73     case DocumentMarker::kComposition:
74       return DocumentMarker::kCompositionMarkerIndex;
75     case DocumentMarker::kActiveSuggestion:
76       return DocumentMarker::kActiveSuggestionMarkerIndex;
77     case DocumentMarker::kSuggestion:
78       return DocumentMarker::kSuggestionMarkerIndex;
79     case DocumentMarker::kTextFragment:
80       return DocumentMarker::kTextFragmentMarkerIndex;
81   }
82 
83   NOTREACHED();
84   return DocumentMarker::kSpellingMarkerIndex;
85 }
86 
CreateListForType(DocumentMarker::MarkerType type)87 DocumentMarkerList* CreateListForType(DocumentMarker::MarkerType type) {
88   switch (type) {
89     case DocumentMarker::kActiveSuggestion:
90       return MakeGarbageCollected<ActiveSuggestionMarkerListImpl>();
91     case DocumentMarker::kComposition:
92       return MakeGarbageCollected<CompositionMarkerListImpl>();
93     case DocumentMarker::kSpelling:
94       return MakeGarbageCollected<SpellingMarkerListImpl>();
95     case DocumentMarker::kGrammar:
96       return MakeGarbageCollected<GrammarMarkerListImpl>();
97     case DocumentMarker::kSuggestion:
98       return MakeGarbageCollected<SuggestionMarkerListImpl>();
99     case DocumentMarker::kTextMatch:
100       return MakeGarbageCollected<TextMatchMarkerListImpl>();
101     case DocumentMarker::kTextFragment:
102       return MakeGarbageCollected<TextFragmentMarkerListImpl>();
103   }
104 
105   NOTREACHED();
106   return nullptr;
107 }
108 
InvalidatePaintForNode(const Node & node)109 void InvalidatePaintForNode(const Node& node) {
110   if (!node.GetLayoutObject())
111     return;
112 
113   node.GetLayoutObject()->SetShouldDoFullPaintInvalidation(
114       PaintInvalidationReason::kDocumentMarker);
115 
116   // Tell accessibility about the new marker.
117   AXObjectCache* ax_object_cache = node.GetDocument().ExistingAXObjectCache();
118   if (!ax_object_cache)
119     return;
120   // TODO(nektar): Do major refactoring of all AX classes to comply with const
121   // correctness.
122   Node* non_const_node = &const_cast<Node&>(node);
123   ax_object_cache->HandleTextMarkerDataAdded(non_const_node, non_const_node);
124 }
125 
SearchAroundPositionStart(const PositionInFlatTree & position)126 PositionInFlatTree SearchAroundPositionStart(
127     const PositionInFlatTree& position) {
128   const PositionInFlatTree start_of_word_or_null =
129       StartOfWordPosition(position, kPreviousWordIfOnBoundary);
130   return start_of_word_or_null.IsNotNull() ? start_of_word_or_null : position;
131 }
132 
SearchAroundPositionEnd(const PositionInFlatTree & position)133 PositionInFlatTree SearchAroundPositionEnd(const PositionInFlatTree& position) {
134   const PositionInFlatTree end_of_word_or_null =
135       EndOfWordPosition(position, kNextWordIfOnBoundary);
136   return end_of_word_or_null.IsNotNull() ? end_of_word_or_null : position;
137 }
138 
139 }  // namespace
140 
ListForType(MarkerLists * marker_lists,DocumentMarker::MarkerType type)141 Member<DocumentMarkerList>& DocumentMarkerController::ListForType(
142     MarkerLists* marker_lists,
143     DocumentMarker::MarkerType type) {
144   const wtf_size_t marker_list_index = MarkerTypeToMarkerIndex(type);
145   return (*marker_lists)[marker_list_index];
146 }
147 
PossiblyHasMarkers(DocumentMarker::MarkerType type) const148 bool DocumentMarkerController::PossiblyHasMarkers(
149     DocumentMarker::MarkerType type) const {
150   return PossiblyHasMarkers(DocumentMarker::MarkerTypes(type));
151 }
152 
PossiblyHasMarkers(DocumentMarker::MarkerTypes types) const153 inline bool DocumentMarkerController::PossiblyHasMarkers(
154     DocumentMarker::MarkerTypes types) const {
155   DCHECK(!markers_.IsEmpty() ||
156          possibly_existing_marker_types_ == DocumentMarker::MarkerTypes(0));
157   return possibly_existing_marker_types_.Intersects(types);
158 }
159 
DocumentMarkerController(Document & document)160 DocumentMarkerController::DocumentMarkerController(Document& document)
161     : document_(&document) {}
162 
Clear()163 void DocumentMarkerController::Clear() {
164   markers_.clear();
165   possibly_existing_marker_types_ = DocumentMarker::MarkerTypes();
166   SetDocument(nullptr);
167 }
168 
AddSpellingMarker(const EphemeralRange & range,const String & description)169 void DocumentMarkerController::AddSpellingMarker(const EphemeralRange& range,
170                                                  const String& description) {
171   AddMarkerInternal(range, [&description](int start_offset, int end_offset) {
172     return MakeGarbageCollected<SpellingMarker>(start_offset, end_offset,
173                                                 description);
174   });
175 }
176 
AddGrammarMarker(const EphemeralRange & range,const String & description)177 void DocumentMarkerController::AddGrammarMarker(const EphemeralRange& range,
178                                                 const String& description) {
179   AddMarkerInternal(range, [&description](int start_offset, int end_offset) {
180     return MakeGarbageCollected<GrammarMarker>(start_offset, end_offset,
181                                                description);
182   });
183 }
184 
AddTextMatchMarker(const EphemeralRange & range,TextMatchMarker::MatchStatus match_status)185 void DocumentMarkerController::AddTextMatchMarker(
186     const EphemeralRange& range,
187     TextMatchMarker::MatchStatus match_status) {
188   DCHECK(!document_->NeedsLayoutTreeUpdate());
189   AddMarkerInternal(
190       range,
191       [match_status](int start_offset, int end_offset) {
192         return MakeGarbageCollected<TextMatchMarker>(start_offset, end_offset,
193                                                      match_status);
194       },
195       // Since we've already determined to have a match in the given range (via
196       // FindBuffer), we can ignore the display lock for the purposes of finding
197       // where to put the marker.
198       TextIteratorBehavior::Builder().SetIgnoresDisplayLock(true).Build());
199   // Don't invalidate tickmarks here. TextFinder invalidates tickmarks using a
200   // throttling algorithm. crbug.com/6819.
201 }
202 
AddCompositionMarker(const EphemeralRange & range,Color underline_color,ui::mojom::ImeTextSpanThickness thickness,ui::mojom::ImeTextSpanUnderlineStyle underline_style,Color text_color,Color background_color)203 void DocumentMarkerController::AddCompositionMarker(
204     const EphemeralRange& range,
205     Color underline_color,
206     ui::mojom::ImeTextSpanThickness thickness,
207     ui::mojom::ImeTextSpanUnderlineStyle underline_style,
208     Color text_color,
209     Color background_color) {
210   DCHECK(!document_->NeedsLayoutTreeUpdate());
211   AddMarkerInternal(range,
212                     [underline_color, thickness, underline_style, text_color,
213                      background_color](int start_offset, int end_offset) {
214                       return MakeGarbageCollected<CompositionMarker>(
215                           start_offset, end_offset, underline_color, thickness,
216                           underline_style, text_color, background_color);
217                     });
218 }
219 
AddActiveSuggestionMarker(const EphemeralRange & range,Color underline_color,ui::mojom::ImeTextSpanThickness thickness,ui::mojom::ImeTextSpanUnderlineStyle underline_style,Color text_color,Color background_color)220 void DocumentMarkerController::AddActiveSuggestionMarker(
221     const EphemeralRange& range,
222     Color underline_color,
223     ui::mojom::ImeTextSpanThickness thickness,
224     ui::mojom::ImeTextSpanUnderlineStyle underline_style,
225     Color text_color,
226     Color background_color) {
227   DCHECK(!document_->NeedsLayoutTreeUpdate());
228   AddMarkerInternal(range,
229                     [underline_color, thickness, underline_style, text_color,
230                      background_color](int start_offset, int end_offset) {
231                       return MakeGarbageCollected<ActiveSuggestionMarker>(
232                           start_offset, end_offset, underline_color, thickness,
233                           underline_style, text_color, background_color);
234                     });
235 }
236 
AddSuggestionMarker(const EphemeralRange & range,const SuggestionMarkerProperties & properties)237 void DocumentMarkerController::AddSuggestionMarker(
238     const EphemeralRange& range,
239     const SuggestionMarkerProperties& properties) {
240   DCHECK(!document_->NeedsLayoutTreeUpdate());
241   AddMarkerInternal(range, [&properties](int start_offset, int end_offset) {
242     return MakeGarbageCollected<SuggestionMarker>(start_offset, end_offset,
243                                                   properties);
244   });
245 }
246 
AddTextFragmentMarker(const EphemeralRange & range)247 void DocumentMarkerController::AddTextFragmentMarker(
248     const EphemeralRange& range) {
249   DCHECK(!document_->NeedsLayoutTreeUpdate());
250   AddMarkerInternal(range, [](int start_offset, int end_offset) {
251     return MakeGarbageCollected<TextFragmentMarker>(start_offset, end_offset);
252   });
253 }
254 
PrepareForDestruction()255 void DocumentMarkerController::PrepareForDestruction() {
256   Clear();
257 }
258 
RemoveMarkers(TextIterator & marked_text,DocumentMarker::MarkerTypes marker_types)259 void DocumentMarkerController::RemoveMarkers(
260     TextIterator& marked_text,
261     DocumentMarker::MarkerTypes marker_types) {
262   for (; !marked_text.AtEnd(); marked_text.Advance()) {
263     if (!PossiblyHasMarkers(marker_types))
264       return;
265     DCHECK(!markers_.IsEmpty());
266     const Node& node = marked_text.CurrentContainer();
267     auto* text_node = DynamicTo<Text>(node);
268     if (!text_node)
269       continue;
270     int start_offset = marked_text.StartOffsetInCurrentContainer();
271     int end_offset = marked_text.EndOffsetInCurrentContainer();
272     RemoveMarkersInternal(*text_node, start_offset, end_offset - start_offset,
273                           marker_types);
274   }
275 }
276 
RemoveMarkersInRange(const EphemeralRange & range,DocumentMarker::MarkerTypes marker_types)277 void DocumentMarkerController::RemoveMarkersInRange(
278     const EphemeralRange& range,
279     DocumentMarker::MarkerTypes marker_types) {
280   DCHECK(!document_->NeedsLayoutTreeUpdate());
281 
282   TextIterator marked_text(range.StartPosition(), range.EndPosition());
283   DocumentMarkerController::RemoveMarkers(marked_text, marker_types);
284 }
285 
AddMarkerInternal(const EphemeralRange & range,std::function<DocumentMarker * (int,int)> create_marker_from_offsets,const TextIteratorBehavior & iterator_behavior)286 void DocumentMarkerController::AddMarkerInternal(
287     const EphemeralRange& range,
288     std::function<DocumentMarker*(int, int)> create_marker_from_offsets,
289     const TextIteratorBehavior& iterator_behavior) {
290   for (TextIterator marked_text(range.StartPosition(), range.EndPosition(),
291                                 iterator_behavior);
292        !marked_text.AtEnd(); marked_text.Advance()) {
293     const int start_offset_in_current_container =
294         marked_text.StartOffsetInCurrentContainer();
295     const int end_offset_in_current_container =
296         marked_text.EndOffsetInCurrentContainer();
297 
298     DCHECK_GE(end_offset_in_current_container,
299               start_offset_in_current_container);
300 
301     // TODO(editing-dev): TextIterator sometimes emits ranges where the start
302     // and end offsets are the same. Investigate if TextIterator should be
303     // changed to not do this. See crbug.com/727929
304     if (end_offset_in_current_container == start_offset_in_current_container)
305       continue;
306 
307     // Ignore text emitted by TextIterator for non-text nodes (e.g. implicit
308     // newlines)
309     const auto* text_node = DynamicTo<Text>(marked_text.CurrentContainer());
310     if (!text_node)
311       continue;
312 
313     DocumentMarker* const new_marker = create_marker_from_offsets(
314         start_offset_in_current_container, end_offset_in_current_container);
315     AddMarkerToNode(*text_node, new_marker);
316   }
317 }
318 
AddMarkerToNode(const Text & text,DocumentMarker * new_marker)319 void DocumentMarkerController::AddMarkerToNode(const Text& text,
320                                                DocumentMarker* new_marker) {
321   DCHECK_GE(text.length(), new_marker->EndOffset());
322   possibly_existing_marker_types_ = possibly_existing_marker_types_.Add(
323       DocumentMarker::MarkerTypes(new_marker->GetType()));
324   SetDocument(document_);
325 
326   Member<MarkerLists>& markers =
327       markers_.insert(&text, nullptr).stored_value->value;
328   if (!markers) {
329     markers = MakeGarbageCollected<MarkerLists>();
330     markers->Grow(DocumentMarker::kMarkerTypeIndexesCount);
331   }
332 
333   const DocumentMarker::MarkerType new_marker_type = new_marker->GetType();
334   if (!ListForType(markers, new_marker_type))
335     ListForType(markers, new_marker_type) = CreateListForType(new_marker_type);
336 
337   DocumentMarkerList* const list = ListForType(markers, new_marker_type);
338   list->Add(new_marker);
339 
340   InvalidatePaintForNode(text);
341 }
342 
343 // Moves markers from src_node to dst_node. Markers are moved if their start
344 // offset is less than length. Markers that run past that point are truncated.
MoveMarkers(const Text & src_node,int length,const Text & dst_node)345 void DocumentMarkerController::MoveMarkers(const Text& src_node,
346                                            int length,
347                                            const Text& dst_node) {
348   if (length <= 0)
349     return;
350 
351   if (!PossiblyHasMarkers(DocumentMarker::MarkerTypes::All()))
352     return;
353   DCHECK(!markers_.IsEmpty());
354 
355   MarkerLists* const src_markers = markers_.at(&src_node);
356   if (!src_markers)
357     return;
358 
359   auto& dst_marker_entry =
360       markers_.insert(&dst_node, nullptr).stored_value->value;
361   if (!dst_marker_entry) {
362     dst_marker_entry = MakeGarbageCollected<MarkerLists>(
363         DocumentMarker::kMarkerTypeIndexesCount);
364   }
365   MarkerLists* const dst_markers = dst_marker_entry;
366 
367   bool doc_dirty = false;
368   for (DocumentMarker::MarkerType type : DocumentMarker::MarkerTypes::All()) {
369     DocumentMarkerList* const src_list = ListForType(src_markers, type);
370     if (!src_list)
371       continue;
372 
373     if (!ListForType(dst_markers, type))
374       ListForType(dst_markers, type) = CreateListForType(type);
375 
376     DocumentMarkerList* const dst_list = ListForType(dst_markers, type);
377     if (src_list->MoveMarkers(length, dst_list))
378       doc_dirty = true;
379   }
380 
381   if (!doc_dirty)
382     return;
383 
384   InvalidatePaintForNode(dst_node);
385 }
386 
RemoveMarkersInternal(const Text & text,unsigned start_offset,int length,DocumentMarker::MarkerTypes marker_types)387 void DocumentMarkerController::RemoveMarkersInternal(
388     const Text& text,
389     unsigned start_offset,
390     int length,
391     DocumentMarker::MarkerTypes marker_types) {
392   if (length <= 0)
393     return;
394 
395   if (!PossiblyHasMarkers(marker_types))
396     return;
397   DCHECK(!(markers_.IsEmpty()));
398 
399   MarkerLists* const markers = markers_.at(&text);
400   if (!markers)
401     return;
402 
403   bool doc_dirty = false;
404   size_t empty_lists_count = 0;
405   for (DocumentMarker::MarkerType type : DocumentMarker::MarkerTypes::All()) {
406     DocumentMarkerList* const list = ListForType(markers, type);
407     if (!list || list->IsEmpty()) {
408       if (list && list->IsEmpty())
409         ListForType(markers, type) = nullptr;
410       ++empty_lists_count;
411       continue;
412     }
413     if (!marker_types.Contains(type))
414       continue;
415 
416     if (list->RemoveMarkers(start_offset, length))
417       doc_dirty = true;
418 
419     if (list->IsEmpty()) {
420       ListForType(markers, type) = nullptr;
421       ++empty_lists_count;
422     }
423   }
424 
425   if (empty_lists_count == DocumentMarker::kMarkerTypeIndexesCount) {
426     markers_.erase(&text);
427     if (markers_.IsEmpty()) {
428       possibly_existing_marker_types_ = DocumentMarker::MarkerTypes();
429       SetDocument(nullptr);
430     }
431   }
432 
433   if (!doc_dirty)
434     return;
435 
436   InvalidatePaintForNode(text);
437 }
438 
FirstMarkerAroundPosition(const PositionInFlatTree & position,DocumentMarker::MarkerTypes types)439 DocumentMarker* DocumentMarkerController::FirstMarkerAroundPosition(
440     const PositionInFlatTree& position,
441     DocumentMarker::MarkerTypes types) {
442   if (position.IsNull())
443     return nullptr;
444   const PositionInFlatTree& start = SearchAroundPositionStart(position);
445   const PositionInFlatTree& end = SearchAroundPositionEnd(position);
446 
447   if (start > end) {
448     // TODO(crbug/1114021): Investigate why this might happen.
449     NOTREACHED() << "|start| should be before |end|.";
450     return nullptr;
451   }
452 
453   const Node* const start_node = start.ComputeContainerNode();
454   const unsigned start_offset = start.ComputeOffsetInContainerNode();
455   const Node* const end_node = end.ComputeContainerNode();
456   const unsigned end_offset = end.ComputeOffsetInContainerNode();
457 
458   for (const Node& node : EphemeralRangeInFlatTree(start, end).Nodes()) {
459     auto* text_node = DynamicTo<Text>(node);
460     if (!text_node)
461       continue;
462 
463     const unsigned start_range_offset = node == start_node ? start_offset : 0;
464     const unsigned end_range_offset =
465         node == end_node ? end_offset : text_node->length();
466 
467     DocumentMarker* const found_marker = FirstMarkerIntersectingOffsetRange(
468         *text_node, start_range_offset, end_range_offset, types);
469     if (found_marker)
470       return found_marker;
471   }
472 
473   return nullptr;
474 }
475 
FirstMarkerIntersectingEphemeralRange(const EphemeralRange & range,DocumentMarker::MarkerTypes types)476 DocumentMarker* DocumentMarkerController::FirstMarkerIntersectingEphemeralRange(
477     const EphemeralRange& range,
478     DocumentMarker::MarkerTypes types) {
479   if (range.IsNull())
480     return nullptr;
481 
482   if (range.IsCollapsed()) {
483     return FirstMarkerAroundPosition(
484         ToPositionInFlatTree(range.StartPosition()), types);
485   }
486 
487   const Node* const start_container =
488       range.StartPosition().ComputeContainerNode();
489   const Node* const end_container = range.EndPosition().ComputeContainerNode();
490 
491   // We don't currently support the case where a marker spans multiple nodes.
492   // See crbug.com/720065
493   if (start_container != end_container)
494     return nullptr;
495 
496   auto* text_node = DynamicTo<Text>(start_container);
497   if (!text_node)
498     return nullptr;
499 
500   const unsigned start_offset =
501       range.StartPosition().ComputeOffsetInContainerNode();
502   const unsigned end_offset =
503       range.EndPosition().ComputeOffsetInContainerNode();
504 
505   return FirstMarkerIntersectingOffsetRange(*text_node, start_offset,
506                                             end_offset, types);
507 }
508 
FirstMarkerIntersectingOffsetRange(const Text & node,unsigned start_offset,unsigned end_offset,DocumentMarker::MarkerTypes types)509 DocumentMarker* DocumentMarkerController::FirstMarkerIntersectingOffsetRange(
510     const Text& node,
511     unsigned start_offset,
512     unsigned end_offset,
513     DocumentMarker::MarkerTypes types) {
514   if (!PossiblyHasMarkers(types))
515     return nullptr;
516 
517   // Minor optimization: if we have an empty range at a node boundary, it
518   // doesn't fall in the interior of any marker.
519   if (start_offset == 0 && end_offset == 0)
520     return nullptr;
521   const unsigned node_length = node.length();
522   if (start_offset == node_length && end_offset == node_length)
523     return nullptr;
524 
525   MarkerLists* const markers = markers_.at(&node);
526   if (!markers)
527     return nullptr;
528 
529   for (DocumentMarker::MarkerType type : types) {
530     const DocumentMarkerList* const list = ListForType(markers, type);
531     if (!list)
532       continue;
533 
534     DocumentMarker* found_marker =
535         list->FirstMarkerIntersectingRange(start_offset, end_offset);
536     if (found_marker)
537       return found_marker;
538   }
539 
540   return nullptr;
541 }
542 
543 HeapVector<std::pair<Member<const Text>, Member<DocumentMarker>>>
MarkersAroundPosition(const PositionInFlatTree & position,DocumentMarker::MarkerTypes types)544 DocumentMarkerController::MarkersAroundPosition(
545     const PositionInFlatTree& position,
546     DocumentMarker::MarkerTypes types) {
547   HeapVector<std::pair<Member<const Text>, Member<DocumentMarker>>>
548       node_marker_pairs;
549 
550   if (position.IsNull())
551     return node_marker_pairs;
552 
553   if (!PossiblyHasMarkers(types))
554     return node_marker_pairs;
555 
556   const PositionInFlatTree& start = SearchAroundPositionStart(position);
557   const PositionInFlatTree& end = SearchAroundPositionEnd(position);
558 
559   if (start > end) {
560     // TODO(crbug/1114021): Investigate why this might happen.
561     NOTREACHED() << "|start| should be before |end|.";
562     return node_marker_pairs;
563   }
564 
565   const Node* const start_node = start.ComputeContainerNode();
566   const unsigned start_offset = start.ComputeOffsetInContainerNode();
567   const Node* const end_node = end.ComputeContainerNode();
568   const unsigned end_offset = end.ComputeOffsetInContainerNode();
569 
570   for (const Node& node : EphemeralRangeInFlatTree(start, end).Nodes()) {
571     auto* text_node = DynamicTo<Text>(node);
572     if (!text_node)
573       continue;
574 
575     MarkerLists* const marker_lists = markers_.at(text_node);
576     if (!marker_lists)
577       continue;
578 
579     const unsigned start_range_offset = node == start_node ? start_offset : 0;
580     const unsigned end_range_offset =
581         node == end_node ? end_offset : text_node->length();
582 
583     // Minor optimization: if we have an empty range at a node boundary, it
584     // doesn't fall in the interior of any marker.
585     if (start_range_offset == 0 && end_range_offset == 0)
586       continue;
587     const unsigned node_length = To<CharacterData>(node).length();
588     if (start_range_offset == node_length && end_range_offset == node_length)
589       continue;
590 
591     for (DocumentMarker::MarkerType type : types) {
592       const DocumentMarkerList* const list = ListForType(marker_lists, type);
593       if (!list)
594         continue;
595 
596       const DocumentMarkerVector& marker_vector =
597           list->MarkersIntersectingRange(start_range_offset, end_range_offset);
598 
599       for (DocumentMarker* marker : marker_vector)
600         node_marker_pairs.push_back(std::make_pair(&To<Text>(node), marker));
601     }
602   }
603   return node_marker_pairs;
604 }
605 
606 HeapVector<std::pair<Member<const Text>, Member<DocumentMarker>>>
MarkersIntersectingRange(const EphemeralRangeInFlatTree & range,DocumentMarker::MarkerTypes types)607 DocumentMarkerController::MarkersIntersectingRange(
608     const EphemeralRangeInFlatTree& range,
609     DocumentMarker::MarkerTypes types) {
610   HeapVector<std::pair<Member<const Text>, Member<DocumentMarker>>>
611       node_marker_pairs;
612   if (!PossiblyHasMarkers(types))
613     return node_marker_pairs;
614 
615   const Node* const range_start_container =
616       range.StartPosition().ComputeContainerNode();
617   const unsigned range_start_offset =
618       range.StartPosition().ComputeOffsetInContainerNode();
619   const Node* const range_end_container =
620       range.EndPosition().ComputeContainerNode();
621   const unsigned range_end_offset =
622       range.EndPosition().ComputeOffsetInContainerNode();
623 
624   for (Node& node : range.Nodes()) {
625     auto* text_node = DynamicTo<Text>(node);
626     if (!text_node)
627       continue;
628     MarkerLists* const markers = markers_.at(text_node);
629     if (!markers)
630       continue;
631 
632     for (DocumentMarker::MarkerType type : types) {
633       const DocumentMarkerList* const list = ListForType(markers, type);
634       if (!list)
635         continue;
636 
637       const unsigned start_offset =
638           node == range_start_container ? range_start_offset : 0;
639       const unsigned max_character_offset = To<CharacterData>(node).length();
640       const unsigned end_offset =
641           node == range_end_container ? range_end_offset : max_character_offset;
642 
643       // Minor optimization: if we have an empty offset range at the boundary
644       // of a text node, it doesn't fall into the interior of any marker.
645       if (start_offset == 0 && end_offset == 0)
646         continue;
647       if (start_offset == max_character_offset && end_offset == 0)
648         continue;
649 
650       const DocumentMarkerVector& markers_from_this_list =
651           list->MarkersIntersectingRange(start_offset, end_offset);
652       for (DocumentMarker* marker : markers_from_this_list)
653         node_marker_pairs.push_back(std::make_pair(&To<Text>(node), marker));
654     }
655   }
656 
657   return node_marker_pairs;
658 }
659 
MarkersFor(const Text & text,DocumentMarker::MarkerTypes marker_types) const660 DocumentMarkerVector DocumentMarkerController::MarkersFor(
661     const Text& text,
662     DocumentMarker::MarkerTypes marker_types) const {
663   DocumentMarkerVector result;
664   if (!PossiblyHasMarkers(marker_types))
665     return result;
666 
667   MarkerLists* markers = markers_.at(&text);
668   if (!markers)
669     return result;
670 
671   for (DocumentMarker::MarkerType type : marker_types) {
672     DocumentMarkerList* const list = ListForType(markers, type);
673     if (!list || list->IsEmpty())
674       continue;
675 
676     result.AppendVector(list->GetMarkers());
677   }
678 
679   std::sort(result.begin(), result.end(),
680             [](const Member<DocumentMarker>& marker1,
681                const Member<DocumentMarker>& marker2) {
682               return marker1->StartOffset() < marker2->StartOffset();
683             });
684   return result;
685 }
686 
Markers() const687 DocumentMarkerVector DocumentMarkerController::Markers() const {
688   DocumentMarkerVector result;
689   for (const auto& node_markers : markers_) {
690     MarkerLists* markers = node_markers.value;
691     for (DocumentMarker::MarkerType type : DocumentMarker::MarkerTypes::All()) {
692       DocumentMarkerList* const list = ListForType(markers, type);
693       if (!list)
694         continue;
695       result.AppendVector(list->GetMarkers());
696     }
697   }
698   std::sort(result.begin(), result.end(),
699             [](const Member<DocumentMarker>& marker1,
700                const Member<DocumentMarker>& marker2) {
701               return marker1->StartOffset() < marker2->StartOffset();
702             });
703   return result;
704 }
705 
ComputeMarkersToPaint(const Text & text) const706 DocumentMarkerVector DocumentMarkerController::ComputeMarkersToPaint(
707     const Text& text) const {
708   // We don't render composition or spelling markers that overlap suggestion
709   // markers.
710   // Note: DocumentMarkerController::MarkersFor() returns markers sorted by
711   // start offset.
712   const DocumentMarkerVector& suggestion_markers =
713       MarkersFor(text, DocumentMarker::MarkerTypes::Suggestion());
714   if (suggestion_markers.IsEmpty()) {
715     // If there are no suggestion markers, we can return early as a minor
716     // performance optimization.
717     return MarkersFor(
718         text, DocumentMarker::MarkerTypes::AllBut(
719                   DocumentMarker::MarkerTypes(DocumentMarker::kSuggestion)));
720   }
721 
722   const DocumentMarkerVector& markers_overridden_by_suggestion_markers =
723       MarkersFor(text,
724                  DocumentMarker::MarkerTypes(DocumentMarker::kComposition |
725                                              DocumentMarker::kSpelling));
726 
727   Vector<unsigned> suggestion_starts;
728   Vector<unsigned> suggestion_ends;
729   for (const DocumentMarker* suggestion_marker : suggestion_markers) {
730     suggestion_starts.push_back(suggestion_marker->StartOffset());
731     suggestion_ends.push_back(suggestion_marker->EndOffset());
732   }
733 
734   std::sort(suggestion_starts.begin(), suggestion_starts.end());
735   std::sort(suggestion_ends.begin(), suggestion_ends.end());
736 
737   unsigned suggestion_starts_index = 0;
738   unsigned suggestion_ends_index = 0;
739   unsigned number_suggestions_currently_inside = 0;
740 
741   DocumentMarkerVector markers_to_paint;
742   for (DocumentMarker* marker : markers_overridden_by_suggestion_markers) {
743     while (suggestion_starts_index < suggestion_starts.size() &&
744            suggestion_starts[suggestion_starts_index] <=
745                marker->StartOffset()) {
746       ++suggestion_starts_index;
747       ++number_suggestions_currently_inside;
748     }
749     while (suggestion_ends_index < suggestion_ends.size() &&
750            suggestion_ends[suggestion_ends_index] <= marker->StartOffset()) {
751       ++suggestion_ends_index;
752       --number_suggestions_currently_inside;
753     }
754 
755     // At this point, number_suggestions_currently_inside should be equal to the
756     // number of suggestion markers overlapping the point marker->StartOffset()
757     // (marker endpoints don't count as overlapping).
758 
759     // Marker is overlapped by a suggestion marker, do not paint.
760     if (number_suggestions_currently_inside)
761       continue;
762 
763     // Verify that no suggestion marker starts before the current marker ends.
764     if (suggestion_starts_index < suggestion_starts.size() &&
765         suggestion_starts[suggestion_starts_index] < marker->EndOffset())
766       continue;
767 
768     markers_to_paint.push_back(marker);
769   }
770 
771   markers_to_paint.AppendVector(suggestion_markers);
772 
773   markers_to_paint.AppendVector(MarkersFor(
774       text, DocumentMarker::MarkerTypes::AllBut(DocumentMarker::MarkerTypes(
775                 DocumentMarker::kComposition | DocumentMarker::kSpelling |
776                 DocumentMarker::kSuggestion))));
777 
778   return markers_to_paint;
779 }
780 
PossiblyHasTextMatchMarkers() const781 bool DocumentMarkerController::PossiblyHasTextMatchMarkers() const {
782   return PossiblyHasMarkers(DocumentMarker::kTextMatch);
783 }
784 
LayoutRectsForTextMatchMarkers()785 Vector<IntRect> DocumentMarkerController::LayoutRectsForTextMatchMarkers() {
786   DCHECK(!document_->View()->NeedsLayout());
787   DCHECK(!document_->NeedsLayoutTreeUpdate());
788 
789   Vector<IntRect> result;
790 
791   if (!PossiblyHasMarkers(DocumentMarker::kTextMatch))
792     return result;
793   DCHECK(!(markers_.IsEmpty()));
794 
795   // outer loop: process each node
796   MarkerMap::iterator end = markers_.end();
797   for (MarkerMap::iterator node_iterator = markers_.begin();
798        node_iterator != end; ++node_iterator) {
799     // inner loop; process each marker in this node
800     const Node& node = *node_iterator->key;
801     if (!node.isConnected())
802       continue;
803     MarkerLists* markers = node_iterator->value.Get();
804     DocumentMarkerList* const list =
805         ListForType(markers, DocumentMarker::kTextMatch);
806     if (!list)
807       continue;
808     result.AppendVector(To<TextMatchMarkerListImpl>(list)->LayoutRects(node));
809   }
810 
811   return result;
812 }
813 
InvalidatePaintForTickmarks(const Node & node)814 static void InvalidatePaintForTickmarks(const Node& node) {
815   if (LayoutView* layout_view = node.GetDocument().GetLayoutView())
816     layout_view->InvalidatePaintForTickmarks();
817 }
818 
InvalidateRectsForTextMatchMarkersInNode(const Text & node)819 void DocumentMarkerController::InvalidateRectsForTextMatchMarkersInNode(
820     const Text& node) {
821   MarkerLists* markers = markers_.at(&node);
822 
823   const DocumentMarkerList* const marker_list =
824       ListForType(markers, DocumentMarker::kTextMatch);
825   if (!marker_list || marker_list->IsEmpty())
826     return;
827 
828   const HeapVector<Member<DocumentMarker>>& markers_in_list =
829       marker_list->GetMarkers();
830   for (auto& marker : markers_in_list)
831     To<TextMatchMarker>(marker.Get())->Invalidate();
832 
833   InvalidatePaintForTickmarks(node);
834 }
835 
InvalidateRectsForAllTextMatchMarkers()836 void DocumentMarkerController::InvalidateRectsForAllTextMatchMarkers() {
837   for (auto& node_markers : markers_) {
838     const Text& node = *node_markers.key;
839     InvalidateRectsForTextMatchMarkersInNode(node);
840   }
841 }
842 
DidProcessMarkerMap(const LivenessBroker &)843 void DocumentMarkerController::DidProcessMarkerMap(const LivenessBroker&) {
844   if (markers_.IsEmpty())
845     Clear();
846 }
847 
Trace(Visitor * visitor) const848 void DocumentMarkerController::Trace(Visitor* visitor) const {
849   // Note: To make |DidProcessMarkerMap()| called after weak members callback
850   // of |markers_|, we should register it before tracing |markers_|.
851   visitor->template RegisterWeakCallbackMethod<
852       DocumentMarkerController, &DocumentMarkerController::DidProcessMarkerMap>(
853       this);
854   visitor->Trace(markers_);
855   visitor->Trace(document_);
856   SynchronousMutationObserver::Trace(visitor);
857 }
858 
RemoveMarkersForNode(const Text & text,DocumentMarker::MarkerTypes marker_types)859 void DocumentMarkerController::RemoveMarkersForNode(
860     const Text& text,
861     DocumentMarker::MarkerTypes marker_types) {
862   if (!PossiblyHasMarkers(marker_types))
863     return;
864   DCHECK(!markers_.IsEmpty());
865 
866   MarkerMap::iterator iterator = markers_.find(&text);
867   if (iterator != markers_.end())
868     RemoveMarkersFromList(iterator, marker_types);
869 }
870 
RemoveSpellingMarkersUnderWords(const Vector<String> & words)871 void DocumentMarkerController::RemoveSpellingMarkersUnderWords(
872     const Vector<String>& words) {
873   for (auto& node_markers : markers_) {
874     const Text& text = *node_markers.key;
875     MarkerLists* markers = node_markers.value;
876     for (DocumentMarker::MarkerType type :
877          DocumentMarker::MarkerTypes::Misspelling()) {
878       DocumentMarkerList* const list = ListForType(markers, type);
879       if (!list)
880         continue;
881       if (To<SpellCheckMarkerListImpl>(list)->RemoveMarkersUnderWords(
882               text.data(), words)) {
883         InvalidatePaintForNode(text);
884       }
885     }
886   }
887 }
888 
RemoveSuggestionMarkerInRangeOnFinish(const EphemeralRangeInFlatTree & range)889 void DocumentMarkerController::RemoveSuggestionMarkerInRangeOnFinish(
890     const EphemeralRangeInFlatTree& range) {
891   // MarkersIntersectingRange() might be expensive. In practice, we hope we will
892   // only check one node for composing range.
893   const HeapVector<std::pair<Member<const Text>, Member<DocumentMarker>>>&
894       node_marker_pairs = MarkersIntersectingRange(
895           range, DocumentMarker::MarkerTypes::Suggestion());
896   for (const auto& node_marker_pair : node_marker_pairs) {
897     auto* suggestion_marker =
898         To<SuggestionMarker>(node_marker_pair.second.Get());
899     if (suggestion_marker->NeedsRemovalOnFinishComposing()) {
900       const Text& text = *node_marker_pair.first;
901       DocumentMarkerList* const list =
902           ListForType(markers_.at(&text), DocumentMarker::kSuggestion);
903       // RemoveMarkerByTag() might be expensive. In practice, we have at most
904       // one suggestion marker needs to be removed.
905       To<SuggestionMarkerListImpl>(list)->RemoveMarkerByTag(
906           suggestion_marker->Tag());
907       InvalidatePaintForNode(text);
908     }
909   }
910 }
911 
RemoveSuggestionMarkerByType(const EphemeralRangeInFlatTree & range,const SuggestionMarker::SuggestionType & type)912 void DocumentMarkerController::RemoveSuggestionMarkerByType(
913     const EphemeralRangeInFlatTree& range,
914     const SuggestionMarker::SuggestionType& type) {
915   // MarkersIntersectingRange() might be expensive. In practice, we hope we will
916   // only check one node for the range.
917   const HeapVector<std::pair<Member<const Text>, Member<DocumentMarker>>>&
918       node_marker_pairs = MarkersIntersectingRange(
919           range, DocumentMarker::MarkerTypes::Suggestion());
920   for (const auto& node_marker_pair : node_marker_pairs) {
921     const Text& text = *node_marker_pair.first;
922     DocumentMarkerList* const list =
923         ListForType(markers_.at(&text), DocumentMarker::kSuggestion);
924     // RemoveMarkerByType() might be expensive. In practice, we have at most
925     // one suggestion marker needs to be removed.
926     To<SuggestionMarkerListImpl>(list)->RemoveMarkerByType(type);
927     InvalidatePaintForNode(text);
928   }
929 }
930 
RemoveSuggestionMarkerByType(const SuggestionMarker::SuggestionType & type)931 void DocumentMarkerController::RemoveSuggestionMarkerByType(
932     const SuggestionMarker::SuggestionType& type) {
933   if (!PossiblyHasMarkers(DocumentMarker::kSuggestion))
934     return;
935   DCHECK(!markers_.IsEmpty());
936 
937   for (const auto& node_markers : markers_) {
938     MarkerLists* markers = node_markers.value;
939     DocumentMarkerList* const list =
940         ListForType(markers, DocumentMarker::kSuggestion);
941     if (!list)
942       continue;
943     if (To<SuggestionMarkerListImpl>(list)->RemoveMarkerByType(type)) {
944       InvalidatePaintForNode(*node_markers.key);
945       return;
946     }
947   }
948 }
949 
RemoveSuggestionMarkerByTag(const Text & text,int32_t marker_tag)950 void DocumentMarkerController::RemoveSuggestionMarkerByTag(const Text& text,
951                                                            int32_t marker_tag) {
952   MarkerLists* markers = markers_.at(&text);
953   auto* const list = To<SuggestionMarkerListImpl>(
954       ListForType(markers, DocumentMarker::kSuggestion).Get());
955   if (!list->RemoveMarkerByTag(marker_tag))
956     return;
957   InvalidatePaintForNode(text);
958 }
959 
RemoveMarkersOfTypes(DocumentMarker::MarkerTypes marker_types)960 void DocumentMarkerController::RemoveMarkersOfTypes(
961     DocumentMarker::MarkerTypes marker_types) {
962   if (!PossiblyHasMarkers(marker_types))
963     return;
964   DCHECK(!markers_.IsEmpty());
965 
966   HeapVector<Member<const Text>> nodes_with_markers;
967   CopyKeysToVector(markers_, nodes_with_markers);
968   unsigned size = nodes_with_markers.size();
969   for (unsigned i = 0; i < size; ++i) {
970     MarkerMap::iterator iterator = markers_.find(nodes_with_markers[i]);
971     if (iterator != markers_.end())
972       RemoveMarkersFromList(iterator, marker_types);
973   }
974 
975   if (PossiblyHasMarkers(DocumentMarker::MarkerTypes::AllBut(marker_types)))
976     return;
977   SetDocument(nullptr);
978 }
979 
RemoveMarkersFromList(MarkerMap::iterator iterator,DocumentMarker::MarkerTypes marker_types)980 void DocumentMarkerController::RemoveMarkersFromList(
981     MarkerMap::iterator iterator,
982     DocumentMarker::MarkerTypes marker_types) {
983   bool needs_repainting = false;
984   bool node_can_be_removed;
985 
986   size_t empty_lists_count = 0;
987   if (marker_types == DocumentMarker::MarkerTypes::All()) {
988     needs_repainting = true;
989     node_can_be_removed = true;
990   } else {
991     MarkerLists* markers = iterator->value.Get();
992 
993     for (DocumentMarker::MarkerType type : DocumentMarker::MarkerTypes::All()) {
994       DocumentMarkerList* const list = ListForType(markers, type);
995       if (!list || list->IsEmpty()) {
996         if (list && list->IsEmpty())
997           ListForType(markers, type) = nullptr;
998         ++empty_lists_count;
999         continue;
1000       }
1001       if (marker_types.Contains(type)) {
1002         list->Clear();
1003         ListForType(markers, type) = nullptr;
1004         ++empty_lists_count;
1005         needs_repainting = true;
1006       }
1007     }
1008 
1009     node_can_be_removed =
1010         empty_lists_count == DocumentMarker::kMarkerTypeIndexesCount;
1011   }
1012 
1013   if (needs_repainting) {
1014     const Text& node = *iterator->key;
1015     InvalidatePaintForNode(node);
1016     InvalidatePaintForTickmarks(node);
1017   }
1018 
1019   if (node_can_be_removed) {
1020     markers_.erase(iterator);
1021     if (markers_.IsEmpty()) {
1022       possibly_existing_marker_types_ = DocumentMarker::MarkerTypes();
1023       SetDocument(nullptr);
1024     }
1025   }
1026 }
1027 
RepaintMarkers(DocumentMarker::MarkerTypes marker_types)1028 void DocumentMarkerController::RepaintMarkers(
1029     DocumentMarker::MarkerTypes marker_types) {
1030   if (!PossiblyHasMarkers(marker_types))
1031     return;
1032   DCHECK(!markers_.IsEmpty());
1033 
1034   // outer loop: process each markered Text in the document
1035   for (auto& iterator : markers_) {
1036     // inner loop: process each marker in the current Text
1037     MarkerLists* markers = iterator.value.Get();
1038     for (DocumentMarker::MarkerType type : DocumentMarker::MarkerTypes::All()) {
1039       DocumentMarkerList* const list = ListForType(markers, type);
1040       if (!list || list->IsEmpty() || !marker_types.Contains(type))
1041         continue;
1042 
1043       InvalidatePaintForNode(*iterator.key);
1044     }
1045   }
1046 }
1047 
SetTextMatchMarkersActive(const EphemeralRange & range,bool active)1048 bool DocumentMarkerController::SetTextMatchMarkersActive(
1049     const EphemeralRange& range,
1050     bool active) {
1051   if (!PossiblyHasMarkers(DocumentMarker::kTextMatch))
1052     return false;
1053 
1054   DCHECK(!markers_.IsEmpty());
1055 
1056   const Node* const start_container =
1057       range.StartPosition().ComputeContainerNode();
1058   DCHECK(start_container);
1059   const Node* const end_container = range.EndPosition().ComputeContainerNode();
1060   DCHECK(end_container);
1061 
1062   const unsigned container_start_offset =
1063       range.StartPosition().ComputeOffsetInContainerNode();
1064   const unsigned container_end_offset =
1065       range.EndPosition().ComputeOffsetInContainerNode();
1066 
1067   bool marker_found = false;
1068   for (Node& node : range.Nodes()) {
1069     auto* text_node = DynamicTo<Text>(node);
1070     if (!text_node)
1071       continue;
1072     int start_offset = node == start_container ? container_start_offset : 0;
1073     int end_offset = node == end_container ? container_end_offset : INT_MAX;
1074     marker_found |=
1075         SetTextMatchMarkersActive(*text_node, start_offset, end_offset, active);
1076   }
1077   return marker_found;
1078 }
1079 
SetTextMatchMarkersActive(const Text & text,unsigned start_offset,unsigned end_offset,bool active)1080 bool DocumentMarkerController::SetTextMatchMarkersActive(const Text& text,
1081                                                          unsigned start_offset,
1082                                                          unsigned end_offset,
1083                                                          bool active) {
1084   MarkerLists* markers = markers_.at(&text);
1085   if (!markers)
1086     return false;
1087 
1088   DocumentMarkerList* const list =
1089       ListForType(markers, DocumentMarker::kTextMatch);
1090   if (!list)
1091     return false;
1092 
1093   bool doc_dirty = To<TextMatchMarkerListImpl>(list)->SetTextMatchMarkersActive(
1094       start_offset, end_offset, active);
1095 
1096   if (!doc_dirty)
1097     return false;
1098   InvalidatePaintForNode(text);
1099   return true;
1100 }
1101 
1102 #if DCHECK_IS_ON()
ShowMarkers() const1103 void DocumentMarkerController::ShowMarkers() const {
1104   StringBuilder builder;
1105   for (auto& node_iterator : markers_) {
1106     const Text* node = node_iterator.key;
1107     builder.AppendFormat("%p", node);
1108     MarkerLists* markers = markers_.at(node);
1109     for (DocumentMarker::MarkerType type : DocumentMarker::MarkerTypes::All()) {
1110       DocumentMarkerList* const list = ListForType(markers, type);
1111       if (!list)
1112         continue;
1113 
1114       const HeapVector<Member<DocumentMarker>>& markers_in_list =
1115           list->GetMarkers();
1116       for (const DocumentMarker* marker : markers_in_list) {
1117         bool is_active_match = false;
1118         if (auto* text_match = DynamicTo<TextMatchMarker>(marker))
1119           is_active_match = text_match->IsActiveMatch();
1120 
1121         builder.AppendFormat(
1122             " %u:[%u:%u](%d)", static_cast<uint32_t>(marker->GetType()),
1123             marker->StartOffset(), marker->EndOffset(), is_active_match);
1124       }
1125     }
1126     builder.Append("\n");
1127   }
1128   LOG(INFO) << markers_.size() << " nodes have markers:\n"
1129             << builder.ToString().Utf8();
1130 }
1131 #endif
1132 
1133 // SynchronousMutationObserver
DidUpdateCharacterData(CharacterData * node,unsigned offset,unsigned old_length,unsigned new_length)1134 void DocumentMarkerController::DidUpdateCharacterData(CharacterData* node,
1135                                                       unsigned offset,
1136                                                       unsigned old_length,
1137                                                       unsigned new_length) {
1138   if (!PossiblyHasMarkers(DocumentMarker::MarkerTypes::All()))
1139     return;
1140   DCHECK(!markers_.IsEmpty());
1141   auto* text_node = DynamicTo<Text>(node);
1142   if (!text_node)
1143     return;
1144   MarkerLists* markers = markers_.at(text_node);
1145   if (!markers)
1146     return;
1147 
1148   bool did_shift_marker = false;
1149   for (DocumentMarkerList* const list : *markers) {
1150     if (!list)
1151       continue;
1152 
1153     if (list->ShiftMarkers(node->data(), offset, old_length, new_length))
1154       did_shift_marker = true;
1155   }
1156 
1157   if (!did_shift_marker)
1158     return;
1159   if (!node->GetLayoutObject())
1160     return;
1161   InvalidateRectsForTextMatchMarkersInNode(*text_node);
1162   InvalidatePaintForNode(*node);
1163 }
1164 
1165 }  // namespace blink
1166 
1167 #if DCHECK_IS_ON()
showDocumentMarkers(const blink::DocumentMarkerController * controller)1168 void showDocumentMarkers(const blink::DocumentMarkerController* controller) {
1169   if (controller)
1170     controller->ShowMarkers();
1171 }
1172 #endif
1173