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

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: Add DCHECK for empty text, fix ImeTest.java 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 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
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
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
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
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