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()); }
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); }
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()); }
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()); }
// 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); }
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()); }
bool WebLocalFrameImpl::setEditableSelectionOffsets(int start, int end) { return frame()->inputMethodController().setEditableSelectionOffsets(PlainTextRange(start, end)); }