Bug 685073 part.1 Manage nested key events for IME r=smichaud
authorMasayuki Nakano <masayuki@d-toybox.com>
Fri, 30 Sep 2011 15:17:32 +0900
changeset 77904 9c0aad93edc4
parent 77903 78dd488fa082
child 77905 e734bd300268
push id3
push userfelipc@gmail.com
push dateFri, 30 Sep 2011 20:09:13 +0000
reviewerssmichaud
bugs685073
milestone10.0a1
Bug 685073 part.1 Manage nested key events for IME r=smichaud
widget/src/cocoa/TextInputHandler.h
widget/src/cocoa/TextInputHandler.mm
--- a/widget/src/cocoa/TextInputHandler.h
+++ b/widget/src/cocoa/TextInputHandler.h
@@ -477,19 +477,29 @@ protected:
     NSEvent* mKeyEvent;
     // Whether keydown event was consumed by web contents or chrome contents.
     bool mKeyDownHandled;
     // Whether keypress event was dispatched for mKeyEvent.
     bool mKeyPressDispatched;
     // Whether keypress event was consumed by web contents or chrome contents.
     bool mKeyPressHandled;
 
-    KeyEventState() : mKeyEvent(nsnull)
+    KeyEventState(NSEvent* aNativeKeyEvent) : mKeyEvent(nsnull)
     {
       Clear();
+      Set(aNativeKeyEvent);
+    }
+
+    KeyEventState(const KeyEventState &aOther) : mKeyEvent(nsnull)
+    {
+      Clear();
+      mKeyEvent = [aOther.mKeyEvent retain];
+      mKeyDownHandled = aOther.mKeyDownHandled;
+      mKeyPressDispatched = aOther.mKeyPressDispatched;
+      mKeyPressHandled = aOther.mKeyPressHandled;
     }
 
     ~KeyEventState()
     {
       Clear();
     }
 
     void Set(NSEvent* aNativeKeyEvent)
@@ -509,40 +519,70 @@ protected:
       mKeyPressDispatched = PR_FALSE;
       mKeyPressHandled = PR_FALSE;
     }
 
     bool KeyDownOrPressHandled()
     {
       return mKeyDownHandled || mKeyPressHandled;
     }
+
+  protected:
+    KeyEventState()
+    {
+    }    
   };
 
   /**
    * Helper class for guaranteeing cleaning mCurrentKeyEvent
    */
   class AutoKeyEventStateCleaner
   {
   public:
     AutoKeyEventStateCleaner(TextInputHandlerBase* aHandler) :
       mHandler(aHandler)
     {
     }
 
     ~AutoKeyEventStateCleaner()
     {
-      mHandler->mCurrentKeyEvent.Clear();
+      NS_ASSERTION(mHandler->mCurrentKeyEvents.Length() > 0,
+                   "The key event was removed by manually?");
+      mHandler->mCurrentKeyEvents.RemoveElementAt(0);
     }
   private:
-    TextInputHandlerBase* mHandler;
+    nsRefPtr<TextInputHandlerBase> mHandler;
   };
 
