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/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