This source file includes following definitions.
- m_ordinal
- trace
- m_reset
- doTimeout
- find
- stopFindingAndClearSelection
- scopeStringMatches
- flushCurrentScopingEffort
- finishCurrentScopingEffort
- cancelPendingScopingEffort
- increaseMatchCount
- reportFindInPageSelection
- resetMatchCount
- clearFindMatchesCache
- isActiveMatchFrameValid
- updateFindMatchRects
- activeFindMatchRect
- findMatchRects
- appendFindMatchRects
- selectNearestFindMatch
- nearestFindMatch
- selectFindMatch
- create
- m_findMatchRectsAreValid
- addMarker
- setMarkerActive
- ordinalOfFirstMatchForFrame
- shouldScopeMatches
- scopeStringMatchesSoon
- callScopeStringMatches
- invalidateIfNecessary
- flushCurrentScoping
- setMatchMarkerActive
- decrementFramesScopingCount
- ordinalOfFirstMatch
#include "config.h"
#include "TextFinder.h"
#include "FindInPageCoordinates.h"
#include "WebFindOptions.h"
#include "WebFrameClient.h"
#include "WebFrameImpl.h"
#include "WebViewClient.h"
#include "WebViewImpl.h"
#include "core/dom/DocumentMarker.h"
#include "core/dom/DocumentMarkerController.h"
#include "core/dom/Range.h"
#include "core/dom/shadow/ShadowRoot.h"
#include "core/editing/Editor.h"
#include "core/editing/TextIterator.h"
#include "core/editing/VisibleSelection.h"
#include "core/frame/FrameView.h"
#include "platform/Timer.h"
#include "public/platform/WebVector.h"
#include "wtf/CurrentTime.h"
using namespace WebCore;
namespace blink {
TextFinder::FindMatch::FindMatch(PassRefPtrWillBeRawPtr<Range> range, int ordinal)
    : m_range(range)
    , m_ordinal(ordinal)
{
}
void TextFinder::FindMatch::trace(WebCore::Visitor* visitor)
{
    visitor->trace(m_range);
}
class TextFinder::DeferredScopeStringMatches {
public:
    DeferredScopeStringMatches(TextFinder* textFinder, int identifier, const WebString& searchText, const WebFindOptions& options, bool reset)
        : m_timer(this, &DeferredScopeStringMatches::doTimeout)
        , m_textFinder(textFinder)
        , m_identifier(identifier)
        , m_searchText(searchText)
        , m_options(options)
        , m_reset(reset)
    {
        m_timer.startOneShot(0.0, FROM_HERE);
    }
private:
    void doTimeout(Timer<DeferredScopeStringMatches>*)
    {
        m_textFinder->callScopeStringMatches(this, m_identifier, m_searchText, m_options, m_reset);
    }
    Timer<DeferredScopeStringMatches> m_timer;
    TextFinder* m_textFinder;
    const int m_identifier;
    const WebString m_searchText;
    const WebFindOptions m_options;
    const bool m_reset;
};
bool TextFinder::find(int identifier, const WebString& searchText, const WebFindOptions& options, bool wrapWithinFrame, WebRect* selectionRect)
{
    if (!m_ownerFrame.frame() || !m_ownerFrame.frame()->page())
        return false;
    WebFrameImpl* mainFrameImpl = m_ownerFrame.viewImpl()->mainFrameImpl();
    if (!options.findNext)
        m_ownerFrame.frame()->page()->unmarkAllTextMatches();
    else
        setMarkerActive(m_activeMatch.get(), false);
    if (m_activeMatch && &m_activeMatch->ownerDocument() != m_ownerFrame.frame()->document())
        m_activeMatch = nullptr;
    
    
    
    VisibleSelection selection(m_ownerFrame.frame()->selection().selection());
    bool activeSelection = !selection.isNone();
    if (activeSelection) {
        m_activeMatch = selection.firstRange().get();
        m_ownerFrame.frame()->selection().clear();
    }
    ASSERT(m_ownerFrame.frame() && m_ownerFrame.frame()->view());
    const FindOptions findOptions = (options.forward ? 0 : Backwards)
        | (options.matchCase ? 0 : CaseInsensitive)
        | (wrapWithinFrame ? WrapAround : 0)
        | (options.wordStart ? AtWordStarts : 0)
        | (options.medialCapitalAsWordStart ? TreatMedialCapitalAsWordStart : 0)
        | (options.findNext ? 0 : StartInSelection);
    m_activeMatch = m_ownerFrame.frame()->editor().findStringAndScrollToVisible(searchText, m_activeMatch.get(), findOptions);
    if (!m_activeMatch) {
        
        
        if (!options.findNext)
            clearFindMatchesCache();
        m_ownerFrame.invalidateAll();
        return false;
    }
#if OS(ANDROID)
    m_ownerFrame.viewImpl()->zoomToFindInPageRect(m_ownerFrame.frameView()->contentsToWindow(enclosingIntRect(RenderObject::absoluteBoundingBoxRectForRange(m_activeMatch.get()))));
#endif
    setMarkerActive(m_activeMatch.get(), true);
    WebFrameImpl* oldActiveFrame = mainFrameImpl->ensureTextFinder().m_currentActiveMatchFrame;
    mainFrameImpl->ensureTextFinder().m_currentActiveMatchFrame = &m_ownerFrame;
    
    m_ownerFrame.frame()->document()->setFocusedElement(nullptr);
    if (!options.findNext || activeSelection) {
        
        
        
        m_locatingActiveRect = true;
    } else {
        if (oldActiveFrame != &m_ownerFrame) {
            if (options.forward)
                m_activeMatchIndexInCurrentFrame = 0;
            else
                m_activeMatchIndexInCurrentFrame = m_lastMatchCount - 1;
        } else {
            if (options.forward)
                ++m_activeMatchIndexInCurrentFrame;
            else
                --m_activeMatchIndexInCurrentFrame;
            if (m_activeMatchIndexInCurrentFrame + 1 > m_lastMatchCount)
                m_activeMatchIndexInCurrentFrame = 0;
            if (m_activeMatchIndexInCurrentFrame == -1)
                m_activeMatchIndexInCurrentFrame = m_lastMatchCount - 1;
        }
        if (selectionRect) {
            *selectionRect = m_ownerFrame.frameView()->contentsToWindow(m_activeMatch->boundingBox());
            reportFindInPageSelection(*selectionRect, m_activeMatchIndexInCurrentFrame + 1, identifier);
        }
    }
    return true;
}
void TextFinder::stopFindingAndClearSelection()
{
    cancelPendingScopingEffort();
    
    m_ownerFrame.frame()->document()->markers().removeMarkers(DocumentMarker::TextMatch);
    m_ownerFrame.frame()->editor().setMarkedTextMatchesAreHighlighted(false);
    clearFindMatchesCache();
    
    m_ownerFrame.invalidateAll();
}
void TextFinder::scopeStringMatches(int identifier, const WebString& searchText, const WebFindOptions& options, bool reset)
{
    if (reset) {
        
        
        m_scopingInProgress = true;
        
        
        m_findRequestIdentifier = identifier;
        
        LocalFrame* frame = m_ownerFrame.frame();
        if (frame && frame->page() && frame->editor().markedTextMatchesAreHighlighted())
            frame->page()->unmarkAllTextMatches();
        
        clearFindMatchesCache();
        
        m_lastMatchCount = 0;
        m_nextInvalidateAfter = 0;
        m_resumeScopingFromRange = nullptr;
        
        if (frame && frame->page())
            m_ownerFrame.viewImpl()->mainFrameImpl()->ensureTextFinder().m_framesScopingCount++;
        
        scopeStringMatchesSoon(identifier, searchText, options, false); 
        return;
    }
    if (!shouldScopeMatches(searchText)) {
        
        
        
        finishCurrentScopingEffort(identifier);
        return;
    }
    WebFrameImpl* mainFrameImpl = m_ownerFrame.viewImpl()->mainFrameImpl();
    RefPtrWillBeRawPtr<Range> searchRange(rangeOfContents(m_ownerFrame.frame()->document()));
    Node* originalEndContainer = searchRange->endContainer();
    int originalEndOffset = searchRange->endOffset();
    TrackExceptionState exceptionState, exceptionState2;
    if (m_resumeScopingFromRange) {
        
        
        searchRange->setStart(m_resumeScopingFromRange->startContainer(), m_resumeScopingFromRange->startOffset(exceptionState2) + 1, exceptionState);
        if (exceptionState.hadException() || exceptionState2.hadException()) {
            if (exceptionState2.hadException()) 
                ASSERT_NOT_REACHED();
            return;
        }
    }
    
    
    
    const double maxScopingDuration = 0.1; 
    int matchCount = 0;
    bool timedOut = false;
    double startTime = currentTime();
    do {
        
        
        
        
        
        RefPtrWillBeRawPtr<Range> resultRange(findPlainText(
            searchRange.get(), searchText, options.matchCase ? 0 : CaseInsensitive));
        if (resultRange->collapsed(exceptionState)) {
            if (!resultRange->startContainer()->isInShadowTree())
                break;
            searchRange->setStartAfter(
                resultRange->startContainer()->deprecatedShadowAncestorNode(), exceptionState);
            searchRange->setEnd(originalEndContainer, originalEndOffset, exceptionState);
            continue;
        }
        ++matchCount;
        
        
        
        IntRect resultBounds = resultRange->boundingBox();
        IntRect activeSelectionRect;
        if (m_locatingActiveRect) {
            activeSelectionRect = m_activeMatch.get() ?
                m_activeMatch->boundingBox() : resultBounds;
        }
        
        
        
        
        bool foundActiveMatch = false;
        if (m_locatingActiveRect && (activeSelectionRect == resultBounds)) {
            
            mainFrameImpl->ensureTextFinder().m_currentActiveMatchFrame = &m_ownerFrame;
            foundActiveMatch = true;
            
            m_activeMatchIndexInCurrentFrame = matchCount - 1;
            
            m_locatingActiveRect = false;
            
            reportFindInPageSelection(
                m_ownerFrame.frameView()->contentsToWindow(resultBounds),
                m_activeMatchIndexInCurrentFrame + 1,
                identifier);
        }
        addMarker(resultRange.get(), foundActiveMatch);
        m_findMatchesCache.append(FindMatch(resultRange.get(), m_lastMatchCount + matchCount));
        
        
        
        
        searchRange->setStart(resultRange->endContainer(exceptionState), resultRange->endOffset(exceptionState), exceptionState);
        Node* shadowTreeRoot = searchRange->shadowRoot();
        if (searchRange->collapsed(exceptionState) && shadowTreeRoot)
            searchRange->setEnd(shadowTreeRoot, shadowTreeRoot->countChildren(), exceptionState);
        m_resumeScopingFromRange = resultRange;
        timedOut = (currentTime() - startTime) >= maxScopingDuration;
    } while (!timedOut);
    
    
    m_lastSearchString = searchText;
    if (matchCount > 0) {
        m_ownerFrame.frame()->editor().setMarkedTextMatchesAreHighlighted(true);
        m_lastMatchCount += matchCount;
        
        mainFrameImpl->increaseMatchCount(matchCount, identifier);
    }
    if (timedOut) {
        
        
        
        if (matchCount > 0)
            invalidateIfNecessary();
        
        scopeStringMatchesSoon(
            identifier,
            searchText,
            options,
            false); 
        return; 
    }
    finishCurrentScopingEffort(identifier);
}
void TextFinder::flushCurrentScopingEffort(int identifier)
{
    if (!m_ownerFrame.frame() || !m_ownerFrame.frame()->page())
        return;
    WebFrameImpl* mainFrameImpl = m_ownerFrame.viewImpl()->mainFrameImpl();
    mainFrameImpl->ensureTextFinder().decrementFramesScopingCount(identifier);
}
void TextFinder::finishCurrentScopingEffort(int identifier)
{
    flushCurrentScopingEffort(identifier);
    m_scopingInProgress = false;
    m_lastFindRequestCompletedWithNoMatches = !m_lastMatchCount;
    
    m_ownerFrame.invalidateScrollbar();
}
void TextFinder::cancelPendingScopingEffort()
{
    deleteAllValues(m_deferredScopingWork);
    m_deferredScopingWork.clear();
    m_activeMatchIndexInCurrentFrame = -1;
    
    if (m_scopingInProgress)
        m_lastFindRequestCompletedWithNoMatches = false;
    m_scopingInProgress = false;
}
void TextFinder::increaseMatchCount(int identifier, int count)
{
    if (count)
        ++m_findMatchMarkersVersion;
    m_totalMatchCount += count;
    
    if (m_ownerFrame.client())
        m_ownerFrame.client()->reportFindInPageMatchCount(identifier, m_totalMatchCount, !m_framesScopingCount);
}
void TextFinder::reportFindInPageSelection(const WebRect& selectionRect, int activeMatchOrdinal, int identifier)
{
    
    if (m_ownerFrame.client())
        m_ownerFrame.client()->reportFindInPageSelection(identifier, ordinalOfFirstMatch() + activeMatchOrdinal, selectionRect);
}
void TextFinder::resetMatchCount()
{
    if (m_totalMatchCount > 0)
        ++m_findMatchMarkersVersion;
    m_totalMatchCount = 0;
    m_framesScopingCount = 0;
}
void TextFinder::clearFindMatchesCache()
{
    if (!m_findMatchesCache.isEmpty())
        m_ownerFrame.viewImpl()->mainFrameImpl()->ensureTextFinder().m_findMatchMarkersVersion++;
    m_findMatchesCache.clear();
    m_findMatchRectsAreValid = false;
}
bool TextFinder::isActiveMatchFrameValid() const
{
    WebFrameImpl* mainFrameImpl = m_ownerFrame.viewImpl()->mainFrameImpl();
    WebFrameImpl* activeMatchFrame = mainFrameImpl->activeMatchFrame();
    return activeMatchFrame && activeMatchFrame->activeMatch() && activeMatchFrame->frame()->tree().isDescendantOf(mainFrameImpl->frame());
}
void TextFinder::updateFindMatchRects()
{
    IntSize currentContentsSize = m_ownerFrame.contentsSize();
    if (m_contentsSizeForCurrentFindMatchRects != currentContentsSize) {
        m_contentsSizeForCurrentFindMatchRects = currentContentsSize;
        m_findMatchRectsAreValid = false;
    }
    size_t deadMatches = 0;
    for (Vector<FindMatch>::iterator it = m_findMatchesCache.begin(); it != m_findMatchesCache.end(); ++it) {
        if (!it->m_range->boundaryPointsValid() || !it->m_range->startContainer()->inDocument())
            it->m_rect = FloatRect();
        else if (!m_findMatchRectsAreValid)
            it->m_rect = findInPageRectFromRange(it->m_range.get());
        if (it->m_rect.isEmpty())
            ++deadMatches;
    }
    
    if (deadMatches) {
        WillBeHeapVector<FindMatch> filteredMatches;
        filteredMatches.reserveCapacity(m_findMatchesCache.size() - deadMatches);
        for (Vector<FindMatch>::const_iterator it = m_findMatchesCache.begin(); it != m_findMatchesCache.end(); ++it) {
            if (!it->m_rect.isEmpty())
                filteredMatches.append(*it);
        }
        m_findMatchesCache.swap(filteredMatches);
    }
    
    if (!m_findMatchRectsAreValid)
        for (WebFrame* child = m_ownerFrame.firstChild(); child; child = child->nextSibling())
            toWebFrameImpl(child)->ensureTextFinder().m_findMatchRectsAreValid = false;
    m_findMatchRectsAreValid = true;
}
WebFloatRect TextFinder::activeFindMatchRect()
{
    if (!isActiveMatchFrameValid())
        return WebFloatRect();
    return WebFloatRect(findInPageRectFromRange(m_currentActiveMatchFrame->activeMatch()));
}
void TextFinder::findMatchRects(WebVector<WebFloatRect>& outputRects)
{
    Vector<WebFloatRect> matchRects;
    for (WebFrameImpl* frame = &m_ownerFrame; frame; frame = toWebFrameImpl(frame->traverseNext(false)))
        frame->ensureTextFinder().appendFindMatchRects(matchRects);
    outputRects = matchRects;
}
void TextFinder::appendFindMatchRects(Vector<WebFloatRect>& frameRects)
{
    updateFindMatchRects();
    frameRects.reserveCapacity(frameRects.size() + m_findMatchesCache.size());
    for (Vector<FindMatch>::const_iterator it = m_findMatchesCache.begin(); it != m_findMatchesCache.end(); ++it) {
        ASSERT(!it->m_rect.isEmpty());
        frameRects.append(it->m_rect);
    }
}
int TextFinder::selectNearestFindMatch(const WebFloatPoint& point, WebRect* selectionRect)
{
    TextFinder* bestFinder = 0;
    int indexInBestFrame = -1;
    float distanceInBestFrame = FLT_MAX;
    for (WebFrameImpl* frame = &m_ownerFrame; frame; frame = toWebFrameImpl(frame->traverseNext(false))) {
        float distanceInFrame;
        TextFinder& finder = frame->ensureTextFinder();
        int indexInFrame = finder.nearestFindMatch(point, distanceInFrame);
        if (distanceInFrame < distanceInBestFrame) {
            bestFinder = &finder;
            indexInBestFrame = indexInFrame;
            distanceInBestFrame = distanceInFrame;
        }
    }
    if (indexInBestFrame != -1)
        return bestFinder->selectFindMatch(static_cast<unsigned>(indexInBestFrame), selectionRect);
    return -1;
}
int TextFinder::nearestFindMatch(const FloatPoint& point, float& distanceSquared)
{
    updateFindMatchRects();
    int nearest = -1;
    distanceSquared = FLT_MAX;
    for (size_t i = 0; i < m_findMatchesCache.size(); ++i) {
        ASSERT(!m_findMatchesCache[i].m_rect.isEmpty());
        FloatSize offset = point - m_findMatchesCache[i].m_rect.center();
        float width = offset.width();
        float height = offset.height();
        float currentDistanceSquared = width * width + height * height;
        if (currentDistanceSquared < distanceSquared) {
            nearest = i;
            distanceSquared = currentDistanceSquared;
        }
    }
    return nearest;
}
int TextFinder::selectFindMatch(unsigned index, WebRect* selectionRect)
{
    ASSERT_WITH_SECURITY_IMPLICATION(index < m_findMatchesCache.size());
    RefPtrWillBeRawPtr<Range> range = m_findMatchesCache[index].m_range;
    if (!range->boundaryPointsValid() || !range->startContainer()->inDocument())
        return -1;
    
    TextFinder& mainFrameTextFinder = m_ownerFrame.viewImpl()->mainFrameImpl()->ensureTextFinder();
    WebFrameImpl* activeMatchFrame = mainFrameTextFinder.m_currentActiveMatchFrame;
    if (&m_ownerFrame != activeMatchFrame || !m_activeMatch || !areRangesEqual(m_activeMatch.get(), range.get())) {
        if (isActiveMatchFrameValid())
            activeMatchFrame->ensureTextFinder().setMatchMarkerActive(false);
        m_activeMatchIndexInCurrentFrame = m_findMatchesCache[index].m_ordinal - 1;
        
        mainFrameTextFinder.m_currentActiveMatchFrame = &m_ownerFrame;
        m_ownerFrame.viewImpl()->setFocusedFrame(&m_ownerFrame);
        m_activeMatch = range.release();
        setMarkerActive(m_activeMatch.get(), true);
        
        m_ownerFrame.frame()->selection().clear();
        
        m_ownerFrame.frame()->document()->setFocusedElement(nullptr);
    }
    IntRect activeMatchRect;
    IntRect activeMatchBoundingBox = enclosingIntRect(RenderObject::absoluteBoundingBoxRectForRange(m_activeMatch.get()));
    if (!activeMatchBoundingBox.isEmpty()) {
        if (m_activeMatch->firstNode() && m_activeMatch->firstNode()->renderer()) {
            m_activeMatch->firstNode()->renderer()->scrollRectToVisible(
                activeMatchBoundingBox, ScrollAlignment::alignCenterIfNeeded, ScrollAlignment::alignCenterIfNeeded);
        }
        
        activeMatchRect = m_ownerFrame.frameView()->contentsToWindow(activeMatchBoundingBox);
        m_ownerFrame.viewImpl()->zoomToFindInPageRect(activeMatchRect);
    }
    if (selectionRect)
        *selectionRect = activeMatchRect;
    return ordinalOfFirstMatch() + m_activeMatchIndexInCurrentFrame + 1;
}
PassOwnPtr<TextFinder> TextFinder::create(WebFrameImpl& ownerFrame)
{
    return adoptPtr(new TextFinder(ownerFrame));
}
TextFinder::TextFinder(WebFrameImpl& ownerFrame)
    : m_ownerFrame(ownerFrame)
    , m_currentActiveMatchFrame(0)
    , m_activeMatchIndexInCurrentFrame(-1)
    , m_resumeScopingFromRange(nullptr)
    , m_lastMatchCount(-1)
    , m_totalMatchCount(-1)
    , m_framesScopingCount(-1)
    , m_findRequestIdentifier(-1)
    , m_nextInvalidateAfter(0)
    , m_findMatchMarkersVersion(0)
    , m_locatingActiveRect(false)
    , m_scopingInProgress(false)
    , m_lastFindRequestCompletedWithNoMatches(false)
    , m_findMatchRectsAreValid(false)
{
}
TextFinder::~TextFinder()
{
    cancelPendingScopingEffort();
}
void TextFinder::addMarker(Range* range, bool activeMatch)
{
    m_ownerFrame.frame()->document()->markers().addTextMatchMarker(range, activeMatch);
}
void TextFinder::setMarkerActive(Range* range, bool active)
{
    if (!range || range->collapsed(IGNORE_EXCEPTION))
        return;
    m_ownerFrame.frame()->document()->markers().setMarkersActive(range, active);
}
int TextFinder::ordinalOfFirstMatchForFrame(WebFrameImpl* frame) const
{
    int ordinal = 0;
    WebFrameImpl* mainFrameImpl = m_ownerFrame.viewImpl()->mainFrameImpl();
    
    
    for (WebFrameImpl* it = mainFrameImpl; it != frame; it = toWebFrameImpl(it->traverseNext(true))) {
        TextFinder& finder = it->ensureTextFinder();
        if (finder.m_lastMatchCount > 0)
            ordinal += finder.m_lastMatchCount;
    }
    return ordinal;
}
bool TextFinder::shouldScopeMatches(const String& searchText)
{
    
    
    
    LocalFrame* frame = m_ownerFrame.frame();
    if (!frame || !frame->view() || !frame->page() || !m_ownerFrame.hasVisibleContent())
        return false;
    ASSERT(frame->document() && frame->view());
    
    
    
    if (m_lastFindRequestCompletedWithNoMatches && !m_lastSearchString.isEmpty()) {
        
        String previousSearchPrefix =
            searchText.substring(0, m_lastSearchString.length());
        if (previousSearchPrefix == m_lastSearchString)
            return false; 
    }
    return true;
}
void TextFinder::scopeStringMatchesSoon(int identifier, const WebString& searchText, const WebFindOptions& options, bool reset)
{
    m_deferredScopingWork.append(new DeferredScopeStringMatches(this, identifier, searchText, options, reset));
}
void TextFinder::callScopeStringMatches(DeferredScopeStringMatches* caller, int identifier, const WebString& searchText, const WebFindOptions& options, bool reset)
{
    m_deferredScopingWork.remove(m_deferredScopingWork.find(caller));
    scopeStringMatches(identifier, searchText, options, reset);
    
    delete caller;
}
void TextFinder::invalidateIfNecessary()
{
    if (m_lastMatchCount <= m_nextInvalidateAfter)
        return;
    
    
    
    
    
    
    
    static const int startSlowingDownAfter = 500;
    static const int slowdown = 750;
    int i = m_lastMatchCount / startSlowingDownAfter;
    m_nextInvalidateAfter += i * slowdown;
    m_ownerFrame.invalidateScrollbar();
}
void TextFinder::flushCurrentScoping()
{
    flushCurrentScopingEffort(m_findRequestIdentifier);
}
void TextFinder::setMatchMarkerActive(bool active)
{
    setMarkerActive(m_activeMatch.get(), active);
}
void TextFinder::decrementFramesScopingCount(int identifier)
{
    
    
    --m_framesScopingCount;
    
    
    if (!m_framesScopingCount)
        m_ownerFrame.increaseMatchCount(0, identifier);
}
int TextFinder::ordinalOfFirstMatch() const
{
    return ordinalOfFirstMatchForFrame(&m_ownerFrame);
}
}