-  // XXX If keydown event was nested, the key event is overwritten by newer
-  //     event.  This is wrong behavior.  Some IMEs are making such situation.
-  KeyEventState mCurrentKeyEvent;
+  /**
+   * mCurrentKeyEvents stores all key events which are being processed.
+   * When we call interpretKeyEvents, IME may generate other key events.
+   * mCurrentKeyEvents[0] is the latest key event.
+   */
+  nsTArray<KeyEventState> mCurrentKeyEvents;
+
+  /**
+   *
+   */
+  KeyEventState* PushKeyEvent(NSEvent* aNativeKeyEvent)
+  {
+    KeyEventState keyEventState(aNativeKeyEvent);
+    return mCurrentKeyEvents.InsertElementAt(0, keyEventState);
+  }
+
+  /**
+   * GetCurrentKeyEvent() returns current processing key event.
+   */
+  KeyEventState* GetCurrentKeyEvent()
+  {
+    if (mCurrentKeyEvents.Length() == 0) {
+      return nsnull;
+    }
+    return &mCurrentKeyEvents[0];
+  }
 
   /**
    * IsPrintableChar() checks whether the unicode character is
    * a non-printable ASCII character or not.  Note that this returns
    * TRUE even if aChar is a non-printable UNICODE character.
    *
    * @param aChar                 A unicode character.
    * @return                      TRUE if aChar is a printable ASCII character
@@ -1114,17 +1154,18 @@ public:
    *
    * @return                      TRUE if keypress event for latest native key
    *                              event was handled.  Otherwise, FALSE.
    *                              If this handler isn't handling any key events,
    *                              always returns FALSE.
    */
   bool KeyPressWasHandled()
   {
-    return mCurrentKeyEvent.mKeyPressHandled;
+    KeyEventState* currentKeyEvent = GetCurrentKeyEvent();
+    return currentKeyEvent && currentKeyEvent->mKeyPressHandled;
   }
 
 protected:
   /**
    * DispatchKeyEventForFlagsChanged() dispatches keydown event or keyup event
    * for the aNativeEvent.
    *
    * @param aNativeEvent          A native flagschanged event which you want to
--- a/widget/src/cocoa/TextInputHandler.mm
+++ b/widget/src/cocoa/TextInputHandler.mm
@@ -992,17 +992,17 @@ TextInputHandler::HandleKeyDownEvent(NSE
      "charactersIgnoringModifiers=\"%s\"",
      this, aNativeEvent, GetNativeKeyEventType(aNativeEvent),
      [aNativeEvent keyCode], [aNativeEvent keyCode],
      [aNativeEvent modifierFlags], GetCharacters([aNativeEvent characters]),
      GetCharacters([aNativeEvent charactersIgnoringModifiers])));
 
   nsRefPtr<nsChildView> kungFuDeathGrip(mWidget);
 
-  mCurrentKeyEvent.Set(aNativeEvent);
+  KeyEventState* currentKeyEvent = PushKeyEvent(aNativeEvent);
   AutoKeyEventStateCleaner remover(this);
 
   BOOL nonDeadKeyPress = [[aNativeEvent characters] length] > 0;
   if (nonDeadKeyPress && !IsIMEComposing()) {
     NSResponder* firstResponder = [[mView window] firstResponder];
 
     nsKeyEvent keydownEvent(PR_TRUE, NS_KEY_DOWN, mWidget);
     InitKeyEvent(aNativeEvent, keydownEvent);
@@ -1010,32 +1010,32 @@ TextInputHandler::HandleKeyDownEvent(NSE
 #ifndef NP_NO_CARBON
     EventRecord carbonEvent;
     if ([mView pluginEventModel] == NPEventModelCarbon) {
       ConvertCocoaKeyEventToCarbonEvent(aNativeEvent, carbonEvent, PR_TRUE);
       keydownEvent.pluginEvent = &carbonEvent;
     }
 #endif // #ifndef NP_NO_CARBON
 
-    mCurrentKeyEvent.mKeyDownHandled = DispatchEvent(keydownEvent);
+    currentKeyEvent->mKeyDownHandled = DispatchEvent(keydownEvent);
     if (Destroyed()) {
       PR_LOG(gLog, PR_LOG_ALWAYS,
         ("%p TextInputHandler::HandleKeyDownEvent, "
          "widget was destroyed by keydown event", this));
-      return mCurrentKeyEvent.KeyDownOrPressHandled();
+      return currentKeyEvent->KeyDownOrPressHandled();
     }
 
     // The key down event may have shifted the focus, in which
     // case we should not fire the key press.
     // XXX This is a special code only on Cocoa widget, why is this needed?
     if (firstResponder != [[mView window] firstResponder]) {
       PR_LOG(gLog, PR_LOG_ALWAYS,
         ("%p TextInputHandler::HandleKeyDownEvent, "
          "view lost focus by keydown event", this));
-      return mCurrentKeyEvent.KeyDownOrPressHandled();
+      return currentKeyEvent->KeyDownOrPressHandled();
     }
 
     // If this is the context menu key command, send a context menu key event.
     NSUInteger modifierFlags =
       [aNativeEvent modifierFlags] & NSDeviceIndependentModifierFlagsMask;
     if (modifierFlags == NSControlKeyMask &&
         [[aNativeEvent charactersIgnoringModifiers] isEqualToString:@" "]) {
       nsMouseEvent contextMenuEvent(PR_TRUE, NS_CONTEXTMENU,
@@ -1047,40 +1047,40 @@ TextInputHandler::HandleKeyDownEvent(NSE
       bool cmEventHandled = DispatchEvent(contextMenuEvent);
       PR_LOG(gLog, PR_LOG_ALWAYS,
         ("%p TextInputHandler::HandleKeyDownEvent, "
          "context menu event dispatched, handled=%s%s",
          this, TrueOrFalse(cmEventHandled),
          Destroyed() ? " and widget was destroyed" : ""));
       [mView maybeInitContextMenuTracking];
       // Bail, there is nothing else to do here.
-      return (cmEventHandled || mCurrentKeyEvent.KeyDownOrPressHandled());
+      return (cmEventHandled || currentKeyEvent->KeyDownOrPressHandled());
     }
 
     nsKeyEvent keypressEvent(PR_TRUE, NS_KEY_PRESS, mWidget);
     InitKeyEvent(aNativeEvent, keypressEvent);
 
     // if this is a non-letter keypress, or the control key is down,
     // dispatch the keydown to gecko, so that we trap delete,
     // control-letter combinations etc before Cocoa tries to use
     // them for keybindings.
     // XXX This is wrong. IME may be handle the non-letter keypress event as
     //     its owning shortcut key.  See bug 477291.
     if ((!keypressEvent.isChar || keypressEvent.isControl) &&
         !IsIMEComposing()) {
-      if (mCurrentKeyEvent.mKeyDownHandled) {
+      if (currentKeyEvent->mKeyDownHandled) {
         keypressEvent.flags |= NS_EVENT_FLAG_NO_DEFAULT;
       }
-      mCurrentKeyEvent.mKeyPressHandled = DispatchEvent(keypressEvent);
-      mCurrentKeyEvent.mKeyPressDispatched = PR_TRUE;
+      currentKeyEvent->mKeyPressHandled = DispatchEvent(keypressEvent);
+      currentKeyEvent->mKeyPressDispatched = PR_TRUE;
       if (Destroyed()) {
         PR_LOG(gLog, PR_LOG_ALWAYS,
           ("%p TextInputHandler::HandleKeyDownEvent, "
            "widget was destroyed by keypress event", this));
-        return mCurrentKeyEvent.KeyDownOrPressHandled();
+        return currentKeyEvent->KeyDownOrPressHandled();
       }
     }
   }
 
   // Let Cocoa interpret the key events, caching IsIMEComposing first.
   bool wasComposing = IsIMEComposing();
   bool interpretKeyEventsCalled = false;
   if (IsIMEEnabled() || IsASCIICapableOnly()) {
@@ -1093,25 +1093,25 @@ TextInputHandler::HandleKeyDownEvent(NSE
       ("%p TextInputHandler::HandleKeyDownEvent, called interpretKeyEvents",
        this));
   }
 
   if (Destroyed()) {
     PR_LOG(gLog, PR_LOG_ALWAYS,
       ("%p TextInputHandler::HandleKeyDownEvent, widget was destroyed",
        this));
-    return mCurrentKeyEvent.KeyDownOrPressHandled();
+    return currentKeyEvent->KeyDownOrPressHandled();
   }
 
   PR_LOG(gLog, PR_LOG_ALWAYS,
     ("%p TextInputHandler::HandleKeyDownEvent, wasComposing=%s, "
      "IsIMEComposing()=%s",
      this, TrueOrFalse(wasComposing), TrueOrFalse(IsIMEComposing())));
 
-  if (!mCurrentKeyEvent.mKeyPressDispatched && nonDeadKeyPress &&
+  if (!currentKeyEvent->mKeyPressDispatched && nonDeadKeyPress &&
       !wasComposing && !IsIMEComposing()) {
     nsKeyEvent keypressEvent(PR_TRUE, NS_KEY_PRESS, mWidget);
     InitKeyEvent(aNativeEvent, keypressEvent);
 
     // If we called interpretKeyEvents and this isn't normal character input
     // then IME probably ate the event for some reason. We do not want to
     // send a key press event in that case.
     // TODO:
@@ -1119,34 +1119,34 @@ TextInputHandler::HandleKeyDownEvent(NSE
     // 1. If key events were nested during calling interpretKeyEvents, it means
     //    that IME did something.  Then, we should do nothing.
     // 2. If one or more commands are called like "deleteBackward", we should
     //    dispatch keypress event at that time.  Note that the command may have
     //    been a converted or generated action by IME.  Then, we shouldn't do
     //    our default action for this key.
     if (!(interpretKeyEventsCalled &&
           IsNormalCharInputtingEvent(keypressEvent))) {
-      if (mCurrentKeyEvent.mKeyDownHandled) {
+      if (currentKeyEvent->mKeyDownHandled) {
         keypressEvent.flags |= NS_EVENT_FLAG_NO_DEFAULT;
       }
-      mCurrentKeyEvent.mKeyPressHandled = DispatchEvent(keypressEvent);
+      currentKeyEvent->mKeyPressHandled = DispatchEvent(keypressEvent);
       PR_LOG(gLog, PR_LOG_ALWAYS,
         ("%p TextInputHandler::HandleKeyDownEvent, keypress event dispatched",
          this));
     }
   }
 
   // Note: mWidget might have become null here. Don't count on it from here on.
 
   PR_LOG(gLog, PR_LOG_ALWAYS,
     ("%p TextInputHandler::HandleKeyDownEvent, "
      "keydown handled=%s, keypress handled=%s",
-     this, TrueOrFalse(mCurrentKeyEvent.mKeyDownHandled),
-     TrueOrFalse(mCurrentKeyEvent.mKeyPressHandled)));
-  return mCurrentKeyEvent.KeyDownOrPressHandled();
+     this, TrueOrFalse(currentKeyEvent->mKeyDownHandled),
+     TrueOrFalse(currentKeyEvent->mKeyPressHandled)));
+  return currentKeyEvent->KeyDownOrPressHandled();
 
   NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(PR_FALSE);
 }
 
 void
 TextInputHandler::HandleKeyUpEvent(NSEvent* aNativeEvent)
 {
   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
@@ -1307,23 +1307,27 @@ void
 TextInputHandler::InsertText(NSAttributedString *aAttrString)
 {
   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
 
   if (Destroyed()) {
     return;
   }
 
+  KeyEventState* currentKeyEvent = GetCurrentKeyEvent();
+
   PR_LOG(gLog, PR_LOG_ALWAYS,
     ("%p TextInputHandler::InsertText, aAttrString=\"%s\", "
      "IsIMEComposing()=%s, IgnoreIMEComposition()=%s, "
      "keyevent=%p, keypressDispatched=%s",
      this, GetCharacters([aAttrString string]), TrueOrFalse(IsIMEComposing()),
-     TrueOrFalse(IgnoreIMEComposition()), mCurrentKeyEvent.mKeyEvent,
-     TrueOrFalse(mCurrentKeyEvent.mKeyPressDispatched)));
+     TrueOrFalse(IgnoreIMEComposition()),
+     currentKeyEvent ? currentKeyEvent->mKeyEvent : nsnull,
+     currentKeyEvent ?
+       TrueOrFalse(currentKeyEvent->mKeyPressDispatched) : "N/A"));
 
   if (IgnoreIMEComposition()) {
     return;
   }
 
   nsString str;
   nsCocoaUtils::GetStringForNSString([aAttrString string], str);
   if (!IsIMEComposing() && str.IsEmpty()) {
@@ -1332,17 +1336,17 @@ TextInputHandler::InsertText(NSAttribute
 
   if (str.Length() != 1 || IsIMEComposing()) {
     InsertTextAsCommittingComposition(aAttrString);
     return;
   }
 
   // Don't let the same event be fired twice when hitting
   // enter/return! (Bug 420502)
-  if (mCurrentKeyEvent.mKeyPressDispatched) {
+  if (currentKeyEvent && currentKeyEvent->mKeyPressDispatched) {
     return;
   }
 
   nsRefPtr<nsChildView> kungFuDeathGrip(mWidget);
 
   // Dispatch keypress event with char instead of textEvent
   nsKeyEvent keypressEvent(PR_TRUE, NS_KEY_PRESS, mWidget);
   keypressEvent.time      = PR_IntervalNow();
@@ -1354,31 +1358,31 @@ TextInputHandler::InsertText(NSAttribute
   // -insertText: they've already been taken into account in creating
   // the input string.
 
   // create event for use by plugins
 #ifndef NP_NO_CARBON
   EventRecord carbonEvent;
 #endif // #ifndef NP_NO_CARBON
 
-  if (mCurrentKeyEvent.mKeyEvent) {
-    NSEvent* keyEvent = mCurrentKeyEvent.mKeyEvent;
+  if (currentKeyEvent) {
+    NSEvent* keyEvent = currentKeyEvent->mKeyEvent;
 
     // XXX The ASCII characters inputting mode of egbridge (Japanese IME)
     // might send the keyDown event with wrong keyboard layout if other
     // keyboard layouts are already loaded. In that case, the native event
     // doesn't match to this gecko event...
 #ifndef NP_NO_CARBON
     if ([mView pluginEventModel] == NPEventModelCarbon) {
       ConvertCocoaKeyEventToCarbonEvent(keyEvent, carbonEvent, PR_TRUE);
       keypressEvent.pluginEvent = &carbonEvent;
     }
 #endif // #ifndef NP_NO_CARBON
 
-    if (mCurrentKeyEvent.mKeyDownHandled) {
+    if (currentKeyEvent->mKeyDownHandled) {
       keypressEvent.flags |= NS_EVENT_FLAG_NO_DEFAULT;
     }
 
     keypressEvent.isShift = ([keyEvent modifierFlags] & NSShiftKeyMask) != 0;
     if (!IsPrintableChar(keypressEvent.charCode)) {
       keypressEvent.keyCode =
         ComputeGeckoKeyCode([keyEvent keyCode],
                             [keyEvent charactersIgnoringModifiers]);
@@ -1395,34 +1399,37 @@ TextInputHandler::InsertText(NSAttribute
 
   // TODO:
   // If mCurrentKeyEvent.mKeyEvent is null and when we implement textInput
   // event of DOM3 Events, we should dispatch it instead of keypress event.
   bool keyPressHandled = DispatchEvent(keypressEvent);
 
   // Note: mWidget might have become null here. Don't count on it from here on.
 
-  if (mCurrentKeyEvent.mKeyEvent) {
-    mCurrentKeyEvent.mKeyPressHandled = keyPressHandled;
-    mCurrentKeyEvent.mKeyPressDispatched = PR_TRUE;
+  if (currentKeyEvent) {
+    currentKeyEvent->mKeyPressHandled = keyPressHandled;
+    currentKeyEvent->mKeyPressDispatched = PR_TRUE;
   }
 
   NS_OBJC_END_TRY_ABORT_BLOCK;
 }
 
 bool
 TextInputHandler::DoCommandBySelector(const char* aSelector)
 {
+  KeyEventState* currentKeyEvent = GetCurrentKeyEvent();
+
   PR_LOG(gLog, PR_LOG_ALWAYS,
     ("%p TextInputHandler::DoCommandBySelector, aSelector=\"%s\", "
      "Destroyed()=%s, keypressHandled=%s",
      this, aSelector ? aSelector : "", TrueOrFalse(Destroyed()),
-     TrueOrFalse(mCurrentKeyEvent.mKeyPressHandled)));
-
-  return !Destroyed() && mCurrentKeyEvent.mKeyPressHandled;
+     currentKeyEvent ?
+       TrueOrFalse(currentKeyEvent->mKeyPressHandled) : "N/A"));
+
+  return !Destroyed() && currentKeyEvent && currentKeyEvent->mKeyPressHandled;
 }
 
 
 #pragma mark -
 
 
 /******************************************************************************
  *