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