Example #1
0
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);
}
Example #2
0
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();
}
Example #3
0
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);
}
Example #4
0
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;
}
Example #5
0
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;
}
Example #6
0
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);
}