Bug 1403759 - part 2: Handle edit/selection commands like insertNewline: in TextInputHandler::HandleCommand() r=m_kato
authorMasayuki Nakano <masayuki@d-toybox.com>
Sat, 02 Dec 2017 14:53:10 +0900
changeset 394978 afc17c7465c2371b7ba0f50c92ec065c099e9fd3
parent 394977 f5039cf4ead7e14b067052ab1ecff2277ecbeace
child 394979 793d2834b3e3c2d1411216740161f1735f5fa70d
push id97987
push usernerli@mozilla.com
push dateTue, 05 Dec 2017 13:52:50 +0000
treeherdermozilla-inbound@8842dba7396b [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersm_kato
bugs1403759
milestone59.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 1403759 - part 2: Handle edit/selection commands like insertNewline: in TextInputHandler::HandleCommand() r=m_kato Let's make TextInputHandler::HandleCommand() handle other commands which are caused by Backspace, Delete, Tab, ArrowUp, ArrowDown, ArrowRight, ArrowLeft, PageUp, PageDown, Home, End and Escape keys with various modifiers. This patch makes Korean users can do most key operation in editor even with composing Hangul character. Note that this patch has a hack for cancelOperation: command. The command is typically fired for Escape key press. However, it's also fired for Command + Period. Unfortunately, this behavior is really odd if subclass of NSResponder implements |void cancelOperation:(id)sender|. If it's implemented, Cocoa doesn't call its |void keyDown:(NSEvent)theEvent|. Instead, it calls only |void doCommandBySelector:(SEL)aSelector| and |void cancelOperation:(id)sender| when Command + Period is pressed. Therefore, we cannot dispatch keydown nor keypress event for this key combination if we implement it. Therefore, this patch doesn't implement the method but handle it in doCommandBySelector even though the super class of ChildView cannot handle the command with this path. MozReview-Commit-ID: 4hS23SiwNJv
widget/CommandList.h
widget/EventForwards.h
widget/WidgetEventImpl.cpp
widget/cocoa/TextInputHandler.h
widget/cocoa/TextInputHandler.mm
widget/cocoa/nsChildView.mm
--- a/widget/CommandList.h
+++ b/widget/CommandList.h
@@ -1,17 +1,20 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 /**
- * Define NS_DEFIVE_COMMAND(aName, aCommandStr) before including this.
+ * Define NS_DEFINE_COMMAND(aName, aCommandStr) before including this.
  * @param aName          The name useful in C++ of the command.
  * @param aCommandStr    The command string in JS.
+ *
+ * Define NS_DEFINE_COMMAND_NO_EXEC_COMMAND(aName) before including this.
+ * @param aName          The name useful in C++ of the command.
  */
 
 NS_DEFINE_COMMAND(BeginLine,                   cmd_beginLine)
 NS_DEFINE_COMMAND(CharNext,                    cmd_charNext)
 NS_DEFINE_COMMAND(CharPrevious,                cmd_charPrevious)
 NS_DEFINE_COMMAND(Copy,                        cmd_copy)
 NS_DEFINE_COMMAND(Cut,                         cmd_cut)
 NS_DEFINE_COMMAND(Delete,                      cmd_delete)
@@ -47,8 +50,13 @@ NS_DEFINE_COMMAND(SelectLineNext,       
 NS_DEFINE_COMMAND(SelectLinePrevious,          cmd_selectLinePrevious)
 NS_DEFINE_COMMAND(SelectPageDown,              cmd_selectPageDown)
 NS_DEFINE_COMMAND(SelectPageUp,                cmd_selectPageUp)
 NS_DEFINE_COMMAND(SelectTop,                   cmd_selectTop)
 NS_DEFINE_COMMAND(SelectWordNext,              cmd_selectWordNext)
 NS_DEFINE_COMMAND(SelectWordPrevious,          cmd_selectWordPrevious)
 NS_DEFINE_COMMAND(WordNext,                    cmd_wordNext)
 NS_DEFINE_COMMAND(WordPrevious,                cmd_wordPrevious)
+
+NS_DEFINE_COMMAND_NO_EXEC_COMMAND(CancelOperation)
+NS_DEFINE_COMMAND_NO_EXEC_COMMAND(Complete)
+NS_DEFINE_COMMAND_NO_EXEC_COMMAND(InsertBacktab)
+NS_DEFINE_COMMAND_NO_EXEC_COMMAND(InsertTab)
--- a/widget/EventForwards.h
+++ b/widget/EventForwards.h
@@ -109,25 +109,27 @@ enum CodeNameIndex : CodeNameIndexType
   CODE_NAME_INDEX_USE_STRING
 };
 
 #undef NS_DEFINE_PHYSICAL_KEY_CODE_NAME
 
 const nsCString ToString(CodeNameIndex aCodeNameIndex);
 
 #define NS_DEFINE_COMMAND(aName, aCommandStr) , Command##aName
+#define NS_DEFINE_COMMAND_NO_EXEC_COMMAND(aName) , Command##aName
 
 typedef int8_t CommandInt;
 enum Command : CommandInt
 {
   CommandDoNothing
 
 #include "mozilla/CommandList.h"
 };
 #undef NS_DEFINE_COMMAND
+#undef NS_DEFINE_COMMAND_NO_EXEC_COMMAND
 
 const char* ToChar(Command aCommand);
 
 } // namespace mozilla
 
 /**
  * All header files should include this header instead of *Events.h.
  */
