flutter_flutter/sky/engine/core/editing/InputMethodController.cpp
Hixie 87e83e552d Remove EventTarget, and subsequent fallout.
The primary goal of this change was to remove EventTarget from the
sky_engine C++ code. Since EventTarget is so core to the entire event
system that sky_engine was based on, this is a rather invasive change.
As such, it had some knock-on effects. I deleted some of the files
that were affected, and cauterised the remainder.

In many cases, a file would depend on another file that it didn't
include directly, but instead included indirectly via another file
that I deleted. When this happened, if the features that this broke
were obsolete, I sometimes just removed the features instead.

Specifically:
- removed EventTarget
- removed EventQueue, since without a target, what's a queue going to
  do?
- same with EventDispatch*
- removed ExecutionContext, since it had an EventQueue and nothing
  else it did was relevant to Sky anymore
- removed ActiveDOMObject, which was all about ExecutionContexts
- removed ContextLifecycleNotifier since it dependend on
  ExecutionContext and ActiveDOMObject
- removed the other Lifecycle classes for consistency, and replaced
  them with four booleans in the Document class
- removed some of the attributes that are no longer relevant from
  IDLExtendedAttributes (ConstructorCallWith and
  CallWith=ExecutionContext)
- removed the Document member on DOMDartState since we never set it to
  anything but null.
- removed BuiltinSky::InstallWindow since it relied on the Document
  member of DOMDartState
- removed EventHandler, EventListener, and mentions of those in
  various binding scripts
