| Index: third_party/WebKit/Source/core/editing/InputMethodController.cpp
|
| diff --git a/third_party/WebKit/Source/core/editing/InputMethodController.cpp b/third_party/WebKit/Source/core/editing/InputMethodController.cpp
|
| index b70befe6440a692d856ddb169fc145088ea45c63..bce455ebbcc63d4b2a2b92977150d405b19b892c 100644
|
| --- a/third_party/WebKit/Source/core/editing/InputMethodController.cpp
|
| +++ b/third_party/WebKit/Source/core/editing/InputMethodController.cpp
|
| @@ -44,6 +44,80 @@
|
|
|
| namespace blink {
|
|
|
| +namespace {
|
| +
|
| +void dispatchCompositionUpdateEvent(LocalFrame& frame, const String& text)
|
| +{
|
| + Element* target = frame.document()->focusedElement();
|
| + if (!target)
|
| + return;
|
| +
|
| + CompositionEvent* event = CompositionEvent::create(EventTypeNames::compositionupdate, frame.domWindow(), text);
|
| + target->dispatchEvent(event);
|
| +}
|
| +
|
| +void dispatchCompositionEndEvent(LocalFrame& frame, const String& text)
|
| +{
|
| + Element* target = frame.document()->focusedElement();
|
| + if (!target)
|
| + return;
|
| +
|
| + CompositionEvent* event = CompositionEvent::create(EventTypeNames::compositionend, frame.domWindow(), text);
|
| + target->dispatchEvent(event);
|
| +}
|
| +
|
| +// Used to insert/replace text during composition update and confirm composition.
|
| +// Procedure:
|
| +// 1. Fire 'beforeinput' event for (TODO(chongz): deleted composed text) and inserted text
|
| +// 2. Fire 'compositionupdate' event
|
| +// 3. Fire TextEvent and modify DOM
|
| +// TODO(chongz): 4. Fire 'input' event
|
| +void insertTextDuringCompositionWithEvents(LocalFrame& frame, const String& text, TypingCommand::Options options, TypingCommand::TextCompositionType compositionType)
|
| +{
|
| + DCHECK(compositionType == TypingCommand::TextCompositionType::TextCompositionUpdate || compositionType == TypingCommand::TextCompositionType::TextCompositionConfirm)
|
| + << "compositionType should be TextCompositionUpdate or TextCompositionConfirm, but got " << static_cast<int>(compositionType);
|
| + if (!frame.document())
|
| + return;
|
| +
|
| + Element* target = frame.document()->focusedElement();
|
| + if (!target)
|
| + return;
|
| +
|
| + // TODO(chongz): Fire 'beforeinput' for the composed text being replaced/deleted.
|
| +
|
| + // Only the last confirmed text is cancelable.
|
| + InputEvent::EventCancelable beforeInputCancelable = (compositionType == TypingCommand::TextCompositionType::TextCompositionUpdate) ? InputEvent::EventCancelable::NotCancelable : InputEvent::EventCancelable::IsCancelable;
|
| + DispatchEventResult result = dispatchBeforeInputFromComposition(target, InputEvent::InputType::InsertText, text, beforeInputCancelable);
|
| +
|
| + if (beforeInputCancelable == InputEvent::EventCancelable::IsCancelable && result != DispatchEventResult::NotCanceled)
|
| + return;
|
| +
|
| + // 'beforeinput' event handler may destroy document.
|
| + if (!frame.document())
|
| + return;
|
| +
|
| + dispatchCompositionUpdateEvent(frame, text);
|
| + // 'compositionupdate' event handler may destroy document.
|
| + if (!frame.document())
|
| + return;
|
| +
|
| + switch (compositionType) {
|
| + case TypingCommand::TextCompositionType::TextCompositionUpdate:
|
| + TypingCommand::insertText(*frame.document(), text, options, compositionType);
|
| + break;
|
| + case TypingCommand::TextCompositionType::TextCompositionConfirm:
|
| + // TODO(chongz): Use TypingCommand::insertText after TextEvent was removed. (Removed from spec since 2012)
|
| + // See TextEvent.idl.
|
| + frame.eventHandler().handleTextInputEvent(text, 0, TextEventInputComposition);
|
| + break;
|
| + default:
|
| + NOTREACHED();
|
| + }
|
| + // TODO(chongz): Fire 'input' event.
|
| +}
|
| +
|
| +} // anonymous namespace
|
| +
|
| InputMethodController::SelectionOffsetsScope::SelectionOffsetsScope(InputMethodController* inputMethodController)
|
| : m_inputMethodController(inputMethodController)
|
| , m_offsets(inputMethodController->getSelectionOffsets())
|
| @@ -96,11 +170,6 @@ void InputMethodController::documentDetached()
|
| m_compositionRange = nullptr;
|
| }
|
|
|
| -bool InputMethodController::insertTextForConfirmedComposition(const String& text)
|
| -{
|
| - return frame().eventHandler().handleTextInputEvent(text, 0, TextEventInputComposition);
|
| -}
|
| -
|
| void InputMethodController::selectComposition() const
|
| {
|
| const EphemeralRange range = compositionEphemeralRange();
|
| @@ -119,19 +188,6 @@ bool InputMethodController::confirmComposition()
|
| return confirmComposition(composingText());
|
| }
|
|
|
| -static void dispatchCompositionEndEvent(LocalFrame& frame, const String& text)
|
| -{
|
| - // 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.
|
| - Element* target = frame.document()->focusedElement();
|
| - if (!target)
|
| - return;
|
| -
|
| - CompositionEvent* event =
|
| - CompositionEvent::create(EventTypeNames::compositionend, frame.domWindow(), text);
|
| - target->dispatchEvent(event);
|
| -}
|
| -
|
| bool InputMethodController::confirmComposition(const String& text, ConfirmCompositionBehavior confirmBehavior)
|
| {
|
| if (!hasComposition())
|
| @@ -155,8 +211,6 @@ bool InputMethodController::confirmComposition(const String& text, ConfirmCompos
|
| if (frame().selection().isNone())
|
| return false;
|
|
|
| - dispatchCompositionEndEvent(frame(), text);
|
| -
|
| if (!frame().document())
|
| return false;
|
|
|
| @@ -168,12 +222,13 @@ bool InputMethodController::confirmComposition(const String& text, ConfirmCompos
|
|
|
| clear();
|
|
|
| - // TODO(chongz): DOM update should happen before 'compositionend' and along with 'compositionupdate'.
|
| - // https://crbug.com/575294
|
| - if (dispatchBeforeInputInsertText(frame().document()->focusedElement(), text) != DispatchEventResult::NotCanceled)
|
| + insertTextDuringCompositionWithEvents(frame(), text, 0, TypingCommand::TextCompositionType::TextCompositionConfirm);
|
| + // Event handler might destroy document.
|
| + if (!frame().document())
|
| return false;
|
|
|
| - insertTextForConfirmedComposition(text);
|
| + // No DOM update after 'compositionend'.
|
| + dispatchCompositionEndEvent(frame(), text);
|
|
|
| return true;
|
| }
|
| @@ -213,13 +268,22 @@ void InputMethodController::cancelComposition()
|
| if (frame().selection().isNone())
|
| return;
|
|
|
| - dispatchCompositionEndEvent(frame(), emptyString());
|
| clear();
|
| - insertTextForConfirmedComposition(emptyString());
|
| +
|
| + // TODO(chongz): Update InputType::DeleteComposedCharacter with latest discussion.
|
| + dispatchBeforeInputFromComposition(frame().document()->focusedElement(), InputEvent::InputType::DeleteComposedCharacter, emptyString(), InputEvent::EventCancelable::NotCancelable);
|
| + dispatchCompositionUpdateEvent(frame(), emptyString());
|
| + insertTextDuringCompositionWithEvents(frame(), emptyString(), 0, TypingCommand::TextCompositionType::TextCompositionConfirm);
|
| + // Event handler might destroy document.
|
| + if (!frame().document())
|
| + return;
|
|
|
| // An open typing command that disagrees about current selection would cause
|
| // issues with typing later on.
|
| TypingCommand::closeTyping(m_frame);
|
| +
|
| + // No DOM update after 'compositionend'.
|
| + dispatchCompositionEndEvent(frame(), emptyString());
|
| }
|
|
|
| void InputMethodController::cancelCompositionIfSelectionIsInvalid()
|
| @@ -253,58 +317,52 @@ void InputMethodController::setComposition(const String& text, const Vector<Comp
|
| 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().
|
| - 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) {
|
| - // TODO(chongz): Support canceling IME composition.
|
| - // TODO(chongz): Should fire InsertText or DeleteComposedCharacter based on action.
|
| - if (event->type() == EventTypeNames::compositionupdate)
|
| - dispatchBeforeInputFromComposition(target, InputEvent::InputType::InsertText, text);
|
| - target->dispatchEvent(event);
|
| - }
|
| - }
|
| + Element* target = frame().document()->focusedElement();
|
| + if (!target)
|
| + return;
|
|
|
| - // 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.
|
| + // 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().
|
| if (text.isEmpty()) {
|
| - DCHECK(frame().document());
|
| + if (hasComposition()) {
|
| + confirmComposition(emptyString());
|
| + return;
|
| + }
|
| + // It's weird to call |setComposition()| with empty text outside composition, however some IME
|
| + // (e.g. Japanese IBus-Anthy) did this, so we simply delete selection without sending extra events.
|
| TypingCommand::deleteSelection(*frame().document(), TypingCommand::PreventSpellChecking);
|
| + return;
|
| }
|
|
|
| + // 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 (!hasComposition()) {
|
| + target->dispatchEvent(CompositionEvent::create(EventTypeNames::compositionstart, frame().domWindow(), frame().selectedText()));
|
| + if (!frame().document())
|
| + return;
|
| + }
|
| +
|
| + DCHECK(!text.isEmpty());
|
| +
|
| clear();
|
|
|
| - if (text.isEmpty())
|
| + insertTextDuringCompositionWithEvents(frame(), text, TypingCommand::SelectInsertedText | TypingCommand::PreventSpellChecking, TypingCommand::TextCompositionUpdate);
|
| + // Event handlers might destroy document.
|
| + if (!frame().document())
|
| return;
|
| - DCHECK(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());
|
|
|