--- a/widget/WidgetEventImpl.cpp
+++ b/widget/WidgetEventImpl.cpp
@@ -92,20 +92,24 @@ ToChar(Command aCommand)
     return "CommandDoNothing";
   }
 
   switch (aCommand) {
 
 #define NS_DEFINE_COMMAND(aName, aCommandStr) \
     case Command##aName: \
       return "Command" #aName;
+#define NS_DEFINE_COMMAND_NO_EXEC_COMMAND(aName) \
+    case Command##aName: \
+      return "Command" #aName;
 
 #include "mozilla/CommandList.h"
 
 #undef NS_DEFINE_COMMAND
+#undef NS_DEFINE_COMMAND_NO_EXEC_COMMAND
 
     default:
       return "illegal command value";
   }
 }
 
 const nsCString
 GetDOMKeyCodeName(uint32_t aKeyCode)
@@ -1159,21 +1163,23 @@ WidgetKeyboardEvent::GetCodeNameIndex(co
   sCodeNameIndexHashtable->Get(aCodeValue, &result);
   return result;
 }
 
 /* static */ const char*
 WidgetKeyboardEvent::GetCommandStr(Command aCommand)
 {
 #define NS_DEFINE_COMMAND(aName, aCommandStr) , #aCommandStr
+#define NS_DEFINE_COMMAND_NO_EXEC_COMMAND(aName)
   static const char* const kCommands[] = {
     "" // CommandDoNothing
 #include "mozilla/CommandList.h"
   };
 #undef NS_DEFINE_COMMAND
+#undef NS_DEFINE_COMMAND_NO_EXEC_COMMAND
 
   MOZ_RELEASE_ASSERT(static_cast<size_t>(aCommand) < ArrayLength(kCommands),
                      "Illegal command enumeration value");
   return kCommands[aCommand];
 }
 
 /* static */ uint32_t
 WidgetKeyboardEvent::ComputeLocationFromCodeValue(CodeNameIndex aCodeNameIndex)
--- a/widget/cocoa/TextInputHandler.h
+++ b/widget/cocoa/TextInputHandler.h
@@ -622,24 +622,151 @@ protected:
       return !mKeyPressDispatched && !IsDefaultPrevented();
     }
 
     bool CanHandleCommand() const
     {
       return !mKeyDownHandled && !mKeyPressHandled;
     }
 
