Bug 1119609 part.1 Implement key event dispatcher in TextEventDispatcher r=smaug
--- a/widget/TextEventDispatcher.cpp
+++ b/widget/TextEventDispatcher.cpp
@@ -88,19 +88,20 @@ TextEventDispatcher::GetState() const
}
if (!mWidget || mWidget->Destroyed()) {
return NS_ERROR_NOT_AVAILABLE;
}
return NS_OK;
}
void
-TextEventDispatcher::InitEvent(WidgetCompositionEvent& aEvent) const
+TextEventDispatcher::InitEvent(WidgetGUIEvent& aEvent) const
{
aEvent.time = PR_IntervalNow();
+ aEvent.refPoint = LayoutDeviceIntPoint(0, 0);
aEvent.mFlags.mIsSynthesizedForTests = mForTests;
}
nsresult
TextEventDispatcher::StartComposition(nsEventStatus& aStatus)
{
aStatus = nsEventStatus_eIgnore;
@@ -219,16 +220,142 @@ TextEventDispatcher::NotifyIME(const IME
// notification or request for now. In this case, we should return
// NS_ERROR_NOT_IMPLEMENTED because it's not implemented at such moment.
if (rv == NS_ERROR_NOT_AVAILABLE) {
return NS_ERROR_NOT_IMPLEMENTED;
}
return rv;
}
+bool
+TextEventDispatcher::DispatchKeyboardEvent(
+ uint32_t aMessage,
+ const WidgetKeyboardEvent& aKeyboardEvent,
+ nsEventStatus& aStatus)
+{
+ return DispatchKeyboardEventInternal(aMessage, aKeyboardEvent, aStatus);
+}
+
+bool
+TextEventDispatcher::DispatchKeyboardEventInternal(
+ uint32_t aMessage,
+ const WidgetKeyboardEvent& aKeyboardEvent,
+ nsEventStatus& aStatus,
+ uint32_t aIndexOfKeypress)
+{
+ MOZ_ASSERT(aMessage == NS_KEY_DOWN || aMessage == NS_KEY_UP ||
+ aMessage == NS_KEY_PRESS, "Invalid aMessage value");
+ nsresult rv = GetState();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return false;
+ }
+
+ // If the key shouldn't cause keypress events, don't this patch them.
+ if (aMessage == NS_KEY_PRESS && !aKeyboardEvent.ShouldCauseKeypressEvents()) {
+ return false;
+ }
+
+ nsCOMPtr<nsIWidget> widget(mWidget);
+
+ WidgetKeyboardEvent keyEvent(true, aMessage, widget);
+ InitEvent(keyEvent);
+ keyEvent.AssignKeyEventData(aKeyboardEvent, false);
+
+ if (aStatus == nsEventStatus_eConsumeNoDefault) {
+ // If the key event should be dispatched as consumed event, marking it here.
+ // This is useful to prevent double action. E.g., when the key was already
+ // handled by system, our chrome shouldn't handle it.
+ keyEvent.mFlags.mDefaultPrevented = true;
+ }
+
+ // Corrects each member for the specific key event type.
+ if (aMessage == NS_KEY_DOWN || aMessage == NS_KEY_UP) {
+ MOZ_ASSERT(!aIndexOfKeypress,
+ "aIndexOfKeypress must be 0 for either NS_KEY_DOWN or NS_KEY_UP");
+ // charCode of keydown and keyup should be 0.
+ keyEvent.charCode = 0;
+ } else if (keyEvent.mKeyNameIndex != KEY_NAME_INDEX_USE_STRING) {
+ MOZ_ASSERT(!aIndexOfKeypress,
+ "aIndexOfKeypress must be 0 for NS_KEY_PRESS of non-printable key");
+ // If keypress event isn't caused by printable key, its charCode should
+ // be 0.
+ keyEvent.charCode = 0;
+ } else {
+ MOZ_RELEASE_ASSERT(
+ !aIndexOfKeypress || aIndexOfKeypress < keyEvent.mKeyValue.Length(),
+ "aIndexOfKeypress must be 0 - mKeyValue.Length() - 1");
+ keyEvent.keyCode = 0;
+ wchar_t ch =
+ keyEvent.mKeyValue.IsEmpty() ? 0 : keyEvent.mKeyValue[aIndexOfKeypress];
+ keyEvent.charCode = static_cast<uint32_t>(ch);
+ if (ch) {
+ keyEvent.mKeyValue.Assign(ch);
+ } else {
+ keyEvent.mKeyValue.Truncate();
+ }
+ }
+ if (aMessage == NS_KEY_UP) {
+ // mIsRepeat of keyup event must be false.
+ keyEvent.mIsRepeat = false;
+ }
+ // mIsComposing should be initialized later.
+ keyEvent.mIsComposing = false;
+ // XXX Currently, we don't support to dispatch key event with native key
+ // event information.
+ keyEvent.mNativeKeyEvent = nullptr;
+ // XXX Currently, we don't support to dispatch key events with data for
+ // plugins.
+ keyEvent.mPluginEvent.Clear();
+ // TODO: Manage mUniqueId here.
+
+ widget->DispatchEvent(&keyEvent, aStatus);
+ return true;
+}
+
+bool
+TextEventDispatcher::MaybeDispatchKeypressEvents(
+ const WidgetKeyboardEvent& aKeyboardEvent,
+ nsEventStatus& aStatus)
+{
+ // If the key event was consumed, keypress event shouldn't be fired.
+ if (aStatus == nsEventStatus_eConsumeNoDefault) {
+ return false;
+ }
+
+ // If the key isn't a printable key or just inputting one character or
+ // no character, we should dispatch only one keypress. Otherwise, i.e.,
+ // if the key is a printable key and inputs multiple characters, keypress
+ // event should be dispatched the count of inputting characters times.
+ size_t keypressCount =
+ aKeyboardEvent.mKeyNameIndex != KEY_NAME_INDEX_USE_STRING ?
+ 1 : std::max(static_cast<nsAString::size_type>(1),
+ aKeyboardEvent.mKeyValue.Length());
+ bool isDispatched = false;
+ bool consumed = false;
+ for (size_t i = 0; i < keypressCount; i++) {
+ aStatus = nsEventStatus_eIgnore;
+ if (!DispatchKeyboardEventInternal(NS_KEY_PRESS, aKeyboardEvent,
+ aStatus, i)) {
+ // The widget must have been gone.
+ break;
+ }
+ isDispatched = true;
+ if (!consumed) {
+ consumed = (aStatus == nsEventStatus_eConsumeNoDefault);
+ }
+ }
+
+ // If one of the keypress event was consumed, return ConsumeNoDefault.
+ if (consumed) {
+ aStatus = nsEventStatus_eConsumeNoDefault;
+ }
+
+ return isDispatched;
+}
+
/******************************************************************************
* TextEventDispatcher::PendingComposition
*****************************************************************************/
TextEventDispatcher::PendingComposition::PendingComposition()
{
Clear();
}
--- a/widget/TextEventDispatcher.h
+++ b/widget/TextEventDispatcher.h
@@ -18,18 +18,18 @@ class nsIWidget;
namespace mozilla {
namespace widget {
struct IMENotification;
/**
* TextEventDispatcher is a helper class for dispatching widget events defined
* in TextEvents.h. Currently, this is a helper for dispatching
- * WidgetCompositionEvent. However, WidgetKeyboardEvent and/or
- * WidgetQueryContentEvent may be supported by this class in the future.
+ * WidgetCompositionEvent and WidgetKeyboardEvent. This manages the behavior
+ * of them for conforming to DOM Level 3 Events.
* An instance of this class is created by nsIWidget instance and owned by it.
* This is typically created only by the top level widgets because only they
* handle IME.
*/
class TextEventDispatcher MOZ_FINAL
{
~TextEventDispatcher()
@@ -154,16 +154,52 @@ public:
return mPendingComposition.Flush(this, aStatus);
}
/**
* @see nsIWidget::NotifyIME()
*/
nsresult NotifyIME(const IMENotification& aIMENotification);
+ /**
+ * DispatchKeyboardEvent() maybe dispatches aKeyboardEvent.
+ *
+ * @param aMessage Must be NS_KEY_DOWN or NS_KEY_UP.
+ * Use MaybeDispatchKeypressEvents() for dispatching
+ * NS_KEY_PRESS.
+ * @param aKeyboardEvent A keyboard event.
+ * @param aStatus If dispatching event should be marked as consumed,
+ * set nsEventStatus_eConsumeNoDefault. Otherwise,
+ * set nsEventStatus_eIgnore. After dispatching
+ * a event and it's consumed this returns
+ * nsEventStatus_eConsumeNoDefault.
+ * @return true if an event is dispatched. Otherwise, false.
+ */
+ bool DispatchKeyboardEvent(uint32_t aMessage,
+ const WidgetKeyboardEvent& aKeyboardEvent,
+ nsEventStatus& aStatus);
+
+ /**
+ * MaybeDispatchKeypressEvents() maybe dispatches a keypress event which is
+ * generated from aKeydownEvent.
+ *
+ * @param aKeyboardEvent A keyboard event.
+ * @param aStatus Sets the result when the caller dispatches
+ * aKeyboardEvent. Note that if the value is
+ * nsEventStatus_eConsumeNoDefault, this does NOT
+ * dispatch keypress events.
+ * When this method dispatches one or more keypress
+ * events and one of them is consumed, this returns
+ * nsEventStatus_eConsumeNoDefault.
+ * @return true if one or more events are dispatched.
+ * Otherwise, false.
+ */
+ bool MaybeDispatchKeypressEvents(const WidgetKeyboardEvent& aKeyboardEvent,
+ nsEventStatus& aStatus);
+
private:
// mWidget is owner of the instance. When this is created, this is set.
// And when mWidget is released, this is cleared by OnDestroyWidget().
// Note that mWidget may be destroyed already (i.e., mWidget->Destroyed() may
// return true).
nsIWidget* mWidget;
// mListener is a weak reference to TextEventDispatcherListener. That might
// be referred by JS. Therefore, the listener might be difficult to release
@@ -202,31 +238,57 @@ private:
nsresult BeginInputTransactionInternal(
TextEventDispatcherListener* aListener,
bool aForTests);
/**
* InitEvent() initializes aEvent. This must be called before dispatching
* the event.
*/
- void InitEvent(WidgetCompositionEvent& aEvent) const;
+ void InitEvent(WidgetGUIEvent& aEvent) const;
/**
* StartCompositionAutomaticallyIfNecessary() starts composition if it hasn't
* been started it yet.
*
* @param aStatus If it succeeded to start composition normally, this
* returns nsEventStatus_eIgnore. Otherwise, e.g.,
* the composition is canceled during dispatching
* compositionstart event, this returns
* nsEventStatus_eConsumeNoDefault. In this case,
* the caller shouldn't keep doing its job.
* @return Only when something unexpected occurs, this returns
* an error. Otherwise, returns NS_OK even if aStatus
* is nsEventStatus_eConsumeNoDefault.
*/
nsresult StartCompositionAutomaticallyIfNecessary(nsEventStatus& aStatus);
+
+ /**
+ * DispatchKeyboardEventInternal() maybe dispatches aKeyboardEvent.
+ *
+ * @param aMessage Must be NS_KEY_DOWN, NS_KEY_UP or NS_KEY_PRESS.
+ * @param aKeyboardEvent A keyboard event. If aMessage is NS_KEY_PRESS and
+ * the event is for second or later character, its
+ * mKeyValue should be empty string.
+ * @param aStatus If dispatching event should be marked as consumed,
+ * set nsEventStatus_eConsumeNoDefault. Otherwise,
+ * set nsEventStatus_eIgnore. After dispatching
+ * a event and it's consumed this returns
+ * nsEventStatus_eConsumeNoDefault.
+ * @param aIndexOfKeypress This must be 0 if aMessage isn't NS_KEY_PRESS or
+ * aKeyboard.mKeyNameIndex isn't
+ * KEY_NAME_INDEX_USE_STRING. Otherwise, i.e.,
+ * when an NS_KEY_PRESS event causes inputting
+ * text, this must be between 0 and
+ * mKeyValue.Length() - 1 since keypress events
+ * sending only one character per event.
+ * @return true if an event is dispatched. Otherwise, false.
+ */
+ bool DispatchKeyboardEventInternal(uint32_t aMessage,
+ const WidgetKeyboardEvent& aKeyboardEvent,
+ nsEventStatus& aStatus,
+ uint32_t aIndexOfKeypress = 0);
};
} // namespace widget
} // namespace mozilla
#endif // #ifndef mozilla_widget_textcompositionsynthesizer_h_
--- a/widget/TextEvents.h
+++ b/widget/TextEvents.h
@@ -159,16 +159,20 @@ public:
#ifdef XP_MACOSX
// Values given by a native NSEvent, for use with Cocoa NPAPI plugins.
uint16_t mNativeKeyCode;
uint32_t mNativeModifierFlags;
nsString mNativeCharacters;
nsString mNativeCharactersIgnoringModifiers;
#endif
+ // If the key should cause keypress events, this returns true.
+ // Otherwise, false.
+ bool ShouldCauseKeypressEvents() const;
+
void GetDOMKeyName(nsAString& aKeyName)
{
if (mKeyNameIndex == KEY_NAME_INDEX_USE_STRING) {
aKeyName = mKeyValue;
return;
}
GetDOMKeyName(mKeyNameIndex, aKeyName);
}
--- a/widget/WidgetEventImpl.cpp
+++ b/widget/WidgetEventImpl.cpp
@@ -284,16 +284,42 @@ WidgetInputEvent::AccelModifier()
}
return sAccelModifier;
}
/******************************************************************************
* mozilla::WidgetKeyboardEvent (TextEvents.h)
******************************************************************************/
+bool
+WidgetKeyboardEvent::ShouldCauseKeypressEvents() const
+{
+ // Currently, we don't dispatch keypress events of modifier keys.
+ switch (mKeyNameIndex) {
+ case KEY_NAME_INDEX_Alt:
+ case KEY_NAME_INDEX_AltGraph:
+ case KEY_NAME_INDEX_CapsLock:
+ case KEY_NAME_INDEX_Control:
+ case KEY_NAME_INDEX_Fn:
+ // case KEY_NAME_INDEX_FnLock:
+ // case KEY_NAME_INDEX_Hyper:
+ case KEY_NAME_INDEX_Meta:
+ case KEY_NAME_INDEX_NumLock:
+ case KEY_NAME_INDEX_OS:
+ case KEY_NAME_INDEX_ScrollLock:
+ case KEY_NAME_INDEX_Shift:
+ // case KEY_NAME_INDEX_Super:
+ case KEY_NAME_INDEX_Symbol:
+ // case KEY_NAME_INDEX_SymbolLock:
+ return false;
+ default:
+ return true;
+ }
+}
+
/*static*/ void
WidgetKeyboardEvent::GetDOMKeyName(KeyNameIndex aKeyNameIndex,
nsAString& aKeyName)
{
// The expected way to implement this function would be to use a
// switch statement. By using a table-based implementation, below, we
// ensure that this function executes in constant time in cases where
// compilers wouldn't be able to convert the switch statement to a