2 * Copyright (C) 2009 Google Inc. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions are
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
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.
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.
33 #include "web/TextFinder.h"
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"
53 using namespace WebCore;
57 TextFinder::FindMatch::FindMatch(PassRefPtrWillBeRawPtr<Range> range, int ordinal)
63 void TextFinder::FindMatch::trace(WebCore::Visitor* visitor)
65 visitor->trace(m_range);
68 class TextFinder::DeferredScopeStringMatches {
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)
78 m_timer.startOneShot(0.0, FROM_HERE);
82 void doTimeout(Timer<DeferredScopeStringMatches>*)
84 m_textFinder->callScopeStringMatches(this, m_identifier, m_searchText, m_options, m_reset);
87 Timer<DeferredScopeStringMatches> m_timer;
88 TextFinder* m_textFinder;
89 const int m_identifier;
90 const WebString m_searchText;
91 const WebFindOptions m_options;
95 bool TextFinder::find(int identifier, const WebString& searchText, const WebFindOptions& options, bool wrapWithinFrame, WebRect* selectionRect)
97 if (!m_ownerFrame.frame() || !m_ownerFrame.frame()->page())
100 WebLocalFrameImpl* mainFrameImpl = m_ownerFrame.viewImpl()->mainFrameImpl();
102 if (!options.findNext)
103 m_ownerFrame.frame()->page()->unmarkAllTextMatches();
105 setMarkerActive(m_activeMatch.get(), false);
107 if (m_activeMatch && &m_activeMatch->ownerDocument() != m_ownerFrame.frame()->document())
108 m_activeMatch = nullptr;
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();
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);
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();
135 m_ownerFrame.invalidateAll();
140 m_ownerFrame.viewImpl()->zoomToFindInPageRect(m_ownerFrame.frameView()->contentsToWindow(enclosingIntRect(RenderObject::absoluteBoundingBoxRectForRange(m_activeMatch.get()))));
143 setMarkerActive(m_activeMatch.get(), true);
144 WebLocalFrameImpl* oldActiveFrame = mainFrameImpl->ensureTextFinder().m_currentActiveMatchFrame;
145 mainFrameImpl->ensureTextFinder().m_currentActiveMatchFrame = &m_ownerFrame;
147 // Make sure no node is focused. See http://crbug.com/38700.
148 m_ownerFrame.frame()->document()->setFocusedElement(nullptr);
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;
156 if (oldActiveFrame != &m_ownerFrame) {
158 m_activeMatchIndexInCurrentFrame = 0;
160 m_activeMatchIndexInCurrentFrame = m_lastMatchCount - 1;
163 ++m_activeMatchIndexInCurrentFrame;
165 --m_activeMatchIndexInCurrentFrame;
167 if (m_activeMatchIndexInCurrentFrame + 1 > m_lastMatchCount)
168 m_activeMatchIndexInCurrentFrame = 0;
169 if (m_activeMatchIndexInCurrentFrame == -1)
170 m_activeMatchIndexInCurrentFrame = m_lastMatchCount - 1;
173 *selectionRect = m_ownerFrame.frameView()->contentsToWindow(m_activeMatch->boundingBox());
174 reportFindInPageSelection(*selectionRect, m_activeMatchIndexInCurrentFrame + 1, identifier);
181 void TextFinder::stopFindingAndClearSelection()
183 cancelPendingScopingEffort();
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();
190 // Let the frame know that we don't want tickmarks or highlighting anymore.
191 m_ownerFrame.invalidateAll();
194 void TextFinder::scopeStringMatches(int identifier, const WebString& searchText, const WebFindOptions& options, bool 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;
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;
205 // Clear highlighting for this frame.
206 LocalFrame* frame = m_ownerFrame.frame();
207 if (frame && frame->page() && frame->editor().markedTextMatchesAreHighlighted())
208 frame->page()->unmarkAllTextMatches();
210 // Clear the tickmarks and results cache.
211 clearFindMatchesCache();
213 // Clear the counters from last operation.
214 m_lastMatchCount = 0;
215 m_nextInvalidateAfter = 0;
216 m_resumeScopingFromRange = nullptr;
218 // The view might be null on detached frames.
219 if (frame && frame->page())
220 m_ownerFrame.viewImpl()->mainFrameImpl()->ensureTextFinder().m_framesScopingCount++;
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.
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);
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());
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())
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
255 bool timedOut = false;
256 double startTime = currentTime();
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;
265 findPlainText(searchStart, searchEnd, searchText, options.matchCase ? 0 : CaseInsensitive, resultStart, resultEnd);
266 if (resultStart == resultEnd) {
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();
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;
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
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;
305 // Notify browser of new location for the selected rectangle.
306 reportFindInPageSelection(
307 m_ownerFrame.frameView()->contentsToWindow(resultBounds),
308 m_activeMatchIndexInCurrentFrame + 1,
312 addMarker(resultRange.get(), foundActiveMatch);
314 m_findMatchesCache.append(FindMatch(resultRange.get(), m_lastMatchCount + matchCount));
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
320 searchStart = resultStart.next();
322 m_resumeScopingFromRange = Range::create(*resultStart.document(), resultStart, resultStart);
323 timedOut = (currentTime() - startTime) >= maxScopingDuration;
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;
330 if (matchCount > 0) {
331 m_ownerFrame.frame()->editor().setMarkedTextMatchesAreHighlighted(true);
333 m_lastMatchCount += matchCount;
335 // Let the mainframe know how much we found during this pass.
336 mainFrameImpl->increaseMatchCount(matchCount, identifier);
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.
344 invalidateIfNecessary();
346 // Scoping effort ran out of time, lets ask for another time-slice.
347 scopeStringMatchesSoon(
351 false); // don't reset.
352 return; // Done for now, resume work later.
355 finishCurrentScopingEffort(identifier);
358 void TextFinder::flushCurrentScopingEffort(int identifier)
360 if (!m_ownerFrame.frame() || !m_ownerFrame.frame()->page())
363 WebLocalFrameImpl* mainFrameImpl = m_ownerFrame.viewImpl()->mainFrameImpl();
364 mainFrameImpl->ensureTextFinder().decrementFramesScopingCount(identifier);
367 void TextFinder::finishCurrentScopingEffort(int identifier)
369 flushCurrentScopingEffort(identifier);
371 m_scopingInProgress = false;
372 m_lastFindRequestCompletedWithNoMatches = !m_lastMatchCount;
374 // This frame is done, so show any scrollbar tickmarks we haven't drawn yet.
375 m_ownerFrame.invalidateScrollbar();
378 void TextFinder::cancelPendingScopingEffort()
380 deleteAllValues(m_deferredScopingWork);
381 m_deferredScopingWork.clear();
383 m_activeMatchIndexInCurrentFrame = -1;
385 // Last request didn't complete.
386 if (m_scopingInProgress)
387 m_lastFindRequestCompletedWithNoMatches = false;
389 m_scopingInProgress = false;
392 void TextFinder::increaseMatchCount(int identifier, int count)
395 ++m_findMatchMarkersVersion;
397 m_totalMatchCount += count;
399 // Update the UI with the latest findings.
400 if (m_ownerFrame.client())
401 m_ownerFrame.client()->reportFindInPageMatchCount(identifier, m_totalMatchCount, !m_framesScopingCount);
404 void TextFinder::reportFindInPageSelection(const WebRect& selectionRect, int activeMatchOrdinal, int identifier)
406 // Update the UI with the latest selection rect.
407 if (m_ownerFrame.client())
408 m_ownerFrame.client()->reportFindInPageSelection(identifier, ordinalOfFirstMatch() + activeMatchOrdinal, selectionRect);
411 void TextFinder::resetMatchCount()
413 if (m_totalMatchCount > 0)
414 ++m_findMatchMarkersVersion;
416 m_totalMatchCount = 0;
417 m_framesScopingCount = 0;
420 void TextFinder::clearFindMatchesCache()
422 if (!m_findMatchesCache.isEmpty())
423 m_ownerFrame.viewImpl()->mainFrameImpl()->ensureTextFinder().m_findMatchMarkersVersion++;
425 m_findMatchesCache.clear();
426 m_findMatchRectsAreValid = false;
429 bool TextFinder::isActiveMatchFrameValid() const
431 WebLocalFrameImpl* mainFrameImpl = m_ownerFrame.viewImpl()->mainFrameImpl();
432 WebLocalFrameImpl* activeMatchFrame = mainFrameImpl->activeMatchFrame();
433 return activeMatchFrame && activeMatchFrame->activeMatch() && activeMatchFrame->frame()->tree().isDescendantOf(mainFrameImpl->frame());
436 void TextFinder::updateFindMatchRects()
438 IntSize currentContentsSize = m_ownerFrame.contentsSize();
439 if (m_contentsSizeForCurrentFindMatchRects != currentContentsSize) {
440 m_contentsSizeForCurrentFindMatchRects = currentContentsSize;
441 m_findMatchRectsAreValid = false;
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());
451 if (it->m_rect.isEmpty())
455 // Remove any invalid matches from the cache.
457 WillBeHeapVector<FindMatch> filteredMatches;
458 filteredMatches.reserveCapacity(m_findMatchesCache.size() - deadMatches);
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);
465 m_findMatchesCache.swap(filteredMatches);
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;
473 m_findMatchRectsAreValid = true;
476 WebFloatRect TextFinder::activeFindMatchRect()
478 if (!isActiveMatchFrameValid())
479 return WebFloatRect();
481 return WebFloatRect(findInPageRectFromRange(m_currentActiveMatchFrame->activeMatch()));
484 void TextFinder::findMatchRects(WebVector<WebFloatRect>& outputRects)
486 Vector<WebFloatRect> matchRects;
487 for (WebLocalFrameImpl* frame = &m_ownerFrame; frame; frame = toWebLocalFrameImpl(frame->traverseNext(false)))
488 frame->ensureTextFinder().appendFindMatchRects(matchRects);
490 outputRects = matchRects;
493 void TextFinder::appendFindMatchRects(Vector<WebFloatRect>& frameRects)
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);
503 int TextFinder::selectNearestFindMatch(const WebFloatPoint& point, WebRect* selectionRect)
505 TextFinder* bestFinder = 0;
506 int indexInBestFrame = -1;
507 float distanceInBestFrame = FLT_MAX;
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;
520 if (indexInBestFrame != -1)
521 return bestFinder->selectFindMatch(static_cast<unsigned>(indexInBestFrame), selectionRect);
526 int TextFinder::nearestFindMatch(const FloatPoint& point, float& distanceSquared)
528 updateFindMatchRects();
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) {
540 distanceSquared = currentDistanceSquared;
546 int TextFinder::selectFindMatch(unsigned index, WebRect* selectionRect)
548 ASSERT_WITH_SECURITY_IMPLICATION(index < m_findMatchesCache.size());
550 RefPtrWillBeRawPtr<Range> range = m_findMatchesCache[index].m_range;
551 if (!range->boundaryPointsValid() || !range->startContainer()->inDocument())
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);
561 m_activeMatchIndexInCurrentFrame = m_findMatchesCache[index].m_ordinal - 1;
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);
567 m_activeMatch = range.release();
568 setMarkerActive(m_activeMatch.get(), true);
570 // Clear any user selection, to make sure Find Next continues on from the match we just activated.
571 m_ownerFrame.frame()->selection().clear();
573 // Make sure no node is focused. See http://crbug.com/38700.
574 m_ownerFrame.frame()->document()->setFocusedElement(nullptr);
577 IntRect activeMatchRect;
578 IntRect activeMatchBoundingBox = enclosingIntRect(RenderObject::absoluteBoundingBoxRectForRange(m_activeMatch.get()));
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);
586 // Zoom to the active match.
587 activeMatchRect = m_ownerFrame.frameView()->contentsToWindow(activeMatchBoundingBox);
588 m_ownerFrame.viewImpl()->zoomToFindInPageRect(activeMatchRect);
592 *selectionRect = activeMatchRect;
594 return ordinalOfFirstMatch() + m_activeMatchIndexInCurrentFrame + 1;
597 PassOwnPtr<TextFinder> TextFinder::create(WebLocalFrameImpl& ownerFrame)
599 return adoptPtr(new TextFinder(ownerFrame));
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)
620 TextFinder::~TextFinder()
622 cancelPendingScopingEffort();
625 void TextFinder::addMarker(Range* range, bool activeMatch)
627 m_ownerFrame.frame()->document()->markers().addTextMatchMarker(range, activeMatch);
630 void TextFinder::setMarkerActive(Range* range, bool active)
632 if (!range || range->collapsed())
634 m_ownerFrame.frame()->document()->markers().setMarkersActive(range, active);
637 int TextFinder::ordinalOfFirstMatchForFrame(WebLocalFrameImpl* frame) const
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;
651 bool TextFinder::shouldScopeMatches(const String& searchText)
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())
660 ASSERT(frame->document() && frame->view());
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());
670 if (previousSearchPrefix == m_lastSearchString)
671 return false; // Don't search this frame, it will be fruitless.
677 void TextFinder::scopeStringMatchesSoon(int identifier, const WebString& searchText, const WebFindOptions& options, bool reset)
679 m_deferredScopingWork.append(new DeferredScopeStringMatches(this, identifier, searchText, options, reset));
682 void TextFinder::callScopeStringMatches(DeferredScopeStringMatches* caller, int identifier, const WebString& searchText, const WebFindOptions& options, bool reset)
684 m_deferredScopingWork.remove(m_deferredScopingWork.find(caller));
685 scopeStringMatches(identifier, searchText, options, reset);
687 // This needs to happen last since searchText is passed by reference.
691 void TextFinder::invalidateIfNecessary()
693 if (m_lastMatchCount <= m_nextInvalidateAfter)
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;
706 int i = m_lastMatchCount / startSlowingDownAfter;
707 m_nextInvalidateAfter += i * slowdown;
708 m_ownerFrame.invalidateScrollbar();
711 void TextFinder::flushCurrentScoping()
713 flushCurrentScopingEffort(m_findRequestIdentifier);
716 void TextFinder::setMatchMarkerActive(bool active)
718 setMarkerActive(m_activeMatch.get(), active);
721 void TextFinder::decrementFramesScopingCount(int identifier)
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;
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);
733 int TextFinder::ordinalOfFirstMatch() const
735 return ordinalOfFirstMatchForFrame(&m_ownerFrame);