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

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: 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;
53 const double kSuggestionUnderlineAlphaMultiplier = 0.4;
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 // Used to insert/replace text during composition update and confirm 84 // Used to insert/replace text during composition update and confirm
85 // composition. 85 // composition.
(...skipping 191 matching lines...) Expand 10 before | Expand all | Expand 10 after
277 // We should do nothing in this case, because: 277 // We should do nothing in this case, because:
278 // 1. No need to insert text when text is empty. 278 // 1. No need to insert text when text is empty.
279 // 2. Shouldn't move caret when relativeCaretPosition == 0 to avoid 279 // 2. Shouldn't move caret when relativeCaretPosition == 0 to avoid
280 // duplicate selection change event. 280 // duplicate selection change event.
281 if (!text.length() && !relativeCaretPosition) 281 if (!text.length() && !relativeCaretPosition)
282 return false; 282 return false;
283 283
284 return insertTextAndMoveCaret(text, relativeCaretPosition, underlines); 284 return insertTextAndMoveCaret(text, relativeCaretPosition, underlines);
285 } 285 }
286 286
287 void InputMethodController::applySuggestionReplacement(int documentMarkerID,
288 int suggestionIndex) {
289 closeSuggestionMenu();
290
291 Element* rootEditableElement = frame().selection().rootEditableElement();
292 DocumentMarkerVector suggestionMarkers =
293 rootEditableElement->document().markers().markersInRange(
294 firstEphemeralRangeOf(m_frame->selection().selection()),
295 DocumentMarker::Suggestion);
296
297 DocumentMarker* markerPtr = nullptr;
298 for (const Member<DocumentMarker>& marker : suggestionMarkers) {
299 if (marker->suggestionMarkerID() == documentMarkerID) {
300 markerPtr = marker.get();
301 break;
302 }
303 }
304
305 if (markerPtr == nullptr)
306 return;
307
308 // Remove markers that contain part, but not all, of the text range to be
309 // replaced.
310 Vector<int> markerIdsToRemove;
311 HeapVector<Member<DocumentMarker>> markersToExpand;
312 for (const Member<DocumentMarker>& marker : suggestionMarkers) {
313 if ((marker->startOffset() > markerPtr->startOffset() &&
314 marker->startOffset() <= markerPtr->endOffset()) ||
aelias_OOO_until_Jul13 2017/01/25 03:34:27 Isn't it equivalent to say "marker->startOffset()
rlanday 2017/01/25 18:48:10 I don't think so, because your check doesn't handl
315 (marker->endOffset() < markerPtr->endOffset() &&
316 marker->endOffset() >= markerPtr->startOffset())) {
317 markerIdsToRemove.push_back(marker->suggestionMarkerID());
318 }
319
320 if (marker->startOffset() == markerPtr->startOffset() &&
321 marker->endOffset() == markerPtr->endOffset()) {
322 // Special case: DocumentMarkerController handles shifting most of the
323 // markers around, but if we replace exactly the range of text spanned by
324 // a marker, in some cases, the range will get collapsed to a single
325 // position when the old text is removed and will not be re-expanded. So
326 // we have to re-expand those markers here after the replacement.
327
328 // We actually have to remove and re-insert these markers because in the
329 // case
330 // where all the text is replaced, it's possible for the text node to get
331 // removed and re-added, thereby dropping the markers.
332 markerIdsToRemove.push_back(marker->suggestionMarkerID());
333 markersToExpand.push_back(marker);
aelias_OOO_until_Jul13 2017/01/25 03:34:26 Seems a bit weird, though I don't understand the s
rlanday 2017/01/25 18:48:10 I'll look into that code
rlanday 2017/01/25 18:48:10 I'll look into that code
rlanday 2017/01/28 02:35:28 Ok, there are actually two methods involved with u
334 }
335 }
336
337 document().markers().removeSuggestionMarkersByID(markerIdsToRemove);
338
339 setCompositionFromExistingText(Vector<CompositionUnderline>(),
340 markerPtr->startOffset(),
341 markerPtr->endOffset());
342
343 // The entry in the suggestion drop-down that the user tapped on is replaced
344 // by the text they just overwrote
345 String replacement =
346 String::fromUTF8(markerPtr->suggestions()[suggestionIndex].c_str());
347 int replacementLength = replacement.length();
348 String newSuggestion = composingText();
349 replaceComposition(replacement);
350 markerPtr->replaceSuggestion(suggestionIndex, newSuggestion.utf8().data());
351
352 // Could have changed when we replaced the composition, if we deleted all the
353 // text
354 rootEditableElement = frame().selection().rootEditableElement();
355 for (const Member<DocumentMarker>& marker : markersToExpand) {
356 const EphemeralRange& range =
357 PlainTextRange(marker->startOffset(),
358 marker->startOffset() + replacementLength)
359 .createRange(*rootEditableElement);
360 document().markers().addCompositionMarker(
361 range.startPosition(), range.endPosition(), marker->underlineColor(),
362 marker->thick(), marker->backgroundColor(), marker->suggestions());
363 }
364 }
365
366 void InputMethodController::deleteSuggestionHighlight() {
367 Element* rootEditableElement = frame().selection().rootEditableElement();
368 DocumentMarkerVector suggestionHighlightMarkers =
369 rootEditableElement->document().markers().markersInRange(
370 firstEphemeralRangeOf(m_frame->selection().selection()),
371 DocumentMarker::SuggestionBackgroundHighlight);
372
373 if (suggestionHighlightMarkers.size() == 0)
aelias_OOO_until_Jul13 2017/01/25 03:34:26 isEmpty()
374 return;
375
376 const DocumentMarker* suggestionHighlightMarkerPtr =
377 suggestionHighlightMarkers[0].get();
378
379 // Remove markers that contain part, but not all, of the text range to be
380 // deleted.
381 Vector<int> markerIdsToRemove;
382 HeapVector<Member<DocumentMarker>> markersToExpand;
aelias_OOO_until_Jul13 2017/01/25 03:34:27 unused
383 for (const Member<DocumentMarker>& marker : suggestionHighlightMarkers) {
384 if ((marker->startOffset() > suggestionHighlightMarkerPtr->startOffset() &&
385 marker->startOffset() <= suggestionHighlightMarkerPtr->endOffset()) ||
386 (marker->endOffset() < suggestionHighlightMarkerPtr->endOffset() &&
387 marker->endOffset() >= suggestionHighlightMarkerPtr->startOffset())) {
388 markerIdsToRemove.push_back(marker->suggestionMarkerID());
389 }
390 }
391
392 document().markers().removeSuggestionMarkersByID(markerIdsToRemove);
393
394 // If the character immediately following the range to be deleted is a space,
395 // delete it if either of these conditions holds:
396 // - We're deleting at the beginning of the editable text (to avoid ending up
397 // with a space at the beginning)
398 // - The character immediately before the range being deleted is also a space
399 // (to avoid ending up with two adjacent spaces)
400
401 bool deleteNextChar = false;
402
403 const PlainTextRange nextCharacterRange(
404 suggestionHighlightMarkerPtr->endOffset(),
405 suggestionHighlightMarkerPtr->endOffset() + 1);
406 const EphemeralRange& nextCharacterEphemeralRange =
407 nextCharacterRange.createRange(*rootEditableElement);
408 if (!nextCharacterEphemeralRange.isNull()) {
409 String nextCharacterStr =
410 plainText(nextCharacterEphemeralRange, TextIteratorEmitsOriginalText);
aelias_OOO_until_Jul13 2017/01/25 03:34:26 EmitsOriginalText is for password fields not to be
rlanday 2017/01/26 18:28:52 I copied this from the composingText() method, are
aelias_OOO_until_Jul13 2017/01/27 19:56:36 It doesn't really make sense for password fields t
411
412 if (WTF::isASCIISpace(nextCharacterStr[0])) {
aelias_OOO_until_Jul13 2017/01/25 03:34:26 Probably want to consider non-breaking spaces as w
rlanday 2017/01/25 18:48:10 I was having trouble finding a function that seeme
413 if (suggestionHighlightMarkerPtr->startOffset() == 0) {
aelias_OOO_until_Jul13 2017/01/25 03:34:27 The range code works even if this is 0, right? Co
rlanday 2017/01/25 18:48:10 This is not for performance, this is so if you hav
414 deleteNextChar = true;
415 } else {
416 const PlainTextRange prevCharacterRange(
417 suggestionHighlightMarkerPtr->startOffset() - 1,
418 suggestionHighlightMarkerPtr->startOffset());
419 const EphemeralRange prevCharacterEphemeralRange =
420 prevCharacterRange.createRange(*rootEditableElement);
421 if (!prevCharacterEphemeralRange.isNull()) {
422 String prevCharacterStr = plainText(prevCharacterEphemeralRange,
423 TextIteratorEmitsOriginalText);
424 if (WTF::isASCIISpace(prevCharacterStr[0])) {
425 deleteNextChar = true;
426 }
427 }
428 }
429 }
430 }
431
432 setCompositionFromExistingText(
aelias_OOO_until_Jul13 2017/01/25 03:34:27 We don't want to send composition events to Javasc
433 Vector<CompositionUnderline>(),
434 suggestionHighlightMarkerPtr->startOffset(),
435 suggestionHighlightMarkerPtr->endOffset() + deleteNextChar);
436 // send msg to IME
437 replaceComposition("");
438
439 closeSuggestionMenu();
440 }
441
442 void InputMethodController::closeSuggestionMenu() {
443 document().markers().removeMarkers(
444 DocumentMarker::SuggestionBackgroundHighlight);
445 frame().selection().setCaretVisible(true);
446 }
447
287 bool InputMethodController::replaceComposition(const String& text) { 448 bool InputMethodController::replaceComposition(const String& text) {
288 if (!hasComposition()) 449 if (!hasComposition())
289 return false; 450 return false;
290 451
291 // Select the text that will be deleted or replaced. 452 // Select the text that will be deleted or replaced.
292 selectComposition(); 453 selectComposition();
293 454
294 if (frame().selection().isNone()) 455 if (frame().selection().isNone())
295 return false; 456 return false;
296 457
(...skipping 37 matching lines...) Expand 10 before | Expand all | Expand 10 after
334 unsigned underlineEnd = offset + underline.endOffset(); 495 unsigned underlineEnd = offset + underline.endOffset();
335 496
336 EphemeralRange ephemeralLineRange = 497 EphemeralRange ephemeralLineRange =
337 PlainTextRange(underlineStart, underlineEnd) 498 PlainTextRange(underlineStart, underlineEnd)
338 .createRange(*rootEditableElement); 499 .createRange(*rootEditableElement);
339 if (ephemeralLineRange.isNull()) 500 if (ephemeralLineRange.isNull())
340 continue; 501 continue;
341 502
342 document().markers().addCompositionMarker( 503 document().markers().addCompositionMarker(
343 ephemeralLineRange.startPosition(), ephemeralLineRange.endPosition(), 504 ephemeralLineRange.startPosition(), ephemeralLineRange.endPosition(),
344 underline.color(), underline.thick(), underline.backgroundColor()); 505 underline.color(), underline.thick(), underline.backgroundColor(),
506 underline.suggestions());
345 } 507 }
346 } 508 }
347 509
510 void InputMethodController::clearSuggestionMarkersTouchingSelection() {
511 Element* rootEditableElement = frame().selection().rootEditableElement();
512
513 DocumentMarkerVector suggestionMarkers =
514 rootEditableElement->document().markers().markersInRange(
515 firstEphemeralRangeOf(m_frame->selection().selection()),
516 DocumentMarker::Suggestion);
517
518 // We can't use DocumentMarkerController::removeMarkers() to remove markers
519 // overlapping with the selection range because we also want to remove markers
520 // merely touching, not overlapping, the selection
521 Vector<int> markerIdsToRemove;
522 for (const Member<DocumentMarker>& marker : suggestionMarkers) {
523 markerIdsToRemove.push_back(marker->suggestionMarkerID());
524 }
525
526 document().markers().removeSuggestionMarkersByID(markerIdsToRemove);
527 }
528
348 bool InputMethodController::replaceCompositionAndMoveCaret( 529 bool InputMethodController::replaceCompositionAndMoveCaret(
349 const String& text, 530 const String& text,
350 int relativeCaretPosition, 531 int relativeCaretPosition,
351 const Vector<CompositionUnderline>& underlines) { 532 const Vector<CompositionUnderline>& underlines) {
352 Element* rootEditableElement = frame().selection().rootEditableElement(); 533 Element* rootEditableElement = frame().selection().rootEditableElement();
353 if (!rootEditableElement) 534 if (!rootEditableElement)
354 return false; 535 return false;
355 DCHECK(hasComposition()); 536 DCHECK(hasComposition());
356 PlainTextRange compositionRange = 537 PlainTextRange compositionRange =
357 PlainTextRange::create(*rootEditableElement, *m_compositionRange); 538 PlainTextRange::create(*rootEditableElement, *m_compositionRange);
358 if (compositionRange.isNull()) 539 if (compositionRange.isNull())
359 return false; 540 return false;
541
360 int textStart = compositionRange.start(); 542 int textStart = compositionRange.start();
361 543
362 if (!replaceComposition(text)) 544 if (!replaceComposition(text))
363 return false; 545 return false;
364 546
365 // TODO(xiaochengh): The use of updateStyleAndLayoutIgnorePendingStylesheets 547 // TODO(xiaochengh): The use of updateStyleAndLayoutIgnorePendingStylesheets
366 // needs to be audited. see http://crbug.com/590369 for more details. 548 // needs to be audited. see http://crbug.com/590369 for more details.
367 document().updateStyleAndLayoutIgnorePendingStylesheets(); 549 document().updateStyleAndLayoutIgnorePendingStylesheets();
368 550
369 addCompositionUnderlines(underlines, rootEditableElement, textStart); 551 addCompositionUnderlines(underlines, rootEditableElement, textStart);
(...skipping 11 matching lines...) Expand all
381 return true; 563 return true;
382 } 564 }
383 565
384 bool InputMethodController::insertTextAndMoveCaret( 566 bool InputMethodController::insertTextAndMoveCaret(
385 const String& text, 567 const String& text,
386 int relativeCaretPosition, 568 int relativeCaretPosition,
387 const Vector<CompositionUnderline>& underlines) { 569 const Vector<CompositionUnderline>& underlines) {
388 PlainTextRange selectionRange = getSelectionOffsets(); 570 PlainTextRange selectionRange = getSelectionOffsets();
389 if (selectionRange.isNull()) 571 if (selectionRange.isNull())
390 return false; 572 return false;
573
574 clearSuggestionMarkersTouchingSelection();
575
391 int textStart = selectionRange.start(); 576 int textStart = selectionRange.start();
392 577
393 if (text.length()) { 578 if (text.length()) {
394 if (!insertText(text)) 579 if (!insertText(text))
395 return false; 580 return false;
396 581
397 Element* rootEditableElement = frame().selection().rootEditableElement(); 582 Element* rootEditableElement = frame().selection().rootEditableElement();
398 if (rootEditableElement) { 583 if (rootEditableElement) {
399 addCompositionUnderlines(underlines, rootEditableElement, textStart); 584 addCompositionUnderlines(underlines, rootEditableElement, textStart);
400 } 585 }
(...skipping 92 matching lines...) Expand 10 before | Expand all | Expand 10 after
493 678
494 selectComposition(); 679 selectComposition();
495 680
496 if (frame().selection().isNone()) 681 if (frame().selection().isNone())
497 return; 682 return;
498 683
499 Element* target = document().focusedElement(); 684 Element* target = document().focusedElement();
500 if (!target) 685 if (!target)
501 return; 686 return;
502 687
688 clearSuggestionMarkersTouchingSelection();
689
503 // TODO(xiaochengh): The use of updateStyleAndLayoutIgnorePendingStylesheets 690 // TODO(xiaochengh): The use of updateStyleAndLayoutIgnorePendingStylesheets
504 // needs to be audited. see http://crbug.com/590369 for more details. 691 // needs to be audited. see http://crbug.com/590369 for more details.
505 document().updateStyleAndLayoutIgnorePendingStylesheets(); 692 document().updateStyleAndLayoutIgnorePendingStylesheets();
506 693
507 PlainTextRange selectedRange = createSelectionRangeForSetComposition( 694 PlainTextRange selectedRange = createSelectionRangeForSetComposition(
508 selectionStart, selectionEnd, text.length()); 695 selectionStart, selectionEnd, text.length());
509
510 // Dispatch an appropriate composition event to the focused node. 696 // Dispatch an appropriate composition event to the focused node.
511 // We check the composition status and choose an appropriate composition event 697 // We check the composition status and choose an appropriate composition event
512 // since this function is used for three purposes: 698 // since this function is used for three purposes:
513 // 1. Starting a new composition. 699 // 1. Starting a new composition.
514 // Send a compositionstart and a compositionupdate event when this function 700 // Send a compositionstart and a compositionupdate event when this function
515 // creates a new composition node, i.e. !hasComposition() && 701 // creates a new composition node, i.e. !hasComposition() &&
516 // !text.isEmpty(). 702 // !text.isEmpty().
517 // Sending a compositionupdate event at this time ensures that at least one 703 // Sending a compositionupdate event at this time ensures that at least one
518 // compositionupdate event is dispatched. 704 // compositionupdate event is dispatched.
519 // 2. Updating the existing composition node. 705 // 2. Updating the existing composition node.
(...skipping 74 matching lines...) Expand 10 before | Expand all | Expand 10 after
594 // needs to be audited. see http://crbug.com/590369 for more details. 780 // needs to be audited. see http://crbug.com/590369 for more details.
595 document().updateStyleAndLayoutIgnorePendingStylesheets(); 781 document().updateStyleAndLayoutIgnorePendingStylesheets();
596 782
597 // We shouldn't close typing in the middle of setComposition. 783 // We shouldn't close typing in the middle of setComposition.
598 setEditableSelectionOffsets(selectedRange, NotUserTriggered); 784 setEditableSelectionOffsets(selectedRange, NotUserTriggered);
599 785
600 if (underlines.isEmpty()) { 786 if (underlines.isEmpty()) {
601 document().markers().addCompositionMarker( 787 document().markers().addCompositionMarker(
602 m_compositionRange->startPosition(), m_compositionRange->endPosition(), 788 m_compositionRange->startPosition(), m_compositionRange->endPosition(),
603 Color::black, false, 789 Color::black, false,
604 LayoutTheme::theme().platformDefaultCompositionBackgroundColor()); 790 LayoutTheme::theme().platformDefaultCompositionBackgroundColor(),
791 std::vector<std::string>());
605 return; 792 return;
606 } 793 }
607 794
608 addCompositionUnderlines(underlines, baseNode->parentNode(), baseOffset); 795 addCompositionUnderlines(underlines, baseNode->parentNode(), baseOffset);
609 } 796 }
610 797
611 PlainTextRange InputMethodController::createSelectionRangeForSetComposition( 798 PlainTextRange InputMethodController::createSelectionRangeForSetComposition(
612 int selectionStart, 799 int selectionStart,
613 int selectionEnd, 800 int selectionEnd,
614 size_t textLength) const { 801 size_t textLength) const {
(...skipping 439 matching lines...) Expand 10 before | Expand all | Expand 10 after
1054 return WebTextInputTypeDateTimeField; 1241 return WebTextInputTypeDateTimeField;
1055 } 1242 }
1056 1243
1057 document().updateStyleAndLayoutTree(); 1244 document().updateStyleAndLayoutTree();
1058 if (hasEditableStyle(*element)) 1245 if (hasEditableStyle(*element))
1059 return WebTextInputTypeContentEditable; 1246 return WebTextInputTypeContentEditable;
1060 1247
1061 return WebTextInputTypeNone; 1248 return WebTextInputTypeNone;
1062 } 1249 }
1063 1250
1251 WebVector<blink::WebTextSuggestionInfo>
1252 InputMethodController::getTextSuggestionInfosUnderCaret() const {
1253 Element* rootEditableElement = frame().selection().rootEditableElement();
1254 if (!rootEditableElement)
1255 return WebVector<blink::WebTextSuggestionInfo>();
1256
1257 DocumentMarkerVector suggestionMarkers =
1258 rootEditableElement->document().markers().markersInRange(
1259 firstEphemeralRangeOf(m_frame->selection().selection()),
1260 DocumentMarker::Suggestion);
1261
1262 Vector<TextSuggestionList> suggestionSpans;
1263 for (const Member<DocumentMarker>& marker : suggestionMarkers) {
1264 // ignore ranges that have been collapsed as a result of editing
1265 // operations
1266 if (marker->endOffset() == marker->startOffset())
1267 continue;
1268
1269 TextSuggestionList suggestionList;
1270 suggestionList.documentMarkerID = marker->suggestionMarkerID();
1271 suggestionList.start = marker->startOffset();
1272 suggestionList.end = marker->endOffset();
1273 suggestionList.suggestions = marker->suggestions();
1274 suggestionSpans.push_back(suggestionList);
1275 }
1276
1277 std::sort(suggestionSpans.begin(), suggestionSpans.end());
1278
1279 std::vector<WebTextSuggestionInfo> suggestionInfos;
1280
1281 for (const TextSuggestionList& suggestionList : suggestionSpans) {
1282 if (suggestionInfos.size() == kMaxNumberSuggestions)
1283 break;
1284
1285 for (size_t suggestionIndex = 0;
1286 suggestionIndex < suggestionList.suggestions.size();
1287 suggestionIndex++) {
1288 const std::string& suggestion =
1289 suggestionList.suggestions[suggestionIndex];
1290 bool isDupe = false;
1291 for (size_t i = 0; i < suggestionInfos.size(); i++) {
1292 const WebTextSuggestionInfo& otherSuggestionInfo = suggestionInfos[i];
1293 if (otherSuggestionInfo.suggestion == suggestion) {
1294 if (suggestionList.start == otherSuggestionInfo.spanStart &&
1295 suggestionList.end == otherSuggestionInfo.spanEnd) {
1296 isDupe = true;
1297 break;
1298 }
1299 }
1300 }
1301
1302 if (isDupe)
1303 continue;
1304
1305 WebTextSuggestionInfo suggestionInfo;
1306 suggestionInfo.documentMarkerID = suggestionList.documentMarkerID;
1307 suggestionInfo.suggestionStart = 0;
1308 suggestionInfo.suggestionEnd = suggestion.length();
1309 suggestionInfo.spanStart = suggestionList.start;
1310 suggestionInfo.spanEnd = suggestionList.end;
1311 suggestionInfo.suggestionIndex = suggestionIndex;
1312 suggestionInfo.suggestion = suggestion;
1313 suggestionInfos.push_back(suggestionInfo);
1314 if (suggestionInfos.size() == kMaxNumberSuggestions)
1315 break;
1316 }
1317 }
1318
1319 if (suggestionInfos.size() == 0)
1320 return WebVector<blink::WebTextSuggestionInfo>();
1321
1322 int spanUnionStart = suggestionInfos[0].spanStart;
1323 int spanUnionEnd = suggestionInfos[0].spanEnd;
1324
1325 for (size_t i = 1; i < suggestionInfos.size(); i++) {
1326 spanUnionStart = std::min(spanUnionStart, suggestionInfos[i].spanStart);
1327 spanUnionEnd = std::max(spanUnionEnd, suggestionInfos[i].spanEnd);
1328 }
1329
1330 for (WebTextSuggestionInfo& info : suggestionInfos) {
1331 const EphemeralRange& prefixRange =
1332 PlainTextRange(spanUnionStart, info.spanStart)
1333 .createRange(*rootEditableElement);
1334 String prefix = plainText(prefixRange, TextIteratorEmitsOriginalText);
1335
1336 const EphemeralRange& suffixRange =
1337 PlainTextRange(info.spanEnd, spanUnionEnd)
1338 .createRange(*rootEditableElement);
1339 String suffix = plainText(suffixRange, TextIteratorEmitsOriginalText);
1340
1341 info.prefix = prefix.utf8().data();
1342 info.suffix = suffix.utf8().data();
1343 }
1344
1345 Color highlightColor;
1346 if (suggestionMarkers[0]->underlineColor() != 0) {
1347 highlightColor = suggestionMarkers[0]->underlineColor().combineWithAlpha(
1348 kSuggestionUnderlineAlphaMultiplier);
1349 } else {
1350 highlightColor = LayoutTheme::tapHighlightColor();
1351 }
1352
1353 EphemeralRange backgroundHighlightRange =
1354 PlainTextRange(spanUnionStart, spanUnionEnd)
1355 .createRange(*rootEditableElement);
1356 document().markers().addSuggestionBackgroundHighlightMarker(
1357 backgroundHighlightRange.startPosition(),
1358 backgroundHighlightRange.endPosition(), Color::black, false,
1359 highlightColor);
1360
1361 return suggestionInfos;
1362 }
1363
1364 void InputMethodController::prepareForTextSuggestionMenuToBeShown() {
1365 frame().selection().setCaretVisible(false);
1366 }
1367
1064 void InputMethodController::willChangeFocus() { 1368 void InputMethodController::willChangeFocus() {
1065 if (!finishComposingText(DoNotKeepSelection)) 1369 if (!finishComposingText(DoNotKeepSelection))
1066 return; 1370 return;
1067 frame().chromeClient().resetInputMethod(); 1371 frame().chromeClient().resetInputMethod();
1068 } 1372 }
1069 1373
1070 DEFINE_TRACE(InputMethodController) { 1374 DEFINE_TRACE(InputMethodController) {
1071 visitor->trace(m_frame); 1375 visitor->trace(m_frame);
1072 visitor->trace(m_compositionRange); 1376 visitor->trace(m_compositionRange);
1073 SynchronousMutationObserver::trace(visitor); 1377 SynchronousMutationObserver::trace(visitor);
1074 } 1378 }
1075 1379
1076 } // namespace blink 1380 } // namespace blink
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698