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