Upstream version 10.38.208.0
[platform/framework/web/crosswalk.git] / src / third_party / WebKit / Source / core / editing / InputMethodController.cpp
1 /*
2  * Copyright (C) 2006, 2007, 2008, 2011 Apple Inc. All rights reserved.
3  * Copyright (C) 2008 Nokia Corporation and/or its subsidiary(-ies)
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
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.
13  *
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.
25  */
26
27 #include "config.h"
28 #include "core/editing/InputMethodController.h"
29
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"
43
44 namespace blink {
45
46 InputMethodController::SelectionOffsetsScope::SelectionOffsetsScope(InputMethodController* inputMethodController)
47     : m_inputMethodController(inputMethodController)
48     , m_offsets(inputMethodController->getSelectionOffsets())
49 {
50 }
51
52 InputMethodController::SelectionOffsetsScope::~SelectionOffsetsScope()
53 {
54     m_inputMethodController->setSelectionOffsets(m_offsets);
55 }
56
57 // ----------------------------
58
59 PassOwnPtr<InputMethodController> InputMethodController::create(LocalFrame& frame)
60 {
61     return adoptPtr(new InputMethodController(frame));
62 }
63
64 InputMethodController::InputMethodController(LocalFrame& frame)
65     : m_frame(frame)
66     , m_compositionStart(0)
67     , m_compositionEnd(0)
68 {
69 }
70
71 InputMethodController::~InputMethodController()
72 {
73 }
74
75 bool InputMethodController::hasComposition() const
76 {
77     return m_compositionNode && m_compositionNode->isContentEditable();
78 }
79
80 inline Editor& InputMethodController::editor() const
81 {
82     return m_frame.editor();
83 }
84
85 void InputMethodController::clear()
86 {
87     m_compositionNode = nullptr;
88     m_customCompositionUnderlines.clear();
89 }
90
91 bool InputMethodController::insertTextForConfirmedComposition(const String& text)
92 {
93     return m_frame.eventHandler().handleTextInputEvent(text, 0, TextEventInputComposition);
94 }
95
96 void InputMethodController::selectComposition() const
97 {
98     RefPtrWillBeRawPtr<Range> range = compositionRange();
99     if (!range)
100         return;
101
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     m_frame.selection().setSelection(selection, 0);
107 }
108
109 bool InputMethodController::confirmComposition()
110 {
111     if (!hasComposition())
112         return false;
113     return finishComposition(m_compositionNode->data().substring(m_compositionStart, m_compositionEnd - m_compositionStart), ConfirmComposition);
114 }
115
116 bool InputMethodController::confirmComposition(const String& text)
117 {
118     return finishComposition(text, ConfirmComposition);
119 }
120
121 bool InputMethodController::confirmCompositionOrInsertText(const String& text, ConfirmCompositionBehavior confirmBehavior)
122 {
123     if (!hasComposition()) {
124         if (!text.length())
125             return false;
126         editor().insertText(text, 0);
127         return true;
128     }
129
130     if (text.length()) {
131         confirmComposition(text);
132         return true;
133     }
134
135     if (confirmBehavior != KeepSelection)
136         return confirmComposition();
137
138     SelectionOffsetsScope selectionOffsetsScope(this);
139     return confirmComposition();
140 }
141
142 void InputMethodController::confirmCompositionAndResetState()
143 {
144     if (!hasComposition())
145         return;
146
147     // ChromeClient::willSetInputMethodState() resets input method and the composition string is committed.
148     m_frame.chromeClient().willSetInputMethodState();
149 }
150
151 void InputMethodController::cancelComposition()
152 {
153     finishComposition(emptyString(), CancelComposition);
154 }
155
156 void InputMethodController::cancelCompositionIfSelectionIsInvalid()
157 {
158     if (!hasComposition() || editor().preventRevealSelection())
159         return;
160
161     // Check if selection start and selection end are valid.
162     Position start = m_frame.selection().start();
163     Position end = m_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)
168         return;
169
170     cancelComposition();
171     m_frame.chromeClient().didCancelCompositionOnSelectionChange();
172 }
173
174 bool InputMethodController::finishComposition(const String& text, FinishCompositionMode mode)
175 {
176     if (!hasComposition())
177         return false;
178
179     ASSERT(mode == ConfirmComposition || mode == CancelComposition);
180
181     Editor::RevealSelectionScope revealSelectionScope(&editor());
182
183     if (mode == CancelComposition)
184         ASSERT(text == emptyString());
185     else
186         selectComposition();
187
188     if (m_frame.selection().isNone())
189         return false;
190
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 = m_frame.document()->focusedElement()) {
195         unsigned baseOffset = m_frame.selection().base().downstream().deprecatedEditingOffset();
196         Vector<CompositionUnderline> underlines;
197         for (size_t i = 0; i < m_customCompositionUnderlines.size(); ++i) {
198             CompositionUnderline underline = m_customCompositionUnderlines[i];
199             underline.startOffset -= baseOffset;
200             underline.endOffset -= baseOffset;
201             underlines.append(underline);
202         }
203         RefPtrWillBeRawPtr<CompositionEvent> event = CompositionEvent::create(EventTypeNames::compositionend, m_frame.domWindow(), text, underlines);
204         target->dispatchEvent(event, IGNORE_EXCEPTION);
205     }
206
207     // If text is empty, then delete the old composition here. If text is non-empty, InsertTextCommand::input
208     // will delete the old composition with an optimized replace operation.
209     if (text.isEmpty() && mode != CancelComposition) {
210         ASSERT(m_frame.document());
211         TypingCommand::deleteSelection(*m_frame.document(), 0);
212     }
213
214     m_compositionNode = nullptr;
215     m_customCompositionUnderlines.clear();
216
217     insertTextForConfirmedComposition(text);
218
219     if (mode == CancelComposition) {
220         // An open typing command that disagrees about current selection would cause issues with typing later on.
221         TypingCommand::closeTyping(&m_frame);
222     }
223
224     return true;
225 }
226
227 void InputMethodController::setComposition(const String& text, const Vector<CompositionUnderline>& underlines, unsigned selectionStart, unsigned selectionEnd)
228 {
229     Editor::RevealSelectionScope revealSelectionScope(&editor());
230
231     // Updates styles before setting selection for composition to prevent
232     // inserting the previous composition text into text nodes oddly.
233     // See https://bugs.webkit.org/show_bug.cgi?id=46868
234     m_frame.document()->updateRenderTreeIfNeeded();
235
236     selectComposition();
237
238     if (m_frame.selection().isNone())
239         return;
240
241     if (Element* target = m_frame.document()->focusedElement()) {
242         // Dispatch an appropriate composition event to the focused node.
243         // We check the composition status and choose an appropriate composition event since this
244         // function is used for three purposes:
245         // 1. Starting a new composition.
246         //    Send a compositionstart and a compositionupdate event when this function creates
247         //    a new composition node, i.e.
248         //    m_compositionNode == 0 && !text.isEmpty().
249         //    Sending a compositionupdate event at this time ensures that at least one
250         //    compositionupdate event is dispatched.
251         // 2. Updating the existing composition node.
252         //    Send a compositionupdate event when this function updates the existing composition
253         //    node, i.e. m_compositionNode != 0 && !text.isEmpty().
254         // 3. Canceling the ongoing composition.
255         //    Send a compositionend event when function deletes the existing composition node, i.e.
256         //    m_compositionNode != 0 && test.isEmpty().
257         RefPtrWillBeRawPtr<CompositionEvent> event = nullptr;
258         if (!hasComposition()) {
259             // We should send a compositionstart event only when the given text is not empty because this
260             // function doesn't create a composition node when the text is empty.
261             if (!text.isEmpty()) {
262                 target->dispatchEvent(CompositionEvent::create(EventTypeNames::compositionstart, m_frame.domWindow(), m_frame.selectedText(), underlines));
263                 event = CompositionEvent::create(EventTypeNames::compositionupdate, m_frame.domWindow(), text, underlines);
264             }
265         } else {
266             if (!text.isEmpty())
267                 event = CompositionEvent::create(EventTypeNames::compositionupdate, m_frame.domWindow(), text, underlines);
268             else
269                 event = CompositionEvent::create(EventTypeNames::compositionend, m_frame.domWindow(), text, underlines);
270         }
271         if (event.get())
272             target->dispatchEvent(event, IGNORE_EXCEPTION);
273     }
274
275     // If text is empty, then delete the old composition here. If text is non-empty, InsertTextCommand::input
276     // will delete the old composition with an optimized replace operation.
277     if (text.isEmpty()) {
278         ASSERT(m_frame.document());
279         TypingCommand::deleteSelection(*m_frame.document(), TypingCommand::PreventSpellChecking);
280     }
281
282     m_compositionNode = nullptr;
283     m_customCompositionUnderlines.clear();
284
285     if (!text.isEmpty()) {
286         ASSERT(m_frame.document());
287         TypingCommand::insertText(*m_frame.document(), text, TypingCommand::SelectInsertedText | TypingCommand::PreventSpellChecking, TypingCommand::TextCompositionUpdate);
288
289         // Find out what node has the composition now.
290         Position base = m_frame.selection().base().downstream();
291         Position extent = m_frame.selection().extent();
292         Node* baseNode = base.deprecatedNode();
293         unsigned baseOffset = base.deprecatedEditingOffset();
294         Node* extentNode = extent.deprecatedNode();
295         unsigned extentOffset = extent.deprecatedEditingOffset();
296
297         if (baseNode && baseNode == extentNode && baseNode->isTextNode() && baseOffset + text.length() == extentOffset) {
298             m_compositionNode = toText(baseNode);
299             m_compositionStart = baseOffset;
300             m_compositionEnd = extentOffset;
301             m_customCompositionUnderlines = underlines;
302             size_t numUnderlines = m_customCompositionUnderlines.size();
303             for (size_t i = 0; i < numUnderlines; ++i) {
304                 m_customCompositionUnderlines[i].startOffset += baseOffset;
305                 m_customCompositionUnderlines[i].endOffset += baseOffset;
306             }
307             if (baseNode->renderer())
308                 baseNode->renderer()->paintInvalidationForWholeRenderer();
309
310             unsigned start = std::min(baseOffset + selectionStart, extentOffset);
311             unsigned end = std::min(std::max(start, baseOffset + selectionEnd), extentOffset);
312             RefPtrWillBeRawPtr<Range> selectedRange = Range::create(baseNode->document(), baseNode, start, baseNode, end);
313             m_frame.selection().setSelectedRange(selectedRange.get(), DOWNSTREAM, FrameSelection::NonDirectional, NotUserTriggered);
314         }
315     }
316 }
317
318 void InputMethodController::setCompositionFromExistingText(const Vector<CompositionUnderline>& underlines, unsigned compositionStart, unsigned compositionEnd)
319 {
320     Element* editable = m_frame.selection().rootEditableElement();
321     Position base = m_frame.selection().base().downstream();
322     Node* baseNode = base.anchorNode();
323     if (editable->firstChild() == baseNode && editable->lastChild() == baseNode && baseNode->isTextNode()) {
324         m_compositionNode = nullptr;
325         m_customCompositionUnderlines.clear();
326
327         if (base.anchorType() != Position::PositionIsOffsetInAnchor)
328             return;
329         if (!baseNode || baseNode != m_frame.selection().extent().anchorNode())
330             return;
331
332         m_compositionNode = toText(baseNode);
333         RefPtrWillBeRawPtr<Range> range = PlainTextRange(compositionStart, compositionEnd).createRange(*editable);
334         if (!range)
335             return;
336
337         m_compositionStart = range->startOffset();
338         m_compositionEnd = range->endOffset();
339         m_customCompositionUnderlines = underlines;
340         size_t numUnderlines = m_customCompositionUnderlines.size();
341         for (size_t i = 0; i < numUnderlines; ++i) {
342             m_customCompositionUnderlines[i].startOffset += m_compositionStart;
343             m_customCompositionUnderlines[i].endOffset += m_compositionStart;
344         }
345         if (baseNode->renderer())
346             baseNode->renderer()->paintInvalidationForWholeRenderer();
347         return;
348     }
349
350     Editor::RevealSelectionScope revealSelectionScope(&editor());
351     SelectionOffsetsScope selectionOffsetsScope(this);
352     setSelectionOffsets(PlainTextRange(compositionStart, compositionEnd));
353     setComposition(m_frame.selectedText(), underlines, 0, 0);
354 }
355
356 PassRefPtrWillBeRawPtr<Range> InputMethodController::compositionRange() const
357 {
358     if (!hasComposition())
359         return nullptr;
360     unsigned length = m_compositionNode->length();
361     unsigned start = std::min(m_compositionStart, length);
362     unsigned end = std::min(std::max(start, m_compositionEnd), length);
363     if (start >= end)
364         return nullptr;
365     return Range::create(m_compositionNode->document(), m_compositionNode.get(), start, m_compositionNode.get(), end);
366 }
367
368 PlainTextRange InputMethodController::getSelectionOffsets() const
369 {
370     RefPtrWillBeRawPtr<Range> range = m_frame.selection().selection().firstRange();
371     if (!range)
372         return PlainTextRange();
373     ContainerNode* editable = m_frame.selection().rootEditableElementOrTreeScopeRootNode();
374     ASSERT(editable);
375     return PlainTextRange::create(*editable, *range.get());
376 }
377
378 bool InputMethodController::setSelectionOffsets(const PlainTextRange& selectionOffsets)
379 {
380     if (selectionOffsets.isNull())
381         return false;
382     Element* rootEditableElement = m_frame.selection().rootEditableElement();
383     if (!rootEditableElement)
384         return false;
385
386     RefPtrWillBeRawPtr<Range> range = selectionOffsets.createRange(*rootEditableElement);
387     if (!range)
388         return false;
389
390     return m_frame.selection().setSelectedRange(range.get(), VP_DEFAULT_AFFINITY, FrameSelection::NonDirectional, FrameSelection::CloseTyping);
391 }
392
393 bool InputMethodController::setEditableSelectionOffsets(const PlainTextRange& selectionOffsets)
394 {
395     if (!editor().canEdit())
396         return false;
397     return setSelectionOffsets(selectionOffsets);
398 }
399
400 void InputMethodController::extendSelectionAndDelete(int before, int after)
401 {
402     if (!editor().canEdit())
403         return;
404     PlainTextRange selectionOffsets(getSelectionOffsets());
405     if (selectionOffsets.isNull())
406         return;
407
408     // A common call of before=1 and after=0 will fail if the last character
409     // is multi-code-word UTF-16, including both multi-16bit code-points and
410     // Unicode combining character sequences of multiple single-16bit code-
411     // points (officially called "compositions"). Try more until success.
412     // http://crbug.com/355995
413     //
414     // FIXME: Note that this is not an ideal solution when this function is
415     // called to implement "backspace". In that case, there should be some call
416     // that will not delete a full multi-code-point composition but rather
417     // only the last code-point so that it's possible for a user to correct
418     // a composition without starting it from the beginning.
419     // http://crbug.com/37993
420     do {
421         if (!setSelectionOffsets(PlainTextRange(std::max(static_cast<int>(selectionOffsets.start()) - before, 0), selectionOffsets.end() + after)))
422             return;
423         if (before == 0)
424             break;
425         ++before;
426     } while (m_frame.selection().start() == m_frame.selection().end() && before <= static_cast<int>(selectionOffsets.start()));
427     TypingCommand::deleteSelection(*m_frame.document());
428 }
429
430 } // namespace blink