1 /* 2 * Copyright (C) 2006, 2007, 2008, 2011 Apple Inc. All rights reserved. 3 * Copyright (C) 2008 Nokia Corporation and/or its subsidiary(-ies) 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions 7 * are met: 8 * 1. Redistributions of source code must retain the above copyright 9 * notice, this list of conditions and the following disclaimer. 10 * 2. Redistributions in binary form must reproduce the above copyright 11 * notice, this list of conditions and the following disclaimer in the 12 * documentation and/or other materials provided with the distribution. 13 * 14 * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY 15 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 17 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR 18 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 19 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 20 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 21 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY 22 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 23 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 24 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 */ 26 27 #include "config.h" 28 #include "core/editing/InputMethodController.h" 29 30 #include "core/events/CompositionEvent.h" 31 #include "core/dom/Document.h" 32 #include "core/dom/Element.h" 33 #include "core/dom/Range.h" 34 #include "core/dom/Text.h" 35 #include "core/editing/Editor.h" 36 #include "core/editing/TypingCommand.h" 37 #include "core/frame/LocalFrame.h" 38 #include "core/html/HTMLTextAreaElement.h" 39 #include "core/page/Chrome.h" 40 #include "core/page/ChromeClient.h" 41 #include "core/page/EventHandler.h" 42 #include "core/rendering/RenderObject.h" 43 44 namespace blink { 45 46 InputMethodController::SelectionOffsetsScope::SelectionOffsetsScope(InputMethodController* inputMethodController) 47 : m_inputMethodController(inputMethodController) 48 , m_offsets(inputMethodController->getSelectionOffsets()) 49 { 50 } 51 52 InputMethodController::SelectionOffsetsScope::~SelectionOffsetsScope() 53 { 54 m_inputMethodController->setSelectionOffsets(m_offsets); 55 } 56 57 // ---------------------------- 58 59 PassOwnPtrWillBeRawPtr<InputMethodController> InputMethodController::create(LocalFrame& frame) 60 { 61 return adoptPtrWillBeNoop(new InputMethodController(frame)); 62 } 63 64 InputMethodController::InputMethodController(LocalFrame& frame) 65 : m_frame(&frame) 66 , m_compositionStart(0) 67 , m_compositionEnd(0) 68 { 69 } 70 71 InputMethodController::~InputMethodController() 72 { 73 } 74 75 bool InputMethodController::hasComposition() const 76 { 77 return m_compositionNode && m_compositionNode->isContentEditable(); 78 } 79 80 inline Editor& InputMethodController::editor() const 81 { 82 return frame().editor(); 83 } 84 85 void InputMethodController::clear() 86 { 87 m_compositionNode = nullptr; 88 m_customCompositionUnderlines.clear(); 89 } 90 91 bool InputMethodController::insertTextForConfirmedComposition(const String& text) 92 { 93 return frame().eventHandler().handleTextInputEvent(text, 0, TextEventInputComposition); 94 } 95 96 void InputMethodController::selectComposition() const 97 { 98 RefPtrWillBeRawPtr<Range> range = compositionRange(); 99 if (!range) 100 return; 101 102 // The composition can start inside a composed character sequence, so we have to override checks. 103 // See <http://bugs.webkit.org/show_bug.cgi?id=15781> 104 VisibleSelection selection; 105 selection.setWithoutValidation(range->startPosition(), range->endPosition()); 106 frame().selection().setSelection(selection, 0); 107 } 108 109 bool InputMethodController::confirmComposition() 110 { 111 if (!hasComposition()) 112 return false; 113 return finishComposition(m_compositionNode->data().substring(m_compositionStart, m_compositionEnd - m_compositionStart), ConfirmComposition); 114 } 115 116 bool InputMethodController::confirmComposition(const String& text) 117 { 118 return finishComposition(text, ConfirmComposition); 119 } 120 121 bool InputMethodController::confirmCompositionOrInsertText(const String& text, ConfirmCompositionBehavior confirmBehavior) 122 { 123 if (!hasComposition()) { 124 if (!text.length()) 125 return false; 126 editor().insertText(text, 0); 127 return true; 128 } 129 130 if (text.length()) { 131 confirmComposition(text); 132 return true; 133 } 134 135 if (confirmBehavior != KeepSelection) 136 return confirmComposition(); 137 138 SelectionOffsetsScope selectionOffsetsScope(this); 139 return confirmComposition(); 140 } 141 142 void InputMethodController::confirmCompositionAndResetState() 143 { 144 if (!hasComposition()) 145 return; 146 147 // ChromeClient::willSetInputMethodState() resets input method and the composition string is committed. 148 frame().chromeClient().willSetInputMethodState(); 149 } 150 151 void InputMethodController::cancelComposition() 152 { 153 finishComposition(emptyString(), CancelComposition); 154 } 155 156 void InputMethodController::cancelCompositionIfSelectionIsInvalid() 157 { 158 if (!hasComposition() || editor().preventRevealSelection()) 159 return; 160 161 // Check if selection start and selection end are valid. 162 Position start = frame().selection().start(); 163 Position end = frame().selection().end(); 164 if (start.containerNode() == m_compositionNode 165 && end.containerNode() == m_compositionNode 166 && static_cast<unsigned>(start.computeOffsetInContainerNode()) >= m_compositionStart 167 && static_cast<unsigned>(end.computeOffsetInContainerNode()) <= m_compositionEnd) 168 return; 169 170 cancelComposition(); 171 frame().chromeClient().didCancelCompositionOnSelectionChange(); 172 } 173 174 bool InputMethodController::finishComposition(const String& text, FinishCompositionMode mode) 175 { 176 if (!hasComposition()) 177 return false; 178 179 ASSERT(mode == ConfirmComposition || mode == CancelComposition); 180 181 Editor::RevealSelectionScope revealSelectionScope(&editor()); 182 183 if (mode == CancelComposition) 184 ASSERT(text == emptyString()); 185 else 186 selectComposition(); 187 188 if (frame().selection().isNone()) 189 return false; 190 191 // Dispatch a compositionend event to the focused node. 192 // We should send this event before sending a TextEvent as written in Section 6.2.2 and 6.2.3 of 193 // the DOM Event specification. 194 if (Element* target = frame().document()->focusedElement()) { 195 unsigned baseOffset = frame().selection().base().downstream().deprecatedEditingOffset(); 196 Vector<CompositionUnderline> underlines; 197 for (size_t i = 0; i < m_customCompositionUnderlines.size(); ++i) { 198 CompositionUnderline underline = m_customCompositionUnderlines[i]; 199 underline.startOffset -= baseOffset; 200 underline.endOffset -= baseOffset; 201 underlines.append(underline); 202 } 203 RefPtrWillBeRawPtr<CompositionEvent> event = CompositionEvent::create(EventTypeNames::compositionend, frame().domWindow(), text, underlines); 204 target->dispatchEvent(event, IGNORE_EXCEPTION); 205 } 206 207 // If text is empty, then delete the old composition here. If text is non-empty, InsertTextCommand::input 208 // will delete the old composition with an optimized replace operation. 209 if (text.isEmpty() && mode != CancelComposition) { 210 ASSERT(frame().document()); 211 TypingCommand::deleteSelection(*frame().document(), 0); 212 } 213 214 m_compositionNode = nullptr; 215 m_customCompositionUnderlines.clear(); 216 217 insertTextForConfirmedComposition(text); 218 219 if (mode == CancelComposition) { 220 // An open typing command that disagrees about current selection would cause issues with typing later on. 221 TypingCommand::closeTyping(m_frame); 222 } 223 224 return true; 225 } 226 227 void InputMethodController::setComposition(const String& text, const Vector<CompositionUnderline>& underlines, unsigned selectionStart, unsigned selectionEnd) 228 { 229 Editor::RevealSelectionScope revealSelectionScope(&editor()); 230 231 // Updates styles before setting selection for composition to prevent 232 // inserting the previous composition text into text nodes oddly. 233 // See https://bugs.webkit.org/show_bug.cgi?id=46868 234 frame().document()->updateRenderTreeIfNeeded(); 235 236 selectComposition(); 237 238 if (frame().selection().isNone()) 239 return; 240 241 if (Element* target = frame().document()->focusedElement()) { 242 // Dispatch an appropriate composition event to the focused node. 243 // We check the composition status and choose an appropriate composition event since this 244 // function is used for three purposes: 245 // 1. Starting a new composition. 246 // Send a compositionstart and a compositionupdate event when this function creates 247 // a new composition node, i.e. 248 // m_compositionNode == 0 && !text.isEmpty(). 249 // Sending a compositionupdate event at this time ensures that at least one 250 // compositionupdate event is dispatched. 251 // 2. Updating the existing composition node. 252 // Send a compositionupdate event when this function updates the existing composition 253 // node, i.e. m_compositionNode != 0 && !text.isEmpty(). 254 // 3. Canceling the ongoing composition. 255 // Send a compositionend event when function deletes the existing composition node, i.e. 256 // m_compositionNode != 0 && test.isEmpty(). 257 RefPtrWillBeRawPtr<CompositionEvent> event = nullptr; 258 if (!hasComposition()) { 259 // We should send a compositionstart event only when the given text is not empty because this 260 // function doesn't create a composition node when the text is empty. 261 if (!text.isEmpty()) { 262 target->dispatchEvent(CompositionEvent::create(EventTypeNames::compositionstart, frame().domWindow(), frame().selectedText(), underlines)); 263 event = CompositionEvent::create(EventTypeNames::compositionupdate, frame().domWindow(), text, underlines); 264 } 265 } else { 266 if (!text.isEmpty()) 267 event = CompositionEvent::create(EventTypeNames::compositionupdate, frame().domWindow(), text, underlines); 268 else 269 event = CompositionEvent::create(EventTypeNames::compositionend, frame().domWindow(), text, underlines); 270 } 271 if (event.get()) 272 target->dispatchEvent(event, IGNORE_EXCEPTION); 273 } 274 275 // If text is empty, then delete the old composition here. If text is non-empty, InsertTextCommand::input 276 // will delete the old composition with an optimized replace operation. 277 if (text.isEmpty()) { 278 ASSERT(frame().document()); 279 TypingCommand::deleteSelection(*frame().document(), TypingCommand::PreventSpellChecking); 280 } 281 282 m_compositionNode = nullptr; 283 m_customCompositionUnderlines.clear(); 284 285 if (!text.isEmpty()) { 286 ASSERT(frame().document()); 287 TypingCommand::insertText(*frame().document(), text, TypingCommand::SelectInsertedText | TypingCommand::PreventSpellChecking, TypingCommand::TextCompositionUpdate); 288 289 // Find out what node has the composition now. 290 Position base = frame().selection().base().downstream(); 291 Position extent = frame().selection().extent(); 292 Node* baseNode = base.deprecatedNode(); 293 unsigned baseOffset = base.deprecatedEditingOffset(); 294 Node* extentNode = extent.deprecatedNode(); 295 unsigned extentOffset = extent.deprecatedEditingOffset(); 296 297 if (baseNode && baseNode == extentNode && baseNode->isTextNode() && baseOffset + text.length() == extentOffset) { 298 m_compositionNode = toText(baseNode); 299 m_compositionStart = baseOffset; 300 m_compositionEnd = extentOffset; 301 m_customCompositionUnderlines = underlines; 302 size_t numUnderlines = m_customCompositionUnderlines.size(); 303 for (size_t i = 0; i < numUnderlines; ++i) { 304 m_customCompositionUnderlines[i].startOffset += baseOffset; 305 m_customCompositionUnderlines[i].endOffset += baseOffset; 306 } 307 if (baseNode->renderer()) 308 baseNode->renderer()->setShouldDoFullPaintInvalidation(true); 309 310 unsigned start = std::min(baseOffset + selectionStart, extentOffset); 311 unsigned end = std::min(std::max(start, baseOffset + selectionEnd), extentOffset); 312 RefPtrWillBeRawPtr<Range> selectedRange = Range::create(baseNode->document(), baseNode, start, baseNode, end); 313 frame().selection().setSelectedRange(selectedRange.get(), DOWNSTREAM, FrameSelection::NonDirectional, NotUserTriggered); 314 } 315 } 316 } 317 318 void InputMethodController::setCompositionFromExistingText(const Vector<CompositionUnderline>& underlines, unsigned compositionStart, unsigned compositionEnd) 319 { 320 Element* editable = frame().selection().rootEditableElement(); 321 Position base = frame().selection().base().downstream(); 322 Node* baseNode = base.anchorNode(); 323 if (editable->firstChild() == baseNode && editable->lastChild() == baseNode && baseNode->isTextNode()) { 324 m_compositionNode = nullptr; 325 m_customCompositionUnderlines.clear(); 326 327 if (base.anchorType() != Position::PositionIsOffsetInAnchor) 328 return; 329 if (!baseNode || baseNode != frame().selection().extent().anchorNode()) 330 return; 331 332 m_compositionNode = toText(baseNode); 333 RefPtrWillBeRawPtr<Range> range = PlainTextRange(compositionStart, compositionEnd).createRange(*editable); 334 if (!range) 335 return; 336 337 m_compositionStart = range->startOffset(); 338 m_compositionEnd = range->endOffset(); 339 m_customCompositionUnderlines = underlines; 340 size_t numUnderlines = m_customCompositionUnderlines.size(); 341 for (size_t i = 0; i < numUnderlines; ++i) { 342 m_customCompositionUnderlines[i].startOffset += m_compositionStart; 343 m_customCompositionUnderlines[i].endOffset += m_compositionStart; 344 } 345 if (baseNode->renderer()) 346 baseNode->renderer()->setShouldDoFullPaintInvalidation(true); 347 return; 348 } 349 350 Editor::RevealSelectionScope revealSelectionScope(&editor()); 351 SelectionOffsetsScope selectionOffsetsScope(this); 352 setSelectionOffsets(PlainTextRange(compositionStart, compositionEnd)); 353 setComposition(frame().selectedText(), underlines, 0, 0); 354 } 355 356 PassRefPtrWillBeRawPtr<Range> InputMethodController::compositionRange() const 357 { 358 if (!hasComposition()) 359 return nullptr; 360 unsigned length = m_compositionNode->length(); 361 unsigned start = std::min(m_compositionStart, length); 362 unsigned end = std::min(std::max(start, m_compositionEnd), length); 363 if (start >= end) 364 return nullptr; 365 return Range::create(m_compositionNode->document(), m_compositionNode.get(), start, m_compositionNode.get(), end); 366 } 367 368 PlainTextRange InputMethodController::getSelectionOffsets() const 369 { 370 RefPtrWillBeRawPtr<Range> range = frame().selection().selection().firstRange(); 371 if (!range) 372 return PlainTextRange(); 373 ContainerNode* editable = frame().selection().rootEditableElementOrTreeScopeRootNode(); 374 ASSERT(editable); 375 return PlainTextRange::create(*editable, *range.get()); 376 } 377 378 bool InputMethodController::setSelectionOffsets(const PlainTextRange& selectionOffsets) 379 { 380 if (selectionOffsets.isNull()) 381 return false; 382 Element* rootEditableElement = frame().selection().rootEditableElement(); 383 if (!rootEditableElement) 384 return false; 385 386 RefPtrWillBeRawPtr<Range> range = selectionOffsets.createRange(*rootEditableElement); 387 if (!range) 388 return false; 389 390 return frame().selection().setSelectedRange(range.get(), VP_DEFAULT_AFFINITY, FrameSelection::NonDirectional, FrameSelection::CloseTyping); 391 } 392 393 bool InputMethodController::setEditableSelectionOffsets(const PlainTextRange& selectionOffsets) 394 { 395 if (!editor().canEdit()) 396 return false; 397 return setSelectionOffsets(selectionOffsets); 398 } 399 400 void InputMethodController::extendSelectionAndDelete(int before, int after) 401 { 402 if (!editor().canEdit()) 403 return; 404 PlainTextRange selectionOffsets(getSelectionOffsets()); 405 if (selectionOffsets.isNull()) 406 return; 407 408 // A common call of before=1 and after=0 will fail if the last character 409 // is multi-code-word UTF-16, including both multi-16bit code-points and 410 // Unicode combining character sequences of multiple single-16bit code- 411 // points (officially called "compositions"). Try more until success. 412 // http://crbug.com/355995 413 // 414 // FIXME: Note that this is not an ideal solution when this function is 415 // called to implement "backspace". In that case, there should be some call 416 // that will not delete a full multi-code-point composition but rather 417 // only the last code-point so that it's possible for a user to correct 418 // a composition without starting it from the beginning. 419 // http://crbug.com/37993 420 do { 421 if (!setSelectionOffsets(PlainTextRange(std::max(static_cast<int>(selectionOffsets.start()) - before, 0), selectionOffsets.end() + after))) 422 return; 423 if (before == 0) 424 break; 425 ++before; 426 } while (frame().selection().start() == frame().selection().end() && before <= static_cast<int>(selectionOffsets.start())); 427 TypingCommand::deleteSelection(*frame().document()); 428 } 429 430 void InputMethodController::trace(Visitor* visitor) 431 { 432 visitor->trace(m_frame); 433 visitor->trace(m_compositionNode); 434 } 435 436 } // namespace blink 437