blob: 56c32b82612ce3892043d2e90963c9bc21c8c07a [file] [log] [blame]
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "core/events/InputEvent.h"
#include "core/dom/Range.h"
#include "core/events/EventDispatcher.h"
#include "public/platform/WebEditingCommandType.h"
namespace blink {
namespace {
const struct {
InputEvent::InputType inputType;
const char* stringName;
} kInputTypeStringNameMap[] = {
{InputEvent::InputType::None, ""},
{InputEvent::InputType::InsertText, "insertText"},
{InputEvent::InputType::InsertNonText, "insertNonText"},
{InputEvent::InputType::InsertLineBreak, "insertLineBreak"},
{InputEvent::InputType::InsertParagraph, "insertParagraph"},
{InputEvent::InputType::InsertOrderedList, "insertOrderedList"},
{InputEvent::InputType::InsertUnorderedList, "insertUnorderedList"},
{InputEvent::InputType::InsertHorizontalRule, "insertHorizontalRule"},
{InputEvent::InputType::InsertFromPaste, "insertFromPaste"},
{InputEvent::InputType::InsertFromDrop, "insertFromDrop"},
{InputEvent::InputType::DeleteComposedCharacterForward,
"deleteComposedCharacterForward"},
{InputEvent::InputType::DeleteComposedCharacterBackward,
"deleteComposedCharacterBackward"},
{InputEvent::InputType::DeleteWordBackward, "deleteWordBackward"},
{InputEvent::InputType::DeleteWordForward, "deleteWordForward"},
{InputEvent::InputType::DeleteLineBackward, "deleteLineBackward"},
{InputEvent::InputType::DeleteLineForward, "deleteLineForward"},
{InputEvent::InputType::DeleteContentBackward, "deleteContentBackward"},
{InputEvent::InputType::DeleteContentForward, "deleteContentForward"},
{InputEvent::InputType::DeleteByCut, "deleteByCut"},
{InputEvent::InputType::DeleteByDrag, "deleteByDrag"},
{InputEvent::InputType::Undo, "undo"},
{InputEvent::InputType::Redo, "redo"},
{InputEvent::InputType::Bold, "bold"},
{InputEvent::InputType::Italic, "italic"},
{InputEvent::InputType::Underline, "underline"},
{InputEvent::InputType::StrikeThrough, "strikeThrough"},
{InputEvent::InputType::Superscript, "superscript"},
{InputEvent::InputType::Subscript, "subscript"},
{InputEvent::InputType::JustifyCenter, "justifyCenter"},
{InputEvent::InputType::JustifyRight, "justifyRight"},
{InputEvent::InputType::JustifyLeft, "justifyLeft"},
{InputEvent::InputType::Indent, "indent"},
{InputEvent::InputType::Outdent, "outdent"},
{InputEvent::InputType::RemoveFormat, "removeFormat"},
{InputEvent::InputType::JustifyFull, "justifyFull"},
{InputEvent::InputType::SetColor, "setColor"},
{InputEvent::InputType::SetBackgroundColor, "setBackgroundColor"},
{InputEvent::InputType::SetFont, "setFont"},
{InputEvent::InputType::ChangeAttributes, "changeAttributes"},
{InputEvent::InputType::SetWritingDirection, "setWritingDirection"},
{InputEvent::InputType::Unscript, "unscript"},
{InputEvent::InputType::CreateLink, "createLink"},
{InputEvent::InputType::Unlink, "unlink"},
{InputEvent::InputType::FormatBlock, "formatBlock"},
};
static_assert(
arraysize(kInputTypeStringNameMap) ==
static_cast<size_t>(InputEvent::InputType::NumberOfInputTypes),
"must handle all InputEvent::InputType");
String convertInputTypeToString(InputEvent::InputType inputType) {
const auto& it =
std::begin(kInputTypeStringNameMap) + static_cast<size_t>(inputType);
if (it >= std::begin(kInputTypeStringNameMap) &&
it < std::end(kInputTypeStringNameMap))
return AtomicString(it->stringName);
return emptyString();
}
InputEvent::InputType convertStringToInputType(const String& stringName) {
// TODO(chongz): Use binary search if the map goes larger.
for (const auto& entry : kInputTypeStringNameMap) {
if (stringName == entry.stringName)
return entry.inputType;
}
return InputEvent::InputType::None;
}
} // anonymous namespace
InputEvent::InputEvent(const AtomicString& type,
const InputEventInit& initializer)
: UIEvent(type, initializer) {
// TODO(ojan): We should find a way to prevent conversion like
// String->enum->String just in order to use initializer.
// See InputEvent::createBeforeInput() for the first conversion.
if (initializer.hasInputType())
m_inputType = convertStringToInputType(initializer.inputType());
if (initializer.hasData())
m_data = initializer.data();
if (initializer.hasDataTransfer())
m_dataTransfer = initializer.dataTransfer();
if (initializer.hasIsComposing())
m_isComposing = initializer.isComposing();
if (initializer.hasRanges())
m_ranges = initializer.ranges();
}
/* static */
InputEvent* InputEvent::createBeforeInput(InputType inputType,
const String& data,
EventCancelable cancelable,
EventIsComposing isComposing,
const RangeVector* ranges) {
InputEventInit inputEventInit;
inputEventInit.setBubbles(true);
inputEventInit.setCancelable(cancelable == IsCancelable);
// TODO(ojan): We should find a way to prevent conversion like
// String->enum->String just in order to use initializer.
// See InputEvent::InputEvent() for the second conversion.
inputEventInit.setInputType(convertInputTypeToString(inputType));
inputEventInit.setData(data);
inputEventInit.setIsComposing(isComposing == IsComposing);
if (ranges)
inputEventInit.setRanges(*ranges);
return InputEvent::create(EventTypeNames::beforeinput, inputEventInit);
}
/* static */
InputEvent* InputEvent::createBeforeInput(InputType inputType,
DataTransfer* dataTransfer,
EventCancelable cancelable,
EventIsComposing isComposing,
const RangeVector* ranges) {
InputEventInit inputEventInit;
inputEventInit.setBubbles(true);
inputEventInit.setCancelable(cancelable == IsCancelable);
inputEventInit.setInputType(convertInputTypeToString(inputType));
inputEventInit.setDataTransfer(dataTransfer);
inputEventInit.setIsComposing(isComposing == IsComposing);
if (ranges)
inputEventInit.setRanges(*ranges);
return InputEvent::create(EventTypeNames::beforeinput, inputEventInit);
}
/* static */
InputEvent* InputEvent::createInput(InputType inputType,
const String& data,
EventIsComposing isComposing,
const RangeVector* ranges) {
InputEventInit inputEventInit;
inputEventInit.setBubbles(true);
inputEventInit.setCancelable(false);
// TODO(ojan): We should find a way to prevent conversion like
// String->enum->String just in order to use initializer.
// See InputEvent::InputEvent() for the second conversion.
inputEventInit.setInputType(convertInputTypeToString(inputType));
inputEventInit.setData(data);
inputEventInit.setIsComposing(isComposing == IsComposing);
if (ranges)
inputEventInit.setRanges(*ranges);
return InputEvent::create(EventTypeNames::input, inputEventInit);
}
String InputEvent::inputType() const {
return convertInputTypeToString(m_inputType);
}
StaticRangeVector InputEvent::getTargetRanges() const {
StaticRangeVector staticRanges;
for (const auto& range : m_ranges)
staticRanges.append(StaticRange::create(
range->ownerDocument(), range->startContainer(), range->startOffset(),
range->endContainer(), range->endOffset()));
return staticRanges;
}
bool InputEvent::isInputEvent() const {
return true;
}
// TODO(chongz): We should get rid of this |EventDispatchMediator| pattern and
// introduce simpler interface such as |beforeDispatchEvent()| and
// |afterDispatchEvent()| virtual methods.
EventDispatchMediator* InputEvent::createMediator() {
return InputEventDispatchMediator::create(this);
}
DEFINE_TRACE(InputEvent) {
UIEvent::trace(visitor);
visitor->trace(m_dataTransfer);
visitor->trace(m_ranges);
}
InputEventDispatchMediator* InputEventDispatchMediator::create(
InputEvent* inputEvent) {
return new InputEventDispatchMediator(inputEvent);
}
InputEventDispatchMediator::InputEventDispatchMediator(InputEvent* inputEvent)
: EventDispatchMediator(inputEvent) {}
InputEvent& InputEventDispatchMediator::event() const {
return toInputEvent(EventDispatchMediator::event());
}
DispatchEventResult InputEventDispatchMediator::dispatchEvent(
EventDispatcher& dispatcher) const {
DispatchEventResult result = dispatcher.dispatch();
// It's weird to hold and clear live |Range| objects internally, and only
// expose |StaticRange| through |getTargetRanges()|. However there is no
// better solutions due to the following issues:
// 1. We don't want to expose live |Range| objects for the author to hold as
// it will slow down all DOM operations. So we just expose |StaticRange|.
// 2. Event handlers in chain might modify DOM, which means we have to keep
// a copy of live |Range| internally and return snapshots.
// 3. We don't want authors to hold live |Range| indefinitely by holding
// |InputEvent|, so we clear them after dispatch.
// Authors should explicitly call |getTargetRanges()|->|toRange()| if they
// want to keep a copy of |Range|. See Editing TF meeting notes:
// https://docs.google.com/document/d/1hCj6QX77NYIVY0RWrMHT1Yra6t8_Qu8PopaWLG0AM58/edit?usp=sharing
event().m_ranges.clear();
return result;
}
} // namespace blink