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 | |
43 | namespace WebCore { |
44 | |
45 | inline bool DocumentMarkerController::possiblyHasMarkers(OptionSet<DocumentMarker::MarkerType> types) |
46 | { |
47 | return m_possiblyExistingMarkerTypes.containsAny(types); |
48 | } |
49 | |
50 | DocumentMarkerController::DocumentMarkerController(Document& document) |
51 | : m_document(document) |
52 | { |
53 | } |
54 | |
55 | DocumentMarkerController::~DocumentMarkerController() = default; |
56 | |
57 | void DocumentMarkerController::detach() |
58 | { |
59 | m_markers.clear(); |
60 | m_possiblyExistingMarkerTypes = { }; |
61 | } |
62 | |
63 | void 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 | |
71 | void 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 | |
80 | void DocumentMarkerController::addMarkerToNode(Node& node, unsigned startOffset, unsigned length, DocumentMarker::MarkerType type) |
81 | { |
82 | addMarker(node, DocumentMarker(type, startOffset, startOffset + length)); |
83 | } |
84 | |
85 | void 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 | |
90 | void 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 | |
102 | void 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 | |
110 | void 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 | |
126 | void 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 | |
136 | void 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) |
146 | void 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 | |
156 | void 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 | |
170 | void 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 | |
184 | static 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 | |
201 | void 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 | |
215 | void 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 | |
230 | static 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 | |
243 | void 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 | |
271 | Vector<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 | |
326 | static 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 | |
348 | void 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. |
427 | void 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 | |
466 | void 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 | |
536 | DocumentMarker* 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 ▮ |
551 | } |
552 | } |
553 | |
554 | return nullptr; |
555 | } |
556 | |
557 | Vector<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 | |
575 | Vector<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 | |
601 | void 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 | |
612 | void 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 | |
627 | void 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 | |
668 | void 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 | |
696 | void 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 | |
747 | void 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 | |
765 | void 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 | |
789 | bool 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 | |
814 | void 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) |
850 | void 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) |
867 | void showDocumentMarkers(const WebCore::DocumentMarkerController* controller) |
868 | { |
869 | if (controller) |
870 | controller->showMarkers(); |
871 | } |
872 | #endif |
873 | |