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::stopFindingAndClearSelection() { cancelPendingScopingEffort(); // Remove all markers for matches found and turn off the highlighting. m_ownerFrame.frame()->document()->markers().removeMarkers(DocumentMarker::TextMatch); m_ownerFrame.frame()->editor().setMarkedTextMatchesAreHighlighted(false); clearFindMatchesCache(); // Let the frame know that we don't want tickmarks or highlighting anymore. m_ownerFrame.invalidateAll(); }
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; }
bool TextFinder::find(int identifier, const WebString& searchText, const WebFindOptions& options, bool wrapWithinFrame, WebRect* selectionRect) { if (!m_ownerFrame.frame() || !m_ownerFrame.frame()->page()) return false; WebLocalFrameImpl* mainFrameImpl = m_ownerFrame.viewImpl()->mainFrameImpl(); if (!options.findNext) m_ownerFrame.frame()->page()->unmarkAllTextMatches(); else setMarkerActive(m_activeMatch.get(), false); if (m_activeMatch && &m_activeMatch->ownerDocument() != m_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(m_ownerFrame.frame()->selection().selection()); bool activeSelection = !selection.isNone(); if (activeSelection) { m_activeMatch = selection.firstRange().get(); m_ownerFrame.frame()->selection().clear(); } ASSERT(m_ownerFrame.frame() && m_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 = m_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(); m_ownerFrame.invalidateAll(); return false; } #if OS(ANDROID) m_ownerFrame.viewImpl()->zoomToFindInPageRect(m_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 = &m_ownerFrame; // Make sure no node is focused. See http://crbug.com/38700. m_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 != &m_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 = m_ownerFrame.frameView()->contentsToWindow(m_activeMatch->boundingBox()); reportFindInPageSelection(*selectionRect, m_activeMatchIndexInCurrentFrame + 1, identifier); } } return true; }
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 = m_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()) m_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 = m_ownerFrame.viewImpl()->mainFrameImpl(); Position searchStart = firstPositionInNode(m_ownerFrame.frame()->document()); Position searchEnd = lastPositionInNode(m_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->startPosition().next(); 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 = resultStart.next(); 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 = &m_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( m_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 = resultStart.next(); m_resumeScopingFromRange = Range::create(*resultStart.document(), resultStart, resultStart); 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) { m_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); }