int TextCheckingHelper::findFirstGrammarDetail(const Vector<GrammarDetail>& grammarDetails, int badGrammarPhraseLocation, int startOffset, int endOffset, bool markAll) const { // Found some bad grammar. Find the earliest detail range that starts in our search range (if any). // Optionally add a DocumentMarker for each detail in the range. int earliestDetailLocationSoFar = -1; int earliestDetailIndex = -1; for (unsigned i = 0; i < grammarDetails.size(); i++) { const GrammarDetail* detail = &grammarDetails[i]; ASSERT(detail->length > 0 && detail->location >= 0); int detailStartOffsetInParagraph = badGrammarPhraseLocation + detail->location; // Skip this detail if it starts before the original search range if (detailStartOffsetInParagraph < startOffset) continue; // Skip this detail if it starts after the original search range if (detailStartOffsetInParagraph >= endOffset) continue; if (markAll) { const EphemeralRange badGrammarRange = calculateCharacterSubrange(EphemeralRange(m_start, m_end), badGrammarPhraseLocation - startOffset + detail->location, detail->length); badGrammarRange.document().markers().addMarker(badGrammarRange.startPosition(), badGrammarRange.endPosition(), DocumentMarker::Grammar, detail->userDescription); } // Remember this detail only if it's earlier than our current candidate (the details aren't in a guaranteed order) if (earliestDetailIndex < 0 || earliestDetailLocationSoFar > detail->location) { earliestDetailIndex = i; earliestDetailLocationSoFar = detail->location; } } return earliestDetailIndex; }
EphemeralRange TextCheckingParagraph::offsetAsRange() const { ASSERT(m_checkingRange.isNotNull()); if (m_offsetAsRange.isNull()) m_offsetAsRange = EphemeralRange(paragraphRange().startPosition(), checkingRange().startPosition()); return m_offsetAsRange; }
String TextCheckingHelper::findFirstBadGrammar(GrammarDetail& outGrammarDetail, int& outGrammarPhraseOffset, bool markAll) { // Initialize out parameters; these will be updated if we find something to return. outGrammarDetail.location = -1; outGrammarDetail.length = 0; outGrammarDetail.guesses.clear(); outGrammarDetail.userDescription = ""; outGrammarPhraseOffset = 0; String firstBadGrammarPhrase; // Expand the search range to encompass entire paragraphs, since grammar checking needs that much context. // Determine the character offset from the start of the paragraph to the start of the original search range, // since we will want to ignore results in this area. TextCheckingParagraph paragraph(EphemeralRange(m_start, m_end)); // Start checking from beginning of paragraph, but skip past results that occur before the start of the original search range. int startOffset = 0; while (startOffset < paragraph.checkingEnd()) { Vector<GrammarDetail> grammarDetails; int badGrammarPhraseLocation = -1; int badGrammarPhraseLength = 0; m_client->textChecker().checkGrammarOfString(paragraph.textSubstring(startOffset), grammarDetails, &badGrammarPhraseLocation, &badGrammarPhraseLength); if (!badGrammarPhraseLength) { ASSERT(badGrammarPhraseLocation == -1); return String(); } ASSERT(badGrammarPhraseLocation >= 0); badGrammarPhraseLocation += startOffset; // Found some bad grammar. Find the earliest detail range that starts in our search range (if any). int badGrammarIndex = findFirstGrammarDetail(grammarDetails, badGrammarPhraseLocation, paragraph.checkingStart(), paragraph.checkingEnd(), markAll); if (badGrammarIndex >= 0) { ASSERT(static_cast<unsigned>(badGrammarIndex) < grammarDetails.size()); outGrammarDetail = grammarDetails[badGrammarIndex]; } // If we found a detail in range, then we have found the first bad phrase (unless we found one earlier but // kept going so we could mark all instances). if (badGrammarIndex >= 0 && firstBadGrammarPhrase.isEmpty()) { outGrammarPhraseOffset = badGrammarPhraseLocation - paragraph.checkingStart(); firstBadGrammarPhrase = paragraph.textSubstring(badGrammarPhraseLocation, badGrammarPhraseLength); // Found one. We're done now, unless we're marking each instance. if (!markAll) break; } // These results were all between the start of the paragraph and the start of the search range; look // beyond this phrase. startOffset = badGrammarPhraseLocation + badGrammarPhraseLength; } return firstBadGrammarPhrase; }
String AbstractInlineTextBox::text() const { if (!m_inlineTextBox || !m_lineLayoutItem) return String(); unsigned start = m_inlineTextBox->start(); unsigned len = m_inlineTextBox->len(); if (Node* node = m_lineLayoutItem.node()) { if (node->isTextNode()) return plainText(EphemeralRange(Position(node, start), Position(node, start + len)), TextIteratorIgnoresStyleVisibility); return plainText(EphemeralRange(Position(node, PositionAnchorType::BeforeAnchor), Position(node, PositionAnchorType::AfterAnchor)), TextIteratorIgnoresStyleVisibility); } String result = m_lineLayoutItem.text().substring(start, len).simplifyWhiteSpace(WTF::DoNotStripWhiteSpace); if (m_inlineTextBox->nextTextBox() && m_inlineTextBox->nextTextBox()->start() > m_inlineTextBox->end() && result.length() && !result.right(1).containsOnlyWhitespace()) return result + " "; return result; }
String TextCheckingHelper::findFirstMisspelling(int& firstMisspellingOffset, bool markAll) { WordAwareIterator it(m_start, m_end); firstMisspellingOffset = 0; String firstMisspelling; int currentChunkOffset = 0; while (!it.atEnd()) { int length = it.length(); // Skip some work for one-space-char hunks if (!(length == 1 && it.characterAt(0) == ' ')) { int misspellingLocation = -1; int misspellingLength = 0; m_client->textChecker().checkSpellingOfString(it.substring(0, length), &misspellingLocation, &misspellingLength); // 5490627 shows that there was some code path here where the String constructor below crashes. // We don't know exactly what combination of bad input caused this, so we're making this much // more robust against bad input on release builds. ASSERT(misspellingLength >= 0); ASSERT(misspellingLocation >= -1); ASSERT(!misspellingLength || misspellingLocation >= 0); ASSERT(misspellingLocation < length); ASSERT(misspellingLength <= length); ASSERT(misspellingLocation + misspellingLength <= length); if (misspellingLocation >= 0 && misspellingLength > 0 && misspellingLocation < length && misspellingLength <= length && misspellingLocation + misspellingLength <= length) { // Compute range of misspelled word const EphemeralRange misspellingRange = calculateCharacterSubrange(EphemeralRange(m_start, m_end), currentChunkOffset + misspellingLocation, misspellingLength); // Remember first-encountered misspelling and its offset. if (!firstMisspelling) { firstMisspellingOffset = currentChunkOffset + misspellingLocation; firstMisspelling = it.substring(misspellingLocation, misspellingLength); } // Store marker for misspelled word. misspellingRange.document().markers().addMarker(misspellingRange.startPosition(), misspellingRange.endPosition(), DocumentMarker::Spelling); // Bail out if we're marking only the first misspelling, and not all instances. if (!markAll) break; } } currentChunkOffset += length; it.advance(); } return firstMisspelling; }
void SpellCheckRequester::didCheckSucceed(int sequence, const Vector<TextCheckingResult>& results) { TextCheckingRequestData requestData = m_processingRequest->data(); if (requestData.sequence() == sequence) { DocumentMarker::MarkerTypes markers = DocumentMarker::SpellCheckClientMarkers(); if (!requestData.maskContains(TextCheckingTypeSpelling)) markers.remove(DocumentMarker::Spelling); if (!requestData.maskContains(TextCheckingTypeGrammar)) markers.remove(DocumentMarker::Grammar); RefPtrWillBeRawPtr<Range> checkingRange = m_processingRequest->checkingRange(); frame().document()->markers().removeMarkers(EphemeralRange(checkingRange.get()), markers); } didCheck(sequence, results); }
TEST_F(CharacterIteratorTest, SubrangeWithReplacedElements) { static const char* bodyContent = "<div id='div' contenteditable='true'>1<img src='foo.png'>345</div>"; setBodyContent(bodyContent); document().view()->updateAllLifecyclePhases(); Node* divNode = document().getElementById("div"); Range* entireRange = Range::create(document(), divNode, 0, divNode, 3); EphemeralRange result = calculateCharacterSubrange(EphemeralRange(entireRange), 2, 3); Node* textNode = divNode->lastChild(); EXPECT_EQ(Position(textNode, 0), result.startPosition()); EXPECT_EQ(Position(textNode, 3), result.endPosition()); }
static EphemeralRange expandEndToSentenceBoundary(const EphemeralRange& range) { DCHECK(range.isNotNull()); const VisiblePosition& visibleEnd = createVisiblePosition(range.endPosition()); DCHECK(visibleEnd.isNotNull()); const Position& sentenceEnd = endOfSentence(visibleEnd).deepEquivalent(); // TODO(xiaochengh): |sentenceEnd < range.endPosition()| is possible, // which would trigger a DCHECK in EphemeralRange's constructor if we return // it directly. However, this shouldn't happen and needs to be fixed. return EphemeralRange( range.startPosition(), sentenceEnd.isNotNull() && sentenceEnd > range.endPosition() ? sentenceEnd : range.endPosition()); }
void InputMethodController::setComposition(const String& text, const Vector<CompositionUnderline>& underlines, unsigned selectionStart, unsigned selectionEnd) { Editor::RevealSelectionScope revealSelectionScope(&editor()); // Updates styles before setting selection for composition to prevent // inserting the previous composition text into text nodes oddly. // See https://bugs.webkit.org/show_bug.cgi?id=46868 frame().document()->updateLayoutTreeIfNeeded(); selectComposition(); if (frame().selection().isNone()) return; if (Element* target = frame().document()->focusedElement()) { // Dispatch an appropriate composition event to the focused node. // We check the composition status and choose an appropriate composition event since this // function is used for three purposes: // 1. Starting a new composition. // Send a compositionstart and a compositionupdate event when this function creates // a new composition node, i.e. // !hasComposition() && !text.isEmpty(). // Sending a compositionupdate event at this time ensures that at least one // compositionupdate event is dispatched. // 2. Updating the existing composition node. // Send a compositionupdate event when this function updates the existing composition // node, i.e. hasComposition() && !text.isEmpty(). // 3. Canceling the ongoing composition. // Send a compositionend event when function deletes the existing composition node, i.e. // !hasComposition() && test.isEmpty(). RefPtrWillBeRawPtr<CompositionEvent> event = nullptr; if (!hasComposition()) { // We should send a compositionstart event only when the given text is not empty because this // function doesn't create a composition node when the text is empty. if (!text.isEmpty()) { target->dispatchEvent(CompositionEvent::create(EventTypeNames::compositionstart, frame().domWindow(), frame().selectedText())); event = CompositionEvent::create(EventTypeNames::compositionupdate, frame().domWindow(), text); } } else { if (!text.isEmpty()) event = CompositionEvent::create(EventTypeNames::compositionupdate, frame().domWindow(), text); else event = CompositionEvent::create(EventTypeNames::compositionend, frame().domWindow(), text); } if (event.get()) target->dispatchEvent(event); } // If text is empty, then delete the old composition here. If text is non-empty, InsertTextCommand::input // will delete the old composition with an optimized replace operation. if (text.isEmpty()) { ASSERT(frame().document()); TypingCommand::deleteSelection(*frame().document(), TypingCommand::PreventSpellChecking); } clear(); if (text.isEmpty()) return; ASSERT(frame().document()); TypingCommand::insertText(*frame().document(), text, TypingCommand::SelectInsertedText | TypingCommand::PreventSpellChecking, TypingCommand::TextCompositionUpdate); // Find out what node has the composition now. Position base = mostForwardCaretPosition(frame().selection().base()); Node* baseNode = base.anchorNode(); if (!baseNode || !baseNode->isTextNode()) return; Position extent = frame().selection().extent(); Node* extentNode = extent.anchorNode(); if (baseNode != extentNode) return; unsigned extentOffset = extent.computeOffsetInContainerNode(); unsigned baseOffset = base.computeOffsetInContainerNode(); if (baseOffset + text.length() != extentOffset) return; m_isDirty = true; m_hasComposition = true; if (!m_compositionRange) m_compositionRange = Range::create(baseNode->document()); m_compositionRange->setStart(baseNode, baseOffset); m_compositionRange->setEnd(baseNode, extentOffset); if (baseNode->layoutObject()) baseNode->layoutObject()->setShouldDoFullPaintInvalidation(); unsigned start = std::min(baseOffset + selectionStart, extentOffset); unsigned end = std::min(std::max(start, baseOffset + selectionEnd), extentOffset); RefPtrWillBeRawPtr<Range> selectedRange = Range::create(baseNode->document(), baseNode, start, baseNode, end); frame().selection().setSelectedRange(selectedRange.get(), TextAffinity::Downstream, SelectionDirectionalMode::NonDirectional, NotUserTriggered); if (underlines.isEmpty()) { frame().document()->markers().addCompositionMarker(m_compositionRange->startPosition(), m_compositionRange->endPosition(), Color::black, false, LayoutTheme::theme().platformDefaultCompositionBackgroundColor()); return; } for (const auto& underline : underlines) { unsigned underlineStart = baseOffset + underline.startOffset; unsigned underlineEnd = baseOffset + underline.endOffset; EphemeralRange ephemeralLineRange = EphemeralRange(Position(baseNode, underlineStart), Position(baseNode, underlineEnd)); if (ephemeralLineRange.isNull()) continue; frame().document()->markers().addCompositionMarker(ephemeralLineRange.startPosition(), ephemeralLineRange.endPosition(), underline.color, underline.thick, underline.backgroundColor); } }
String TextCheckingHelper::findFirstMisspellingOrBadGrammar(bool& outIsSpelling, int& outFirstFoundOffset, GrammarDetail& outGrammarDetail) { if (!unifiedTextCheckerEnabled()) return ""; String firstFoundItem; String misspelledWord; String badGrammarPhrase; // Initialize out parameters; these will be updated if we find something to return. outIsSpelling = true; outFirstFoundOffset = 0; outGrammarDetail.location = -1; outGrammarDetail.length = 0; outGrammarDetail.guesses.clear(); outGrammarDetail.userDescription = ""; // Expand the search range to encompass entire paragraphs, since text checking needs that much context. // Determine the character offset from the start of the paragraph to the start of the original search range, // since we will want to ignore results in this area. Position paragraphStart = startOfParagraph(createVisiblePosition(m_start)).toParentAnchoredPosition(); Position paragraphEnd = m_end; int totalRangeLength = TextIterator::rangeLength(paragraphStart, paragraphEnd); paragraphEnd = endOfParagraph(createVisiblePosition(m_start)).toParentAnchoredPosition(); int rangeStartOffset = TextIterator::rangeLength(paragraphStart, m_start); int totalLengthProcessed = 0; bool firstIteration = true; bool lastIteration = false; while (totalLengthProcessed < totalRangeLength) { // Iterate through the search range by paragraphs, checking each one for spelling and grammar. int currentLength = TextIterator::rangeLength(paragraphStart, paragraphEnd); int currentStartOffset = firstIteration ? rangeStartOffset : 0; int currentEndOffset = currentLength; if (inSameParagraph(createVisiblePosition(paragraphStart), createVisiblePosition(m_end))) { // Determine the character offset from the end of the original search range to the end of the paragraph, // since we will want to ignore results in this area. currentEndOffset = TextIterator::rangeLength(paragraphStart, m_end); lastIteration = true; } if (currentStartOffset < currentEndOffset) { String paragraphString = plainText(EphemeralRange(paragraphStart, paragraphEnd)); if (paragraphString.length() > 0) { bool foundGrammar = false; int spellingLocation = 0; int grammarPhraseLocation = 0; int grammarDetailLocation = 0; unsigned grammarDetailIndex = 0; Vector<TextCheckingResult> results; TextCheckingTypeMask checkingTypes = TextCheckingTypeSpelling | TextCheckingTypeGrammar; checkTextOfParagraph(m_client->textChecker(), paragraphString, checkingTypes, results); for (unsigned i = 0; i < results.size(); i++) { const TextCheckingResult* result = &results[i]; if (result->decoration == TextDecorationTypeSpelling && result->location >= currentStartOffset && result->location + result->length <= currentEndOffset) { ASSERT(result->length > 0 && result->location >= 0); spellingLocation = result->location; misspelledWord = paragraphString.substring(result->location, result->length); ASSERT(misspelledWord.length()); break; } if (result->decoration == TextDecorationTypeGrammar && result->location < currentEndOffset && result->location + result->length > currentStartOffset) { ASSERT(result->length > 0 && result->location >= 0); // We can't stop after the first grammar result, since there might still be a spelling result after // it begins but before the first detail in it, but we can stop if we find a second grammar result. if (foundGrammar) break; for (unsigned j = 0; j < result->details.size(); j++) { const GrammarDetail* detail = &result->details[j]; ASSERT(detail->length > 0 && detail->location >= 0); if (result->location + detail->location >= currentStartOffset && result->location + detail->location + detail->length <= currentEndOffset && (!foundGrammar || result->location + detail->location < grammarDetailLocation)) { grammarDetailIndex = j; grammarDetailLocation = result->location + detail->location; foundGrammar = true; } } if (foundGrammar) { grammarPhraseLocation = result->location; outGrammarDetail = result->details[grammarDetailIndex]; badGrammarPhrase = paragraphString.substring(result->location, result->length); ASSERT(badGrammarPhrase.length()); } } } if (!misspelledWord.isEmpty() && (badGrammarPhrase.isEmpty() || spellingLocation <= grammarDetailLocation)) { int spellingOffset = spellingLocation - currentStartOffset; if (!firstIteration) spellingOffset += TextIterator::rangeLength(m_start, paragraphStart); outIsSpelling = true; outFirstFoundOffset = spellingOffset; firstFoundItem = misspelledWord; break; } if (!badGrammarPhrase.isEmpty()) { int grammarPhraseOffset = grammarPhraseLocation - currentStartOffset; if (!firstIteration) grammarPhraseOffset += TextIterator::rangeLength(m_start, paragraphStart); outIsSpelling = false; outFirstFoundOffset = grammarPhraseOffset; firstFoundItem = badGrammarPhrase; break; } } } if (lastIteration || totalLengthProcessed + currentLength >= totalRangeLength) break; VisiblePosition newParagraphStart = startOfNextParagraph(createVisiblePosition(paragraphEnd)); paragraphStart = newParagraphStart.toParentAnchoredPosition(); paragraphEnd = endOfParagraph(newParagraphStart).toParentAnchoredPosition(); firstIteration = false; totalLengthProcessed += currentLength; } return firstFoundItem; }
PlainTextRange PlainTextRange::create(const ContainerNode& scope, const Range& range) { return create(scope, EphemeralRange(&range)); }
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); }
EphemeralRange PlainTextRange::createRangeFor(const ContainerNode& scope, GetRangeFor getRangeFor) const { ASSERT(isNotNull()); size_t docTextPosition = 0; bool startRangeFound = false; Position textRunStartPosition; Position textRunEndPosition; TextIteratorBehaviorFlags behaviorFlags = TextIteratorEmitsObjectReplacementCharacter; if (getRangeFor == ForSelection) behaviorFlags |= TextIteratorEmitsCharactersBetweenAllVisiblePositions; auto range = EphemeralRange::rangeOfContents(scope); TextIterator it(range.startPosition(), range.endPosition(), behaviorFlags); // FIXME: the atEnd() check shouldn't be necessary, workaround for // <http://bugs.webkit.org/show_bug.cgi?id=6289>. if (!start() && !length() && it.atEnd()) return EphemeralRange(Position(it.currentContainer(), 0)); Position resultStart = Position(&scope.document(), 0); Position resultEnd = resultStart; for (; !it.atEnd(); it.advance()) { int len = it.length(); textRunStartPosition = it.startPositionInCurrentContainer(); textRunEndPosition = it.endPositionInCurrentContainer(); bool foundStart = start() >= docTextPosition && start() <= docTextPosition + len; bool foundEnd = end() >= docTextPosition && end() <= docTextPosition + len; // Fix textRunRange->endPosition(), but only if foundStart || foundEnd, because it is only // in those cases that textRunRange is used. if (foundEnd) { // FIXME: This is a workaround for the fact that the end of a run // is often at the wrong position for emitted '\n's or if the // layoutObject of the current node is a replaced element. if (len == 1 && (it.characterAt(0) == '\n' || it.isInsideAtomicInlineElement())) { it.advance(); if (!it.atEnd()) { textRunEndPosition = it.startPositionInCurrentContainer(); } else { Position runEnd = nextPositionOf(createVisiblePosition(textRunStartPosition)).deepEquivalent(); if (runEnd.isNotNull()) textRunEndPosition = runEnd; } } } if (foundStart) { startRangeFound = true; if (textRunStartPosition.computeContainerNode()->isTextNode()) { int offset = start() - docTextPosition; resultStart = Position(textRunStartPosition.computeContainerNode(), offset + textRunStartPosition.offsetInContainerNode()); } else { if (start() == docTextPosition) resultStart = textRunStartPosition; else resultStart = textRunEndPosition; } } if (foundEnd) { if (textRunStartPosition.computeContainerNode()->isTextNode()) { int offset = end() - docTextPosition; resultEnd = Position(textRunStartPosition.computeContainerNode(), offset + textRunStartPosition.offsetInContainerNode()); } else { if (end() == docTextPosition) resultEnd = textRunStartPosition; else resultEnd = textRunEndPosition; } docTextPosition += len; break; } docTextPosition += len; } if (!startRangeFound) return EphemeralRange(); if (length() && end() > docTextPosition) { // end() is out of bounds resultEnd = textRunEndPosition; } return EphemeralRange(resultStart.toOffsetInAnchor(), resultEnd.toOffsetInAnchor()); }
void TextCheckingParagraph::invalidateParagraphRangeValues() { m_checkingStart = m_checkingEnd = -1; m_offsetAsRange = EphemeralRange(); m_text = String(); }
void TextCheckingParagraph::expandRangeToNextEnd() { ASSERT(m_checkingRange.isNotNull()); setParagraphRange(EphemeralRange(paragraphRange().startPosition(), endOfParagraph(startOfNextParagraph(createVisiblePosition(paragraphRange().startPosition()))).deepEquivalent())); invalidateParagraphRangeValues(); }
bool TextFinder::setMarkerActive(Range* range, bool active) { if (!range || range->collapsed()) return false; return ownerFrame().frame()->document()->markers().setMarkersActive( EphemeralRange(range), active); }
static EphemeralRange expandToParagraphBoundary(const EphemeralRange& range) { return EphemeralRange(startOfParagraph(createVisiblePosition(range.startPosition())).deepEquivalent(), endOfParagraph(createVisiblePosition(range.endPosition())).deepEquivalent()); }
// static PassRefPtrWillBeRawPtr<SpellCheckRequest> SpellCheckRequest::create(TextCheckingTypeMask textCheckingOptions, TextCheckingProcessType processType, PassRefPtrWillBeRawPtr<Range> checkingRange, PassRefPtrWillBeRawPtr<Range> paragraphRange, int requestNumber) { ASSERT(checkingRange); ASSERT(paragraphRange); String text = checkingRange->text(); if (!text.length()) return nullptr; const DocumentMarkerVector& markers = checkingRange->ownerDocument().markers().markersInRange(EphemeralRange(checkingRange.get()), DocumentMarker::SpellCheckClientMarkers()); Vector<uint32_t> hashes(markers.size()); Vector<unsigned> offsets(markers.size()); for (size_t i = 0; i < markers.size(); i++) { hashes[i] = markers[i]->hash(); offsets[i] = markers[i]->startOffset(); } return adoptRefWillBeNoop(new SpellCheckRequest(checkingRange, paragraphRange, text, textCheckingOptions, processType, hashes, offsets, requestNumber)); }
void DOMSelection::addRange(Range* newRange) { DCHECK(newRange); if (!isAvailable()) return; if (newRange->ownerDocument() != frame()->document()) return; if (!newRange->isConnected()) { addConsoleError("The given range isn't in document."); return; } FrameSelection& selection = frame()->selection(); if (newRange->ownerDocument() != selection.document()) { // "editing/selection/selection-in-iframe-removed-crash.html" goes here. return; } // TODO(xiaochengh): The use of updateStyleAndLayoutIgnorePendingStylesheets // needs to be audited. See http://crbug.com/590369 for more details. // In the long term, we should change FrameSelection::setSelection to take a // parameter that does not require clean layout, so that modifying selection // no longer performs synchronous layout by itself. frame()->document()->updateStyleAndLayoutIgnorePendingStylesheets(); if (selection.isNone()) { selection.setSelectedRange(EphemeralRange(newRange), VP_DEFAULT_AFFINITY); return; } Range* originalRange = selection.firstRange(); if (originalRange->startContainer()->document() != newRange->startContainer()->document()) { addConsoleError( "The given range does not belong to the current selection's document."); return; } if (originalRange->startContainer()->treeScope() != newRange->startContainer()->treeScope()) { addConsoleError( "The given range and the current selection belong to two different " "document fragments."); return; } if (originalRange->compareBoundaryPoints(Range::kStartToEnd, newRange, ASSERT_NO_EXCEPTION) < 0 || newRange->compareBoundaryPoints(Range::kStartToEnd, originalRange, ASSERT_NO_EXCEPTION) < 0) { addConsoleError("Discontiguous selection is not supported."); return; } // FIXME: "Merge the ranges if they intersect" is Blink-specific behavior; // other browsers supporting discontiguous selection (obviously) keep each // Range added and return it in getRangeAt(). But it's unclear if we can // really do the same, since we don't support discontiguous selection. Further // discussions at // <https://code.google.com/p/chromium/issues/detail?id=353069>. Range* start = originalRange->compareBoundaryPoints( Range::kStartToStart, newRange, ASSERT_NO_EXCEPTION) < 0 ? originalRange : newRange; Range* end = originalRange->compareBoundaryPoints(Range::kEndToEnd, newRange, ASSERT_NO_EXCEPTION) < 0 ? newRange : originalRange; const EphemeralRange merged = EphemeralRange(start->startPosition(), end->endPosition()); TextAffinity affinity = selection.selection().affinity(); selection.setSelectedRange(merged, affinity); }
EphemeralRange InputMethodController::compositionEphemeralRange() const { if (!hasComposition()) return EphemeralRange(); return EphemeralRange(m_compositionRange.get()); }