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); }
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()); }
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(); }
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); }
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; } }
VisibleSelection VisibleSelection::selectionFromContentsOfNode(Node* node) { ASSERT(!editingIgnoresContent(node)); return VisibleSelection(firstPositionInNode(node), lastPositionInNode(node), DOWNSTREAM); }
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); }
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); }