Chromium Code Reviews| 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 58 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 69 } | 69 } |
| 70 | 70 |
| 71 // Used to insert/replace text during composition update and confirm | 71 // Used to insert/replace text during composition update and confirm |
| 72 // composition. | 72 // composition. |
| 73 // Procedure: | 73 // Procedure: |
| 74 // 1. Fire 'beforeinput' event for (TODO(chongz): deleted composed text) and | 74 // 1. Fire 'beforeinput' event for (TODO(chongz): deleted composed text) and |
| 75 // inserted text | 75 // inserted text |
| 76 // 2. Fire 'compositionupdate' event | 76 // 2. Fire 'compositionupdate' event |
| 77 // 3. Fire TextEvent and modify DOM | 77 // 3. Fire TextEvent and modify DOM |
| 78 // TODO(chongz): 4. Fire 'input' event | 78 // TODO(chongz): 4. Fire 'input' event |
| 79 void insertTextDuringCompositionWithEvents( | 79 void insertIncrementtalTextDuringCompositionWithEvents( |
| 80 LocalFrame& frame, | 80 LocalFrame& frame, |
| 81 const String& text, | 81 const String& oldText, |
| 82 const String& newText, | |
| 82 TypingCommand::Options options, | 83 TypingCommand::Options options, |
| 83 TypingCommand::TextCompositionType compositionType) { | 84 TypingCommand::TextCompositionType compositionType) { |
| 84 DCHECK(compositionType == | 85 DCHECK(compositionType == |
| 85 TypingCommand::TextCompositionType::TextCompositionUpdate || | 86 TypingCommand::TextCompositionType::TextCompositionUpdate || |
| 86 compositionType == | 87 compositionType == |
| 87 TypingCommand::TextCompositionType::TextCompositionConfirm || | 88 TypingCommand::TextCompositionType::TextCompositionConfirm || |
| 88 compositionType == | 89 compositionType == |
| 89 TypingCommand::TextCompositionType::TextCompositionCancel) | 90 TypingCommand::TextCompositionType::TextCompositionCancel) |
| 90 << "compositionType should be TextCompositionUpdate or " | 91 << "compositionType should be TextCompositionUpdate or " |
| 91 "TextCompositionConfirm or TextCompositionCancel, but got " | 92 "TextCompositionConfirm or TextCompositionCancel, but got " |
| 92 << static_cast<int>(compositionType); | 93 << static_cast<int>(compositionType); |
| 93 if (!frame.document()) | 94 if (!frame.document()) |
| 94 return; | 95 return; |
| 95 | 96 |
| 96 Element* target = frame.document()->focusedElement(); | 97 Element* target = frame.document()->focusedElement(); |
| 97 if (!target) | 98 if (!target) |
| 98 return; | 99 return; |
| 99 | 100 |
| 100 // TODO(chongz): Fire 'beforeinput' for the composed text being | 101 // TODO(chongz): Fire 'beforeinput' for the composed text being |
| 101 // replaced/deleted. | 102 // replaced/deleted. |
| 102 | 103 |
| 103 // Only the last confirmed text is cancelable. | 104 // Only the last confirmed text is cancelable. |
| 104 InputEvent::EventCancelable beforeInputCancelable = | 105 InputEvent::EventCancelable beforeInputCancelable = |
| 105 (compositionType == | 106 (compositionType == |
| 106 TypingCommand::TextCompositionType::TextCompositionUpdate) | 107 TypingCommand::TextCompositionType::TextCompositionUpdate) |
| 107 ? InputEvent::EventCancelable::NotCancelable | 108 ? InputEvent::EventCancelable::NotCancelable |
| 108 : InputEvent::EventCancelable::IsCancelable; | 109 : InputEvent::EventCancelable::IsCancelable; |
| 109 DispatchEventResult result = dispatchBeforeInputFromComposition( | 110 DispatchEventResult result = dispatchBeforeInputFromComposition( |
| 110 target, InputEvent::InputType::InsertText, text, beforeInputCancelable); | 111 target, InputEvent::InputType::InsertText, newText, |
| 112 beforeInputCancelable); | |
| 111 | 113 |
| 112 if (beforeInputCancelable == InputEvent::EventCancelable::IsCancelable && | 114 if (beforeInputCancelable == InputEvent::EventCancelable::IsCancelable && |
| 113 result != DispatchEventResult::NotCanceled) | 115 result != DispatchEventResult::NotCanceled) |
| 114 return; | 116 return; |
| 115 | 117 |
| 116 // 'beforeinput' event handler may destroy document. | 118 // 'beforeinput' event handler may destroy document. |
| 117 if (!frame.document()) | 119 if (!frame.document()) |
| 118 return; | 120 return; |
| 119 | 121 |
| 120 dispatchCompositionUpdateEvent(frame, text); | 122 dispatchCompositionUpdateEvent(frame, newText); |
| 121 // 'compositionupdate' event handler may destroy document. | 123 // 'compositionupdate' event handler may destroy document. |
| 122 if (!frame.document()) | 124 if (!frame.document()) |
| 123 return; | 125 return; |
| 124 | 126 |
| 125 switch (compositionType) { | 127 switch (compositionType) { |
| 126 case TypingCommand::TextCompositionType::TextCompositionUpdate: | 128 case TypingCommand::TextCompositionType::TextCompositionUpdate: |
| 127 TypingCommand::insertText(*frame.document(), text, options, | 129 TypingCommand::insertIncrementalText(*frame.document(), oldText, newText, |
|
chongz
2016/11/28 16:01:04
Is it possible to utilize |FrameSelection::selecte
yabinh
2016/11/29 02:15:21
Here, oldText refers to the previous composing tex
yabinh
2016/12/02 08:41:55
It turns out to be possible after a workaround. Se
| |
| 128 compositionType); | 130 options, compositionType); |
| 129 break; | 131 break; |
| 130 case TypingCommand::TextCompositionType::TextCompositionConfirm: | 132 case TypingCommand::TextCompositionType::TextCompositionConfirm: |
| 133 // When there is previous composition, and call commitText() | |
| 134 // (or setComposition()) with empty text, |newText| will be empty. In that | |
| 135 // case, we should do nothing to avoid firing additional events. | |
| 136 if (newText.length()) { | |
| 137 TypingCommand::insertIncrementalText(*frame.document(), oldText, | |
| 138 newText, options, compositionType); | |
| 139 } | |
| 140 break; | |
|
chongz
2016/11/28 16:01:04
We cannot simply remove 'textInput' event due to i
yosin_UTC9
2016/11/29 02:10:16
Could you split use counter for 'textInput' into n
chongz
2016/11/29 22:20:58
Adding foolip@ for more input as he is the owner o
foolip
2016/11/30 13:05:28
I added a TODO in TextEvent.idl and a use counter,
| |
| 131 case TypingCommand::TextCompositionType::TextCompositionCancel: | 141 case TypingCommand::TextCompositionType::TextCompositionCancel: |
| 132 // TODO(chongz): Use TypingCommand::insertText after TextEvent was | 142 // TODO(chongz): Use TypingCommand::insertText after TextEvent was |
| 133 // removed. (Removed from spec since 2012) | 143 // removed. (Removed from spec since 2012) |
| 134 // See TextEvent.idl. | 144 // See TextEvent.idl. |
| 135 frame.eventHandler().handleTextInputEvent(text, 0, | 145 frame.eventHandler().handleTextInputEvent(newText, 0, |
| 136 TextEventInputComposition); | 146 TextEventInputComposition); |
| 137 break; | 147 break; |
| 138 default: | 148 default: |
| 139 NOTREACHED(); | 149 NOTREACHED(); |
| 140 } | 150 } |
| 141 // TODO(chongz): Fire 'input' event. | 151 // TODO(chongz): Fire 'input' event. |
| 142 } | 152 } |
| 143 | 153 |
| 144 AtomicString getInputModeAttribute(Element* element) { | 154 AtomicString getInputModeAttribute(Element* element) { |
| 145 if (!element) | 155 if (!element) |
| (...skipping 18 matching lines...) Expand all Loading... | |
| 164 return element->fastGetAttribute(HTMLNames::inputmodeAttr).lower(); | 174 return element->fastGetAttribute(HTMLNames::inputmodeAttr).lower(); |
| 165 } | 175 } |
| 166 | 176 |
| 167 } // anonymous namespace | 177 } // anonymous namespace |
| 168 | 178 |
| 169 InputMethodController* InputMethodController::create(LocalFrame& frame) { | 179 InputMethodController* InputMethodController::create(LocalFrame& frame) { |
| 170 return new InputMethodController(frame); | 180 return new InputMethodController(frame); |
| 171 } | 181 } |
| 172 | 182 |
| 173 InputMethodController::InputMethodController(LocalFrame& frame) | 183 InputMethodController::InputMethodController(LocalFrame& frame) |
| 174 : m_frame(&frame), m_isDirty(false), m_hasComposition(false) {} | 184 : m_frame(&frame), m_hasComposition(false) {} |
| 175 | 185 |
| 176 InputMethodController::~InputMethodController() = default; | 186 InputMethodController::~InputMethodController() = default; |
| 177 | 187 |
| 178 bool InputMethodController::isAvailable() const { | 188 bool InputMethodController::isAvailable() const { |
| 179 return frame().document(); | 189 return frame().document(); |
| 180 } | 190 } |
| 181 | 191 |
| 182 Document& InputMethodController::document() const { | 192 Document& InputMethodController::document() const { |
| 183 DCHECK(isAvailable()); | 193 DCHECK(isAvailable()); |
| 184 return *frame().document(); | 194 return *frame().document(); |
| 185 } | 195 } |
| 186 | 196 |
| 187 bool InputMethodController::hasComposition() const { | 197 bool InputMethodController::hasComposition() const { |
| 188 return m_hasComposition && !m_compositionRange->collapsed() && | 198 return m_hasComposition && !m_compositionRange->collapsed() && |
| 189 m_compositionRange->isConnected(); | 199 m_compositionRange->isConnected(); |
| 190 } | 200 } |
| 191 | 201 |
| 192 inline Editor& InputMethodController::editor() const { | 202 inline Editor& InputMethodController::editor() const { |
| 193 return frame().editor(); | 203 return frame().editor(); |
| 194 } | 204 } |
| 195 | 205 |
| 196 void InputMethodController::clear() { | 206 void InputMethodController::clear() { |
| 197 m_hasComposition = false; | 207 m_hasComposition = false; |
| 198 if (m_compositionRange) { | 208 if (m_compositionRange) { |
| 199 m_compositionRange->setStart(&document(), 0); | 209 m_compositionRange->setStart(&document(), 0); |
| 200 m_compositionRange->collapse(true); | 210 m_compositionRange->collapse(true); |
| 201 } | 211 } |
| 202 document().markers().removeMarkers(DocumentMarker::Composition); | 212 document().markers().removeMarkers(DocumentMarker::Composition); |
| 203 m_isDirty = false; | |
| 204 } | 213 } |
| 205 | 214 |
| 206 void InputMethodController::contextDestroyed() { | 215 void InputMethodController::contextDestroyed() { |
| 207 clear(); | 216 clear(); |
| 208 m_compositionRange = nullptr; | 217 m_compositionRange = nullptr; |
| 209 } | 218 } |
| 210 | 219 |
| 211 void InputMethodController::documentAttached(Document* document) { | 220 void InputMethodController::documentAttached(Document* document) { |
| 212 DCHECK(document); | 221 DCHECK(document); |
| 213 setContext(document); | 222 setContext(document); |
| (...skipping 48 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 262 // duplicate selection change event. | 271 // duplicate selection change event. |
| 263 if (!text.length() && !relativeCaretPosition) | 272 if (!text.length() && !relativeCaretPosition) |
| 264 return false; | 273 return false; |
| 265 return insertTextAndMoveCaret(text, relativeCaretPosition); | 274 return insertTextAndMoveCaret(text, relativeCaretPosition); |
| 266 } | 275 } |
| 267 | 276 |
| 268 bool InputMethodController::replaceComposition(const String& text) { | 277 bool InputMethodController::replaceComposition(const String& text) { |
| 269 if (!hasComposition()) | 278 if (!hasComposition()) |
| 270 return false; | 279 return false; |
| 271 | 280 |
| 272 // If the composition was set from existing text and didn't change, then | |
| 273 // there's nothing to do here (and we should avoid doing anything as that | |
| 274 // may clobber multi-node styled text). | |
| 275 if (!m_isDirty && composingText() == text) { | |
| 276 clear(); | |
| 277 return true; | |
| 278 } | |
| 279 | |
| 280 // Select the text that will be deleted or replaced. | 281 // Select the text that will be deleted or replaced. |
| 281 selectComposition(); | 282 selectComposition(); |
| 282 | 283 |
| 283 if (frame().selection().isNone()) | 284 if (frame().selection().isNone()) |
| 284 return false; | 285 return false; |
| 285 | 286 |
| 286 if (!isAvailable()) | 287 if (!isAvailable()) |
| 287 return false; | 288 return false; |
| 288 | 289 |
| 289 // If text is empty, then delete the old composition here. If text is | 290 // If text is empty, then delete the old composition here. If text is |
| 290 // non-empty, InsertTextCommand::input will delete the old composition with | 291 // non-empty, InsertTextCommand::input will delete the old composition with |
| 291 // an optimized replace operation. | 292 // an optimized replace operation. |
| 292 if (text.isEmpty()) | 293 if (text.isEmpty()) { |
| 293 TypingCommand::deleteSelection(document(), 0); | 294 TypingCommand::deleteSelection(document(), 0); |
| 294 | 295 |
| 296 // TODO(xiaochengh): The use of updateStyleAndLayoutIgnorePendingStylesheets | |
| 297 // needs to be audited. see http://crbug.com/590369 for more details. | |
| 298 document().updateStyleAndLayoutIgnorePendingStylesheets(); | |
| 299 } | |
| 300 | |
| 301 const String& composing = composingText(); | |
| 295 clear(); | 302 clear(); |
| 296 | 303 |
| 297 insertTextDuringCompositionWithEvents( | 304 insertIncrementtalTextDuringCompositionWithEvents( |
| 298 frame(), text, 0, | 305 frame(), composing, text, 0, |
| 299 TypingCommand::TextCompositionType::TextCompositionConfirm); | 306 TypingCommand::TextCompositionType::TextCompositionConfirm); |
| 300 // Event handler might destroy document. | 307 // Event handler might destroy document. |
| 301 if (!isAvailable()) | 308 if (!isAvailable()) |
| 302 return false; | 309 return false; |
| 303 | 310 |
| 304 return true; | 311 return true; |
| 305 } | 312 } |
| 306 | 313 |
| 307 // relativeCaretPosition is relative to the end of the text. | 314 // relativeCaretPosition is relative to the end of the text. |
| 308 static int computeAbsoluteCaretPosition(size_t textStart, | 315 static int computeAbsoluteCaretPosition(size_t textStart, |
| (...skipping 64 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 373 return; | 380 return; |
| 374 | 381 |
| 375 clear(); | 382 clear(); |
| 376 | 383 |
| 377 // TODO(chongz): Figure out which InputType should we use here. | 384 // TODO(chongz): Figure out which InputType should we use here. |
| 378 dispatchBeforeInputFromComposition( | 385 dispatchBeforeInputFromComposition( |
| 379 document().focusedElement(), | 386 document().focusedElement(), |
| 380 InputEvent::InputType::DeleteComposedCharacterBackward, nullAtom, | 387 InputEvent::InputType::DeleteComposedCharacterBackward, nullAtom, |
| 381 InputEvent::EventCancelable::NotCancelable); | 388 InputEvent::EventCancelable::NotCancelable); |
| 382 dispatchCompositionUpdateEvent(frame(), emptyString()); | 389 dispatchCompositionUpdateEvent(frame(), emptyString()); |
| 383 insertTextDuringCompositionWithEvents( | 390 insertIncrementtalTextDuringCompositionWithEvents( |
| 384 frame(), emptyString(), 0, | 391 frame(), emptyString(), emptyString(), 0, |
| 385 TypingCommand::TextCompositionType::TextCompositionCancel); | 392 TypingCommand::TextCompositionType::TextCompositionCancel); |
| 386 // Event handler might destroy document. | 393 // Event handler might destroy document. |
| 387 if (!isAvailable()) | 394 if (!isAvailable()) |
| 388 return; | 395 return; |
| 389 | 396 |
| 390 // An open typing command that disagrees about current selection would cause | 397 // An open typing command that disagrees about current selection would cause |
| 391 // issues with typing later on. | 398 // issues with typing later on. |
| 392 TypingCommand::closeTyping(m_frame); | 399 TypingCommand::closeTyping(m_frame); |
| 393 | 400 |
| 394 // No DOM update after 'compositionend'. | 401 // No DOM update after 'compositionend'. |
| 395 dispatchCompositionEndEvent(frame(), emptyString()); | 402 dispatchCompositionEndEvent(frame(), emptyString()); |
| 396 } | 403 } |
| 397 | 404 |
| 398 void InputMethodController::cancelCompositionIfSelectionIsInvalid() { | 405 void InputMethodController::cancelCompositionIfSelectionIsInvalid() { |
| 399 if (!hasComposition() || editor().preventRevealSelection()) | 406 if (!hasComposition() || editor().preventRevealSelection()) |
| 400 return; | 407 return; |
| 401 | 408 |
| 402 // Check if selection start and selection end are valid. | 409 // Check if selection start and selection end are valid. |
| 403 FrameSelection& selection = frame().selection(); | 410 FrameSelection& selection = frame().selection(); |
| 404 if (!selection.isNone() && !m_compositionRange->collapsed()) { | 411 if (!selection.isNone() && !m_compositionRange->collapsed()) { |
| 405 if (selection.start().compareTo(m_compositionRange->startPosition()) >= 0 && | 412 if (selection.start().compareTo(m_compositionRange->startPosition()) >= 0 && |
| 406 selection.end().compareTo(m_compositionRange->endPosition()) <= 0) | 413 selection.end().compareTo(m_compositionRange->endPosition()) <= 0) |
| 407 return; | 414 return; |
| 408 } | 415 } |
| 409 | 416 |
| 410 cancelComposition(); | 417 cancelComposition(); |
| 411 frame().chromeClient().didCancelCompositionOnSelectionChange(); | 418 frame().chromeClient().didCancelCompositionOnSelectionChange(); |
| 412 } | 419 } |
| 413 | 420 |
| 414 static size_t computeCommonPrefixLength(const String& str1, | |
| 415 const String& str2) { | |
| 416 const size_t maxCommonPrefixLength = std::min(str1.length(), str2.length()); | |
| 417 for (size_t index = 0; index < maxCommonPrefixLength; ++index) { | |
| 418 if (str1[index] != str2[index]) | |
| 419 return index; | |
| 420 } | |
| 421 return maxCommonPrefixLength; | |
| 422 } | |
| 423 | |
| 424 static size_t computeCommonSuffixLength(const String& str1, | |
| 425 const String& str2) { | |
| 426 const size_t length1 = str1.length(); | |
| 427 const size_t length2 = str2.length(); | |
| 428 const size_t maxCommonSuffixLength = std::min(length1, length2); | |
| 429 for (size_t index = 0; index < maxCommonSuffixLength; ++index) { | |
| 430 if (str1[length1 - index - 1] != str2[length2 - index - 1]) | |
| 431 return index; | |
| 432 } | |
| 433 return maxCommonSuffixLength; | |
| 434 } | |
| 435 | |
| 436 // If current position is at grapheme boundary, return 0; otherwise, return the | 421 // If current position is at grapheme boundary, return 0; otherwise, return the |
| 437 // distance to its nearest left grapheme boundary. | 422 // distance to its nearest left grapheme boundary. |
| 438 static size_t computeDistanceToLeftGraphemeBoundary(const Position& position) { | 423 static size_t computeDistanceToLeftGraphemeBoundary(const Position& position) { |
| 439 const Position& adjustedPosition = previousPositionOf( | 424 const Position& adjustedPosition = previousPositionOf( |
| 440 nextPositionOf(position, PositionMoveType::GraphemeCluster), | 425 nextPositionOf(position, PositionMoveType::GraphemeCluster), |
| 441 PositionMoveType::GraphemeCluster); | 426 PositionMoveType::GraphemeCluster); |
| 442 DCHECK_EQ(position.anchorNode(), adjustedPosition.anchorNode()); | 427 DCHECK_EQ(position.anchorNode(), adjustedPosition.anchorNode()); |
| 443 DCHECK_GE(position.computeOffsetInContainerNode(), | 428 DCHECK_GE(position.computeOffsetInContainerNode(), |
| 444 adjustedPosition.computeOffsetInContainerNode()); | 429 adjustedPosition.computeOffsetInContainerNode()); |
| 445 return static_cast<size_t>(position.computeOffsetInContainerNode() - | 430 return static_cast<size_t>(position.computeOffsetInContainerNode() - |
| 446 adjustedPosition.computeOffsetInContainerNode()); | 431 adjustedPosition.computeOffsetInContainerNode()); |
| 447 } | 432 } |
| 448 | 433 |
| 449 static size_t computeCommonGraphemeClusterPrefixLengthForSetComposition( | |
| 450 const String& oldText, | |
| 451 const String& newText, | |
| 452 const Element* rootEditableElement) { | |
| 453 const size_t commonPrefixLength = computeCommonPrefixLength(oldText, newText); | |
| 454 | |
| 455 // For grapheme cluster, we should adjust it for grapheme boundary. | |
| 456 const EphemeralRange& range = | |
| 457 PlainTextRange(0, commonPrefixLength).createRange(*rootEditableElement); | |
| 458 if (range.isNull()) | |
| 459 return 0; | |
| 460 const Position& position = range.endPosition(); | |
| 461 const size_t diff = computeDistanceToLeftGraphemeBoundary(position); | |
| 462 DCHECK_GE(commonPrefixLength, diff); | |
| 463 return commonPrefixLength - diff; | |
| 464 } | |
| 465 | |
| 466 // If current position is at grapheme boundary, return 0; otherwise, return the | 434 // If current position is at grapheme boundary, return 0; otherwise, return the |
| 467 // distance to its nearest right grapheme boundary. | 435 // distance to its nearest right grapheme boundary. |
| 468 static size_t computeDistanceToRightGraphemeBoundary(const Position& position) { | 436 static size_t computeDistanceToRightGraphemeBoundary(const Position& position) { |
| 469 const Position& adjustedPosition = nextPositionOf( | 437 const Position& adjustedPosition = nextPositionOf( |
| 470 previousPositionOf(position, PositionMoveType::GraphemeCluster), | 438 previousPositionOf(position, PositionMoveType::GraphemeCluster), |
| 471 PositionMoveType::GraphemeCluster); | 439 PositionMoveType::GraphemeCluster); |
| 472 DCHECK_EQ(position.anchorNode(), adjustedPosition.anchorNode()); | 440 DCHECK_EQ(position.anchorNode(), adjustedPosition.anchorNode()); |
| 473 DCHECK_GE(adjustedPosition.computeOffsetInContainerNode(), | 441 DCHECK_GE(adjustedPosition.computeOffsetInContainerNode(), |
| 474 position.computeOffsetInContainerNode()); | 442 position.computeOffsetInContainerNode()); |
| 475 return static_cast<size_t>(adjustedPosition.computeOffsetInContainerNode() - | 443 return static_cast<size_t>(adjustedPosition.computeOffsetInContainerNode() - |
| 476 position.computeOffsetInContainerNode()); | 444 position.computeOffsetInContainerNode()); |
| 477 } | 445 } |
| 478 | 446 |
| 479 static size_t computeCommonGraphemeClusterSuffixLengthForSetComposition( | |
| 480 const String& oldText, | |
| 481 const String& newText, | |
| 482 const Element* rootEditableElement) { | |
| 483 const size_t commonSuffixLength = computeCommonSuffixLength(oldText, newText); | |
| 484 | |
| 485 // For grapheme cluster, we should adjust it for grapheme boundary. | |
| 486 const EphemeralRange& range = | |
| 487 PlainTextRange(0, oldText.length() - commonSuffixLength) | |
| 488 .createRange(*rootEditableElement); | |
| 489 if (range.isNull()) | |
| 490 return 0; | |
| 491 const Position& position = range.endPosition(); | |
| 492 const size_t diff = computeDistanceToRightGraphemeBoundary(position); | |
| 493 DCHECK_GE(commonSuffixLength, diff); | |
| 494 return commonSuffixLength - diff; | |
| 495 } | |
| 496 | |
| 497 void InputMethodController::setCompositionWithIncrementalText( | |
| 498 const String& text, | |
| 499 const Vector<CompositionUnderline>& underlines, | |
| 500 int selectionStart, | |
| 501 int selectionEnd) { | |
| 502 Element* editable = frame().selection().rootEditableElement(); | |
| 503 if (!editable) | |
| 504 return; | |
| 505 | |
| 506 DCHECK_LE(selectionStart, selectionEnd); | |
| 507 String composing = composingText(); | |
| 508 const size_t commonPrefixLength = | |
| 509 computeCommonGraphemeClusterPrefixLengthForSetComposition(composing, text, | |
| 510 editable); | |
| 511 | |
| 512 // We should ignore common prefix when finding common suffix. | |
| 513 const size_t commonSuffixLength = | |
| 514 computeCommonGraphemeClusterSuffixLengthForSetComposition( | |
| 515 composing.right(composing.length() - commonPrefixLength), | |
| 516 text.right(text.length() - commonPrefixLength), editable); | |
| 517 | |
| 518 const bool inserting = | |
| 519 text.length() > commonPrefixLength + commonSuffixLength; | |
| 520 const bool deleting = | |
| 521 composing.length() > commonPrefixLength + commonSuffixLength; | |
| 522 | |
| 523 if (inserting || deleting) { | |
| 524 // Select the text to be deleted. | |
| 525 const size_t compositionStart = | |
| 526 PlainTextRange::create(*editable, compositionEphemeralRange()).start(); | |
| 527 const size_t deletionStart = compositionStart + commonPrefixLength; | |
| 528 const size_t deletionEnd = | |
| 529 compositionStart + composing.length() - commonSuffixLength; | |
| 530 const EphemeralRange& deletionRange = | |
| 531 PlainTextRange(deletionStart, deletionEnd).createRange(*editable); | |
| 532 Document& currentDocument = document(); | |
| 533 frame().selection().setSelection( | |
| 534 SelectionInDOMTree::Builder().setBaseAndExtent(deletionRange).build(), | |
| 535 0); | |
| 536 clear(); | |
| 537 | |
| 538 // FrameSeleciton::setSelection() can change document associate to |frame|. | |
| 539 if (!isAvailable() || currentDocument != document()) | |
| 540 return; | |
| 541 if (!currentDocument.focusedElement()) | |
| 542 return; | |
| 543 | |
| 544 // Insert the incremental text. | |
| 545 const size_t insertionLength = | |
| 546 text.length() - commonPrefixLength - commonSuffixLength; | |
| 547 const String& insertingText = | |
| 548 text.substring(commonPrefixLength, insertionLength); | |
| 549 insertTextDuringCompositionWithEvents(frame(), insertingText, | |
| 550 TypingCommand::PreventSpellChecking, | |
| 551 TypingCommand::TextCompositionUpdate); | |
| 552 | |
| 553 // Event handlers might destroy document. | |
| 554 if (!isAvailable() || currentDocument != document()) | |
| 555 return; | |
| 556 | |
| 557 // TODO(yosin): The use of updateStyleAndLayoutIgnorePendingStylesheets | |
| 558 // needs to be audited. see http://crbug.com/590369 for more details. | |
| 559 document().updateStyleAndLayoutIgnorePendingStylesheets(); | |
| 560 | |
| 561 // Now recreate the composition starting at its original start, and | |
| 562 // apply the specified final selection offsets. | |
| 563 setCompositionFromExistingText(underlines, compositionStart, | |
| 564 compositionStart + text.length()); | |
| 565 } | |
| 566 | |
| 567 selectComposition(); | |
| 568 | |
| 569 // TODO(xiaochengh): The use of updateStyleAndLayoutIgnorePendingStylesheets | |
| 570 // needs to be audited. see http://crbug.com/590369 for more details. | |
| 571 document().updateStyleAndLayoutIgnorePendingStylesheets(); | |
| 572 | |
| 573 const PlainTextRange& selectedRange = createSelectionRangeForSetComposition( | |
| 574 selectionStart, selectionEnd, text.length()); | |
| 575 // We shouldn't close typing in the middle of setComposition. | |
| 576 setEditableSelectionOffsets(selectedRange, NotUserTriggered); | |
| 577 m_isDirty = true; | |
| 578 } | |
| 579 | |
| 580 void InputMethodController::setComposition( | 447 void InputMethodController::setComposition( |
| 581 const String& text, | 448 const String& text, |
| 582 const Vector<CompositionUnderline>& underlines, | 449 const Vector<CompositionUnderline>& underlines, |
| 583 int selectionStart, | 450 int selectionStart, |
| 584 int selectionEnd) { | 451 int selectionEnd) { |
| 585 Editor::RevealSelectionScope revealSelectionScope(&editor()); | 452 Editor::RevealSelectionScope revealSelectionScope(&editor()); |
| 586 | 453 |
| 587 // Updates styles before setting selection for composition to prevent | 454 // Updates styles before setting selection for composition to prevent |
| 588 // inserting the previous composition text into text nodes oddly. | 455 // inserting the previous composition text into text nodes oddly. |
| 589 // See https://bugs.webkit.org/show_bug.cgi?id=46868 | 456 // See https://bugs.webkit.org/show_bug.cgi?id=46868 |
| 590 document().updateStyleAndLayoutTree(); | 457 document().updateStyleAndLayoutTree(); |
| 591 | 458 |
| 592 // When the IME only wants to change a few characters at the end of the | |
| 593 // composition, only touch those characters in order to preserve rich text | |
| 594 // substructure. | |
| 595 if (hasComposition() && text.length()) { | |
| 596 return setCompositionWithIncrementalText(text, underlines, selectionStart, | |
| 597 selectionEnd); | |
| 598 } | |
| 599 | |
| 600 selectComposition(); | 459 selectComposition(); |
| 601 | 460 |
| 602 if (frame().selection().isNone()) | 461 if (frame().selection().isNone()) |
| 603 return; | 462 return; |
| 604 | 463 |
| 605 Element* target = document().focusedElement(); | 464 Element* target = document().focusedElement(); |
| 606 if (!target) | 465 if (!target) |
| 607 return; | 466 return; |
| 608 | 467 |
| 609 // TODO(xiaochengh): The use of updateStyleAndLayoutIgnorePendingStylesheets | 468 // TODO(xiaochengh): The use of updateStyleAndLayoutIgnorePendingStylesheets |
| (...skipping 46 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 656 if (!hasComposition()) { | 515 if (!hasComposition()) { |
| 657 target->dispatchEvent( | 516 target->dispatchEvent( |
| 658 CompositionEvent::create(EventTypeNames::compositionstart, | 517 CompositionEvent::create(EventTypeNames::compositionstart, |
| 659 frame().domWindow(), frame().selectedText())); | 518 frame().domWindow(), frame().selectedText())); |
| 660 if (!isAvailable()) | 519 if (!isAvailable()) |
| 661 return; | 520 return; |
| 662 } | 521 } |
| 663 | 522 |
| 664 DCHECK(!text.isEmpty()); | 523 DCHECK(!text.isEmpty()); |
| 665 | 524 |
| 525 const String& composing = composingText(); | |
| 666 clear(); | 526 clear(); |
| 667 | 527 |
| 668 insertTextDuringCompositionWithEvents( | 528 insertIncrementtalTextDuringCompositionWithEvents( |
| 669 frame(), text, | 529 frame(), composing, text, |
| 670 TypingCommand::SelectInsertedText | TypingCommand::PreventSpellChecking, | 530 TypingCommand::SelectInsertedText | TypingCommand::PreventSpellChecking, |
| 671 TypingCommand::TextCompositionUpdate); | 531 TypingCommand::TextCompositionUpdate); |
| 672 // Event handlers might destroy document. | 532 // Event handlers might destroy document. |
| 673 if (!isAvailable()) | 533 if (!isAvailable()) |
| 674 return; | 534 return; |
| 675 | 535 |
| 676 // TODO(yosin): The use of updateStyleAndLayoutIgnorePendingStylesheets | 536 // TODO(yosin): The use of updateStyleAndLayoutIgnorePendingStylesheets |
| 677 // needs to be audited. see http://crbug.com/590369 for more details. | 537 // needs to be audited. see http://crbug.com/590369 for more details. |
| 678 document().updateStyleAndLayoutIgnorePendingStylesheets(); | 538 document().updateStyleAndLayoutIgnorePendingStylesheets(); |
| 679 | 539 |
| 680 // Find out what node has the composition now. | 540 // Find out what node has the composition now. |
| 681 Position base = mostForwardCaretPosition(frame().selection().base()); | 541 Position base = mostForwardCaretPosition(frame().selection().base()); |
| 682 Node* baseNode = base.anchorNode(); | 542 Node* baseNode = base.anchorNode(); |
| 683 if (!baseNode || !baseNode->isTextNode()) | 543 if (!baseNode || !baseNode->isTextNode()) |
| 684 return; | 544 return; |
| 685 | 545 |
| 686 Position extent = frame().selection().extent(); | 546 Position extent = frame().selection().extent(); |
| 687 Node* extentNode = extent.anchorNode(); | 547 Node* extentNode = extent.anchorNode(); |
| 688 if (baseNode != extentNode) | |
| 689 return; | |
| 690 | 548 |
| 691 unsigned extentOffset = extent.computeOffsetInContainerNode(); | 549 unsigned extentOffset = extent.computeOffsetInContainerNode(); |
| 692 unsigned baseOffset = base.computeOffsetInContainerNode(); | 550 unsigned baseOffset = base.computeOffsetInContainerNode(); |
| 693 if (baseOffset + text.length() != extentOffset) | |
| 694 return; | |
| 695 | 551 |
| 696 m_isDirty = true; | |
| 697 m_hasComposition = true; | 552 m_hasComposition = true; |
| 698 if (!m_compositionRange) | 553 if (!m_compositionRange) |
| 699 m_compositionRange = Range::create(document()); | 554 m_compositionRange = Range::create(document()); |
| 700 m_compositionRange->setStart(baseNode, baseOffset); | 555 m_compositionRange->setStart(baseNode, baseOffset); |
| 701 m_compositionRange->setEnd(baseNode, extentOffset); | 556 m_compositionRange->setEnd(extentNode, extentOffset); |
| 702 | 557 |
| 703 if (baseNode->layoutObject()) | 558 if (baseNode->layoutObject()) |
| 704 baseNode->layoutObject()->setShouldDoFullPaintInvalidation(); | 559 baseNode->layoutObject()->setShouldDoFullPaintInvalidation(); |
| 705 | 560 |
| 706 // TODO(xiaochengh): The use of updateStyleAndLayoutIgnorePendingStylesheets | 561 // TODO(xiaochengh): The use of updateStyleAndLayoutIgnorePendingStylesheets |
| 707 // needs to be audited. see http://crbug.com/590369 for more details. | 562 // needs to be audited. see http://crbug.com/590369 for more details. |
| 708 document().updateStyleAndLayoutIgnorePendingStylesheets(); | 563 document().updateStyleAndLayoutIgnorePendingStylesheets(); |
| 709 | 564 |
| 710 // We shouldn't close typing in the middle of setComposition. | 565 // We shouldn't close typing in the middle of setComposition. |
| 711 setEditableSelectionOffsets(selectedRange, NotUserTriggered); | 566 setEditableSelectionOffsets(selectedRange, NotUserTriggered); |
| (...skipping 481 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 1193 return WebTextInputTypeNone; | 1048 return WebTextInputTypeNone; |
| 1194 } | 1049 } |
| 1195 | 1050 |
| 1196 DEFINE_TRACE(InputMethodController) { | 1051 DEFINE_TRACE(InputMethodController) { |
| 1197 visitor->trace(m_frame); | 1052 visitor->trace(m_frame); |
| 1198 visitor->trace(m_compositionRange); | 1053 visitor->trace(m_compositionRange); |
| 1199 SynchronousMutationObserver::trace(visitor); | 1054 SynchronousMutationObserver::trace(visitor); |
| 1200 } | 1055 } |
| 1201 | 1056 |
| 1202 } // namespace blink | 1057 } // namespace blink |
| OLD | NEW |