Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(289)

Side by Side Diff: third_party/WebKit/Source/core/editing/InputMethodController.cpp

Issue 1998783002: [IME] Fire 'compositionend' after 'textInput' event and all other DOM updates (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Created 4 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
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
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
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
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
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
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
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698