void SurroundingText::initialize(const Position& startPosition, const Position& endPosition, unsigned maxLength)
{
    ASSERT(startPosition.document() == endPosition.document());

    const unsigned halfMaxLength = maxLength / 2;

    Document* document = startPosition.document();
    // The position will have no document if it is null (as in no position).
    if (!document || !document->documentElement())
        return;

    // The forward range starts at the selection end and ends at the document's
    // end. It will then be updated to only contain the text in the text in the
    // right range around the selection.
    CharacterIterator forwardIterator(endPosition, lastPositionInNode(document->documentElement()).parentAnchoredEquivalent(), TextIteratorStopsOnFormControls);
    // FIXME: why do we stop going trough the text if we were not able to select something on the right?
    if (!forwardIterator.atEnd())
        forwardIterator.advance(maxLength - halfMaxLength);

    EphemeralRange forwardRange = forwardIterator.range();
    if (forwardRange.isNull() || !Range::create(*document, endPosition, forwardRange.startPosition())->text().length())
        return;

    // Same as with the forward range but with the backward range. The range
    // starts at the document's start and ends at the selection start and will
    // be updated.
    BackwardsCharacterIterator backwardsIterator(firstPositionInNode(document->documentElement()).parentAnchoredEquivalent(), startPosition, TextIteratorStopsOnFormControls);
    if (!backwardsIterator.atEnd())
        backwardsIterator.advance(halfMaxLength);

    m_startOffsetInContent = Range::create(*document, backwardsIterator.endPosition(), startPosition)->text().length();
    m_endOffsetInContent = Range::create(*document, backwardsIterator.endPosition(), endPosition)->text().length();
    m_contentRange = Range::create(*document, backwardsIterator.endPosition(), forwardRange.startPosition());
    ASSERT(m_contentRange);
}
Пример #2
0
PassRefPtr<InjectedBundleRangeHandle> InjectedBundleNodeHandle::visibleRange()
{
    VisiblePosition start = firstPositionInNode(m_node.ptr());
    VisiblePosition end = lastPositionInNode(m_node.ptr());

    RefPtr<Range> range = makeRange(start, end);
    return InjectedBundleRangeHandle::getOrCreate(range.get());
}
Пример #3
0
PassRefPtr<Range> Editor::rangeOfString(const String& target, Range* referenceRange, FindOptions options)
{
    if (target.isEmpty())
        return nullptr;

    // Start from an edge of the reference range. Which edge is used depends on whether we're searching forward or
    // backward, and whether startInSelection is set.
    Position searchStart = firstPositionInNode(m_frame.document());
    Position searchEnd = lastPositionInNode(m_frame.document());

    bool forward = !(options & Backwards);
    bool startInReferenceRange = referenceRange && (options & StartInSelection);
    if (referenceRange) {
        if (forward)
            searchStart = startInReferenceRange ? referenceRange->startPosition() : referenceRange->endPosition();
        else
            searchEnd = startInReferenceRange ? referenceRange->endPosition() : referenceRange->startPosition();
    }

    RefPtr<Range> resultRange = findStringBetweenPositions(target, searchStart, searchEnd, options);

    // If we started in the reference range and the found range exactly matches the reference range, find again.
    // Build a selection with the found range to remove collapsed whitespace.
    // Compare ranges instead of selection objects to ignore the way that the current selection was made.
    if (resultRange && startInReferenceRange && areRangesEqual(VisibleSelection(resultRange.get()).toNormalizedRange().get(), referenceRange)) {
        if (forward)
            searchStart = resultRange->endPosition();
        else
            searchEnd = resultRange->startPosition();
        resultRange = findStringBetweenPositions(target, searchStart, searchEnd, options);
    }

    if (!resultRange && options & WrapAround) {
        searchStart = firstPositionInNode(m_frame.document());
        searchEnd = lastPositionInNode(m_frame.document());
        resultRange = findStringBetweenPositions(target, searchStart, searchEnd, options);
    }

    return resultRange.release();
}
Пример #4
0
SurroundingText::SurroundingText(const Position& position, unsigned maxLength)
    : m_positionOffsetInContent(0)
{
    const unsigned halfMaxLength = maxLength / 2;

    Document* document = position.document();
    // The |position| will have no document if it is null (as in no position).
    if (!document)
        return;

    // The forward range starts at the selection end and ends at the document's
    // end. It will then be updated to only contain the text in the text in the
    // right range around the selection.
    RefPtrWillBeRawPtr<Range> forwardRange = Range::create(*document, position, lastPositionInNode(document->documentElement()).parentAnchoredEquivalent());
    CharacterIterator forwardIterator(forwardRange.get(), TextIteratorStopsOnFormControls);
    if (!forwardIterator.atEnd())
        forwardIterator.advance(maxLength - halfMaxLength);

    forwardRange = forwardIterator.range();
    if (!forwardRange || !Range::create(*document, position, forwardRange->startPosition())->text().length()) {
        ASSERT(forwardRange);
        return;
    }

    // Same as with the forward range but with the backward range. The range
    // starts at the document's start and ends at the selection start and will
    // be updated.
    RefPtrWillBeRawPtr<Range> backwardsRange = Range::create(*document, firstPositionInNode(document->documentElement()).parentAnchoredEquivalent(), position);
    BackwardsCharacterIterator backwardsIterator(backwardsRange.get(), TextIteratorStopsOnFormControls);
    if (!backwardsIterator.atEnd())
        backwardsIterator.advance(halfMaxLength);

    backwardsRange = backwardsIterator.range();
    if (!backwardsRange) {
        ASSERT(backwardsRange);
        return;
    }

    m_positionOffsetInContent = Range::create(*document, backwardsRange->endPosition(), position)->text().length();
    m_contentRange = Range::create(*document, backwardsRange->endPosition(), forwardRange->startPosition());
    ASSERT(m_contentRange);
}
Пример #5
0
Position InsertTextCommand::insertTab(const Position& pos)
{
    Position insertPos = VisiblePosition(pos, DOWNSTREAM).deepEquivalent();
    if (insertPos.isNull())
        return pos;

    Node* node = insertPos.containerNode();
    unsigned offset = node->isTextNode() ? insertPos.offsetInContainerNode() : 0;

    // keep tabs coalesced in tab span
    if (isTabSpanTextNode(node)) {
        RefPtrWillBeRawPtr<Text> textNode = toText(node);
        insertTextIntoNode(textNode, offset, "\t");
        return Position(textNode.release(), offset + 1);
    }

    // create new tab span
    RefPtrWillBeRawPtr<Element> spanNode = createTabSpanElement(document());

    // place it
    if (!node->isTextNode()) {
        insertNodeAt(spanNode.get(), insertPos);
    } else {
        RefPtrWillBeRawPtr<Text> textNode = toText(node);
        if (offset >= textNode->length())
            insertNodeAfter(spanNode, textNode.release());
        else {
            // split node to make room for the span
            // NOTE: splitTextNode uses textNode for the
            // second node in the split, so we need to
            // insert the span before it.
            if (offset > 0)
                splitTextNode(textNode, offset);
            insertNodeBefore(spanNode, textNode.release());
        }
    }

    // return the position following the new tab
    return lastPositionInNode(spanNode.get());
}
void VisibleSelection::adjustSelectionToAvoidCrossingShadowBoundaries()
{
    if (m_base.isNull() || m_start.isNull() || m_end.isNull())
        return;

    Node* startRootNode = m_start.anchorNode()->shadowTreeRootNode();
    Node* endRootNode = m_end.anchorNode()->shadowTreeRootNode();

    if (!startRootNode && !endRootNode)
        return;

    if (startRootNode == endRootNode)
        return;

    if (m_baseIsFirst) {
        m_extent = startRootNode ? lastPositionInNode(startRootNode) : positionBeforeNode(endRootNode->shadowHost());
        m_end = m_extent;
    } else {
        m_extent = endRootNode ? firstPositionInNode(endRootNode) : positionAfterNode(startRootNode->shadowHost());
        m_start = m_extent;
    }
}
Пример #7
0
VisibleSelection VisibleSelection::selectionFromContentsOfNode(Node* node)
{
    ASSERT(!editingIgnoresContent(node));
    return VisibleSelection(firstPositionInNode(node), lastPositionInNode(node), DOWNSTREAM);
}
Пример #8
0
void SpellChecker::chunkAndMarkAllMisspellingsAndBadGrammar(Node* node)
{
    if (!node)
        return;
    RefPtr<Range> rangeToCheck = Range::create(*m_frame.document(), firstPositionInNode(node), lastPositionInNode(node));
    TextCheckingParagraph textToCheck(rangeToCheck, rangeToCheck);
    bool asynchronous = true;
    chunkAndMarkAllMisspellingsAndBadGrammar(resolveTextCheckingTypeMask(TextCheckingTypeSpelling | TextCheckingTypeGrammar), textToCheck, asynchronous);
}
Пример #9
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);
}