-    bool IsEnterKeyEvent() const
+    bool IsProperKeyEvent(Command aCommand) const
     {
       if (NS_WARN_IF(!mKeyEvent)) {
         return false;
       }
       KeyNameIndex keyNameIndex =
         TISInputSourceWrapper::ComputeGeckoKeyNameIndex([mKeyEvent keyCode]);
-      return keyNameIndex == KEY_NAME_INDEX_Enter;
+      Modifiers modifiers =
+        nsCocoaUtils::ModifiersForEvent(mKeyEvent) & (MODIFIER_SHIFT |
+                                                      MODIFIER_CONTROL |
+                                                      MODIFIER_ALT |
+                                                      MODIFIER_META);
+      switch (aCommand) {
+        case CommandInsertLineBreak:
+          return keyNameIndex == KEY_NAME_INDEX_Enter &&
+                 modifiers == MODIFIER_CONTROL;
+        case CommandInsertParagraph:
+          return keyNameIndex == KEY_NAME_INDEX_Enter &&
+                 modifiers == MODIFIER_NONE;
+        case CommandDeleteCharBackward:
+          return keyNameIndex == KEY_NAME_INDEX_Backspace &&
+                 modifiers == MODIFIER_NONE;
+        case CommandDeleteToBeginningOfLine:
+          return keyNameIndex == KEY_NAME_INDEX_Backspace &&
+                 modifiers == MODIFIER_META;
+        case CommandDeleteWordBackward:
+          return keyNameIndex == KEY_NAME_INDEX_Backspace &&
+                 modifiers == MODIFIER_ALT;
+        case CommandDeleteCharForward:
+          return keyNameIndex == KEY_NAME_INDEX_Delete &&
+                 modifiers == MODIFIER_NONE;
+        case CommandDeleteWordForward:
+          return keyNameIndex == KEY_NAME_INDEX_Delete &&
+                 modifiers == MODIFIER_ALT;
+        case CommandInsertTab:
+          return keyNameIndex == KEY_NAME_INDEX_Tab &&
+                 modifiers == MODIFIER_NONE;
+        case CommandInsertBacktab:
+          return keyNameIndex == KEY_NAME_INDEX_Tab &&
+                 modifiers == MODIFIER_SHIFT;
+        case CommandCharNext:
+          return keyNameIndex == KEY_NAME_INDEX_ArrowRight &&
+                 modifiers == MODIFIER_NONE;
+        case CommandSelectCharNext:
+          return keyNameIndex == KEY_NAME_INDEX_ArrowRight &&
+                 modifiers == MODIFIER_SHIFT;
+        case CommandWordNext:
+          return keyNameIndex == KEY_NAME_INDEX_ArrowRight &&
+                 modifiers == MODIFIER_ALT;
+        case CommandSelectWordNext:
+          return keyNameIndex == KEY_NAME_INDEX_ArrowRight &&
+                 modifiers == (MODIFIER_ALT | MODIFIER_SHIFT);
+        case CommandEndLine:
+          return keyNameIndex == KEY_NAME_INDEX_ArrowRight &&
+                 modifiers == MODIFIER_META;
+        case CommandSelectEndLine:
+          return keyNameIndex == KEY_NAME_INDEX_ArrowRight &&
+                 modifiers == (MODIFIER_META | MODIFIER_SHIFT);
+        case CommandCharPrevious:
+          return keyNameIndex == KEY_NAME_INDEX_ArrowLeft &&
+                 modifiers == MODIFIER_NONE;
+        case CommandSelectCharPrevious:
+          return keyNameIndex == KEY_NAME_INDEX_ArrowLeft &&
+                 modifiers == MODIFIER_SHIFT;
+        case CommandWordPrevious:
+          return keyNameIndex == KEY_NAME_INDEX_ArrowLeft &&
+                 modifiers == MODIFIER_ALT;
+        case CommandSelectWordPrevious:
+          return keyNameIndex == KEY_NAME_INDEX_ArrowLeft &&
+                 modifiers == (MODIFIER_ALT | MODIFIER_SHIFT);
+        case CommandBeginLine:
+          return keyNameIndex == KEY_NAME_INDEX_ArrowLeft &&
+                 modifiers == MODIFIER_META;
+        case CommandSelectBeginLine:
+          return keyNameIndex == KEY_NAME_INDEX_ArrowLeft &&
+                 modifiers == (MODIFIER_META | MODIFIER_SHIFT);
+        case CommandLinePrevious:
+          return keyNameIndex == KEY_NAME_INDEX_ArrowUp &&
+                 modifiers == MODIFIER_NONE;
+        case CommandSelectLinePrevious:
+          return keyNameIndex == KEY_NAME_INDEX_ArrowUp &&
+                 modifiers == MODIFIER_SHIFT;
+        case CommandMoveTop:
+          return keyNameIndex == KEY_NAME_INDEX_ArrowUp &&
+                 modifiers == MODIFIER_META;
+        case CommandSelectTop:
+          return (keyNameIndex == KEY_NAME_INDEX_ArrowUp &&
+                  modifiers == (MODIFIER_META | MODIFIER_SHIFT)) ||
+                 (keyNameIndex == KEY_NAME_INDEX_Home &&
+                  modifiers == MODIFIER_SHIFT);
+        case CommandLineNext:
+          return keyNameIndex == KEY_NAME_INDEX_ArrowDown &&
+                 modifiers == MODIFIER_NONE;
+        case CommandSelectLineNext:
+          return keyNameIndex == KEY_NAME_INDEX_ArrowDown &&
+                 modifiers == MODIFIER_SHIFT;
+        case CommandMoveBottom:
+          return keyNameIndex == KEY_NAME_INDEX_ArrowDown &&
+                 modifiers == MODIFIER_META;
+        case CommandSelectBottom:
+          return (keyNameIndex == KEY_NAME_INDEX_ArrowDown &&
+                  modifiers == (MODIFIER_META | MODIFIER_SHIFT)) ||
+                 (keyNameIndex == KEY_NAME_INDEX_End &&
+                  modifiers == MODIFIER_SHIFT);
+        case CommandScrollPageUp:
+          return keyNameIndex == KEY_NAME_INDEX_PageUp &&
+                 modifiers == MODIFIER_NONE;
+        case CommandSelectPageUp:
+          return keyNameIndex == KEY_NAME_INDEX_PageUp &&
+                 modifiers == MODIFIER_SHIFT;
+        case CommandScrollPageDown:
+          return keyNameIndex == KEY_NAME_INDEX_PageDown &&
+                 modifiers == MODIFIER_NONE;
+        case CommandSelectPageDown:
+          return keyNameIndex == KEY_NAME_INDEX_PageDown &&
+                 modifiers == MODIFIER_SHIFT;
+        case CommandScrollBottom:
+          return keyNameIndex == KEY_NAME_INDEX_End &&
+                 modifiers == MODIFIER_NONE;
+        case CommandScrollTop:
+          return keyNameIndex == KEY_NAME_INDEX_Home &&
+                 modifiers == MODIFIER_NONE;
+        case CommandCancelOperation:
+          return (keyNameIndex == KEY_NAME_INDEX_Escape &&
+                  (modifiers == MODIFIER_NONE ||
+                   modifiers == MODIFIER_SHIFT)) ||
+                 ([mKeyEvent keyCode] == kVK_ANSI_Period &&
+                  modifiers == MODIFIER_META);
+        case CommandComplete:
+          return keyNameIndex == KEY_NAME_INDEX_Escape &&
+                 (modifiers == MODIFIER_ALT ||
+                  modifiers == (MODIFIER_ALT | MODIFIER_SHIFT));
+        default:
+          return false;
+      }
     }
 
     void InitKeyEvent(TextInputHandlerBase* aHandler,
                       WidgetKeyboardEvent& aKeyEvent);
 
     /**
      * GetUnhandledString() returns characters of the event which have not been
      * handled with InsertText() yet. For example, if there is a composition
@@ -1156,18 +1283,22 @@ public:
    * @param aReplacementRange     The range which will be replaced with the
    *                              aAttrString instead of current selection.
    */
   void InsertText(NSAttributedString *aAttrString,
                   NSRange* aReplacementRange = nullptr);
 
   /**
    * Handles aCommand.  This may cause dispatching an eKeyPress event.
+   *
+   * @param aCommand    The command which receives from Cocoa.
+   * @return            true if this handles the command even if it does
+   *                    nothing actually.  Otherwise, false.
    */
-  void HandleCommand(Command aCommand);
+  bool HandleCommand(Command aCommand);
 
   /**
    * doCommandBySelector event handler.
    *
    * @param aSelector             A selector of the command.
    * @return                      TRUE if the command is consumed.  Otherwise,
    *                              FALSE.
    */
--- a/widget/cocoa/TextInputHandler.mm
+++ b/widget/cocoa/TextInputHandler.mm
@@ -2345,23 +2345,23 @@ TextInputHandler::InsertText(NSAttribute
   if (currentKeyEvent) {
     currentKeyEvent->mKeyPressHandled = keyPressHandled;
     currentKeyEvent->mKeyPressDispatched = keyPressDispatched;
   }
 
   NS_OBJC_END_TRY_ABORT_BLOCK;
 }
 
