blob: 5190265f33c8bfcc2b2d67973e228330fb6b1a12 [file] [log] [blame]
/*
* 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 "core/editing/markers/DocumentMarkerController.h"
#include "core/dom/Node.h"
#include "core/dom/NodeTraversal.h"
#include "core/dom/Range.h"
#include "core/dom/Text.h"
#include "core/editing/iterators/TextIterator.h"
#include "core/editing/markers/RenderedDocumentMarker.h"
#include "core/frame/FrameView.h"
#include "core/layout/LayoutObject.h"
#include <algorithm>
#ifndef NDEBUG
#include <stdio.h>
#endif
namespace blink {
MarkerRemoverPredicate::MarkerRemoverPredicate(const Vector<String>& words)
: m_words(words) {}
bool MarkerRemoverPredicate::operator()(const DocumentMarker& documentMarker,
const Text& textNode) const {
unsigned start = documentMarker.startOffset();
unsigned length = documentMarker.endOffset() - documentMarker.startOffset();
String markerText = textNode.data().substring(start, length);
return m_words.contains(markerText);
}
namespace {
DocumentMarker::MarkerTypeIndex MarkerTypeToMarkerIndex(
DocumentMarker::MarkerType type) {
switch (type) {
case DocumentMarker::Spelling:
return DocumentMarker::SpellingMarkerIndex;
case DocumentMarker::Grammar:
return DocumentMarker::GrammarMarkerIndex;
case DocumentMarker::TextMatch:
return DocumentMarker::TextMatchMarkerIndex;
case DocumentMarker::Composition:
return DocumentMarker::CompositionMarkerIndex;
}
NOTREACHED();
return DocumentMarker::SpellingMarkerIndex;
}
} // namespace
inline bool DocumentMarkerController::possiblyHasMarkers(
DocumentMarker::MarkerTypes types) {
return m_possiblyExistingMarkerTypes.intersects(types);
}
DocumentMarkerController::DocumentMarkerController(Document& document)
: m_possiblyExistingMarkerTypes(0), m_document(&document) {
setContext(&document);
}
void DocumentMarkerController::clear() {
m_markers.clear();
m_possiblyExistingMarkerTypes = 0;
}
void DocumentMarkerController::addMarker(const Position& start,
const Position& end,
DocumentMarker::MarkerType type,
const String& description) {
// Use a TextIterator to visit the potentially multiple nodes the range
// covers.
for (TextIterator markedText(start, end); !markedText.atEnd();
markedText.advance()) {
addMarker(
markedText.currentContainer(),
DocumentMarker(type, markedText.startOffsetInCurrentContainer(),
markedText.endOffsetInCurrentContainer(), description));
}
}
void DocumentMarkerController::addTextMatchMarker(
const EphemeralRange& range,
DocumentMarker::MatchStatus matchStatus) {
DCHECK(!m_document->needsLayoutTreeUpdate());
// Use a TextIterator to visit the potentially multiple nodes the range
// covers.
for (TextIterator markedText(range.startPosition(), range.endPosition());
!markedText.atEnd(); markedText.advance()) {
addMarker(
markedText.currentContainer(),
DocumentMarker(markedText.startOffsetInCurrentContainer(),
markedText.endOffsetInCurrentContainer(), matchStatus));
}
// Don't invalidate tickmarks here. TextFinder invalidates tickmarks using a
// throttling algorithm. crbug.com/6819.
}
void DocumentMarkerController::addCompositionMarker(const Position& start,
const Position& end,
Color underlineColor,
bool thick,
Color backgroundColor) {
DCHECK(!m_document->needsLayoutTreeUpdate());
for (TextIterator markedText(start, end); !markedText.atEnd();
markedText.advance())
addMarker(markedText.currentContainer(),
DocumentMarker(markedText.startOffsetInCurrentContainer(),
markedText.endOffsetInCurrentContainer(),
underlineColor, thick, backgroundColor));
}
void DocumentMarkerController::prepareForDestruction() {
clear();
}
void DocumentMarkerController::removeMarkers(
TextIterator& markedText,
DocumentMarker::MarkerTypes markerTypes,
RemovePartiallyOverlappingMarkerOrNot
shouldRemovePartiallyOverlappingMarker) {
for (; !markedText.atEnd(); markedText.advance()) {
if (!possiblyHasMarkers(markerTypes))
return;
DCHECK(!m_markers.isEmpty());
int startOffset = markedText.startOffsetInCurrentContainer();
int endOffset = markedText.endOffsetInCurrentContainer();
removeMarkers(markedText.currentContainer(), startOffset,
endOffset - startOffset, markerTypes,
shouldRemovePartiallyOverlappingMarker);
}
}
void DocumentMarkerController::removeMarkers(
const EphemeralRange& range,
DocumentMarker::MarkerTypes markerTypes,
RemovePartiallyOverlappingMarkerOrNot
shouldRemovePartiallyOverlappingMarker) {
DCHECK(!m_document->needsLayoutTreeUpdate());
TextIterator markedText(range.startPosition(), range.endPosition());
DocumentMarkerController::removeMarkers(
markedText, markerTypes, shouldRemovePartiallyOverlappingMarker);
}
static bool startsFurther(const Member<RenderedDocumentMarker>& lhv,
const DocumentMarker* rhv) {
return lhv->startOffset() < rhv->startOffset();
}
static bool startsAfter(const Member<RenderedDocumentMarker>& marker,
size_t startOffset) {
return marker->startOffset() < startOffset;
}
static bool endsBefore(size_t startOffset,
const Member<RenderedDocumentMarker>& rhv) {
return startOffset < rhv->endOffset();
}
static bool compareByStart(const Member<DocumentMarker>& lhv,
const Member<DocumentMarker>& rhv) {
return lhv->startOffset() < rhv->startOffset();
}
static bool doesNotOverlap(const Member<RenderedDocumentMarker>& lhv,
const DocumentMarker* rhv) {
return lhv->endOffset() < rhv->startOffset();
}
static bool doesNotInclude(const Member<RenderedDocumentMarker>& marker,
size_t startOffset) {
return marker->endOffset() < startOffset;
}
static void updateMarkerRenderedRect(const Node& node,
RenderedDocumentMarker& marker) {
Range* range = Range::create(node.document());
// The offsets of the marker may be out-dated, so check for exceptions.
DummyExceptionStateForTesting exceptionState;
range->setStart(&const_cast<Node&>(node), marker.startOffset(),
exceptionState);
if (!exceptionState.hadException()) {
range->setEnd(&const_cast<Node&>(node), marker.endOffset(),
IGNORE_EXCEPTION_FOR_TESTING);
}
if (!exceptionState.hadException()) {
// TODO(yosin): Once we have a |EphemeralRange| version of |boundingBox()|,
// we should use it instead of |Range| version.
marker.setRenderedRect(LayoutRect(range->boundingBox()));
} else {
marker.nullifyRenderedRect();
}
range->dispose();
}
// 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) {
DCHECK_GE(newMarker.endOffset(), newMarker.startOffset());
if (newMarker.endOffset() == newMarker.startOffset())
return;
m_possiblyExistingMarkerTypes.add(newMarker.type());
Member<MarkerLists>& markers =
m_markers.insert(node, nullptr).storedValue->value;
if (!markers) {
markers = new MarkerLists;
markers->grow(DocumentMarker::MarkerTypeIndexesCount);
}
DocumentMarker::MarkerTypeIndex markerListIndex =
MarkerTypeToMarkerIndex(newMarker.type());
if (!markers->at(markerListIndex)) {
markers->at(markerListIndex) = new MarkerList;
}
Member<MarkerList>& list = markers->at(markerListIndex);
RenderedDocumentMarker* newRenderedMarker =
RenderedDocumentMarker::create(newMarker);
if (list->isEmpty() || list->back()->endOffset() < newMarker.startOffset()) {
list->push_back(newRenderedMarker);
} else {
if (newMarker.type() != DocumentMarker::TextMatch &&
newMarker.type() != DocumentMarker::Composition) {
mergeOverlapping(list.get(), newRenderedMarker);
} else {
MarkerList::iterator pos = std::lower_bound(list->begin(), list->end(),
&newMarker, startsFurther);
list->insert(pos - list->begin(), newRenderedMarker);
}
}
// repaint the affected node
if (node->layoutObject()) {
node->layoutObject()->setShouldDoFullPaintInvalidation(
PaintInvalidationDocumentMarkerChange);
}
}
void DocumentMarkerController::mergeOverlapping(
MarkerList* list,
RenderedDocumentMarker* toInsert) {
MarkerList::iterator firstOverlapping =
std::lower_bound(list->begin(), list->end(), toInsert, doesNotOverlap);
size_t index = firstOverlapping - list->begin();
list->insert(index, toInsert);
MarkerList::iterator inserted = list->begin() + index;
firstOverlapping = inserted + 1;
for (MarkerList::iterator i = firstOverlapping;
i != list->end() && (*i)->startOffset() <= (*inserted)->endOffset();) {
(*inserted)->setStartOffset(
std::min((*inserted)->startOffset(), (*i)->startOffset()));
(*inserted)->setEndOffset(
std::max((*inserted)->endOffset(), (*i)->endOffset()));
list->erase(i - list->begin());
}
}
// 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;
DCHECK(!m_markers.isEmpty());
MarkerLists* markers = m_markers.at(srcNode);
if (!markers)
return;
bool docDirty = false;
for (size_t markerListIndex = 0;
markerListIndex < DocumentMarker::MarkerTypeIndexesCount;
++markerListIndex) {
Member<MarkerList>& list = (*markers)[markerListIndex];
if (!list)
continue;
unsigned endOffset = startOffset + length - 1;
MarkerList::iterator startPos = std::lower_bound(
list->begin(), list->end(), startOffset, doesNotInclude);
for (MarkerList::iterator i = startPos; i != list->end(); ++i) {
DocumentMarker* marker = i->get();
// stop if we are now past the specified range
if (marker->startOffset() > endOffset)
break;
// 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);
}
}
// repaint the affected node
if (docDirty && dstNode->layoutObject()) {
dstNode->layoutObject()->setShouldDoFullPaintInvalidation(
PaintInvalidationDocumentMarkerChange);
}
}
void DocumentMarkerController::removeMarkers(
Node* node,
unsigned startOffset,
int length,
DocumentMarker::MarkerTypes markerTypes,
RemovePartiallyOverlappingMarkerOrNot
shouldRemovePartiallyOverlappingMarker) {
if (length <= 0)
return;
if (!possiblyHasMarkers(markerTypes))
return;
DCHECK(!(m_markers.isEmpty()));
MarkerLists* markers = m_markers.at(node);
if (!markers)
return;
bool docDirty = false;
size_t emptyListsCount = 0;
for (size_t markerListIndex = 0;
markerListIndex < DocumentMarker::MarkerTypeIndexesCount;
++markerListIndex) {
Member<MarkerList>& list = (*markers)[markerListIndex];
if (!list || list->isEmpty()) {
if (list.get() && list->isEmpty())
list.clear();
++emptyListsCount;
continue;
}
if (!markerTypes.contains((*list->begin())->type()))
continue;
unsigned endOffset = startOffset + length;
MarkerList::iterator startPos =
std::upper_bound(list->begin(), list->end(), startOffset, endsBefore);
for (MarkerList::iterator i = startPos; i != list->end();) {
DocumentMarker marker(*i->get());
// markers are returned in order, so stop if we are now past the specified
// range
if (marker.startOffset() >= endOffset)
break;
// at this point we know that marker and target intersect in some way
docDirty = true;
// pitch the old marker
list->erase(i - list->begin());
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);
size_t insertIndex = i - list->begin();
list->insert(insertIndex, RenderedDocumentMarker::create(newLeft));
// Move to the marker after the inserted one.
i = list->begin() + insertIndex + 1;
}
if (marker.endOffset() > endOffset) {
DocumentMarker newRight = marker;
newRight.setStartOffset(endOffset);
size_t insertIndex = i - list->begin();
list->insert(insertIndex, RenderedDocumentMarker::create(newRight));
// Move to the marker after the inserted one.
i = list->begin() + insertIndex + 1;
}
}
if (list->isEmpty()) {
list.clear();
++emptyListsCount;
}
}
if (emptyListsCount == DocumentMarker::MarkerTypeIndexesCount) {
m_markers.erase(node);
if (m_markers.isEmpty())
m_possiblyExistingMarkerTypes = 0;
}
// repaint the affected node
if (docDirty && node->layoutObject()) {
node->layoutObject()->setShouldDoFullPaintInvalidation(
PaintInvalidationDocumentMarkerChange);
}
}
DocumentMarkerVector DocumentMarkerController::markersFor(
Node* node,
DocumentMarker::MarkerTypes markerTypes) {
DocumentMarkerVector result;
MarkerLists* markers = m_markers.at(node);
if (!markers)
return result;
for (size_t markerListIndex = 0;
markerListIndex < DocumentMarker::MarkerTypeIndexesCount;
++markerListIndex) {
Member<MarkerList>& list = (*markers)[markerListIndex];
if (!list || list->isEmpty() ||
!markerTypes.contains((*list->begin())->type()))
continue;
for (size_t i = 0; i < list->size(); ++i)
result.push_back(list->at(i).get());
}
std::sort(result.begin(), result.end(), compareByStart);
return result;
}
DocumentMarkerVector DocumentMarkerController::markers() {
DocumentMarkerVector result;
for (MarkerMap::iterator i = m_markers.begin(); i != m_markers.end(); ++i) {
MarkerLists* markers = i->value.get();
for (size_t markerListIndex = 0;
markerListIndex < DocumentMarker::MarkerTypeIndexesCount;
++markerListIndex) {
Member<MarkerList>& list = (*markers)[markerListIndex];
for (size_t j = 0; list.get() && j < list->size(); ++j)
result.push_back(list->at(j).get());
}
}
std::sort(result.begin(), result.end(), compareByStart);
return result;
}
DocumentMarkerVector DocumentMarkerController::markersInRange(
const EphemeralRange& range,
DocumentMarker::MarkerTypes markerTypes) {
if (!possiblyHasMarkers(markerTypes))
return DocumentMarkerVector();
DocumentMarkerVector foundMarkers;
Node* startContainer = range.startPosition().computeContainerNode();
DCHECK(startContainer);
unsigned startOffset = static_cast<unsigned>(
range.startPosition().computeOffsetInContainerNode());
Node* endContainer = range.endPosition().computeContainerNode();
DCHECK(endContainer);
unsigned endOffset =
static_cast<unsigned>(range.endPosition().computeOffsetInContainerNode());
for (Node& node : range.nodes()) {
for (DocumentMarker* marker : markersFor(&node)) {
if (!markerTypes.contains(marker->type()))
continue;
if (node == startContainer && marker->endOffset() <= startOffset)
continue;
if (node == endContainer && marker->startOffset() >= endOffset)
continue;
foundMarkers.push_back(marker);
}
}
return foundMarkers;
}
Vector<IntRect> DocumentMarkerController::renderedRectsForMarkers(
DocumentMarker::MarkerType markerType) {
Vector<IntRect> result;
if (!possiblyHasMarkers(markerType))
return result;
DCHECK(!(m_markers.isEmpty()));
// outer loop: process each node
MarkerMap::iterator end = m_markers.end();
for (MarkerMap::iterator nodeIterator = m_markers.begin();
nodeIterator != end; ++nodeIterator) {
// inner loop; process each marker in this node
const Node& node = *nodeIterator->key;
MarkerLists* markers = nodeIterator->value.get();
for (size_t markerListIndex = 0;
markerListIndex < DocumentMarker::MarkerTypeIndexesCount;
++markerListIndex) {
Member<MarkerList>& list = (*markers)[markerListIndex];
if (!list || list->isEmpty() || (*list->begin())->type() != markerType)
continue;
for (unsigned markerIndex = 0; markerIndex < list->size();
++markerIndex) {
RenderedDocumentMarker* marker = list->at(markerIndex).get();
updateMarkerRenderedRectIfNeeded(node, *marker);
if (!marker->isRendered())
continue;
result.push_back(marker->renderedRect());
}
}
}
return result;
}
static void invalidatePaintForTickmarks(const Node& node) {
if (FrameView* frameView = node.document().view())
frameView->invalidatePaintForTickmarks();
}
void DocumentMarkerController::updateMarkerRenderedRectIfNeeded(
const Node& node,
RenderedDocumentMarker& marker) {
DCHECK(!m_document->view() || !m_document->view()->needsLayout());
DCHECK(!m_document->needsLayoutTreeUpdate());
if (!marker.isValid())
updateMarkerRenderedRect(node, marker);
}
void DocumentMarkerController::invalidateRectsForMarkersInNode(
const Node& node) {
MarkerLists* markers = m_markers.at(&node);
for (auto& markerList : *markers) {
if (!markerList || markerList->isEmpty())
continue;
for (auto& marker : *markerList)
marker->invalidate();
if (markerList->front()->type() == DocumentMarker::TextMatch)
invalidatePaintForTickmarks(node);
}
}
void DocumentMarkerController::invalidateRectsForAllMarkers() {
for (auto& nodeMarkers : m_markers) {
const Node& node = *nodeMarkers.key;
for (auto& markerList : *nodeMarkers.value) {
if (!markerList || markerList->isEmpty())
continue;
for (auto& marker : *markerList)
marker->invalidate();
if (markerList->front()->type() == DocumentMarker::TextMatch)
invalidatePaintForTickmarks(node);
}
}
}
DEFINE_TRACE(DocumentMarkerController) {
visitor->trace(m_markers);
visitor->trace(m_document);
SynchronousMutationObserver::trace(visitor);
}
void DocumentMarkerController::removeMarkers(
Node* node,
DocumentMarker::MarkerTypes markerTypes) {
if (!possiblyHasMarkers(markerTypes))
return;
DCHECK(!m_markers.isEmpty());
MarkerMap::iterator iterator = m_markers.find(node);
if (iterator != m_markers.end())
removeMarkersFromList(iterator, markerTypes);
}
void DocumentMarkerController::removeMarkers(
const MarkerRemoverPredicate& shouldRemoveMarker) {
for (auto& nodeMarkers : m_markers) {
const Node& node = *nodeMarkers.key;
if (!node.isTextNode()) // MarkerRemoverPredicate requires a Text node.
continue;
MarkerLists& markers = *nodeMarkers.value;
for (size_t markerListIndex = 0;
markerListIndex < DocumentMarker::MarkerTypeIndexesCount;
++markerListIndex) {
Member<MarkerList>& list = markers[markerListIndex];
if (!list)
continue;
bool removedMarkers = false;
for (size_t j = list->size(); j > 0; --j) {
if (shouldRemoveMarker(*list->at(j - 1),
static_cast<const Text&>(node))) {
list->erase(j - 1);
removedMarkers = true;
}
}
if (removedMarkers &&
markerListIndex == DocumentMarker::TextMatchMarkerIndex)
invalidatePaintForTickmarks(node);
}
}
}
void DocumentMarkerController::removeMarkers(
DocumentMarker::MarkerTypes markerTypes) {
if (!possiblyHasMarkers(markerTypes))
return;
DCHECK(!m_markers.isEmpty());
HeapVector<Member<const Node>> nodesWithMarkers;
copyKeysToVector(m_markers, nodesWithMarkers);
unsigned size = nodesWithMarkers.size();
for (unsigned i = 0; i < size; ++i) {
MarkerMap::iterator iterator = m_markers.find(nodesWithMarkers[i]);
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 nodeCanBeRemoved;
size_t emptyListsCount = 0;
if (markerTypes == DocumentMarker::AllMarkers()) {
needsRepainting = true;
nodeCanBeRemoved = true;
} else {
MarkerLists* markers = iterator->value.get();
for (size_t markerListIndex = 0;
markerListIndex < DocumentMarker::MarkerTypeIndexesCount;
++markerListIndex) {
Member<MarkerList>& list = (*markers)[markerListIndex];
if (!list || list->isEmpty()) {
if (list.get() && list->isEmpty())
list.clear();
++emptyListsCount;
continue;
}
if (markerTypes.contains((*list->begin())->type())) {
list->clear();
list.clear();
++emptyListsCount;
needsRepainting = true;
}
}
nodeCanBeRemoved =
emptyListsCount == DocumentMarker::MarkerTypeIndexesCount;
}
if (needsRepainting) {
const Node& node = *iterator->key;
if (LayoutObject* layoutObject = node.layoutObject()) {
layoutObject->setShouldDoFullPaintInvalidation(
PaintInvalidationDocumentMarkerChange);
}
invalidatePaintForTickmarks(node);
}
if (nodeCanBeRemoved) {
m_markers.erase(iterator);
if (m_markers.isEmpty())
m_possiblyExistingMarkerTypes = 0;
}
}
void DocumentMarkerController::repaintMarkers(
DocumentMarker::MarkerTypes markerTypes) {
if (!possiblyHasMarkers(markerTypes))
return;
DCHECK(!m_markers.isEmpty());
// outer loop: process each markered node in the document
MarkerMap::iterator end = m_markers.end();
for (MarkerMap::iterator i = m_markers.begin(); i != end; ++i) {
const Node* node = i->key;
// inner loop: process each marker in the current node
MarkerLists* markers = i->value.get();
for (size_t markerListIndex = 0;
markerListIndex < DocumentMarker::MarkerTypeIndexesCount;
++markerListIndex) {
Member<MarkerList>& list = (*markers)[markerListIndex];
if (!list || list->isEmpty() ||
!markerTypes.contains((*list->begin())->type()))
continue;
// cause the node to be redrawn
if (LayoutObject* layoutObject = node->layoutObject()) {
layoutObject->setShouldDoFullPaintInvalidation(
PaintInvalidationDocumentMarkerChange);
break;
}
}
}
}
void DocumentMarkerController::shiftMarkers(Node* node,
unsigned startOffset,
int delta) {
if (!possiblyHasMarkers(DocumentMarker::AllMarkers()))
return;
DCHECK(!m_markers.isEmpty());
MarkerLists* markers = m_markers.at(node);
if (!markers)
return;
bool didShiftMarker = false;
for (size_t markerListIndex = 0;
markerListIndex < DocumentMarker::MarkerTypeIndexesCount;
++markerListIndex) {
Member<MarkerList>& list = (*markers)[markerListIndex];
if (!list)
continue;
MarkerList::iterator startPos =
std::lower_bound(list->begin(), list->end(), startOffset, startsAfter);
for (MarkerList::iterator marker = startPos; marker != list->end();
++marker) {
#if DCHECK_IS_ON()
int startOffset = (*marker)->startOffset();
DCHECK_GE(startOffset + delta, 0);
#endif
(*marker)->shiftOffsets(delta);
didShiftMarker = true;
}
}
if (didShiftMarker) {
invalidateRectsForMarkersInNode(*node);
// repaint the affected node
if (node->layoutObject()) {
node->layoutObject()->setShouldDoFullPaintInvalidation(
PaintInvalidationDocumentMarkerChange);
}
}
}
bool DocumentMarkerController::setMarkersActive(const EphemeralRange& range,
bool active) {
if (!possiblyHasMarkers(DocumentMarker::AllMarkers()))
return false;
DCHECK(!m_markers.isEmpty());
Node* const startContainer = range.startPosition().computeContainerNode();
DCHECK(startContainer);
Node* const endContainer = range.endPosition().computeContainerNode();
DCHECK(endContainer);
const unsigned containerStartOffset =
range.startPosition().computeOffsetInContainerNode();
const unsigned containerEndOffset =
range.endPosition().computeOffsetInContainerNode();
bool markerFound = false;
for (Node& node : range.nodes()) {
int startOffset = node == startContainer ? containerStartOffset : 0;
int endOffset = node == endContainer ? containerEndOffset : INT_MAX;
markerFound |= setMarkersActive(&node, startOffset, endOffset, active);
}
return markerFound;
}
bool DocumentMarkerController::setMarkersActive(Node* node,
unsigned startOffset,
unsigned endOffset,
bool active) {
MarkerLists* markers = m_markers.at(node);
if (!markers)
return false;
bool docDirty = false;
Member<MarkerList>& list =
(*markers)[MarkerTypeToMarkerIndex(DocumentMarker::TextMatch)];
if (!list)
return false;
MarkerList::iterator startPos =
std::upper_bound(list->begin(), list->end(), startOffset, endsBefore);
for (MarkerList::iterator marker = startPos; marker != list->end();
++marker) {
// Markers are returned in order, so stop if we are now past the specified
// range.
if ((*marker)->startOffset() >= endOffset)
break;
(*marker)->setIsActiveMatch(active);
docDirty = true;
}
// repaint the affected node
if (docDirty && node->layoutObject()) {
node->layoutObject()->setShouldDoFullPaintInvalidation(
PaintInvalidationDocumentMarkerChange);
}
return docDirty;
}
#ifndef NDEBUG
void DocumentMarkerController::showMarkers() const {
StringBuilder builder;
MarkerMap::const_iterator end = m_markers.end();
for (MarkerMap::const_iterator nodeIterator = m_markers.begin();
nodeIterator != end; ++nodeIterator) {
const Node* node = nodeIterator->key;
builder.append(String::format("%p", node));
MarkerLists* markers = m_markers.at(node);
for (size_t markerListIndex = 0;
markerListIndex < DocumentMarker::MarkerTypeIndexesCount;
++markerListIndex) {
Member<MarkerList>& list = (*markers)[markerListIndex];
for (unsigned markerIndex = 0; list.get() && markerIndex < list->size();
++markerIndex) {
DocumentMarker* marker = list->at(markerIndex).get();
builder.append(" ");
builder.appendNumber(marker->type());
builder.append(":[");
builder.appendNumber(marker->startOffset());
builder.append(":");
builder.appendNumber(marker->endOffset());
builder.append("](");
builder.appendNumber(marker->IsActiveMatch());
builder.append(")");
}
}
builder.append("\n");
}
LOG(INFO) << m_markers.size() << " nodes have markers:\n"
<< builder.toString().utf8().data();
}
#endif
// SynchronousMutationObserver
void DocumentMarkerController::didUpdateCharacterData(CharacterData* node,
unsigned offset,
unsigned oldLength,
unsigned newLength) {
// Shift markers as if we first remove the old text, then insert the new text
removeMarkers(node, offset, oldLength);
shiftMarkers(node, offset + oldLength, 0 - oldLength);
shiftMarkers(node, offset, newLength);
}
} // namespace blink
#ifndef NDEBUG
void showDocumentMarkers(const blink::DocumentMarkerController* controller) {
if (controller)
controller->showMarkers();
}
#endif