/* * Copyright (C) 1999 Lars Knoll (knoll@kde.org) * (C) 1999 Antti Koivisto (koivisto@kde.org) * (C) 2001 Dirk Mueller (mueller@kde.org) * (C) 2006 Alexey Proskuryakov (ap@webkit.org) * Copyright (C) 2004, 2005, 2006, 2007, 2008, 2009, 2010 Apple Inc. All rights reserved. * Copyright (C) 2008, 2009 Torch Mobile Inc. All rights reserved. (http://www.torchmobile.com/) * Copyright (C) Research In Motion Limited 2010. All rights reserved. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. * */ #include "config.h" #include "DocumentMarkerController.h" #include "Chrome.h" #include "ChromeClient.h" #include "MainFrame.h" #include "NodeTraversal.h" #include "Range.h" #include "RenderBlockFlow.h" #include "RenderLayer.h" #include "RenderText.h" #include "RenderedDocumentMarker.h" #include "TextIterator.h" #include namespace WebCore { inline bool DocumentMarkerController::possiblyHasMarkers(DocumentMarker::MarkerTypes types) { return m_possiblyExistingMarkerTypes.intersects(types); } DocumentMarkerController::DocumentMarkerController(Document& document) : m_document(document) { } DocumentMarkerController::~DocumentMarkerController() { } void DocumentMarkerController::detach() { m_markers.clear(); m_possiblyExistingMarkerTypes = 0; } void DocumentMarkerController::addMarker(Range* range, DocumentMarker::MarkerType type, const String& description) { for (TextIterator markedText(range); !markedText.atEnd(); markedText.advance()) { RefPtr textPiece = markedText.range(); addMarker(&textPiece->startContainer(), DocumentMarker(type, textPiece->startOffset(), textPiece->endOffset(), description)); } } void DocumentMarkerController::addMarker(Range* range, DocumentMarker::MarkerType type) { for (TextIterator markedText(range); !markedText.atEnd(); markedText.advance()) { RefPtr textPiece = markedText.range(); addMarker(&textPiece->startContainer(), DocumentMarker(type, textPiece->startOffset(), textPiece->endOffset())); } } void DocumentMarkerController::addMarkerToNode(Node* node, unsigned startOffset, unsigned length, DocumentMarker::MarkerType type) { addMarker(node, DocumentMarker(type, startOffset, startOffset + length)); } void DocumentMarkerController::addMarkerToNode(Node* node, unsigned startOffset, unsigned length, DocumentMarker::MarkerType type, PassRefPtr details) { addMarker(node, DocumentMarker(type, startOffset, startOffset + length, details)); } void DocumentMarkerController::addTextMatchMarker(const Range* range, bool activeMatch) { for (TextIterator markedText(range); !markedText.atEnd(); markedText.advance()) { RefPtr textPiece = markedText.range(); unsigned startOffset = textPiece->startOffset(); unsigned endOffset = textPiece->endOffset(); addMarker(&textPiece->startContainer(), DocumentMarker(startOffset, endOffset, activeMatch)); } } #if PLATFORM(IOS) void DocumentMarkerController::addMarker(Range* range, DocumentMarker::MarkerType type, const String& description, const Vector& interpretations, const RetainPtr& metadata) { for (TextIterator markedText(range); !markedText.atEnd(); markedText.advance()) { RefPtr textPiece = markedText.range(); addMarker(&textPiece->startContainer(), DocumentMarker(type, textPiece->startOffset(), textPiece->endOffset(), description, interpretations, metadata)); } } void DocumentMarkerController::addDictationPhraseWithAlternativesMarker(Range* range, const Vector& interpretations) { ASSERT(interpretations.size() > 1); if (interpretations.size() <= 1) return; size_t numberOfAlternatives = interpretations.size() - 1; for (TextIterator markedText(range); !markedText.atEnd(); markedText.advance()) { RefPtr textPiece = markedText.range(); DocumentMarker marker(DocumentMarker::DictationPhraseWithAlternatives, textPiece->startOffset(), textPiece->endOffset(), "", Vector(numberOfAlternatives), RetainPtr()); for (size_t i = 0; i < numberOfAlternatives; ++i) marker.setAlternative(interpretations[i + 1], i); addMarker(&textPiece->startContainer(), marker); } } void DocumentMarkerController::addDictationResultMarker(Range* range, const RetainPtr& metadata) { for (TextIterator markedText(range); !markedText.atEnd(); markedText.advance()) { RefPtr textPiece = markedText.range(); addMarker(&textPiece->startContainer(), DocumentMarker(DocumentMarker::DictationResult, textPiece->startOffset(), textPiece->endOffset(), String(), Vector(), metadata)); } } #endif void DocumentMarkerController::removeMarkers(Range* range, DocumentMarker::MarkerTypes markerTypes, RemovePartiallyOverlappingMarkerOrNot shouldRemovePartiallyOverlappingMarker) { for (TextIterator markedText(range); !markedText.atEnd(); markedText.advance()) { if (!possiblyHasMarkers(markerTypes)) return; ASSERT(!m_markers.isEmpty()); RefPtr textPiece = markedText.range(); int startOffset = textPiece->startOffset(); int endOffset = textPiece->endOffset(); removeMarkers(&textPiece->startContainer(), startOffset, endOffset - startOffset, markerTypes, shouldRemovePartiallyOverlappingMarker); } } static void updateRenderedRectsForMarker(RenderedDocumentMarker& marker, Node& node) { ASSERT(!node.document().view() || !node.document().view()->needsLayout()); // FIXME: We should refactor this so that we don't use Range (because we only have one Node), but still share code with absoluteTextQuads(). RefPtr markerRange = Range::create(node.document(), &node, marker.startOffset(), &node, marker.endOffset()); if (!markerRange) return; Vector absoluteMarkerQuads; markerRange->absoluteTextQuads(absoluteMarkerQuads, true); Vector absoluteMarkerRects; absoluteMarkerRects.reserveInitialCapacity(absoluteMarkerQuads.size()); for (const auto& quad : absoluteMarkerQuads) absoluteMarkerRects.append(quad.boundingBox()); marker.setUnclippedAbsoluteRects(absoluteMarkerRects); } void DocumentMarkerController::invalidateRectsForAllMarkers() { if (!hasMarkers()) return; for (auto& markers : m_markers.values()) { for (auto& marker : *markers) marker.invalidate(); } if (Page* page = m_document.page()) page->chrome().client().didInvalidateDocumentMarkerRects(); } void DocumentMarkerController::invalidateRectsForMarkersInNode(Node& node) { if (!hasMarkers()) return; MarkerList* markers = m_markers.get(&node); ASSERT(markers); for (auto& marker : *markers) marker.invalidate(); if (Page* page = m_document.page()) page->chrome().client().didInvalidateDocumentMarkerRects(); } static void updateMainFrameLayoutIfNeeded(Document& document) { Frame* frame = document.frame(); if (!frame) return; FrameView* mainFrameView = frame->mainFrame().view(); if (!mainFrameView) return; mainFrameView->updateLayoutAndStyleIfNeededRecursive(); } void DocumentMarkerController::updateRectsForInvalidatedMarkersOfType(DocumentMarker::MarkerType markerType) { if (!possiblyHasMarkers(markerType)) return; ASSERT(!(m_markers.isEmpty())); bool needsLayoutIfAnyRectsAreDirty = true; for (auto& nodeAndMarkers : m_markers) { Node& node = *nodeAndMarkers.key; for (auto& marker : *nodeAndMarkers.value) { if (marker.type() != markerType) continue; if (marker.isValid()) continue; // We'll do up to one layout per call if we have any dirty markers. if (needsLayoutIfAnyRectsAreDirty) { updateMainFrameLayoutIfNeeded(m_document); needsLayoutIfAnyRectsAreDirty = false; } updateRenderedRectsForMarker(marker, node); } } } Vector DocumentMarkerController::renderedRectsForMarkers(DocumentMarker::MarkerType markerType) { Vector result; if (!possiblyHasMarkers(markerType)) return result; ASSERT(!(m_markers.isEmpty())); Frame* frame = m_document.frame(); if (!frame) return result; FrameView* frameView = frame->view(); if (!frameView) return result; updateRectsForInvalidatedMarkersOfType(markerType); bool isSubframe = !frame->isMainFrame(); IntRect subframeClipRect; if (isSubframe) subframeClipRect = frameView->windowToContents(frameView->windowClipRect()); for (auto& nodeAndMarkers : m_markers) { Node& node = *nodeAndMarkers.key; FloatRect overflowClipRect; if (RenderObject* renderer = node.renderer()) overflowClipRect = renderer->absoluteClippedOverflowRect(); for (auto& marker : *nodeAndMarkers.value) { if (marker.type() != markerType) continue; auto renderedRects = marker.unclippedAbsoluteRects(); // Clip document markers by their overflow clip. if (node.renderer()) { for (auto& rect : renderedRects) rect.intersect(overflowClipRect); } // Clip subframe document markers by their frame. if (isSubframe) { for (auto& rect : renderedRects) rect.intersect(subframeClipRect); } for (const auto& rect : renderedRects) { if (!rect.isEmpty()) result.append(rect); } } } return result; } // Markers are stored in order sorted by their start offset. // Markers of the same type do not overlap each other. void DocumentMarkerController::addMarker(Node* node, const DocumentMarker& newMarker) { ASSERT(newMarker.endOffset() >= newMarker.startOffset()); if (newMarker.endOffset() == newMarker.startOffset()) return; if (auto* renderer = node->renderer()) { // FIXME: Factor the marker painting code out of InlineTextBox and teach simple line layout to use it. if (is(*renderer)) downcast(*renderer).ensureLineBoxes(); else if (is(*renderer)) downcast(*renderer).ensureLineBoxes(); } m_possiblyExistingMarkerTypes.add(newMarker.type()); std::unique_ptr& list = m_markers.add(node, nullptr).iterator->value; if (!list) { list = std::make_unique(); list->append(RenderedDocumentMarker(newMarker)); #if PLATFORM(IOS) } else if (newMarker.type() == DocumentMarker::DictationPhraseWithAlternatives || newMarker.type() == DocumentMarker::DictationResult) { // We don't merge dictation markers. size_t i; size_t numberOfMarkers = list->size(); for (i = 0; i < numberOfMarkers; ++i) { DocumentMarker marker = list->at(i); if (marker.startOffset() > newMarker.startOffset()) break; } list->insert(i, RenderedDocumentMarker(newMarker)); #endif } else { RenderedDocumentMarker toInsert(newMarker); size_t numMarkers = list->size(); size_t i; // Iterate over all markers whose start offset is less than or equal to the new marker's. // If one of them is of the same type as the new marker and touches it or intersects with it // (there is at most one), remove it and adjust the new marker's start offset to encompass it. for (i = 0; i < numMarkers; ++i) { DocumentMarker marker = list->at(i); if (marker.startOffset() > toInsert.startOffset()) break; if (marker.type() == toInsert.type() && marker.endOffset() >= toInsert.startOffset()) { toInsert.setStartOffset(marker.startOffset()); list->remove(i); numMarkers--; break; } } size_t j = i; // Iterate over all markers whose end offset is less than or equal to the new marker's, // removing markers of the same type as the new marker which touch it or intersect with it, // adjusting the new marker's end offset to cover them if necessary. while (j < numMarkers) { DocumentMarker marker = list->at(j); if (marker.startOffset() > toInsert.endOffset()) break; if (marker.type() == toInsert.type()) { list->remove(j); if (toInsert.endOffset() <= marker.endOffset()) { toInsert.setEndOffset(marker.endOffset()); break; } numMarkers--; } else j++; } // At this point i points to the node before which we want to insert. list->insert(i, RenderedDocumentMarker(toInsert)); } if (node->renderer()) node->renderer()->repaint(); invalidateRectsForMarkersInNode(*node); } // copies markers from srcNode to dstNode, applying the specified shift delta to the copies. The shift is // useful if, e.g., the caller has created the dstNode from a non-prefix substring of the srcNode. void DocumentMarkerController::copyMarkers(Node* srcNode, unsigned startOffset, int length, Node* dstNode, int delta) { if (length <= 0) return; if (!possiblyHasMarkers(DocumentMarker::AllMarkers())) return; ASSERT(!m_markers.isEmpty()); MarkerList* list = m_markers.get(srcNode); if (!list) return; bool docDirty = false; unsigned endOffset = startOffset + length - 1; for (auto& marker : *list) { // stop if we are now past the specified range if (marker.startOffset() > endOffset) break; // skip marker that is before the specified range or is the wrong type if (marker.endOffset() < startOffset) continue; // pin the marker to the specified range and apply the shift delta docDirty = true; if (marker.startOffset() < startOffset) marker.setStartOffset(startOffset); if (marker.endOffset() > endOffset) marker.setEndOffset(endOffset); marker.shiftOffsets(delta); addMarker(dstNode, marker); } if (docDirty && dstNode->renderer()) dstNode->renderer()->repaint(); } void DocumentMarkerController::removeMarkers(Node* node, unsigned startOffset, int length, DocumentMarker::MarkerTypes markerTypes, RemovePartiallyOverlappingMarkerOrNot shouldRemovePartiallyOverlappingMarker) { if (length <= 0) return; if (!possiblyHasMarkers(markerTypes)) return; ASSERT(!(m_markers.isEmpty())); MarkerList* list = m_markers.get(node); if (!list) return; bool docDirty = false; unsigned endOffset = startOffset + length; for (size_t i = 0; i < list->size();) { DocumentMarker marker = list->at(i); // markers are returned in order, so stop if we are now past the specified range if (marker.startOffset() >= endOffset) break; // skip marker that is wrong type or before target if (marker.endOffset() <= startOffset || !markerTypes.contains(marker.type())) { i++; continue; } // at this point we know that marker and target intersect in some way docDirty = true; // pitch the old marker list->remove(i); if (shouldRemovePartiallyOverlappingMarker) // Stop here. Don't add resulting slices back. continue; // add either of the resulting slices that are left after removing target if (startOffset > marker.startOffset()) { DocumentMarker newLeft = marker; newLeft.setEndOffset(startOffset); list->insert(i, RenderedDocumentMarker(newLeft)); // i now points to the newly-inserted node, but we want to skip that one i++; } if (marker.endOffset() > endOffset) { DocumentMarker newRight = marker; newRight.setStartOffset(endOffset); list->insert(i, RenderedDocumentMarker(newRight)); // i now points to the newly-inserted node, but we want to skip that one i++; } } if (list->isEmpty()) { m_markers.remove(node); if (m_markers.isEmpty()) m_possiblyExistingMarkerTypes = 0; } if (docDirty && node->renderer()) node->renderer()->repaint(); } DocumentMarker* DocumentMarkerController::markerContainingPoint(const LayoutPoint& point, DocumentMarker::MarkerType markerType) { if (!possiblyHasMarkers(markerType)) return nullptr; ASSERT(!(m_markers.isEmpty())); updateRectsForInvalidatedMarkersOfType(markerType); for (auto& nodeAndMarkers : m_markers) { for (auto& marker : *nodeAndMarkers.value) { if (marker.type() != markerType) continue; if (marker.contains(point)) return ▮ } } return nullptr; } Vector DocumentMarkerController::markersFor(Node* node, DocumentMarker::MarkerTypes markerTypes) { Vector result; MarkerList* list = m_markers.get(node); if (!list) return result; for (auto& marker : *list) { if (markerTypes.contains(marker.type())) result.append(&marker); } return result; } Vector DocumentMarkerController::markersInRange(Range* range, DocumentMarker::MarkerTypes markerTypes) { if (!possiblyHasMarkers(markerTypes)) return Vector(); Vector foundMarkers; Node& startContainer = range->startContainer(); Node& endContainer = range->endContainer(); Node* pastLastNode = range->pastLastNode(); for (Node* node = range->firstNode(); node != pastLastNode; node = NodeTraversal::next(*node)) { for (auto* marker : markersFor(node)) { if (!markerTypes.contains(marker->type())) continue; if (node == &startContainer && marker->endOffset() <= static_cast(range->startOffset())) continue; if (node == &endContainer && marker->startOffset() >= static_cast(range->endOffset())) continue; foundMarkers.append(marker); } } return foundMarkers; } void DocumentMarkerController::removeMarkers(Node* node, DocumentMarker::MarkerTypes markerTypes) { if (!possiblyHasMarkers(markerTypes)) return; ASSERT(!m_markers.isEmpty()); MarkerMap::iterator iterator = m_markers.find(node); if (iterator != m_markers.end()) removeMarkersFromList(iterator, markerTypes); } void DocumentMarkerController::removeMarkers(DocumentMarker::MarkerTypes markerTypes) { if (!possiblyHasMarkers(markerTypes)) return; ASSERT(!m_markers.isEmpty()); Vector> nodesWithMarkers; copyKeysToVector(m_markers, nodesWithMarkers); for (auto& node : nodesWithMarkers) { auto iterator = m_markers.find(node); if (iterator != m_markers.end()) removeMarkersFromList(iterator, markerTypes); } m_possiblyExistingMarkerTypes.remove(markerTypes); } void DocumentMarkerController::removeMarkersFromList(MarkerMap::iterator iterator, DocumentMarker::MarkerTypes markerTypes) { bool needsRepainting = false; bool listCanBeRemoved; if (markerTypes == DocumentMarker::AllMarkers()) { needsRepainting = true; listCanBeRemoved = true; } else { MarkerList* list = iterator->value.get(); for (size_t i = 0; i != list->size(); ) { DocumentMarker marker = list->at(i); // skip nodes that are not of the specified type if (!markerTypes.contains(marker.type())) { ++i; continue; } // pitch the old marker list->remove(i); needsRepainting = true; // i now is the index of the next marker } listCanBeRemoved = list->isEmpty(); } if (needsRepainting) { if (auto renderer = iterator->key->renderer()) renderer->repaint(); } if (listCanBeRemoved) { m_markers.remove(iterator); if (m_markers.isEmpty()) m_possiblyExistingMarkerTypes = 0; } } void DocumentMarkerController::repaintMarkers(DocumentMarker::MarkerTypes markerTypes) { if (!possiblyHasMarkers(markerTypes)) return; ASSERT(!m_markers.isEmpty()); // outer loop: process each markered node in the document for (auto& marker : m_markers) { Node* node = marker.key.get(); // inner loop: process each marker in the current node bool nodeNeedsRepaint = false; for (auto& documentMarker : *marker.value) { if (markerTypes.contains(documentMarker.type())) { nodeNeedsRepaint = true; break; } } if (!nodeNeedsRepaint) continue; // cause the node to be redrawn if (auto renderer = node->renderer()) renderer->repaint(); } } void DocumentMarkerController::shiftMarkers(Node* node, unsigned startOffset, int delta) { if (!possiblyHasMarkers(DocumentMarker::AllMarkers())) return; ASSERT(!m_markers.isEmpty()); MarkerList* list = m_markers.get(node); if (!list) return; bool didShiftMarker = false; for (size_t i = 0; i != list->size(); ) { RenderedDocumentMarker& marker = list->at(i); // FIXME: How can this possibly be iOS-specific code? #if PLATFORM(IOS) int targetStartOffset = marker.startOffset() + delta; int targetEndOffset = marker.endOffset() + delta; if (targetStartOffset >= node->maxCharacterOffset() || targetEndOffset <= 0) { list->remove(i); continue; } #endif if (marker.startOffset() >= startOffset) { ASSERT((int)marker.startOffset() + delta >= 0); marker.shiftOffsets(delta); didShiftMarker = true; #if !PLATFORM(IOS) } #else // FIXME: Inserting text inside a DocumentMarker does not grow the marker. // See . } else if (marker.endOffset() > startOffset) { if (marker.endOffset() + delta <= marker.startOffset()) { list->remove(i); continue; } marker.setEndOffset(targetEndOffset < node->maxCharacterOffset() ? targetEndOffset : node->maxCharacterOffset()); didShiftMarker = true; } #endif ++i; } if (didShiftMarker) { invalidateRectsForMarkersInNode(*node); if (node->renderer()) node->renderer()->repaint(); } } void DocumentMarkerController::setMarkersActive(Range* range, bool active) { if (!possiblyHasMarkers(DocumentMarker::AllMarkers())) return; ASSERT(!m_markers.isEmpty()); Node& startContainer = range->startContainer(); Node& endContainer = range->endContainer(); Node* pastLastNode = range->pastLastNode(); for (Node* node = range->firstNode(); node != pastLastNode; node = NodeTraversal::next(*node)) { int startOffset = node == &startContainer ? range->startOffset() : 0; int endOffset = node == &endContainer ? range->endOffset() : INT_MAX; setMarkersActive(node, startOffset, endOffset, active); } } void DocumentMarkerController::setMarkersActive(Node* node, unsigned startOffset, unsigned endOffset, bool active) { MarkerList* list = m_markers.get(node); if (!list) return; bool didActivateMarker = false; for (auto& marker : *list) { // Markers are returned in order, so stop if we are now past the specified range. if (marker.startOffset() >= endOffset) break; // Skip marker that is wrong type or before target. if (marker.endOffset() < startOffset || marker.type() != DocumentMarker::TextMatch) continue; marker.setActiveMatch(active); didActivateMarker = true; } if (didActivateMarker && node->renderer()) node->renderer()->repaint(); } bool DocumentMarkerController::hasMarkers(Range* range, DocumentMarker::MarkerTypes markerTypes) { if (!possiblyHasMarkers(markerTypes)) return false; ASSERT(!m_markers.isEmpty()); Node& startContainer = range->startContainer(); Node& endContainer = range->endContainer(); Node* pastLastNode = range->pastLastNode(); for (Node* node = range->firstNode(); node != pastLastNode; node = NodeTraversal::next(*node)) { for (auto* marker : markersFor(node)) { if (!markerTypes.contains(marker->type())) continue; if (node == &startContainer && marker->endOffset() <= static_cast(range->startOffset())) continue; if (node == &endContainer && marker->startOffset() >= static_cast(range->endOffset())) continue; return true; } } return false; } void DocumentMarkerController::clearDescriptionOnMarkersIntersectingRange(Range* range, DocumentMarker::MarkerTypes markerTypes) { if (!possiblyHasMarkers(markerTypes)) return; ASSERT(!m_markers.isEmpty()); Node& startContainer = range->startContainer(); Node& endContainer = range->endContainer(); Node* pastLastNode = range->pastLastNode(); for (Node* node = range->firstNode(); node != pastLastNode; node = NodeTraversal::next(*node)) { unsigned startOffset = node == &startContainer ? range->startOffset() : 0; unsigned endOffset = node == &endContainer ? static_cast(range->endOffset()) : std::numeric_limits::max(); MarkerList* list = m_markers.get(node); if (!list) continue; for (size_t i = 0; i < list->size(); ++i) { DocumentMarker& marker = list->at(i); // markers are returned in order, so stop if we are now past the specified range if (marker.startOffset() >= endOffset) break; // skip marker that is wrong type or before target if (marker.endOffset() <= startOffset || !markerTypes.contains(marker.type())) { i++; continue; } marker.clearDetails(); } } } #if ENABLE(TREE_DEBUGGING) void DocumentMarkerController::showMarkers() const { fprintf(stderr, "%d nodes have markers:\n", m_markers.size()); for (auto& marker : m_markers) { Node* node = marker.key.get(); fprintf(stderr, "%p", node); for (auto& documentMarker : *marker.value) fprintf(stderr, " %d:[%d:%d](%d)", documentMarker.type(), documentMarker.startOffset(), documentMarker.endOffset(), documentMarker.activeMatch()); fprintf(stderr, "\n"); } } #endif } // namespace WebCore #if ENABLE(TREE_DEBUGGING) void showDocumentMarkers(const WebCore::DocumentMarkerController* controller) { if (controller) controller->showMarkers(); } #endif