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));
}
Пример #4
0
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());
}
Пример #5
0
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();
}
Пример #6
0
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();
}
Пример #7
0
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.
    }
}
Пример #9
0
// 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;
}
Пример #11
0
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);
}
Пример #12
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;
}
Пример #13
0
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);
}
Пример #14
0
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());
}
Пример #15
0
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));
}
Пример #17
0
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);
}
Пример #19
0
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();
}
Пример #22
0
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);
    }
}