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