2 * Copyright (C) 2006, 2007, 2008, 2011 Apple Inc. All rights reserved.
3 * Copyright (C) 2008 Nokia Corporation and/or its subsidiary(-ies)
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
8 * 1. Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 * notice, this list of conditions and the following disclaimer in the
12 * documentation and/or other materials provided with the distribution.
14 * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
15 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
17 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR
18 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
19 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
20 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
21 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
22 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
24 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 #include "core/editing/InputMethodController.h"
30 #include "core/events/CompositionEvent.h"
31 #include "core/dom/Document.h"
32 #include "core/dom/Element.h"
33 #include "core/dom/Range.h"
34 #include "core/dom/Text.h"
35 #include "core/editing/Editor.h"
36 #include "core/editing/TypingCommand.h"
37 #include "core/frame/LocalFrame.h"
38 #include "core/html/HTMLTextAreaElement.h"
39 #include "core/page/Chrome.h"
40 #include "core/page/ChromeClient.h"
41 #include "core/page/EventHandler.h"
42 #include "core/rendering/RenderObject.h"
46 InputMethodController::SelectionOffsetsScope::SelectionOffsetsScope(InputMethodController* inputMethodController)
47 : m_inputMethodController(inputMethodController)
48 , m_offsets(inputMethodController->getSelectionOffsets())
52 InputMethodController::SelectionOffsetsScope::~SelectionOffsetsScope()
54 m_inputMethodController->setSelectionOffsets(m_offsets);
57 // ----------------------------
59 PassOwnPtrWillBeRawPtr<InputMethodController> InputMethodController::create(LocalFrame& frame)
61 return adoptPtrWillBeNoop(new InputMethodController(frame));
64 InputMethodController::InputMethodController(LocalFrame& frame)
66 , m_compositionStart(0)
71 InputMethodController::~InputMethodController()
75 bool InputMethodController::hasComposition() const
77 return m_compositionNode && m_compositionNode->isContentEditable();
80 inline Editor& InputMethodController::editor() const
82 return frame().editor();
85 void InputMethodController::clear()
87 m_compositionNode = nullptr;
88 m_customCompositionUnderlines.clear();
91 bool InputMethodController::insertTextForConfirmedComposition(const String& text)
93 return frame().eventHandler().handleTextInputEvent(text, 0, TextEventInputComposition);
96 void InputMethodController::selectComposition() const
98 RefPtrWillBeRawPtr<Range> range = compositionRange();
102 // The composition can start inside a composed character sequence, so we have to override checks.
103 // See <http://bugs.webkit.org/show_bug.cgi?id=15781>
104 VisibleSelection selection;
105 selection.setWithoutValidation(range->startPosition(), range->endPosition());
106 frame().selection().setSelection(selection, 0);
109 bool InputMethodController::confirmComposition()
111 if (!hasComposition())
113 return finishComposition(m_compositionNode->data().substring(m_compositionStart, m_compositionEnd - m_compositionStart), ConfirmComposition);
116 bool InputMethodController::confirmComposition(const String& text)
118 return finishComposition(text, ConfirmComposition);
121 bool InputMethodController::confirmCompositionOrInsertText(const String& text, ConfirmCompositionBehavior confirmBehavior)
123 if (!hasComposition()) {
126 editor().insertText(text, 0);
131 confirmComposition(text);
135 if (confirmBehavior != KeepSelection)
136 return confirmComposition();
138 SelectionOffsetsScope selectionOffsetsScope(this);
139 return confirmComposition();
142 void InputMethodController::confirmCompositionAndResetState()
144 if (!hasComposition())
147 // ChromeClient::willSetInputMethodState() resets input method and the composition string is committed.
148 frame().chromeClient().willSetInputMethodState();
151 void InputMethodController::cancelComposition()
153 finishComposition(emptyString(), CancelComposition);
156 void InputMethodController::cancelCompositionIfSelectionIsInvalid()
158 if (!hasComposition() || editor().preventRevealSelection())
161 // Check if selection start and selection end are valid.
162 Position start = frame().selection().start();
163 Position end = frame().selection().end();
164 if (start.containerNode() == m_compositionNode
165 && end.containerNode() == m_compositionNode
166 && static_cast<unsigned>(start.computeOffsetInContainerNode()) >= m_compositionStart
167 && static_cast<unsigned>(end.computeOffsetInContainerNode()) <= m_compositionEnd)
171 frame().chromeClient().didCancelCompositionOnSelectionChange();
174 bool InputMethodController::finishComposition(const String& text, FinishCompositionMode mode)
176 if (!hasComposition())
179 ASSERT(mode == ConfirmComposition || mode == CancelComposition);
181 Editor::RevealSelectionScope revealSelectionScope(&editor());
183 if (mode == CancelComposition)
184 ASSERT(text == emptyString());
188 if (frame().selection().isNone())
191 // Dispatch a compositionend event to the focused node.
192 // We should send this event before sending a TextEvent as written in Section 6.2.2 and 6.2.3 of
193 // the DOM Event specification.
194 if (Element* target = frame().document()->focusedElement()) {
195 unsigned baseOffset = frame().selection().base().downstream().deprecatedEditingOffset();
196 Vector<CompositionUnderline> underlines;
197 for (auto underline : m_customCompositionUnderlines) {
198 underline.startOffset -= baseOffset;
199 underline.endOffset -= baseOffset;
200 underlines.append(underline);
202 RefPtrWillBeRawPtr<CompositionEvent> event = CompositionEvent::create(EventTypeNames::compositionend, frame().domWindow(), text, underlines);
203 target->dispatchEvent(event, IGNORE_EXCEPTION);
206 // If text is empty, then delete the old composition here. If text is non-empty, InsertTextCommand::input
207 // will delete the old composition with an optimized replace operation.
208 if (text.isEmpty() && mode != CancelComposition) {
209 ASSERT(frame().document());
210 TypingCommand::deleteSelection(*frame().document(), 0);
213 m_compositionNode = nullptr;
214 m_customCompositionUnderlines.clear();
216 insertTextForConfirmedComposition(text);
218 if (mode == CancelComposition) {
219 // An open typing command that disagrees about current selection would cause issues with typing later on.
220 TypingCommand::closeTyping(m_frame);
226 void InputMethodController::setComposition(const String& text, const Vector<CompositionUnderline>& underlines, unsigned selectionStart, unsigned selectionEnd)
228 Editor::RevealSelectionScope revealSelectionScope(&editor());
230 // Updates styles before setting selection for composition to prevent
231 // inserting the previous composition text into text nodes oddly.
232 // See https://bugs.webkit.org/show_bug.cgi?id=46868
233 frame().document()->updateRenderTreeIfNeeded();
237 if (frame().selection().isNone())
240 if (Element* target = frame().document()->focusedElement()) {
241 // Dispatch an appropriate composition event to the focused node.
242 // We check the composition status and choose an appropriate composition event since this
243 // function is used for three purposes:
244 // 1. Starting a new composition.
245 // Send a compositionstart and a compositionupdate event when this function creates
246 // a new composition node, i.e.
247 // m_compositionNode == 0 && !text.isEmpty().
248 // Sending a compositionupdate event at this time ensures that at least one
249 // compositionupdate event is dispatched.
250 // 2. Updating the existing composition node.
251 // Send a compositionupdate event when this function updates the existing composition
252 // node, i.e. m_compositionNode != 0 && !text.isEmpty().
253 // 3. Canceling the ongoing composition.
254 // Send a compositionend event when function deletes the existing composition node, i.e.
255 // m_compositionNode != 0 && test.isEmpty().
256 RefPtrWillBeRawPtr<CompositionEvent> event = nullptr;
257 if (!hasComposition()) {
258 // We should send a compositionstart event only when the given text is not empty because this
259 // function doesn't create a composition node when the text is empty.
260 if (!text.isEmpty()) {
261 target->dispatchEvent(CompositionEvent::create(EventTypeNames::compositionstart, frame().domWindow(), frame().selectedText(), underlines));
262 event = CompositionEvent::create(EventTypeNames::compositionupdate, frame().domWindow(), text, underlines);
266 event = CompositionEvent::create(EventTypeNames::compositionupdate, frame().domWindow(), text, underlines);
268 event = CompositionEvent::create(EventTypeNames::compositionend, frame().domWindow(), text, underlines);
271 target->dispatchEvent(event, IGNORE_EXCEPTION);
274 // If text is empty, then delete the old composition here. If text is non-empty, InsertTextCommand::input
275 // will delete the old composition with an optimized replace operation.
276 if (text.isEmpty()) {
277 ASSERT(frame().document());
278 TypingCommand::deleteSelection(*frame().document(), TypingCommand::PreventSpellChecking);
281 m_compositionNode = nullptr;
282 m_customCompositionUnderlines.clear();
284 if (!text.isEmpty()) {
285 ASSERT(frame().document());
286 TypingCommand::insertText(*frame().document(), text, TypingCommand::SelectInsertedText | TypingCommand::PreventSpellChecking, TypingCommand::TextCompositionUpdate);
288 // Find out what node has the composition now.
289 Position base = frame().selection().base().downstream();
290 Position extent = frame().selection().extent();
291 Node* baseNode = base.deprecatedNode();
292 unsigned baseOffset = base.deprecatedEditingOffset();
293 Node* extentNode = extent.deprecatedNode();
294 unsigned extentOffset = extent.deprecatedEditingOffset();
296 if (baseNode && baseNode == extentNode && baseNode->isTextNode() && baseOffset + text.length() == extentOffset) {
297 m_compositionNode = toText(baseNode);
298 m_compositionStart = baseOffset;
299 m_compositionEnd = extentOffset;
300 m_customCompositionUnderlines = underlines;
301 for (auto& underline : m_customCompositionUnderlines) {
302 underline.startOffset += baseOffset;
303 underline.endOffset += baseOffset;
305 if (baseNode->renderer())
306 baseNode->renderer()->setShouldDoFullPaintInvalidation();
308 unsigned start = std::min(baseOffset + selectionStart, extentOffset);
309 unsigned end = std::min(std::max(start, baseOffset + selectionEnd), extentOffset);
310 RefPtrWillBeRawPtr<Range> selectedRange = Range::create(baseNode->document(), baseNode, start, baseNode, end);
311 frame().selection().setSelectedRange(selectedRange.get(), DOWNSTREAM, FrameSelection::NonDirectional, NotUserTriggered);
316 void InputMethodController::setCompositionFromExistingText(const Vector<CompositionUnderline>& underlines, unsigned compositionStart, unsigned compositionEnd)
318 Element* editable = frame().selection().rootEditableElement();
319 Position base = frame().selection().base().downstream();
320 Node* baseNode = base.anchorNode();
321 if (editable->firstChild() == baseNode && editable->lastChild() == baseNode && baseNode->isTextNode()) {
322 m_compositionNode = nullptr;
323 m_customCompositionUnderlines.clear();
325 if (base.anchorType() != Position::PositionIsOffsetInAnchor)
327 if (!baseNode || baseNode != frame().selection().extent().anchorNode())
330 m_compositionNode = toText(baseNode);
331 RefPtrWillBeRawPtr<Range> range = PlainTextRange(compositionStart, compositionEnd).createRange(*editable);
335 m_compositionStart = range->startOffset();
336 m_compositionEnd = range->endOffset();
337 m_customCompositionUnderlines = underlines;
338 size_t numUnderlines = m_customCompositionUnderlines.size();
339 for (size_t i = 0; i < numUnderlines; ++i) {
340 m_customCompositionUnderlines[i].startOffset += m_compositionStart;
341 m_customCompositionUnderlines[i].endOffset += m_compositionStart;
343 if (baseNode->renderer())
344 baseNode->renderer()->setShouldDoFullPaintInvalidation();
348 Editor::RevealSelectionScope revealSelectionScope(&editor());
349 SelectionOffsetsScope selectionOffsetsScope(this);
350 setSelectionOffsets(PlainTextRange(compositionStart, compositionEnd));
351 setComposition(frame().selectedText(), underlines, 0, 0);
354 PassRefPtrWillBeRawPtr<Range> InputMethodController::compositionRange() const
356 if (!hasComposition())
358 unsigned length = m_compositionNode->length();
359 unsigned start = std::min(m_compositionStart, length);
360 unsigned end = std::min(std::max(start, m_compositionEnd), length);
363 return Range::create(m_compositionNode->document(), m_compositionNode.get(), start, m_compositionNode.get(), end);
366 PlainTextRange InputMethodController::getSelectionOffsets() const
368 RefPtrWillBeRawPtr<Range> range = frame().selection().selection().firstRange();
370 return PlainTextRange();
371 ContainerNode* editable = frame().selection().rootEditableElementOrTreeScopeRootNode();
373 return PlainTextRange::create(*editable, *range.get());
376 bool InputMethodController::setSelectionOffsets(const PlainTextRange& selectionOffsets)
378 if (selectionOffsets.isNull())
380 Element* rootEditableElement = frame().selection().rootEditableElement();
381 if (!rootEditableElement)
384 RefPtrWillBeRawPtr<Range> range = selectionOffsets.createRange(*rootEditableElement);
388 return frame().selection().setSelectedRange(range.get(), VP_DEFAULT_AFFINITY, FrameSelection::NonDirectional, FrameSelection::CloseTyping);
391 bool InputMethodController::setEditableSelectionOffsets(const PlainTextRange& selectionOffsets)
393 if (!editor().canEdit())
395 return setSelectionOffsets(selectionOffsets);
398 void InputMethodController::extendSelectionAndDelete(int before, int after)
400 if (!editor().canEdit())
402 PlainTextRange selectionOffsets(getSelectionOffsets());
403 if (selectionOffsets.isNull())
406 // A common call of before=1 and after=0 will fail if the last character
407 // is multi-code-word UTF-16, including both multi-16bit code-points and
408 // Unicode combining character sequences of multiple single-16bit code-
409 // points (officially called "compositions"). Try more until success.
410 // http://crbug.com/355995
412 // FIXME: Note that this is not an ideal solution when this function is
413 // called to implement "backspace". In that case, there should be some call
414 // that will not delete a full multi-code-point composition but rather
415 // only the last code-point so that it's possible for a user to correct
416 // a composition without starting it from the beginning.
417 // http://crbug.com/37993
419 if (!setSelectionOffsets(PlainTextRange(std::max(static_cast<int>(selectionOffsets.start()) - before, 0), selectionOffsets.end() + after)))
424 } while (frame().selection().start() == frame().selection().end() && before <= static_cast<int>(selectionOffsets.start()));
425 TypingCommand::deleteSelection(*frame().document());
428 void InputMethodController::trace(Visitor* visitor)
430 visitor->trace(m_frame);
431 visitor->trace(m_compositionNode);