OLD | NEW |
---|---|
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 Loading... | |
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 Loading... | |
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 Loading... | |
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 Loading... | |
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 Loading... | |
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 Loading... | |
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 Loading... | |
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 |
OLD | NEW |