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