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

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

Issue 2372493002: Workaround for setComposition styling clobber (Closed)
Patch Set: Add some DCHECK. Created 4 years, 2 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 339 matching lines...) Expand 10 before | Expand all | Expand 10 after
350 if (!selection.isNone() && !m_compositionRange->collapsed()) { 350 if (!selection.isNone() && !m_compositionRange->collapsed()) {
351 if (selection.start().compareTo(m_compositionRange->startPosition()) >= 0 && 351 if (selection.start().compareTo(m_compositionRange->startPosition()) >= 0 &&
352 selection.end().compareTo(m_compositionRange->endPosition()) <= 0) 352 selection.end().compareTo(m_compositionRange->endPosition()) <= 0)
353 return; 353 return;
354 } 354 }
355 355
356 cancelComposition(); 356 cancelComposition();
357 frame().chromeClient().didCancelCompositionOnSelectionChange(); 357 frame().chromeClient().didCancelCompositionOnSelectionChange();
358 } 358 }
359 359
360 static size_t computeCommonPrefixLength(const String& str1,
361 const String& str2) {
362 const size_t maxCommonPrefixLength = std::min(str1.length(), str2.length());
363 for (size_t index = 0; index < maxCommonPrefixLength; ++index) {
364 if (str1[index] != str2[index])
365 return index;
366 }
367 return maxCommonPrefixLength;
368 }
369
370 static size_t computeCommonSuffixLength(const String& str1,
371 const String& str2) {
372 const size_t length1 = str1.length();
373 const size_t length2 = str2.length();
374 const size_t maxCommonSuffixLength = std::min(length1, length2);
375 for (size_t index = 0; index < maxCommonSuffixLength; ++index) {
376 if (str1[length1 - index - 1] != str2[length2 - index - 1])
377 return index;
378 }
379 return maxCommonSuffixLength;
380 }
381
382 // If current position is at grapheme boundary, return 0; otherwise, return the
383 // distance to its nearest left grapheme boundary.
384 static size_t computeDistanceToLeftGraphemeBoundary(const Position& position) {
385 const Position& adjustedPosition = previousPositionOf(
386 nextPositionOf(position, PositionMoveType::GraphemeCluster),
387 PositionMoveType::GraphemeCluster);
388 DCHECK_EQ(position.anchorNode(), adjustedPosition.anchorNode());
389 DCHECK_GE(position.computeOffsetInContainerNode(),
390 adjustedPosition.computeOffsetInContainerNode());
391 return static_cast<size_t>(position.computeOffsetInContainerNode() -
392 adjustedPosition.computeOffsetInContainerNode());
393 }
394
395 static size_t computeCommonGraphemeClusterPrefixLengthForSetComposition(
396 const String& oldText,
397 const String& newText,
398 const Element* rootEditableElement) {
399 const size_t commonPrefixLength = computeCommonPrefixLength(oldText, newText);
400
401 // For grapheme cluster, we should adjust it for grapheme boundary.
402 const EphemeralRange& range =
403 PlainTextRange(0, commonPrefixLength).createRange(*rootEditableElement);
404 if (range.isNull())
405 return 0;
406 const Position& position = range.endPosition();
407 const size_t diff = computeDistanceToLeftGraphemeBoundary(position);
408 DCHECK_GE(commonPrefixLength, diff);
409 return commonPrefixLength - diff;
410 }
411
412 // If current position is at grapheme boundary, return 0; otherwise, return the
413 // distance to its nearest right grapheme boundary.
414 static size_t computeDistanceToRightGraphemeBoundary(const Position& position) {
415 const Position& adjustedPosition = nextPositionOf(
416 previousPositionOf(position, PositionMoveType::GraphemeCluster),
417 PositionMoveType::GraphemeCluster);
418 DCHECK_EQ(position.anchorNode(), adjustedPosition.anchorNode());
419 DCHECK_GE(adjustedPosition.computeOffsetInContainerNode(),
420 position.computeOffsetInContainerNode());
421 return static_cast<size_t>(adjustedPosition.computeOffsetInContainerNode() -
422 position.computeOffsetInContainerNode());
423 }
424
425 static size_t computeCommonGraphemeClusterSuffixLengthForSetComposition(
426 const String& oldText,
427 const String& newText,
428 const Element* rootEditableElement) {
429 const size_t commonSuffixLength = computeCommonSuffixLength(oldText, newText);
430
431 // For grapheme cluster, we should adjust it for grapheme boundary.
432 const EphemeralRange& range =
433 PlainTextRange(0, oldText.length() - commonSuffixLength)
434 .createRange(*rootEditableElement);
435 if (range.isNull())
436 return 0;
437 const Position& position = range.endPosition();
438 const size_t diff = computeDistanceToRightGraphemeBoundary(position);
439 DCHECK_GE(commonSuffixLength, diff);
440 return commonSuffixLength - diff;
441 }
442
443 void InputMethodController::setCompositionWithIncrementalText(
444 const String& text,
445 const Vector<CompositionUnderline>& underlines,
446 int selectionStart,
447 int selectionEnd) {
448 Element* editable = frame().selection().rootEditableElement();
449 if (!editable)
450 return;
451
452 DCHECK_LE(selectionStart, selectionEnd);
453 String composing = composingText();
454 const size_t commonPrefixLength =
455 computeCommonGraphemeClusterPrefixLengthForSetComposition(composing, text,
456 editable);
457
458 // We should ignore common prefix when finding common suffix.
459 const size_t commonSuffixLength =
460 computeCommonGraphemeClusterSuffixLengthForSetComposition(
461 composing.right(composing.length() - commonPrefixLength),
462 text.right(text.length() - commonPrefixLength), editable);
463
464 const bool inserting =
465 text.length() > commonPrefixLength + commonSuffixLength;
466 const bool deleting =
467 composing.length() > commonPrefixLength + commonSuffixLength;
468
469 if (inserting || deleting) {
470 // Select the text to be deleted.
471 const size_t compositionStart =
472 PlainTextRange::create(*editable, compositionEphemeralRange()).start();
473 const size_t deletionStart = compositionStart + commonPrefixLength;
474 const size_t deletionEnd =
475 compositionStart + composing.length() - commonSuffixLength;
476 const EphemeralRange& deletionRange =
477 PlainTextRange(deletionStart, deletionEnd).createRange(*editable);
478 VisibleSelection selection;
479 selection.setWithoutValidation(deletionRange.startPosition(),
480 deletionRange.endPosition());
481 Document* const currentDocument = frame().document();
482 frame().selection().setSelection(selection, 0);
483 clear();
484
485 // FrameSeleciton::setSelection() can change document associate to |frame|.
486 if (currentDocument != frame().document())
487 return;
488 if (!currentDocument->focusedElement())
489 return;
490
491 // Insert the incremental text.
492 const size_t insertionLength =
493 text.length() - commonPrefixLength - commonSuffixLength;
494 const String& insertingText =
495 text.substring(commonPrefixLength, insertionLength);
496 insertTextDuringCompositionWithEvents(frame(), insertingText,
497 TypingCommand::PreventSpellChecking,
498 TypingCommand::TextCompositionUpdate);
499
500 // Event handlers might destroy document.
501 if (currentDocument != frame().document())
502 return;
503
504 // TODO(yosin): The use of updateStyleAndLayoutIgnorePendingStylesheets
505 // needs to be audited. see http://crbug.com/590369 for more details.
506 frame().document()->updateStyleAndLayoutIgnorePendingStylesheets();
507
508 // Now recreate the composition starting at its original start, and
509 // apply the specified final selection offsets.
510 setCompositionFromExistingText(underlines, compositionStart,
511 compositionStart + text.length());
512 }
513
514 selectComposition();
515
516 // TODO(xiaochengh): The use of updateStyleAndLayoutIgnorePendingStylesheets
517 // needs to be audited. see http://crbug.com/590369 for more details.
518 frame().document()->updateStyleAndLayoutIgnorePendingStylesheets();
519
520 const PlainTextRange& selectedRange = createSelectionRangeForSetComposition(
521 selectionStart, selectionEnd, text.length());
522 // We shouldn't close typing in the middle of setComposition.
523 setEditableSelectionOffsets(selectedRange, NotUserTriggered);
524 m_isDirty = true;
525 }
526
360 void InputMethodController::setComposition( 527 void InputMethodController::setComposition(
361 const String& text, 528 const String& text,
362 const Vector<CompositionUnderline>& underlines, 529 const Vector<CompositionUnderline>& underlines,
363 int selectionStart, 530 int selectionStart,
364 int selectionEnd) { 531 int selectionEnd) {
365 Editor::RevealSelectionScope revealSelectionScope(&editor()); 532 Editor::RevealSelectionScope revealSelectionScope(&editor());
366 533
367 // Updates styles before setting selection for composition to prevent 534 // Updates styles before setting selection for composition to prevent
368 // inserting the previous composition text into text nodes oddly. 535 // inserting the previous composition text into text nodes oddly.
369 // See https://bugs.webkit.org/show_bug.cgi?id=46868 536 // See https://bugs.webkit.org/show_bug.cgi?id=46868
370 frame().document()->updateStyleAndLayoutTree(); 537 frame().document()->updateStyleAndLayoutTree();
371 538
539 // When the IME only wants to change a few characters at the end of the
540 // composition, only touch those characters in order to preserve rich text
541 // substructure.
542 if (hasComposition() && text.length()) {
543 return setCompositionWithIncrementalText(text, underlines, selectionStart,
544 selectionEnd);
545 }
546
372 selectComposition(); 547 selectComposition();
373 548
374 if (frame().selection().isNone()) 549 if (frame().selection().isNone())
375 return; 550 return;
376 551
377 Element* target = frame().document()->focusedElement(); 552 Element* target = frame().document()->focusedElement();
378 if (!target) 553 if (!target)
379 return; 554 return;
380 555
381 // TODO(xiaochengh): The use of updateStyleAndLayoutIgnorePendingStylesheets 556 // TODO(xiaochengh): The use of updateStyleAndLayoutIgnorePendingStylesheets
382 // needs to be audited. see http://crbug.com/590369 for more details. 557 // needs to be audited. see http://crbug.com/590369 for more details.
383 frame().document()->updateStyleAndLayoutIgnorePendingStylesheets(); 558 frame().document()->updateStyleAndLayoutIgnorePendingStylesheets();
384 559
385 int selectionOffsetsStart = static_cast<int>(getSelectionOffsets().start()); 560 PlainTextRange selectedRange = createSelectionRangeForSetComposition(
386 int start = selectionOffsetsStart + selectionStart; 561 selectionStart, selectionEnd, text.length());
387 int end = selectionOffsetsStart + selectionEnd;
388 PlainTextRange selectedRange =
389 createRangeForSelection(start, end, text.length());
390 562
391 // Dispatch an appropriate composition event to the focused node. 563 // Dispatch an appropriate composition event to the focused node.
392 // We check the composition status and choose an appropriate composition event 564 // We check the composition status and choose an appropriate composition event
393 // since this function is used for three purposes: 565 // since this function is used for three purposes:
394 // 1. Starting a new composition. 566 // 1. Starting a new composition.
395 // Send a compositionstart and a compositionupdate event when this function 567 // Send a compositionstart and a compositionupdate event when this function
396 // creates a new composition node, i.e. !hasComposition() && 568 // creates a new composition node, i.e. !hasComposition() &&
397 // !text.isEmpty(). 569 // !text.isEmpty().
398 // Sending a compositionupdate event at this time ensures that at least one 570 // Sending a compositionupdate event at this time ensures that at least one
399 // compositionupdate event is dispatched. 571 // compositionupdate event is dispatched.
(...skipping 96 matching lines...) Expand 10 before | Expand all | Expand 10 after
496 EphemeralRange ephemeralLineRange = EphemeralRange( 668 EphemeralRange ephemeralLineRange = EphemeralRange(
497 Position(baseNode, underlineStart), Position(baseNode, underlineEnd)); 669 Position(baseNode, underlineStart), Position(baseNode, underlineEnd));
498 if (ephemeralLineRange.isNull()) 670 if (ephemeralLineRange.isNull())
499 continue; 671 continue;
500 frame().document()->markers().addCompositionMarker( 672 frame().document()->markers().addCompositionMarker(
501 ephemeralLineRange.startPosition(), ephemeralLineRange.endPosition(), 673 ephemeralLineRange.startPosition(), ephemeralLineRange.endPosition(),
502 underline.color(), underline.thick(), underline.backgroundColor()); 674 underline.color(), underline.thick(), underline.backgroundColor());
503 } 675 }
504 } 676 }
505 677
678 PlainTextRange InputMethodController::createSelectionRangeForSetComposition(
679 int selectionStart,
680 int selectionEnd,
681 size_t textLength) const {
682 const int selectionOffsetsStart =
683 static_cast<int>(getSelectionOffsets().start());
684 const int start = selectionOffsetsStart + selectionStart;
685 const int end = selectionOffsetsStart + selectionEnd;
686 return createRangeForSelection(start, end, textLength);
687 }
688
506 void InputMethodController::setCompositionFromExistingText( 689 void InputMethodController::setCompositionFromExistingText(
507 const Vector<CompositionUnderline>& underlines, 690 const Vector<CompositionUnderline>& underlines,
508 unsigned compositionStart, 691 unsigned compositionStart,
509 unsigned compositionEnd) { 692 unsigned compositionEnd) {
510 Element* editable = frame().selection().rootEditableElement(); 693 Element* editable = frame().selection().rootEditableElement();
511 if (!editable) 694 if (!editable)
512 return; 695 return;
513 696
514 DCHECK(!editable->document().needsLayoutTreeUpdate()); 697 DCHECK(!editable->document().needsLayoutTreeUpdate());
515 698
(...skipping 168 matching lines...) Expand 10 before | Expand all | Expand 10 after
684 new RangeVector(1, m_frame->selection().firstRange())); 867 new RangeVector(1, m_frame->selection().firstRange()));
685 TypingCommand::deleteSelection(*frame().document()); 868 TypingCommand::deleteSelection(*frame().document());
686 } 869 }
687 870
688 DEFINE_TRACE(InputMethodController) { 871 DEFINE_TRACE(InputMethodController) {
689 visitor->trace(m_frame); 872 visitor->trace(m_frame);
690 visitor->trace(m_compositionRange); 873 visitor->trace(m_compositionRange);
691 } 874 }
692 875
693 } // namespace blink 876 } // namespace blink
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698