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

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

Issue 2530843003: Introduce InsertIncrementalTextCommand to respect existing style for composition (Closed)
Patch Set: Created 4 years 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 58 matching lines...) Expand 10 before | Expand all | Expand 10 after
69 } 69 }
70 70
71 // Used to insert/replace text during composition update and confirm 71 // Used to insert/replace text during composition update and confirm
72 // composition. 72 // composition.
73 // Procedure: 73 // Procedure:
74 // 1. Fire 'beforeinput' event for (TODO(chongz): deleted composed text) and 74 // 1. Fire 'beforeinput' event for (TODO(chongz): deleted composed text) and
75 // inserted text 75 // inserted text
76 // 2. Fire 'compositionupdate' event 76 // 2. Fire 'compositionupdate' event
77 // 3. Fire TextEvent and modify DOM 77 // 3. Fire TextEvent and modify DOM
78 // TODO(chongz): 4. Fire 'input' event 78 // TODO(chongz): 4. Fire 'input' event
79 void insertTextDuringCompositionWithEvents( 79 void insertIncrementtalTextDuringCompositionWithEvents(
80 LocalFrame& frame, 80 LocalFrame& frame,
81 const String& text, 81 const String& oldText,
82 const String& newText,
82 TypingCommand::Options options, 83 TypingCommand::Options options,
83 TypingCommand::TextCompositionType compositionType) { 84 TypingCommand::TextCompositionType compositionType) {
84 DCHECK(compositionType == 85 DCHECK(compositionType ==
85 TypingCommand::TextCompositionType::TextCompositionUpdate || 86 TypingCommand::TextCompositionType::TextCompositionUpdate ||
86 compositionType == 87 compositionType ==
87 TypingCommand::TextCompositionType::TextCompositionConfirm || 88 TypingCommand::TextCompositionType::TextCompositionConfirm ||
88 compositionType == 89 compositionType ==
89 TypingCommand::TextCompositionType::TextCompositionCancel) 90 TypingCommand::TextCompositionType::TextCompositionCancel)
90 << "compositionType should be TextCompositionUpdate or " 91 << "compositionType should be TextCompositionUpdate or "
91 "TextCompositionConfirm or TextCompositionCancel, but got " 92 "TextCompositionConfirm or TextCompositionCancel, but got "
92 << static_cast<int>(compositionType); 93 << static_cast<int>(compositionType);
93 if (!frame.document()) 94 if (!frame.document())
94 return; 95 return;
95 96
96 Element* target = frame.document()->focusedElement(); 97 Element* target = frame.document()->focusedElement();
97 if (!target) 98 if (!target)
98 return; 99 return;
99 100
100 // TODO(chongz): Fire 'beforeinput' for the composed text being 101 // TODO(chongz): Fire 'beforeinput' for the composed text being
101 // replaced/deleted. 102 // replaced/deleted.
102 103
103 // Only the last confirmed text is cancelable. 104 // Only the last confirmed text is cancelable.
104 InputEvent::EventCancelable beforeInputCancelable = 105 InputEvent::EventCancelable beforeInputCancelable =
105 (compositionType == 106 (compositionType ==
106 TypingCommand::TextCompositionType::TextCompositionUpdate) 107 TypingCommand::TextCompositionType::TextCompositionUpdate)
107 ? InputEvent::EventCancelable::NotCancelable 108 ? InputEvent::EventCancelable::NotCancelable
108 : InputEvent::EventCancelable::IsCancelable; 109 : InputEvent::EventCancelable::IsCancelable;
109 DispatchEventResult result = dispatchBeforeInputFromComposition( 110 DispatchEventResult result = dispatchBeforeInputFromComposition(
110 target, InputEvent::InputType::InsertText, text, beforeInputCancelable); 111 target, InputEvent::InputType::InsertText, newText,
112 beforeInputCancelable);
111 113
112 if (beforeInputCancelable == InputEvent::EventCancelable::IsCancelable && 114 if (beforeInputCancelable == InputEvent::EventCancelable::IsCancelable &&
113 result != DispatchEventResult::NotCanceled) 115 result != DispatchEventResult::NotCanceled)
114 return; 116 return;
115 117
116 // 'beforeinput' event handler may destroy document. 118 // 'beforeinput' event handler may destroy document.
117 if (!frame.document()) 119 if (!frame.document())
118 return; 120 return;
119 121
120 dispatchCompositionUpdateEvent(frame, text); 122 dispatchCompositionUpdateEvent(frame, newText);
121 // 'compositionupdate' event handler may destroy document. 123 // 'compositionupdate' event handler may destroy document.
122 if (!frame.document()) 124 if (!frame.document())
123 return; 125 return;
124 126
125 switch (compositionType) { 127 switch (compositionType) {
126 case TypingCommand::TextCompositionType::TextCompositionUpdate: 128 case TypingCommand::TextCompositionType::TextCompositionUpdate:
127 TypingCommand::insertText(*frame.document(), text, options, 129 TypingCommand::insertIncrementalText(*frame.document(), oldText, newText,
128 compositionType); 130 options, compositionType);
129 break; 131 break;
130 case TypingCommand::TextCompositionType::TextCompositionConfirm: 132 case TypingCommand::TextCompositionType::TextCompositionConfirm:
133 // When there is previous composition, and call commitText()
134 // (or setComposition()) with empty text, |newText| will be empty. In that
135 // case, we should do nothing to avoid firing additional events.
136 if (newText.length()) {
137 TypingCommand::insertIncrementalText(*frame.document(), oldText,
138 newText, options, compositionType);
139 }
140 break;
131 case TypingCommand::TextCompositionType::TextCompositionCancel: 141 case TypingCommand::TextCompositionType::TextCompositionCancel:
132 // TODO(chongz): Use TypingCommand::insertText after TextEvent was 142 // TODO(chongz): Use TypingCommand::insertText after TextEvent was
133 // removed. (Removed from spec since 2012) 143 // removed. (Removed from spec since 2012)
134 // See TextEvent.idl. 144 // See TextEvent.idl.
135 frame.eventHandler().handleTextInputEvent(text, 0, 145 frame.eventHandler().handleTextInputEvent(newText, 0,
136 TextEventInputComposition); 146 TextEventInputComposition);
137 break; 147 break;
138 default: 148 default:
139 NOTREACHED(); 149 NOTREACHED();
140 } 150 }
141 // TODO(chongz): Fire 'input' event. 151 // TODO(chongz): Fire 'input' event.
142 } 152 }
143 153
144 AtomicString getInputModeAttribute(Element* element) { 154 AtomicString getInputModeAttribute(Element* element) {
145 if (!element) 155 if (!element)
(...skipping 18 matching lines...) Expand all
164 return element->fastGetAttribute(HTMLNames::inputmodeAttr).lower(); 174 return element->fastGetAttribute(HTMLNames::inputmodeAttr).lower();
165 } 175 }
166 176
167 } // anonymous namespace 177 } // anonymous namespace
168 178
169 InputMethodController* InputMethodController::create(LocalFrame& frame) { 179 InputMethodController* InputMethodController::create(LocalFrame& frame) {
170 return new InputMethodController(frame); 180 return new InputMethodController(frame);
171 } 181 }
172 182
173 InputMethodController::InputMethodController(LocalFrame& frame) 183 InputMethodController::InputMethodController(LocalFrame& frame)
174 : m_frame(&frame), m_isDirty(false), m_hasComposition(false) {} 184 : m_frame(&frame), m_hasComposition(false) {}
175 185
176 InputMethodController::~InputMethodController() = default; 186 InputMethodController::~InputMethodController() = default;
177 187
178 bool InputMethodController::isAvailable() const { 188 bool InputMethodController::isAvailable() const {
179 return frame().document(); 189 return frame().document();
180 } 190 }
181 191
182 Document& InputMethodController::document() const { 192 Document& InputMethodController::document() const {
183 DCHECK(isAvailable()); 193 DCHECK(isAvailable());
184 return *frame().document(); 194 return *frame().document();
185 } 195 }
186 196
187 bool InputMethodController::hasComposition() const { 197 bool InputMethodController::hasComposition() const {
188 return m_hasComposition; 198 return m_hasComposition;
189 } 199 }
190 200
191 inline Editor& InputMethodController::editor() const { 201 inline Editor& InputMethodController::editor() const {
192 return frame().editor(); 202 return frame().editor();
193 } 203 }
194 204
195 void InputMethodController::clear() { 205 void InputMethodController::clear() {
196 m_hasComposition = false; 206 m_hasComposition = false;
197 if (m_compositionRange) { 207 if (m_compositionRange) {
198 m_compositionRange->setStart(&document(), 0); 208 m_compositionRange->setStart(&document(), 0);
199 m_compositionRange->collapse(true); 209 m_compositionRange->collapse(true);
200 } 210 }
201 document().markers().removeMarkers(DocumentMarker::Composition); 211 document().markers().removeMarkers(DocumentMarker::Composition);
202 m_isDirty = false;
203 } 212 }
204 213
205 void InputMethodController::contextDestroyed() { 214 void InputMethodController::contextDestroyed() {
206 clear(); 215 clear();
207 m_compositionRange = nullptr; 216 m_compositionRange = nullptr;
208 } 217 }
209 218
210 void InputMethodController::documentAttached(Document* document) { 219 void InputMethodController::documentAttached(Document* document) {
211 DCHECK(document); 220 DCHECK(document);
212 setContext(document); 221 setContext(document);
(...skipping 49 matching lines...) Expand 10 before | Expand all | Expand 10 after
262 // duplicate selection change event. 271 // duplicate selection change event.
263 if (!text.length() && !relativeCaretPosition) 272 if (!text.length() && !relativeCaretPosition)
264 return false; 273 return false;
265 return insertTextAndMoveCaret(text, relativeCaretPosition); 274 return insertTextAndMoveCaret(text, relativeCaretPosition);
266 } 275 }
267 276
268 bool InputMethodController::replaceComposition(const String& text) { 277 bool InputMethodController::replaceComposition(const String& text) {
269 if (!hasComposition()) 278 if (!hasComposition())
270 return false; 279 return false;
271 280
272 // If the composition was set from existing text and didn't change, then
273 // there's nothing to do here (and we should avoid doing anything as that
274 // may clobber multi-node styled text).
275 if (!m_isDirty && composingText() == text) {
276 clear();
277 return true;
278 }
279
280 // Select the text that will be deleted or replaced. 281 // Select the text that will be deleted or replaced.
281 selectComposition(); 282 selectComposition();
282 283
283 if (frame().selection().isNone()) 284 if (frame().selection().isNone())
284 return false; 285 return false;
285 286
286 if (!isAvailable()) 287 if (!isAvailable())
287 return false; 288 return false;
288 289
289 // If text is empty, then delete the old composition here. If text is 290 // If text is empty, then delete the old composition here. If text is
290 // non-empty, InsertTextCommand::input will delete the old composition with 291 // non-empty, InsertTextCommand::input will delete the old composition with
291 // an optimized replace operation. 292 // an optimized replace operation.
292 if (text.isEmpty()) 293 if (text.isEmpty()) {
293 TypingCommand::deleteSelection(document(), 0); 294 TypingCommand::deleteSelection(document(), 0);
294 295
296 // TODO(xiaochengh): The use of updateStyleAndLayoutIgnorePendingStylesheets
297 // needs to be audited. see http://crbug.com/590369 for more details.
298 document().updateStyleAndLayoutIgnorePendingStylesheets();
299 }
300
301 const String& composing = composingText();
295 clear(); 302 clear();
296 303
297 insertTextDuringCompositionWithEvents( 304 insertIncrementtalTextDuringCompositionWithEvents(
298 frame(), text, 0, 305 frame(), composing, text, 0,
299 TypingCommand::TextCompositionType::TextCompositionConfirm); 306 TypingCommand::TextCompositionType::TextCompositionConfirm);
300 // Event handler might destroy document. 307 // Event handler might destroy document.
301 if (!isAvailable()) 308 if (!isAvailable())
302 return false; 309 return false;
303 310
304 return true; 311 return true;
305 } 312 }
306 313
307 // relativeCaretPosition is relative to the end of the text. 314 // relativeCaretPosition is relative to the end of the text.
308 static int computeAbsoluteCaretPosition(size_t textStart, 315 static int computeAbsoluteCaretPosition(size_t textStart,
(...skipping 63 matching lines...) Expand 10 before | Expand all | Expand 10 after
372 return; 379 return;
373 380
374 clear(); 381 clear();
375 382
376 // TODO(chongz): Figure out which InputType should we use here. 383 // TODO(chongz): Figure out which InputType should we use here.
377 dispatchBeforeInputFromComposition( 384 dispatchBeforeInputFromComposition(
378 document().focusedElement(), 385 document().focusedElement(),
379 InputEvent::InputType::DeleteComposedCharacterBackward, nullAtom, 386 InputEvent::InputType::DeleteComposedCharacterBackward, nullAtom,
380 InputEvent::EventCancelable::NotCancelable); 387 InputEvent::EventCancelable::NotCancelable);
381 dispatchCompositionUpdateEvent(frame(), emptyString()); 388 dispatchCompositionUpdateEvent(frame(), emptyString());
382 insertTextDuringCompositionWithEvents( 389 insertIncrementtalTextDuringCompositionWithEvents(
383 frame(), emptyString(), 0, 390 frame(), emptyString(), emptyString(), 0,
384 TypingCommand::TextCompositionType::TextCompositionCancel); 391 TypingCommand::TextCompositionType::TextCompositionCancel);
385 // Event handler might destroy document. 392 // Event handler might destroy document.
386 if (!isAvailable()) 393 if (!isAvailable())
387 return; 394 return;
388 395
389 // An open typing command that disagrees about current selection would cause 396 // An open typing command that disagrees about current selection would cause
390 // issues with typing later on. 397 // issues with typing later on.
391 TypingCommand::closeTyping(m_frame); 398 TypingCommand::closeTyping(m_frame);
392 399
393 // No DOM update after 'compositionend'. 400 // No DOM update after 'compositionend'.
394 dispatchCompositionEndEvent(frame(), emptyString()); 401 dispatchCompositionEndEvent(frame(), emptyString());
395 } 402 }
396 403
397 void InputMethodController::cancelCompositionIfSelectionIsInvalid() { 404 void InputMethodController::cancelCompositionIfSelectionIsInvalid() {
398 if (!hasComposition() || editor().preventRevealSelection()) 405 if (!hasComposition() || editor().preventRevealSelection())
399 return; 406 return;
400 407
401 // Check if selection start and selection end are valid. 408 // Check if selection start and selection end are valid.
402 FrameSelection& selection = frame().selection(); 409 FrameSelection& selection = frame().selection();
403 if (!selection.isNone() && !m_compositionRange->collapsed()) { 410 if (!selection.isNone() && !m_compositionRange->collapsed()) {
404 if (selection.start().compareTo(m_compositionRange->startPosition()) >= 0 && 411 if (selection.start().compareTo(m_compositionRange->startPosition()) >= 0 &&
405 selection.end().compareTo(m_compositionRange->endPosition()) <= 0) 412 selection.end().compareTo(m_compositionRange->endPosition()) <= 0)
406 return; 413 return;
407 } 414 }
408 415
409 cancelComposition(); 416 cancelComposition();
410 frame().chromeClient().didCancelCompositionOnSelectionChange(); 417 frame().chromeClient().didCancelCompositionOnSelectionChange();
411 } 418 }
412 419
413 static size_t computeCommonPrefixLength(const String& str1,
414 const String& str2) {
415 const size_t maxCommonPrefixLength = std::min(str1.length(), str2.length());
416 for (size_t index = 0; index < maxCommonPrefixLength; ++index) {
417 if (str1[index] != str2[index])
418 return index;
419 }
420 return maxCommonPrefixLength;
421 }
422
423 static size_t computeCommonSuffixLength(const String& str1,
424 const String& str2) {
425 const size_t length1 = str1.length();
426 const size_t length2 = str2.length();
427 const size_t maxCommonSuffixLength = std::min(length1, length2);
428 for (size_t index = 0; index < maxCommonSuffixLength; ++index) {
429 if (str1[length1 - index - 1] != str2[length2 - index - 1])
430 return index;
431 }
432 return maxCommonSuffixLength;
433 }
434
435 // If current position is at grapheme boundary, return 0; otherwise, return the 420 // If current position is at grapheme boundary, return 0; otherwise, return the
436 // distance to its nearest left grapheme boundary. 421 // distance to its nearest left grapheme boundary.
437 static size_t computeDistanceToLeftGraphemeBoundary(const Position& position) { 422 static size_t computeDistanceToLeftGraphemeBoundary(const Position& position) {
438 const Position& adjustedPosition = previousPositionOf( 423 const Position& adjustedPosition = previousPositionOf(
439 nextPositionOf(position, PositionMoveType::GraphemeCluster), 424 nextPositionOf(position, PositionMoveType::GraphemeCluster),
440 PositionMoveType::GraphemeCluster); 425 PositionMoveType::GraphemeCluster);
441 DCHECK_EQ(position.anchorNode(), adjustedPosition.anchorNode()); 426 DCHECK_EQ(position.anchorNode(), adjustedPosition.anchorNode());
442 DCHECK_GE(position.computeOffsetInContainerNode(), 427 DCHECK_GE(position.computeOffsetInContainerNode(),
443 adjustedPosition.computeOffsetInContainerNode()); 428 adjustedPosition.computeOffsetInContainerNode());
444 return static_cast<size_t>(position.computeOffsetInContainerNode() - 429 return static_cast<size_t>(position.computeOffsetInContainerNode() -
445 adjustedPosition.computeOffsetInContainerNode()); 430 adjustedPosition.computeOffsetInContainerNode());
446 } 431 }
447 432
448 static size_t computeCommonGraphemeClusterPrefixLengthForSetComposition(
449 const String& oldText,
450 const String& newText,
451 const Element* rootEditableElement) {
452 const size_t commonPrefixLength = computeCommonPrefixLength(oldText, newText);
453
454 // For grapheme cluster, we should adjust it for grapheme boundary.
455 const EphemeralRange& range =
456 PlainTextRange(0, commonPrefixLength).createRange(*rootEditableElement);
457 if (range.isNull())
458 return 0;
459 const Position& position = range.endPosition();
460 const size_t diff = computeDistanceToLeftGraphemeBoundary(position);
461 DCHECK_GE(commonPrefixLength, diff);
462 return commonPrefixLength - diff;
463 }
464
465 // If current position is at grapheme boundary, return 0; otherwise, return the 433 // If current position is at grapheme boundary, return 0; otherwise, return the
466 // distance to its nearest right grapheme boundary. 434 // distance to its nearest right grapheme boundary.
467 static size_t computeDistanceToRightGraphemeBoundary(const Position& position) { 435 static size_t computeDistanceToRightGraphemeBoundary(const Position& position) {
468 const Position& adjustedPosition = nextPositionOf( 436 const Position& adjustedPosition = nextPositionOf(
469 previousPositionOf(position, PositionMoveType::GraphemeCluster), 437 previousPositionOf(position, PositionMoveType::GraphemeCluster),
470 PositionMoveType::GraphemeCluster); 438 PositionMoveType::GraphemeCluster);
471 DCHECK_EQ(position.anchorNode(), adjustedPosition.anchorNode()); 439 DCHECK_EQ(position.anchorNode(), adjustedPosition.anchorNode());
472 DCHECK_GE(adjustedPosition.computeOffsetInContainerNode(), 440 DCHECK_GE(adjustedPosition.computeOffsetInContainerNode(),
473 position.computeOffsetInContainerNode()); 441 position.computeOffsetInContainerNode());
474 return static_cast<size_t>(adjustedPosition.computeOffsetInContainerNode() - 442 return static_cast<size_t>(adjustedPosition.computeOffsetInContainerNode() -
475 position.computeOffsetInContainerNode()); 443 position.computeOffsetInContainerNode());
476 } 444 }
477 445
478 static size_t computeCommonGraphemeClusterSuffixLengthForSetComposition(
479 const String& oldText,
480 const String& newText,
481 const Element* rootEditableElement) {
482 const size_t commonSuffixLength = computeCommonSuffixLength(oldText, newText);
483
484 // For grapheme cluster, we should adjust it for grapheme boundary.
485 const EphemeralRange& range =
486 PlainTextRange(0, oldText.length() - commonSuffixLength)
487 .createRange(*rootEditableElement);
488 if (range.isNull())
489 return 0;
490 const Position& position = range.endPosition();
491 const size_t diff = computeDistanceToRightGraphemeBoundary(position);
492 DCHECK_GE(commonSuffixLength, diff);
493 return commonSuffixLength - diff;
494 }
495
496 void InputMethodController::setCompositionWithIncrementalText(
497 const String& text,
498 const Vector<CompositionUnderline>& underlines,
499 int selectionStart,
500 int selectionEnd) {
501 Element* editable = frame().selection().rootEditableElement();
502 if (!editable)
503 return;
504
505 DCHECK_LE(selectionStart, selectionEnd);
506 String composing = composingText();
507 const size_t commonPrefixLength =
508 computeCommonGraphemeClusterPrefixLengthForSetComposition(composing, text,
509 editable);
510
511 // We should ignore common prefix when finding common suffix.
512 const size_t commonSuffixLength =
513 computeCommonGraphemeClusterSuffixLengthForSetComposition(
514 composing.right(composing.length() - commonPrefixLength),
515 text.right(text.length() - commonPrefixLength), editable);
516
517 const bool inserting =
518 text.length() > commonPrefixLength + commonSuffixLength;
519 const bool deleting =
520 composing.length() > commonPrefixLength + commonSuffixLength;
521
522 if (inserting || deleting) {
523 // Select the text to be deleted.
524 const size_t compositionStart =
525 PlainTextRange::create(*editable, compositionEphemeralRange()).start();
526 const size_t deletionStart = compositionStart + commonPrefixLength;
527 const size_t deletionEnd =
528 compositionStart + composing.length() - commonSuffixLength;
529 const EphemeralRange& deletionRange =
530 PlainTextRange(deletionStart, deletionEnd).createRange(*editable);
531 VisibleSelection selection;
532 selection.setWithoutValidation(deletionRange.startPosition(),
533 deletionRange.endPosition());
534 Document& currentDocument = document();
535 frame().selection().setSelection(selection, 0);
536 clear();
537
538 // FrameSeleciton::setSelection() can change document associate to |frame|.
539 if (!isAvailable() || currentDocument != document())
540 return;
541 if (!currentDocument.focusedElement())
542 return;
543
544 // Insert the incremental text.
545 const size_t insertionLength =
546 text.length() - commonPrefixLength - commonSuffixLength;
547 const String& insertingText =
548 text.substring(commonPrefixLength, insertionLength);
549 insertTextDuringCompositionWithEvents(frame(), insertingText,
550 TypingCommand::PreventSpellChecking,
551 TypingCommand::TextCompositionUpdate);
552
553 // Event handlers might destroy document.
554 if (!isAvailable() || currentDocument != document())
555 return;
556
557 // TODO(yosin): The use of updateStyleAndLayoutIgnorePendingStylesheets
558 // needs to be audited. see http://crbug.com/590369 for more details.
559 document().updateStyleAndLayoutIgnorePendingStylesheets();
560
561 // Now recreate the composition starting at its original start, and
562 // apply the specified final selection offsets.
563 setCompositionFromExistingText(underlines, compositionStart,
564 compositionStart + text.length());
565 }
566
567 selectComposition();
568
569 // TODO(xiaochengh): The use of updateStyleAndLayoutIgnorePendingStylesheets
570 // needs to be audited. see http://crbug.com/590369 for more details.
571 document().updateStyleAndLayoutIgnorePendingStylesheets();
572
573 const PlainTextRange& selectedRange = createSelectionRangeForSetComposition(
574 selectionStart, selectionEnd, text.length());
575 // We shouldn't close typing in the middle of setComposition.
576 setEditableSelectionOffsets(selectedRange, NotUserTriggered);
577 m_isDirty = true;
578 }
579
580 void InputMethodController::setComposition( 446 void InputMethodController::setComposition(
581 const String& text, 447 const String& text,
582 const Vector<CompositionUnderline>& underlines, 448 const Vector<CompositionUnderline>& underlines,
583 int selectionStart, 449 int selectionStart,
584 int selectionEnd) { 450 int selectionEnd) {
585 Editor::RevealSelectionScope revealSelectionScope(&editor()); 451 Editor::RevealSelectionScope revealSelectionScope(&editor());
586 452
587 // Updates styles before setting selection for composition to prevent 453 // Updates styles before setting selection for composition to prevent
588 // inserting the previous composition text into text nodes oddly. 454 // inserting the previous composition text into text nodes oddly.
589 // See https://bugs.webkit.org/show_bug.cgi?id=46868 455 // See https://bugs.webkit.org/show_bug.cgi?id=46868
590 document().updateStyleAndLayoutTree(); 456 document().updateStyleAndLayoutTree();
591 457
592 // When the IME only wants to change a few characters at the end of the
593 // composition, only touch those characters in order to preserve rich text
594 // substructure.
595 if (hasComposition() && text.length()) {
596 return setCompositionWithIncrementalText(text, underlines, selectionStart,
597 selectionEnd);
598 }
599
600 selectComposition(); 458 selectComposition();
601 459
602 if (frame().selection().isNone()) 460 if (frame().selection().isNone())
603 return; 461 return;
604 462
605 Element* target = document().focusedElement(); 463 Element* target = document().focusedElement();
606 if (!target) 464 if (!target)
607 return; 465 return;
608 466
609 // TODO(xiaochengh): The use of updateStyleAndLayoutIgnorePendingStylesheets 467 // TODO(xiaochengh): The use of updateStyleAndLayoutIgnorePendingStylesheets
(...skipping 46 matching lines...) Expand 10 before | Expand all | Expand 10 after
656 if (!hasComposition()) { 514 if (!hasComposition()) {
657 target->dispatchEvent( 515 target->dispatchEvent(
658 CompositionEvent::create(EventTypeNames::compositionstart, 516 CompositionEvent::create(EventTypeNames::compositionstart,
659 frame().domWindow(), frame().selectedText())); 517 frame().domWindow(), frame().selectedText()));
660 if (!isAvailable()) 518 if (!isAvailable())
661 return; 519 return;
662 } 520 }
663 521
664 DCHECK(!text.isEmpty()); 522 DCHECK(!text.isEmpty());
665 523
524 const String& composing = composingText();
666 clear(); 525 clear();
667 526
668 insertTextDuringCompositionWithEvents( 527 insertIncrementtalTextDuringCompositionWithEvents(
669 frame(), text, 528 frame(), composing, text,
670 TypingCommand::SelectInsertedText | TypingCommand::PreventSpellChecking, 529 TypingCommand::SelectInsertedText | TypingCommand::PreventSpellChecking,
671 TypingCommand::TextCompositionUpdate); 530 TypingCommand::TextCompositionUpdate);
672 // Event handlers might destroy document. 531 // Event handlers might destroy document.
673 if (!isAvailable()) 532 if (!isAvailable())
674 return; 533 return;
675 534
676 // TODO(yosin): The use of updateStyleAndLayoutIgnorePendingStylesheets 535 // TODO(yosin): The use of updateStyleAndLayoutIgnorePendingStylesheets
677 // needs to be audited. see http://crbug.com/590369 for more details. 536 // needs to be audited. see http://crbug.com/590369 for more details.
678 document().updateStyleAndLayoutIgnorePendingStylesheets(); 537 document().updateStyleAndLayoutIgnorePendingStylesheets();
679 538
680 // Find out what node has the composition now. 539 // Find out what node has the composition now.
681 Position base = mostForwardCaretPosition(frame().selection().base()); 540 Position base = mostForwardCaretPosition(frame().selection().base());
682 Node* baseNode = base.anchorNode(); 541 Node* baseNode = base.anchorNode();
683 if (!baseNode || !baseNode->isTextNode()) 542 if (!baseNode || !baseNode->isTextNode())
684 return; 543 return;
685 544
686 Position extent = frame().selection().extent(); 545 Position extent = frame().selection().extent();
687 Node* extentNode = extent.anchorNode(); 546 Node* extentNode = extent.anchorNode();
yabinh 2016/11/25 04:49:29 baseNode and extentNode could be different anchor
688 if (baseNode != extentNode)
689 return;
690 547
691 unsigned extentOffset = extent.computeOffsetInContainerNode(); 548 unsigned extentOffset = extent.computeOffsetInContainerNode();
692 unsigned baseOffset = base.computeOffsetInContainerNode(); 549 unsigned baseOffset = base.computeOffsetInContainerNode();
yabinh 2016/11/25 04:49:29 Note that the offset is relative to its anchor nod
693 if (baseOffset + text.length() != extentOffset)
694 return;
695 550
696 m_isDirty = true;
697 m_hasComposition = true; 551 m_hasComposition = true;
698 if (!m_compositionRange) 552 if (!m_compositionRange)
699 m_compositionRange = Range::create(document()); 553 m_compositionRange = Range::create(document());
700 m_compositionRange->setStart(baseNode, baseOffset); 554 m_compositionRange->setStart(baseNode, baseOffset);
701 m_compositionRange->setEnd(baseNode, extentOffset); 555 m_compositionRange->setEnd(extentNode, extentOffset);
702 556
703 if (baseNode->layoutObject()) 557 if (baseNode->layoutObject())
704 baseNode->layoutObject()->setShouldDoFullPaintInvalidation(); 558 baseNode->layoutObject()->setShouldDoFullPaintInvalidation();
705 559
706 // TODO(xiaochengh): The use of updateStyleAndLayoutIgnorePendingStylesheets 560 // TODO(xiaochengh): The use of updateStyleAndLayoutIgnorePendingStylesheets
707 // needs to be audited. see http://crbug.com/590369 for more details. 561 // needs to be audited. see http://crbug.com/590369 for more details.
708 document().updateStyleAndLayoutIgnorePendingStylesheets(); 562 document().updateStyleAndLayoutIgnorePendingStylesheets();
709 563
710 // We shouldn't close typing in the middle of setComposition. 564 // We shouldn't close typing in the middle of setComposition.
711 setEditableSelectionOffsets(selectedRange, NotUserTriggered); 565 setEditableSelectionOffsets(selectedRange, NotUserTriggered);
(...skipping 481 matching lines...) Expand 10 before | Expand all | Expand 10 after
1193 return WebTextInputTypeNone; 1047 return WebTextInputTypeNone;
1194 } 1048 }
1195 1049
1196 DEFINE_TRACE(InputMethodController) { 1050 DEFINE_TRACE(InputMethodController) {
1197 visitor->trace(m_frame); 1051 visitor->trace(m_frame);
1198 visitor->trace(m_compositionRange); 1052 visitor->trace(m_compositionRange);
1199 SynchronousMutationObserver::trace(visitor); 1053 SynchronousMutationObserver::trace(visitor);
1200 } 1054 }
1201 1055
1202 } // namespace blink 1056 } // namespace blink
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698