-void
+bool
 TextInputHandler::HandleCommand(Command aCommand)
 {
-  NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+  NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
 
   if (Destroyed()) {
-    return;
+    return false;
   }
 
   KeyEventState* currentKeyEvent = GetCurrentKeyEvent();
 
   MOZ_LOG(gLog, LogLevel::Info,
     ("%p TextInputHandler::HandleCommand, "
      "aCommand=%s, IsIMEComposing()=%s, "
      "keyevent=%p, keydownHandled=%s, keypressDispatched=%s, "
@@ -2372,19 +2372,19 @@ TextInputHandler::HandleCommand(Command 
        TrueOrFalse(currentKeyEvent->mKeyDownHandled) : "N/A",
      currentKeyEvent ?
        TrueOrFalse(currentKeyEvent->mKeyPressDispatched) : "N/A",
      currentKeyEvent ?
        TrueOrFalse(currentKeyEvent->mCausedOtherKeyEvents) : "N/A",
      currentKeyEvent ?
        TrueOrFalse(currentKeyEvent->mCompositionDispatched) : "N/A"));
 
-  // If "insertNewline:" command shouldn't be handled, let's ignore it.
+  // The command shouldn't be handled, let's ignore it.
   if (currentKeyEvent && !currentKeyEvent->CanHandleCommand()) {
-    return;
+    return false;
   }
 
   // If it's in composition, we cannot dispatch keypress event.
   // Therefore, we should use different approach or give up to handle
   // the command.
   if (IsIMEComposing()) {
     switch (aCommand) {
       case CommandInsertLineBreak:
@@ -2397,43 +2397,87 @@ TextInputHandler::HandleCommand(Command 
         // NSKeyDown event or should insert it with committing composition.
         NSAttributedString* lineBreaker =
           [[NSAttributedString alloc] initWithString:@"\n"];
         InsertTextAsCommittingComposition(lineBreaker, nullptr);
         if (currentKeyEvent) {
           currentKeyEvent->mCompositionDispatched = true;
         }
         [lineBreaker release];
-        return;
+        return true;
       }
+      case CommandDeleteCharBackward:
+      case CommandDeleteCharForward:
+      case CommandDeleteToBeginningOfLine:
+      case CommandDeleteWordBackward:
+      case CommandDeleteWordForward:
+        // Don't remove any contents during composition.
+        return false;
+      case CommandInsertTab:
+      case CommandInsertBacktab:
+        // Don't move focus during composition.
+        return false;
+      case CommandCharNext:
+      case CommandSelectCharNext:
+      case CommandWordNext:
+      case CommandSelectWordNext:
+      case CommandEndLine:
+      case CommandSelectEndLine:
+      case CommandCharPrevious:
+      case CommandSelectCharPrevious:
+      case CommandWordPrevious:
+      case CommandSelectWordPrevious:
+      case CommandBeginLine:
+      case CommandSelectBeginLine:
+      case CommandLinePrevious:
+      case CommandSelectLinePrevious:
+      case CommandMoveTop:
+      case CommandLineNext:
+      case CommandSelectLineNext:
+      case CommandMoveBottom:
+      case CommandSelectBottom:
+      case CommandSelectPageUp:
+      case CommandSelectPageDown:
+      case CommandScrollBottom:
+      case CommandScrollTop:
+        // Don't move selection during composition.
+        return false;
+      case CommandCancelOperation:
+      case CommandComplete:
+        // Don't handle Escape key by ourselves during composition.
+        return false;
+      case CommandScrollPageUp:
+      case CommandScrollPageDown:
+        // Allow to scroll.
+        break;
       default:
         break;
     }
   }
 
   RefPtr<nsChildView> widget(mWidget);
   nsresult rv = mDispatcher->BeginNativeInputTransaction();
   if (NS_WARN_IF(NS_FAILED(rv))) {
     MOZ_LOG(gLog, LogLevel::Error,
       ("%p, IMEInputHandler::HandleCommand, "
        "FAILED, due to BeginNativeInputTransaction() failure", this));
-    return;
+    return false;
   }
 
   // TODO: If it's not appropriate keypress but user customized the OS
   //       settings to do the command with other key, we should just set
   //       command to the keypress event and it should be handled as
   //       the key press in editor.
 
   // If it's handling actual key event and hasn't cause any composition
   // events nor other key events, we should expose actual modifier state.
   // Otherwise, we should adjust Control, Option and Command state since
   // editor may behave differently if some of them are active.
   bool dispatchFakeKeyPress =
-    !(currentKeyEvent && currentKeyEvent->IsEnterKeyEvent() &&
+    !(currentKeyEvent && currentKeyEvent->IsProperKeyEvent(aCommand) &&
       currentKeyEvent->CanDispatchKeyPressEvent());
 
   WidgetKeyboardEvent keypressEvent(true, eKeyPress, widget);
   if (!dispatchFakeKeyPress) {
     // If we're acutally handling a key press, we should dispatch
     // the keypress event as-is.
     currentKeyEvent->InitKeyEvent(this, keypressEvent);
   } else {
@@ -2459,18 +2503,215 @@ TextInputHandler::HandleCommand(Command 
                                       MODIFIER_META);
         if (aCommand == CommandInsertLineBreak) {
           // In default settings, Ctrl + Enter causes insertLineBreak command.
           // So, let's make Ctrl state active of the keypress event.
           keypressEvent.mModifiers |= MODIFIER_CONTROL;
         }
         break;
       }
+      case CommandDeleteCharBackward:
+      case CommandDeleteToBeginningOfLine:
+      case CommandDeleteWordBackward: {
+        NSEvent* keyEvent =
+          currentKeyEvent ? currentKeyEvent->mKeyEvent : nullptr;
+        nsCocoaUtils::InitInputEvent(keypressEvent, keyEvent);
+        keypressEvent.mKeyCode = NS_VK_BACK;
+        keypressEvent.mKeyNameIndex = KEY_NAME_INDEX_Backspace;
+        keypressEvent.mModifiers &= ~(MODIFIER_CONTROL |
+                                      MODIFIER_ALT |
+                                      MODIFIER_META);
+        if (aCommand == CommandDeleteToBeginningOfLine) {
+          keypressEvent.mModifiers |= MODIFIER_META;
+        } else if (aCommand == CommandDeleteWordBackward) {
+          keypressEvent.mModifiers |= MODIFIER_ALT;
+        }
+        break;
+      }
+      case CommandDeleteCharForward:
+      case CommandDeleteWordForward: {
+        NSEvent* keyEvent =
+          currentKeyEvent ? currentKeyEvent->mKeyEvent : nullptr;
+        nsCocoaUtils::InitInputEvent(keypressEvent, keyEvent);
+        keypressEvent.mKeyCode = NS_VK_DELETE;
+        keypressEvent.mKeyNameIndex = KEY_NAME_INDEX_Delete;
+        keypressEvent.mModifiers &= ~(MODIFIER_CONTROL |
+                                      MODIFIER_ALT |
+                                      MODIFIER_META);
+        if (aCommand == CommandDeleteWordForward) {
+          keypressEvent.mModifiers |= MODIFIER_ALT;
+        }
+        break;
+      }
+      case CommandCharNext:
+      case CommandSelectCharNext:
+      case CommandWordNext:
+      case CommandSelectWordNext:
+      case CommandEndLine:
+      case CommandSelectEndLine: {
+        NSEvent* keyEvent =
+          currentKeyEvent ? currentKeyEvent->mKeyEvent : nullptr;
+        nsCocoaUtils::InitInputEvent(keypressEvent, keyEvent);
+        keypressEvent.mKeyCode = NS_VK_RIGHT;
+        keypressEvent.mKeyNameIndex = KEY_NAME_INDEX_ArrowRight;
+        keypressEvent.mModifiers &= ~(MODIFIER_CONTROL |
+                                      MODIFIER_ALT |
+                                      MODIFIER_META);
+        if (aCommand == CommandSelectCharNext ||
+            aCommand == CommandSelectWordNext ||
+            aCommand == CommandSelectEndLine) {
+          keypressEvent.mModifiers |= MODIFIER_SHIFT;
+        }
+        if (aCommand == CommandWordNext ||
+            aCommand == CommandSelectWordNext) {
+          keypressEvent.mModifiers |= MODIFIER_ALT;
+        }
+        if (aCommand == CommandEndLine ||
+            aCommand == CommandSelectEndLine) {
+          keypressEvent.mModifiers |= MODIFIER_META;
+        }
+        break;
+      }
+      case CommandCharPrevious:
+      case CommandSelectCharPrevious:
+      case CommandWordPrevious:
+      case CommandSelectWordPrevious:
+      case CommandBeginLine:
+      case CommandSelectBeginLine: {
+        NSEvent* keyEvent =
+          currentKeyEvent ? currentKeyEvent->mKeyEvent : nullptr;
+        nsCocoaUtils::InitInputEvent(keypressEvent, keyEvent);
+        keypressEvent.mKeyCode = NS_VK_LEFT;
+        keypressEvent.mKeyNameIndex = KEY_NAME_INDEX_ArrowLeft;
+        keypressEvent.mModifiers &= ~(MODIFIER_CONTROL |
+                                      MODIFIER_ALT |
+                                      MODIFIER_META);
+        if (aCommand == CommandSelectCharPrevious ||
+            aCommand == CommandSelectWordPrevious ||
+            aCommand == CommandSelectBeginLine) {
+          keypressEvent.mModifiers |= MODIFIER_SHIFT;
+        }
+        if (aCommand == CommandWordPrevious ||
+            aCommand == CommandSelectWordPrevious) {
+          keypressEvent.mModifiers |= MODIFIER_ALT;
+        }
+        if (aCommand == CommandBeginLine ||
+            aCommand == CommandSelectBeginLine) {
+          keypressEvent.mModifiers |= MODIFIER_META;
+        }
+        break;
+      }
+      case CommandLinePrevious:
+      case CommandSelectLinePrevious:
+      case CommandMoveTop:
+      case CommandSelectTop: {
+        NSEvent* keyEvent =
+          currentKeyEvent ? currentKeyEvent->mKeyEvent : nullptr;
+        nsCocoaUtils::InitInputEvent(keypressEvent, keyEvent);
+        keypressEvent.mKeyCode = NS_VK_UP;
+        keypressEvent.mKeyNameIndex = KEY_NAME_INDEX_ArrowUp;
+        keypressEvent.mModifiers &= ~(MODIFIER_CONTROL |
+                                      MODIFIER_ALT |
+                                      MODIFIER_META);
+        if (aCommand == CommandSelectLinePrevious ||
+            aCommand == CommandSelectTop) {
+          keypressEvent.mModifiers |= MODIFIER_SHIFT;
+        }
+        if (aCommand == CommandMoveTop ||
+            aCommand == CommandSelectTop) {
+          keypressEvent.mModifiers |= MODIFIER_META;
+        }
+        break;
+      }
+      case CommandLineNext:
+      case CommandSelectLineNext:
+      case CommandMoveBottom:
+      case CommandSelectBottom: {
+        NSEvent* keyEvent =
+          currentKeyEvent ? currentKeyEvent->mKeyEvent : nullptr;
+        nsCocoaUtils::InitInputEvent(keypressEvent, keyEvent);
+        keypressEvent.mKeyCode = NS_VK_DOWN;
+        keypressEvent.mKeyNameIndex = KEY_NAME_INDEX_ArrowDown;
+        keypressEvent.mModifiers &= ~(MODIFIER_CONTROL |
+                                      MODIFIER_ALT |
+                                      MODIFIER_META);
+        if (aCommand == CommandSelectLineNext ||
+            aCommand == CommandSelectBottom) {
+          keypressEvent.mModifiers |= MODIFIER_SHIFT;
+        }
+        if (aCommand == CommandMoveBottom ||
+            aCommand == CommandSelectBottom) {
+          keypressEvent.mModifiers |= MODIFIER_META;
+        }
+        break;
+      }
+      case CommandScrollPageUp:
+      case CommandSelectPageUp: {
+        NSEvent* keyEvent =
+          currentKeyEvent ? currentKeyEvent->mKeyEvent : nullptr;
+        nsCocoaUtils::InitInputEvent(keypressEvent, keyEvent);
+        keypressEvent.mKeyCode = NS_VK_PAGE_UP;
+        keypressEvent.mKeyNameIndex = KEY_NAME_INDEX_PageUp;
+        keypressEvent.mModifiers &= ~(MODIFIER_CONTROL |
+                                      MODIFIER_ALT |
+                                      MODIFIER_META);
+        if (aCommand == CommandSelectPageUp) {
+          keypressEvent.mModifiers |= MODIFIER_SHIFT;
+        }
+        break;
+      }
+      case CommandScrollPageDown:
+      case CommandSelectPageDown: {
+        NSEvent* keyEvent =
+          currentKeyEvent ? currentKeyEvent->mKeyEvent : nullptr;
+        nsCocoaUtils::InitInputEvent(keypressEvent, keyEvent);
+        keypressEvent.mKeyCode = NS_VK_PAGE_DOWN;
+        keypressEvent.mKeyNameIndex = KEY_NAME_INDEX_PageDown;
+        keypressEvent.mModifiers &= ~(MODIFIER_CONTROL |
+                                      MODIFIER_ALT |
+                                      MODIFIER_META);
+        if (aCommand == CommandSelectPageDown) {
+          keypressEvent.mModifiers |= MODIFIER_SHIFT;
+        }
+        break;
+      }
+      case CommandScrollBottom:
+      case CommandScrollTop: {
+        NSEvent* keyEvent =
+          currentKeyEvent ? currentKeyEvent->mKeyEvent : nullptr;
+        nsCocoaUtils::InitInputEvent(keypressEvent, keyEvent);
+        if (aCommand == CommandScrollBottom) {
+          keypressEvent.mKeyCode = NS_VK_END;
+          keypressEvent.mKeyNameIndex = KEY_NAME_INDEX_End;
+        } else {
+          keypressEvent.mKeyCode = NS_VK_HOME;
+          keypressEvent.mKeyNameIndex = KEY_NAME_INDEX_Home;
+        }
+        keypressEvent.mModifiers &= ~(MODIFIER_CONTROL |
+                                      MODIFIER_ALT |
+                                      MODIFIER_META);
+        break;
+      }
+      case CommandCancelOperation:
+      case CommandComplete: {
+        NSEvent* keyEvent =
+          currentKeyEvent ? currentKeyEvent->mKeyEvent : nullptr;
+        nsCocoaUtils::InitInputEvent(keypressEvent, keyEvent);
+        keypressEvent.mKeyCode = NS_VK_ESCAPE;
+        keypressEvent.mKeyNameIndex = KEY_NAME_INDEX_Escape;
+        keypressEvent.mModifiers &= ~(MODIFIER_CONTROL |
+                                      MODIFIER_ALT |
+                                      MODIFIER_META);
+        if (aCommand == CommandComplete) {
+          keypressEvent.mModifiers |= MODIFIER_ALT;
+        }
+        break;
+      }
       default:
-        return;
+        return false;
     }
   }
 
   nsEventStatus status = nsEventStatus_eIgnore;
   bool keyPressDispatched =
     mDispatcher->MaybeDispatchKeypressEvents(keypressEvent, status,
                                              currentKeyEvent);
   bool keyPressHandled = (status == nsEventStatus_eConsumeNoDefault);
@@ -2481,30 +2722,36 @@ TextInputHandler::HandleCommand(Command 
     // Record the keypress event state only when it dispatched actual Enter
     // keypress event because in other cases, the keypress event just a
     // messenger.  E.g., if it's caused by different key, keypress event for
     // the actual key should be dispatched.
     if (!dispatchFakeKeyPress && currentKeyEvent) {
       currentKeyEvent->mKeyPressHandled = keyPressHandled;
       currentKeyEvent->mKeyPressDispatched = keyPressDispatched;
     }
-    return;
+    return true;
   }
 
   // If keypress event isn't dispatched as expected, we should fallback to
   // using composition events.
-  NSAttributedString* lineBreaker =
-    [[NSAttributedString alloc] initWithString:@"\n"];
-  InsertTextAsCommittingComposition(lineBreaker, nullptr);
-  if (currentKeyEvent) {
-    currentKeyEvent->mCompositionDispatched = true;
+  if (aCommand == CommandInsertLineBreak ||
+      aCommand == CommandInsertParagraph) {
+    NSAttributedString* lineBreaker =
+      [[NSAttributedString alloc] initWithString:@"\n"];
+    InsertTextAsCommittingComposition(lineBreaker, nullptr);
+    if (currentKeyEvent) {
+      currentKeyEvent->mCompositionDispatched = true;
+    }
+    [lineBreaker release];
+    return true;
   }
-  [lineBreaker release];
-
-  NS_OBJC_END_TRY_ABORT_BLOCK;
+
+  return false;
+
+  NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(false);
 }
 
 bool
 TextInputHandler::DoCommandBySelector(const char* aSelector)
 {
   RefPtr<nsChildView> widget(mWidget);
 
   KeyEventState* currentKeyEvent = GetCurrentKeyEvent();
@@ -2562,17 +2809,36 @@ TextInputHandler::DoCommandBySelector(co
     return true;
   }
 
   // If the key operation didn't cause keypress event or caused keypress event
   // but not prevented its default, we need to honor the command.  For example,
   // Korean IME sends "insertNewline:" when committing existing composition
   // with Enter key press.  In such case, the key operation has been consumed
   // by the committing composition but we still need to handle the command.
-  return Destroyed() || !currentKeyEvent->CanHandleCommand();
+  if (Destroyed() || !currentKeyEvent->CanHandleCommand()) {
+    return true;
+  }
+
+  // cancelOperation: command is fired after Escape or Command + Period.
+  // However, if ChildView implements cancelOperation:, calling
+  // [[ChildView super] doCommandBySelector:aSelector] when Command + Period
+  // causes only a call of [ChildView cancelOperation:sender].  I.e.,
+  // [ChildView keyDown:theEvent] becomes to be never called.  For avoiding
+  // this odd behavior, we need to handle the command before super class of
+  // ChildView only when current key event is proper event to fire Escape
+  // keypress event.
+  if (!strcmp(aSelector, "cancelOperatiorn:") && currentKeyEvent &&
+      currentKeyEvent->IsProperKeyEvent(CommandCancelOperation)) {
+    return HandleCommand(CommandCancelOperation);
+  }
+
+  // Otherwise, we've not handled the command yet.  Propagate the command
+  // to the super class of ChildView.
+  return false;
 }
 
 
 #pragma mark -
 
 
 /******************************************************************************
  *
--- a/widget/cocoa/nsChildView.mm
+++ b/widget/cocoa/nsChildView.mm
@@ -5594,16 +5594,300 @@ GetIntegerDeltaForEvent(NSEvent* aEvent)
 - (void)insertLineBreak:(id)sender
 {
   // Ctrl + Enter in the default settings.
   if (mTextInputHandler) {
     mTextInputHandler->HandleCommand(CommandInsertLineBreak);
   }
 }
 
+- (void) deleteBackward:(id)sender
+{
+  // Backspace in the default settings.
+  if (mTextInputHandler) {
+    mTextInputHandler->HandleCommand(CommandDeleteCharBackward);
+  }
+}
+
+- (void) deleteBackwardByDecomposingPreviousCharacter:(id)sender
+{
+  // Ctrl + Backspace in the default settings.
+  if (mTextInputHandler) {
+    mTextInputHandler->HandleCommand(CommandDeleteCharBackward);
+  }
+}
+
+- (void) deleteWordBackward:(id)sender
+{
+  // Alt + Backspace in the default settings.
+  if (mTextInputHandler) {
+    mTextInputHandler->HandleCommand(CommandDeleteWordBackward);
+  }
+}
+
+- (void) deleteToBeginningOfBackward:(id)sender
+{
+  // Command + Backspace in the default settings.
+  if (mTextInputHandler) {
+    mTextInputHandler->HandleCommand(CommandDeleteToBeginningOfLine);
+  }
+}
+
+- (void) deleteForward:(id)sender
+{
+  // Delete in the default settings.
+  if (mTextInputHandler) {
+    mTextInputHandler->HandleCommand(CommandDeleteCharForward);
+  }
+}
+
+- (void) deleteWordForward:(id)sender
+{
+  // Alt + Delete in the default settings.
+  if (mTextInputHandler) {
+    mTextInputHandler->HandleCommand(CommandDeleteWordForward);
+  }
+}
+
+- (void) insertTab:(id)sender
+{
+  // Tab in the default settings.
+  if (mTextInputHandler) {
+    mTextInputHandler->HandleCommand(CommandInsertTab);
+  }
+}
+
+- (void) insertBacktab:(id)sender
+{
+  // Shift + Tab in the default settings.
+  if (mTextInputHandler) {
+    mTextInputHandler->HandleCommand(CommandInsertBacktab);
+  }
+}
+
+- (void) moveRight:(id)sender
+{
+  // RightArrow in the default settings.
+  if (mTextInputHandler) {
+    mTextInputHandler->HandleCommand(CommandCharNext);
+  }
+}
+
+- (void) moveRightAndModifySelection:(id)sender
+{
+  // Shift + RightArrow in the default settings.
+  if (mTextInputHandler) {
+    mTextInputHandler->HandleCommand(CommandSelectCharNext);
+  }
+}
+
+- (void) moveWordRight:(id)sender
+{
+  // Alt + RightArrow in the default settings.
+  if (mTextInputHandler) {
+    mTextInputHandler->HandleCommand(CommandWordNext);
+  }
+}
+
+- (void) moveWordRightAndModifySelection:(id)sender
+{
+  // Alt + Shift + RightArrow in the default settings.
+  if (mTextInputHandler) {
+    mTextInputHandler->HandleCommand(CommandSelectWordNext);
+  }
+}
+
+- (void) moveToRightEndOfLine:(id)sender
+{
+  // Command + RightArrow in the default settings.
+  if (mTextInputHandler) {
+    mTextInputHandler->HandleCommand(CommandEndLine);
+  }
+}
+
+- (void) moveToRightEndOfLineAndModifySelection:(id)sender
+{
+  // Command + Shift + RightArrow in the default settings.
+  if (mTextInputHandler) {
+    mTextInputHandler->HandleCommand(CommandSelectEndLine);
+  }
+}
+
+- (void) moveLeft:(id)sender
+{
+  // LeftArrow in the default settings.
+  if (mTextInputHandler) {
+    mTextInputHandler->HandleCommand(CommandCharPrevious);
+  }
+}
+
+- (void) moveLeftAndModifySelection:(id)sender
+{
+  // Shift + LeftArrow in the default settings.
+  if (mTextInputHandler) {
+    mTextInputHandler->HandleCommand(CommandSelectCharPrevious);
+  }
+}
+
+- (void) moveWordLeft:(id)sender
+{
+  // Alt + LeftArrow in the default settings.
+  if (mTextInputHandler) {
+    mTextInputHandler->HandleCommand(CommandWordPrevious);
+  }
+}
+
+- (void) moveWordLeftAndModifySelection:(id)sender
+{
+  // Alt + Shift + LeftArrow in the default settings.
+  if (mTextInputHandler) {
+    mTextInputHandler->HandleCommand(CommandSelectWordPrevious);
+  }
+}
+
+- (void) moveToLeftEndOfLine:(id)sender
+{
+  // Command + LeftArrow in the default settings.
+  if (mTextInputHandler) {
+    mTextInputHandler->HandleCommand(CommandBeginLine);
+  }
+}
+
+- (void) moveToLeftEndOfLineAndModifySelection:(id)sender
+{
+  // Command + Shift + LeftArrow in the default settings.
+  if (mTextInputHandler) {
+    mTextInputHandler->HandleCommand(CommandSelectBeginLine);
+  }
+}
+
+- (void) moveUp:(id)sender
+{
+  // ArrowUp in the default settings.
+  if (mTextInputHandler) {
+    mTextInputHandler->HandleCommand(CommandLinePrevious);
+  }
+}
+
+- (void) moveUpAndModifySelection:(id)sender
+{
+  // Shift + ArrowUp in the default settings.
+  if (mTextInputHandler) {
+    mTextInputHandler->HandleCommand(CommandSelectLinePrevious);
+  }
+}
+
+- (void) moveToBeginningOfDocument:(id)sender
+{
+  // Command + ArrowUp in the default settings.
+  if (mTextInputHandler) {
+    mTextInputHandler->HandleCommand(CommandMoveTop);
+  }
+}
+
+- (void) moveToBeginningOfDocumentAndModifySelection:(id)sender
+{
+  // Command + Shift + ArrowUp or Shift + Home in the default settings.
+  if (mTextInputHandler) {
+    mTextInputHandler->HandleCommand(CommandSelectTop);
+  }
+}
+
+- (void) moveDown:(id)sender
+{
+  // ArrowDown in the default settings.
+  if (mTextInputHandler) {
+    mTextInputHandler->HandleCommand(CommandLineNext);
+  }
+}
+
+- (void) moveDownAndModifySelection:(id)sender
+{
+  // Shift + ArrowDown in the default settings.
+  if (mTextInputHandler) {
+    mTextInputHandler->HandleCommand(CommandSelectLineNext);
+  }
+}
+
+- (void) moveToEndOfDocument:(id)sender
+{
+  // Command + ArrowDown in the default settings.
+  if (mTextInputHandler) {
+    mTextInputHandler->HandleCommand(CommandMoveBottom);
+  }
+}
+
+- (void) moveToEndOfDocumentAndModifySelection:(id)sender
+{
+  // Command + Shift + ArrowDown or Shift + End in the default settings.
+  if (mTextInputHandler) {
+    mTextInputHandler->HandleCommand(CommandSelectBottom);
+  }
+}
+
+- (void) scrollPageUp:(id)sender
+{
+  // PageUp in the default settings.
+  if (mTextInputHandler) {
+    mTextInputHandler->HandleCommand(CommandScrollPageUp);
+  }
+}
+
+- (void) pageUpAndModifySelection:(id)sender
+{
+  // Shift + PageUp in the default settings.
+  if (mTextInputHandler) {
+    mTextInputHandler->HandleCommand(CommandSelectPageUp);
+  }
+}
+
+- (void) scrollPageDown:(id)sender
+{
+  // PageDown in the default settings.
+  if (mTextInputHandler) {
+    mTextInputHandler->HandleCommand(CommandScrollPageDown);
+  }
+}
+
+- (void) pageDownAndModifySelection:(id)sender
+{
+  // Shift + PageDown in the default settings.
+  if (mTextInputHandler) {
+    mTextInputHandler->HandleCommand(CommandSelectPageDown);
+  }
+}
+
+- (void) scrollToEndOfDocument:(id)sender
+{
+  // End in the default settings.
+  if (mTextInputHandler) {
+    mTextInputHandler->HandleCommand(CommandScrollBottom);
+  }
+}
+
+- (void) scrollToBeginningOfDocument:(id)sender
+{
+  // Home in the default settings.
+  if (mTextInputHandler) {
+    mTextInputHandler->HandleCommand(CommandScrollTop);
+  }
+}
+
+// XXX Don't decleare nor implement calcelOperation: because it
+//     causes not calling keyDown: for Command + Period.
+//     We need to handle it from doCommandBySelector:.
+
+- (void) complete:(id)sender
+{
+  // Alt + Escape or Alt + Shift + Escape in the default settings.
+  if (mTextInputHandler) {
+    mTextInputHandler->HandleCommand(CommandComplete);
+  }
+}
+
 - (void)flagsChanged:(NSEvent*)theEvent
 {
   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
 
   NS_ENSURE_TRUE(mGeckoChild, );
 
   nsAutoRetainCocoaObject kungFuDeathGrip(self);
   mTextInputHandler->HandleFlagsChanged(theEvent);