Home | History | Annotate | Download | only in web
      1 /*
      2  * Copyright (C) 2009 Google Inc. All rights reserved.
      3  *
      4  * Redistribution and use in source and binary forms, with or without
      5  * modification, are permitted provided that the following conditions are
      6  * met:
      7  *
      8  *     * Redistributions of source code must retain the above copyright
      9  * notice, this list of conditions and the following disclaimer.
     10  *     * Redistributions in binary form must reproduce the above
     11  * copyright notice, this list of conditions and the following disclaimer
     12  * in the documentation and/or other materials provided with the
     13  * distribution.
     14  *     * Neither the name of Google Inc. nor the names of its
     15  * contributors may be used to endorse or promote products derived from
     16  * this software without specific prior written permission.
     17  *
     18  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
     19  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
     20  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
     21  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
     22  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
     23  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
     24  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
     25  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
     26  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
     27  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
     28  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     29  */
     30 
     31 
     32 #include "config.h"
     33 #include "web/TextFinder.h"
     34 
     35 #include "core/dom/DocumentMarker.h"
     36 #include "core/dom/DocumentMarkerController.h"
     37 #include "core/dom/Range.h"
     38 #include "core/dom/shadow/ShadowRoot.h"
     39 #include "core/editing/Editor.h"
     40 #include "core/editing/TextIterator.h"
     41 #include "core/editing/VisibleSelection.h"
     42 #include "core/frame/FrameView.h"
     43 #include "platform/Timer.h"
     44 #include "public/platform/WebVector.h"
     45 #include "public/web/WebFindOptions.h"
     46 #include "public/web/WebFrameClient.h"
     47 #include "public/web/WebViewClient.h"
     48 #include "web/FindInPageCoordinates.h"
     49 #include "web/WebLocalFrameImpl.h"
     50 #include "web/WebViewImpl.h"
     51 #include "wtf/CurrentTime.h"
     52 
     53 using namespace WebCore;
     54 
     55 namespace blink {
     56 
     57 TextFinder::FindMatch::FindMatch(PassRefPtrWillBeRawPtr<Range> range, int ordinal)
     58     : m_range(range)
     59     , m_ordinal(ordinal)
     60 {
     61 }
     62 
     63 void TextFinder::FindMatch::trace(WebCore::Visitor* visitor)
     64 {
     65     visitor->trace(m_range);
     66 }
     67 
     68 class TextFinder::DeferredScopeStringMatches {
     69 public:
     70     DeferredScopeStringMatches(TextFinder* textFinder, int identifier, const WebString& searchText, const WebFindOptions& options, bool reset)
     71         : m_timer(this, &DeferredScopeStringMatches::doTimeout)
     72         , m_textFinder(textFinder)
     73         , m_identifier(identifier)
     74         , m_searchText(searchText)
     75         , m_options(options)
     76         , m_reset(reset)
     77     {
     78         m_timer.startOneShot(0.0, FROM_HERE);
     79     }
     80 
     81 private:
     82     void doTimeout(Timer<DeferredScopeStringMatches>*)
     83     {
     84         m_textFinder->callScopeStringMatches(this, m_identifier, m_searchText, m_options, m_reset);
     85     }
     86 
     87     Timer<DeferredScopeStringMatches> m_timer;
     88     TextFinder* m_textFinder;
     89     const int m_identifier;
     90     const WebString m_searchText;
     91     const WebFindOptions m_options;
     92     const bool m_reset;
     93 };
     94 
     95 bool TextFinder::find(int identifier, const WebString& searchText, const WebFindOptions& options, bool wrapWithinFrame, WebRect* selectionRect)
     96 {
     97     if (!m_ownerFrame.frame() || !m_ownerFrame.frame()->page())
     98         return false;
     99 
    100     WebLocalFrameImpl* mainFrameImpl = m_ownerFrame.viewImpl()->mainFrameImpl();
    101 
    102     if (!options.findNext)
    103         m_ownerFrame.frame()->page()->unmarkAllTextMatches();
    104     else
    105         setMarkerActive(m_activeMatch.get(), false);
    106 
    107     if (m_activeMatch && &m_activeMatch->ownerDocument() != m_ownerFrame.frame()->document())
    108         m_activeMatch = nullptr;
    109 
    110     // If the user has selected something since the last Find operation we want
    111     // to start from there. Otherwise, we start searching from where the last Find
    112     // operation left off (either a Find or a FindNext operation).
    113     VisibleSelection selection(m_ownerFrame.frame()->selection().selection());
    114     bool activeSelection = !selection.isNone();
    115     if (activeSelection) {
    116         m_activeMatch = selection.firstRange().get();
    117         m_ownerFrame.frame()->selection().clear();
    118     }
    119 
    120     ASSERT(m_ownerFrame.frame() && m_ownerFrame.frame()->view());
    121     const FindOptions findOptions = (options.forward ? 0 : Backwards)
    122         | (options.matchCase ? 0 : CaseInsensitive)
    123         | (wrapWithinFrame ? WrapAround : 0)
    124         | (options.wordStart ? AtWordStarts : 0)
    125         | (options.medialCapitalAsWordStart ? TreatMedialCapitalAsWordStart : 0)
    126         | (options.findNext ? 0 : StartInSelection);
    127     m_activeMatch = m_ownerFrame.frame()->editor().findStringAndScrollToVisible(searchText, m_activeMatch.get(), findOptions);
    128 
    129     if (!m_activeMatch) {
    130         // If we're finding next the next active match might not be in the current frame.
    131         // In this case we don't want to clear the matches cache.
    132         if (!options.findNext)
    133             clearFindMatchesCache();
    134 
    135         m_ownerFrame.invalidateAll();
    136         return false;
    137     }
    138 
    139 #if OS(ANDROID)
    140     m_ownerFrame.viewImpl()->zoomToFindInPageRect(m_ownerFrame.frameView()->contentsToWindow(enclosingIntRect(RenderObject::absoluteBoundingBoxRectForRange(m_activeMatch.get()))));
    141 #endif
    142 
    143     setMarkerActive(m_activeMatch.get(), true);
    144     WebLocalFrameImpl* oldActiveFrame = mainFrameImpl->ensureTextFinder().m_currentActiveMatchFrame;
    145     mainFrameImpl->ensureTextFinder().m_currentActiveMatchFrame = &m_ownerFrame;
    146 
    147     // Make sure no node is focused. See http://crbug.com/38700.
    148     m_ownerFrame.frame()->document()->setFocusedElement(nullptr);
    149 
    150     if (!options.findNext || activeSelection) {
    151         // This is either a Find operation or a Find-next from a new start point
    152         // due to a selection, so we set the flag to ask the scoping effort
    153         // to find the active rect for us and report it back to the UI.
    154         m_locatingActiveRect = true;
    155     } else {
    156         if (oldActiveFrame != &m_ownerFrame) {
    157             if (options.forward)
    158                 m_activeMatchIndexInCurrentFrame = 0;
    159             else
    160                 m_activeMatchIndexInCurrentFrame = m_lastMatchCount - 1;
    161         } else {
    162             if (options.forward)
    163                 ++m_activeMatchIndexInCurrentFrame;
    164             else
    165                 --m_activeMatchIndexInCurrentFrame;
    166 
    167             if (m_activeMatchIndexInCurrentFrame + 1 > m_lastMatchCount)
    168                 m_activeMatchIndexInCurrentFrame = 0;
    169             if (m_activeMatchIndexInCurrentFrame == -1)
    170                 m_activeMatchIndexInCurrentFrame = m_lastMatchCount - 1;
    171         }
    172         if (selectionRect) {
    173             *selectionRect = m_ownerFrame.frameView()->contentsToWindow(m_activeMatch->boundingBox());
    174             reportFindInPageSelection(*selectionRect, m_activeMatchIndexInCurrentFrame + 1, identifier);
    175         }
    176     }
    177 
    178     return true;
    179 }
    180 
    181 void TextFinder::stopFindingAndClearSelection()
    182 {
    183     cancelPendingScopingEffort();
    184 
    185     // Remove all markers for matches found and turn off the highlighting.
    186     m_ownerFrame.frame()->document()->markers().removeMarkers(DocumentMarker::TextMatch);
    187     m_ownerFrame.frame()->editor().setMarkedTextMatchesAreHighlighted(false);
    188     clearFindMatchesCache();
    189 
    190     // Let the frame know that we don't want tickmarks or highlighting anymore.
    191     m_ownerFrame.invalidateAll();
    192 }
    193 
    194 void TextFinder::scopeStringMatches(int identifier, const WebString& searchText, const WebFindOptions& options, bool reset)
    195 {
    196     if (reset) {
    197         // This is a brand new search, so we need to reset everything.
    198         // Scoping is just about to begin.
    199         m_scopingInProgress = true;
    200 
    201         // Need to keep the current identifier locally in order to finish the
    202         // request in case the frame is detached during the process.
    203         m_findRequestIdentifier = identifier;
    204 
    205         // Clear highlighting for this frame.
    206         LocalFrame* frame = m_ownerFrame.frame();
    207         if (frame && frame->page() && frame->editor().markedTextMatchesAreHighlighted())
    208             frame->page()->unmarkAllTextMatches();
    209 
    210         // Clear the tickmarks and results cache.
    211         clearFindMatchesCache();
    212 
    213         // Clear the counters from last operation.
    214         m_lastMatchCount = 0;
    215         m_nextInvalidateAfter = 0;
    216         m_resumeScopingFromRange = nullptr;
    217 
    218         // The view might be null on detached frames.
    219         if (frame && frame->page())
    220             m_ownerFrame.viewImpl()->mainFrameImpl()->ensureTextFinder().m_framesScopingCount++;
    221 
    222         // Now, defer scoping until later to allow find operation to finish quickly.
    223         scopeStringMatchesSoon(identifier, searchText, options, false); // false means just reset, so don't do it again.
    224         return;
    225     }
    226 
    227     if (!shouldScopeMatches(searchText)) {
    228         // Note that we want to defer the final update when resetting even if shouldScopeMatches returns false.
    229         // This is done in order to prevent sending a final message based only on the results of the first frame
    230         // since m_framesScopingCount would be 0 as other frames have yet to reset.
    231         finishCurrentScopingEffort(identifier);
    232         return;
    233     }
    234 
    235     WebLocalFrameImpl* mainFrameImpl = m_ownerFrame.viewImpl()->mainFrameImpl();
    236     Position searchStart = firstPositionInNode(m_ownerFrame.frame()->document());
    237     Position searchEnd = lastPositionInNode(m_ownerFrame.frame()->document());
    238     ASSERT(searchStart.document() == searchEnd.document());
    239 
    240     if (m_resumeScopingFromRange) {
    241         // This is a continuation of a scoping operation that timed out and didn't
    242         // complete last time around, so we should start from where we left off.
    243         ASSERT(m_resumeScopingFromRange->collapsed());
    244         searchStart = m_resumeScopingFromRange->startPosition().next();
    245         if (searchStart.document() != searchEnd.document())
    246             return;
    247     }
    248 
    249     // This timeout controls how long we scope before releasing control. This
    250     // value does not prevent us from running for longer than this, but it is
    251     // periodically checked to see if we have exceeded our allocated time.
    252     const double maxScopingDuration = 0.1; // seconds
    253 
    254     int matchCount = 0;
    255     bool timedOut = false;
    256     double startTime = currentTime();
    257     do {
    258         // Find next occurrence of the search string.
    259         // FIXME: (http://crbug.com/6818) This WebKit operation may run for longer
    260         // than the timeout value, and is not interruptible as it is currently
    261         // written. We may need to rewrite it with interruptibility in mind, or
    262         // find an alternative.
    263         Position resultStart;
    264         Position resultEnd;
    265         findPlainText(searchStart, searchEnd, searchText, options.matchCase ? 0 : CaseInsensitive, resultStart, resultEnd);
    266         if (resultStart == resultEnd) {
    267             // Not found.
    268             break;
    269         }
    270 
    271         RefPtrWillBeRawPtr<Range> resultRange = Range::create(*resultStart.document(), resultStart, resultEnd);
    272         if (resultRange->collapsed()) {
    273             // resultRange will be collapsed if the matched text spans over multiple TreeScopes.
    274             // FIXME: Show such matches to users.
    275             searchStart = resultStart.next();
    276             continue;
    277         }
    278 
    279         ++matchCount;
    280 
    281         // Catch a special case where Find found something but doesn't know what
    282         // the bounding box for it is. In this case we set the first match we find
    283         // as the active rect.
    284         IntRect resultBounds = resultRange->boundingBox();
    285         IntRect activeSelectionRect;
    286         if (m_locatingActiveRect) {
    287             activeSelectionRect = m_activeMatch.get() ?
    288                 m_activeMatch->boundingBox() : resultBounds;
    289         }
    290 
    291         // If the Find function found a match it will have stored where the
    292         // match was found in m_activeSelectionRect on the current frame. If we
    293         // find this rect during scoping it means we have found the active
    294         // tickmark.
    295         bool foundActiveMatch = false;
    296         if (m_locatingActiveRect && (activeSelectionRect == resultBounds)) {
    297             // We have found the active tickmark frame.
    298             mainFrameImpl->ensureTextFinder().m_currentActiveMatchFrame = &m_ownerFrame;
    299             foundActiveMatch = true;
    300             // We also know which tickmark is active now.
    301             m_activeMatchIndexInCurrentFrame = matchCount - 1;
    302             // To stop looking for the active tickmark, we set this flag.
    303             m_locatingActiveRect = false;
    304 
    305             // Notify browser of new location for the selected rectangle.
    306             reportFindInPageSelection(
    307                 m_ownerFrame.frameView()->contentsToWindow(resultBounds),
    308                 m_activeMatchIndexInCurrentFrame + 1,
    309                 identifier);
    310         }
    311 
    312         addMarker(resultRange.get(), foundActiveMatch);
    313 
    314         m_findMatchesCache.append(FindMatch(resultRange.get(), m_lastMatchCount + matchCount));
    315 
    316         // Set the new start for the search range to be the end of the previous
    317         // result range. There is no need to use a VisiblePosition here,
    318         // since findPlainText will use a TextIterator to go over the visible
    319         // text nodes.
    320         searchStart = resultStart.next();
    321 
    322         m_resumeScopingFromRange = Range::create(*resultStart.document(), resultStart, resultStart);
    323         timedOut = (currentTime() - startTime) >= maxScopingDuration;
    324     } while (!timedOut);
    325 
    326     // Remember what we search for last time, so we can skip searching if more
    327     // letters are added to the search string (and last outcome was 0).
    328     m_lastSearchString = searchText;
    329 
    330     if (matchCount > 0) {
    331         m_ownerFrame.frame()->editor().setMarkedTextMatchesAreHighlighted(true);
    332 
    333         m_lastMatchCount += matchCount;
    334 
    335         // Let the mainframe know how much we found during this pass.
    336         mainFrameImpl->increaseMatchCount(matchCount, identifier);
    337     }
    338 
    339     if (timedOut) {
    340         // If we found anything during this pass, we should redraw. However, we
    341         // don't want to spam too much if the page is extremely long, so if we
    342         // reach a certain point we start throttling the redraw requests.
    343         if (matchCount > 0)
    344             invalidateIfNecessary();
    345 
    346         // Scoping effort ran out of time, lets ask for another time-slice.
    347         scopeStringMatchesSoon(
    348             identifier,
    349             searchText,
    350             options,
    351             false); // don't reset.
    352         return; // Done for now, resume work later.
    353     }
    354 
    355     finishCurrentScopingEffort(identifier);
    356 }
    357 
    358 void TextFinder::flushCurrentScopingEffort(int identifier)
    359 {
    360     if (!m_ownerFrame.frame() || !m_ownerFrame.frame()->page())
    361         return;
    362 
    363     WebLocalFrameImpl* mainFrameImpl = m_ownerFrame.viewImpl()->mainFrameImpl();
    364     mainFrameImpl->ensureTextFinder().decrementFramesScopingCount(identifier);
    365 }
    366 
    367 void TextFinder::finishCurrentScopingEffort(int identifier)
    368 {
    369     flushCurrentScopingEffort(identifier);
    370 
    371     m_scopingInProgress = false;
    372     m_lastFindRequestCompletedWithNoMatches = !m_lastMatchCount;
    373 
    374     // This frame is done, so show any scrollbar tickmarks we haven't drawn yet.
    375     m_ownerFrame.invalidateScrollbar();
    376 }
    377 
    378 void TextFinder::cancelPendingScopingEffort()
    379 {
    380     deleteAllValues(m_deferredScopingWork);
    381     m_deferredScopingWork.clear();
    382 
    383     m_activeMatchIndexInCurrentFrame = -1;
    384 
    385     // Last request didn't complete.
    386     if (m_scopingInProgress)
    387         m_lastFindRequestCompletedWithNoMatches = false;
    388 
    389     m_scopingInProgress = false;
    390 }
    391 
    392 void TextFinder::increaseMatchCount(int identifier, int count)
    393 {
    394     if (count)
    395         ++m_findMatchMarkersVersion;
    396 
    397     m_totalMatchCount += count;
    398 
    399     // Update the UI with the latest findings.
    400     if (m_ownerFrame.client())
    401         m_ownerFrame.client()->reportFindInPageMatchCount(identifier, m_totalMatchCount, !m_framesScopingCount);
    402 }
    403 
    404 void TextFinder::reportFindInPageSelection(const WebRect& selectionRect, int activeMatchOrdinal, int identifier)
    405 {
    406     // Update the UI with the latest selection rect.
    407     if (m_ownerFrame.client())
    408         m_ownerFrame.client()->reportFindInPageSelection(identifier, ordinalOfFirstMatch() + activeMatchOrdinal, selectionRect);
    409 }
    410 
    411 void TextFinder::resetMatchCount()
    412 {
    413     if (m_totalMatchCount > 0)
    414         ++m_findMatchMarkersVersion;
    415 
    416     m_totalMatchCount = 0;
    417     m_framesScopingCount = 0;
    418 }
    419 
    420 void TextFinder::clearFindMatchesCache()
    421 {
    422     if (!m_findMatchesCache.isEmpty())
    423         m_ownerFrame.viewImpl()->mainFrameImpl()->ensureTextFinder().m_findMatchMarkersVersion++;
    424 
    425     m_findMatchesCache.clear();
    426     m_findMatchRectsAreValid = false;
    427 }
    428 
    429 bool TextFinder::isActiveMatchFrameValid() const
    430 {
    431     WebLocalFrameImpl* mainFrameImpl = m_ownerFrame.viewImpl()->mainFrameImpl();
    432     WebLocalFrameImpl* activeMatchFrame = mainFrameImpl->activeMatchFrame();
    433     return activeMatchFrame && activeMatchFrame->activeMatch() && activeMatchFrame->frame()->tree().isDescendantOf(mainFrameImpl->frame());
    434 }
    435 
    436 void TextFinder::updateFindMatchRects()
    437 {
    438     IntSize currentContentsSize = m_ownerFrame.contentsSize();
    439     if (m_contentsSizeForCurrentFindMatchRects != currentContentsSize) {
    440         m_contentsSizeForCurrentFindMatchRects = currentContentsSize;
    441         m_findMatchRectsAreValid = false;
    442     }
    443 
    444     size_t deadMatches = 0;
    445     for (Vector<FindMatch>::iterator it = m_findMatchesCache.begin(); it != m_findMatchesCache.end(); ++it) {
    446         if (!it->m_range->boundaryPointsValid() || !it->m_range->startContainer()->inDocument())
    447             it->m_rect = FloatRect();
    448         else if (!m_findMatchRectsAreValid)
    449             it->m_rect = findInPageRectFromRange(it->m_range.get());
    450 
    451         if (it->m_rect.isEmpty())
    452             ++deadMatches;
    453     }
    454 
    455     // Remove any invalid matches from the cache.
    456     if (deadMatches) {
    457         WillBeHeapVector<FindMatch> filteredMatches;
    458         filteredMatches.reserveCapacity(m_findMatchesCache.size() - deadMatches);
    459 
    460         for (Vector<FindMatch>::const_iterator it = m_findMatchesCache.begin(); it != m_findMatchesCache.end(); ++it) {
    461             if (!it->m_rect.isEmpty())
    462                 filteredMatches.append(*it);
    463         }
    464 
    465         m_findMatchesCache.swap(filteredMatches);
    466     }
    467 
    468     // Invalidate the rects in child frames. Will be updated later during traversal.
    469     if (!m_findMatchRectsAreValid)
    470         for (WebFrame* child = m_ownerFrame.firstChild(); child; child = child->nextSibling())
    471             toWebLocalFrameImpl(child)->ensureTextFinder().m_findMatchRectsAreValid = false;
    472 
    473     m_findMatchRectsAreValid = true;
    474 }
    475 
    476 WebFloatRect TextFinder::activeFindMatchRect()
    477 {
    478     if (!isActiveMatchFrameValid())
    479         return WebFloatRect();
    480 
    481     return WebFloatRect(findInPageRectFromRange(m_currentActiveMatchFrame->activeMatch()));
    482 }
    483 
    484 void TextFinder::findMatchRects(WebVector<WebFloatRect>& outputRects)
    485 {
    486     Vector<WebFloatRect> matchRects;
    487     for (WebLocalFrameImpl* frame = &m_ownerFrame; frame; frame = toWebLocalFrameImpl(frame->traverseNext(false)))
    488         frame->ensureTextFinder().appendFindMatchRects(matchRects);
    489 
    490     outputRects = matchRects;
    491 }
    492 
    493 void TextFinder::appendFindMatchRects(Vector<WebFloatRect>& frameRects)
    494 {
    495     updateFindMatchRects();
    496     frameRects.reserveCapacity(frameRects.size() + m_findMatchesCache.size());
    497     for (Vector<FindMatch>::const_iterator it = m_findMatchesCache.begin(); it != m_findMatchesCache.end(); ++it) {
    498         ASSERT(!it->m_rect.isEmpty());
    499         frameRects.append(it->m_rect);
    500     }
    501 }
    502 
    503 int TextFinder::selectNearestFindMatch(const WebFloatPoint& point, WebRect* selectionRect)
    504 {
    505     TextFinder* bestFinder = 0;
    506     int indexInBestFrame = -1;
    507     float distanceInBestFrame = FLT_MAX;
    508 
    509     for (WebLocalFrameImpl* frame = &m_ownerFrame; frame; frame = toWebLocalFrameImpl(frame->traverseNext(false))) {
    510         float distanceInFrame;
    511         TextFinder& finder = frame->ensureTextFinder();
    512         int indexInFrame = finder.nearestFindMatch(point, distanceInFrame);
    513         if (distanceInFrame < distanceInBestFrame) {
    514             bestFinder = &finder;
    515             indexInBestFrame = indexInFrame;
    516             distanceInBestFrame = distanceInFrame;
    517         }
    518     }
    519 
    520     if (indexInBestFrame != -1)
    521         return bestFinder->selectFindMatch(static_cast<unsigned>(indexInBestFrame), selectionRect);
    522 
    523     return -1;
    524 }
    525 
    526 int TextFinder::nearestFindMatch(const FloatPoint& point, float& distanceSquared)
    527 {
    528     updateFindMatchRects();
    529 
    530     int nearest = -1;
    531     distanceSquared = FLT_MAX;
    532     for (size_t i = 0; i < m_findMatchesCache.size(); ++i) {
    533         ASSERT(!m_findMatchesCache[i].m_rect.isEmpty());
    534         FloatSize offset = point - m_findMatchesCache[i].m_rect.center();
    535         float width = offset.width();
    536         float height = offset.height();
    537         float currentDistanceSquared = width * width + height * height;
    538         if (currentDistanceSquared < distanceSquared) {
    539             nearest = i;
    540             distanceSquared = currentDistanceSquared;
    541         }
    542     }
    543     return nearest;
    544 }
    545 
    546 int TextFinder::selectFindMatch(unsigned index, WebRect* selectionRect)
    547 {
    548     ASSERT_WITH_SECURITY_IMPLICATION(index < m_findMatchesCache.size());
    549 
    550     RefPtrWillBeRawPtr<Range> range = m_findMatchesCache[index].m_range;
    551     if (!range->boundaryPointsValid() || !range->startContainer()->inDocument())
    552         return -1;
    553 
    554     // Check if the match is already selected.
    555     TextFinder& mainFrameTextFinder = m_ownerFrame.viewImpl()->mainFrameImpl()->ensureTextFinder();
    556     WebLocalFrameImpl* activeMatchFrame = mainFrameTextFinder.m_currentActiveMatchFrame;
    557     if (&m_ownerFrame != activeMatchFrame || !m_activeMatch || !areRangesEqual(m_activeMatch.get(), range.get())) {
    558         if (isActiveMatchFrameValid())
    559             activeMatchFrame->ensureTextFinder().setMatchMarkerActive(false);
    560 
    561         m_activeMatchIndexInCurrentFrame = m_findMatchesCache[index].m_ordinal - 1;
    562 
    563         // Set this frame as the active frame (the one with the active highlight).
    564         mainFrameTextFinder.m_currentActiveMatchFrame = &m_ownerFrame;
    565         m_ownerFrame.viewImpl()->setFocusedFrame(&m_ownerFrame);
    566 
    567         m_activeMatch = range.release();
    568         setMarkerActive(m_activeMatch.get(), true);
    569 
    570         // Clear any user selection, to make sure Find Next continues on from the match we just activated.
    571         m_ownerFrame.frame()->selection().clear();
    572 
    573         // Make sure no node is focused. See http://crbug.com/38700.
    574         m_ownerFrame.frame()->document()->setFocusedElement(nullptr);
    575     }
    576 
    577     IntRect activeMatchRect;
    578     IntRect activeMatchBoundingBox = enclosingIntRect(RenderObject::absoluteBoundingBoxRectForRange(m_activeMatch.get()));
    579 
    580     if (!activeMatchBoundingBox.isEmpty()) {
    581         if (m_activeMatch->firstNode() && m_activeMatch->firstNode()->renderer()) {
    582             m_activeMatch->firstNode()->renderer()->scrollRectToVisible(
    583                 activeMatchBoundingBox, ScrollAlignment::alignCenterIfNeeded, ScrollAlignment::alignCenterIfNeeded);
    584         }
    585 
    586         // Zoom to the active match.
    587         activeMatchRect = m_ownerFrame.frameView()->contentsToWindow(activeMatchBoundingBox);
    588         m_ownerFrame.viewImpl()->zoomToFindInPageRect(activeMatchRect);
    589     }
    590 
    591     if (selectionRect)
    592         *selectionRect = activeMatchRect;
    593 
    594     return ordinalOfFirstMatch() + m_activeMatchIndexInCurrentFrame + 1;
    595 }
    596 
    597 PassOwnPtr<TextFinder> TextFinder::create(WebLocalFrameImpl& ownerFrame)
    598 {
    599     return adoptPtr(new TextFinder(ownerFrame));
    600 }
    601 
    602 TextFinder::TextFinder(WebLocalFrameImpl& ownerFrame)
    603     : m_ownerFrame(ownerFrame)
    604     , m_currentActiveMatchFrame(0)
    605     , m_activeMatchIndexInCurrentFrame(-1)
    606     , m_resumeScopingFromRange(nullptr)
    607     , m_lastMatchCount(-1)
    608     , m_totalMatchCount(-1)
    609     , m_framesScopingCount(-1)
    610     , m_findRequestIdentifier(-1)
    611     , m_nextInvalidateAfter(0)
    612     , m_findMatchMarkersVersion(0)
    613     , m_locatingActiveRect(false)
    614     , m_scopingInProgress(false)
    615     , m_lastFindRequestCompletedWithNoMatches(false)
    616     , m_findMatchRectsAreValid(false)
    617 {
    618 }
    619 
    620 TextFinder::~TextFinder()
    621 {
    622     cancelPendingScopingEffort();
    623 }
    624 
    625 void TextFinder::addMarker(Range* range, bool activeMatch)
    626 {
    627     m_ownerFrame.frame()->document()->markers().addTextMatchMarker(range, activeMatch);
    628 }
    629 
    630 void TextFinder::setMarkerActive(Range* range, bool active)
    631 {
    632     if (!range || range->collapsed())
    633         return;
    634     m_ownerFrame.frame()->document()->markers().setMarkersActive(range, active);
    635 }
    636 
    637 int TextFinder::ordinalOfFirstMatchForFrame(WebLocalFrameImpl* frame) const
    638 {
    639     int ordinal = 0;
    640     WebLocalFrameImpl* mainFrameImpl = m_ownerFrame.viewImpl()->mainFrameImpl();
    641     // Iterate from the main frame up to (but not including) |frame| and
    642     // add up the number of matches found so far.
    643     for (WebLocalFrameImpl* it = mainFrameImpl; it != frame; it = toWebLocalFrameImpl(it->traverseNext(true))) {
    644         TextFinder& finder = it->ensureTextFinder();
    645         if (finder.m_lastMatchCount > 0)
    646             ordinal += finder.m_lastMatchCount;
    647     }
    648     return ordinal;
    649 }
    650 
    651 bool TextFinder::shouldScopeMatches(const String& searchText)
    652 {
    653     // Don't scope if we can't find a frame or a view.
    654     // The user may have closed the tab/application, so abort.
    655     // Also ignore detached frames, as many find operations report to the main frame.
    656     LocalFrame* frame = m_ownerFrame.frame();
    657     if (!frame || !frame->view() || !frame->page() || !m_ownerFrame.hasVisibleContent())
    658         return false;
    659 
    660     ASSERT(frame->document() && frame->view());
    661 
    662     // If the frame completed the scoping operation and found 0 matches the last
    663     // time it was searched, then we don't have to search it again if the user is
    664     // just adding to the search string or sending the same search string again.
    665     if (m_lastFindRequestCompletedWithNoMatches && !m_lastSearchString.isEmpty()) {
    666         // Check to see if the search string prefixes match.
    667         String previousSearchPrefix =
    668             searchText.substring(0, m_lastSearchString.length());
    669 
    670         if (previousSearchPrefix == m_lastSearchString)
    671             return false; // Don't search this frame, it will be fruitless.
    672     }
    673 
    674     return true;
    675 }
    676 
    677 void TextFinder::scopeStringMatchesSoon(int identifier, const WebString& searchText, const WebFindOptions& options, bool reset)
    678 {
    679     m_deferredScopingWork.append(new DeferredScopeStringMatches(this, identifier, searchText, options, reset));
    680 }
    681 
    682 void TextFinder::callScopeStringMatches(DeferredScopeStringMatches* caller, int identifier, const WebString& searchText, const WebFindOptions& options, bool reset)
    683 {
    684     m_deferredScopingWork.remove(m_deferredScopingWork.find(caller));
    685     scopeStringMatches(identifier, searchText, options, reset);
    686 
    687     // This needs to happen last since searchText is passed by reference.
    688     delete caller;
    689 }
    690 
    691 void TextFinder::invalidateIfNecessary()
    692 {
    693     if (m_lastMatchCount <= m_nextInvalidateAfter)
    694         return;
    695 
    696     // FIXME: (http://b/1088165) Optimize the drawing of the tickmarks and
    697     // remove this. This calculation sets a milestone for when next to
    698     // invalidate the scrollbar and the content area. We do this so that we
    699     // don't spend too much time drawing the scrollbar over and over again.
    700     // Basically, up until the first 500 matches there is no throttle.
    701     // After the first 500 matches, we set set the milestone further and
    702     // further out (750, 1125, 1688, 2K, 3K).
    703     static const int startSlowingDownAfter = 500;
    704     static const int slowdown = 750;
    705 
    706     int i = m_lastMatchCount / startSlowingDownAfter;
    707     m_nextInvalidateAfter += i * slowdown;
    708     m_ownerFrame.invalidateScrollbar();
    709 }
    710 
    711 void TextFinder::flushCurrentScoping()
    712 {
    713     flushCurrentScopingEffort(m_findRequestIdentifier);
    714 }
    715 
    716 void TextFinder::setMatchMarkerActive(bool active)
    717 {
    718     setMarkerActive(m_activeMatch.get(), active);
    719 }
    720 
    721 void TextFinder::decrementFramesScopingCount(int identifier)
    722 {
    723     // This frame has no further scoping left, so it is done. Other frames might,
    724     // of course, continue to scope matches.
    725     --m_framesScopingCount;
    726 
    727     // If this is the last frame to finish scoping we need to trigger the final
    728     // update to be sent.
    729     if (!m_framesScopingCount)
    730         m_ownerFrame.increaseMatchCount(0, identifier);
    731 }
    732 
    733 int TextFinder::ordinalOfFirstMatch() const
    734 {
    735     return ordinalOfFirstMatchForFrame(&m_ownerFrame);
    736 }
    737 
    738 } // namespace blink
    739