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/dom/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/dom/UserTypingGestureIndicator.h" 36 #include "core/editing/Editor.h" 37 #include "core/editing/TypingCommand.h" 38 #include "core/html/HTMLTextAreaElement.h" 39 #include "core/page/EditorClient.h" 40 #include "core/page/EventHandler.h" 41 #include "core/page/Frame.h" 42 #include "core/rendering/RenderObject.h" 43 44 namespace WebCore { 45 46 PlainTextOffsets::PlainTextOffsets() 47 : m_start(notFound) 48 , m_end(notFound) 49 { 50 } 51 52 PlainTextOffsets::PlainTextOffsets(int start, int end) 53 : m_start(start) 54 , m_end(end) 55 { 56 ASSERT(start != notFound); 57 ASSERT(end != notFound); 58 ASSERT(start <= end); 59 } 60 61 // ---------------------------- 62 63 InputMethodController::SelectionOffsetsScope::SelectionOffsetsScope(InputMethodController* inputMethodController) 64 : m_inputMethodController(inputMethodController) 65 , m_offsets(inputMethodController->getSelectionOffsets()) 66 { 67 } 68 69 InputMethodController::SelectionOffsetsScope::~SelectionOffsetsScope() 70 { 71 m_inputMethodController->setSelectionOffsets(m_offsets); 72 } 73 74 // ---------------------------- 75 76 PassOwnPtr<InputMethodController> InputMethodController::create(Frame* frame) 77 { 78 return adoptPtr(new InputMethodController(frame)); 79 } 80 81 InputMethodController::InputMethodController(Frame* frame) 82 : m_frame(frame) 83 , m_compositionStart(0) 84 , m_compositionEnd(0) 85 { 86 } 87 88 InputMethodController::~InputMethodController() 89 { 90 } 91 92 inline Editor& InputMethodController::editor() const 93 { 94 return *m_frame->editor(); 95 } 96 97 inline EditorClient* InputMethodController::editorClient() const 98 { 99 return editor().client(); 100 } 101 102 void InputMethodController::clear() 103 { 104 m_compositionNode = 0; 105 m_customCompositionUnderlines.clear(); 106 } 107 108 bool InputMethodController::insertTextForConfirmedComposition(const String& text) 109 { 110 return m_frame->eventHandler()->handleTextInputEvent(text, 0, TextEventInputComposition); 111 } 112 113 void InputMethodController::selectComposition() const 114 { 115 RefPtr<Range> range = compositionRange(); 116 if (!range) 117 return; 118 119 // The composition can start inside a composed character sequence, so we have to override checks. 120 // See <http://bugs.webkit.org/show_bug.cgi?id=15781> 121 VisibleSelection selection; 122 selection.setWithoutValidation(range->startPosition(), range->endPosition()); 123 m_frame->selection()->setSelection(selection, 0); 124 } 125 126 void InputMethodController::confirmComposition() 127 { 128 if (!m_compositionNode) 129 return; 130 finishComposition(m_compositionNode->data().substring(m_compositionStart, m_compositionEnd - m_compositionStart), ConfirmComposition); 131 } 132 133 void InputMethodController::confirmComposition(const String& text) 134 { 135 finishComposition(text, ConfirmComposition); 136 } 137 138 void InputMethodController::confirmCompositionAndResetState() 139 { 140 if (!hasComposition()) 141 return; 142 143 // We should verify the parent node of this IME composition node are 144 // editable because JavaScript may delete a parent node of the composition 145 // node. In this case, WebKit crashes while deleting texts from the parent 146 // node, which doesn't exist any longer. 147 RefPtr<Range> range = compositionRange(); 148 if (range) { 149 Node* node = range->startContainer(); 150 if (!node || !node->isContentEditable()) 151 return; 152 } 153 154 // EditorClient::willSetInputMethodState() resets input method and the composition string is committed. 155 if (EditorClient* client = editorClient()) 156 client->willSetInputMethodState(); 157 } 158 159 void InputMethodController::cancelComposition() 160 { 161 if (!m_compositionNode) 162 return; 163 finishComposition(emptyString(), CancelComposition); 164 } 165 166 void InputMethodController::cancelCompositionIfSelectionIsInvalid() 167 { 168 if (!hasComposition() || editor().preventRevealSelection()) 169 return; 170 171 // Check if selection start and selection end are valid. 172 Position start = m_frame->selection()->start(); 173 Position end = m_frame->selection()->end(); 174 if (start.containerNode() == m_compositionNode 175 && end.containerNode() == m_compositionNode 176 && static_cast<unsigned>(start.computeOffsetInContainerNode()) >= m_compositionStart 177 && static_cast<unsigned>(end.computeOffsetInContainerNode()) <= m_compositionEnd) 178 return; 179 180 cancelComposition(); 181 if (editorClient()) 182 editorClient()->didCancelCompositionOnSelectionChange(); 183 } 184 185 void InputMethodController::finishComposition(const String& text, FinishCompositionMode mode) 186 { 187 ASSERT(mode == ConfirmComposition || mode == CancelComposition); 188 UserTypingGestureIndicator typingGestureIndicator(m_frame); 189 190 Editor::RevealSelectionScope revealSelectionScope(&editor()); 191 192 if (mode == CancelComposition) 193 ASSERT(text == emptyString()); 194 else 195 selectComposition(); 196 197 if (m_frame->selection()->isNone()) 198 return; 199 200 // Dispatch a compositionend event to the focused node. 201 // We should send this event before sending a TextEvent as written in Section 6.2.2 and 6.2.3 of 202 // the DOM Event specification. 203 if (Element* target = m_frame->document()->focusedElement()) { 204 RefPtr<CompositionEvent> event = CompositionEvent::create(eventNames().compositionendEvent, m_frame->domWindow(), text); 205 target->dispatchEvent(event, IGNORE_EXCEPTION); 206 } 207 208 // If text is empty, then delete the old composition here. If text is non-empty, InsertTextCommand::input 209 // will delete the old composition with an optimized replace operation. 210 if (text.isEmpty() && mode != CancelComposition) 211 TypingCommand::deleteSelection(m_frame->document(), 0); 212 213 m_compositionNode = 0; 214 m_customCompositionUnderlines.clear(); 215 216 insertTextForConfirmedComposition(text); 217 218 if (mode == CancelComposition) { 219 // An open typing command that disagrees about current selection would cause issues with typing later on. 220 TypingCommand::closeTyping(m_frame); 221 } 222 } 223 224 void InputMethodController::setComposition(const String& text, const Vector<CompositionUnderline>& underlines, unsigned selectionStart, unsigned selectionEnd) 225 { 226 UserTypingGestureIndicator typingGestureIndicator(m_frame); 227 228 Editor::RevealSelectionScope revealSelectionScope(&editor()); 229 230 // Updates styles before setting selection for composition to prevent 231 // inserting the previous composition text into text nodes oddly. 232 // See https://bugs.webkit.org/show_bug.cgi?id=46868 233 m_frame->document()->updateStyleIfNeeded(); 234 235 selectComposition(); 236 237 if (m_frame->selection()->isNone()) 238 return; 239 240 if (Element* target = m_frame->document()->focusedElement()) { 241 // Dispatch an appropriate composition event to the focused node. 242 // We check the composition status and choose an appropriate composition event since this 243 // function is used for three purposes: 244 // 1. Starting a new composition. 245 // Send a compositionstart and a compositionupdate event when this function creates 246 // a new composition node, i.e. 247 // m_compositionNode == 0 && !text.isEmpty(). 248 // Sending a compositionupdate event at this time ensures that at least one 249 // compositionupdate event is dispatched. 250 // 2. Updating the existing composition node. 251 // Send a compositionupdate event when this function updates the existing composition 252 // node, i.e. m_compositionNode != 0 && !text.isEmpty(). 253 // 3. Canceling the ongoing composition. 254 // Send a compositionend event when function deletes the existing composition node, i.e. 255 // m_compositionNode != 0 && test.isEmpty(). 256 RefPtr<CompositionEvent> event; 257 if (!m_compositionNode) { 258 // We should send a compositionstart event only when the given text is not empty because this 259 // function doesn't create a composition node when the text is empty. 260 if (!text.isEmpty()) { 261 target->dispatchEvent(CompositionEvent::create(eventNames().compositionstartEvent, m_frame->domWindow(), m_frame->selectedText())); 262 event = CompositionEvent::create(eventNames().compositionupdateEvent, m_frame->domWindow(), text); 263 } 264 } else { 265 if (!text.isEmpty()) 266 event = CompositionEvent::create(eventNames().compositionupdateEvent, m_frame->domWindow(), text); 267 else 268 event = CompositionEvent::create(eventNames().compositionendEvent, m_frame->domWindow(), text); 269 } 270 if (event.get()) 271 target->dispatchEvent(event, IGNORE_EXCEPTION); 272 } 273 274 // If text is empty, then delete the old composition here. If text is non-empty, InsertTextCommand::input 275 // will delete the old composition with an optimized replace operation. 276 if (text.isEmpty()) 277 TypingCommand::deleteSelection(m_frame->document(), TypingCommand::PreventSpellChecking); 278 279 m_compositionNode = 0; 280 m_customCompositionUnderlines.clear(); 281 282 if (!text.isEmpty()) { 283 TypingCommand::insertText(m_frame->document(), text, TypingCommand::SelectInsertedText | TypingCommand::PreventSpellChecking, TypingCommand::TextCompositionUpdate); 284 285 // Find out what node has the composition now. 286 Position base = m_frame->selection()->base().downstream(); 287 Position extent = m_frame->selection()->extent(); 288 Node* baseNode = base.deprecatedNode(); 289 unsigned baseOffset = base.deprecatedEditingOffset(); 290 Node* extentNode = extent.deprecatedNode(); 291 unsigned extentOffset = extent.deprecatedEditingOffset(); 292 293 if (baseNode && baseNode == extentNode && baseNode->isTextNode() && baseOffset + text.length() == extentOffset) { 294 m_compositionNode = toText(baseNode); 295 m_compositionStart = baseOffset; 296 m_compositionEnd = extentOffset; 297 m_customCompositionUnderlines = underlines; 298 size_t numUnderlines = m_customCompositionUnderlines.size(); 299 for (size_t i = 0; i < numUnderlines; ++i) { 300 m_customCompositionUnderlines[i].startOffset += baseOffset; 301 m_customCompositionUnderlines[i].endOffset += baseOffset; 302 } 303 if (baseNode->renderer()) 304 baseNode->renderer()->repaint(); 305 306 unsigned start = std::min(baseOffset + selectionStart, extentOffset); 307 unsigned end = std::min(std::max(start, baseOffset + selectionEnd), extentOffset); 308 RefPtr<Range> selectedRange = Range::create(baseNode->document(), baseNode, start, baseNode, end); 309 m_frame->selection()->setSelectedRange(selectedRange.get(), DOWNSTREAM, false); 310 } 311 } 312 } 313 314 void InputMethodController::setCompositionFromExistingText(const Vector<CompositionUnderline>& underlines, unsigned compositionStart, unsigned compositionEnd) 315 { 316 Node* editable = m_frame->selection()->rootEditableElement(); 317 Position base = m_frame->selection()->base().downstream(); 318 Node* baseNode = base.anchorNode(); 319 if (editable->firstChild() == baseNode && editable->lastChild() == baseNode && baseNode->isTextNode()) { 320 m_compositionNode = 0; 321 m_customCompositionUnderlines.clear(); 322 323 if (base.anchorType() != Position::PositionIsOffsetInAnchor) 324 return; 325 if (!baseNode || baseNode != m_frame->selection()->extent().anchorNode()) 326 return; 327 328 m_compositionNode = toText(baseNode); 329 m_compositionStart = compositionStart; 330 m_compositionEnd = compositionEnd; 331 m_customCompositionUnderlines = underlines; 332 size_t numUnderlines = m_customCompositionUnderlines.size(); 333 for (size_t i = 0; i < numUnderlines; ++i) { 334 m_customCompositionUnderlines[i].startOffset += compositionStart; 335 m_customCompositionUnderlines[i].endOffset += compositionStart; 336 } 337 if (baseNode->renderer()) 338 baseNode->renderer()->repaint(); 339 return; 340 } 341 342 Editor::RevealSelectionScope revealSelectionScope(&editor()); 343 SelectionOffsetsScope selectionOffsetsScope(this); 344 setSelectionOffsets(PlainTextOffsets(compositionStart, compositionEnd)); 345 setComposition(m_frame->selectedText(), underlines, 0, 0); 346 } 347 348 PassRefPtr<Range> InputMethodController::compositionRange() const 349 { 350 if (!m_compositionNode) 351 return 0; 352 unsigned length = m_compositionNode->length(); 353 unsigned start = std::min(m_compositionStart, length); 354 unsigned end = std::min(std::max(start, m_compositionEnd), length); 355 if (start >= end) 356 return 0; 357 return Range::create(m_compositionNode->document(), m_compositionNode.get(), start, m_compositionNode.get(), end); 358 } 359 360 PlainTextOffsets InputMethodController::getSelectionOffsets() const 361 { 362 RefPtr<Range> range = m_frame->selection()->selection().firstRange(); 363 if (!range) 364 return PlainTextOffsets(); 365 size_t location; 366 size_t length; 367 // FIXME: We should change TextIterator::getLocationAndLengthFromRange() returns PlainTextOffsets. 368 if (TextIterator::getLocationAndLengthFromRange(m_frame->selection()->rootEditableElementOrTreeScopeRootNode(), range.get(), location, length)) 369 return PlainTextOffsets(location, location + length); 370 return PlainTextOffsets(); 371 } 372 373 bool InputMethodController::setSelectionOffsets(const PlainTextOffsets& selectionOffsets) 374 { 375 if (selectionOffsets.isNull()) 376 return false; 377 // FIXME: We should move Editor::setSelectionOffsets() into InputMethodController class. 378 return editor().setSelectionOffsets(selectionOffsets.start(), selectionOffsets.end()); 379 } 380 381 } // namespace WebCore 382