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 WebCore { 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 PassOwnPtr<InputMethodController> InputMethodController::create(LocalFrame& frame) 60 { 61 return adoptPtr(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 m_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 m_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 m_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 m_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 = m_frame.selection().start(); 163 Position end = m_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 m_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 (m_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 = m_frame.document()->focusedElement()) { 195 unsigned baseOffset = m_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, m_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(m_frame.document()); 211 TypingCommand::deleteSelection(*m_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 m_frame.document()->updateRenderTreeIfNeeded(); 235 236 selectComposition(); 237 238 if (m_frame.selection().isNone()) 239 return; 240 241 if (Element* target = m_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, m_frame.domWindow(), m_frame.selectedText(), underlines)); 263 event = CompositionEvent::create(EventTypeNames::compositionupdate, m_frame.domWindow(), text, underlines); 264 } 265 } else { 266 if (!text.isEmpty()) 267 event = CompositionEvent::create(EventTypeNames::compositionupdate, m_frame.domWindow(), text, underlines); 268 else 269 event = CompositionEvent::create(EventTypeNames::compositionend, m_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(m_frame.document()); 279 TypingCommand::deleteSelection(*m_frame.document(), TypingCommand::PreventSpellChecking); 280 } 281 282 m_compositionNode = nullptr; 283 m_customCompositionUnderlines.clear(); 284 285 if (!text.isEmpty()) { 286 ASSERT(m_frame.document()); 287 TypingCommand::insertText(*m_frame.document(), text, TypingCommand::SelectInsertedText | TypingCommand::PreventSpellChecking, TypingCommand::TextCompositionUpdate); 288 289 // Find out what node has the composition now. 290 Position base = m_frame.selection().base().downstream(); 291 Position extent = m_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()->paintInvalidationForWholeRenderer(); 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 m_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 = m_frame.selection().rootEditableElement(); 321 Position base = m_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 != m_frame.selection().extent().anchorNode()) 330 return; 331 332 m_compositionNode = toText(baseNode); 333 RefPtrWillBeRawPtr<Range> range = PlainTextRange(compositionStart, compositionEnd).createRange(*editable); 334 m_compositionStart = range->startOffset(); 335 m_compositionEnd = range->endOffset(); 336 m_customCompositionUnderlines = underlines; 337 size_t numUnderlines = m_customCompositionUnderlines.size(); 338 for (size_t i = 0; i < numUnderlines; ++i) { 339 m_customCompositionUnderlines[i].startOffset += m_compositionStart; 340 m_customCompositionUnderlines[i].endOffset += m_compositionStart; 341 } 342 if (baseNode->renderer()) 343 baseNode->renderer()->paintInvalidationForWholeRenderer(); 344 return; 345 } 346 347 Editor::RevealSelectionScope revealSelectionScope(&editor()); 348 SelectionOffsetsScope selectionOffsetsScope(this); 349 setSelectionOffsets(PlainTextRange(compositionStart, compositionEnd)); 350 setComposition(m_frame.selectedText(), underlines, 0, 0); 351 } 352 353 PassRefPtrWillBeRawPtr<Range> InputMethodController::compositionRange() const 354 { 355 if (!hasComposition()) 356 return nullptr; 357 unsigned length = m_compositionNode->length(); 358 unsigned start = std::min(m_compositionStart, length); 359 unsigned end = std::min(std::max(start, m_compositionEnd), length); 360 if (start >= end) 361 return nullptr; 362 return Range::create(m_compositionNode->document(), m_compositionNode.get(), start, m_compositionNode.get(), end); 363 } 364 365 PlainTextRange InputMethodController::getSelectionOffsets() const 366 { 367 RefPtrWillBeRawPtr<Range> range = m_frame.selection().selection().firstRange(); 368 if (!range) 369 return PlainTextRange(); 370 Node* editable = m_frame.selection().rootEditableElementOrTreeScopeRootNode(); 371 ASSERT(editable); 372 return PlainTextRange::create(*editable, *range.get()); 373 } 374 375 bool InputMethodController::setSelectionOffsets(const PlainTextRange& selectionOffsets) 376 { 377 if (selectionOffsets.isNull()) 378 return false; 379 Element* rootEditableElement = m_frame.selection().rootEditableElement(); 380 if (!rootEditableElement) 381 return false; 382 383 RefPtrWillBeRawPtr<Range> range = selectionOffsets.createRange(*rootEditableElement); 384 if (!range) 385 return false; 386 387 return m_frame.selection().setSelectedRange(range.get(), VP_DEFAULT_AFFINITY, FrameSelection::NonDirectional, FrameSelection::CloseTyping); 388 } 389 390 bool InputMethodController::setEditableSelectionOffsets(const PlainTextRange& selectionOffsets) 391 { 392 if (!editor().canEdit()) 393 return false; 394 return setSelectionOffsets(selectionOffsets); 395 } 396 397 void InputMethodController::extendSelectionAndDelete(int before, int after) 398 { 399 if (!editor().canEdit()) 400 return; 401 PlainTextRange selectionOffsets(getSelectionOffsets()); 402 if (selectionOffsets.isNull()) 403 return; 404 405 // A common call of before=1 and after=0 will fail if the last character 406 // is multi-code-word UTF-16, including both multi-16bit code-points and 407 // Unicode combining character sequences of multiple single-16bit code- 408 // points (officially called "compositions"). Try more until success. 409 // http://crbug.com/355995 410 // 411 // FIXME: Note that this is not an ideal solution when this function is 412 // called to implement "backspace". In that case, there should be some call 413 // that will not delete a full multi-code-point composition but rather 414 // only the last code-point so that it's possible for a user to correct 415 // a composition without starting it from the beginning. 416 // http://crbug.com/37993 417 do { 418 if (!setSelectionOffsets(PlainTextRange(std::max(static_cast<int>(selectionOffsets.start()) - before, 0), selectionOffsets.end() + after))) 419 return; 420 if (before == 0) 421 break; 422 ++before; 423 } while (m_frame.selection().start() == m_frame.selection().end() && before <= static_cast<int>(selectionOffsets.start())); 424 TypingCommand::deleteSelection(*m_frame.document()); 425 } 426 427 } // namespace WebCore 428