void InputMethodController::confirmCompositionAndResetState()
{
    if (!hasComposition())
        return;

    // ChromeClient::willSetInputMethodState() resets input method and the composition string is committed.
    frame().chromeClient().willSetInputMethodState();
}
PassRefPtrWillBeRawPtr<Range> InputMethodController::compositionRange() const
{
    if (!hasComposition())
        return nullptr;
    unsigned length = m_compositionNode->length();
    unsigned start = std::min(m_compositionStart, length);
    unsigned end = std::min(std::max(start, m_compositionEnd), length);
    if (start >= end)
        return nullptr;
    return Range::create(m_compositionNode->document(), m_compositionNode.get(), start, m_compositionNode.get(), end);
}
bool InputMethodController::finishComposition(const String& text, FinishCompositionMode mode)
{
    if (!hasComposition())
        return false;

    ASSERT(mode == ConfirmComposition || mode == CancelComposition);

    Editor::RevealSelectionScope revealSelectionScope(&editor());

    if (mode == CancelComposition)
        ASSERT(text == emptyString());
    else
        selectComposition();

    if (m_frame.selection().isNone())
        return false;

    // Dispatch a compositionend event to the focused node.
    // We should send this event before sending a TextEvent as written in Section 6.2.2 and 6.2.3 of
    // the DOM Event specification.
    if (Element* target = m_frame.document()->focusedElement()) {
        unsigned baseOffset = m_frame.selection().base().downstream().deprecatedEditingOffset();
        Vector<CompositionUnderline> underlines;
        for (size_t i = 0; i < m_customCompositionUnderlines.size(); ++i) {
            CompositionUnderline underline = m_customCompositionUnderlines[i];
            underline.startOffset -= baseOffset;
            underline.endOffset -= baseOffset;
            underlines.append(underline);
        }
        RefPtrWillBeRawPtr<CompositionEvent> event = CompositionEvent::create(EventTypeNames::compositionend, m_frame.domWindow(), text, underlines);
        target->dispatchEvent(event, IGNORE_EXCEPTION);
    }

    // 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() && mode != CancelComposition) {
        ASSERT(m_frame.document());
        TypingCommand::deleteSelection(*m_frame.document(), 0);
    }

    m_compositionNode = nullptr;
    m_customCompositionUnderlines.clear();

    insertTextForConfirmedComposition(text);

    if (mode == CancelComposition) {
        // An open typing command that disagrees about current selection would cause issues with typing later on.
        TypingCommand::closeTyping(&m_frame);
    }

    return true;
}
示例#4
0
void InputMethodController::cancelCompositionIfSelectionIsInvalid()
{
    if (!hasComposition() || editor().preventRevealSelection())
        return;

    // Check if selection start and selection end are valid.
    FrameSelection& selection = frame().selection();
    if (!selection.isNone() && !m_compositionRange->collapsed()) {
        if (selection.start().compareTo(m_compositionRange->startPosition()) >= 0
            && selection.end().compareTo(m_compositionRange->endPosition()) <= 0)
            return;
    }

    cancelComposition();
    frame().chromeClient().didCancelCompositionOnSelectionChange();
}
void InputMethodController::cancelCompositionIfSelectionIsInvalid()
{
    if (!hasComposition() || editor().preventRevealSelection())
        return;

    // Check if selection start and selection end are valid.
    Position start = m_frame.selection().start();
    Position end = m_frame.selection().end();
    if (start.containerNode() == m_compositionNode
        && end.containerNode() == m_compositionNode
        && static_cast<unsigned>(start.computeOffsetInContainerNode()) >= m_compositionStart
        && static_cast<unsigned>(end.computeOffsetInContainerNode()) <= m_compositionEnd)
        return;

    cancelComposition();
}
bool InputMethodController::finishComposition(const String& text, FinishCompositionMode mode)
{
    if (!hasComposition())
        return false;

    ASSERT(mode == ConfirmComposition || mode == CancelComposition);

    Editor::RevealSelectionScope revealSelectionScope(&editor());

    bool dirty = m_isDirty || plainText(compositionEphemeralRange()) != text;

    if (mode == CancelComposition) {
        ASSERT(text == emptyString());
    } else if (dirty) {
        selectComposition();
    }

    if (frame().selection().isNone())
        return false;

    // Dispatch a compositionend event to the focused node.
    // We should send this event before sending a TextEvent as written in Section 6.2.2 and 6.2.3 of
    // the DOM Event specification.
    if (Element* target = frame().document()->focusedElement()) {
        RefPtrWillBeRawPtr<CompositionEvent> event = CompositionEvent::create(EventTypeNames::compositionend, frame().domWindow(), text);
        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() && mode != CancelComposition && dirty) {
        ASSERT(frame().document());
        TypingCommand::deleteSelection(*frame().document(), 0);
    }

    clear();

    if (dirty)
        insertTextForConfirmedComposition(text);

    if (mode == CancelComposition) {
        // An open typing command that disagrees about current selection would cause issues with typing later on.
        TypingCommand::closeTyping(m_frame);
    }

    return true;
}
示例#7
0
void InputMethodController::cancelComposition()
{
    if (!hasComposition())
        return;

    Editor::RevealSelectionScope revealSelectionScope(&editor());

    if (frame().selection().isNone())
        return;

    dispatchCompositionEndEvent(frame(), emptyString());
    clear();
    insertTextForConfirmedComposition(emptyString());

    // An open typing command that disagrees about current selection would cause
    // issues with typing later on.
    TypingCommand::closeTyping(m_frame);
}
void InputMethodController::confirmCompositionAndResetState()
{
    if (!hasComposition())
        return;

    // We should verify the parent node of this IME composition node are
    // editable because JavaScript may delete a parent node of the composition
    // node. In this case, WebKit crashes while deleting texts from the parent
    // node, which doesn't exist any longer.
    RefPtr<Range> range = compositionRange();
    if (range) {
        Node* node = range->startContainer();
        if (!node || !node->isContentEditable())
            return;
    }

    // EditorClient::willSetInputMethodState() resets input method and the composition string is committed.
    editorClient().willSetInputMethodState();
}
bool InputMethodController::confirmCompositionOrInsertText(const String& text, ConfirmCompositionBehavior confirmBehavior)
{
    if (!hasComposition()) {
        if (!text.length())
            return false;
        editor().insertText(text, 0);
        return true;
    }

    if (text.length()) {
        confirmComposition(text);
        return true;
    }

    if (confirmBehavior != KeepSelection)
        return confirmComposition();

    SelectionOffsetsScope selectionOffsetsScope(this);
    return confirmComposition();
}
bool InputMethodController::confirmComposition(const String& text, ConfirmCompositionBehavior confirmBehavior)
{
    if (!hasComposition())
        return false;

    Optional<Editor::RevealSelectionScope> revealSelectionScope;
    if (confirmBehavior == KeepSelection)
        revealSelectionScope.emplace(&editor());

    // If the composition was set from existing text and didn't change, then
    // there's nothing to do here (and we should avoid doing anything as that
    // may clobber multi-node styled text).
    if (!m_isDirty && composingText() == text) {
        clear();
        return true;
    }

    // Select the text that will be deleted or replaced.
    selectComposition();

    if (frame().selection().isNone())
        return false;

    dispatchCompositionEndEvent(frame(), text);

    if (!frame().document())
        return false;

    // 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())
        TypingCommand::deleteSelection(*frame().document(), 0);

    clear();

    insertTextForConfirmedComposition(text);

    return true;
}
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.
        //    m_compositionNode == 0 && !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. m_compositionNode != 0 && !text.isEmpty().
        // 3. Canceling the ongoing composition.
        //    Send a compositionend event when function deletes the existing composition node, i.e.
        //    m_compositionNode != 0 && 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(), underlines));
                event = CompositionEvent::create(EventTypeNames::compositionupdate, frame().domWindow(), text, underlines);
            }
        } else {
            if (!text.isEmpty())
                event = CompositionEvent::create(EventTypeNames::compositionupdate, frame().domWindow(), text, underlines);
            else
                event = CompositionEvent::create(EventTypeNames::compositionend, frame().domWindow(), text, underlines);
        }
        if (event.get())
            target->dispatchEvent(event, IGNORE_EXCEPTION);
    }

    // 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);
    }

    m_compositionNode = nullptr;
    m_customCompositionUnderlines.clear();

    if (!text.isEmpty()) {
        ASSERT(frame().document());
        TypingCommand::insertText(*frame().document(), text, TypingCommand::SelectInsertedText | TypingCommand::PreventSpellChecking, TypingCommand::TextCompositionUpdate);

        // Find out what node has the composition now.
        Position base = frame().selection().base().downstream();
        Position extent = frame().selection().extent();
        Node* baseNode = base.deprecatedNode();
        unsigned baseOffset = base.deprecatedEditingOffset();
        Node* extentNode = extent.deprecatedNode();
        unsigned extentOffset = extent.deprecatedEditingOffset();

        if (baseNode && baseNode == extentNode && baseNode->isTextNode() && baseOffset + text.length() == extentOffset) {
            m_compositionNode = toText(baseNode);
            m_compositionStart = baseOffset;
            m_compositionEnd = extentOffset;
            m_customCompositionUnderlines = underlines;
            for (auto& underline : m_customCompositionUnderlines) {
                underline.startOffset += baseOffset;
                underline.endOffset += baseOffset;
            }
            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(), DOWNSTREAM, FrameSelection::NonDirectional, NotUserTriggered);
        }
    }
}
bool InputMethodController::confirmComposition()
{
    if (!hasComposition())
        return false;
    return finishComposition(m_compositionNode->data().substring(m_compositionStart, m_compositionEnd - m_compositionStart), ConfirmComposition);
}
示例#13
0
PassRefPtrWillBeRawPtr<Range> InputMethodController::compositionRange() const
{
    return hasComposition() ? m_compositionRange : nullptr;
}
示例#14
0
EphemeralRange InputMethodController::compositionEphemeralRange() const
{
    if (!hasComposition())
        return EphemeralRange();
    return EphemeralRange(m_compositionRange.get());
}
示例#15
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);
    }
}
bool InputMethodController::confirmComposition()
{
    if (!hasComposition())
        return false;
    return confirmComposition(plainText(compositionEphemeralRange()));
}