Bug 685073 part.1 Manage nested key events for IME r=smichaud
authorMasayuki Nakano <masayuki@d-toybox.com>
Wed, 05 Oct 2011 11:19:24 +0900
changeset 78109 788aeed429a5d7a1b460f3736fa709777aa5011c
parent 78108 64f1fe963b513260dec8da7e0792a514cda96831
child 78110 7c37f7b456cab2937ac22eabfb0f17138504f4da
push id2388
push usermasayuki@d-toybox.com
push dateWed, 05 Oct 2011 02:19:43 +0000
treeherdermozilla-inbound@308f307cabab [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssmichaud
bugs685073
milestone10.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
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
@@ -480,16 +480,33 @@ protected:
     // 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)
     {
       Clear();
+    }    
+
+    KeyEventState(NSEvent* aNativeKeyEvent) : mKeyEvent(nsnull)
+    {
+      Clear();
+      Set(aNativeKeyEvent);
+    }
+
+    KeyEventState(const KeyEventState &aOther) : mKeyEvent(nsnull)
+    {
+      Clear();
+      if (aOther.mKeyEvent) {
+        mKeyEvent = [aOther.mKeyEvent retain];
+      }
+      mKeyDownHandled = aOther.mKeyDownHandled;
+      mKeyPressDispatched = aOther.mKeyPressDispatched;
+      mKeyPressHandled = aOther.mKeyPressHandled;
     }
 
     ~KeyEventState()
     {
       Clear();
     }
 
     void Set(NSEvent* aNativeKeyEvent)
@@ -524,25 +541,77 @@ protected:
   public:
     AutoKeyEventStateCleaner(TextInputHandlerBase* aHandler) :
       mHandler(aHandler)
     {
     }
 
     ~AutoKeyEventStateCleaner()
     {
-      mHandler->mCurrentKeyEvent.Clear();
+      mHandler->RemoveCurrentKeyEvent();
     }
   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;
+
+  /**
+   * mFirstKeyEvent must be used for first key event.  This member prevents
+   * memory fragmentation for most key events.
+   */
+  KeyEventState mFirstKeyEvent;
+
+  /**
+   * PushKeyEvent() adds the current key event to mCurrentKeyEvents.
+   */
+  KeyEventState* PushKeyEvent(NSEvent* aNativeKeyEvent)
+  {
+    KeyEventState* keyEvent = nsnull;
+    if (mCurrentKeyEvents.Length() == 0) {
+      mFirstKeyEvent.Set(aNativeKeyEvent);
+      keyEvent = &mFirstKeyEvent;
+    } else {
+      keyEvent = new KeyEventState(aNativeKeyEvent);
+    }
+    return *mCurrentKeyEvents.AppendElement(keyEvent);
+  }
+
+  /**
+   * RemoveCurrentKeyEvent() removes the current key event from
+   * mCurrentKeyEvents.
+   */
+  void RemoveCurrentKeyEvent()
+  {
+    NS_ASSERTION(mCurrentKeyEvents.Length() > 0,
+                 "RemoveCurrentKeyEvent() is called unexpectedly");
+    KeyEventState* keyEvent = GetCurrentKeyEvent();
+    mCurrentKeyEvents.RemoveElementAt(mCurrentKeyEvents.Length() - 1);
+    if (keyEvent == &mFirstKeyEvent) {
+      keyEvent->Clear();
+    } else {
+      delete keyEvent;
+    }
+  }
+
+  /**
+   * GetCurrentKeyEvent() returns current processing key event.
+   */
+  KeyEventState* GetCurrentKeyEvent()
+  {
+    if (mCurrentKeyEvents.Length() == 0) {
+      return nsnull;
+    }
+    return mCurrentKeyEvents[mCurrentKeyEvents.Length() - 1];
+  }
 
   /**
    * 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 +1183,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(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, 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(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(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 = true;
+      currentKeyEvent->mKeyPressHandled = DispatchEvent(keypressEvent);
+      currentKeyEvent->mKeyPressDispatched = 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(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(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(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, 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 = true;
+  if (currentKeyEvent) {
+    currentKeyEvent->mKeyPressHandled = keyPressHandled;
+    currentKeyEvent->mKeyPressDispatched = 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 -
 
 
 /******************************************************************************
  *