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 CHECK(compositionType == TypingCommand::TextCompositionType::TextComposition Update || compositionType == TypingCommand::TextCompositionType::TextComposition Confirm); | |
yosin_UTC9
2016/05/20 07:58:12
Please add "<< compositionType" for ease of debugg
chongz
2016/05/20 17:44:48
Done. Also changed to DCHECK().
| |
78 if (!frame.document()) | |
79 return; | |
80 | |
81 Element* target = frame.document()->focusedElement(); | |
82 if (!target) | |
83 return; | |
84 | |
85 // TODO(chongz): Fire 'beforeinput' for the composed text being replaced/del eted. | |
86 | |
87 // Only the last confirmed text is cancelable. | |
88 InputEvent::EventCancelable beforeInputCancelable = (compositionType == Typi ngCommand::TextCompositionType::TextCompositionUpdate) ? InputEvent::EventCancel able::NotCancelable : InputEvent::EventCancelable::IsCancelable; | |
89 DispatchEventResult result = dispatchBeforeInputFromComposition(target, Inpu tEvent::InputType::InsertText, text, beforeInputCancelable); | |
90 | |
91 if (beforeInputCancelable == InputEvent::EventCancelable::IsCancelable && re sult != DispatchEventResult::NotCanceled) | |
92 return; | |
93 | |
94 // 'beforeinput' event handler may destroy document. | |
95 if (!frame.document()) | |
96 return; | |
chongz
2016/05/19 21:44:19
When would event handler destroy document...? I te
| |
97 | |
98 dispatchCompositionUpdateEvent(frame, text); | |
99 // 'compositionupdate' event handler may destroy document. | |
100 if (!frame.document()) | |
101 return; | |
102 | |
103 switch (compositionType) { | |
104 case TypingCommand::TextCompositionType::TextCompositionUpdate: | |
105 TypingCommand::insertText(*frame.document(), text, options, compositionT ype); | |
106 break; | |
107 case TypingCommand::TextCompositionType::TextCompositionConfirm: | |
108 // TODO(chongz): Use TypingCommand::insertText after TextEvent was remov ed. (Removed from spec since 2012) | |
109 // See TextEvent.idl. | |
110 frame.eventHandler().handleTextInputEvent(text, 0, TextEventInputComposi tion); | |
111 break; | |
112 default: | |
113 NOTREACHED(); | |
114 } | |
115 // TODO(chongz): Fire 'input' event. | |
116 } | |
117 | |
118 } // anonymous namespace | |
119 | |
47 InputMethodController::SelectionOffsetsScope::SelectionOffsetsScope(InputMethodC ontroller* inputMethodController) | 120 InputMethodController::SelectionOffsetsScope::SelectionOffsetsScope(InputMethodC ontroller* inputMethodController) |
48 : m_inputMethodController(inputMethodController) | 121 : m_inputMethodController(inputMethodController) |
49 , m_offsets(inputMethodController->getSelectionOffsets()) | 122 , m_offsets(inputMethodController->getSelectionOffsets()) |
50 { | 123 { |
51 } | 124 } |
52 | 125 |
53 InputMethodController::SelectionOffsetsScope::~SelectionOffsetsScope() | 126 InputMethodController::SelectionOffsetsScope::~SelectionOffsetsScope() |
54 { | 127 { |
55 m_inputMethodController->setSelectionOffsets(m_offsets); | 128 m_inputMethodController->setSelectionOffsets(m_offsets); |
56 } | 129 } |
(...skipping 32 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
89 frame().document()->markers().removeMarkers(DocumentMarker::Composition); | 162 frame().document()->markers().removeMarkers(DocumentMarker::Composition); |
90 m_isDirty = false; | 163 m_isDirty = false; |
91 } | 164 } |
92 | 165 |
93 void InputMethodController::documentDetached() | 166 void InputMethodController::documentDetached() |
94 { | 167 { |
95 clear(); | 168 clear(); |
96 m_compositionRange = nullptr; | 169 m_compositionRange = nullptr; |
97 } | 170 } |
98 | 171 |
99 bool InputMethodController::insertTextForConfirmedComposition(const String& text ) | |
100 { | |
101 return frame().eventHandler().handleTextInputEvent(text, 0, TextEventInputCo mposition); | |
102 } | |
103 | |
104 void InputMethodController::selectComposition() const | 172 void InputMethodController::selectComposition() const |
105 { | 173 { |
106 const EphemeralRange range = compositionEphemeralRange(); | 174 const EphemeralRange range = compositionEphemeralRange(); |
107 if (range.isNull()) | 175 if (range.isNull()) |
108 return; | 176 return; |
109 | 177 |
110 // The composition can start inside a composed character sequence, so we hav e to override checks. | 178 // 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> | 179 // See <http://bugs.webkit.org/show_bug.cgi?id=15781> |
112 VisibleSelection selection; | 180 VisibleSelection selection; |
113 selection.setWithoutValidation(range.startPosition(), range.endPosition()); | 181 selection.setWithoutValidation(range.startPosition(), range.endPosition()); |
114 frame().selection().setSelection(selection, 0); | 182 frame().selection().setSelection(selection, 0); |
115 } | 183 } |
116 | 184 |
117 bool InputMethodController::confirmComposition() | 185 bool InputMethodController::confirmComposition() |
118 { | 186 { |
119 return confirmComposition(composingText()); | 187 return confirmComposition(composingText()); |
120 } | 188 } |
121 | 189 |
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) | 190 bool InputMethodController::confirmComposition(const String& text, ConfirmCompos itionBehavior confirmBehavior) |
136 { | 191 { |
137 if (!hasComposition()) | 192 if (!hasComposition()) |
138 return false; | 193 return false; |
139 | 194 |
140 Optional<Editor::RevealSelectionScope> revealSelectionScope; | 195 Optional<Editor::RevealSelectionScope> revealSelectionScope; |
141 if (confirmBehavior == KeepSelection) | 196 if (confirmBehavior == KeepSelection) |
142 revealSelectionScope.emplace(&editor()); | 197 revealSelectionScope.emplace(&editor()); |
143 | 198 |
144 // If the composition was set from existing text and didn't change, then | 199 // 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 | 200 // there's nothing to do here (and we should avoid doing anything as that |
146 // may clobber multi-node styled text). | 201 // may clobber multi-node styled text). |
147 if (!m_isDirty && composingText() == text) { | 202 if (!m_isDirty && composingText() == text) { |
148 clear(); | 203 clear(); |
149 return true; | 204 return true; |
150 } | 205 } |
151 | 206 |
152 // Select the text that will be deleted or replaced. | 207 // Select the text that will be deleted or replaced. |
153 selectComposition(); | 208 selectComposition(); |
154 | 209 |
155 if (frame().selection().isNone()) | 210 if (frame().selection().isNone()) |
156 return false; | 211 return false; |
157 | 212 |
158 dispatchCompositionEndEvent(frame(), text); | |
159 | |
160 if (!frame().document()) | 213 if (!frame().document()) |
161 return false; | 214 return false; |
162 | 215 |
163 // If text is empty, then delete the old composition here. If text is | 216 // If text is empty, then delete the old composition here. If text is |
164 // non-empty, InsertTextCommand::input will delete the old composition with | 217 // non-empty, InsertTextCommand::input will delete the old composition with |
165 // an optimized replace operation. | 218 // an optimized replace operation. |
166 if (text.isEmpty()) | 219 if (text.isEmpty()) |
167 TypingCommand::deleteSelection(*frame().document(), 0); | 220 TypingCommand::deleteSelection(*frame().document(), 0); |
168 | 221 |
169 clear(); | 222 clear(); |
170 | 223 |
171 // TODO(chongz): DOM update should happen before 'compositionend' and along with 'compositionupdate'. | 224 insertTextDuringCompositionWithEvents(frame(), text, 0, TypingCommand::TextC ompositionType::TextCompositionConfirm); |
172 // https://crbug.com/575294 | 225 // Event handler might destroy document. |
173 if (dispatchBeforeInputInsertText(frame().document()->focusedElement(), text ) != DispatchEventResult::NotCanceled) | 226 if (!frame().document()) |
174 return false; | 227 return false; |
175 | 228 |
176 insertTextForConfirmedComposition(text); | 229 // No DOM update after 'compositionend'. |
230 dispatchCompositionEndEvent(frame(), text); | |
177 | 231 |
178 return true; | 232 return true; |
179 } | 233 } |
180 | 234 |
181 bool InputMethodController::confirmCompositionOrInsertText(const String& text, C onfirmCompositionBehavior confirmBehavior) | 235 bool InputMethodController::confirmCompositionOrInsertText(const String& text, C onfirmCompositionBehavior confirmBehavior) |
182 { | 236 { |
183 if (!hasComposition()) { | 237 if (!hasComposition()) { |
184 if (!text.length()) | 238 if (!text.length()) |
185 return false; | 239 return false; |
186 | 240 |
(...skipping 19 matching lines...) Expand all Loading... | |
206 void InputMethodController::cancelComposition() | 260 void InputMethodController::cancelComposition() |
207 { | 261 { |
208 if (!hasComposition()) | 262 if (!hasComposition()) |
209 return; | 263 return; |
210 | 264 |
211 Editor::RevealSelectionScope revealSelectionScope(&editor()); | 265 Editor::RevealSelectionScope revealSelectionScope(&editor()); |
212 | 266 |
213 if (frame().selection().isNone()) | 267 if (frame().selection().isNone()) |
214 return; | 268 return; |
215 | 269 |
216 dispatchCompositionEndEvent(frame(), emptyString()); | |
217 clear(); | 270 clear(); |
218 insertTextForConfirmedComposition(emptyString()); | 271 |
272 // TODO(chongz): Update InputType::DeleteComposedCharacter with latest discu ssion. | |
273 dispatchBeforeInputFromComposition(frame().document()->focusedElement(), Inp utEvent::InputType::DeleteComposedCharacter, emptyString(), InputEvent::EventCan celable::NotCancelable); | |
274 dispatchCompositionUpdateEvent(frame(), emptyString()); | |
275 insertTextDuringCompositionWithEvents(frame(), emptyString(), 0, TypingComma nd::TextCompositionType::TextCompositionConfirm); | |
276 // Event handler might destroy document. | |
277 if (!frame().document()) | |
278 return; | |
219 | 279 |
220 // An open typing command that disagrees about current selection would cause | 280 // An open typing command that disagrees about current selection would cause |
221 // issues with typing later on. | 281 // issues with typing later on. |
222 TypingCommand::closeTyping(m_frame); | 282 TypingCommand::closeTyping(m_frame); |
283 | |
284 // No DOM update after 'compositionend'. | |
285 dispatchCompositionEndEvent(frame(), emptyString()); | |
223 } | 286 } |
224 | 287 |
225 void InputMethodController::cancelCompositionIfSelectionIsInvalid() | 288 void InputMethodController::cancelCompositionIfSelectionIsInvalid() |
226 { | 289 { |
227 if (!hasComposition() || editor().preventRevealSelection()) | 290 if (!hasComposition() || editor().preventRevealSelection()) |
228 return; | 291 return; |
229 | 292 |
230 // Check if selection start and selection end are valid. | 293 // Check if selection start and selection end are valid. |
231 FrameSelection& selection = frame().selection(); | 294 FrameSelection& selection = frame().selection(); |
232 if (!selection.isNone() && !m_compositionRange->collapsed()) { | 295 if (!selection.isNone() && !m_compositionRange->collapsed()) { |
(...skipping 13 matching lines...) Expand all Loading... | |
246 // Updates styles before setting selection for composition to prevent | 309 // Updates styles before setting selection for composition to prevent |
247 // inserting the previous composition text into text nodes oddly. | 310 // inserting the previous composition text into text nodes oddly. |
248 // See https://bugs.webkit.org/show_bug.cgi?id=46868 | 311 // See https://bugs.webkit.org/show_bug.cgi?id=46868 |
249 frame().document()->updateStyleAndLayoutTree(); | 312 frame().document()->updateStyleAndLayoutTree(); |
250 | 313 |
251 selectComposition(); | 314 selectComposition(); |
252 | 315 |
253 if (frame().selection().isNone()) | 316 if (frame().selection().isNone()) |
254 return; | 317 return; |
255 | 318 |
256 if (Element* target = frame().document()->focusedElement()) { | 319 Element* target = frame().document()->focusedElement(); |
257 // Dispatch an appropriate composition event to the focused node. | 320 if (!target) |
258 // We check the composition status and choose an appropriate composition event since this | 321 return; |
259 // function is used for three purposes: | 322 |
260 // 1. Starting a new composition. | 323 // Dispatch an appropriate composition event to the focused node. |
261 // Send a compositionstart and a compositionupdate event when this fu nction creates | 324 // We check the composition status and choose an appropriate composition eve nt since this |
262 // a new composition node, i.e. | 325 // function is used for three purposes: |
263 // !hasComposition() && !text.isEmpty(). | 326 // 1. Starting a new composition. |
264 // Sending a compositionupdate event at this time ensures that at lea st one | 327 // Send a compositionstart and a compositionupdate event when this functi on creates |
265 // compositionupdate event is dispatched. | 328 // a new composition node, i.e. |
266 // 2. Updating the existing composition node. | 329 // !hasComposition() && !text.isEmpty(). |
267 // Send a compositionupdate event when this function updates the exis ting composition | 330 // Sending a compositionupdate event at this time ensures that at least o ne |
268 // node, i.e. hasComposition() && !text.isEmpty(). | 331 // compositionupdate event is dispatched. |
269 // 3. Canceling the ongoing composition. | 332 // 2. Updating the existing composition node. |
270 // Send a compositionend event when function deletes the existing com position node, i.e. | 333 // Send a compositionupdate event when this function updates the existing composition |
271 // !hasComposition() && test.isEmpty(). | 334 // node, i.e. hasComposition() && !text.isEmpty(). |
272 CompositionEvent* event = nullptr; | 335 // 3. Canceling the ongoing composition. |
273 if (!hasComposition()) { | 336 // 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 | 337 // !hasComposition() && test.isEmpty(). |
275 // function doesn't create a composition node when the text is empty . | 338 if (text.isEmpty()) { |
276 if (!text.isEmpty()) { | 339 if (hasComposition()) { |
277 target->dispatchEvent(CompositionEvent::create(EventTypeNames::c ompositionstart, frame().domWindow(), frame().selectedText())); | 340 confirmComposition(emptyString()); |
278 event = CompositionEvent::create(EventTypeNames::compositionupda te, frame().domWindow(), text); | 341 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 } | 342 } |
286 if (event) { | 343 // It's weird to call |setComposition()| with empty text outside composi tion, however some IME |
287 // TODO(chongz): Support canceling IME composition. | 344 // (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. | 345 TypingCommand::deleteSelection(*frame().document(), TypingCommand::Preve ntSpellChecking); |
289 if (event->type() == EventTypeNames::compositionupdate) | 346 return; |
290 dispatchBeforeInputFromComposition(target, InputEvent::InputType ::InsertText, text); | |
291 target->dispatchEvent(event); | |
292 } | |
293 } | 347 } |
294 | 348 |
295 // If text is empty, then delete the old composition here. If text is non-em pty, InsertTextCommand::input | 349 // 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. | 350 // function doesn't create a composition node when the text is empty. |
297 if (text.isEmpty()) { | 351 if (!hasComposition()) { |
298 DCHECK(frame().document()); | 352 target->dispatchEvent(CompositionEvent::create(EventTypeNames::compositi onstart, frame().domWindow(), frame().selectedText())); |
299 TypingCommand::deleteSelection(*frame().document(), TypingCommand::Preve ntSpellChecking); | 353 if (!frame().document()) |
354 return; | |
300 } | 355 } |
301 | 356 |
357 CHECK(!text.isEmpty()); | |
yosin_UTC9
2016/05/20 07:58:12
Do we really need to use |CHECK()|?
chongz
2016/05/20 17:44:48
Probably overkill since I already filtered it out.
yosin_UTC9
2016/05/23 04:57:28
I supposed to use |DCHECK()| instead of |CHECK()|.
| |
358 CHECK(frame().document()); | |
yosin_UTC9
2016/05/20 07:58:12
This CHECK() makes L364 redundant. Which one shoul
chongz
2016/05/20 17:44:48
L364 was trying to handle the case you mentioned b
yosin_UTC9
2016/05/23 04:57:28
ClusterFuzz will find test case. ;-)
| |
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 |