void TextFinder::reportFindInPageResultToAccessibility(int identifier) { if (!m_activeMatch) return; AXObjectCacheImpl* axObjectCache = toAXObjectCacheImpl( ownerFrame().frame()->document()->existingAXObjectCache()); if (!axObjectCache) return; AXObject* startObject = axObjectCache->get(m_activeMatch->startContainer()); AXObject* endObject = axObjectCache->get(m_activeMatch->endContainer()); if (!startObject || !endObject) return; // Notify the client of new text marker data. axObjectCache->postNotification( startObject, AXObjectCache::AXNotification::AXChildrenChanged); if (startObject != endObject) axObjectCache->postNotification( endObject, AXObjectCache::AXNotification::AXChildrenChanged); if (ownerFrame().client()) { ownerFrame().client()->handleAccessibilityFindInPageResult( identifier, m_activeMatchIndex + 1, WebAXObject(startObject), m_activeMatch->startOffset(), WebAXObject(endObject), m_activeMatch->endOffset()); } }
bool TextFinder::shouldScopeMatches(const String& searchText, const WebFindOptions& options) { // Don't scope if we can't find a frame or a view. // The user may have closed the tab/application, so abort. LocalFrame* frame = ownerFrame().frame(); if (!frame || !frame->view() || !frame->page()) return false; DCHECK(frame->document()); DCHECK(frame->view()); if (options.force) return true; if (!ownerFrame().hasVisibleContent()) return false; // If the frame completed the scoping operation and found 0 matches the last // time it was searched, then we don't have to search it again if the user is // just adding to the search string or sending the same search string again. if (m_lastFindRequestCompletedWithNoMatches && !m_lastSearchString.isEmpty()) { // Check to see if the search string prefixes match. String previousSearchPrefix = searchText.substring(0, m_lastSearchString.length()); if (previousSearchPrefix == m_lastSearchString) return false; // Don't search this frame, it will be fruitless. } return true; }
void TextFinder::flushCurrentScopingEffort(int identifier) { if (!ownerFrame().frame() || !ownerFrame().frame()->page()) return; m_frameScoping = false; ownerFrame().increaseMatchCount(0, identifier); }
void TextFinder::flushCurrentScopingEffort(int identifier) { if (!ownerFrame().frame() || !ownerFrame().frame()->page()) return; WebLocalFrameImpl* mainFrameImpl = ownerFrame().viewImpl()->mainFrameImpl(); mainFrameImpl->ensureTextFinder().decrementFramesScopingCount(identifier); }
void TextFinder::unmarkAllTextMatches() { LocalFrame* frame = ownerFrame().frame(); if (frame && frame->page() && frame->editor().markedTextMatchesAreHighlighted()) { if (ownerFrame().client() && ownerFrame().client()->shouldSearchSingleFrame()) frame->document()->markers().removeMarkers(DocumentMarker::TextMatch); else frame->page()->unmarkAllTextMatches(); } }
void TextFinder::reportFindInPageSelection(const WebRect& selectionRect, int activeMatchOrdinal, int identifier) { // Update the UI with the latest selection rect. if (ownerFrame().client()) ownerFrame().client()->reportFindInPageSelection(identifier, ordinalOfFirstMatch() + activeMatchOrdinal, selectionRect); // Update accessibility too, so if the user commits to this query // we can move accessibility focus to this result. reportFindInPageResultToAccessibility(identifier); }
void TextFinder::increaseMatchCount(int identifier, int count) { if (count) ++m_findMatchMarkersVersion; m_totalMatchCount += count; // Update the UI with the latest findings. if (ownerFrame().client()) ownerFrame().client()->reportFindInPageMatchCount( identifier, m_totalMatchCount, !m_frameScoping || !m_totalMatchCount); }
void TextFinder::finishCurrentScopingEffort(int identifier) { flushCurrentScopingEffort(identifier); m_scopingInProgress = false; m_lastFindRequestCompletedWithNoMatches = !m_lastMatchCount; ownerFrame().client()->reportFindInFrameMatchCount(identifier, m_lastMatchCount, true); // This frame is done, so show any scrollbar tickmarks we haven't drawn yet. ownerFrame().frameView()->invalidatePaintForTickmarks(); }
void TextFinder::stopFindingAndClearSelection() { cancelPendingScopingEffort(); // Remove all markers for matches found and turn off the highlighting. ownerFrame().frame()->document()->markers().removeMarkers(DocumentMarker::TextMatch); ownerFrame().frame()->editor().setMarkedTextMatchesAreHighlighted(false); clearFindMatchesCache(); // Let the frame know that we don't want tickmarks or highlighting anymore. ownerFrame().invalidateAll(); }
void TextFinder::startScopingStringMatches(int identifier, const WebString& searchText, const WebFindOptions& options) { cancelPendingScopingEffort(); // This is a brand new search, so we need to reset everything. // Scoping is just about to begin. m_scopingInProgress = true; // Need to keep the current identifier locally in order to finish the // request in case the frame is detached during the process. m_findRequestIdentifier = identifier; // Clear highlighting for this frame. unmarkAllTextMatches(); // Clear the tickmarks and results cache. clearFindMatchesCache(); // Clear the total match count and increment markers version. resetMatchCount(); // Clear the counters from last operation. m_lastMatchCount = 0; m_nextInvalidateAfter = 0; m_resumeScopingFromRange = nullptr; // The view might be null on detached frames. LocalFrame* frame = ownerFrame().frame(); if (frame && frame->page()) m_frameScoping = true; // Now, defer scoping until later to allow find operation to finish quickly. scopeStringMatchesSoon(identifier, searchText, options); }
void TextFinder::clearFindMatchesCache() { if (!m_findMatchesCache.isEmpty()) ownerFrame().viewImpl()->mainFrameImpl()->ensureTextFinder().m_findMatchMarkersVersion++; m_findMatchesCache.clear(); m_findMatchRectsAreValid = false; }
void TextFinder::findMatchRects(WebVector<WebFloatRect>& outputRects) { Vector<WebFloatRect> matchRects; for (WebLocalFrameImpl* frame = &ownerFrame(); frame; frame = toWebLocalFrameImpl(frame->traverseNextLocal(false))) frame->ensureTextFinder().appendFindMatchRects(matchRects); outputRects = matchRects; }
int TextFinder::selectFindMatch(unsigned index, WebRect* selectionRect) { ASSERT_WITH_SECURITY_IMPLICATION(index < m_findMatchesCache.size()); RefPtrWillBeRawPtr<Range> range = m_findMatchesCache[index].m_range; if (!range->boundaryPointsValid() || !range->startContainer()->inDocument()) return -1; // Check if the match is already selected. TextFinder& mainFrameTextFinder = ownerFrame().viewImpl()->mainFrameImpl()->ensureTextFinder(); WebLocalFrameImpl* activeMatchFrame = mainFrameTextFinder.m_currentActiveMatchFrame; if (&ownerFrame() != activeMatchFrame || !m_activeMatch || !areRangesEqual(m_activeMatch.get(), range.get())) { if (isActiveMatchFrameValid()) activeMatchFrame->ensureTextFinder().setMatchMarkerActive(false); m_activeMatchIndexInCurrentFrame = m_findMatchesCache[index].m_ordinal - 1; // Set this frame as the active frame (the one with the active highlight). mainFrameTextFinder.m_currentActiveMatchFrame = &ownerFrame(); ownerFrame().viewImpl()->setFocusedFrame(&ownerFrame()); m_activeMatch = range.release(); setMarkerActive(m_activeMatch.get(), true); // Clear any user selection, to make sure Find Next continues on from the match we just activated. ownerFrame().frame()->selection().clear(); // Make sure no node is focused. See http://crbug.com/38700. ownerFrame().frame()->document()->clearFocusedElement(); } IntRect activeMatchRect; IntRect activeMatchBoundingBox = enclosingIntRect(LayoutObject::absoluteBoundingBoxRectForRange(m_activeMatch.get())); if (!activeMatchBoundingBox.isEmpty()) { if (m_activeMatch->firstNode() && m_activeMatch->firstNode()->layoutObject()) { m_activeMatch->firstNode()->layoutObject()->scrollRectToVisible( LayoutRect(activeMatchBoundingBox), ScrollAlignment::alignCenterIfNeeded, ScrollAlignment::alignCenterIfNeeded, UserScroll); } // Zoom to the active match. activeMatchRect = ownerFrame().frameView()->contentsToRootFrame(activeMatchBoundingBox); ownerFrame().viewImpl()->zoomToFindInPageRect(activeMatchRect); } if (selectionRect) *selectionRect = activeMatchRect; return ordinalOfFirstMatch() + m_activeMatchIndexInCurrentFrame + 1; }
void TextFinder::finishCurrentScopingEffort(int identifier) { flushCurrentScopingEffort(identifier); m_scopingInProgress = false; m_lastFindRequestCompletedWithNoMatches = !m_lastMatchCount; // This frame is done, so show any scrollbar tickmarks we haven't drawn yet. ownerFrame().invalidateScrollbar(); }
void TextFinder::reportFindInPageResultToAccessibility(int identifier) { AXObjectCacheImpl* axObjectCache = toAXObjectCacheImpl(ownerFrame().frame()->document()->existingAXObjectCache()); if (!axObjectCache) return; AXObject* startObject = axObjectCache->get(m_activeMatch->startContainer()); AXObject* endObject = axObjectCache->get(m_activeMatch->endContainer()); if (!startObject || !endObject) return; WebLocalFrameImpl* mainFrameImpl = ownerFrame().viewImpl()->mainFrameImpl(); if (mainFrameImpl && mainFrameImpl->client()) { mainFrameImpl->client()->handleAccessibilityFindInPageResult( identifier, m_activeMatchIndexInCurrentFrame + 1, WebAXObject(startObject), m_activeMatch->startOffset(), WebAXObject(endObject), m_activeMatch->endOffset()); } }
void TextFinder::updateFindMatchRects() { IntSize currentContentsSize = ownerFrame().contentsSize(); if (m_contentsSizeForCurrentFindMatchRects != currentContentsSize) { m_contentsSizeForCurrentFindMatchRects = currentContentsSize; m_findMatchRectsAreValid = false; } size_t deadMatches = 0; for (FindMatch& match : m_findMatchesCache) { if (!match.m_range->boundaryPointsValid() || !match.m_range->startContainer()->isConnected()) match.m_rect = FloatRect(); else if (!m_findMatchRectsAreValid) match.m_rect = findInPageRectFromRange(match.m_range.get()); if (match.m_rect.isEmpty()) ++deadMatches; } // Remove any invalid matches from the cache. if (deadMatches) { HeapVector<FindMatch> filteredMatches; filteredMatches.reserveCapacity(m_findMatchesCache.size() - deadMatches); for (const FindMatch& match : m_findMatchesCache) { if (!match.m_rect.isEmpty()) filteredMatches.append(match); } m_findMatchesCache.swap(filteredMatches); } // Invalidate the rects in child frames. Will be updated later during // traversal. if (!m_findMatchRectsAreValid) for (WebFrame* child = ownerFrame().firstChild(); child; child = child->nextSibling()) toWebLocalFrameImpl(child)->ensureTextFinder().m_findMatchRectsAreValid = false; m_findMatchRectsAreValid = true; }
void TextFinder::decrementFramesScopingCount(int identifier) { // This frame has no further scoping left, so it is done. Other frames might, // of course, continue to scope matches. --m_framesScopingCount; // If this is the last frame to finish scoping we need to trigger the final // update to be sent. if (!m_framesScopingCount) ownerFrame().increaseMatchCount(0, identifier); }
int TextFinder::ordinalOfFirstMatchForFrame(WebLocalFrameImpl* frame) const { int ordinal = 0; WebLocalFrameImpl* mainFrameImpl = ownerFrame().viewImpl()->mainFrameImpl(); // Iterate from the main frame up to (but not including) |frame| and // add up the number of matches found so far. for (WebLocalFrameImpl* it = mainFrameImpl; it != frame; it = toWebLocalFrameImpl(it->traverseNextLocal(true))) { TextFinder& finder = it->ensureTextFinder(); if (finder.m_lastMatchCount > 0) ordinal += finder.m_lastMatchCount; } return ordinal; }
int TextFinder::selectFindMatch(unsigned index, WebRect* selectionRect) { SECURITY_DCHECK(index < m_findMatchesCache.size()); Range* range = m_findMatchesCache[index].m_range; if (!range->boundaryPointsValid() || !range->startContainer()->isConnected()) return -1; // Check if the match is already selected. if (!m_currentActiveMatchFrame || !m_activeMatch || !areRangesEqual(m_activeMatch.get(), range)) { m_activeMatchIndex = m_findMatchesCache[index].m_ordinal - 1; // Set this frame as the active frame (the one with the active highlight). m_currentActiveMatchFrame = true; ownerFrame().viewImpl()->setFocusedFrame(&ownerFrame()); if (m_activeMatch) setMarkerActive(m_activeMatch.get(), false); m_activeMatch = range; setMarkerActive(m_activeMatch.get(), true); // Clear any user selection, to make sure Find Next continues on from the // match we just activated. ownerFrame().frame()->selection().clear(); // Make sure no node is focused. See http://crbug.com/38700. ownerFrame().frame()->document()->clearFocusedElement(); } IntRect activeMatchRect; IntRect activeMatchBoundingBox = enclosingIntRect( LayoutObject::absoluteBoundingBoxRectForRange(m_activeMatch.get())); if (!activeMatchBoundingBox.isEmpty()) { if (m_activeMatch->firstNode() && m_activeMatch->firstNode()->layoutObject()) { m_activeMatch->firstNode()->layoutObject()->scrollRectToVisible( LayoutRect(activeMatchBoundingBox), ScrollAlignment::alignCenterIfNeeded, ScrollAlignment::alignCenterIfNeeded, UserScroll); } // Zoom to the active match. activeMatchRect = ownerFrame().frameView()->contentsToRootFrame(activeMatchBoundingBox); ownerFrame().viewImpl()->zoomToFindInPageRect(activeMatchRect); } if (selectionRect) *selectionRect = activeMatchRect; return m_activeMatchIndex + 1; }
void TextFinder::invalidateIfNecessary() { if (m_lastMatchCount <= m_nextInvalidateAfter) return; // FIXME: (http://crbug.com/6819) Optimize the drawing of the tickmarks and // remove this. This calculation sets a milestone for when next to // invalidate the scrollbar and the content area. We do this so that we // don't spend too much time drawing the scrollbar over and over again. // Basically, up until the first 500 matches there is no throttle. // After the first 500 matches, we set set the milestone further and // further out (750, 1125, 1688, 2K, 3K). static const int startSlowingDownAfter = 500; static const int slowdown = 750; int i = m_lastMatchCount / startSlowingDownAfter; m_nextInvalidateAfter += i * slowdown; ownerFrame().frameView()->invalidatePaintForTickmarks(); }
int TextFinder::selectNearestFindMatch(const WebFloatPoint& point, WebRect* selectionRect) { TextFinder* bestFinder = nullptr; int indexInBestFrame = -1; float distanceInBestFrame = FLT_MAX; for (WebLocalFrameImpl* frame = &ownerFrame(); frame; frame = toWebLocalFrameImpl(frame->traverseNextLocal(false))) { float distanceInFrame; TextFinder& finder = frame->ensureTextFinder(); int indexInFrame = finder.nearestFindMatch(point, distanceInFrame); if (distanceInFrame < distanceInBestFrame) { bestFinder = &finder; indexInBestFrame = indexInFrame; distanceInBestFrame = distanceInFrame; } } if (indexInBestFrame != -1) return bestFinder->selectFindMatch(static_cast<unsigned>(indexInBestFrame), selectionRect); return -1; }
void TextFinder::unmarkAllTextMatches() { LocalFrame* frame = ownerFrame().frame(); if (frame && frame->page() && frame->editor().markedTextMatchesAreHighlighted()) frame->document()->markers().removeMarkers(DocumentMarker::TextMatch); }
bool TextFinder::setMarkerActive(Range* range, bool active) { if (!range || range->collapsed()) return false; return ownerFrame().frame()->document()->markers().setMarkersActive( EphemeralRange(range), active); }
void TextFinder::scopeStringMatches(int identifier, const WebString& searchText, const WebFindOptions& options, bool reset) { if (reset) { // This is a brand new search, so we need to reset everything. // Scoping is just about to begin. m_scopingInProgress = true; // Need to keep the current identifier locally in order to finish the // request in case the frame is detached during the process. m_findRequestIdentifier = identifier; // Clear highlighting for this frame. LocalFrame* frame = ownerFrame().frame(); if (frame && frame->page() && frame->editor().markedTextMatchesAreHighlighted()) frame->page()->unmarkAllTextMatches(); // Clear the tickmarks and results cache. clearFindMatchesCache(); // Clear the counters from last operation. m_lastMatchCount = 0; m_nextInvalidateAfter = 0; m_resumeScopingFromRange = nullptr; // The view might be null on detached frames. if (frame && frame->page()) ownerFrame().viewImpl()->mainFrameImpl()->ensureTextFinder().m_framesScopingCount++; // Now, defer scoping until later to allow find operation to finish quickly. scopeStringMatchesSoon(identifier, searchText, options, false); // false means just reset, so don't do it again. return; } if (!shouldScopeMatches(searchText)) { // Note that we want to defer the final update when resetting even if shouldScopeMatches returns false. // This is done in order to prevent sending a final message based only on the results of the first frame // since m_framesScopingCount would be 0 as other frames have yet to reset. finishCurrentScopingEffort(identifier); return; } WebLocalFrameImpl* mainFrameImpl = ownerFrame().viewImpl()->mainFrameImpl(); Position searchStart = firstPositionInNode(ownerFrame().frame()->document()); Position searchEnd = lastPositionInNode(ownerFrame().frame()->document()); ASSERT(searchStart.document() == searchEnd.document()); if (m_resumeScopingFromRange) { // This is a continuation of a scoping operation that timed out and didn't // complete last time around, so we should start from where we left off. ASSERT(m_resumeScopingFromRange->collapsed()); searchStart = m_resumeScopingFromRange->endPosition(); if (searchStart.document() != searchEnd.document()) return; } // This timeout controls how long we scope before releasing control. This // value does not prevent us from running for longer than this, but it is // periodically checked to see if we have exceeded our allocated time. const double maxScopingDuration = 0.1; // seconds int matchCount = 0; bool timedOut = false; double startTime = currentTime(); do { // Find next occurrence of the search string. // FIXME: (http://crbug.com/6818) This WebKit operation may run for longer // than the timeout value, and is not interruptible as it is currently // written. We may need to rewrite it with interruptibility in mind, or // find an alternative. Position resultStart; Position resultEnd; findPlainText(searchStart, searchEnd, searchText, options.matchCase ? 0 : CaseInsensitive, resultStart, resultEnd); if (resultStart == resultEnd) { // Not found. break; } RefPtrWillBeRawPtr<Range> resultRange = Range::create(*resultStart.document(), resultStart, resultEnd); if (resultRange->collapsed()) { // resultRange will be collapsed if the matched text spans over multiple TreeScopes. // FIXME: Show such matches to users. searchStart = resultEnd; continue; } ++matchCount; // Catch a special case where Find found something but doesn't know what // the bounding box for it is. In this case we set the first match we find // as the active rect. IntRect resultBounds = resultRange->boundingBox(); IntRect activeSelectionRect; if (m_locatingActiveRect) { activeSelectionRect = m_activeMatch.get() ? m_activeMatch->boundingBox() : resultBounds; } // If the Find function found a match it will have stored where the // match was found in m_activeSelectionRect on the current frame. If we // find this rect during scoping it means we have found the active // tickmark. bool foundActiveMatch = false; if (m_locatingActiveRect && (activeSelectionRect == resultBounds)) { // We have found the active tickmark frame. mainFrameImpl->ensureTextFinder().m_currentActiveMatchFrame = &ownerFrame(); foundActiveMatch = true; // We also know which tickmark is active now. m_activeMatchIndexInCurrentFrame = matchCount - 1; // To stop looking for the active tickmark, we set this flag. m_locatingActiveRect = false; // Notify browser of new location for the selected rectangle. reportFindInPageSelection( ownerFrame().frameView()->contentsToWindow(resultBounds), m_activeMatchIndexInCurrentFrame + 1, identifier); } addMarker(resultRange.get(), foundActiveMatch); m_findMatchesCache.append(FindMatch(resultRange.get(), m_lastMatchCount + matchCount)); // Set the new start for the search range to be the end of the previous // result range. There is no need to use a VisiblePosition here, // since findPlainText will use a TextIterator to go over the visible // text nodes. searchStart = resultEnd; m_resumeScopingFromRange = Range::create(*resultStart.document(), resultEnd, resultEnd); timedOut = (currentTime() - startTime) >= maxScopingDuration; } while (!timedOut); // Remember what we search for last time, so we can skip searching if more // letters are added to the search string (and last outcome was 0). m_lastSearchString = searchText; if (matchCount > 0) { ownerFrame().frame()->editor().setMarkedTextMatchesAreHighlighted(true); m_lastMatchCount += matchCount; // Let the mainframe know how much we found during this pass. mainFrameImpl->increaseMatchCount(matchCount, identifier); } if (timedOut) { // If we found anything during this pass, we should redraw. However, we // don't want to spam too much if the page is extremely long, so if we // reach a certain point we start throttling the redraw requests. if (matchCount > 0) invalidateIfNecessary(); // Scoping effort ran out of time, lets ask for another time-slice. scopeStringMatchesSoon( identifier, searchText, options, false); // don't reset. return; // Done for now, resume work later. } finishCurrentScopingEffort(identifier); }
void TextFinder::setMarkerActive(Range* range, bool active) { if (!range || range->collapsed()) return; ownerFrame().frame()->document()->markers().setMarkersActive(range, active); }
bool TextFinder::isActiveMatchFrameValid() const { WebLocalFrameImpl* mainFrameImpl = ownerFrame().viewImpl()->mainFrameImpl(); WebLocalFrameImpl* activeMatchFrame = mainFrameImpl->activeMatchFrame(); return activeMatchFrame && activeMatchFrame->activeMatch() && activeMatchFrame->frame()->tree().isDescendantOf(mainFrameImpl->frame()); }
void TextFinder::scopeStringMatches(int identifier, const WebString& searchText, const WebFindOptions& options, bool reset) { // TODO(dglazkov): The reset/continue cases need to be untangled into two // separate functions. This collation of logic is unnecessary and adds to // overall complexity of the code. if (reset) { // This is a brand new search, so we need to reset everything. // Scoping is just about to begin. m_scopingInProgress = true; // Need to keep the current identifier locally in order to finish the // request in case the frame is detached during the process. m_findRequestIdentifier = identifier; // Clear highlighting for this frame. unmarkAllTextMatches(); // Clear the tickmarks and results cache. clearFindMatchesCache(); // Clear the counters from last operation. m_lastMatchCount = 0; m_nextInvalidateAfter = 0; m_resumeScopingFromRange = nullptr; // The view might be null on detached frames. LocalFrame* frame = ownerFrame().frame(); if (frame && frame->page()) m_frameScoping = true; // Now, defer scoping until later to allow find operation to finish quickly. scopeStringMatchesSoon( identifier, searchText, options, false); // false means just reset, so don't do it again. return; } if (!shouldScopeMatches(searchText, options)) { finishCurrentScopingEffort(identifier); return; } PositionInFlatTree searchStart = PositionInFlatTree::firstPositionInNode(ownerFrame().frame()->document()); PositionInFlatTree searchEnd = PositionInFlatTree::lastPositionInNode(ownerFrame().frame()->document()); DCHECK_EQ(searchStart.document(), searchEnd.document()); if (m_resumeScopingFromRange) { // This is a continuation of a scoping operation that timed out and didn't // complete last time around, so we should start from where we left off. DCHECK(m_resumeScopingFromRange->collapsed()); searchStart = fromPositionInDOMTree<EditingInFlatTreeStrategy>( m_resumeScopingFromRange->endPosition()); if (searchStart.document() != searchEnd.document()) return; } // TODO(dglazkov): The use of updateStyleAndLayoutIgnorePendingStylesheets // needs to be audited. see http://crbug.com/590369 for more details. searchStart.document()->updateStyleAndLayoutIgnorePendingStylesheets(); // This timeout controls how long we scope before releasing control. This // value does not prevent us from running for longer than this, but it is // periodically checked to see if we have exceeded our allocated time. const double maxScopingDuration = 0.1; // seconds int matchCount = 0; bool timedOut = false; double startTime = currentTime(); do { // Find next occurrence of the search string. // FIXME: (http://crbug.com/6818) This WebKit operation may run for longer // than the timeout value, and is not interruptible as it is currently // written. We may need to rewrite it with interruptibility in mind, or // find an alternative. const EphemeralRangeInFlatTree result = findPlainText(EphemeralRangeInFlatTree(searchStart, searchEnd), searchText, options.matchCase ? 0 : CaseInsensitive); if (result.isCollapsed()) { // Not found. break; } Range* resultRange = Range::create( result.document(), toPositionInDOMTree(result.startPosition()), toPositionInDOMTree(result.endPosition())); if (resultRange->collapsed()) { // resultRange will be collapsed if the matched text spans over multiple // TreeScopes. FIXME: Show such matches to users. searchStart = result.endPosition(); continue; } ++matchCount; // Catch a special case where Find found something but doesn't know what // the bounding box for it is. In this case we set the first match we find // as the active rect. IntRect resultBounds = resultRange->boundingBox(); IntRect activeSelectionRect; if (m_locatingActiveRect) { activeSelectionRect = m_activeMatch.get() ? m_activeMatch->boundingBox() : resultBounds; } // If the Find function found a match it will have stored where the // match was found in m_activeSelectionRect on the current frame. If we // find this rect during scoping it means we have found the active // tickmark. bool foundActiveMatch = false; if (m_locatingActiveRect && (activeSelectionRect == resultBounds)) { // We have found the active tickmark frame. m_currentActiveMatchFrame = true; foundActiveMatch = true; // We also know which tickmark is active now. m_activeMatchIndex = matchCount - 1; // To stop looking for the active tickmark, we set this flag. m_locatingActiveRect = false; // Notify browser of new location for the selected rectangle. reportFindInPageSelection( ownerFrame().frameView()->contentsToRootFrame(resultBounds), m_activeMatchIndex + 1, identifier); } ownerFrame().frame()->document()->markers().addTextMatchMarker( EphemeralRange(resultRange), foundActiveMatch); m_findMatchesCache.append( FindMatch(resultRange, m_lastMatchCount + matchCount)); // Set the new start for the search range to be the end of the previous // result range. There is no need to use a VisiblePosition here, // since findPlainText will use a TextIterator to go over the visible // text nodes. searchStart = result.endPosition(); m_resumeScopingFromRange = Range::create( result.document(), toPositionInDOMTree(result.endPosition()), toPositionInDOMTree(result.endPosition())); timedOut = (currentTime() - startTime) >= maxScopingDuration; } while (!timedOut); // Remember what we search for last time, so we can skip searching if more // letters are added to the search string (and last outcome was 0). m_lastSearchString = searchText; if (matchCount > 0) { ownerFrame().frame()->editor().setMarkedTextMatchesAreHighlighted(true); m_lastMatchCount += matchCount; // Let the frame know how many matches we found during this pass. ownerFrame().increaseMatchCount(matchCount, identifier); } if (timedOut) { // If we found anything during this pass, we should redraw. However, we // don't want to spam too much if the page is extremely long, so if we // reach a certain point we start throttling the redraw requests. if (matchCount > 0) invalidateIfNecessary(); // Scoping effort ran out of time, lets ask for another time-slice. scopeStringMatchesSoon(identifier, searchText, options, false); // don't reset. return; // Done for now, resume work later. } finishCurrentScopingEffort(identifier); }
bool TextFinder::find(int identifier, const WebString& searchText, const WebFindOptions& options, bool wrapWithinFrame, bool* activeNow) { if (!options.findNext) unmarkAllTextMatches(); else setMarkerActive(m_activeMatch.get(), false); if (m_activeMatch && &m_activeMatch->ownerDocument() != ownerFrame().frame()->document()) m_activeMatch = nullptr; // If the user has selected something since the last Find operation we want // to start from there. Otherwise, we start searching from where the last Find // operation left off (either a Find or a FindNext operation). VisibleSelection selection(ownerFrame().frame()->selection().selection()); bool activeSelection = !selection.isNone(); if (activeSelection) { m_activeMatch = firstRangeOf(selection); ownerFrame().frame()->selection().clear(); } DCHECK(ownerFrame().frame()); DCHECK(ownerFrame().frame()->view()); const FindOptions findOptions = (options.forward ? 0 : Backwards) | (options.matchCase ? 0 : CaseInsensitive) | (wrapWithinFrame ? WrapAround : 0) | (options.wordStart ? AtWordStarts : 0) | (options.medialCapitalAsWordStart ? TreatMedialCapitalAsWordStart : 0) | (options.findNext ? 0 : StartInSelection); m_activeMatch = ownerFrame().frame()->editor().findStringAndScrollToVisible( searchText, m_activeMatch.get(), findOptions); if (!m_activeMatch) { // If we're finding next the next active match might not be in the current // frame. In this case we don't want to clear the matches cache. if (!options.findNext) clearFindMatchesCache(); ownerFrame().frameView()->invalidatePaintForTickmarks(); return false; } // If the user is browsing a page with autosizing, adjust the zoom to the // column where the next hit has been found. Doing this when autosizing is // not set will result in a zoom reset on small devices. if (ownerFrame() .frame() ->document() ->textAutosizer() ->pageNeedsAutosizing()) { ownerFrame().viewImpl()->zoomToFindInPageRect( ownerFrame().frameView()->contentsToRootFrame( enclosingIntRect(LayoutObject::absoluteBoundingBoxRectForRange( m_activeMatch.get())))); } bool wasActiveFrame = m_currentActiveMatchFrame; m_currentActiveMatchFrame = true; bool isActive = setMarkerActive(m_activeMatch.get(), true); if (activeNow) *activeNow = isActive; // Make sure no node is focused. See http://crbug.com/38700. ownerFrame().frame()->document()->clearFocusedElement(); // Set this frame as focused. ownerFrame().viewImpl()->setFocusedFrame(&ownerFrame()); if (!options.findNext || activeSelection || !isActive) { // This is either an initial Find operation, a Find-next from a new // start point due to a selection, or new matches were found during // Find-next due to DOM alteration (that couldn't be set as active), so // we set the flag to ask the scoping effort to find the active rect for // us and report it back to the UI. m_locatingActiveRect = true; } else { if (!wasActiveFrame) { if (options.forward) m_activeMatchIndex = 0; else m_activeMatchIndex = m_lastMatchCount - 1; } else { if (options.forward) ++m_activeMatchIndex; else --m_activeMatchIndex; if (m_activeMatchIndex + 1 > m_lastMatchCount) m_activeMatchIndex = 0; else if (m_activeMatchIndex < 0) m_activeMatchIndex = m_lastMatchCount - 1; } WebRect selectionRect = ownerFrame().frameView()->contentsToRootFrame( m_activeMatch->boundingBox()); reportFindInPageSelection(selectionRect, m_activeMatchIndex + 1, identifier); } return true; }
void TextFinder::addMarker(Range* range, bool activeMatch) { ownerFrame().frame()->document()->markers().addTextMatchMarker(range, activeMatch); }
bool TextFinder::find(int identifier, const WebString& searchText, const WebFindOptions& options, bool wrapWithinFrame, WebRect* selectionRect) { if (!ownerFrame().frame() || !ownerFrame().frame()->page()) return false; WebLocalFrameImpl* mainFrameImpl = ownerFrame().viewImpl()->mainFrameImpl(); if (!options.findNext) ownerFrame().frame()->page()->unmarkAllTextMatches(); else setMarkerActive(m_activeMatch.get(), false); if (m_activeMatch && &m_activeMatch->ownerDocument() != ownerFrame().frame()->document()) m_activeMatch = nullptr; // If the user has selected something since the last Find operation we want // to start from there. Otherwise, we start searching from where the last Find // operation left off (either a Find or a FindNext operation). VisibleSelection selection(ownerFrame().frame()->selection().selection()); bool activeSelection = !selection.isNone(); if (activeSelection) { m_activeMatch = selection.firstRange().get(); ownerFrame().frame()->selection().clear(); } ASSERT(ownerFrame().frame() && ownerFrame().frame()->view()); const FindOptions findOptions = (options.forward ? 0 : Backwards) | (options.matchCase ? 0 : CaseInsensitive) | (wrapWithinFrame ? WrapAround : 0) | (options.wordStart ? AtWordStarts : 0) | (options.medialCapitalAsWordStart ? TreatMedialCapitalAsWordStart : 0) | (options.findNext ? 0 : StartInSelection); m_activeMatch = ownerFrame().frame()->editor().findStringAndScrollToVisible(searchText, m_activeMatch.get(), findOptions); if (!m_activeMatch) { // If we're finding next the next active match might not be in the current frame. // In this case we don't want to clear the matches cache. if (!options.findNext) clearFindMatchesCache(); ownerFrame().invalidateAll(); return false; } #if OS(ANDROID) ownerFrame().viewImpl()->zoomToFindInPageRect(ownerFrame().frameView()->contentsToWindow(enclosingIntRect(RenderObject::absoluteBoundingBoxRectForRange(m_activeMatch.get())))); #endif setMarkerActive(m_activeMatch.get(), true); WebLocalFrameImpl* oldActiveFrame = mainFrameImpl->ensureTextFinder().m_currentActiveMatchFrame; mainFrameImpl->ensureTextFinder().m_currentActiveMatchFrame = &ownerFrame(); // Make sure no node is focused. See http://crbug.com/38700. ownerFrame().frame()->document()->setFocusedElement(nullptr); if (!options.findNext || activeSelection) { // This is either a Find operation or a Find-next from a new start point // due to a selection, so we set the flag to ask the scoping effort // to find the active rect for us and report it back to the UI. m_locatingActiveRect = true; } else { if (oldActiveFrame != &ownerFrame()) { if (options.forward) m_activeMatchIndexInCurrentFrame = 0; else m_activeMatchIndexInCurrentFrame = m_lastMatchCount - 1; } else { if (options.forward) ++m_activeMatchIndexInCurrentFrame; else --m_activeMatchIndexInCurrentFrame; if (m_activeMatchIndexInCurrentFrame + 1 > m_lastMatchCount) m_activeMatchIndexInCurrentFrame = 0; if (m_activeMatchIndexInCurrentFrame == -1) m_activeMatchIndexInCurrentFrame = m_lastMatchCount - 1; } if (selectionRect) { *selectionRect = ownerFrame().frameView()->contentsToWindow(m_activeMatch->boundingBox()); reportFindInPageSelection(*selectionRect, m_activeMatchIndexInCurrentFrame + 1, identifier); } } return true; }