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 reserved.
7 * Copyright (C) 2008, 2009 Torch Mobile Inc. All rights reserved. (http://www.torchmobile.com/)
8 * Copyright (C) Research In Motion Limited 2010. All rights reserved.
9 *
10 * This library is free software; you can redistribute it and/or
11 * modify it under the terms of the GNU Library General Public
12 * License as published by the Free Software Foundation; either
13 * version 2 of the License, or (at your option) any later version.
14 *
15 * This library is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
18 * Library General Public License for more details.
19 *
20 * You should have received a copy of the GNU Library General Public License
21 * along with this library; see the file COPYING.LIB. If not, write to
22 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
23 * Boston, MA 02110-1301, USA.
24 *
25 */
26
27#include "config.h"
28#include "DocumentMarkerController.h"
29
30#include "Chrome.h"
31#include "ChromeClient.h"
32#include "Frame.h"
33#include "NodeTraversal.h"
34#include "Page.h"
35#include "Range.h"
36#include "RenderBlockFlow.h"
37#include "RenderLayer.h"
38#include "RenderText.h"
39#include "RenderedDocumentMarker.h"
40#include "TextIterator.h"
41#include <stdio.h>
42
43namespace WebCore {
44
45inline bool DocumentMarkerController::possiblyHasMarkers(OptionSet<DocumentMarker::MarkerType> types)
46{
47 return m_possiblyExistingMarkerTypes.containsAny(types);
48}
49
50DocumentMarkerController::DocumentMarkerController(Document& document)
51 : m_document(document)
52{
53}
54
55DocumentMarkerController::~DocumentMarkerController() = default;
56
57void DocumentMarkerController::detach()
58{
59 m_markers.clear();
60 m_possiblyExistingMarkerTypes = { };
61}
62
63void DocumentMarkerController::addMarker(Range& range, DocumentMarker::MarkerType type, const String& description)
64{
65 for (TextIterator markedText(&range); !markedText.atEnd(); markedText.advance()) {
66 RefPtr<Range> textPiece = markedText.range();
67 addMarker(textPiece->startContainer(), DocumentMarker(type, textPiece->startOffset(), textPiece->endOffset(), description));
68 }
69}
70
71void DocumentMarkerController::addMarker(Range& range, DocumentMarker::MarkerType type)
72{
73 for (TextIterator markedText(&range); !markedText.atEnd(); markedText.advance()) {
74 RefPtr<Range> textPiece = markedText.range();
75 addMarker(textPiece->startContainer(), DocumentMarker(type, textPiece->startOffset(), textPiece->endOffset()));
76 }
77
78}
79
80void DocumentMarkerController::addMarkerToNode(Node& node, unsigned startOffset, unsigned length, DocumentMarker::MarkerType type)
81{
82 addMarker(node, DocumentMarker(type, startOffset, startOffset + length));
83}
84
85void DocumentMarkerController::addMarkerToNode(Node& node, unsigned startOffset, unsigned length, DocumentMarker::MarkerType type, DocumentMarker::Data&& data)
86{
87 addMarker(node, DocumentMarker(type, startOffset, startOffset + length, WTFMove(data)));
88}
89
90void DocumentMarkerController::addTextMatchMarker(const Range& range, bool activeMatch)
91{
92 for (TextIterator markedText(&range); !markedText.atEnd(); markedText.advance()) {
93 RefPtr<Range> textPiece = markedText.range();
94 unsigned startOffset = textPiece->startOffset();
95 unsigned endOffset = textPiece->endOffset();
96 addMarker(textPiece->startContainer(), DocumentMarker(startOffset, endOffset, activeMatch));
97 }
98}
99
100#if PLATFORM(IOS_FAMILY)
101
102void DocumentMarkerController::addMarker(Range& range, DocumentMarker::MarkerType type, const String& description, const Vector<String>& interpretations, const RetainPtr<id>& metadata)
103{
104 for (TextIterator markedText(&range); !markedText.atEnd(); markedText.advance()) {
105 RefPtr<Range> textPiece = markedText.range();
106 addMarker(textPiece->startContainer(), DocumentMarker(type, textPiece->startOffset(), textPiece->endOffset(), description, interpretations, metadata));
107 }
108}
109
110void DocumentMarkerController::addDictationPhraseWithAlternativesMarker(Range& range, const Vector<String>& interpretations)
111{
112 ASSERT(interpretations.size() > 1);
113 if (interpretations.size() <= 1)
114 return;
115
116 size_t numberOfAlternatives = interpretations.size() - 1;
117 for (TextIterator markedText(&range); !markedText.atEnd(); markedText.advance()) {
118 RefPtr<Range> textPiece = markedText.range();
119 DocumentMarker marker(DocumentMarker::DictationPhraseWithAlternatives, textPiece->startOffset(), textPiece->endOffset(), emptyString(), Vector<String>(numberOfAlternatives), RetainPtr<id>());
120 for (size_t i = 0; i < numberOfAlternatives; ++i)
121 marker.setAlternative(interpretations[i + 1], i);
122 addMarker(textPiece->startContainer(), marker);
123 }
124}
125
126void DocumentMarkerController::addDictationResultMarker(Range& range, const RetainPtr<id>& metadata)
127{
128 for (TextIterator markedText(&range); !markedText.atEnd(); markedText.advance()) {
129 RefPtr<Range> textPiece = markedText.range();
130 addMarker(textPiece->startContainer(), DocumentMarker(DocumentMarker::DictationResult, textPiece->startOffset(), textPiece->endOffset(), String(), Vector<String>(), metadata));
131 }
132}
133
134#endif
135
136void DocumentMarkerController::addDraggedContentMarker(Range& range)
137{
138 for (TextIterator markedText(&range); !markedText.atEnd(); markedText.advance()) {
139 auto textPiece = markedText.range();
140 DocumentMarker::DraggedContentData draggedContentData { markedText.node() };
141 addMarker(textPiece->startContainer(), { DocumentMarker::DraggedContent, textPiece->startOffset(), textPiece->endOffset(), WTFMove(draggedContentData) });
142 }
143}
144
145#if ENABLE(PLATFORM_DRIVEN_TEXT_CHECKING)
146void DocumentMarkerController::addPlatformTextCheckingMarker(Range& range, const String& key, const String& value)
147{
148 for (TextIterator markedText(&range); !markedText.atEnd(); markedText.advance()) {
149 auto textPiece = markedText.range();
150 DocumentMarker::PlatformTextCheckingData textCheckingData { key, value };
151 addMarker(textPiece->startContainer(), { DocumentMarker::PlatformTextChecking, textPiece->startOffset(), textPiece->endOffset(), WTFMove(textCheckingData) });
152 }
153}
154#endif
155
156void DocumentMarkerController::removeMarkers(Range& range, OptionSet<DocumentMarker::MarkerType> markerTypes, RemovePartiallyOverlappingMarkerOrNot shouldRemovePartiallyOverlappingMarker)
157{
158 for (TextIterator markedText(&range); !markedText.atEnd(); markedText.advance()) {
159 if (!possiblyHasMarkers(markerTypes))
160 return;
161 ASSERT(!m_markers.isEmpty());
162
163 auto textPiece = markedText.range();
164 unsigned startOffset = textPiece->startOffset();
165 unsigned endOffset = textPiece->endOffset();
166 removeMarkers(textPiece->startContainer(), startOffset, endOffset - startOffset, markerTypes, nullptr, shouldRemovePartiallyOverlappingMarker);
167 }
168}
169
170void DocumentMarkerController::filterMarkers(Range& range, std::function<bool(DocumentMarker*)> filterFunction, OptionSet<DocumentMarker::MarkerType> markerTypes, RemovePartiallyOverlappingMarkerOrNot shouldRemovePartiallyOverlappingMarker)
171{
172 for (TextIterator markedText(&range); !markedText.atEnd(); markedText.advance()) {
173 if (!possiblyHasMarkers(markerTypes))
174 return;
175 ASSERT(!m_markers.isEmpty());
176
177 auto textPiece = markedText.range();
178 unsigned startOffset = textPiece->startOffset();
179 unsigned endOffset = textPiece->endOffset();
180 removeMarkers(textPiece->startContainer(), startOffset, endOffset - startOffset, markerTypes, filterFunction, shouldRemovePartiallyOverlappingMarker);
181 }
182}
183
184static void updateRenderedRectsForMarker(RenderedDocumentMarker& marker, Node& node)
185{
186 ASSERT(!node.document().view() || !node.document().view()->needsLayout());
187
188 // FIXME: We should refactor this so that we don't use Range (because we only have one Node), but still share code with absoluteTextQuads().
189 auto markerRange = Range::create(node.document(), &node, marker.startOffset(), &node, marker.endOffset());
190 Vector<FloatQuad> absoluteMarkerQuads;
191 markerRange->absoluteTextQuads(absoluteMarkerQuads, true);
192
193 Vector<FloatRect> absoluteMarkerRects;
194 absoluteMarkerRects.reserveInitialCapacity(absoluteMarkerQuads.size());
195 for (const auto& quad : absoluteMarkerQuads)
196 absoluteMarkerRects.uncheckedAppend(quad.boundingBox());
197
198 marker.setUnclippedAbsoluteRects(absoluteMarkerRects);
199}
200
201void DocumentMarkerController::invalidateRectsForAllMarkers()
202{
203 if (!hasMarkers())
204 return;
205
206 for (auto& markers : m_markers.values()) {
207 for (auto& marker : *markers)
208 marker.invalidate();
209 }
210
211 if (Page* page = m_document.page())
212 page->chrome().client().didInvalidateDocumentMarkerRects();
213}
214
215void DocumentMarkerController::invalidateRectsForMarkersInNode(Node& node)
216{
217 if (!hasMarkers())
218 return;
219
220 MarkerList* markers = m_markers.get(&node);
221 ASSERT(markers);
222
223 for (auto& marker : *markers)
224 marker.invalidate();
225
226 if (Page* page = m_document.page())
227 page->chrome().client().didInvalidateDocumentMarkerRects();
228}
229
230static void updateMainFrameLayoutIfNeeded(Document& document)
231{
232 Frame* frame = document.frame();
233 if (!frame)
234 return;
235
236 FrameView* mainFrameView = frame->mainFrame().view();
237 if (!mainFrameView)
238 return;
239
240 mainFrameView->updateLayoutAndStyleIfNeededRecursive();
241}
242
243void DocumentMarkerController::updateRectsForInvalidatedMarkersOfType(DocumentMarker::MarkerType markerType)
244{
245 if (!possiblyHasMarkers(markerType))
246 return;
247 ASSERT(!(m_markers.isEmpty()));
248
249 bool needsLayoutIfAnyRectsAreDirty = true;
250
251 for (auto& nodeAndMarkers : m_markers) {
252 Node& node = *nodeAndMarkers.key;
253 for (auto& marker : *nodeAndMarkers.value) {
254 if (marker.type() != markerType)
255 continue;
256
257 if (marker.isValid())
258 continue;
259
260 // We'll do up to one layout per call if we have any dirty markers.
261 if (needsLayoutIfAnyRectsAreDirty) {
262 updateMainFrameLayoutIfNeeded(m_document);
263 needsLayoutIfAnyRectsAreDirty = false;
264 }
265
266 updateRenderedRectsForMarker(marker, node);
267 }
268 }
269}
270
271Vector<FloatRect> DocumentMarkerController::renderedRectsForMarkers(DocumentMarker::MarkerType markerType)
272{
273 Vector<FloatRect> result;
274
275 if (!possiblyHasMarkers(markerType))
276 return result;
277 ASSERT(!(m_markers.isEmpty()));
278
279 RefPtr<Frame> frame = m_document.frame();
280 if (!frame)
281 return result;
282 FrameView* frameView = frame->view();
283 if (!frameView)
284 return result;
285
286 updateRectsForInvalidatedMarkersOfType(markerType);
287
288 bool isSubframe = !frame->isMainFrame();
289 IntRect subframeClipRect;
290 if (isSubframe)
291 subframeClipRect = frameView->windowToContents(frameView->windowClipRect());
292
293 for (auto& nodeAndMarkers : m_markers) {
294 Node& node = *nodeAndMarkers.key;
295 FloatRect overflowClipRect;
296 if (RenderObject* renderer = node.renderer())
297 overflowClipRect = renderer->absoluteClippedOverflowRect();
298 for (auto& marker : *nodeAndMarkers.value) {
299 if (marker.type() != markerType)
300 continue;
301
302 auto renderedRects = marker.unclippedAbsoluteRects();
303
304 // Clip document markers by their overflow clip.
305 if (node.renderer()) {
306 for (auto& rect : renderedRects)
307 rect.intersect(overflowClipRect);
308 }
309
310 // Clip subframe document markers by their frame.
311 if (isSubframe) {
312 for (auto& rect : renderedRects)
313 rect.intersect(subframeClipRect);
314 }
315
316 for (const auto& rect : renderedRects) {
317 if (!rect.isEmpty())
318 result.append(rect);
319 }
320 }
321 }
322
323 return result;
324}
325
326static bool shouldInsertAsSeparateMarker(const DocumentMarker& newMarker)
327{
328#if ENABLE(PLATFORM_DRIVEN_TEXT_CHECKING)
329 if (newMarker.type() == DocumentMarker::PlatformTextChecking)
330 return true;
331#endif
332
333#if PLATFORM(IOS_FAMILY)
334 if (newMarker.type() == DocumentMarker::DictationPhraseWithAlternatives || newMarker.type() == DocumentMarker::DictationResult)
335 return true;
336#endif
337 if (newMarker.type() == DocumentMarker::DraggedContent) {
338 if (auto targetNode = WTF::get<DocumentMarker::DraggedContentData>(newMarker.data()).targetNode)
339 return targetNode->renderer() && targetNode->renderer()->isRenderReplaced();
340 }
341
342 return false;
343}
344
345// Markers are stored in order sorted by their start offset.
346// Markers of the same type do not overlap each other.
347
348void DocumentMarkerController::addMarker(Node& node, const DocumentMarker& newMarker)
349{
350 ASSERT(newMarker.endOffset() >= newMarker.startOffset());
351 if (newMarker.endOffset() == newMarker.startOffset())
352 return;
353
354 if (auto* renderer = node.renderer()) {
355 // FIXME: Factor the marker painting code out of InlineTextBox and teach simple line layout to use it.
356 if (is<RenderText>(*renderer))
357 downcast<RenderText>(*renderer).ensureLineBoxes();
358 else if (is<RenderBlockFlow>(*renderer))
359 downcast<RenderBlockFlow>(*renderer).ensureLineBoxes();
360 }
361
362 m_possiblyExistingMarkerTypes.add(newMarker.type());
363
364 std::unique_ptr<MarkerList>& list = m_markers.add(&node, nullptr).iterator->value;
365
366 if (!list) {
367 list = std::make_unique<MarkerList>();
368 list->append(RenderedDocumentMarker(newMarker));
369 } else if (shouldInsertAsSeparateMarker(newMarker)) {
370 // We don't merge dictation markers.
371 size_t i;
372 size_t numberOfMarkers = list->size();
373 for (i = 0; i < numberOfMarkers; ++i) {
374 DocumentMarker marker = list->at(i);
375 if (marker.startOffset() > newMarker.startOffset())
376 break;
377 }
378 list->insert(i, RenderedDocumentMarker(newMarker));
379 } else {
380 RenderedDocumentMarker toInsert(newMarker);
381 size_t numMarkers = list->size();
382 size_t i;
383 // Iterate over all markers whose start offset is less than or equal to the new marker's.
384 // If one of them is of the same type as the new marker and touches it or intersects with it
385 // (there is at most one), remove it and adjust the new marker's start offset to encompass it.
386 for (i = 0; i < numMarkers; ++i) {
387 DocumentMarker marker = list->at(i);
388 if (marker.startOffset() > toInsert.startOffset())
389 break;
390 if (marker.type() == toInsert.type() && marker.endOffset() >= toInsert.startOffset()) {
391 toInsert.setStartOffset(marker.startOffset());
392 list->remove(i);
393 numMarkers--;
394 break;
395 }
396 }
397 size_t j = i;
398 // Iterate over all markers whose end offset is less than or equal to the new marker's,
399 // removing markers of the same type as the new marker which touch it or intersect with it,
400 // adjusting the new marker's end offset to cover them if necessary.
401 while (j < numMarkers) {
402 DocumentMarker marker = list->at(j);
403 if (marker.startOffset() > toInsert.endOffset())
404 break;
405 if (marker.type() == toInsert.type()) {
406 list->remove(j);
407 if (toInsert.endOffset() <= marker.endOffset()) {
408 toInsert.setEndOffset(marker.endOffset());
409 break;
410 }
411 numMarkers--;
412 } else
413 j++;
414 }
415 // At this point i points to the node before which we want to insert.
416 list->insert(i, RenderedDocumentMarker(toInsert));
417 }
418
419 if (node.renderer())
420 node.renderer()->repaint();
421
422 invalidateRectsForMarkersInNode(node);
423}
424
425// copies markers from srcNode to dstNode, applying the specified shift delta to the copies. The shift is
426// useful if, e.g., the caller has created the dstNode from a non-prefix substring of the srcNode.
427void DocumentMarkerController::copyMarkers(Node& srcNode, unsigned startOffset, int length, Node& dstNode, int delta)
428{
429 if (length <= 0)
430 return;
431
432 if (!possiblyHasMarkers(DocumentMarker::allMarkers()))
433 return;
434 ASSERT(!m_markers.isEmpty());
435
436 MarkerList* list = m_markers.get(&srcNode);
437 if (!list)
438 return;
439
440 bool docDirty = false;
441 unsigned endOffset = startOffset + length - 1;
442 for (auto& marker : *list) {
443 // stop if we are now past the specified range
444 if (marker.startOffset() > endOffset)
445 break;
446
447 // skip marker that is before the specified range or is the wrong type
448 if (marker.endOffset() < startOffset)
449 continue;
450
451 // pin the marker to the specified range and apply the shift delta
452 docDirty = true;
453 if (marker.startOffset() < startOffset)
454 marker.setStartOffset(startOffset);
455 if (marker.endOffset() > endOffset)
456 marker.setEndOffset(endOffset);
457 marker.shiftOffsets(delta);
458
459 addMarker(dstNode, marker);
460 }
461
462 if (docDirty && dstNode.renderer())
463 dstNode.renderer()->repaint();
464}
465
466void DocumentMarkerController::removeMarkers(Node& node, unsigned startOffset, int length, OptionSet<DocumentMarker::MarkerType> markerTypes, std::function<bool(DocumentMarker*)> filterFunction, RemovePartiallyOverlappingMarkerOrNot shouldRemovePartiallyOverlappingMarker)
467{
468 if (length <= 0)
469 return;
470
471 if (!possiblyHasMarkers(markerTypes))
472 return;
473 ASSERT(!(m_markers.isEmpty()));
474
475 MarkerList* list = m_markers.get(&node);
476 if (!list)
477 return;
478
479 bool docDirty = false;
480 unsigned endOffset = startOffset + length;
481 for (size_t i = 0; i < list->size();) {
482 DocumentMarker marker = list->at(i);
483
484 // markers are returned in order, so stop if we are now past the specified range
485 if (marker.startOffset() >= endOffset)
486 break;
487
488 // skip marker that is wrong type or before target
489 if (marker.endOffset() <= startOffset || !markerTypes.contains(marker.type())) {
490 i++;
491 continue;
492 }
493
494 if (filterFunction && !filterFunction(&marker)) {
495 i++;
496 continue;
497 }
498
499 // at this point we know that marker and target intersect in some way
500 docDirty = true;
501
502 // pitch the old marker
503 list->remove(i);
504
505 if (shouldRemovePartiallyOverlappingMarker)
506 // Stop here. Don't add resulting slices back.
507 continue;
508
509 // add either of the resulting slices that are left after removing target
510 if (startOffset > marker.startOffset()) {
511 DocumentMarker newLeft = marker;
512 newLeft.setEndOffset(startOffset);
513 list->insert(i, RenderedDocumentMarker(newLeft));
514 // i now points to the newly-inserted node, but we want to skip that one
515 i++;
516 }
517 if (marker.endOffset() > endOffset) {
518 DocumentMarker newRight = marker;
519 newRight.setStartOffset(endOffset);
520 list->insert(i, RenderedDocumentMarker(newRight));
521 // i now points to the newly-inserted node, but we want to skip that one
522 i++;
523 }
524 }
525
526 if (list->isEmpty()) {
527 m_markers.remove(&node);
528 if (m_markers.isEmpty())
529 m_possiblyExistingMarkerTypes = { };
530 }
531
532 if (docDirty && node.renderer())
533 node.renderer()->repaint();
534}
535
536DocumentMarker* DocumentMarkerController::markerContainingPoint(const LayoutPoint& point, DocumentMarker::MarkerType markerType)
537{
538 if (!possiblyHasMarkers(markerType))
539 return nullptr;
540 ASSERT(!(m_markers.isEmpty()));
541
542 updateRectsForInvalidatedMarkersOfType(markerType);
543
544 for (auto& nodeAndMarkers : m_markers) {
545 for (auto& marker : *nodeAndMarkers.value) {
546 if (marker.type() != markerType)
547 continue;
548
549 if (marker.contains(point))
550 return &marker;
551 }
552 }
553
554 return nullptr;
555}
556
557Vector<RenderedDocumentMarker*> DocumentMarkerController::markersFor(Node& node, OptionSet<DocumentMarker::MarkerType> markerTypes)
558{
559 if (!possiblyHasMarkers(markerTypes))
560 return { };
561
562 Vector<RenderedDocumentMarker*> result;
563 MarkerList* list = m_markers.get(&node);
564 if (!list)
565 return result;
566
567 for (auto& marker : *list) {
568 if (markerTypes.contains(marker.type()))
569 result.append(&marker);
570 }
571
572 return result;
573}
574
575Vector<RenderedDocumentMarker*> DocumentMarkerController::markersInRange(Range& range, OptionSet<DocumentMarker::MarkerType> markerTypes)
576{
577 if (!possiblyHasMarkers(markerTypes))
578 return Vector<RenderedDocumentMarker*>();
579
580 Vector<RenderedDocumentMarker*> foundMarkers;
581
582 Node& startContainer = range.startContainer();
583 Node& endContainer = range.endContainer();
584
585 Node* pastLastNode = range.pastLastNode();
586 for (Node* node = range.firstNode(); node != pastLastNode; node = NodeTraversal::next(*node)) {
587 ASSERT(node);
588 for (auto* marker : markersFor(*node)) {
589 if (!markerTypes.contains(marker->type()))
590 continue;
591 if (node == &startContainer && marker->endOffset() <= range.startOffset())
592 continue;
593 if (node == &endContainer && marker->startOffset() >= range.endOffset())
594 continue;
595 foundMarkers.append(marker);
596 }
597 }
598 return foundMarkers;
599}
600
601void DocumentMarkerController::removeMarkers(Node& node, OptionSet<DocumentMarker::MarkerType> markerTypes)
602{
603 if (!possiblyHasMarkers(markerTypes))
604 return;
605 ASSERT(!m_markers.isEmpty());
606
607 auto iterator = m_markers.find(&node);
608 if (iterator != m_markers.end())
609 removeMarkersFromList(iterator, markerTypes);
610}
611
612void DocumentMarkerController::removeMarkers(OptionSet<DocumentMarker::MarkerType> markerTypes)
613{
614 if (!possiblyHasMarkers(markerTypes))
615 return;
616 ASSERT(!m_markers.isEmpty());
617
618 for (auto& node : copyToVector(m_markers.keys())) {
619 auto iterator = m_markers.find(node);
620 if (iterator != m_markers.end())
621 removeMarkersFromList(iterator, markerTypes);
622 }
623
624 m_possiblyExistingMarkerTypes.remove(markerTypes);
625}
626
627void DocumentMarkerController::removeMarkersFromList(MarkerMap::iterator iterator, OptionSet<DocumentMarker::MarkerType> markerTypes)
628{
629 bool needsRepainting = false;
630 bool listCanBeRemoved;
631
632 if (markerTypes == DocumentMarker::allMarkers()) {
633 needsRepainting = true;
634 listCanBeRemoved = true;
635 } else {
636 MarkerList* list = iterator->value.get();
637
638 for (size_t i = 0; i != list->size(); ) {
639 DocumentMarker marker = list->at(i);
640
641 // skip nodes that are not of the specified type
642 if (!markerTypes.contains(marker.type())) {
643 ++i;
644 continue;
645 }
646
647 // pitch the old marker
648 list->remove(i);
649 needsRepainting = true;
650 // i now is the index of the next marker
651 }
652
653 listCanBeRemoved = list->isEmpty();
654 }
655
656 if (needsRepainting) {
657 if (auto renderer = iterator->key->renderer())
658 renderer->repaint();
659 }
660
661 if (listCanBeRemoved) {
662 m_markers.remove(iterator);
663 if (m_markers.isEmpty())
664 m_possiblyExistingMarkerTypes = { };
665 }
666}
667
668void DocumentMarkerController::repaintMarkers(OptionSet<DocumentMarker::MarkerType> markerTypes)
669{
670 if (!possiblyHasMarkers(markerTypes))
671 return;
672 ASSERT(!m_markers.isEmpty());
673
674 // outer loop: process each markered node in the document
675 for (auto& marker : m_markers) {
676 Node* node = marker.key.get();
677
678 // inner loop: process each marker in the current node
679 bool nodeNeedsRepaint = false;
680 for (auto& documentMarker : *marker.value) {
681 if (markerTypes.contains(documentMarker.type())) {
682 nodeNeedsRepaint = true;
683 break;
684 }
685 }
686
687 if (!nodeNeedsRepaint)
688 continue;
689
690 // cause the node to be redrawn
691 if (auto renderer = node->renderer())
692 renderer->repaint();
693 }
694}
695
696void DocumentMarkerController::shiftMarkers(Node& node, unsigned startOffset, int delta)
697{
698 if (!possiblyHasMarkers(DocumentMarker::allMarkers()))
699 return;
700 ASSERT(!m_markers.isEmpty());
701
702 MarkerList* list = m_markers.get(&node);
703 if (!list)
704 return;
705
706 bool didShiftMarker = false;
707 for (size_t i = 0; i != list->size(); ) {
708 RenderedDocumentMarker& marker = list->at(i);
709 // FIXME: How can this possibly be iOS-specific code?
710#if PLATFORM(IOS_FAMILY)
711 int targetStartOffset = marker.startOffset() + delta;
712 int targetEndOffset = marker.endOffset() + delta;
713 if (targetStartOffset >= node.maxCharacterOffset() || targetEndOffset <= 0) {
714 list->remove(i);
715 continue;
716 }
717#endif
718 if (marker.startOffset() >= startOffset) {
719 ASSERT((int)marker.startOffset() + delta >= 0);
720 marker.shiftOffsets(delta);
721 didShiftMarker = true;
722#if !PLATFORM(IOS_FAMILY)
723 }
724#else
725 // FIXME: Inserting text inside a DocumentMarker does not grow the marker.
726 // See <https://bugs.webkit.org/show_bug.cgi?id=62504>.
727 } else if (marker.endOffset() > startOffset) {
728 if (marker.endOffset() + delta <= marker.startOffset()) {
729 list->remove(i);
730 continue;
731 }
732 marker.setEndOffset(targetEndOffset < node.maxCharacterOffset() ? targetEndOffset : node.maxCharacterOffset());
733 didShiftMarker = true;
734 }
735#endif
736 ++i;
737 }
738
739 if (didShiftMarker) {
740 invalidateRectsForMarkersInNode(node);
741
742 if (node.renderer())
743 node.renderer()->repaint();
744 }
745}
746
747void DocumentMarkerController::setMarkersActive(Range& range, bool active)
748{
749 if (!possiblyHasMarkers(DocumentMarker::allMarkers()))
750 return;
751 ASSERT(!m_markers.isEmpty());
752
753 Node& startContainer = range.startContainer();
754 Node& endContainer = range.endContainer();
755
756 Node* pastLastNode = range.pastLastNode();
757
758 for (Node* node = range.firstNode(); node != pastLastNode; node = NodeTraversal::next(*node)) {
759 unsigned startOffset = node == &startContainer ? range.startOffset() : 0;
760 unsigned endOffset = node == &endContainer ? range.endOffset() : std::numeric_limits<unsigned>::max();
761 setMarkersActive(*node, startOffset, endOffset, active);
762 }
763}
764
765void DocumentMarkerController::setMarkersActive(Node& node, unsigned startOffset, unsigned endOffset, bool active)
766{
767 MarkerList* list = m_markers.get(&node);
768 if (!list)
769 return;
770
771 bool didActivateMarker = false;
772 for (auto& marker : *list) {
773 // Markers are returned in order, so stop if we are now past the specified range.
774 if (marker.startOffset() >= endOffset)
775 break;
776
777 // Skip marker that is wrong type or before target.
778 if (marker.endOffset() < startOffset || marker.type() != DocumentMarker::TextMatch)
779 continue;
780
781 marker.setActiveMatch(active);
782 didActivateMarker = true;
783 }
784
785 if (didActivateMarker && node.renderer())
786 node.renderer()->repaint();
787}
788
789bool DocumentMarkerController::hasMarkers(Range& range, OptionSet<DocumentMarker::MarkerType> markerTypes)
790{
791 if (!possiblyHasMarkers(markerTypes))
792 return false;
793 ASSERT(!m_markers.isEmpty());
794
795 Node& startContainer = range.startContainer();
796 Node& endContainer = range.endContainer();
797
798 Node* pastLastNode = range.pastLastNode();
799 for (Node* node = range.firstNode(); node != pastLastNode; node = NodeTraversal::next(*node)) {
800 ASSERT(node);
801 for (auto* marker : markersFor(*node)) {
802 if (!markerTypes.contains(marker->type()))
803 continue;
804 if (node == &startContainer && marker->endOffset() <= static_cast<unsigned>(range.startOffset()))
805 continue;
806 if (node == &endContainer && marker->startOffset() >= static_cast<unsigned>(range.endOffset()))
807 continue;
808 return true;
809 }
810 }
811 return false;
812}
813
814void DocumentMarkerController::clearDescriptionOnMarkersIntersectingRange(Range& range, OptionSet<DocumentMarker::MarkerType> markerTypes)
815{
816 if (!possiblyHasMarkers(markerTypes))
817 return;
818 ASSERT(!m_markers.isEmpty());
819
820 Node& startContainer = range.startContainer();
821 Node& endContainer = range.endContainer();
822
823 Node* pastLastNode = range.pastLastNode();
824 for (Node* node = range.firstNode(); node != pastLastNode; node = NodeTraversal::next(*node)) {
825 unsigned startOffset = node == &startContainer ? range.startOffset() : 0;
826 unsigned endOffset = node == &endContainer ? static_cast<unsigned>(range.endOffset()) : std::numeric_limits<unsigned>::max();
827 MarkerList* list = m_markers.get(node);
828 if (!list)
829 continue;
830
831 for (size_t i = 0; i < list->size(); ++i) {
832 DocumentMarker& marker = list->at(i);
833
834 // markers are returned in order, so stop if we are now past the specified range
835 if (marker.startOffset() >= endOffset)
836 break;
837
838 // skip marker that is wrong type or before target
839 if (marker.endOffset() <= startOffset || !markerTypes.contains(marker.type())) {
840 i++;
841 continue;
842 }
843
844 marker.clearData();
845 }
846 }
847}
848
849#if ENABLE(TREE_DEBUGGING)
850void DocumentMarkerController::showMarkers() const
851{
852 fprintf(stderr, "%d nodes have markers:\n", m_markers.size());
853 for (auto& marker : m_markers) {
854 Node* node = marker.key.get();
855 fprintf(stderr, "%p", node);
856 for (auto& documentMarker : *marker.value)
857 fprintf(stderr, " %d:[%d:%d](%d)", documentMarker.type(), documentMarker.startOffset(), documentMarker.endOffset(), documentMarker.isActiveMatch());
858
859 fprintf(stderr, "\n");
860 }
861}
862#endif
863
864} // namespace WebCore
865
866#if ENABLE(TREE_DEBUGGING)
867void showDocumentMarkers(const WebCore::DocumentMarkerController* controller)
868{
869 if (controller)
870 controller->showMarkers();
871}
872#endif
873