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/html/HTMLTextAreaElement.h" 38 #include "core/page/Chrome.h" 39 #include "core/page/ChromeClient.h" 40 #include "core/page/EventHandler.h" 41 #include "core/frame/Frame.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(Frame& frame) 60 { 61 return adoptPtr(new InputMethodController(frame)); 62 } 63 64 InputMethodController::InputMethodController(Frame& 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 = 0; 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 RefPtr<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 RefPtr<CompositionEvent> event = CompositionEvent::create(EventTypeNames::compositionend, m_frame.domWindow(), text); 196 target->dispatchEvent(event, IGNORE_EXCEPTION); 197 } 198 199 // If text is empty, then delete the old composition here. If text is non-empty, InsertTextCommand::input 200 // will delete the old composition with an optimized replace operation. 201 if (text.isEmpty() && mode != CancelComposition) { 202 ASSERT(m_frame.document()); 203 TypingCommand::deleteSelection(*m_frame.document(), 0); 204 } 205 206 m_compositionNode = 0; 207 m_customCompositionUnderlines.clear(); 208 209 insertTextForConfirmedComposition(text); 210 211 if (mode == CancelComposition) { 212 // An open typing command that disagrees about current selection would cause issues with typing later on. 213 TypingCommand::closeTyping(&m_frame); 214 } 215 216 return true; 217 } 218 219 void InputMethodController::setComposition(const String& text, const Vector<CompositionUnderline>& underlines, unsigned selectionStart, unsigned selectionEnd) 220 { 221 Editor::RevealSelectionScope revealSelectionScope(&editor()); 222 223 // Updates styles before setting selection for composition to prevent 224 // inserting the previous composition text into text nodes oddly. 225 // See https://bugs.webkit.org/show_bug.cgi?id=46868 226 m_frame.document()->updateStyleIfNeeded(); 227 228 selectComposition(); 229 230 if (m_frame.selection().isNone()) 231 return; 232 233 if (Element* target = m_frame.document()->focusedElement()) { 234 // Dispatch an appropriate composition event to the focused node. 235 // We check the composition status and choose an appropriate composition event since this 236 // function is used for three purposes: 237 // 1. Starting a new composition. 238 // Send a compositionstart and a compositionupdate event when this function creates 239 // a new composition node, i.e. 240 // m_compositionNode == 0 && !text.isEmpty(). 241 // Sending a compositionupdate event at this time ensures that at least one 242 // compositionupdate event is dispatched. 243 // 2. Updating the existing composition node. 244 // Send a compositionupdate event when this function updates the existing composition 245 // node, i.e. m_compositionNode != 0 && !text.isEmpty(). 246 // 3. Canceling the ongoing composition. 247 // Send a compositionend event when function deletes the existing composition node, i.e. 248 // m_compositionNode != 0 && test.isEmpty(). 249 RefPtr<CompositionEvent> event; 250 if (!hasComposition()) { 251 // We should send a compositionstart event only when the given text is not empty because this 252 // function doesn't create a composition node when the text is empty. 253 if (!text.isEmpty()) { 254 target->dispatchEvent(CompositionEvent::create(EventTypeNames::compositionstart, m_frame.domWindow(), m_frame.selectedText())); 255 event = CompositionEvent::create(EventTypeNames::compositionupdate, m_frame.domWindow(), text); 256 } 257 } else { 258 if (!text.isEmpty()) 259 event = CompositionEvent::create(EventTypeNames::compositionupdate, m_frame.domWindow(), text); 260 else 261 event = CompositionEvent::create(EventTypeNames::compositionend, m_frame.domWindow(), text); 262 } 263 if (event.get()) 264 target->dispatchEvent(event, IGNORE_EXCEPTION); 265 } 266 267 // If text is empty, then delete the old composition here. If text is non-empty, InsertTextCommand::input 268 // will delete the old composition with an optimized replace operation. 269 if (text.isEmpty()) { 270 ASSERT(m_frame.document()); 271 TypingCommand::deleteSelection(*m_frame.document(), TypingCommand::PreventSpellChecking); 272 } 273 274 m_compositionNode = 0; 275 m_customCompositionUnderlines.clear(); 276 277 if (!text.isEmpty()) { 278 ASSERT(m_frame.document()); 279 TypingCommand::insertText(*m_frame.document(), text, TypingCommand::SelectInsertedText | TypingCommand::PreventSpellChecking, TypingCommand::TextCompositionUpdate); 280 281 // Find out what node has the composition now. 282 Position base = m_frame.selection().base().downstream(); 283 Position extent = m_frame.selection().extent(); 284 Node* baseNode = base.deprecatedNode(); 285 unsigned baseOffset = base.deprecatedEditingOffset(); 286 Node* extentNode = extent.deprecatedNode(); 287 unsigned extentOffset = extent.deprecatedEditingOffset(); 288 289 if (baseNode && baseNode == extentNode && baseNode->isTextNode() && baseOffset + text.length() == extentOffset) { 290 m_compositionNode = toText(baseNode); 291 m_compositionStart = baseOffset; 292 m_compositionEnd = extentOffset; 293 m_customCompositionUnderlines = underlines; 294 size_t numUnderlines = m_customCompositionUnderlines.size(); 295 for (size_t i = 0; i < numUnderlines; ++i) { 296 m_customCompositionUnderlines[i].startOffset += baseOffset; 297 m_customCompositionUnderlines[i].endOffset += baseOffset; 298 } 299 if (baseNode->renderer()) 300 baseNode->renderer()->repaint(); 301 302 unsigned start = std::min(baseOffset + selectionStart, extentOffset); 303 unsigned end = std::min(std::max(start, baseOffset + selectionEnd), extentOffset); 304 RefPtr<Range> selectedRange = Range::create(baseNode->document(), baseNode, start, baseNode, end); 305 m_frame.selection().setSelectedRange(selectedRange.get(), DOWNSTREAM, false); 306 } 307 } 308 } 309 310 void InputMethodController::setCompositionFromExistingText(const Vector<CompositionUnderline>& underlines, unsigned compositionStart, unsigned compositionEnd) 311 { 312 Node* editable = m_frame.selection().rootEditableElement(); 313 Position base = m_frame.selection().base().downstream(); 314 Node* baseNode = base.anchorNode(); 315 if (editable->firstChild() == baseNode && editable->lastChild() == baseNode && baseNode->isTextNode()) { 316 m_compositionNode = 0; 317 m_customCompositionUnderlines.clear(); 318 319 if (base.anchorType() != Position::PositionIsOffsetInAnchor) 320 return; 321 if (!baseNode || baseNode != m_frame.selection().extent().anchorNode()) 322 return; 323 324 m_compositionNode = toText(baseNode); 325 m_compositionStart = compositionStart; 326 m_compositionEnd = compositionEnd; 327 m_customCompositionUnderlines = underlines; 328 size_t numUnderlines = m_customCompositionUnderlines.size(); 329 for (size_t i = 0; i < numUnderlines; ++i) { 330 m_customCompositionUnderlines[i].startOffset += compositionStart; 331 m_customCompositionUnderlines[i].endOffset += compositionStart; 332 } 333 if (baseNode->renderer()) 334 baseNode->renderer()->repaint(); 335 return; 336 } 337 338 Editor::RevealSelectionScope revealSelectionScope(&editor()); 339 SelectionOffsetsScope selectionOffsetsScope(this); 340 setSelectionOffsets(PlainTextRange(compositionStart, compositionEnd)); 341 setComposition(m_frame.selectedText(), underlines, 0, 0); 342 } 343 344 PassRefPtr<Range> InputMethodController::compositionRange() const 345 { 346 if (!hasComposition()) 347 return 0; 348 unsigned length = m_compositionNode->length(); 349 unsigned start = std::min(m_compositionStart, length); 350 unsigned end = std::min(std::max(start, m_compositionEnd), length); 351 if (start >= end) 352 return 0; 353 return Range::create(m_compositionNode->document(), m_compositionNode.get(), start, m_compositionNode.get(), end); 354 } 355 356 PlainTextRange InputMethodController::getSelectionOffsets() const 357 { 358 RefPtr<Range> range = m_frame.selection().selection().firstRange(); 359 if (!range) 360 return PlainTextRange(); 361 Node* editable = m_frame.selection().rootEditableElementOrTreeScopeRootNode(); 362 ASSERT(editable); 363 return PlainTextRange::create(*editable, *range.get()); 364 } 365 366 bool InputMethodController::setSelectionOffsets(const PlainTextRange& selectionOffsets) 367 { 368 if (selectionOffsets.isNull()) 369 return false; 370 Element* rootEditableElement = m_frame.selection().rootEditableElement(); 371 if (!rootEditableElement) 372 return false; 373 374 RefPtr<Range> range = selectionOffsets.createRange(*rootEditableElement); 375 if (!range) 376 return false; 377 378 return m_frame.selection().setSelectedRange(range.get(), VP_DEFAULT_AFFINITY, true); 379 } 380 381 bool InputMethodController::setEditableSelectionOffsets(const PlainTextRange& selectionOffsets) 382 { 383 if (!editor().canEdit()) 384 return false; 385 return setSelectionOffsets(selectionOffsets); 386 } 387 388 void InputMethodController::extendSelectionAndDelete(int before, int after) 389 { 390 if (!editor().canEdit()) 391 return; 392 PlainTextRange selectionOffsets(getSelectionOffsets()); 393 if (selectionOffsets.isNull()) 394 return; 395 setSelectionOffsets(PlainTextRange(std::max(static_cast<int>(selectionOffsets.start()) - before, 0), selectionOffsets.end() + after)); 396 TypingCommand::deleteSelection(*m_frame.document()); 397 } 398 399 } // namespace WebCore 400