- removed NewEventHandler, since we're not using that either
- removed the following interfaces from the Sky Dart API:
  - EventTarget
  - EventListener (since without a target, there's no way to listen)
  - FocusEvent (since it's only member was an EventTarget)
  - HashChangeEvent (mostly by accident, but it's defunct anyway)
  - FontFace (it used ConstructorCallWith=ExecutionContext)
- changed the following interfaces of the Sky DART API:
  - MediaQueryList is no longer an EventTarget
  - Node is no longer an EventTarget
  - Document no longer has defaultView (depended on
    DOMDartState's document)
  - DocumentFragment, Element, Range, and Text no longer have a
    constructor (they all depended on DOMDartState's document, which
    is now gone)
  - Event lost its EventTarget members and path.
  - Window lost its WindowTimers partial interface (it used
    EventTarget and ExecutionContext a lot)
- removed numerous hacks in the bindings around features that are now
  gone, like addEventListener
- removed a bunch of console logging code, since that relied on
  ExecutionContext
- cauterised the wound in FontFace.cpp by removing constructors and
  methods that called now-removed features
- same with MediaQuery and friends
- same with some editor features and focus-related features
- same with Document
- removed DOMTimer classes since they use ExecutionContexts
2015-07-16 14:40:10 -07:00

380 lines
14 KiB
C++

/*
* 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 "sky/engine/core/editing/InputMethodController.h"
#include "gen/sky/core/EventTypeNames.h"
#include "sky/engine/core/dom/Document.h"
#include "sky/engine/core/dom/Element.h"
#include "sky/engine/core/dom/Range.h"
#include "sky/engine/core/dom/Text.h"
#include "sky/engine/core/editing/Editor.h"
#include "sky/engine/core/editing/TypingCommand.h"
#include "sky/engine/core/events/CompositionEvent.h"
#include "sky/engine/core/frame/LocalFrame.h"
#include "sky/engine/core/page/ChromeClient.h"
#include "sky/engine/core/rendering/RenderObject.h"
// This file is new missing the logic it had relating to firing events, since EventTarget is gone.
// TODO(ianh): This makes it essentially useless, and it should probably be removed soon.
namespace blink {
InputMethodController::SelectionOffsetsScope::SelectionOffsetsScope(InputMethodController* inputMethodController)
: m_inputMethodController(inputMethodController)
, m_offsets(inputMethodController->getSelectionOffsets())
{
}
InputMethodController::SelectionOffsetsScope::~SelectionOffsetsScope()
{
m_inputMethodController->setSelectionOffsets(m_offsets);
}
// ----------------------------
PassOwnPtr<InputMethodController> InputMethodController::create(LocalFrame& frame)
{
return adoptPtr(new InputMethodController(frame));
}
InputMethodController::InputMethodController(LocalFrame& frame)
: m_frame(frame)
, m_compositionStart(0)
, m_compositionEnd(0)
{
}
InputMethodController::~InputMethodController()
{
}
bool InputMethodController::hasComposition() const
{
return m_compositionNode && m_compositionNode->isContentEditable();
}
inline Editor& InputMethodController::editor() const
{
return m_frame.editor();
}
void InputMethodController::clear()
{
m_compositionNode = nullptr;
m_customCompositionUnderlines.clear();
}
bool InputMethodController::insertTextForConfirmedComposition(const String& text)
{
return false;
}
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);
}
bool InputMethodController::confirmComposition()
{
if (!hasComposition())
return false;
return finishComposition(m_compositionNode->data().substring(m_compositionStart, m_compositionEnd - m_compositionStart), ConfirmComposition);
}
bool InputMethodController::confirmComposition(const String& text)
{
return finishComposition(text, ConfirmComposition);
}
bool InputMethodController::confirmCompositionOrInsertText(const String& text, ConfirmCompositionBehavior confirmBehavior)
{
if (!hasComposition()) {
if (!text.length())
return false;
editor().insertText(text, 0);
return true;
}
if (text.length()) {
confirmComposition(text);
return true;
}
if (confirmBehavior != KeepSelection)
return confirmComposition();
SelectionOffsetsScope selectionOffsetsScope(this);
return confirmComposition();
}
void InputMethodController::confirmCompositionAndResetState()
{
// TODO(esprehn): Remove this.
}
void InputMethodController::cancelComposition()
{
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();
}
bool InputMethodController::finishComposition(const String& text, FinishCompositionMode mode)
{
if (!hasComposition())
return false;
ASSERT(mode == ConfirmComposition || mode == CancelComposition);
Editor::RevealSelectionScope revealSelectionScope(&editor());
if (mode == CancelComposition)
ASSERT(text == emptyString());
else
selectComposition();
if (m_frame.selection().isNone())
return false;
// 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) {
ASSERT(m_frame.document());
TypingCommand::deleteSelection(*m_frame.document(), 0);
}
m_compositionNode = nullptr;
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);
}
return true;
}
void InputMethodController::setComposition(const String& text, const Vector<CompositionUnderline>& underlines, unsigned selectionStart, unsigned selectionEnd)
{
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()->updateRenderTreeIfNeeded();
selectComposition();
if (m_frame.selection().isNone())
return;
// 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()) {
ASSERT(m_frame.document());
TypingCommand::deleteSelection(*m_frame.document(), TypingCommand::PreventSpellChecking);
}
m_compositionNode = nullptr;
m_customCompositionUnderlines.clear();
if (!text.isEmpty()) {
ASSERT(m_frame.document());
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;
}
// TODO(ojan): What was this for? Do we need it in sky since we
// don't need to support legacy IMEs?
if (baseNode->renderer())
baseNode->document().scheduleVisualUpdate();
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, FrameSelection::NonDirectional, NotUserTriggered);
}
}
}
void InputMethodController::setCompositionFromExistingText(const Vector<CompositionUnderline>& underlines, unsigned compositionStart, unsigned compositionEnd)
{
Element* editable = m_frame.selection().rootEditableElement();
Position base = m_frame.selection().base().downstream();
Node* baseNode = base.anchorNode();
if (editable->firstChild() == baseNode && editable->lastChild() == baseNode && baseNode->isTextNode()) {
m_compositionNode = nullptr;
m_customCompositionUnderlines.clear();
if (base.anchorType() != Position::PositionIsOffsetInAnchor)
return;
if (!baseNode || baseNode != m_frame.selection().extent().anchorNode())
return;
m_compositionNode = toText(baseNode);
RefPtr<Range> range = PlainTextRange(compositionStart, compositionEnd).createRange(*editable);
m_compositionStart = range->startOffset();
m_compositionEnd = range->endOffset();
m_customCompositionUnderlines = underlines;
size_t numUnderlines = m_customCompositionUnderlines.size();
for (size_t i = 0; i < numUnderlines; ++i) {
m_customCompositionUnderlines[i].startOffset += m_compositionStart;
m_customCompositionUnderlines[i].endOffset += m_compositionStart;
}
// TODO(ojan): What was this for? Do we need it in sky since we
// don't need to support legacy IMEs?
if (baseNode->renderer())
baseNode->document().scheduleVisualUpdate();
return;
}
Editor::RevealSelectionScope revealSelectionScope(&editor());
SelectionOffsetsScope selectionOffsetsScope(this);
setSelectionOffsets(PlainTextRange(compositionStart, compositionEnd));
setComposition(m_frame.selectedText(), underlines, 0, 0);
}
PassRefPtr<Range> InputMethodController::compositionRange() const
{
if (!hasComposition())
return nullptr;
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 nullptr;
return Range::create(m_compositionNode->document(), m_compositionNode.get(), start, m_compositionNode.get(), end);
}
PlainTextRange InputMethodController::getSelectionOffsets() const
{
RefPtr<Range> range = m_frame.selection().selection().firstRange();
if (!range)
return PlainTextRange();
ContainerNode* editable = m_frame.selection().rootEditableElementOrTreeScopeRootNode();
ASSERT(editable);
return PlainTextRange::create(*editable, *range.get());
}
bool InputMethodController::setSelectionOffsets(const PlainTextRange& selectionOffsets)
{
if (selectionOffsets.isNull())
return false;
Element* rootEditableElement = m_frame.selection().rootEditableElement();
if (!rootEditableElement)
return false;
RefPtr<Range> range = selectionOffsets.createRange(*rootEditableElement);
if (!range)
return false;
return m_frame.selection().setSelectedRange(range.get(), VP_DEFAULT_AFFINITY, FrameSelection::NonDirectional, FrameSelection::CloseTyping);
}
bool InputMethodController::setEditableSelectionOffsets(const PlainTextRange& selectionOffsets)
{
if (!editor().canEdit())
return false;
return setSelectionOffsets(selectionOffsets);
}
void InputMethodController::extendSelectionAndDelete(int before, int after)
{
if (!editor().canEdit())
return;
PlainTextRange selectionOffsets(getSelectionOffsets());
if (selectionOffsets.isNull())
return;
// A common call of before=1 and after=0 will fail if the last character
// is multi-code-word UTF-16, including both multi-16bit code-points and
// Unicode combining character sequences of multiple single-16bit code-
// points (officially called "compositions"). Try more until success.
// http://crbug.com/355995
//
// FIXME: Note that this is not an ideal solution when this function is
// called to implement "backspace". In that case, there should be some call
// that will not delete a full multi-code-point composition but rather
// only the last code-point so that it's possible for a user to correct
// a composition without starting it from the beginning.
// http://crbug.com/37993
do {
if (!setSelectionOffsets(PlainTextRange(std::max(static_cast<int>(selectionOffsets.start()) - before, 0), selectionOffsets.end() + after)))
return;
if (before == 0)
break;
++before;
} while (m_frame.selection().start() == m_frame.selection().end() && before <= static_cast<int>(selectionOffsets.start()));
TypingCommand::deleteSelection(*m_frame.document());
}
} // namespace blink