Bug 1119609 part.1 Implement key event dispatcher in TextEventDispatcher r=smaug
authorMasayuki Nakano <masayuki@d-toybox.com>
Thu, 19 Feb 2015 15:50:18 +0900
changeset 229861 e3ab2f41fa6382ffee00ed6dae9ddd5c89b0d9e3
parent 229860 6ab4d5d7fec67b880ede9db0d932151b823555c9
child 229862 eb629fa95d06928dbf1cde8edbbd34893421daed
push id11399
push userryanvm@gmail.com
push dateFri, 20 Feb 2015 00:03:38 +0000
treeherderfx-team@51458a066fda [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssmaug
bugs1119609
milestone38.0a1
Bug 1119609 part.1 Implement key event dispatcher in TextEventDispatcher r=smaug
widget/TextEventDispatcher.cpp
widget/TextEventDispatcher.h
widget/TextEvents.h
widget/WidgetEventImpl.cpp
--- 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