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 26 matching lines...) Expand all Loading... |
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" | 43 #include "wtf/Optional.h" |
44 | 44 |
45 namespace blink { | 45 namespace blink { |
46 | 46 |
| 47 namespace { |
| 48 |
| 49 void dispatchCompositionUpdateEvent(LocalFrame& frame, const String& text) |
| 50 { |
| 51 Element* target = frame.document()->focusedElement(); |
| 52 if (!target) |
| 53 return; |
| 54 |
| 55 CompositionEvent* event = CompositionEvent::create(EventTypeNames::compositi
onupdate, frame.domWindow(), text); |
| 56 target->dispatchEvent(event); |
| 57 } |
| 58 |
| 59 void dispatchCompositionEndEvent(LocalFrame& frame, const String& text) |
| 60 { |
| 61 Element* target = frame.document()->focusedElement(); |
| 62 if (!target) |
| 63 return; |
| 64 |
| 65 CompositionEvent* event = CompositionEvent::create(EventTypeNames::compositi
onend, frame.domWindow(), text); |
| 66 target->dispatchEvent(event); |
| 67 } |
| 68 |
| 69 // Used to insert/replace text during composition update and confirm composition
. |
| 70 // Procedure: |
| 71 // 1. Fire 'beforeinput' event for (TODO(chongz): deleted composed text) and i
nserted text |
| 72 // 2. Fire 'compositionupdate' event |
| 73 // 3. Fire TextEvent and modify DOM |
| 74 // TODO(chongz): 4. Fire 'input' event |
| 75 void insertTextDuringCompositionWithEvents(LocalFrame& frame, const String& text
, TypingCommand::Options options, TypingCommand::TextCompositionType composition
Type) |
| 76 { |
| 77 DCHECK(compositionType == TypingCommand::TextCompositionType::TextCompositio
nUpdate || compositionType == TypingCommand::TextCompositionType::TextCompositio
nConfirm) |
| 78 << "compositionType should be TextCompositionUpdate or TextCompositionCo
nfirm, but got " << static_cast<int>(compositionType); |
| 79 if (!frame.document()) |
| 80 return; |
| 81 |
| 82 Element* target = frame.document()->focusedElement(); |
| 83 if (!target) |
| 84 return; |
| 85 |
| 86 // TODO(chongz): Fire 'beforeinput' for the composed text being replaced/del
eted. |
| 87 |
| 88 // Only the last confirmed text is cancelable. |
| 89 InputEvent::EventCancelable beforeInputCancelable = (compositionType == Typi
ngCommand::TextCompositionType::TextCompositionUpdate) ? InputEvent::EventCancel
able::NotCancelable : InputEvent::EventCancelable::IsCancelable; |
| 90 DispatchEventResult result = dispatchBeforeInputFromComposition(target, Inpu
tEvent::InputType::InsertText, text, beforeInputCancelable); |
| 91 |
| 92 if (beforeInputCancelable == InputEvent::EventCancelable::IsCancelable && re
sult != DispatchEventResult::NotCanceled) |
| 93 return; |
| 94 |
| 95 // 'beforeinput' event handler may destroy document. |
| 96 if (!frame.document()) |
| 97 return; |
| 98 |
| 99 dispatchCompositionUpdateEvent(frame, text); |
| 100 // 'compositionupdate' event handler may destroy document. |
| 101 if (!frame.document()) |
| 102 return; |
| 103 |
| 104 switch (compositionType) { |
| 105 case TypingCommand::TextCompositionType::TextCompositionUpdate: |
| 106 TypingCommand::insertText(*frame.document(), text, options, compositionT
ype); |
| 107 break; |
| 108 case TypingCommand::TextCompositionType::TextCompositionConfirm: |
| 109 // TODO(chongz): Use TypingCommand::insertText after TextEvent was remov
ed. (Removed from spec since 2012) |
| 110 // See TextEvent.idl. |
| 111 frame.eventHandler().handleTextInputEvent(text, 0, TextEventInputComposi
tion); |
| 112 break; |
| 113 default: |
| 114 NOTREACHED(); |
| 115 } |
| 116 // TODO(chongz): Fire 'input' event. |
| 117 } |
| 118 |
| 119 } // anonymous namespace |
| 120 |
47 InputMethodController::SelectionOffsetsScope::SelectionOffsetsScope(InputMethodC
ontroller* inputMethodController) | 121 InputMethodController::SelectionOffsetsScope::SelectionOffsetsScope(InputMethodC
ontroller* inputMethodController) |
48 : m_inputMethodController(inputMethodController) | 122 : m_inputMethodController(inputMethodController) |
49 , m_offsets(inputMethodController->getSelectionOffsets()) | 123 , m_offsets(inputMethodController->getSelectionOffsets()) |
50 { | 124 { |
51 } | 125 } |
52 | 126 |
53 InputMethodController::SelectionOffsetsScope::~SelectionOffsetsScope() | 127 InputMethodController::SelectionOffsetsScope::~SelectionOffsetsScope() |
54 { | 128 { |
55 m_inputMethodController->setSelectionOffsets(m_offsets); | 129 m_inputMethodController->setSelectionOffsets(m_offsets); |
56 } | 130 } |
(...skipping 32 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
89 frame().document()->markers().removeMarkers(DocumentMarker::Composition); | 163 frame().document()->markers().removeMarkers(DocumentMarker::Composition); |
90 m_isDirty = false; | 164 m_isDirty = false; |
91 } | 165 } |
92 | 166 |
93 void InputMethodController::documentDetached() | 167 void InputMethodController::documentDetached() |
94 { | 168 { |
95 clear(); | 169 clear(); |
96 m_compositionRange = nullptr; | 170 m_compositionRange = nullptr; |
97 } | 171 } |
98 | 172 |
99 bool InputMethodController::insertTextForConfirmedComposition(const String& text
) | |
100 { | |
101 return frame().eventHandler().handleTextInputEvent(text, 0, TextEventInputCo
mposition); | |
102 } | |
103 | |
104 void InputMethodController::selectComposition() const | 173 void InputMethodController::selectComposition() const |
105 { | 174 { |
106 const EphemeralRange range = compositionEphemeralRange(); | 175 const EphemeralRange range = compositionEphemeralRange(); |
107 if (range.isNull()) | 176 if (range.isNull()) |
108 return; | 177 return; |
109 | 178 |
110 // The composition can start inside a composed character sequence, so we hav
e to override checks. | 179 // The composition can start inside a composed character sequence, so we hav
e to override checks. |
111 // See <http://bugs.webkit.org/show_bug.cgi?id=15781> | 180 // See <http://bugs.webkit.org/show_bug.cgi?id=15781> |
112 VisibleSelection selection; | 181 VisibleSelection selection; |
113 selection.setWithoutValidation(range.startPosition(), range.endPosition()); | 182 selection.setWithoutValidation(range.startPosition(), range.endPosition()); |
114 frame().selection().setSelection(selection, 0); | 183 frame().selection().setSelection(selection, 0); |
115 } | 184 } |
116 | 185 |
117 bool InputMethodController::confirmComposition() | 186 bool InputMethodController::confirmComposition() |
118 { | 187 { |
119 return confirmComposition(composingText()); | 188 return confirmComposition(composingText()); |
120 } | 189 } |
121 | 190 |
122 static void dispatchCompositionEndEvent(LocalFrame& frame, const String& text) | |
123 { | |
124 // We should send this event before sending a TextEvent as written in | |
125 // Section 6.2.2 and 6.2.3 of the DOM Event specification. | |
126 Element* target = frame.document()->focusedElement(); | |
127 if (!target) | |
128 return; | |
129 | |
130 CompositionEvent* event = | |
131 CompositionEvent::create(EventTypeNames::compositionend, frame.domWindow
(), text); | |
132 target->dispatchEvent(event); | |
133 } | |
134 | |
135 bool InputMethodController::confirmComposition(const String& text, ConfirmCompos
itionBehavior confirmBehavior) | 191 bool InputMethodController::confirmComposition(const String& text, ConfirmCompos
itionBehavior confirmBehavior) |
136 { | 192 { |
137 if (!hasComposition()) | 193 if (!hasComposition()) |
138 return false; | 194 return false; |
139 | 195 |
140 Optional<Editor::RevealSelectionScope> revealSelectionScope; | 196 Optional<Editor::RevealSelectionScope> revealSelectionScope; |
141 if (confirmBehavior == KeepSelection) | 197 if (confirmBehavior == KeepSelection) |
142 revealSelectionScope.emplace(&editor()); | 198 revealSelectionScope.emplace(&editor()); |
143 | 199 |
144 // If the composition was set from existing text and didn't change, then | 200 // If the composition was set from existing text and didn't change, then |
145 // there's nothing to do here (and we should avoid doing anything as that | 201 // there's nothing to do here (and we should avoid doing anything as that |
146 // may clobber multi-node styled text). | 202 // may clobber multi-node styled text). |
147 if (!m_isDirty && composingText() == text) { | 203 if (!m_isDirty && composingText() == text) { |
148 clear(); | 204 clear(); |
149 return true; | 205 return true; |
150 } | 206 } |
151 | 207 |
152 // Select the text that will be deleted or replaced. | 208 // Select the text that will be deleted or replaced. |
153 selectComposition(); | 209 selectComposition(); |
154 | 210 |
155 if (frame().selection().isNone()) | 211 if (frame().selection().isNone()) |
156 return false; | 212 return false; |
157 | 213 |
158 dispatchCompositionEndEvent(frame(), text); | |
159 | |
160 if (!frame().document()) | 214 if (!frame().document()) |
161 return false; | 215 return false; |
162 | 216 |
163 // If text is empty, then delete the old composition here. If text is | 217 // If text is empty, then delete the old composition here. If text is |
164 // non-empty, InsertTextCommand::input will delete the old composition with | 218 // non-empty, InsertTextCommand::input will delete the old composition with |
165 // an optimized replace operation. | 219 // an optimized replace operation. |
166 if (text.isEmpty()) | 220 if (text.isEmpty()) |
167 TypingCommand::deleteSelection(*frame().document(), 0); | 221 TypingCommand::deleteSelection(*frame().document(), 0); |
168 | 222 |
169 clear(); | 223 clear(); |
170 | 224 |
171 // TODO(chongz): DOM update should happen before 'compositionend' and along
with 'compositionupdate'. | 225 insertTextDuringCompositionWithEvents(frame(), text, 0, TypingCommand::TextC
ompositionType::TextCompositionConfirm); |
172 // https://crbug.com/575294 | 226 // Event handler might destroy document. |
173 if (dispatchBeforeInputInsertText(frame().document()->focusedElement(), text
) != DispatchEventResult::NotCanceled) | 227 if (!frame().document()) |
174 return false; | 228 return false; |
175 | 229 |
176 insertTextForConfirmedComposition(text); | 230 // No DOM update after 'compositionend'. |
| 231 dispatchCompositionEndEvent(frame(), text); |
177 | 232 |
178 return true; | 233 return true; |
179 } | 234 } |
180 | 235 |
181 bool InputMethodController::confirmCompositionOrInsertText(const String& text, C
onfirmCompositionBehavior confirmBehavior) | 236 bool InputMethodController::confirmCompositionOrInsertText(const String& text, C
onfirmCompositionBehavior confirmBehavior) |
182 { | 237 { |
183 if (!hasComposition()) { | 238 if (!hasComposition()) { |
184 if (!text.length()) | 239 if (!text.length()) |
185 return false; | 240 return false; |
186 | 241 |
(...skipping 19 matching lines...) Expand all Loading... |
206 void InputMethodController::cancelComposition() | 261 void InputMethodController::cancelComposition() |
207 { | 262 { |
208 if (!hasComposition()) | 263 if (!hasComposition()) |
209 return; | 264 return; |
210 | 265 |
211 Editor::RevealSelectionScope revealSelectionScope(&editor()); | 266 Editor::RevealSelectionScope revealSelectionScope(&editor()); |
212 | 267 |
213 if (frame().selection().isNone()) | 268 if (frame().selection().isNone()) |
214 return; | 269 return; |
215 | 270 |
216 dispatchCompositionEndEvent(frame(), emptyString()); | |
217 clear(); | 271 clear(); |
218 insertTextForConfirmedComposition(emptyString()); | 272 |
| 273 // TODO(chongz): Update InputType::DeleteComposedCharacter with latest discu
ssion. |
| 274 dispatchBeforeInputFromComposition(frame().document()->focusedElement(), Inp
utEvent::InputType::DeleteComposedCharacter, emptyString(), InputEvent::EventCan
celable::NotCancelable); |
| 275 dispatchCompositionUpdateEvent(frame(), emptyString()); |
| 276 insertTextDuringCompositionWithEvents(frame(), emptyString(), 0, TypingComma
nd::TextCompositionType::TextCompositionConfirm); |
| 277 // Event handler might destroy document. |
| 278 if (!frame().document()) |
| 279 return; |
219 | 280 |
220 // An open typing command that disagrees about current selection would cause | 281 // An open typing command that disagrees about current selection would cause |
221 // issues with typing later on. | 282 // issues with typing later on. |
222 TypingCommand::closeTyping(m_frame); | 283 TypingCommand::closeTyping(m_frame); |
| 284 |
| 285 // No DOM update after 'compositionend'. |
| 286 dispatchCompositionEndEvent(frame(), emptyString()); |
223 } | 287 } |
224 | 288 |
225 void InputMethodController::cancelCompositionIfSelectionIsInvalid() | 289 void InputMethodController::cancelCompositionIfSelectionIsInvalid() |
226 { | 290 { |
227 if (!hasComposition() || editor().preventRevealSelection()) | 291 if (!hasComposition() || editor().preventRevealSelection()) |
228 return; | 292 return; |
229 | 293 |
230 // Check if selection start and selection end are valid. | 294 // Check if selection start and selection end are valid. |
231 FrameSelection& selection = frame().selection(); | 295 FrameSelection& selection = frame().selection(); |
232 if (!selection.isNone() && !m_compositionRange->collapsed()) { | 296 if (!selection.isNone() && !m_compositionRange->collapsed()) { |
(...skipping 13 matching lines...) Expand all Loading... |
246 // Updates styles before setting selection for composition to prevent | 310 // Updates styles before setting selection for composition to prevent |
247 // inserting the previous composition text into text nodes oddly. | 311 // inserting the previous composition text into text nodes oddly. |
248 // See https://bugs.webkit.org/show_bug.cgi?id=46868 | 312 // See https://bugs.webkit.org/show_bug.cgi?id=46868 |
249 frame().document()->updateStyleAndLayoutTree(); | 313 frame().document()->updateStyleAndLayoutTree(); |
250 | 314 |
251 selectComposition(); | 315 selectComposition(); |
252 | 316 |
253 if (frame().selection().isNone()) | 317 if (frame().selection().isNone()) |
254 return; | 318 return; |
255 | 319 |
256 if (Element* target = frame().document()->focusedElement()) { | 320 Element* target = frame().document()->focusedElement(); |
257 // Dispatch an appropriate composition event to the focused node. | 321 if (!target) |
258 // We check the composition status and choose an appropriate composition
event since this | 322 return; |
259 // function is used for three purposes: | 323 |
260 // 1. Starting a new composition. | 324 // Dispatch an appropriate composition event to the focused node. |
261 // Send a compositionstart and a compositionupdate event when this fu
nction creates | 325 // We check the composition status and choose an appropriate composition eve
nt since this |
262 // a new composition node, i.e. | 326 // function is used for three purposes: |
263 // !hasComposition() && !text.isEmpty(). | 327 // 1. Starting a new composition. |
264 // Sending a compositionupdate event at this time ensures that at lea
st one | 328 // Send a compositionstart and a compositionupdate event when this functi
on creates |
265 // compositionupdate event is dispatched. | 329 // a new composition node, i.e. |
266 // 2. Updating the existing composition node. | 330 // !hasComposition() && !text.isEmpty(). |
267 // Send a compositionupdate event when this function updates the exis
ting composition | 331 // Sending a compositionupdate event at this time ensures that at least o
ne |
268 // node, i.e. hasComposition() && !text.isEmpty(). | 332 // compositionupdate event is dispatched. |
269 // 3. Canceling the ongoing composition. | 333 // 2. Updating the existing composition node. |
270 // Send a compositionend event when function deletes the existing com
position node, i.e. | 334 // Send a compositionupdate event when this function updates the existing
composition |
271 // !hasComposition() && test.isEmpty(). | 335 // node, i.e. hasComposition() && !text.isEmpty(). |
272 CompositionEvent* event = nullptr; | 336 // 3. Canceling the ongoing composition. |
273 if (!hasComposition()) { | 337 // Send a compositionend event when function deletes the existing composi
tion node, i.e. |
274 // We should send a compositionstart event only when the given text
is not empty because this | 338 // !hasComposition() && test.isEmpty(). |
275 // function doesn't create a composition node when the text is empty
. | 339 if (text.isEmpty()) { |
276 if (!text.isEmpty()) { | 340 if (hasComposition()) { |
277 target->dispatchEvent(CompositionEvent::create(EventTypeNames::c
ompositionstart, frame().domWindow(), frame().selectedText())); | 341 confirmComposition(emptyString()); |
278 event = CompositionEvent::create(EventTypeNames::compositionupda
te, frame().domWindow(), text); | 342 return; |
279 } | |
280 } else { | |
281 if (!text.isEmpty()) | |
282 event = CompositionEvent::create(EventTypeNames::compositionupda
te, frame().domWindow(), text); | |
283 else | |
284 event = CompositionEvent::create(EventTypeNames::compositionend,
frame().domWindow(), text); | |
285 } | 343 } |
286 if (event) { | 344 // It's weird to call |setComposition()| with empty text outside composi
tion, however some IME |
287 // TODO(chongz): Support canceling IME composition. | 345 // (e.g. Japanese IBus-Anthy) did this, so we simply delete selection wi
thout sending extra events. |
288 // TODO(chongz): Should fire InsertText or DeleteComposedCharacter b
ased on action. | 346 TypingCommand::deleteSelection(*frame().document(), TypingCommand::Preve
ntSpellChecking); |
289 if (event->type() == EventTypeNames::compositionupdate) | 347 return; |
290 dispatchBeforeInputFromComposition(target, InputEvent::InputType
::InsertText, text); | |
291 target->dispatchEvent(event); | |
292 } | |
293 } | 348 } |
294 | 349 |
295 // If text is empty, then delete the old composition here. If text is non-em
pty, InsertTextCommand::input | 350 // We should send a 'compositionstart' event only when the given text is not
empty because this |
296 // will delete the old composition with an optimized replace operation. | 351 // function doesn't create a composition node when the text is empty. |
297 if (text.isEmpty()) { | 352 if (!hasComposition()) { |
298 DCHECK(frame().document()); | 353 target->dispatchEvent(CompositionEvent::create(EventTypeNames::compositi
onstart, frame().domWindow(), frame().selectedText())); |
299 TypingCommand::deleteSelection(*frame().document(), TypingCommand::Preve
ntSpellChecking); | 354 if (!frame().document()) |
| 355 return; |
300 } | 356 } |
301 | 357 |
| 358 DCHECK(!text.isEmpty()); |
| 359 |
302 clear(); | 360 clear(); |
303 | 361 |
304 if (text.isEmpty()) | 362 insertTextDuringCompositionWithEvents(frame(), text, TypingCommand::SelectIn
sertedText | TypingCommand::PreventSpellChecking, TypingCommand::TextComposition
Update); |
| 363 // Event handlers might destroy document. |
| 364 if (!frame().document()) |
305 return; | 365 return; |
306 DCHECK(frame().document()); | |
307 TypingCommand::insertText(*frame().document(), text, TypingCommand::SelectIn
sertedText | TypingCommand::PreventSpellChecking, TypingCommand::TextComposition
Update); | |
308 | 366 |
309 // Find out what node has the composition now. | 367 // Find out what node has the composition now. |
310 Position base = mostForwardCaretPosition(frame().selection().base()); | 368 Position base = mostForwardCaretPosition(frame().selection().base()); |
311 Node* baseNode = base.anchorNode(); | 369 Node* baseNode = base.anchorNode(); |
312 if (!baseNode || !baseNode->isTextNode()) | 370 if (!baseNode || !baseNode->isTextNode()) |
313 return; | 371 return; |
314 | 372 |
315 Position extent = frame().selection().extent(); | 373 Position extent = frame().selection().extent(); |
316 Node* extentNode = extent.anchorNode(); | 374 Node* extentNode = extent.anchorNode(); |
317 if (baseNode != extentNode) | 375 if (baseNode != extentNode) |
(...skipping 171 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
489 TypingCommand::deleteSelection(*frame().document()); | 547 TypingCommand::deleteSelection(*frame().document()); |
490 } | 548 } |
491 | 549 |
492 DEFINE_TRACE(InputMethodController) | 550 DEFINE_TRACE(InputMethodController) |
493 { | 551 { |
494 visitor->trace(m_frame); | 552 visitor->trace(m_frame); |
495 visitor->trace(m_compositionRange); | 553 visitor->trace(m_compositionRange); |
496 } | 554 } |
497 | 555 |
498 } // namespace blink | 556 } // namespace blink |
OLD | NEW |