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

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

Issue 2372493002: Workaround for setComposition styling clobber (Closed)
Patch Set: Deal with both prefix and suffix. 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 size_t length1 = str1.length();
363 size_t length2 = str2.length();
364 size_t commonPrefixLength = 0;
yosin_UTC9 2016/10/11 03:58:10 for-loop is better const size_t maxCommonPrefixLe
yabinh 2016/10/11 05:07:54 Done.
365
366 while (commonPrefixLength < length1 && commonPrefixLength < length2 &&
367 str1[commonPrefixLength] == str2[commonPrefixLength])
368 ++commonPrefixLength;
369
370 return commonPrefixLength;
371 }
372
373 static size_t computeCommonSuffixLength(const String& str1,
374 const String& str2) {
375 size_t length1 = str1.length();
376 size_t length2 = str2.length();
377 size_t commonSuffixLength = 0;
378
379 while (commonSuffixLength < length1 && commonSuffixLength < length2 &&
yosin_UTC9 2016/10/11 03:58:10 for-loop is better const size_t maxCommonPrefixLe
yabinh 2016/10/11 05:07:54 Done.
380 str1[length1 - commonSuffixLength - 1] ==
381 str2[length2 - commonSuffixLength - 1])
382 ++commonSuffixLength;
383
384 return commonSuffixLength;
385 }
386
387 static size_t computeCommonGraphemeClusterPrefixLengthForSetComposition(
388 const String& oldText,
389 const String& newText,
390 const Element* rootEditableElement) {
391 size_t commonPrefixLength = computeCommonPrefixLength(oldText, newText);
392
393 // For multi-code text, we should adjust it for grapheme boundary.
yosin_UTC9 2016/10/11 03:58:10 s/multi-code text/grapheme cluster/
yabinh 2016/10/11 05:07:54 Done.
394 const EphemeralRange& range =
395 PlainTextRange(0, commonPrefixLength).createRange(*rootEditableElement);
396 if (range.isNull())
397 return 0;
398 const Position& position = range.endPosition();
399 const Position& adjustedPosition = previousPositionOf(
400 nextPositionOf(position, PositionMoveType::GraphemeCluster),
401 PositionMoveType::GraphemeCluster);
402
403 DCHECK(position.anchorNode()->isEqualNode(adjustedPosition.anchorNode()));
yosin_UTC9 2016/10/11 03:58:10 Let's use DCHECK_EQ(position.anchroNode(), adjuste
yabinh 2016/10/11 05:07:54 Done.
404
405 int diff = position.computeOffsetInContainerNode() -
yosin_UTC9 2016/10/11 03:58:10 Could you add DCHECK_GE(position.computeOffsetInCo
yabinh 2016/10/11 05:07:55 Done.
406 adjustedPosition.computeOffsetInContainerNode();
407 return commonPrefixLength - static_cast<size_t>(diff);
408 }
409
410 static size_t computeCommonGraphemeClusterSuffixLengthForSetComposition(
411 const String& oldText,
412 const String& newText,
413 const Element* rootEditableElement) {
414 size_t commonSuffixLength = computeCommonSuffixLength(oldText, newText);
415
416 // For multi-code text, we should adjust it for grapheme boundary.
417 const EphemeralRange& range =
418 PlainTextRange(0, oldText.length() - commonSuffixLength)
419 .createRange(*rootEditableElement);
420 if (range.isNull())
421 return 0;
422 const Position& position = range.endPosition();
423 const Position& adjustedPosition = nextPositionOf(
424 previousPositionOf(position, PositionMoveType::GraphemeCluster),
425 PositionMoveType::GraphemeCluster);
426
427 DCHECK(position.anchorNode()->isEqualNode(adjustedPosition.anchorNode()));
yosin_UTC9 2016/10/11 03:58:10 See comment in computeCommonGraphemeClusterPrefixL
yabinh 2016/10/11 05:07:54 Done.
428
429 int diff = adjustedPosition.computeOffsetInContainerNode() -
430 position.computeOffsetInContainerNode();
431 return commonSuffixLength - static_cast<size_t>(diff);
432 }
433
434 void InputMethodController::setCompositionWithIncrementalText(
435 const String& text,
436 const Vector<CompositionUnderline>& underlines,
437 int selectionStart,
438 int selectionEnd) {
439 Element* rootEditableElement = frame().selection().rootEditableElement();
440 if (!rootEditableElement)
441 return;
442
443 String composing = composingText();
444 size_t commonPrefixLength =
445 computeCommonGraphemeClusterPrefixLengthForSetComposition(
446 composing, text, rootEditableElement);
447
448 // We should ignore common prefix when finding common suffix.
449 size_t commonSuffixLength =
yosin_UTC9 2016/10/11 03:58:10 |const size_t|?
yabinh 2016/10/11 05:07:54 Done.
450 computeCommonGraphemeClusterSuffixLengthForSetComposition(
451 composing.right(composing.length() - commonPrefixLength),
452 text.right(text.length() - commonPrefixLength), rootEditableElement);
453
454 const bool appending =
455 text.length() > commonPrefixLength + commonSuffixLength;
456 const bool subtracting =
457 composing.length() > commonPrefixLength + +commonSuffixLength;
458
459 if (appending || subtracting) {
460 Element* editable = frame().selection().rootEditableElement();
461 if (!editable)
yosin_UTC9 2016/10/11 03:58:10 We've already check |editable| isn't nullptr at L4
yabinh 2016/10/11 05:07:54 Done.
462 return;
463
464 // Select the text to be subtracted.
465 size_t compositionStart =
466 PlainTextRange::create(*editable, compositionEphemeralRange()).start();
467 size_t subtractionStart = compositionStart + commonPrefixLength;
468 size_t subtractionEnd =
469 compositionStart + composing.length() - commonSuffixLength;
470 const EphemeralRange& subtractionRange =
471 PlainTextRange(subtractionStart, subtractionEnd).createRange(*editable);
472 VisibleSelection selection;
473 selection.setWithoutValidation(subtractionRange.startPosition(),
474 subtractionRange.endPosition());
475 frame().selection().setSelection(selection, 0);
476 clear();
477
478 Element* target = frame().document()->focusedElement();
yosin_UTC9 2016/10/11 03:58:10 FrameSeleciton::setSelection() can change document
yabinh 2016/10/11 05:07:54 Done.
479 if (!target)
480 return;
481
482 // Append the incremental text.
483 size_t appendingLength =
484 text.length() - commonPrefixLength - commonSuffixLength;
485 String appendingText = text.substring(commonPrefixLength, appendingLength);
486 insertTextDuringCompositionWithEvents(frame(), appendingText,
487 TypingCommand::PreventSpellChecking,
488 TypingCommand::TextCompositionUpdate);
489
490 // Event handlers might destroy document.
491 if (!frame().document())
yosin_UTC9 2016/10/11 03:58:10 Similar to L478 comment.
yabinh 2016/10/11 05:07:54 Done.
492 return;
493
494 // TODO(yosin): The use of updateStyleAndLayoutIgnorePendingStylesheets
495 // needs to be audited. see http://crbug.com/590369 for more details.
496 frame().document()->updateStyleAndLayoutIgnorePendingStylesheets();
497
498 // Now recreate the composition starting at its original start, and
499 // apply the specified final selection offsets.
500 setCompositionFromExistingText(underlines, compositionStart,
501 compositionStart + text.length());
502 }
503
504 selectComposition();
505 const PlainTextRange& selectedRange = createSelectionRangeForSetComposition(
506 selectionStart, selectionEnd, text.length());
507 // We shouldn't close typing in the middle of setComposition.
508 setEditableSelectionOffsets(selectedRange, NotUserTriggered);
509 m_isDirty = true;
510 }
511
360 void InputMethodController::setComposition( 512 void InputMethodController::setComposition(
361 const String& text, 513 const String& text,
362 const Vector<CompositionUnderline>& underlines, 514 const Vector<CompositionUnderline>& underlines,
363 int selectionStart, 515 int selectionStart,
364 int selectionEnd) { 516 int selectionEnd) {
365 Editor::RevealSelectionScope revealSelectionScope(&editor()); 517 Editor::RevealSelectionScope revealSelectionScope(&editor());
366 518
367 // Updates styles before setting selection for composition to prevent 519 // Updates styles before setting selection for composition to prevent
368 // inserting the previous composition text into text nodes oddly. 520 // inserting the previous composition text into text nodes oddly.
369 // See https://bugs.webkit.org/show_bug.cgi?id=46868 521 // See https://bugs.webkit.org/show_bug.cgi?id=46868
370 frame().document()->updateStyleAndLayoutTree(); 522 frame().document()->updateStyleAndLayoutTree();
371 523
524 // When the IME only wants to change a few characters at the end of the
525 // composition, only touch those characters in order to preserve rich text
526 // substructure.
527 if (hasComposition() && text.length()) {
528 return setCompositionWithIncrementalText(text, underlines, selectionStart,
529 selectionEnd);
530 }
531
372 selectComposition(); 532 selectComposition();
373 533
374 if (frame().selection().isNone()) 534 if (frame().selection().isNone())
375 return; 535 return;
376 536
377 Element* target = frame().document()->focusedElement(); 537 Element* target = frame().document()->focusedElement();
378 if (!target) 538 if (!target)
379 return; 539 return;
380 540
381 // TODO(xiaochengh): The use of updateStyleAndLayoutIgnorePendingStylesheets 541 PlainTextRange selectedRange = createSelectionRangeForSetComposition(
382 // needs to be audited. see http://crbug.com/590369 for more details. 542 selectionStart, selectionEnd, text.length());
383 frame().document()->updateStyleAndLayoutIgnorePendingStylesheets();
384
385 int selectionOffsetsStart = static_cast<int>(getSelectionOffsets().start());
386 int start = selectionOffsetsStart + selectionStart;
387 int end = selectionOffsetsStart + selectionEnd;
388 PlainTextRange selectedRange =
389 createRangeForSelection(start, end, text.length());
390 543
391 // Dispatch an appropriate composition event to the focused node. 544 // Dispatch an appropriate composition event to the focused node.
392 // We check the composition status and choose an appropriate composition event 545 // We check the composition status and choose an appropriate composition event
393 // since this function is used for three purposes: 546 // since this function is used for three purposes:
394 // 1. Starting a new composition. 547 // 1. Starting a new composition.
395 // Send a compositionstart and a compositionupdate event when this function 548 // Send a compositionstart and a compositionupdate event when this function
396 // creates a new composition node, i.e. !hasComposition() && 549 // creates a new composition node, i.e. !hasComposition() &&
397 // !text.isEmpty(). 550 // !text.isEmpty().
398 // Sending a compositionupdate event at this time ensures that at least one 551 // Sending a compositionupdate event at this time ensures that at least one
399 // compositionupdate event is dispatched. 552 // compositionupdate event is dispatched.
(...skipping 96 matching lines...) Expand 10 before | Expand all | Expand 10 after
496 EphemeralRange ephemeralLineRange = EphemeralRange( 649 EphemeralRange ephemeralLineRange = EphemeralRange(
497 Position(baseNode, underlineStart), Position(baseNode, underlineEnd)); 650 Position(baseNode, underlineStart), Position(baseNode, underlineEnd));
498 if (ephemeralLineRange.isNull()) 651 if (ephemeralLineRange.isNull())
499 continue; 652 continue;
500 frame().document()->markers().addCompositionMarker( 653 frame().document()->markers().addCompositionMarker(
501 ephemeralLineRange.startPosition(), ephemeralLineRange.endPosition(), 654 ephemeralLineRange.startPosition(), ephemeralLineRange.endPosition(),
502 underline.color(), underline.thick(), underline.backgroundColor()); 655 underline.color(), underline.thick(), underline.backgroundColor());
503 } 656 }
504 } 657 }
505 658
659 PlainTextRange InputMethodController::createSelectionRangeForSetComposition(
660 int selectionStart,
661 int selectionEnd,
662 size_t textLength) const {
663 // TODO(xiaochengh): The use of updateStyleAndLayoutIgnorePendingStylesheets
664 // needs to be audited. see http://crbug.com/590369 for more details.
665 frame().document()->updateStyleAndLayoutIgnorePendingStylesheets();
666
667 int selectionOffsetsStart = static_cast<int>(getSelectionOffsets().start());
668 int start = selectionOffsetsStart + selectionStart;
669 int end = selectionOffsetsStart + selectionEnd;
670 return createRangeForSelection(start, end, textLength);
671 }
672
506 void InputMethodController::setCompositionFromExistingText( 673 void InputMethodController::setCompositionFromExistingText(
507 const Vector<CompositionUnderline>& underlines, 674 const Vector<CompositionUnderline>& underlines,
508 unsigned compositionStart, 675 unsigned compositionStart,
509 unsigned compositionEnd) { 676 unsigned compositionEnd) {
510 Element* editable = frame().selection().rootEditableElement(); 677 Element* editable = frame().selection().rootEditableElement();
511 if (!editable) 678 if (!editable)
512 return; 679 return;
513 680
514 DCHECK(!editable->document().needsLayoutTreeUpdate()); 681 DCHECK(!editable->document().needsLayoutTreeUpdate());
515 682
(...skipping 168 matching lines...) Expand 10 before | Expand all | Expand 10 after
684 new RangeVector(1, m_frame->selection().firstRange())); 851 new RangeVector(1, m_frame->selection().firstRange()));
685 TypingCommand::deleteSelection(*frame().document()); 852 TypingCommand::deleteSelection(*frame().document());
686 } 853 }
687 854
688 DEFINE_TRACE(InputMethodController) { 855 DEFINE_TRACE(InputMethodController) {
689 visitor->trace(m_frame); 856 visitor->trace(m_frame);
690 visitor->trace(m_compositionRange); 857 visitor->trace(m_compositionRange);
691 } 858 }
692 859
693 } // namespace blink 860 } // namespace blink
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698