blob: 402a8c289f745cd7c39040c6bed18225e731e208 [file] [log] [blame]
/*
* Copyright (C) 2006, 2007, 2008, 2011 Apple Inc. All rights reserved.
* Copyright (C) 2008 Nokia Corporation and/or its subsidiary(-ies)
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "config.h"
#include "core/editing/InputMethodController.h"
#include "core/dom/CompositionEvent.h"
#include "core/dom/Document.h"
#include "core/dom/Element.h"
#include "core/dom/Range.h"
#include "core/dom/Text.h"
#include "core/dom/UserTypingGestureIndicator.h"
#include "core/editing/Editor.h"
#include "core/editing/TypingCommand.h"
#include "core/page/EditorClient.h"
#include "core/page/EventHandler.h"
#include "core/page/Frame.h"
#include "core/rendering/RenderObject.h"
namespace WebCore {
PassOwnPtr<InputMethodController> InputMethodController::create(Frame* frame)
{
return adoptPtr(new InputMethodController(frame));
}
InputMethodController::InputMethodController(Frame* frame)
: m_frame(frame)
, m_compositionStart(0)
, m_compositionEnd(0)
{
}
InputMethodController::~InputMethodController()
{
}
inline Editor& InputMethodController::editor() const
{
return *m_frame->editor();
}
inline EditorClient* InputMethodController::editorClient() const
{
return editor().client();
}
void InputMethodController::clear()
{
m_compositionNode = 0;
m_customCompositionUnderlines.clear();
}
bool InputMethodController::insertTextForConfirmedComposition(const String& text)
{
return m_frame->eventHandler()->handleTextInputEvent(text, 0, TextEventInputComposition);
}
void InputMethodController::selectComposition() const
{
RefPtr<Range> range = compositionRange();
if (!range)
return;
// The composition can start inside a composed character sequence, so we have to override checks.
// See <http://bugs.webkit.org/show_bug.cgi?id=15781>
VisibleSelection selection;
selection.setWithoutValidation(range->startPosition(), range->endPosition());
m_frame->selection()->setSelection(selection, 0);
}
void InputMethodController::confirmComposition()
{
if (!m_compositionNode)
return;
finishComposition(m_compositionNode->data().substring(m_compositionStart, m_compositionEnd - m_compositionStart), ConfirmComposition);
}
void InputMethodController::confirmComposition(const String& text)
{
finishComposition(text, ConfirmComposition);
}
void InputMethodController::confirmCompositionAndResetState()
{
if (!hasComposition())
return;
// We should verify the parent node of this IME composition node are
// editable because JavaScript may delete a parent node of the composition
// node. In this case, WebKit crashes while deleting texts from the parent
// node, which doesn't exist any longer.
RefPtr<Range> range = compositionRange();
if (range) {
Node* node = range->startContainer();
if (!node || !node->isContentEditable())
return;
}
// EditorClient::willSetInputMethodState() resets input method and the composition string is committed.
if (EditorClient* client = editorClient())
client->willSetInputMethodState();
}
void InputMethodController::cancelComposition()
{
if (!m_compositionNode)
return;
finishComposition(emptyString(), CancelComposition);
}
void InputMethodController::cancelCompositionIfSelectionIsInvalid()
{
if (!hasComposition() || editor().preventRevealSelection())
return;
// Check if selection start and selection end are valid.
Position start = m_frame->selection()->start();
Position end = m_frame->selection()->end();
if (start.containerNode() == m_compositionNode
&& end.containerNode() == m_compositionNode
&& static_cast<unsigned>(start.computeOffsetInContainerNode()) > m_compositionStart
&& static_cast<unsigned>(end.computeOffsetInContainerNode()) < m_compositionEnd)
return;
cancelComposition();
if (editorClient())
editorClient()->didCancelCompositionOnSelectionChange();
}
void InputMethodController::finishComposition(const String& text, FinishCompositionMode mode)
{
ASSERT(mode == ConfirmComposition || mode == CancelComposition);
UserTypingGestureIndicator typingGestureIndicator(m_frame);
Editor::RevealSelectionScope revealSelectionScope(&editor());
if (mode == CancelComposition)
ASSERT(text == emptyString());
else
selectComposition();
if (m_frame->selection()->isNone())
return;
// Dispatch a compositionend event to the focused node.
// We should send this event before sending a TextEvent as written in Section 6.2.2 and 6.2.3 of
// the DOM Event specification.
if (Element* target = m_frame->document()->focusedElement()) {
RefPtr<CompositionEvent> event = CompositionEvent::create(eventNames().compositionendEvent, m_frame->domWindow(), text);
target->dispatchEvent(event, IGNORE_EXCEPTION);
}
// If text is empty, then delete the old composition here. If text is non-empty, InsertTextCommand::input
// will delete the old composition with an optimized replace operation.
if (text.isEmpty() && mode != CancelComposition)
TypingCommand::deleteSelection(m_frame->document(), 0);
m_compositionNode = 0;
m_customCompositionUnderlines.clear();
insertTextForConfirmedComposition(text);
if (mode == CancelComposition) {
// An open typing command that disagrees about current selection would cause issues with typing later on.
TypingCommand::closeTyping(m_frame);
}
}
void InputMethodController::setComposition(const String& text, const Vector<CompositionUnderline>& underlines, unsigned selectionStart, unsigned selectionEnd)
{
UserTypingGestureIndicator typingGestureIndicator(m_frame);
Editor::RevealSelectionScope revealSelectionScope(&editor());
// Updates styles before setting selection for composition to prevent
// inserting the previous composition text into text nodes oddly.
// See https://bugs.webkit.org/show_bug.cgi?id=46868
m_frame->document()->updateStyleIfNeeded();
selectComposition();
if (m_frame->selection()->isNone())
return;
if (Element* target = m_frame->document()->focusedElement()) {
// Dispatch an appropriate composition event to the focused node.
// We check the composition status and choose an appropriate composition event since this
// function is used for three purposes:
// 1. Starting a new composition.
// Send a compositionstart and a compositionupdate event when this function creates
// a new composition node, i.e.
// m_compositionNode == 0 && !text.isEmpty().
// Sending a compositionupdate event at this time ensures that at least one
// compositionupdate event is dispatched.
// 2. Updating the existing composition node.
// Send a compositionupdate event when this function updates the existing composition
// node, i.e. m_compositionNode != 0 && !text.isEmpty().
// 3. Canceling the ongoing composition.
// Send a compositionend event when function deletes the existing composition node, i.e.
// m_compositionNode != 0 && test.isEmpty().
RefPtr<CompositionEvent> event;
if (!m_compositionNode) {
// We should send a compositionstart event only when the given text is not empty because this
// function doesn't create a composition node when the text is empty.
if (!text.isEmpty()) {
target->dispatchEvent(CompositionEvent::create(eventNames().compositionstartEvent, m_frame->domWindow(), m_frame->selectedText()));
event = CompositionEvent::create(eventNames().compositionupdateEvent, m_frame->domWindow(), text);
}
} else {
if (!text.isEmpty())
event = CompositionEvent::create(eventNames().compositionupdateEvent, m_frame->domWindow(), text);
else
event = CompositionEvent::create(eventNames().compositionendEvent, m_frame->domWindow(), text);
}
if (event.get())
target->dispatchEvent(event, IGNORE_EXCEPTION);
}
// If text is empty, then delete the old composition here. If text is non-empty, InsertTextCommand::input
// will delete the old composition with an optimized replace operation.
if (text.isEmpty())
TypingCommand::deleteSelection(m_frame->document(), TypingCommand::PreventSpellChecking);
m_compositionNode = 0;
m_customCompositionUnderlines.clear();
if (!text.isEmpty()) {
TypingCommand::insertText(m_frame->document(), text, TypingCommand::SelectInsertedText | TypingCommand::PreventSpellChecking, TypingCommand::TextCompositionUpdate);
// Find out what node has the composition now.
Position base = m_frame->selection()->base().downstream();
Position extent = m_frame->selection()->extent();
Node* baseNode = base.deprecatedNode();
unsigned baseOffset = base.deprecatedEditingOffset();
Node* extentNode = extent.deprecatedNode();
unsigned extentOffset = extent.deprecatedEditingOffset();
if (baseNode && baseNode == extentNode && baseNode->isTextNode() && baseOffset + text.length() == extentOffset) {
m_compositionNode = toText(baseNode);
m_compositionStart = baseOffset;
m_compositionEnd = extentOffset;
m_customCompositionUnderlines = underlines;
size_t numUnderlines = m_customCompositionUnderlines.size();
for (size_t i = 0; i < numUnderlines; ++i) {
m_customCompositionUnderlines[i].startOffset += baseOffset;
m_customCompositionUnderlines[i].endOffset += baseOffset;
}
if (baseNode->renderer())
baseNode->renderer()->repaint();
unsigned start = std::min(baseOffset + selectionStart, extentOffset);
unsigned end = std::min(std::max(start, baseOffset + selectionEnd), extentOffset);
RefPtr<Range> selectedRange = Range::create(baseNode->document(), baseNode, start, baseNode, end);
m_frame->selection()->setSelectedRange(selectedRange.get(), DOWNSTREAM, false);
}
}
}
void InputMethodController::setCompositionFromExistingText(const Vector<CompositionUnderline>& underlines, unsigned compositionStart, unsigned compositionEnd)
{
m_compositionNode = 0;
m_customCompositionUnderlines.clear();
Position base = m_frame->selection()->base().downstream();
if (base.anchorType() != Position::PositionIsOffsetInAnchor)
return;
Node* baseNode = base.anchorNode();
unsigned baseOffset = base.offsetInContainerNode();
if (!baseNode || baseNode != m_frame->selection()->extent().anchorNode() || !baseNode->isTextNode())
return;
m_compositionNode = toText(baseNode);
m_compositionStart = compositionStart;
m_compositionEnd = compositionEnd;
m_customCompositionUnderlines = underlines;
size_t numUnderlines = m_customCompositionUnderlines.size();
for (size_t i = 0; i < numUnderlines; ++i) {
m_customCompositionUnderlines[i].startOffset += baseOffset;
m_customCompositionUnderlines[i].endOffset += baseOffset;
}
if (baseNode->renderer())
baseNode->renderer()->repaint();
}
PassRefPtr<Range> InputMethodController::compositionRange() const
{
if (!m_compositionNode)
return 0;
unsigned length = m_compositionNode->length();
unsigned start = std::min(m_compositionStart, length);
unsigned end = std::min(std::max(start, m_compositionEnd), length);
if (start >= end)
return 0;
return Range::create(m_compositionNode->document(), m_compositionNode.get(), start, m_compositionNode.get(), end);
}
} // namespace WebCore