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/html/HTMLTextAreaElement.h"
38 #include "core/page/Chrome.h"
39 #include "core/page/ChromeClient.h"
40 #include "core/page/EventHandler.h"
41 #include "core/frame/Frame.h"
42 #include "core/rendering/RenderObject.h"
43
44 namespace WebCore {
45
SelectionOffsetsScope(InputMethodController * inputMethodController)46 InputMethodController::SelectionOffsetsScope::SelectionOffsetsScope(InputMethodController* inputMethodController)
47 : m_inputMethodController(inputMethodController)
48 , m_offsets(inputMethodController->getSelectionOffsets())
49 {
50 }
51
~SelectionOffsetsScope()52 InputMethodController::SelectionOffsetsScope::~SelectionOffsetsScope()
53 {
54 m_inputMethodController->setSelectionOffsets(m_offsets);
55 }
56
57 // ----------------------------
58
create(Frame & frame)59 PassOwnPtr<InputMethodController> InputMethodController::create(Frame& frame)
60 {
61 return adoptPtr(new InputMethodController(frame));
62 }
63
InputMethodController(Frame & frame)64 InputMethodController::InputMethodController(Frame& frame)
65 : m_frame(frame)
66 , m_compositionStart(0)
67 , m_compositionEnd(0)
68 {
69 }
70
~InputMethodController()71 InputMethodController::~InputMethodController()
72 {
73 }
74
hasComposition() const75 bool InputMethodController::hasComposition() const
76 {
77 return m_compositionNode && m_compositionNode->isContentEditable();
78 }
79
editor() const80 inline Editor& InputMethodController::editor() const
81 {
82 return m_frame.editor();
83 }
84
clear()85 void InputMethodController::clear()
86 {
87 m_compositionNode = 0;
88 m_customCompositionUnderlines.clear();
89 }
90
insertTextForConfirmedComposition(const String & text)91 bool InputMethodController::insertTextForConfirmedComposition(const String& text)
92 {
93 return m_frame.eventHandler().handleTextInputEvent(text, 0, TextEventInputComposition);
94 }
95
selectComposition() const96 void InputMethodController::selectComposition() const
97 {
98 RefPtr<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
confirmComposition()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
confirmComposition(const String & text)116 bool InputMethodController::confirmComposition(const String& text)
117 {
118 return finishComposition(text, ConfirmComposition);
119 }
120
confirmCompositionOrInsertText(const String & text,ConfirmCompositionBehavior confirmBehavior)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
confirmCompositionAndResetState()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
cancelComposition()151 void InputMethodController::cancelComposition()
152 {
153 finishComposition(emptyString(), CancelComposition);
154 }
155
cancelCompositionIfSelectionIsInvalid()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
finishComposition(const String & text,FinishCompositionMode mode)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 RefPtr<CompositionEvent> event = CompositionEvent::create(EventTypeNames::compositionend, m_frame.domWindow(), text);
196 target->dispatchEvent(event, IGNORE_EXCEPTION);
197 }
198
199 // If text is empty, then delete the old composition here. If text is non-empty, InsertTextCommand::input
200 // will delete the old composition with an optimized replace operation.
201 if (text.isEmpty() && mode != CancelComposition) {
202 ASSERT(m_frame.document());
203 TypingCommand::deleteSelection(*m_frame.document(), 0);
204 }
205
206 m_compositionNode = 0;
207 m_customCompositionUnderlines.clear();
208
209 insertTextForConfirmedComposition(text);
210
211 if (mode == CancelComposition) {
212 // An open typing command that disagrees about current selection would cause issues with typing later on.
213 TypingCommand::closeTyping(&m_frame);
214 }
215
216 return true;
217 }
218
setComposition(const String & text,const Vector<CompositionUnderline> & underlines,unsigned selectionStart,unsigned selectionEnd)219 void InputMethodController::setComposition(const String& text, const Vector<CompositionUnderline>& underlines, unsigned selectionStart, unsigned selectionEnd)
220 {
221 Editor::RevealSelectionScope revealSelectionScope(&editor());
222
223 // Updates styles before setting selection for composition to prevent
224 // inserting the previous composition text into text nodes oddly.
225 // See https://bugs.webkit.org/show_bug.cgi?id=46868
226 m_frame.document()->updateStyleIfNeeded();
227
228 selectComposition();
229
230 if (m_frame.selection().isNone())
231 return;
232
233 if (Element* target = m_frame.document()->focusedElement()) {
234 // Dispatch an appropriate composition event to the focused node.
235 // We check the composition status and choose an appropriate composition event since this
236 // function is used for three purposes:
237 // 1. Starting a new composition.
238 // Send a compositionstart and a compositionupdate event when this function creates
239 // a new composition node, i.e.
240 // m_compositionNode == 0 && !text.isEmpty().
241 // Sending a compositionupdate event at this time ensures that at least one
242 // compositionupdate event is dispatched.
243 // 2. Updating the existing composition node.
244 // Send a compositionupdate event when this function updates the existing composition
245 // node, i.e. m_compositionNode != 0 && !text.isEmpty().
246 // 3. Canceling the ongoing composition.
247 // Send a compositionend event when function deletes the existing composition node, i.e.
248 // m_compositionNode != 0 && test.isEmpty().
249 RefPtr<CompositionEvent> event;
250 if (!hasComposition()) {
251 // We should send a compositionstart event only when the given text is not empty because this
252 // function doesn't create a composition node when the text is empty.
253 if (!text.isEmpty()) {
254 target->dispatchEvent(CompositionEvent::create(EventTypeNames::compositionstart, m_frame.domWindow(), m_frame.selectedText()));
255 event = CompositionEvent::create(EventTypeNames::compositionupdate, m_frame.domWindow(), text);
256 }
257 } else {
258 if (!text.isEmpty())
259 event = CompositionEvent::create(EventTypeNames::compositionupdate, m_frame.domWindow(), text);
260 else
261 event = CompositionEvent::create(EventTypeNames::compositionend, m_frame.domWindow(), text);
262 }
263 if (event.get())
264 target->dispatchEvent(event, IGNORE_EXCEPTION);
265 }
266
267 // If text is empty, then delete the old composition here. If text is non-empty, InsertTextCommand::input
268 // will delete the old composition with an optimized replace operation.
269 if (text.isEmpty()) {
270 ASSERT(m_frame.document());
271 TypingCommand::deleteSelection(*m_frame.document(), TypingCommand::PreventSpellChecking);
272 }
273
274 m_compositionNode = 0;
275 m_customCompositionUnderlines.clear();
276
277 if (!text.isEmpty()) {
278 ASSERT(m_frame.document());
279 TypingCommand::insertText(*m_frame.document(), text, TypingCommand::SelectInsertedText | TypingCommand::PreventSpellChecking, TypingCommand::TextCompositionUpdate);
280
281 // Find out what node has the composition now.
282 Position base = m_frame.selection().base().downstream();
283 Position extent = m_frame.selection().extent();
284 Node* baseNode = base.deprecatedNode();
285 unsigned baseOffset = base.deprecatedEditingOffset();
286 Node* extentNode = extent.deprecatedNode();
287 unsigned extentOffset = extent.deprecatedEditingOffset();
288
289 if (baseNode && baseNode == extentNode && baseNode->isTextNode() && baseOffset + text.length() == extentOffset) {
290 m_compositionNode = toText(baseNode);
291 m_compositionStart = baseOffset;
292 m_compositionEnd = extentOffset;
293 m_customCompositionUnderlines = underlines;
294 size_t numUnderlines = m_customCompositionUnderlines.size();
295 for (size_t i = 0; i < numUnderlines; ++i) {
296 m_customCompositionUnderlines[i].startOffset += baseOffset;
297 m_customCompositionUnderlines[i].endOffset += baseOffset;
298 }
299 if (baseNode->renderer())
300 baseNode->renderer()->repaint();
301
302 unsigned start = std::min(baseOffset + selectionStart, extentOffset);
303 unsigned end = std::min(std::max(start, baseOffset + selectionEnd), extentOffset);
304 RefPtr<Range> selectedRange = Range::create(baseNode->document(), baseNode, start, baseNode, end);
305 m_frame.selection().setSelectedRange(selectedRange.get(), DOWNSTREAM, false);
306 }
307 }
308 }
309
setCompositionFromExistingText(const Vector<CompositionUnderline> & underlines,unsigned compositionStart,unsigned compositionEnd)310 void InputMethodController::setCompositionFromExistingText(const Vector<CompositionUnderline>& underlines, unsigned compositionStart, unsigned compositionEnd)
311 {
312 Node* editable = m_frame.selection().rootEditableElement();
313 Position base = m_frame.selection().base().downstream();
314 Node* baseNode = base.anchorNode();
315 if (editable->firstChild() == baseNode && editable->lastChild() == baseNode && baseNode->isTextNode()) {
316 m_compositionNode = 0;
317 m_customCompositionUnderlines.clear();
318
319 if (base.anchorType() != Position::PositionIsOffsetInAnchor)
320 return;
321 if (!baseNode || baseNode != m_frame.selection().extent().anchorNode())
322 return;
323
324 m_compositionNode = toText(baseNode);
325 m_compositionStart = compositionStart;
326 m_compositionEnd = compositionEnd;
327 m_customCompositionUnderlines = underlines;
328 size_t numUnderlines = m_customCompositionUnderlines.size();
329 for (size_t i = 0; i < numUnderlines; ++i) {
330 m_customCompositionUnderlines[i].startOffset += compositionStart;
331 m_customCompositionUnderlines[i].endOffset += compositionStart;
332 }
333 if (baseNode->renderer())
334 baseNode->renderer()->repaint();
335 return;
336 }
337
338 Editor::RevealSelectionScope revealSelectionScope(&editor());
339 SelectionOffsetsScope selectionOffsetsScope(this);
340 setSelectionOffsets(PlainTextRange(compositionStart, compositionEnd));
341 setComposition(m_frame.selectedText(), underlines, 0, 0);
342 }
343
compositionRange() const344 PassRefPtr<Range> InputMethodController::compositionRange() const
345 {
346 if (!hasComposition())
347 return 0;
348 unsigned length = m_compositionNode->length();
349 unsigned start = std::min(m_compositionStart, length);
350 unsigned end = std::min(std::max(start, m_compositionEnd), length);
351 if (start >= end)
352 return 0;
353 return Range::create(m_compositionNode->document(), m_compositionNode.get(), start, m_compositionNode.get(), end);
354 }
355
getSelectionOffsets() const356 PlainTextRange InputMethodController::getSelectionOffsets() const
357 {
358 RefPtr<Range> range = m_frame.selection().selection().firstRange();
359 if (!range)
360 return PlainTextRange();
361 Node* editable = m_frame.selection().rootEditableElementOrTreeScopeRootNode();
362 ASSERT(editable);
363 return PlainTextRange::create(*editable, *range.get());
364 }
365
setSelectionOffsets(const PlainTextRange & selectionOffsets)366 bool InputMethodController::setSelectionOffsets(const PlainTextRange& selectionOffsets)
367 {
368 if (selectionOffsets.isNull())
369 return false;
370 Element* rootEditableElement = m_frame.selection().rootEditableElement();
371 if (!rootEditableElement)
372 return false;
373
374 RefPtr<Range> range = selectionOffsets.createRange(*rootEditableElement);
375 if (!range)
376 return false;
377
378 return m_frame.selection().setSelectedRange(range.get(), VP_DEFAULT_AFFINITY, true);
379 }
380
setEditableSelectionOffsets(const PlainTextRange & selectionOffsets)381 bool InputMethodController::setEditableSelectionOffsets(const PlainTextRange& selectionOffsets)
382 {
383 if (!editor().canEdit())
384 return false;
385 return setSelectionOffsets(selectionOffsets);
386 }
387
extendSelectionAndDelete(int before,int after)388 void InputMethodController::extendSelectionAndDelete(int before, int after)
389 {
390 if (!editor().canEdit())
391 return;
392 PlainTextRange selectionOffsets(getSelectionOffsets());
393 if (selectionOffsets.isNull())
394 return;
395 setSelectionOffsets(PlainTextRange(std::max(static_cast<int>(selectionOffsets.start()) - before, 0), selectionOffsets.end() + after));
396 TypingCommand::deleteSelection(*m_frame.document());
397 }
398
399 } // namespace WebCore
400