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); }
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; }
// static PassRefPtrWillBeRawPtr<SpellCheckRequest> SpellCheckRequest::create(TextCheckingTypeMask textCheckingOptions, TextCheckingProcessType processType, const EphemeralRange& checkingRange, const EphemeralRange& paragraphRange, int requestNumber) { if (checkingRange.isNull()) return nullptr; if (!checkingRange.startPosition().computeContainerNode()->rootEditableElement()) return nullptr; String text = plainText(checkingRange, TextIteratorEmitsObjectReplacementCharacter); if (text.isEmpty()) return nullptr; RefPtrWillBeRawPtr<Range> checkingRangeObject = createRange(checkingRange); RefPtrWillBeRawPtr<Range> paragraphRangeObject = nullptr; // Share identical Range objects. if (checkingRange == paragraphRange) paragraphRangeObject = checkingRangeObject; else paragraphRangeObject = createRange(paragraphRange); const DocumentMarkerVector& markers = checkingRangeObject->ownerDocument().markers().markersInRange(checkingRange, 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(checkingRangeObject, paragraphRangeObject, text, textCheckingOptions, processType, hashes, offsets, requestNumber)); }
void InputMethodController::setCompositionFromExistingText(const Vector<CompositionUnderline>& underlines, unsigned compositionStart, unsigned compositionEnd) { Element* editable = frame().selection().rootEditableElement(); if (!editable) return; const EphemeralRange range = PlainTextRange(compositionStart, compositionEnd).createRange(*editable); if (range.isNull()) return; const Position start = range.startPosition(); if (editableRootForPosition(start) != editable) return; const Position end = range.endPosition(); if (editableRootForPosition(end) != editable) return; clear(); for (const auto& underline : underlines) { unsigned underlineStart = compositionStart + underline.startOffset; unsigned underlineEnd = compositionStart + underline.endOffset; EphemeralRange ephemeralLineRange = PlainTextRange(underlineStart, underlineEnd).createRange(*editable); if (ephemeralLineRange.isNull()) continue; frame().document()->markers().addCompositionMarker(ephemeralLineRange.startPosition(), ephemeralLineRange.endPosition(), underline.color, underline.thick, underline.backgroundColor); } m_hasComposition = true; if (!m_compositionRange) m_compositionRange = Range::create(range.document()); m_compositionRange->setStart(range.startPosition()); m_compositionRange->setEnd(range.endPosition()); }
EphemeralRange LocalFrame::rangeForPoint(const IntPoint& framePoint) { const PositionWithAffinity positionWithAffinity = positionForPoint(framePoint); if (positionWithAffinity.isNull()) return EphemeralRange(); VisiblePosition position = createVisiblePosition(positionWithAffinity); VisiblePosition previous = previousPositionOf(position); if (previous.isNotNull()) { const EphemeralRange previousCharacterRange = makeRange(previous, position); IntRect rect = editor().firstRectForRange(previousCharacterRange); if (rect.contains(framePoint)) return EphemeralRange(previousCharacterRange); } VisiblePosition next = nextPositionOf(position); const EphemeralRange nextCharacterRange = makeRange(position, next); if (nextCharacterRange.isNotNull()) { IntRect rect = editor().firstRectForRange(nextCharacterRange); if (rect.contains(framePoint)) return EphemeralRange(nextCharacterRange); } return EphemeralRange(); }
bool DOMSelection::containsNode(const Node* n, bool allowPartial) const { DCHECK(n); if (!isAvailable()) return false; FrameSelection& selection = frame()->selection(); if (frame()->document() != n->document() || selection.isNone()) return false; unsigned nodeIndex = n->nodeIndex(); // TODO(xiaochengh): The use of updateStyleAndLayoutIgnorePendingStylesheets // needs to be audited. See http://crbug.com/590369 for more details. // |VisibleSelection::toNormalizedEphemeralRange| requires clean layout. frame()->document()->updateStyleAndLayoutIgnorePendingStylesheets(); const EphemeralRange selectedRange = selection.selection().toNormalizedEphemeralRange(); ContainerNode* parentNode = n->parentNode(); if (!parentNode) return false; const Position startPosition = selectedRange.startPosition().toOffsetInAnchor(); const Position endPosition = selectedRange.endPosition().toOffsetInAnchor(); TrackExceptionState exceptionState; bool nodeFullySelected = Range::compareBoundaryPoints( parentNode, nodeIndex, startPosition.computeContainerNode(), startPosition.offsetInContainerNode(), exceptionState) >= 0 && !exceptionState.hadException() && Range::compareBoundaryPoints( parentNode, nodeIndex + 1, endPosition.computeContainerNode(), endPosition.offsetInContainerNode(), exceptionState) <= 0 && !exceptionState.hadException(); if (exceptionState.hadException()) return false; if (nodeFullySelected) return true; bool nodeFullyUnselected = (Range::compareBoundaryPoints( parentNode, nodeIndex, endPosition.computeContainerNode(), endPosition.offsetInContainerNode(), exceptionState) > 0 && !exceptionState.hadException()) || (Range::compareBoundaryPoints( parentNode, nodeIndex + 1, startPosition.computeContainerNode(), startPosition.offsetInContainerNode(), exceptionState) < 0 && !exceptionState.hadException()); DCHECK(!exceptionState.hadException()); if (nodeFullyUnselected) return false; return allowPartial || n->isTextNode(); }
PlainTextRange InputMethodController::getSelectionOffsets() const { EphemeralRange range = firstEphemeralRangeOf(frame().selection().selection()); if (range.isNull()) return PlainTextRange(); ContainerNode* editable = frame().selection().rootEditableElementOrTreeScopeRootNode(); ASSERT(editable); return PlainTextRange::create(*editable, range); }
static void frameContentAsPlainText(size_t maxChars, LocalFrame* frame, StringBuilder& output) { Document* document = frame->document(); if (!document) return; if (!frame->view()) return; // Select the document body. if (document->body()) { const EphemeralRange range = EphemeralRange::rangeOfContents(*document->body()); // The text iterator will walk nodes giving us text. This is similar to // the plainText() function in core/editing/TextIterator.h, but we implement the maximum // size and also copy the results directly into a wstring, avoiding the // string conversion. for (TextIterator it(range.startPosition(), range.endPosition()); !it.atEnd(); it.advance()) { it.text().appendTextToStringBuilder(output, 0, maxChars - output.length()); if (output.length() >= maxChars) return; // Filled up the buffer. } } // The separator between frames when the frames are converted to plain text. const LChar frameSeparator[] = { '\n', '\n' }; const size_t frameSeparatorLength = WTF_ARRAY_LENGTH(frameSeparator); // Recursively walk the children. const FrameTree& frameTree = frame->tree(); for (Frame* curChild = frameTree.firstChild(); curChild; curChild = curChild->tree().nextSibling()) { if (!curChild->isLocalFrame()) continue; LocalFrame* curLocalChild = toLocalFrame(curChild); // Ignore the text of non-visible frames. LayoutView* contentLayoutObject = curLocalChild->contentLayoutObject(); LayoutPart* ownerLayoutObject = curLocalChild->ownerLayoutObject(); if (!contentLayoutObject || !contentLayoutObject->size().width() || !contentLayoutObject->size().height() || (contentLayoutObject->location().x() + contentLayoutObject->size().width() <= 0) || (contentLayoutObject->location().y() + contentLayoutObject->size().height() <= 0) || (ownerLayoutObject && ownerLayoutObject->style() && ownerLayoutObject->style()->visibility() != VISIBLE)) { continue; } // Make sure the frame separator won't fill up the buffer, and give up if // it will. The danger is if the separator will make the buffer longer than // maxChars. This will cause the computation above: // maxChars - output->size() // to be a negative number which will crash when the subframe is added. if (output.length() >= maxChars - frameSeparatorLength) return; output.append(frameSeparator, frameSeparatorLength); frameContentAsPlainText(maxChars, curLocalChild, output); if (output.length() >= maxChars) return; // Filled up the buffer. } }
// static WebRange WebRange::fromDocumentRange(WebLocalFrame* frame, int start, int length) { LocalFrame* webFrame = toWebLocalFrameImpl(frame)->frame(); Element* selectionRoot = webFrame->selection().rootEditableElement(); ContainerNode* scope = selectionRoot ? selectionRoot : webFrame->document()->documentElement(); const EphemeralRange range = PlainTextRange(start, start + length).createRange(*scope); if (range.isNull()) return WebRange(); return Range::create(range.document(), range.startPosition(), range.endPosition()); }
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 InputMethodController::selectComposition() const { const EphemeralRange range = compositionEphemeralRange(); if (range.isNull()) return; // The composition can start inside a composed character sequence, so we have to override checks. // See <http://bugs.webkit.org/show_bug.cgi?id=15781> VisibleSelection selection; selection.setWithoutValidation(range.startPosition(), range.endPosition()); frame().selection().setSelection(selection, 0); }
static bool shouldPreserveNewline(const EphemeralRange& range) { if (Node* node = range.startPosition().nodeAsRangeFirstNode()) { if (LayoutObject* layoutObject = node->layoutObject()) return layoutObject->style()->preserveNewline(); } if (Node* node = range.startPosition().anchorNode()) { if (LayoutObject* layoutObject = node->layoutObject()) return layoutObject->style()->preserveNewline(); } return false; }
bool InputMethodController::setSelectionOffsets(const PlainTextRange& selectionOffsets) { if (selectionOffsets.isNull()) return false; Element* rootEditableElement = frame().selection().rootEditableElement(); if (!rootEditableElement) return false; const EphemeralRange range = selectionOffsets.createRange(*rootEditableElement); if (range.isNull()) return false; return frame().selection().setSelectedRange(range, VP_DEFAULT_AFFINITY, SelectionDirectionalMode::NonDirectional, FrameSelection::CloseTyping); }
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()); }
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()); }
TEST_F(DocumentMarkerControllerTest, SetMarkerActiveTest) { setBodyInnerHTML("<b>foo</b>"); Element* bElement = toElement(document().body()->firstChild()); EphemeralRange ephemeralRange = EphemeralRange::rangeOfContents(*bElement); Position startBElement = toPositionInDOMTree(ephemeralRange.startPosition()); Position endBElement = toPositionInDOMTree(ephemeralRange.endPosition()); Range* range = Range::create(document(), startBElement, endBElement); // Try to make active a marker that doesn't exist. EXPECT_FALSE(markerController().setMarkersActive(range, true)); // Add a marker and try it once more. markerController().addTextMatchMarker(range, false); EXPECT_EQ(1u, markerController().markers().size()); EXPECT_TRUE(markerController().setMarkersActive(range, true)); }
bool SelectionEditor::setSelectedRange(const EphemeralRange& range, TextAffinity affinity, SelectionDirectionalMode directional, FrameSelection::SetSelectionOptions options) { if (range.isNull()) return false; // Non-collapsed ranges are not allowed to start at the end of a line that is wrapped, // they start at the beginning of the next line instead m_logicalRange = nullptr; stopObservingVisibleSelectionChangeIfNecessary(); // Since |FrameSeleciton::setSelection()| dispatches events and DOM tree // can be modified by event handlers, we should create |Range| object before // calling it. m_logicalRange = createRange(range); VisibleSelection newSelection(range.startPosition(), range.endPosition(), affinity, directional == SelectionDirectionalMode::Directional); m_frameSelection->setSelection(newSelection, options); startObservingVisibleSelectionChange(); return true; }
PlainTextRange PlainTextRange::create(const ContainerNode& scope, const EphemeralRange& range) { if (range.isNull()) return PlainTextRange(); // The critical assumption is that this only gets called with ranges that // concentrate on a given area containing the selection root. This is done // because of text fields and textareas. The DOM for those is not // directly in the document DOM, so ensure that the range does not cross a // boundary of one of those. Node* startContainer = range.startPosition().computeContainerNode(); if (startContainer != &scope && !startContainer->isDescendantOf(&scope)) return PlainTextRange(); Node* endContainer = range.endPosition().computeContainerNode(); if (endContainer != scope && !endContainer->isDescendantOf(&scope)) return PlainTextRange(); size_t start = TextIterator::rangeLength(Position(&const_cast<ContainerNode&>(scope), 0), range.startPosition()); size_t end = TextIterator::rangeLength(Position(&const_cast<ContainerNode&>(scope), 0), range.endPosition()); return PlainTextRange(start, end); }
bool DOMSelection::containsNode(const Node* n, bool allowPartial) const { ASSERT(n); if (!m_frame) return false; FrameSelection& selection = m_frame->selection(); if (m_frame->document() != n->document() || selection.isNone()) return false; unsigned nodeIndex = n->nodeIndex(); const EphemeralRange selectedRange = selection.selection().toNormalizedEphemeralRange(); ContainerNode* parentNode = n->parentNode(); if (!parentNode) return false; const Position startPosition = selectedRange.startPosition().toOffsetInAnchor(); const Position endPosition = selectedRange.endPosition().toOffsetInAnchor(); TrackExceptionState exceptionState; bool nodeFullySelected = Range::compareBoundaryPoints(parentNode, nodeIndex, startPosition.computeContainerNode(), startPosition.offsetInContainerNode(), exceptionState) >= 0 && !exceptionState.hadException() && Range::compareBoundaryPoints(parentNode, nodeIndex + 1, endPosition.computeContainerNode(), endPosition.offsetInContainerNode(), exceptionState) <= 0 && !exceptionState.hadException(); if (exceptionState.hadException()) return false; if (nodeFullySelected) return true; bool nodeFullyUnselected = (Range::compareBoundaryPoints(parentNode, nodeIndex, endPosition.computeContainerNode(), endPosition.offsetInContainerNode(), exceptionState) > 0 && !exceptionState.hadException()) || (Range::compareBoundaryPoints(parentNode, nodeIndex + 1, startPosition.computeContainerNode(), startPosition.offsetInContainerNode(), exceptionState) < 0 && !exceptionState.hadException()); ASSERT(!exceptionState.hadException()); if (nodeFullyUnselected) return false; return allowPartial || n->isTextNode(); }
static EphemeralRange expandToParagraphBoundary(const EphemeralRange& range) { return EphemeralRange(startOfParagraph(createVisiblePosition(range.startPosition())).deepEquivalent(), endOfParagraph(createVisiblePosition(range.endPosition())).deepEquivalent()); }
static bool areSameRanges(Node* node, const PositionTemplate<Strategy>& startPosition, const PositionTemplate<Strategy>& endPosition) { ASSERT(node); const EphemeralRange range = VisibleSelection::selectionFromContentsOfNode(node).toNormalizedEphemeralRange(); return toPositionInDOMTree(startPosition) == range.startPosition() && toPositionInDOMTree(endPosition) == 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); } }