Home | History | Annotate | Download | only in editing
      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