TEST_F(InputMethodControllerTest, DeleteBySettingEmptyComposition)
{
    HTMLInputElement* input = toHTMLInputElement(
        insertHTMLElement("<input id='sample'>", "sample"));

    input->setValue("foo ");
    controller().setEditableSelectionOffsets(PlainTextRange(4, 4));
    EXPECT_STREQ("foo ", input->value().utf8().data());
    controller().extendSelectionAndDelete(0, 0);
    EXPECT_STREQ("foo ", input->value().utf8().data());

    input->setValue("foo ");
    controller().setEditableSelectionOffsets(PlainTextRange(4, 4));
    EXPECT_STREQ("foo ", input->value().utf8().data());
    controller().extendSelectionAndDelete(1, 0);
    EXPECT_STREQ("foo", input->value().utf8().data());

    Vector<CompositionUnderline> underlines;
    underlines.append(CompositionUnderline(0, 3, Color(255, 0, 0), false, 0));
    controller().setCompositionFromExistingText(underlines, 0, 3);

    controller().setComposition(String(""), underlines, 0, 3);

    EXPECT_STREQ("", input->value().utf8().data());
}
Пример #2
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());
}
void InputMethodController::setCompositionFromExistingText(const Vector<CompositionUnderline>& underlines, unsigned compositionStart, unsigned compositionEnd)
{
    Element* editable = m_frame.selection().rootEditableElement();
    Position base = m_frame.selection().base().downstream();
    Node* baseNode = base.anchorNode();
    if (editable->firstChild() == baseNode && editable->lastChild() == baseNode && baseNode->isTextNode()) {
        m_compositionNode = nullptr;
        m_customCompositionUnderlines.clear();

        if (base.anchorType() != Position::PositionIsOffsetInAnchor)
            return;
        if (!baseNode || baseNode != m_frame.selection().extent().anchorNode())
            return;

        m_compositionNode = toText(baseNode);
        RefPtrWillBeRawPtr<Range> range = PlainTextRange(compositionStart, compositionEnd).createRange(*editable);
        m_compositionStart = range->startOffset();
        m_compositionEnd = range->endOffset();
        m_customCompositionUnderlines = underlines;
        size_t numUnderlines = m_customCompositionUnderlines.size();
        for (size_t i = 0; i < numUnderlines; ++i) {
            m_customCompositionUnderlines[i].startOffset += m_compositionStart;
            m_customCompositionUnderlines[i].endOffset += m_compositionStart;
        }
        if (baseNode->renderer())
            baseNode->renderer()->paintInvalidationForWholeRenderer();
        return;
    }

    Editor::RevealSelectionScope revealSelectionScope(&editor());
    SelectionOffsetsScope selectionOffsetsScope(this);
    setSelectionOffsets(PlainTextRange(compositionStart, compositionEnd));
    setComposition(m_frame.selectedText(), underlines, 0, 0);
}
Пример #4
0
PlainTextRange PlainTextRange::create(const ContainerNode& scope, const Range& range)
{
    if (!range.startContainer())
        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.
    if (range.startContainer() != &scope && !range.startContainer()->isDescendantOf(&scope))
        return PlainTextRange();
    if (range.endContainer() != scope && !range.endContainer()->isDescendantOf(&scope))
        return PlainTextRange();

    RefPtrWillBeRawPtr<Range> testRange = Range::create(scope.document(), const_cast<ContainerNode*>(&scope), 0, range.startContainer(), range.startOffset());
    ASSERT(testRange->startContainer() == &scope);
    size_t start = TextIterator::rangeLength(testRange->startPosition(), testRange->endPosition());

    testRange->setEnd(range.endContainer(), range.endOffset(), IGNORE_EXCEPTION);
    ASSERT(testRange->startContainer() == &scope);
    size_t end = TextIterator::rangeLength(testRange->startPosition(), testRange->endPosition());

    return PlainTextRange(start, end);
}
void InputMethodController::extendSelectionAndDelete(int before, int after)
{
    if (!editor().canEdit())
        return;
    PlainTextRange selectionOffsets(getSelectionOffsets());
    if (selectionOffsets.isNull())
        return;

    // A common call of before=1 and after=0 will fail if the last character
    // is multi-code-word UTF-16, including both multi-16bit code-points and
    // Unicode combining character sequences of multiple single-16bit code-
    // points (officially called "compositions"). Try more until success.
    // http://crbug.com/355995
    //
    // FIXME: Note that this is not an ideal solution when this function is
    // called to implement "backspace". In that case, there should be some call
    // that will not delete a full multi-code-point composition but rather
    // only the last code-point so that it's possible for a user to correct
    // a composition without starting it from the beginning.
    // http://crbug.com/37993
    do {
        if (!setSelectionOffsets(PlainTextRange(std::max(static_cast<int>(selectionOffsets.start()) - before, 0), selectionOffsets.end() + after)))
            return;
        if (before == 0)
            break;
        ++before;
    } while (frame().selection().start() == frame().selection().end() && before <= static_cast<int>(selectionOffsets.start()));
    TypingCommand::deleteSelection(*frame().document());
}
PlainTextRange InputMethodController::getSelectionOffsets() const
{
    RefPtrWillBeRawPtr<Range> range = frame().selection().selection().firstRange();
    if (!range)
        return PlainTextRange();
    ContainerNode* editable = frame().selection().rootEditableElementOrTreeScopeRootNode();
    ASSERT(editable);
    return PlainTextRange::create(*editable, *range.get());
}
Пример #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);
}
TEST_F(InputMethodControllerTest, SetCompositionForInputWithDifferentNewCursorPositions)
{
    HTMLInputElement* input = toHTMLInputElement(
        insertHTMLElement("<input id='sample'>", "sample"));

    input->setValue("hello");
    controller().setEditableSelectionOffsets(PlainTextRange(2, 2));
    EXPECT_STREQ("hello", input->value().utf8().data());
    EXPECT_EQ(2u, controller().getSelectionOffsets().start());
    EXPECT_EQ(2u, controller().getSelectionOffsets().end());

    Vector<CompositionUnderline> underlines;
    underlines.append(CompositionUnderline(0, 2, Color(255, 0, 0), false, 0));

    // The cursor exceeds left boundary.
    // "*heABllo", where * stands for cursor.
    controller().setComposition("AB", underlines, -100, -100);
    EXPECT_STREQ("heABllo", input->value().utf8().data());
    EXPECT_EQ(0u, controller().getSelectionOffsets().start());
    EXPECT_EQ(0u, controller().getSelectionOffsets().end());

    // The cursor is on left boundary.
    // "*heABllo".
    controller().setComposition("AB", underlines, -2, -2);
    EXPECT_STREQ("heABllo", input->value().utf8().data());
    EXPECT_EQ(0u, controller().getSelectionOffsets().start());
    EXPECT_EQ(0u, controller().getSelectionOffsets().end());

    // The cursor is before the composing text.
    // "he*ABllo".
    controller().setComposition("AB", underlines, 0, 0);
    EXPECT_STREQ("heABllo", input->value().utf8().data());
    EXPECT_EQ(2u, controller().getSelectionOffsets().start());
    EXPECT_EQ(2u, controller().getSelectionOffsets().end());

    // The cursor is after the composing text.
    // "heAB*llo".
    controller().setComposition("AB", underlines, 2, 2);
    EXPECT_STREQ("heABllo", input->value().utf8().data());
    EXPECT_EQ(4u, controller().getSelectionOffsets().start());
    EXPECT_EQ(4u, controller().getSelectionOffsets().end());

    // The cursor is on right boundary.
    // "heABllo*".
    controller().setComposition("AB", underlines, 5, 5);
    EXPECT_STREQ("heABllo", input->value().utf8().data());
    EXPECT_EQ(7u, controller().getSelectionOffsets().start());
    EXPECT_EQ(7u, controller().getSelectionOffsets().end());

    // The cursor exceeds right boundary.
    // "heABllo*".
    controller().setComposition("AB", underlines, 100, 100);
    EXPECT_STREQ("heABllo", input->value().utf8().data());
    EXPECT_EQ(7u, controller().getSelectionOffsets().start());
    EXPECT_EQ(7u, controller().getSelectionOffsets().end());
}
void InputMethodController::extendSelectionAndDelete(int before, int after)
{
    if (!editor().canEdit())
        return;
    PlainTextRange selectionOffsets(getSelectionOffsets());
    if (selectionOffsets.isNull())
        return;
    setSelectionOffsets(PlainTextRange(std::max(static_cast<int>(selectionOffsets.start()) - before, 0), selectionOffsets.end() + after));
    TypingCommand::deleteSelection(*m_frame.document());
}
Пример #10
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());
}
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);
}
Пример #12
0
void InputMethodController::setCompositionFromExistingText(const Vector<CompositionUnderline>& underlines, unsigned compositionStart, unsigned compositionEnd)
{
    Element* editable = m_frame.selection().rootEditableElement();
    Position base = m_frame.selection().base().downstream();
    Node* baseNode = base.anchorNode();
    if (editable->firstChild() == baseNode && editable->lastChild() == baseNode && baseNode->isTextNode()) {
        m_compositionNode = nullptr;
        m_customCompositionUnderlines.clear();

        if (base.anchorType() != Position::PositionIsOffsetInAnchor)
            return;
        if (!baseNode || baseNode != m_frame.selection().extent().anchorNode())
            return;

        m_compositionNode = toText(baseNode);
        RefPtr<Range> range = PlainTextRange(compositionStart, compositionEnd).createRange(*editable);
        m_compositionStart = range->startOffset();
        m_compositionEnd = range->endOffset();
        m_customCompositionUnderlines = underlines;
        size_t numUnderlines = m_customCompositionUnderlines.size();
        for (size_t i = 0; i < numUnderlines; ++i) {
            m_customCompositionUnderlines[i].startOffset += m_compositionStart;
            m_customCompositionUnderlines[i].endOffset += m_compositionStart;
        }

        // TODO(ojan): What was this for? Do we need it in sky since we
        // don't need to support legacy IMEs?
        if (baseNode->renderer())
            baseNode->document().scheduleVisualUpdate();

        return;
    }

    Editor::RevealSelectionScope revealSelectionScope(&editor());
    SelectionOffsetsScope selectionOffsetsScope(this);
    setSelectionOffsets(PlainTextRange(compositionStart, compositionEnd));
    setComposition(m_frame.selectedText(), underlines, 0, 0);
}
TEST_F(InputMethodControllerTest, BackspaceFromEndOfInput)
{
    HTMLInputElement* input = toHTMLInputElement(
        insertHTMLElement("<input id='sample'>", "sample"));

    input->setValue("fooX");
    controller().setEditableSelectionOffsets(PlainTextRange(4, 4));
    EXPECT_STREQ("fooX", input->value().utf8().data());
    controller().extendSelectionAndDelete(0, 0);
    EXPECT_STREQ("fooX", input->value().utf8().data());

    input->setValue("fooX");
    controller().setEditableSelectionOffsets(PlainTextRange(4, 4));
    EXPECT_STREQ("fooX", input->value().utf8().data());
    controller().extendSelectionAndDelete(1, 0);
    EXPECT_STREQ("foo", input->value().utf8().data());

    input->setValue(String::fromUTF8("foo\xE2\x98\x85")); // U+2605 == "black star"
    controller().setEditableSelectionOffsets(PlainTextRange(4, 4));
    EXPECT_STREQ("foo\xE2\x98\x85", input->value().utf8().data());
    controller().extendSelectionAndDelete(1, 0);
    EXPECT_STREQ("foo", input->value().utf8().data());

    input->setValue(String::fromUTF8("foo\xF0\x9F\x8F\x86")); // U+1F3C6 == "trophy"
    controller().setEditableSelectionOffsets(PlainTextRange(4, 4));
    EXPECT_STREQ("foo\xF0\x9F\x8F\x86", input->value().utf8().data());
    controller().extendSelectionAndDelete(1, 0);
    EXPECT_STREQ("foo", input->value().utf8().data());

    input->setValue(String::fromUTF8("foo\xE0\xB8\x81\xE0\xB9\x89")); // composed U+0E01 "ka kai" + U+0E49 "mai tho"
    controller().setEditableSelectionOffsets(PlainTextRange(4, 4));
    EXPECT_STREQ("foo\xE0\xB8\x81\xE0\xB9\x89", input->value().utf8().data());
    controller().extendSelectionAndDelete(1, 0);
    EXPECT_STREQ("foo", input->value().utf8().data());

    input->setValue("fooX");
    controller().setEditableSelectionOffsets(PlainTextRange(4, 4));
    EXPECT_STREQ("fooX", input->value().utf8().data());
    controller().extendSelectionAndDelete(0, 1);
    EXPECT_STREQ("fooX", input->value().utf8().data());
}
TEST_F(InputMethodControllerTest, SetCompositionForContentEditableWithDifferentNewCursorPositions)
{
    // There are 7 nodes and 5+1+5+1+3+4+3 characters: "hello", '\n', "world", "\n", "012", "3456", "789".
    Element* div = insertHTMLElement(
        "<div id='sample' contenteditable='true'>"
        "hello"
        "<div id='sample2' contenteditable='true'>world"
        "<p>012<b>3456</b><i>789</i></p>"
        "</div>"
        "</div>",
        "sample");

    controller().setEditableSelectionOffsets(PlainTextRange(17, 17));
    EXPECT_STREQ("hello\nworld\n0123456789", div->innerText().utf8().data());
    EXPECT_EQ(17u, controller().getSelectionOffsets().start());
    EXPECT_EQ(17u, controller().getSelectionOffsets().end());

    Vector<CompositionUnderline> underlines;
    underlines.append(CompositionUnderline(0, 2, Color(255, 0, 0), false, 0));

    // The cursor exceeds left boundary.
    // "*hello\nworld\n01234AB56789", where * stands for cursor.
    controller().setComposition("AB", underlines, -100, -100);
    EXPECT_STREQ("hello\nworld\n01234AB56789", div->innerText().utf8().data());
    EXPECT_EQ(0u, controller().getSelectionOffsets().start());
    EXPECT_EQ(0u, controller().getSelectionOffsets().end());

    // The cursor is on left boundary.
    // "*hello\nworld\n01234AB56789".
    controller().setComposition("AB", underlines, -17, -17);
    EXPECT_STREQ("hello\nworld\n01234AB56789", div->innerText().utf8().data());
    EXPECT_EQ(0u, controller().getSelectionOffsets().start());
    EXPECT_EQ(0u, controller().getSelectionOffsets().end());

    // The cursor is in the 1st node.
    // "he*llo\nworld\n01234AB56789".
    controller().setComposition("AB", underlines, -15, -15);
    EXPECT_STREQ("hello\nworld\n01234AB56789", div->innerText().utf8().data());
    EXPECT_EQ(2u, controller().getSelectionOffsets().start());
    EXPECT_EQ(2u, controller().getSelectionOffsets().end());

    // The cursor is on right boundary of the 1st node.
    // "hello*\nworld\n01234AB56789".
    controller().setComposition("AB", underlines, -12, -12);
    EXPECT_STREQ("hello\nworld\n01234AB56789", div->innerText().utf8().data());
    EXPECT_EQ(5u, controller().getSelectionOffsets().start());
    EXPECT_EQ(5u, controller().getSelectionOffsets().end());

    // The cursor is on right boundary of the 2nd node.
    // "hello\n*world\n01234AB56789".
    controller().setComposition("AB", underlines, -11, -11);
    EXPECT_STREQ("hello\nworld\n01234AB56789", div->innerText().utf8().data());
    EXPECT_EQ(6u, controller().getSelectionOffsets().start());
    EXPECT_EQ(6u, controller().getSelectionOffsets().end());

    // The cursor is on right boundary of the 3rd node.
    // "hello\nworld*\n01234AB56789".
    controller().setComposition("AB", underlines, -6, -6);
    EXPECT_STREQ("hello\nworld\n01234AB56789", div->innerText().utf8().data());
    EXPECT_EQ(11u, controller().getSelectionOffsets().start());
    EXPECT_EQ(11u, controller().getSelectionOffsets().end());

    // The cursor is on right boundary of the 4th node.
    // "hello\nworld\n*01234AB56789".
    controller().setComposition("AB", underlines, -5, -5);
    EXPECT_STREQ("hello\nworld\n01234AB56789", div->innerText().utf8().data());
    EXPECT_EQ(12u, controller().getSelectionOffsets().start());
    EXPECT_EQ(12u, controller().getSelectionOffsets().end());

    // The cursor is before the composing text.
    // "hello\nworld\n01234*AB56789".
    controller().setComposition("AB", underlines, 0, 0);
    EXPECT_STREQ("hello\nworld\n01234AB56789", div->innerText().utf8().data());
    EXPECT_EQ(17u, controller().getSelectionOffsets().start());
    EXPECT_EQ(17u, controller().getSelectionOffsets().end());

    // The cursor is after the composing text.
    // "hello\nworld\n01234AB*56789".
    controller().setComposition("AB", underlines, 2, 2);
    EXPECT_STREQ("hello\nworld\n01234AB56789", div->innerText().utf8().data());
    EXPECT_EQ(19u, controller().getSelectionOffsets().start());
    EXPECT_EQ(19u, controller().getSelectionOffsets().end());

    // The cursor is on right boundary.
    // "hello\nworld\n01234AB56789*".
    controller().setComposition("AB", underlines, 7, 7);
    EXPECT_STREQ("hello\nworld\n01234AB56789", div->innerText().utf8().data());
    EXPECT_EQ(24u, controller().getSelectionOffsets().start());
    EXPECT_EQ(24u, controller().getSelectionOffsets().end());

    // The cursor exceeds right boundary.
    // "hello\nworld\n01234AB56789*".
    controller().setComposition("AB", underlines, 100, 100);
    EXPECT_STREQ("hello\nworld\n01234AB56789", div->innerText().utf8().data());
    EXPECT_EQ(24u, controller().getSelectionOffsets().start());
    EXPECT_EQ(24u, controller().getSelectionOffsets().end());
}
Пример #15
0
bool WebLocalFrameImpl::setEditableSelectionOffsets(int start, int end)
{
    return frame()->inputMethodController().setEditableSelectionOffsets(PlainTextRange(start, end));
}