OLD | NEW |
1 /* | 1 /* |
2 * Copyright (C) 2006, 2007, 2008, 2011 Apple Inc. All rights reserved. | 2 * Copyright (C) 2006, 2007, 2008, 2011 Apple Inc. All rights reserved. |
3 * Copyright (C) 2008 Nokia Corporation and/or its subsidiary(-ies) | 3 * Copyright (C) 2008 Nokia Corporation and/or its subsidiary(-ies) |
4 * | 4 * |
5 * Redistribution and use in source and binary forms, with or without | 5 * Redistribution and use in source and binary forms, with or without |
6 * modification, are permitted provided that the following conditions | 6 * modification, are permitted provided that the following conditions |
7 * are met: | 7 * are met: |
8 * 1. Redistributions of source code must retain the above copyright | 8 * 1. Redistributions of source code must retain the above copyright |
9 * notice, this list of conditions and the following disclaimer. | 9 * notice, this list of conditions and the following disclaimer. |
10 * 2. Redistributions in binary form must reproduce the above copyright | 10 * 2. Redistributions in binary form must reproduce the above copyright |
(...skipping 22 matching lines...) Expand all Loading... |
33 #include "core/editing/Editor.h" | 33 #include "core/editing/Editor.h" |
34 #include "core/editing/commands/TypingCommand.h" | 34 #include "core/editing/commands/TypingCommand.h" |
35 #include "core/editing/markers/DocumentMarkerController.h" | 35 #include "core/editing/markers/DocumentMarkerController.h" |
36 #include "core/events/CompositionEvent.h" | 36 #include "core/events/CompositionEvent.h" |
37 #include "core/frame/LocalFrame.h" | 37 #include "core/frame/LocalFrame.h" |
38 #include "core/html/HTMLTextAreaElement.h" | 38 #include "core/html/HTMLTextAreaElement.h" |
39 #include "core/input/EventHandler.h" | 39 #include "core/input/EventHandler.h" |
40 #include "core/layout/LayoutObject.h" | 40 #include "core/layout/LayoutObject.h" |
41 #include "core/layout/LayoutTheme.h" | 41 #include "core/layout/LayoutTheme.h" |
42 #include "core/page/ChromeClient.h" | 42 #include "core/page/ChromeClient.h" |
43 #include "wtf/Optional.h" | |
44 | 43 |
45 namespace blink { | 44 namespace blink { |
46 | 45 |
47 namespace { | 46 namespace { |
48 | 47 |
49 void dispatchCompositionUpdateEvent(LocalFrame& frame, const String& text) | 48 void dispatchCompositionUpdateEvent(LocalFrame& frame, const String& text) |
50 { | 49 { |
51 Element* target = frame.document()->focusedElement(); | 50 Element* target = frame.document()->focusedElement(); |
52 if (!target) | 51 if (!target) |
53 return; | 52 return; |
(...skipping 109 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
163 if (range.isNull()) | 162 if (range.isNull()) |
164 return; | 163 return; |
165 | 164 |
166 // The composition can start inside a composed character sequence, so we hav
e to override checks. | 165 // The composition can start inside a composed character sequence, so we hav
e to override checks. |
167 // See <http://bugs.webkit.org/show_bug.cgi?id=15781> | 166 // See <http://bugs.webkit.org/show_bug.cgi?id=15781> |
168 VisibleSelection selection; | 167 VisibleSelection selection; |
169 selection.setWithoutValidation(range.startPosition(), range.endPosition()); | 168 selection.setWithoutValidation(range.startPosition(), range.endPosition()); |
170 frame().selection().setSelection(selection, 0); | 169 frame().selection().setSelection(selection, 0); |
171 } | 170 } |
172 | 171 |
173 bool InputMethodController::confirmComposition() | 172 bool InputMethodController::finishComposingText(ConfirmCompositionBehavior confi
rmBehavior) |
174 { | |
175 return confirmComposition(composingText()); | |
176 } | |
177 | |
178 bool InputMethodController::confirmComposition(const String& text, ConfirmCompos
itionBehavior confirmBehavior) | |
179 { | 173 { |
180 if (!hasComposition()) | 174 if (!hasComposition()) |
181 return false; | 175 return false; |
182 | 176 |
183 Optional<Editor::RevealSelectionScope> revealSelectionScope; | 177 if (confirmBehavior == KeepSelection) { |
184 if (confirmBehavior == KeepSelection) | 178 PlainTextRange oldOffsets = getSelectionOffsets(); |
185 revealSelectionScope.emplace(&editor()); | 179 Editor::RevealSelectionScope revealSelectionScope(&editor()); |
| 180 |
| 181 bool result = replaceComposition(composingText()); |
| 182 |
| 183 // TODO(xiaochengh): The use of updateStyleAndLayoutIgnorePendingStylesh
eets |
| 184 // needs to be audited. see http://crbug.com/590369 for more details. |
| 185 frame().document()->updateStyleAndLayoutIgnorePendingStylesheets(); |
| 186 |
| 187 setSelectionOffsets(oldOffsets); |
| 188 return result; |
| 189 } |
| 190 |
| 191 return replaceCompositionAndMoveCaret(composingText(), 0); |
| 192 } |
| 193 |
| 194 bool InputMethodController::commitText(const String& text, int relativeCaretPosi
tion) |
| 195 { |
| 196 if (hasComposition()) |
| 197 return replaceCompositionAndMoveCaret(text, relativeCaretPosition); |
| 198 |
| 199 // We should do nothing in this case, because: |
| 200 // 1. No need to insert text when text is empty. |
| 201 // 2. Shouldn't move caret when relativeCaretPosition == 0 to avoid |
| 202 // duplicate selection change event. |
| 203 if (!text.length() && !relativeCaretPosition) |
| 204 return false; |
| 205 return insertTextAndMoveCaret(text, relativeCaretPosition); |
| 206 } |
| 207 |
| 208 bool InputMethodController::replaceComposition(const String& text) |
| 209 { |
| 210 if (!hasComposition()) |
| 211 return false; |
186 | 212 |
187 // If the composition was set from existing text and didn't change, then | 213 // If the composition was set from existing text and didn't change, then |
188 // there's nothing to do here (and we should avoid doing anything as that | 214 // there's nothing to do here (and we should avoid doing anything as that |
189 // may clobber multi-node styled text). | 215 // may clobber multi-node styled text). |
190 if (!m_isDirty && composingText() == text) { | 216 if (!m_isDirty && composingText() == text) { |
191 clear(); | 217 clear(); |
192 return true; | 218 return true; |
193 } | 219 } |
194 | 220 |
195 // Select the text that will be deleted or replaced. | 221 // Select the text that will be deleted or replaced. |
(...skipping 17 matching lines...) Expand all Loading... |
213 // Event handler might destroy document. | 239 // Event handler might destroy document. |
214 if (!frame().document()) | 240 if (!frame().document()) |
215 return false; | 241 return false; |
216 | 242 |
217 // No DOM update after 'compositionend'. | 243 // No DOM update after 'compositionend'. |
218 dispatchCompositionEndEvent(frame(), text); | 244 dispatchCompositionEndEvent(frame(), text); |
219 | 245 |
220 return true; | 246 return true; |
221 } | 247 } |
222 | 248 |
223 bool InputMethodController::confirmCompositionOrInsertText(const String& text, C
onfirmCompositionBehavior confirmBehavior) | 249 // relativeCaretPosition is relative to the end of the text. |
| 250 static int computeAbsoluteCaretPosition(size_t textStart, size_t textLength, int
relativeCaretPosition) |
224 { | 251 { |
225 if (!hasComposition()) { | 252 return textStart + textLength + relativeCaretPosition; |
226 if (!text.length()) | 253 } |
| 254 |
| 255 bool InputMethodController::replaceCompositionAndMoveCaret(const String& text, i
nt relativeCaretPosition) |
| 256 { |
| 257 Element* rootEditableElement = frame().selection().rootEditableElement(); |
| 258 if (!rootEditableElement) |
| 259 return false; |
| 260 PlainTextRange compositionRange = PlainTextRange::create(*rootEditableElemen
t, *m_compositionRange); |
| 261 if (compositionRange.isNull()) |
| 262 return false; |
| 263 int textStart = compositionRange.start(); |
| 264 |
| 265 if (!replaceComposition(text)) |
| 266 return false; |
| 267 |
| 268 int absoluteCaretPosition = computeAbsoluteCaretPosition(textStart, text.len
gth(), relativeCaretPosition); |
| 269 return moveCaret(absoluteCaretPosition); |
| 270 } |
| 271 |
| 272 bool InputMethodController::insertText(const String& text) |
| 273 { |
| 274 if (dispatchBeforeInputInsertText(frame().document()->focusedElement(), text
) != DispatchEventResult::NotCanceled) |
| 275 return false; |
| 276 editor().insertText(text, 0); |
| 277 return true; |
| 278 } |
| 279 |
| 280 bool InputMethodController::insertTextAndMoveCaret(const String& text, int relat
iveCaretPosition) |
| 281 { |
| 282 PlainTextRange selectionRange = getSelectionOffsets(); |
| 283 if (selectionRange.isNull()) |
| 284 return false; |
| 285 int textStart = selectionRange.start(); |
| 286 |
| 287 if (text.length()) { |
| 288 if (!insertText(text)) |
227 return false; | 289 return false; |
228 | |
229 if (dispatchBeforeInputInsertText(frame().document()->focusedElement(),
text) != DispatchEventResult::NotCanceled) | |
230 return false; | |
231 | |
232 editor().insertText(text, 0); | |
233 return true; | |
234 } | 290 } |
235 | 291 |
236 if (text.length()) { | 292 int absoluteCaretPosition = computeAbsoluteCaretPosition(textStart, text.len
gth(), relativeCaretPosition); |
237 confirmComposition(text); | 293 return moveCaret(absoluteCaretPosition); |
238 return true; | |
239 } | |
240 | |
241 if (confirmBehavior == DoNotKeepSelection) | |
242 return confirmComposition(composingText(), DoNotKeepSelection); | |
243 | |
244 PlainTextRange oldOffsets = getSelectionOffsets(); | |
245 bool result = confirmComposition(); | |
246 | |
247 // TODO(xiaochengh): The use of updateStyleAndLayoutIgnorePendingStylesheets | |
248 // needs to be audited. see http://crbug.com/590369 for more details. | |
249 frame().document()->updateStyleAndLayoutIgnorePendingStylesheets(); | |
250 | |
251 setSelectionOffsets(oldOffsets); | |
252 return result; | |
253 } | 294 } |
254 | 295 |
255 void InputMethodController::cancelComposition() | 296 void InputMethodController::cancelComposition() |
256 { | 297 { |
257 if (!hasComposition()) | 298 if (!hasComposition()) |
258 return; | 299 return; |
259 | 300 |
260 Editor::RevealSelectionScope revealSelectionScope(&editor()); | 301 Editor::RevealSelectionScope revealSelectionScope(&editor()); |
261 | 302 |
262 if (frame().selection().isNone()) | 303 if (frame().selection().isNone()) |
(...skipping 71 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
334 // Sending a compositionupdate event at this time ensures that at least o
ne | 375 // Sending a compositionupdate event at this time ensures that at least o
ne |
335 // compositionupdate event is dispatched. | 376 // compositionupdate event is dispatched. |
336 // 2. Updating the existing composition node. | 377 // 2. Updating the existing composition node. |
337 // Send a compositionupdate event when this function updates the existing
composition | 378 // Send a compositionupdate event when this function updates the existing
composition |
338 // node, i.e. hasComposition() && !text.isEmpty(). | 379 // node, i.e. hasComposition() && !text.isEmpty(). |
339 // 3. Canceling the ongoing composition. | 380 // 3. Canceling the ongoing composition. |
340 // Send a compositionend event when function deletes the existing composi
tion node, i.e. | 381 // Send a compositionend event when function deletes the existing composi
tion node, i.e. |
341 // !hasComposition() && test.isEmpty(). | 382 // !hasComposition() && test.isEmpty(). |
342 if (text.isEmpty()) { | 383 if (text.isEmpty()) { |
343 if (hasComposition()) { | 384 if (hasComposition()) { |
344 confirmComposition(emptyString()); | 385 Editor::RevealSelectionScope revealSelectionScope(&editor()); |
| 386 replaceComposition(emptyString()); |
345 } else { | 387 } else { |
346 // It's weird to call |setComposition()| with empty text outside com
position, however some IME | 388 // It's weird to call |setComposition()| with empty text outside com
position, however some IME |
347 // (e.g. Japanese IBus-Anthy) did this, so we simply delete selectio
n without sending extra events. | 389 // (e.g. Japanese IBus-Anthy) did this, so we simply delete selectio
n without sending extra events. |
348 TypingCommand::deleteSelection(*frame().document(), TypingCommand::P
reventSpellChecking); | 390 TypingCommand::deleteSelection(*frame().document(), TypingCommand::P
reventSpellChecking); |
349 } | 391 } |
350 | 392 |
351 // TODO(xiaochengh): The use of updateStyleAndLayoutIgnorePendingStylesh
eets | 393 // TODO(xiaochengh): The use of updateStyleAndLayoutIgnorePendingStylesh
eets |
352 // needs to be audited. see http://crbug.com/590369 for more details. | 394 // needs to be audited. see http://crbug.com/590369 for more details. |
353 frame().document()->updateStyleAndLayoutIgnorePendingStylesheets(); | 395 frame().document()->updateStyleAndLayoutIgnorePendingStylesheets(); |
354 | 396 |
(...skipping 184 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
539 | 581 |
540 rightBoundary += textLength; | 582 rightBoundary += textLength; |
541 | 583 |
542 // In case of exceeding the right boundary. | 584 // In case of exceeding the right boundary. |
543 start = std::min(start, rightBoundary); | 585 start = std::min(start, rightBoundary); |
544 end = std::min(end, rightBoundary); | 586 end = std::min(end, rightBoundary); |
545 | 587 |
546 return PlainTextRange(start, end); | 588 return PlainTextRange(start, end); |
547 } | 589 } |
548 | 590 |
| 591 bool InputMethodController::moveCaret(int newCaretPosition) |
| 592 { |
| 593 frame().document()->updateStyleAndLayoutIgnorePendingStylesheets(); |
| 594 PlainTextRange selectedRange = createRangeForSelection(newCaretPosition, new
CaretPosition, 0); |
| 595 if (selectedRange.isNull()) |
| 596 return false; |
| 597 return setEditableSelectionOffsets(selectedRange); |
| 598 } |
| 599 |
549 void InputMethodController::extendSelectionAndDelete(int before, int after) | 600 void InputMethodController::extendSelectionAndDelete(int before, int after) |
550 { | 601 { |
551 if (!editor().canEdit()) | 602 if (!editor().canEdit()) |
552 return; | 603 return; |
553 PlainTextRange selectionOffsets(getSelectionOffsets()); | 604 PlainTextRange selectionOffsets(getSelectionOffsets()); |
554 if (selectionOffsets.isNull()) | 605 if (selectionOffsets.isNull()) |
555 return; | 606 return; |
556 | 607 |
557 // A common call of before=1 and after=0 will fail if the last character | 608 // A common call of before=1 and after=0 will fail if the last character |
558 // is multi-code-word UTF-16, including both multi-16bit code-points and | 609 // is multi-code-word UTF-16, including both multi-16bit code-points and |
(...skipping 19 matching lines...) Expand all Loading... |
578 TypingCommand::deleteSelection(*frame().document()); | 629 TypingCommand::deleteSelection(*frame().document()); |
579 } | 630 } |
580 | 631 |
581 DEFINE_TRACE(InputMethodController) | 632 DEFINE_TRACE(InputMethodController) |
582 { | 633 { |
583 visitor->trace(m_frame); | 634 visitor->trace(m_frame); |
584 visitor->trace(m_compositionRange); | 635 visitor->trace(m_compositionRange); |
585 } | 636 } |
586 | 637 |
587 } // namespace blink | 638 } // namespace blink |
OLD | NEW |