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; }
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; }
void InputMethodController::finishComposition(const String& text, FinishCompositionMode mode) { ASSERT(mode == ConfirmComposition || mode == CancelComposition); UserTypingGestureIndicator typingGestureIndicator(m_frame); Editor::RevealSelectionScope revealSelectionScope(&editor()); if (mode == CancelComposition) ASSERT(text == emptyString()); else selectComposition(); if (m_frame->selection().isNone()) return; // 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()) { RefPtr<CompositionEvent> event = CompositionEvent::create(eventNames().compositionendEvent, m_frame->domWindow(), text); 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 = 0; 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); } }
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) { UserTypingGestureIndicator typingGestureIndicator(m_frame); 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 m_frame->document()->updateStyleIfNeeded(); selectComposition(); if (m_frame->selection().isNone()) return; if (Element* target = m_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(). RefPtr<CompositionEvent> event; if (!m_compositionNode) { // 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(eventNames().compositionstartEvent, m_frame->domWindow(), m_frame->selectedText())); event = CompositionEvent::create(eventNames().compositionupdateEvent, m_frame->domWindow(), text); } } else { if (!text.isEmpty()) event = CompositionEvent::create(eventNames().compositionupdateEvent, m_frame->domWindow(), text); else event = CompositionEvent::create(eventNames().compositionendEvent, m_frame->domWindow(), text); } 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(m_frame->document()); TypingCommand::deleteSelection(*m_frame->document(), TypingCommand::PreventSpellChecking); } m_compositionNode = 0; m_customCompositionUnderlines.clear(); if (!text.isEmpty()) { ASSERT(m_frame->document()); TypingCommand::insertText(*m_frame->document(), text, TypingCommand::SelectInsertedText | TypingCommand::PreventSpellChecking, TypingCommand::TextCompositionUpdate); // Find out what node has the composition now. Position base = m_frame->selection().base().downstream(); Position extent = m_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; size_t numUnderlines = m_customCompositionUnderlines.size(); for (size_t i = 0; i < numUnderlines; ++i) { m_customCompositionUnderlines[i].startOffset += baseOffset; m_customCompositionUnderlines[i].endOffset += baseOffset; } if (baseNode->renderer()) baseNode->renderer()->repaint(); unsigned start = std::min(baseOffset + selectionStart, extentOffset); unsigned end = std::min(std::max(start, baseOffset + selectionEnd), extentOffset); RefPtr<Range> selectedRange = Range::create(baseNode->document(), baseNode, start, baseNode, end); m_frame->selection().setSelectedRange(selectedRange.get(), DOWNSTREAM, false); } } }
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); } }