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

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

Issue 2650113004: [WIP] Add support for Android SuggestionSpans when editing text (Closed)
Patch Set: Remove logging statements, fix copyright years in new files Created 3 years, 10 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 15 matching lines...) Expand all
26 26
27 #include "core/editing/InputMethodController.h" 27 #include "core/editing/InputMethodController.h"
28 28
29 #include "core/InputModeNames.h" 29 #include "core/InputModeNames.h"
30 #include "core/InputTypeNames.h" 30 #include "core/InputTypeNames.h"
31 #include "core/dom/Document.h" 31 #include "core/dom/Document.h"
32 #include "core/dom/Element.h" 32 #include "core/dom/Element.h"
33 #include "core/dom/Text.h" 33 #include "core/dom/Text.h"
34 #include "core/editing/EditingUtilities.h" 34 #include "core/editing/EditingUtilities.h"
35 #include "core/editing/Editor.h" 35 #include "core/editing/Editor.h"
36 #include "core/editing/TextSuggestionList.h"
36 #include "core/editing/commands/TypingCommand.h" 37 #include "core/editing/commands/TypingCommand.h"
37 #include "core/editing/markers/DocumentMarkerController.h" 38 #include "core/editing/markers/DocumentMarkerController.h"
38 #include "core/events/CompositionEvent.h" 39 #include "core/events/CompositionEvent.h"
39 #include "core/frame/LocalFrame.h" 40 #include "core/frame/LocalFrame.h"
40 #include "core/html/HTMLInputElement.h" 41 #include "core/html/HTMLInputElement.h"
41 #include "core/html/HTMLTextAreaElement.h" 42 #include "core/html/HTMLTextAreaElement.h"
42 #include "core/input/EventHandler.h" 43 #include "core/input/EventHandler.h"
43 #include "core/layout/LayoutObject.h" 44 #include "core/layout/LayoutObject.h"
44 #include "core/layout/LayoutTheme.h" 45 #include "core/layout/LayoutTheme.h"
45 #include "core/page/ChromeClient.h" 46 #include "core/page/ChromeClient.h"
46 47
47 namespace blink { 48 namespace blink {
48 49
49 namespace { 50 namespace {
50 51
52 const int kMaxNumberSuggestions = 5;
esprehn 2017/01/31 22:41:34 Why 5?
rlanday 2017/01/31 23:30:09 5 is what the native text box on Android uses. Bli
53 const double kSuggestionUnderlineAlphaMultiplier = 0.4;
rlanday 2017/01/31 19:50:21 The suggestion highlight background seems kind of
esprehn 2017/01/31 22:41:34 Did this get a UI review?
rlanday 2017/01/31 23:30:09 This is just existing Android UI we're porting ove
54
51 void dispatchCompositionUpdateEvent(LocalFrame& frame, const String& text) { 55 void dispatchCompositionUpdateEvent(LocalFrame& frame, const String& text) {
52 Element* target = frame.document()->focusedElement(); 56 Element* target = frame.document()->focusedElement();
53 if (!target) 57 if (!target)
54 return; 58 return;
55 59
56 CompositionEvent* event = CompositionEvent::create( 60 CompositionEvent* event = CompositionEvent::create(
57 EventTypeNames::compositionupdate, frame.domWindow(), text); 61 EventTypeNames::compositionupdate, frame.domWindow(), text);
58 target->dispatchEvent(event); 62 target->dispatchEvent(event);
59 } 63 }
60 64
61 void dispatchCompositionEndEvent(LocalFrame& frame, const String& text) { 65 void dispatchCompositionEndEvent(LocalFrame& frame, const String& text) {
62 Element* target = frame.document()->focusedElement(); 66 Element* target = frame.document()->focusedElement();
63 if (!target) 67 if (!target)
64 return; 68 return;
65 69
66 CompositionEvent* event = CompositionEvent::create( 70 CompositionEvent* event = CompositionEvent::create(
67 EventTypeNames::compositionend, frame.domWindow(), text); 71 EventTypeNames::compositionend, frame.domWindow(), text);
68 target->dispatchEvent(event); 72 target->dispatchEvent(event);
69 } 73 }
70 74
71 bool needsIncrementalInsertion(const LocalFrame& frame, const String& newText) { 75 bool needsIncrementalInsertion(const LocalFrame& frame, const String& newText) {
72 // No need to apply incremental insertion if it doesn't support formated text.
73 if (!frame.editor().canEditRichly())
74 return false;
75
76 // No need to apply incremental insertion if the old text (text to be 76 // No need to apply incremental insertion if the old text (text to be
77 // replaced) or the new text (text to be inserted) is empty. 77 // replaced) or the new text (text to be inserted) is empty.
78 if (frame.selectedText().isEmpty() || newText.isEmpty()) 78 if (frame.selectedText().isEmpty() || newText.isEmpty())
79 return false; 79 return false;
80 80
81 return true; 81 return true;
82 } 82 }
83 83
84 DispatchEventResult dispatchBeforeInputFromComposition( 84 DispatchEventResult dispatchBeforeInputFromComposition(
85 EventTarget* target, 85 EventTarget* target,
(...skipping 212 matching lines...) Expand 10 before | Expand all | Expand 10 after
298 // We should do nothing in this case, because: 298 // We should do nothing in this case, because:
299 // 1. No need to insert text when text is empty. 299 // 1. No need to insert text when text is empty.
300 // 2. Shouldn't move caret when relativeCaretPosition == 0 to avoid 300 // 2. Shouldn't move caret when relativeCaretPosition == 0 to avoid
301 // duplicate selection change event. 301 // duplicate selection change event.
302 if (!text.length() && !relativeCaretPosition) 302 if (!text.length() && !relativeCaretPosition)
303 return false; 303 return false;
304 304
305 return insertTextAndMoveCaret(text, relativeCaretPosition, underlines); 305 return insertTextAndMoveCaret(text, relativeCaretPosition, underlines);
306 } 306 }
307 307
308 void InputMethodController::applySuggestionReplacement(int documentMarkerID,
esprehn 2017/01/31 22:41:34 This change is so huge, we probably need a lot of
rlanday 2017/01/31 23:30:09 Yeah I should write some tests for this method. B
309 int suggestionIndex) {
310 suggestionMenuClosed();
311
312 Element* rootEditableElement = frame().selection().rootEditableElement();
313 DocumentMarkerVector suggestionMarkers =
314 rootEditableElement->document().markers().markersInRange(
rlanday 2017/01/31 21:17:12 I introduced a bug here when I was refactoring, th
315 firstEphemeralRangeOf(m_frame->selection().selection()),
316 DocumentMarker::Suggestion);
317
318 DocumentMarker* markerPtr = nullptr;
319 for (const Member<DocumentMarker>& marker : suggestionMarkers) {
320 if (marker->suggestionMarkerID() == documentMarkerID) {
321 markerPtr = marker.get();
322 break;
323 }
324 }
325
326 if (!markerPtr)
327 return;
328
329 // Remove markers that contain part, but not all, of the text range to be
330 // replaced.
331 Vector<int> markerIdsToRemove;
332 HeapVector<Member<DocumentMarker>> markersToExpand;
333 for (const Member<DocumentMarker>& marker : suggestionMarkers) {
334 if ((marker->startOffset() > markerPtr->startOffset() &&
335 marker->startOffset() <= markerPtr->endOffset()) ||
336 (marker->endOffset() < markerPtr->endOffset() &&
337 marker->endOffset() >= markerPtr->startOffset())) {
338 markerIdsToRemove.push_back(marker->suggestionMarkerID());
339 }
340
341 if (marker->startOffset() == markerPtr->startOffset() &&
342 marker->endOffset() == markerPtr->endOffset()) {
343 // Special case: DocumentMarkerController handles shifting most of the
aelias_OOO_until_Jul13 2017/02/10 21:43:55 As a reminder of what we discussed offline, markin
344 // markers around, but if we replace exactly the range of text spanned by
345 // a marker, in some cases, the range will get collapsed to a single
346 // position when the old text is removed and will not be re-expanded. So
347 // we have to re-expand those markers here after the replacement.
348
349 // We actually have to remove and re-insert these markers because in the
350 // case where all the text is replaced, it's possible for the text node
351 // to get removed and re-added, thereby dropping the markers.
352 markerIdsToRemove.push_back(marker->suggestionMarkerID());
353 markersToExpand.push_back(marker);
354 }
355 }
356
357 document().markers().removeSuggestionMarkersByID(markerIdsToRemove);
358
359 setCompositionFromExistingText(Vector<CompositionUnderline>(),
aelias_OOO_until_Jul13 2017/02/10 21:43:55 There's no reason to do anything relating to compo
360 markerPtr->startOffset(),
361 markerPtr->endOffset());
362
363 // The entry in the suggestion drop-down that the user tapped on is replaced
364 // by the text they just overwrote
365 String replacement =
366 String::fromUTF8(markerPtr->suggestions()[suggestionIndex].c_str());
367 int replacementLength = replacement.length();
368 String newSuggestion = composingText();
369
370 // Don't use replaceComposition() here because we don't want to send
371 // JavaScript composition events
372 selectComposition();
373 frame().editor().replaceSelectionWithText(
374 replacement, false, false, InputEvent::InputType::InsertReplacementText);
375
376 markerPtr->replaceSuggestion(suggestionIndex, newSuggestion.utf8().data());
377
378 // Could have changed when we replaced the composition, if we deleted all the
379 // text
380 rootEditableElement = frame().selection().rootEditableElement();
381 for (const Member<DocumentMarker>& marker : markersToExpand) {
382 const EphemeralRange& range =
383 PlainTextRange(marker->startOffset(),
384 marker->startOffset() + replacementLength)
385 .createRange(*rootEditableElement);
386 document().markers().addCompositionMarker(
387 range.startPosition(), range.endPosition(), marker->underlineColor(),
388 marker->thick(), marker->backgroundColor(), marker->suggestions());
389 }
390 }
391
392 void InputMethodController::deleteSuggestionHighlight() {
393 Element* rootEditableElement = frame().selection().rootEditableElement();
394 DocumentMarkerVector suggestionHighlightMarkers =
395 rootEditableElement->document().markers().markersInRange(
396 firstEphemeralRangeOf(m_frame->selection().selection()),
397 DocumentMarker::SuggestionBackgroundHighlight);
398
399 if (suggestionHighlightMarkers.isEmpty())
400 return;
401
402 const DocumentMarker* suggestionHighlightMarkerPtr =
403 suggestionHighlightMarkers[0].get();
404
405 // Remove markers that contain part, but not all, of the text range to be
aelias_OOO_until_Jul13 2017/02/10 21:43:55 It can't be correct for this code to live here, si
406 // deleted.
407 Vector<int> markerIdsToRemove;
408 for (const Member<DocumentMarker>& marker : suggestionHighlightMarkers) {
409 if ((marker->startOffset() > suggestionHighlightMarkerPtr->startOffset() &&
410 marker->startOffset() <= suggestionHighlightMarkerPtr->endOffset()) ||
411 (marker->endOffset() < suggestionHighlightMarkerPtr->endOffset() &&
412 marker->endOffset() >= suggestionHighlightMarkerPtr->startOffset())) {
413 markerIdsToRemove.push_back(marker->suggestionMarkerID());
414 }
415 }
416
417 document().markers().removeSuggestionMarkersByID(markerIdsToRemove);
418
419 // If the character immediately following the range to be deleted is a space,
aelias_OOO_until_Jul13 2017/02/10 21:43:55 Please split this off into a separate method calle
420 // delete it if either of these conditions holds:
421 // - We're deleting at the beginning of the editable text (to avoid ending up
422 // with a space at the beginning)
423 // - The character immediately before the range being deleted is also a space
424 // (to avoid ending up with two adjacent spaces)
425
426 bool deleteNextChar = false;
427
428 const PlainTextRange nextCharacterRange(
429 suggestionHighlightMarkerPtr->endOffset(),
430 suggestionHighlightMarkerPtr->endOffset() + 1);
431 const EphemeralRange& nextCharacterEphemeralRange =
432 nextCharacterRange.createRange(*rootEditableElement);
433 if (!nextCharacterEphemeralRange.isNull()) {
434 String nextCharacterStr = plainText(
435 nextCharacterEphemeralRange,
436 TextIteratorBehavior::Builder().setEmitsSpaceForNbsp(true).build());
437
438 if (WTF::isASCIISpace(nextCharacterStr[0])) {
439 if (suggestionHighlightMarkerPtr->startOffset() == 0) {
440 deleteNextChar = true;
441 } else {
442 const PlainTextRange prevCharacterRange(
443 suggestionHighlightMarkerPtr->startOffset() - 1,
444 suggestionHighlightMarkerPtr->startOffset());
445 const EphemeralRange prevCharacterEphemeralRange =
446 prevCharacterRange.createRange(*rootEditableElement);
447 if (!prevCharacterEphemeralRange.isNull()) {
448 String prevCharacterStr = plainText(prevCharacterEphemeralRange,
449 TextIteratorBehavior::Builder()
450 .setEmitsSpaceForNbsp(true)
451 .build());
452 if (WTF::isASCIISpace(prevCharacterStr[0])) {
453 deleteNextChar = true;
454 }
455 }
456 }
457 }
458 }
459
460 const EphemeralRange highlightRange =
461 PlainTextRange(suggestionHighlightMarkerPtr->startOffset(),
462 suggestionHighlightMarkerPtr->endOffset() + deleteNextChar)
463 .createRange(*rootEditableElement);
464
465 frame().selection().setSelection(
466 SelectionInDOMTree::Builder().setBaseAndExtent(highlightRange).build(),
467 0);
468 // Don't want to send JavaScript composition events here
469 frame().editor().replaceSelectionWithText(
470 "", false, false, InputEvent::InputType::InsertReplacementText);
471
472 suggestionMenuClosed();
473 }
474
475 void InputMethodController::suggestionMenuClosed() {
476 document().markers().removeMarkers(
477 DocumentMarker::SuggestionBackgroundHighlight);
478 frame().selection().setCaretVisible(true);
479 }
480
308 bool InputMethodController::replaceComposition(const String& text) { 481 bool InputMethodController::replaceComposition(const String& text) {
309 if (!hasComposition()) 482 if (!hasComposition())
310 return false; 483 return false;
311 484
312 // Select the text that will be deleted or replaced. 485 // Select the text that will be deleted or replaced.
313 selectComposition(); 486 selectComposition();
314 487
315 if (frame().selection().isNone()) 488 if (frame().selection().isNone())
316 return false; 489 return false;
317 490
(...skipping 37 matching lines...) Expand 10 before | Expand all | Expand 10 after
355 unsigned underlineEnd = offset + underline.endOffset(); 528 unsigned underlineEnd = offset + underline.endOffset();
356 529
357 EphemeralRange ephemeralLineRange = 530 EphemeralRange ephemeralLineRange =
358 PlainTextRange(underlineStart, underlineEnd) 531 PlainTextRange(underlineStart, underlineEnd)
359 .createRange(*rootEditableElement); 532 .createRange(*rootEditableElement);
360 if (ephemeralLineRange.isNull()) 533 if (ephemeralLineRange.isNull())
361 continue; 534 continue;
362 535
363 document().markers().addCompositionMarker( 536 document().markers().addCompositionMarker(
364 ephemeralLineRange.startPosition(), ephemeralLineRange.endPosition(), 537 ephemeralLineRange.startPosition(), ephemeralLineRange.endPosition(),
365 underline.color(), underline.thick(), underline.backgroundColor()); 538 underline.color(), underline.thick(), underline.backgroundColor(),
539 underline.suggestions());
366 } 540 }
367 } 541 }
368 542
543 void InputMethodController::clearSuggestionMarkersTouchingSelection() {
544 document().markers().removeMarkersForWordsAffectedByEditing(
545 DocumentMarker::Suggestion, false);
546 }
547
369 bool InputMethodController::replaceCompositionAndMoveCaret( 548 bool InputMethodController::replaceCompositionAndMoveCaret(
370 const String& text, 549 const String& text,
371 int relativeCaretPosition, 550 int relativeCaretPosition,
372 const Vector<CompositionUnderline>& underlines) { 551 const Vector<CompositionUnderline>& underlines) {
373 Element* rootEditableElement = frame().selection().rootEditableElement(); 552 Element* rootEditableElement = frame().selection().rootEditableElement();
374 if (!rootEditableElement) 553 if (!rootEditableElement)
375 return false; 554 return false;
376 DCHECK(hasComposition()); 555 DCHECK(hasComposition());
377 PlainTextRange compositionRange = 556 PlainTextRange compositionRange =
378 PlainTextRange::create(*rootEditableElement, *m_compositionRange); 557 PlainTextRange::create(*rootEditableElement, *m_compositionRange);
379 if (compositionRange.isNull()) 558 if (compositionRange.isNull())
380 return false; 559 return false;
560
381 int textStart = compositionRange.start(); 561 int textStart = compositionRange.start();
382 562
383 if (!replaceComposition(text)) 563 if (!replaceComposition(text))
384 return false; 564 return false;
385 565
386 // TODO(xiaochengh): The use of updateStyleAndLayoutIgnorePendingStylesheets 566 // TODO(xiaochengh): The use of updateStyleAndLayoutIgnorePendingStylesheets
387 // needs to be audited. see http://crbug.com/590369 for more details. 567 // needs to be audited. see http://crbug.com/590369 for more details.
388 document().updateStyleAndLayoutIgnorePendingStylesheets(); 568 document().updateStyleAndLayoutIgnorePendingStylesheets();
389 569
390 addCompositionUnderlines(underlines, rootEditableElement, textStart); 570 addCompositionUnderlines(underlines, rootEditableElement, textStart);
(...skipping 11 matching lines...) Expand all
402 return true; 582 return true;
403 } 583 }
404 584
405 bool InputMethodController::insertTextAndMoveCaret( 585 bool InputMethodController::insertTextAndMoveCaret(
406 const String& text, 586 const String& text,
407 int relativeCaretPosition, 587 int relativeCaretPosition,
408 const Vector<CompositionUnderline>& underlines) { 588 const Vector<CompositionUnderline>& underlines) {
409 PlainTextRange selectionRange = getSelectionOffsets(); 589 PlainTextRange selectionRange = getSelectionOffsets();
410 if (selectionRange.isNull()) 590 if (selectionRange.isNull())
411 return false; 591 return false;
592
593 clearSuggestionMarkersTouchingSelection();
aelias_OOO_until_Jul13 2017/02/10 21:43:55 If text is inserted by typing on a physical keyboa
594
412 int textStart = selectionRange.start(); 595 int textStart = selectionRange.start();
413 596
414 if (text.length()) { 597 if (text.length()) {
415 if (!insertText(text)) 598 if (!insertText(text))
416 return false; 599 return false;
417 600
418 Element* rootEditableElement = frame().selection().rootEditableElement(); 601 Element* rootEditableElement = frame().selection().rootEditableElement();
419 if (rootEditableElement) { 602 if (rootEditableElement) {
420 addCompositionUnderlines(underlines, rootEditableElement, textStart); 603 addCompositionUnderlines(underlines, rootEditableElement, textStart);
421 } 604 }
(...skipping 76 matching lines...) Expand 10 before | Expand all | Expand 10 after
498 681
499 selectComposition(); 682 selectComposition();
500 683
501 if (frame().selection().isNone()) 684 if (frame().selection().isNone())
502 return; 685 return;
503 686
504 Element* target = document().focusedElement(); 687 Element* target = document().focusedElement();
505 if (!target) 688 if (!target)
506 return; 689 return;
507 690
691 clearSuggestionMarkersTouchingSelection();
692
508 // TODO(xiaochengh): The use of updateStyleAndLayoutIgnorePendingStylesheets 693 // TODO(xiaochengh): The use of updateStyleAndLayoutIgnorePendingStylesheets
509 // needs to be audited. see http://crbug.com/590369 for more details. 694 // needs to be audited. see http://crbug.com/590369 for more details.
510 document().updateStyleAndLayoutIgnorePendingStylesheets(); 695 document().updateStyleAndLayoutIgnorePendingStylesheets();
511 696
512 PlainTextRange selectedRange = createSelectionRangeForSetComposition( 697 PlainTextRange selectedRange = createSelectionRangeForSetComposition(
513 selectionStart, selectionEnd, text.length()); 698 selectionStart, selectionEnd, text.length());
514
515 // Dispatch an appropriate composition event to the focused node. 699 // Dispatch an appropriate composition event to the focused node.
516 // We check the composition status and choose an appropriate composition event 700 // We check the composition status and choose an appropriate composition event
517 // since this function is used for three purposes: 701 // since this function is used for three purposes:
518 // 1. Starting a new composition. 702 // 1. Starting a new composition.
519 // Send a compositionstart and a compositionupdate event when this function 703 // Send a compositionstart and a compositionupdate event when this function
520 // creates a new composition node, i.e. !hasComposition() && 704 // creates a new composition node, i.e. !hasComposition() &&
521 // !text.isEmpty(). 705 // !text.isEmpty().
522 // Sending a compositionupdate event at this time ensures that at least one 706 // Sending a compositionupdate event at this time ensures that at least one
523 // compositionupdate event is dispatched. 707 // compositionupdate event is dispatched.
524 // 2. Updating the existing composition node. 708 // 2. Updating the existing composition node.
(...skipping 74 matching lines...) Expand 10 before | Expand all | Expand 10 after
599 // needs to be audited. see http://crbug.com/590369 for more details. 783 // needs to be audited. see http://crbug.com/590369 for more details.
600 document().updateStyleAndLayoutIgnorePendingStylesheets(); 784 document().updateStyleAndLayoutIgnorePendingStylesheets();
601 785
602 // We shouldn't close typing in the middle of setComposition. 786 // We shouldn't close typing in the middle of setComposition.
603 setEditableSelectionOffsets(selectedRange, NotUserTriggered); 787 setEditableSelectionOffsets(selectedRange, NotUserTriggered);
604 788
605 if (underlines.isEmpty()) { 789 if (underlines.isEmpty()) {
606 document().markers().addCompositionMarker( 790 document().markers().addCompositionMarker(
607 m_compositionRange->startPosition(), m_compositionRange->endPosition(), 791 m_compositionRange->startPosition(), m_compositionRange->endPosition(),
608 Color::black, false, 792 Color::black, false,
609 LayoutTheme::theme().platformDefaultCompositionBackgroundColor()); 793 LayoutTheme::theme().platformDefaultCompositionBackgroundColor(),
794 std::vector<std::string>());
610 return; 795 return;
611 } 796 }
612 797
613 addCompositionUnderlines(underlines, baseNode->parentNode(), baseOffset); 798 addCompositionUnderlines(underlines, baseNode->parentNode(), baseOffset);
614 } 799 }
615 800
616 PlainTextRange InputMethodController::createSelectionRangeForSetComposition( 801 PlainTextRange InputMethodController::createSelectionRangeForSetComposition(
617 int selectionStart, 802 int selectionStart,
618 int selectionEnd, 803 int selectionEnd,
619 size_t textLength) const { 804 size_t textLength) const {
(...skipping 446 matching lines...) Expand 10 before | Expand all | Expand 10 after
1066 return WebTextInputTypeDateTimeField; 1251 return WebTextInputTypeDateTimeField;
1067 } 1252 }
1068 1253
1069 document().updateStyleAndLayoutTree(); 1254 document().updateStyleAndLayoutTree();
1070 if (hasEditableStyle(*element)) 1255 if (hasEditableStyle(*element))
1071 return WebTextInputTypeContentEditable; 1256 return WebTextInputTypeContentEditable;
1072 1257
1073 return WebTextInputTypeNone; 1258 return WebTextInputTypeNone;
1074 } 1259 }
1075 1260
1261 WebVector<blink::WebTextSuggestionInfo>
1262 InputMethodController::getTextSuggestionInfosUnderCaret() const {
aelias_OOO_until_Jul13 2017/02/10 21:43:55 Please move this and most other changes in this fi
1263 Element* rootEditableElement = frame().selection().rootEditableElement();
1264 if (!rootEditableElement)
1265 return WebVector<blink::WebTextSuggestionInfo>();
1266
1267 DocumentMarkerVector suggestionMarkers =
1268 rootEditableElement->document().markers().markersInRangeInclusive(
1269 firstEphemeralRangeOf(m_frame->selection().selection()),
1270 DocumentMarker::Suggestion);
1271
1272 Vector<TextSuggestionList> suggestionSpans;
1273 for (const Member<DocumentMarker>& marker : suggestionMarkers) {
1274 // ignore ranges that have been collapsed as a result of editing
1275 // operations
1276 if (marker->endOffset() == marker->startOffset())
1277 continue;
1278
1279 TextSuggestionList suggestionList;
1280 suggestionList.documentMarkerID = marker->suggestionMarkerID();
1281 suggestionList.start = marker->startOffset();
1282 suggestionList.end = marker->endOffset();
1283 suggestionList.suggestions = marker->suggestions();
1284 suggestionSpans.push_back(suggestionList);
1285 }
1286
1287 std::sort(suggestionSpans.begin(), suggestionSpans.end());
1288
1289 std::vector<WebTextSuggestionInfo> suggestionInfos;
esprehn 2017/01/31 22:41:34 WTF Vector
rlanday 2017/01/31 23:30:09 Ok
1290
1291 for (const TextSuggestionList& suggestionList : suggestionSpans) {
1292 if (suggestionInfos.size() == kMaxNumberSuggestions)
1293 break;
1294
1295 for (size_t suggestionIndex = 0;
1296 suggestionIndex < suggestionList.suggestions.size();
1297 suggestionIndex++) {
1298 const std::string& suggestion =
1299 suggestionList.suggestions[suggestionIndex];
1300 bool isDupe = false;
1301 for (size_t i = 0; i < suggestionInfos.size(); i++) {
1302 const WebTextSuggestionInfo& otherSuggestionInfo = suggestionInfos[i];
1303 if (otherSuggestionInfo.suggestion == suggestion) {
1304 if (suggestionList.start == otherSuggestionInfo.spanStart &&
1305 suggestionList.end == otherSuggestionInfo.spanEnd) {
1306 isDupe = true;
1307 break;
1308 }
1309 }
1310 }
1311
1312 if (isDupe)
1313 continue;
1314
1315 WebTextSuggestionInfo suggestionInfo;
1316 suggestionInfo.documentMarkerID = suggestionList.documentMarkerID;
1317 suggestionInfo.suggestionStart = 0;
1318 suggestionInfo.suggestionEnd = suggestion.length();
1319 suggestionInfo.spanStart = suggestionList.start;
1320 suggestionInfo.spanEnd = suggestionList.end;
1321 suggestionInfo.suggestionIndex = suggestionIndex;
1322 suggestionInfo.suggestion = suggestion;
1323 suggestionInfos.push_back(suggestionInfo);
1324 if (suggestionInfos.size() == kMaxNumberSuggestions)
1325 break;
1326 }
1327 }
1328
1329 if (suggestionInfos.size() == 0)
1330 return WebVector<blink::WebTextSuggestionInfo>();
1331
1332 int spanUnionStart = suggestionInfos[0].spanStart;
1333 int spanUnionEnd = suggestionInfos[0].spanEnd;
1334
1335 for (size_t i = 1; i < suggestionInfos.size(); i++) {
1336 spanUnionStart = std::min(spanUnionStart, suggestionInfos[i].spanStart);
1337 spanUnionEnd = std::max(spanUnionEnd, suggestionInfos[i].spanEnd);
1338 }
1339
1340 for (WebTextSuggestionInfo& info : suggestionInfos) {
1341 const EphemeralRange& prefixRange =
1342 PlainTextRange(spanUnionStart, info.spanStart)
1343 .createRange(*rootEditableElement);
1344 String prefix =
1345 plainText(prefixRange, TextIteratorBehavior::Builder().build());
1346
1347 const EphemeralRange& suffixRange =
1348 PlainTextRange(info.spanEnd, spanUnionEnd)
1349 .createRange(*rootEditableElement);
1350 String suffix =
1351 plainText(suffixRange, TextIteratorBehavior::Builder().build());
1352
1353 info.prefix = prefix.utf8().data();
1354 info.suffix = suffix.utf8().data();
1355 }
1356
1357 Color highlightColor;
1358 if (suggestionMarkers[0]->underlineColor() != 0) {
1359 highlightColor = suggestionMarkers[0]->underlineColor().combineWithAlpha(
1360 kSuggestionUnderlineAlphaMultiplier);
1361 } else {
1362 highlightColor = LayoutTheme::tapHighlightColor();
1363 }
1364
1365 EphemeralRange backgroundHighlightRange =
1366 PlainTextRange(spanUnionStart, spanUnionEnd)
1367 .createRange(*rootEditableElement);
1368 document().markers().addSuggestionBackgroundHighlightMarker(
aelias_OOO_until_Jul13 2017/02/10 21:43:55 I don't think a method called "get...()" should be
1369 backgroundHighlightRange.startPosition(),
1370 backgroundHighlightRange.endPosition(), Color::black, false,
1371 highlightColor);
1372
1373 return suggestionInfos;
1374 }
1375
1376 void InputMethodController::prepareForTextSuggestionMenuToBeShown() {
1377 frame().selection().setCaretVisible(false);
1378 }
1379
1076 void InputMethodController::willChangeFocus() { 1380 void InputMethodController::willChangeFocus() {
1077 if (!finishComposingText(DoNotKeepSelection)) 1381 if (!finishComposingText(DoNotKeepSelection))
1078 return; 1382 return;
1079 frame().chromeClient().resetInputMethod(); 1383 frame().chromeClient().resetInputMethod();
1080 } 1384 }
1081 1385
1082 DEFINE_TRACE(InputMethodController) { 1386 DEFINE_TRACE(InputMethodController) {
1083 visitor->trace(m_frame); 1387 visitor->trace(m_frame);
1084 visitor->trace(m_compositionRange); 1388 visitor->trace(m_compositionRange);
1085 SynchronousMutationObserver::trace(visitor); 1389 SynchronousMutationObserver::trace(visitor);
1086 } 1390 }
1087 1391
1088 } // namespace blink 1392 } // namespace blink
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698