Bug 564669 Remove nsIPlaintextEditor::handleKeyPress() r=smaug+ehsan, sr=roc
authorMasayuki Nakano <masayuki@d-toybox.com>
Thu, 10 Jun 2010 10:16:58 +0900
changeset 43438 145c54158531a0d7181201ed76d05ff9e871e332
parent 43437 e7993ce9d265c527814e154c150076b6afec2699
child 43439 fb588a29e48988da9b9bc4695147c2b800865f99
push idunknown
push userunknown
push dateunknown
reviewerssmaug, roc
bugs564669
milestone1.9.3a5pre
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 564669 Remove nsIPlaintextEditor::handleKeyPress() r=smaug+ehsan, sr=roc
content/events/public/nsIEventListenerService.idl
content/events/src/nsEventListenerManager.cpp
content/events/src/nsEventListenerManager.h
content/events/src/nsEventListenerService.cpp
editor/idl/nsIPlaintextEditor.idl
editor/libeditor/base/nsEditor.cpp
editor/libeditor/base/nsEditor.h
editor/libeditor/base/nsEditorEventListener.cpp
editor/libeditor/html/nsHTMLEditor.cpp
editor/libeditor/html/nsHTMLEditor.h
editor/libeditor/html/tests/Makefile.in
editor/libeditor/html/tests/test_htmleditor_keyevent_handling.html
editor/libeditor/text/nsPlaintextEditor.cpp
editor/libeditor/text/nsPlaintextEditor.h
editor/libeditor/text/tests/Makefile.in
editor/libeditor/text/tests/test_texteditor_keyevent_handling.html
--- a/content/events/public/nsIEventListenerService.idl
+++ b/content/events/public/nsIEventListenerService.idl
@@ -62,17 +62,17 @@ interface nsIEventListenerInfo : nsISupp
 
   /**
    * If jsdIDebuggerService is active and the listener is implemented in JS,
    * this returns the listener as a jsdIValue. Otherwise null.
    */
   nsISupports getDebugObject();
 };
 
-[scriptable, uuid(551cac0f-31ed-45e0-8d67-bc0d6e117b31)]
+[scriptable, uuid(7be78bb6-33f7-4f31-b3f3-97eefaff2762)]
 interface nsIEventListenerService : nsISupports
 {
   /**
    * Returns an array of nsIEventListenerInfo objects.
    * If aEventTarget doesn't have any listeners, this returns null.
    */
   void getListenerInfoFor(in nsIDOMEventTarget aEventTarget,
                           [optional] out unsigned long aCount,
@@ -86,10 +86,15 @@ interface nsIEventListenerService : nsIS
    * dispatching an event to aEventTarget
    * @note Some events, especially 'load', may actually have a shorter
    *       event target chain than what this methods returns.
   */
   void getEventTargetChainFor(in nsIDOMEventTarget aEventTarget,
                               [optional] out unsigned long aCount,
                               [retval, array, size_is(aCount)] out
                                 nsIDOMEventTarget aOutArray);
+
+  /**
+   * Returns system event group.
+   */
+  readonly attribute nsIDOMEventGroup systemEventGroup;
 };
 
--- a/content/events/src/nsEventListenerManager.cpp
+++ b/content/events/src/nsEventListenerManager.cpp
@@ -291,35 +291,44 @@ nsEventListenerManager::nsEventListenerM
 
 nsEventListenerManager::~nsEventListenerManager() 
 {
   NS_ASSERTION(!mTarget, "didn't call Disconnect");
   RemoveAllListeners();
 
   --mInstanceCount;
   if(mInstanceCount == 0) {
-    NS_IF_RELEASE(gSystemEventGroup);
     NS_IF_RELEASE(gDOM2EventGroup);
   }
 }
 
 nsresult
 nsEventListenerManager::RemoveAllListeners()
 {
   mListeners.Clear();
   return NS_OK;
 }
 
 void
 nsEventListenerManager::Shutdown()
 {
+  NS_IF_RELEASE(gSystemEventGroup);
   sAddListenerID = JSVAL_VOID;
   nsDOMEvent::Shutdown();
 }
 
+nsIDOMEventGroup*
+nsEventListenerManager::GetSystemEventGroup()
+{
+  if (!gSystemEventGroup) {
+    CallCreateInstance(kDOMEventGroupCID, &gSystemEventGroup);
+  }
+  return gSystemEventGroup;
+}
+
 NS_IMPL_CYCLE_COLLECTION_CLASS(nsEventListenerManager)
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsEventListenerManager)
    NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIEventListenerManager)
    NS_INTERFACE_MAP_ENTRY(nsIEventListenerManager)
    NS_INTERFACE_MAP_ENTRY(nsIDOMEventTarget)
    NS_INTERFACE_MAP_ENTRY(nsIDOM3EventTarget)
 NS_INTERFACE_MAP_END
@@ -1215,27 +1224,18 @@ nsEventListenerManager::SetListenerTarge
   //WEAK reference, must be set back to nsnull when done by calling Disconnect
   mTarget = aTarget;
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsEventListenerManager::GetSystemEventGroupLM(nsIDOMEventGroup **aGroup)
 {
-  if (!gSystemEventGroup) {
-    nsresult result;
-    nsCOMPtr<nsIDOMEventGroup> group(do_CreateInstance(kDOMEventGroupCID,&result));
-    if (NS_FAILED(result))
-      return result;
-
-    gSystemEventGroup = group;
-    NS_ADDREF(gSystemEventGroup);
-  }
-
-  *aGroup = gSystemEventGroup;
+  *aGroup = GetSystemEventGroup();
+  NS_ENSURE_TRUE(*aGroup, NS_ERROR_OUT_OF_MEMORY);
   NS_ADDREF(*aGroup);
   return NS_OK;
 }
 
 nsresult
 nsEventListenerManager::GetDOM2EventGroup(nsIDOMEventGroup **aGroup)
 {
   if (!gDOM2EventGroup) {
--- a/content/events/src/nsEventListenerManager.h
+++ b/content/events/src/nsEventListenerManager.h
@@ -42,23 +42,25 @@
 #include "jsapi.h"
 #include "nsCOMPtr.h"
 #include "nsIDOMEventTarget.h"
 #include "nsIDOM3EventTarget.h"
 #include "nsHashtable.h"
 #include "nsIScriptContext.h"
 #include "nsCycleCollectionParticipant.h"
 #include "nsTObserverArray.h"
+#include "nsGUIEvent.h"
 
 class nsIDOMEvent;
 class nsIAtom;
 class nsIWidget;
 struct nsPoint;
 struct EventTypeData;
 class nsEventTargetChainItem;
+class nsPIDOMWindow;
 
 typedef struct {
   nsRefPtr<nsIDOMEventListener> mListener;
   PRUint32                      mEventType;
   nsCOMPtr<nsIAtom>             mTypeAtom;
   PRUint16                      mFlags;
   PRUint16                      mGroupFlags;
   PRBool                        mHandlerIsString;
@@ -175,16 +177,18 @@ public:
   // nsIDOMEventTarget
   NS_DECL_NSIDOMEVENTTARGET
 
   // nsIDOM3EventTarget
   NS_DECL_NSIDOM3EVENTTARGET
 
   static void Shutdown();
 
+  static nsIDOMEventGroup* GetSystemEventGroup();
+
   NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(nsEventListenerManager,
                                            nsIEventListenerManager)
 
 protected:
   nsresult HandleEventSubType(nsListenerStruct* aListenerStruct,
                               nsIDOMEventListener* aListener,
                               nsIDOMEvent* aDOMEvent,
                               nsPIDOMEventTarget* aCurrentTarget,
--- a/content/events/src/nsEventListenerService.cpp
+++ b/content/events/src/nsEventListenerService.cpp
@@ -31,31 +31,32 @@
  * decision by deleting the provisions above and replace them with the notice
  * and other provisions required by the GPL or the LGPL. If you do not delete
  * the provisions above, a recipient may use your version of this file under
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 #include "nsEventListenerService.h"
 #include "nsCOMArray.h"
-#include "nsIEventListenerManager.h"
+#include "nsEventListenerManager.h"
 #include "nsPIDOMEventTarget.h"
 #include "nsIVariant.h"
 #include "nsIServiceManager.h"
 #include "nsMemory.h"
 #include "nsContentUtils.h"
 #include "nsIXPConnect.h"
 #include "nsIDOMWindow.h"
 #include "nsPIDOMWindow.h"
 #include "nsJSUtils.h"
 #include "nsIPrivateDOMEvent.h"
 #include "nsIJSContextStack.h"
 #include "nsGUIEvent.h"
 #include "nsEventDispatcher.h"
 #include "nsIJSEventListener.h"
+#include "nsIDOMEventGroup.h"
 #ifdef MOZ_JSDEBUGGER
 #include "jsdIDebuggerService.h"
 #endif
 
 NS_IMPL_CYCLE_COLLECTION_1(nsEventListenerInfo, mListener)
 
 DOMCI_DATA(EventListenerInfo, nsEventListenerInfo)
 
@@ -242,16 +243,26 @@ nsEventListenerService::GetEventTargetCh
     nsCOMPtr<nsIDOMEventTarget> target = do_QueryInterface(targets[i]);
     (*aOutArray)[i] = target.forget().get();
   }
   *aCount = count;
 
   return NS_OK;
 }
 
+NS_IMETHODIMP
+nsEventListenerService::GetSystemEventGroup(nsIDOMEventGroup** aSystemGroup)
+{
+  NS_ENSURE_ARG_POINTER(aSystemGroup);
+  *aSystemGroup = nsEventListenerManager::GetSystemEventGroup();
+  NS_ENSURE_TRUE(*aSystemGroup, NS_ERROR_OUT_OF_MEMORY);
+  NS_ADDREF(*aSystemGroup);
+  return NS_OK;
+}
+
 nsresult
 NS_NewEventListenerService(nsIEventListenerService** aResult)
 {
   *aResult = new nsEventListenerService();
   NS_ENSURE_TRUE(*aResult, NS_ERROR_OUT_OF_MEMORY);
   NS_ADDREF(*aResult);
   return NS_OK;
 }
--- a/editor/idl/nsIPlaintextEditor.idl
+++ b/editor/idl/nsIPlaintextEditor.idl
@@ -32,19 +32,17 @@
  * and other provisions required by the GPL or the LGPL. If you do not delete
  * the provisions above, a recipient may use your version of this file under
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
  
 #include "nsISupports.idl"
 
-interface nsIDOMKeyEvent;
-
-[scriptable, uuid(1480e196-0d5c-40cf-8563-ed8a33eabcf2)]
+[scriptable, uuid(05d312ef-8914-494e-91c9-2be8ed7f8e29)]
 interface nsIPlaintextEditor : nsISupports
 {
 
   // XXX Why aren't these in nsIEditor?
   // only plain text entry is allowed via events
   const long eEditorPlaintextMask       = 0x0001;
   // enter key and CR-LF handled specially
   const long eEditorSingleLineMask      = 0x0002;
@@ -115,22 +113,16 @@ interface nsIPlaintextEditor : nsISuppor
   void setWrapColumn(in long aWrapColumn);
 
   /** Get and set newline handling.
    *
    *  Values are the constants defined above.
    */
   attribute long newlineHandling;
 
-  /** 
-   * EditorKeyPress consumes a keyevent.
-   * @param aKeyEvent    key event to consume
-   */
-  void handleKeyPress(in nsIDOMKeyEvent aKeyEvent);
-
   /**
    * Inserts a string at the current location,
    * given by the selection.
    * If the selection is not collapsed, the selection is deleted
    * and the insertion takes place at the resulting collapsed selection.
    *
    * @param aString   the string to be inserted
    */
--- a/editor/libeditor/base/nsEditor.cpp
+++ b/editor/libeditor/base/nsEditor.cpp
@@ -69,17 +69,18 @@
 #include "nsISelectionPrivate.h"
 #include "nsISelectionController.h"
 #include "nsIEnumerator.h"
 #include "nsEditProperty.h"
 #include "nsIAtom.h"
 #include "nsCaret.h"
 #include "nsIWidget.h"
 #include "nsIPlaintextEditor.h"
-#include "nsGUIEvent.h"  // nsTextEventReply
+#include "nsIPrivateDOMEvent.h"
+#include "nsGUIEvent.h"
 
 #include "nsIFrame.h"  // Needed by IME code
 
 #include "nsCSSStyleSheet.h"
 
 #include "nsIContent.h"
 #include "nsServiceManagerUtils.h"
 
@@ -5042,16 +5043,71 @@ nsresult
 nsEditor::RemoveAttributeOrEquivalent(nsIDOMElement * aElement,
                                       const nsAString & aAttribute,
                                       PRBool aSuppressTransaction)
 {
   return RemoveAttribute(aElement, aAttribute);
 }
 
 nsresult
+nsEditor::HandleKeyPressEvent(nsIDOMKeyEvent* aKeyEvent)
+{
+  // NOTE: When you change this method, you should also change:
+  //   * editor/libeditor/text/tests/test_texteditor_keyevent_handling.html
+  //   * editor/libeditor/html/tests/test_htmleditor_keyevent_handling.html
+  //
+  // And also when you add new key handling, you need to change the subclass's
+  // HandleKeyPressEvent()'s switch statement.
+
+  nsKeyEvent* nativeKeyEvent = GetNativeKeyEvent(aKeyEvent);
+  NS_ENSURE_TRUE(nativeKeyEvent, NS_ERROR_UNEXPECTED);
+  NS_ASSERTION(nativeKeyEvent->message == NS_KEY_PRESS,
+               "HandleKeyPressEvent gets non-keypress event");
+
+  // if we are readonly or disabled, then do nothing.
+  if (IsReadonly() || IsDisabled()) {
+    // consume backspace for disabled and readonly textfields, to prevent
+    // back in history, which could be confusing to users
+    if (nativeKeyEvent->keyCode == nsIDOMKeyEvent::DOM_VK_BACK_SPACE) {
+      aKeyEvent->PreventDefault();
+    }
+    return NS_OK;
+  }
+
+  switch (nativeKeyEvent->keyCode) {
+    case nsIDOMKeyEvent::DOM_VK_META:
+    case nsIDOMKeyEvent::DOM_VK_SHIFT:
+    case nsIDOMKeyEvent::DOM_VK_CONTROL:
+    case nsIDOMKeyEvent::DOM_VK_ALT:
+      aKeyEvent->PreventDefault(); // consumed
+      return NS_OK;
+    case nsIDOMKeyEvent::DOM_VK_BACK_SPACE:
+      if (nativeKeyEvent->isControl || nativeKeyEvent->isAlt ||
+          nativeKeyEvent->isMeta) {
+        return NS_OK;
+      }
+      DeleteSelection(nsIEditor::ePrevious);
+      aKeyEvent->PreventDefault(); // consumed
+      return NS_OK;
+    case nsIDOMKeyEvent::DOM_VK_DELETE:
+      // on certain platforms (such as windows) the shift key
+      // modifies what delete does (cmd_cut in this case).
+      // bailing here to allow the keybindings to do the cut.
+      if (nativeKeyEvent->isShift || nativeKeyEvent->isControl ||
+          nativeKeyEvent->isAlt || nativeKeyEvent->isMeta) {
+        return NS_OK;
+      }
+      DeleteSelection(nsIEditor::eNext);
+      aKeyEvent->PreventDefault(); // consumed
+      return NS_OK; 
+  }
+  return NS_OK;
+}
+
+nsresult
 nsEditor::HandleInlineSpellCheck(PRInt32 action,
                                    nsISelection *aSelection,
                                    nsIDOMNode *previousSelectedNode,
                                    PRInt32 previousSelectedOffset,
                                    nsIDOMNode *aStartNode,
                                    PRInt32 aStartOffset,
                                    nsIDOMNode *aEndNode,
                                    PRInt32 aEndOffset)
@@ -5196,16 +5252,27 @@ nsEditor::DumpNode(nsIDOMNode *aNode, PR
 #endif
 
 PRBool
 nsEditor::IsModifiableNode(nsIDOMNode *aNode)
 {
   return PR_TRUE;
 }
 
+nsKeyEvent*
+nsEditor::GetNativeKeyEvent(nsIDOMKeyEvent* aDOMKeyEvent)
+{
+  nsCOMPtr<nsIPrivateDOMEvent> privDOMEvent = do_QueryInterface(aDOMKeyEvent);
+  NS_ENSURE_TRUE(privDOMEvent, nsnull);
+  nsEvent* nativeEvent = privDOMEvent->GetInternalNSEvent();
+  NS_ENSURE_TRUE(nativeEvent, nsnull);
+  NS_ENSURE_TRUE(nativeEvent->eventStructType == NS_KEY_EVENT, nsnull);
+  return static_cast<nsKeyEvent*>(nativeEvent);
+}
+
 PRBool
 nsEditor::HasFocus()
 {
   nsCOMPtr<nsPIDOMEventTarget> piTarget = GetPIDOMEventTarget();
   if (!piTarget) {
     return PR_FALSE;
   }
 
--- a/editor/libeditor/base/nsEditor.h
+++ b/editor/libeditor/base/nsEditor.h
@@ -81,16 +81,17 @@ class JoinElementTxn;
 class EditAggregateTxn;
 class IMETextTxn;
 class AddStyleSheetTxn;
 class RemoveStyleSheetTxn;
 class nsIFile;
 class nsISelectionController;
 class nsIDOMEventTarget;
 class nsCSSStyleSheet;
+class nsKeyEvent;
 
 #define kMOZEditorBogusNodeAttrAtom nsEditProperty::mozEditorBogusNode
 #define kMOZEditorBogusNodeValue NS_LITERAL_STRING("TRUE")
 
 /** implementation of an editor object.  it will be the controller/focal point 
  *  for the main editor services. i.e. the GUIManager, publishing, transaction 
  *  manager, event interfaces. the idea for the event interfaces is to have them 
  *  delegate the actual commands to the editor independent of the XPFE implementation.
@@ -351,16 +352,18 @@ protected:
   // unregister and release our event listeners
   virtual void RemoveEventListeners();
 
   /**
    * Return true if spellchecking should be enabled for this editor.
    */
   PRBool GetDesiredSpellCheckState();
 
+  nsKeyEvent* GetNativeKeyEvent(nsIDOMKeyEvent* aDOMKeyEvent);
+
 public:
 
   /** All editor operations which alter the doc should be prefaced
    *  with a call to StartOperation, naming the action and direction */
   NS_IMETHOD StartOperation(PRInt32 opID, nsIEditor::EDirection aDirection);
 
   /** All editor operations which alter the doc should be followed
    *  with a call to EndOperation */
@@ -562,16 +565,18 @@ public:
 
   nsresult GetString(const nsAString& name, nsAString& value);
 
   nsresult BeginUpdateViewBatch(void);
   virtual nsresult EndUpdateViewBatch(void);
 
   PRBool GetShouldTxnSetSelection();
 
+  virtual nsresult HandleKeyPressEvent(nsIDOMKeyEvent* aKeyEvent);
+
   nsresult HandleInlineSpellCheck(PRInt32 action,
                                     nsISelection *aSelection,
                                     nsIDOMNode *previousSelectedNode,
                                     PRInt32 previousSelectedOffset,
                                     nsIDOMNode *aStartNode,
                                     PRInt32 aStartOffset,
                                     nsIDOMNode *aEndNode,
                                     PRInt32 aEndOffset);
@@ -642,16 +647,22 @@ public:
     return (mFlags & nsIPlaintextEditor::eEditorAllowInteraction) != 0;
   }
 
   PRBool DontEchoPassword() const
   {
     return (mFlags & nsIPlaintextEditor::eEditorDontEchoPassword) != 0;
   }
 
+  PRBool IsTabbable() const
+  {
+    return IsSingleLineEditor() || IsPasswordEditor() || IsFormWidget() ||
+           IsInteractionAllowed();
+  }
+
   // Whether the editor has focus or not.
   virtual PRBool HasFocus();
 
 protected:
 
   PRUint32        mModCount;		// number of modifications (for undo/redo stack)
   PRUint32        mFlags;		// behavior flags. See nsIPlaintextEditor.idl for the flags we use.
   
--- a/editor/libeditor/base/nsEditorEventListener.cpp
+++ b/editor/libeditor/base/nsEditorEventListener.cpp
@@ -34,17 +34,16 @@
  * decision by deleting the provisions above and replace them with the notice
  * and other provisions required by the GPL or the LGPL. If you do not delete
  * the provisions above, a recipient may use your version of this file under
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 #include "nsEditorEventListener.h"
 #include "nsEditor.h"
-#include "nsIPlaintextEditor.h"
 
 #include "nsIDOMDOMStringList.h"
 #include "nsIDOMEvent.h"
 #include "nsIDOMNSEvent.h"
 #include "nsIDOMDocument.h"
 #include "nsPIDOMEventTarget.h"
 #include "nsIDocument.h"
 #include "nsIPresShell.h"
@@ -312,152 +311,38 @@ nsEditorEventListener::KeyUp(nsIDOMEvent
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsEditorEventListener::KeyPress(nsIDOMEvent* aKeyEvent)
 {
   NS_ENSURE_TRUE(mEditor, NS_ERROR_NOT_AVAILABLE);
 
-  nsCOMPtr<nsIDOMKeyEvent>keyEvent = do_QueryInterface(aKeyEvent);
-  if (!keyEvent)
-  {
-    //non-key event passed to keypress.  bad things.
-    return NS_OK;
-  }
-
-  // Don't handle events which do not belong to us (by making sure that the
-  // target of the event is actually editable).
-  nsCOMPtr<nsIDOMEventTarget> target;
-  nsresult rv = keyEvent->GetTarget(getter_AddRefs(target));
-  NS_ENSURE_SUCCESS(rv, rv);
-  nsCOMPtr<nsIDOMNode> targetNode = do_QueryInterface(target);
-  if (!mEditor->IsModifiableNode(targetNode))
-  {
-    return NS_OK;
-  }
-
   // DOM event handling happens in two passes, the client pass and the system
   // pass.  We do all of our processing in the system pass, to allow client
   // handlers the opportunity to cancel events and prevent typing in the editor.
   // If the client pass cancelled the event, defaultPrevented will be true
   // below.
 
-  nsCOMPtr<nsIDOMNSUIEvent> nsUIEvent = do_QueryInterface(aKeyEvent);
-  if(nsUIEvent) 
-  {
+  nsCOMPtr<nsIDOMNSUIEvent> UIEvent = do_QueryInterface(aKeyEvent);
+  if(UIEvent) {
     PRBool defaultPrevented;
-    nsUIEvent->GetPreventDefault(&defaultPrevented);
-    if(defaultPrevented)
+    UIEvent->GetPreventDefault(&defaultPrevented);
+    if(defaultPrevented) {
       return NS_OK;
+    }
   }
 
-  PRUint32 keyCode;
-  keyEvent->GetKeyCode(&keyCode);
-
-  // if we are readonly or disabled, then do nothing.
-  if (mEditor->IsReadonly() || mEditor->IsDisabled())
-  {
-    // consume backspace for disabled and readonly textfields, to prevent
-    // back in history, which could be confusing to users
-    if (keyCode == nsIDOMKeyEvent::DOM_VK_BACK_SPACE)
-      aKeyEvent->PreventDefault();
-
+  nsCOMPtr<nsIDOMKeyEvent>keyEvent = do_QueryInterface(aKeyEvent);
+  if (!keyEvent) {
+    //non-key event passed to keypress.  bad things.
     return NS_OK;
   }
 
-  nsCOMPtr<nsIPlaintextEditor> textEditor =
-    do_QueryInterface(static_cast<nsIEditor*>(mEditor));
-  NS_ASSERTION(textEditor, "nsEditor must have nsIPlaintextEditor");
-
-  // if there is no charCode, then it's a key that doesn't map to a character,
-  // so look for special keys using keyCode.
-  if (0 != keyCode)
-  {
-    PRBool isAnyModifierKeyButShift;
-    nsresult rv;
-    rv = keyEvent->GetAltKey(&isAnyModifierKeyButShift);
-    if (NS_FAILED(rv)) return rv;
-    
-    if (!isAnyModifierKeyButShift)
-    {
-      rv = keyEvent->GetMetaKey(&isAnyModifierKeyButShift);
-      if (NS_FAILED(rv)) return rv;
-      
-      if (!isAnyModifierKeyButShift)
-      {
-        rv = keyEvent->GetCtrlKey(&isAnyModifierKeyButShift);
-        if (NS_FAILED(rv)) return rv;
-      }
-    }
-
-    switch (keyCode)
-    {
-      case nsIDOMKeyEvent::DOM_VK_META:
-      case nsIDOMKeyEvent::DOM_VK_SHIFT:
-      case nsIDOMKeyEvent::DOM_VK_CONTROL:
-      case nsIDOMKeyEvent::DOM_VK_ALT:
-        aKeyEvent->PreventDefault(); // consumed
-        return NS_OK;
-        break;
-
-      case nsIDOMKeyEvent::DOM_VK_BACK_SPACE: 
-        if (isAnyModifierKeyButShift)
-          return NS_OK;
-
-        mEditor->DeleteSelection(nsIEditor::ePrevious);
-        aKeyEvent->PreventDefault(); // consumed
-        return NS_OK;
-        break;
- 
-      case nsIDOMKeyEvent::DOM_VK_DELETE:
-        /* on certain platforms (such as windows) the shift key
-           modifies what delete does (cmd_cut in this case).
-           bailing here to allow the keybindings to do the cut.*/
-        PRBool isShiftModifierKey;
-        rv = keyEvent->GetShiftKey(&isShiftModifierKey);
-        if (NS_FAILED(rv)) return rv;
-
-        if (isAnyModifierKeyButShift || isShiftModifierKey)
-           return NS_OK;
-        mEditor->DeleteSelection(nsIEditor::eNext);
-        aKeyEvent->PreventDefault(); // consumed
-        return NS_OK; 
-        break;
- 
-      case nsIDOMKeyEvent::DOM_VK_TAB:
-        if (mEditor->IsSingleLineEditor() || mEditor->IsPasswordEditor() ||
-            mEditor->IsFormWidget() || mEditor->IsInteractionAllowed()) {
-          return NS_OK; // let it be used for focus switching
-        }
-
-        if (isAnyModifierKeyButShift)
-          return NS_OK;
-
-        // else we insert the tab straight through
-        textEditor->HandleKeyPress(keyEvent);
-        // let HandleKeyPress consume the event
-        return NS_OK; 
-
-      case nsIDOMKeyEvent::DOM_VK_RETURN:
-      case nsIDOMKeyEvent::DOM_VK_ENTER:
-        if (isAnyModifierKeyButShift)
-          return NS_OK;
-
-        if (!mEditor->IsSingleLineEditor())
-        {
-          textEditor->HandleKeyPress(keyEvent);
-          aKeyEvent->PreventDefault(); // consumed
-        }
-        return NS_OK;
-    }
-  }
-
-  textEditor->HandleKeyPress(keyEvent);
-  return NS_OK; // we don't PreventDefault() here or keybindings like control-x won't work 
+  return mEditor->HandleKeyPressEvent(keyEvent);
 }
 
 /**
  * nsIDOMMouseListener implementation
  */
 
 NS_IMETHODIMP
 nsEditorEventListener::MouseClick(nsIDOMEvent* aMouseEvent)
--- a/editor/libeditor/html/nsHTMLEditor.cpp
+++ b/editor/libeditor/html/nsHTMLEditor.cpp
@@ -511,16 +511,146 @@ nsHTMLEditor::BeginningOfDocument()
       selNode = curNode;
       selOffset = curOffset;
       done = PR_TRUE;
     }
   }
   return selection->Collapse(selNode, selOffset);
 }
 
+nsresult
+nsHTMLEditor::HandleKeyPressEvent(nsIDOMKeyEvent* aKeyEvent)
+{
+  // NOTE: When you change this method, you should also change:
+  //   * editor/libeditor/html/tests/test_htmleditor_keyevent_handling.html
+
+  if (IsReadonly() || IsDisabled()) {
+    // When we're not editable, the events are handled on nsEditor, so, we can
+    // bypass nsPlaintextEditor.
+    return nsEditor::HandleKeyPressEvent(aKeyEvent);
+  }
+
+  // Don't handle events which do not belong to us (by making sure that the
+  // target of the event is actually editable).
+  // XXX we can remove this check after bug 389372
+  nsCOMPtr<nsIDOMEventTarget> target;
+  nsresult rv = aKeyEvent->GetTarget(getter_AddRefs(target));
+  NS_ENSURE_SUCCESS(rv, rv);
+  nsCOMPtr<nsIDOMNode> targetNode = do_QueryInterface(target);
+  if (!IsModifiableNode(targetNode)) {
+    return NS_OK;
+  }
+
+  nsKeyEvent* nativeKeyEvent = GetNativeKeyEvent(aKeyEvent);
+  NS_ENSURE_TRUE(nativeKeyEvent, NS_ERROR_UNEXPECTED);
+  NS_ASSERTION(nativeKeyEvent->message == NS_KEY_PRESS,
+               "HandleKeyPressEvent gets non-keypress event");
+
+  switch (nativeKeyEvent->keyCode) {
+    case nsIDOMKeyEvent::DOM_VK_META:
+    case nsIDOMKeyEvent::DOM_VK_SHIFT:
+    case nsIDOMKeyEvent::DOM_VK_CONTROL:
+    case nsIDOMKeyEvent::DOM_VK_ALT:
+    case nsIDOMKeyEvent::DOM_VK_BACK_SPACE:
+    case nsIDOMKeyEvent::DOM_VK_DELETE:
+      // These keys are handled on nsEditor, so, we can bypass
+      // nsPlaintextEditor.
+      return nsEditor::HandleKeyPressEvent(aKeyEvent);
+    case nsIDOMKeyEvent::DOM_VK_ESCAPE:
+      // XXX nsPlaintextEditor doesn't consume the event by bug 569988,
+      // but nsHTMLEditor should eat the processed keypress event.
+      aKeyEvent->PreventDefault();
+      // This key is handled on nsPlaintextEditor.
+      return nsPlaintextEditor::HandleKeyPressEvent(aKeyEvent);
+    case nsIDOMKeyEvent::DOM_VK_TAB: {
+      if (IsPlaintextEditor()) {
+        // If this works as plain text editor, e.g., mail editor for plain
+        // text, should be handled on nsPlaintextEditor.
+        return nsPlaintextEditor::HandleKeyPressEvent(aKeyEvent);
+      }
+
+      if (IsTabbable()) {
+        return NS_OK; // let it be used for focus switching
+      }
+
+      if (nativeKeyEvent->isControl || nativeKeyEvent->isAlt ||
+          nativeKeyEvent->isMeta) {
+        return NS_OK;
+      }
+
+      nsCOMPtr<nsISelection> selection;
+      nsresult rv = GetSelection(getter_AddRefs(selection));
+      NS_ENSURE_SUCCESS(rv, rv);
+      PRInt32 offset;
+      nsCOMPtr<nsIDOMNode> node, blockParent;
+      rv = GetStartNodeAndOffset(selection, address_of(node), &offset);
+      NS_ENSURE_SUCCESS(rv, rv);
+      NS_ENSURE_TRUE(node, NS_ERROR_FAILURE);
+
+      PRBool isBlock = PR_FALSE;
+      NodeIsBlock(node, &isBlock);
+      if (isBlock) {
+        blockParent = node;
+      } else {
+        blockParent = GetBlockNodeParent(node);
+      }
+
+      if (!blockParent) {
+        break;
+      }
+
+      PRBool handled = PR_FALSE;
+      if (nsHTMLEditUtils::IsTableElement(blockParent)) {
+        rv = TabInTable(nativeKeyEvent->isShift, &handled);
+        if (handled) {
+          ScrollSelectionIntoView(PR_FALSE);
+        }
+      } else if (nsHTMLEditUtils::IsListItem(blockParent)) {
+        rv = Indent(nativeKeyEvent->isShift ?
+                      NS_LITERAL_STRING("outdent") :
+                      NS_LITERAL_STRING("indent"));
+        handled = PR_TRUE;
+      }
+      NS_ENSURE_SUCCESS(rv, rv);
+      if (handled) {
+        return aKeyEvent->PreventDefault(); // consumed
+      }
+      if (nativeKeyEvent->isShift) {
+        return NS_OK; // don't type text for shift tabs
+      }
+      aKeyEvent->PreventDefault();
+      return TypedText(NS_LITERAL_STRING("\t"), eTypedText);
+    }
+    case nsIDOMKeyEvent::DOM_VK_RETURN:
+    case nsIDOMKeyEvent::DOM_VK_ENTER:
+      if (nativeKeyEvent->isControl || nativeKeyEvent->isAlt ||
+          nativeKeyEvent->isMeta) {
+        return NS_OK;
+      }
+      aKeyEvent->PreventDefault(); // consumed
+      if (nativeKeyEvent->isShift && !IsPlaintextEditor()) {
+        // only inserts a br node
+        return TypedText(EmptyString(), eTypedBR);
+      }
+      // uses rules to figure out what to insert
+      return TypedText(EmptyString(), eTypedBreak);
+  }
+
+  // NOTE: On some keyboard layout, some characters are inputted with Control
+  // key or Alt key, but at that time, widget sets FALSE to these keys.
+  if (nativeKeyEvent->charCode == 0 || nativeKeyEvent->isControl ||
+      nativeKeyEvent->isAlt || nativeKeyEvent->isMeta) {
+    // we don't PreventDefault() here or keybindings like control-x won't work
+    return NS_OK;
+  }
+  aKeyEvent->PreventDefault();
+  nsAutoString str(nativeKeyEvent->charCode);
+  return TypedText(str, eTypedText);
+}
+
 /**
  * Returns true if the id represents an element of block type.
  * Can be used to determine if a new paragraph should be started.
  */
 nsresult
 nsHTMLEditor::NodeIsBlockStatic(nsIDOMNode *aNode, PRBool *aIsBlock)
 {
   if (!aNode || !aIsBlock) { return NS_ERROR_NULL_POINTER; }
@@ -1189,113 +1319,16 @@ nsHTMLEditor::UpdateBaseURL()
     nsCOMPtr<nsIDocument> doc = do_QueryInterface(domDoc);
     if (!doc) return NS_ERROR_FAILURE;
 
     return doc->SetBaseURI(doc->GetDocumentURI());
   }
   return NS_OK;
 }
 
-NS_IMETHODIMP nsHTMLEditor::HandleKeyPress(nsIDOMKeyEvent* aKeyEvent)
-{
-  PRUint32 keyCode, character;
-  PRBool   isShift, ctrlKey, altKey, metaKey;
-  nsresult res;
-
-  if (!aKeyEvent) return NS_ERROR_NULL_POINTER;
-
-  if (NS_SUCCEEDED(aKeyEvent->GetKeyCode(&keyCode)) && 
-      NS_SUCCEEDED(aKeyEvent->GetShiftKey(&isShift)) &&
-      NS_SUCCEEDED(aKeyEvent->GetCtrlKey(&ctrlKey)) &&
-      NS_SUCCEEDED(aKeyEvent->GetAltKey(&altKey)) &&
-      NS_SUCCEEDED(aKeyEvent->GetMetaKey(&metaKey)))
-  {
-    // this royally blows: because tabs come in from keyDowns instead
-    // of keyPress, and because GetCharCode refuses to work for keyDown
-    // i have to play games.
-    if (keyCode == nsIDOMKeyEvent::DOM_VK_TAB) character = '\t';
-    else aKeyEvent->GetCharCode(&character);
-    
-    if (keyCode == nsIDOMKeyEvent::DOM_VK_TAB)
-    {
-      if (!IsPlaintextEditor()) {
-        nsCOMPtr<nsISelection>selection;
-        res = GetSelection(getter_AddRefs(selection));
-        if (NS_FAILED(res)) return res;
-        PRInt32 offset;
-        nsCOMPtr<nsIDOMNode> node, blockParent;
-        res = GetStartNodeAndOffset(selection, address_of(node), &offset);
-        if (NS_FAILED(res)) return res;
-        if (!node) return NS_ERROR_FAILURE;
-
-        PRBool isBlock = PR_FALSE;
-        NodeIsBlock(node, &isBlock);
-        if (isBlock) blockParent = node;
-        else blockParent = GetBlockNodeParent(node);
-        
-        if (blockParent)
-        {
-          PRBool bHandled = PR_FALSE;
-          
-          if (nsHTMLEditUtils::IsTableElement(blockParent))
-          {
-            res = TabInTable(isShift, &bHandled);
-            if (bHandled)
-              ScrollSelectionIntoView(PR_FALSE);
-          }
-          else if (nsHTMLEditUtils::IsListItem(blockParent))
-          {
-            nsAutoString indentstr;
-            if (isShift) indentstr.AssignLiteral("outdent");
-            else         indentstr.AssignLiteral("indent");
-            res = Indent(indentstr);
-            bHandled = PR_TRUE;
-          }
-          if (NS_FAILED(res)) return res;
-          if (bHandled)
-            return aKeyEvent->PreventDefault(); // consumed
-        }
-      }
-      if (isShift)
-        return NS_OK; // don't type text for shift tabs
-    }
-    else if (keyCode == nsIDOMKeyEvent::DOM_VK_RETURN
-             || keyCode == nsIDOMKeyEvent::DOM_VK_ENTER)
-    {
-      aKeyEvent->PreventDefault();
-      nsString empty;
-      if (isShift && !IsPlaintextEditor())
-      {
-        return TypedText(empty, eTypedBR);  // only inserts a br node
-      }
-      else 
-      {
-        return TypedText(empty, eTypedBreak);  // uses rules to figure out what to insert
-      }
-    }
-    else if (keyCode == nsIDOMKeyEvent::DOM_VK_ESCAPE)
-    {
-      aKeyEvent->PreventDefault();
-      // pass escape keypresses through as empty strings: needed forime support
-      nsString empty;
-      return TypedText(empty, eTypedText);
-    }
-    
-    // if we got here we either fell out of the tab case or have a normal character.
-    // Either way, treat as normal character.
-    if (character && !altKey && !ctrlKey && !metaKey)
-    {
-      aKeyEvent->PreventDefault();
-      nsAutoString key(character);
-      return TypedText(key, eTypedText);
-    }
-  }
-  return NS_ERROR_FAILURE;
-}
-
 /* This routine is needed to provide a bottleneck for typing for logging
    purposes.  Can't use HandleKeyPress() (above) for that since it takes
    a nsIDOMKeyEvent* parameter.  So instead we pass enough info through
    to TypedText() to determine what action to take, but without passing
    an event.
    */
 NS_IMETHODIMP nsHTMLEditor::TypedText(const nsAString& aString,
                                       PRInt32 aAction)
--- a/editor/libeditor/html/nsHTMLEditor.h
+++ b/editor/libeditor/html/nsHTMLEditor.h
@@ -139,19 +139,19 @@ public:
 // another class. Only the base class should use NS_DECL_ISUPPORTS
   NS_DECL_ISUPPORTS_INHERITED
 
 
            nsHTMLEditor();
   virtual  ~nsHTMLEditor();
 
   /* ------------ nsPlaintextEditor overrides -------------- */
-  NS_IMETHODIMP HandleKeyPress(nsIDOMKeyEvent* aKeyEvent);
   NS_IMETHOD GetIsDocumentEditable(PRBool *aIsDocumentEditable);
   NS_IMETHODIMP BeginningOfDocument();
+  virtual nsresult HandleKeyPressEvent(nsIDOMKeyEvent* aKeyEvent);
   virtual PRBool HasFocus();
 
   /* ------------ nsIEditorIMESupport overrides ------------ */
   NS_IMETHOD GetPreferredIMEState(PRUint32 *aState);
 
   /* ------------ nsIHTMLEditor methods -------------- */
 
   NS_DECL_NSIHTMLEDITOR
--- a/editor/libeditor/html/tests/Makefile.in
+++ b/editor/libeditor/html/tests/Makefile.in
@@ -53,16 +53,17 @@ include $(topsrcdir)/config/rules.mk
 		test_bug456244.html \
 		test_bug478725.html \
 		test_bug480972.html \
 		test_bug484181.html \
 		test_bug487524.html \
 		test_bug525389.html \
 		test_bug537046.html \
 		test_contenteditable_focus.html \
+		test_htmleditor_keyevent_handling.html \
 		test_select_all_without_body.html \
 		file_select_all_without_body.html \
 		$(NULL)
 
 libs:: $(_TEST_FILES)
 	$(INSTALL) $(foreach f,$^,"$f") $(DEPTH)/_tests/testing/mochitest/tests/$(relativesrcdir)
 	(cd $(srcdir) && tar $(TAR_CREATE_FLAGS) - browserscope 2> /dev/null) | (cd $(DEPTH)/_tests/testing/mochitest/tests/$(relativesrcdir) && tar -xf -)
 
new file mode 100644
--- /dev/null
+++ b/editor/libeditor/html/tests/test_htmleditor_keyevent_handling.html
@@ -0,0 +1,666 @@
+<html>
+<head>
+  <title>Test for key event handler of HTML editor</title>
+  <script type="text/javascript"
+          src="chrome://mochikit/content/MochiKit/packed.js"></script>
+  <script type="text/javascript"
+          src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="text/javascript"
+          src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+  <link rel="stylesheet" type="text/css"
+          href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+</head>
+<body>
+<div id="display">
+  <div id="htmlEditor" contenteditable="true"><br></div>
+</div>
+<div id="content" style="display: none">
+  
+</div>
+<pre id="test">
+</pre>
+
+<script class="testbody" type="application/javascript">
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.waitForFocus(runTests, window);
+
+var htmlEditor = document.getElementById("htmlEditor");
+
+const kIsMac = navigator.platform.indexOf("Mac") == 0;
+const kIsWin = navigator.platform.indexOf("Win") == 0;
+const kIsLinux = navigator.platform.indexOf("Linux") == 0;
+
+function runTests()
+{
+  netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
+
+  var fm = Components.classes["@mozilla.org/focus-manager;1"].
+    getService(Components.interfaces.nsIFocusManager);
+
+  var capturingPhase = { fired: false, prevented: false };
+  var bubblingPhase = { fired: false, prevented: false };
+
+  var listener = {
+    handleEvent: function _hv(aEvent)
+    {
+      is(aEvent.type, "keypress", "unexpected event is handled");
+      switch (aEvent.eventPhase) {
+      case aEvent.CAPTURING_PHASE:
+        capturingPhase.fired = true;
+        capturingPhase.prevented = aEvent.getPreventDefault();
+        break;
+      case aEvent.BUBBLING_PHASE:
+        bubblingPhase.fired = true;
+        bubblingPhase.prevented = aEvent.getPreventDefault();
+        aEvent.preventDefault(); // prevent the browser default behavior
+        break;
+      default:
+        ok(false, "event is handled in unexpected phase");
+      }
+    }
+  };
+
+  function check(aDescription,
+                 aFiredOnCapture, aPreventedOnCapture,
+                 aFiredOnBubbling, aPreventedOnBubbling)
+  {
+    function getDesciption(aExpected)
+    {
+      return aDescription + (aExpected ? " wasn't " : " was ");
+    }
+    is(capturingPhase.fired, aFiredOnCapture,
+       getDesciption(aFiredOnCapture) + "fired on capture phase");
+    is(capturingPhase.prevented, aPreventedOnCapture,
+       getDesciption(aPreventedOnCapture) + "prevented on capture phase");
+    is(bubblingPhase.fired, aFiredOnBubbling,
+       getDesciption(aFiredOnBubbling) + "fired on bubbling phase");
+    is(bubblingPhase.prevented, aPreventedOnBubbling,
+       getDesciption(aPreventedOnBubbling) + "prevented on bubbling phase");
+  }
+
+  var systemGroup =
+    Components.classes["@mozilla.org/eventlistenerservice;1"].
+      getService(Components.interfaces.nsIEventListenerService).
+      systemEventGroup;
+  window.QueryInterface(Components.interfaces.nsIDOM3EventTarget);
+  window.addGroupedEventListener("keypress", listener, true, systemGroup);
+  window.addGroupedEventListener("keypress", listener, false, systemGroup);
+
+  function doTest(aElement, aDescription,
+                  aIsReadonly, aIsTabbable, aIsPlaintext)
+  {
+    function reset(aText)
+    {
+      capturingPhase.fired = false;
+      capturingPhase.prevented = false;
+      bubblingPhase.fired = false;
+      bubblingPhase.prevented = false;
+      aElement.innerHTML = aText;
+      var sel = window.getSelection();
+      var range = document.createRange();
+      range.setStart(aElement, aElement.childNodes.length);
+      sel.removeAllRanges();
+      sel.addRange(range);
+    }
+
+    function resetForIndent(aText)
+    {
+      capturingPhase.fired = false;
+      capturingPhase.prevented = false;
+      bubblingPhase.fired = false;
+      bubblingPhase.prevented = false;
+      aElement.innerHTML = aText;
+      var sel = window.getSelection();
+      var range = document.createRange();
+      var target = document.getElementById("target").firstChild;
+      range.setStart(target, target.length);
+      sel.removeAllRanges();
+      sel.addRange(range);
+    }
+
+    if (document.activeElement) {
+      document.activeElement.blur();
+    }
+
+    aDescription += ": "
+
+    aElement.focus();
+    is(fm.focusedElement, aElement, aDescription + "failed to move focus");
+
+    // Modifier keys:
+    //   Only when editor is editable, it consumes.
+    reset("");
+    synthesizeKey("VK_META", { });
+    check(aDescription + "Meta", true, false, true, !aIsReadonly);
+
+    reset("");
+    synthesizeKey("VK_SHIFT", { });
+    check(aDescription + "Shift", true, false, true, !aIsReadonly);
+
+    reset("");
+    synthesizeKey("VK_CONTROL", { });
+    check(aDescription + "Control", true, false, true, !aIsReadonly);
+
+    // Alt key press event installs menubar key event listener, so,
+    // we should pass Alt key testing on Windows and Linux.
+    if (!kIsWin && !kIsLinux) {
+      reset("");
+      synthesizeKey("VK_ALT", { });
+      check(aDescription + "Alt", true, false, true, !aIsReadonly);
+    }
+
+    // Backspace key:
+    //   If editor is readonly, it doesn't consume.
+    //   If editor is editable, it consumes backspace and shift+backspace.
+    //   Otherwise, editor doesn't consume the event.
+    reset("");
+    synthesizeKey("VK_BACK_SPACE", { });
+    check(aDescription + "Backspace", true, false, true, true);
+
+    reset("");
+    synthesizeKey("VK_BACK_SPACE", { shiftKey: true });
+    check(aDescription + "Shift+Backspace", true, false, true, true);
+
+    reset("");
+    synthesizeKey("VK_BACK_SPACE", { ctrlKey: true });
+    check(aDescription + "Ctrl+Backspace", true, false, true, aIsReadonly);
+
+    reset("");
+    synthesizeKey("VK_BACK_SPACE", { altKey: true });
+    check(aDescription + "Alt+Backspace", true, false, true, aIsReadonly);
+
+    reset("");
+    synthesizeKey("VK_BACK_SPACE", { metaKey: true });
+    check(aDescription + "Meta+Backspace", true, false, true, aIsReadonly);
+
+    // Delete key:
+    //   If editor is readonly, it doesn't consume.
+    //   If editor is editable, delete is consumed.
+    //   Otherwise, editor doesn't consume the event.
+    reset("");
+    synthesizeKey("VK_DELETE", { });
+    check(aDescription + "Delete", true, false, true, !aIsReadonly);
+
+    reset("");
+    synthesizeKey("VK_DELETE", { shiftKey: true });
+    check(aDescription + "Shift+Delete", true, false, true, false);
+
+    reset("");
+    synthesizeKey("VK_DELETE", { ctrlKey: true });
+    check(aDescription + "Ctrl+Delete", true, false, true, false);
+
+    reset("");
+    synthesizeKey("VK_DELETE", { altKey: true });
+    check(aDescription + "Alt+Delete", true, false, true, false);
+
+    reset("");
+    synthesizeKey("VK_DELETE", { metaKey: true });
+    check(aDescription + "Meta+Delete", true, false, true, false);
+
+    // Return key:
+    //   If editor is readonly, it doesn't consume.
+    //   If editor is editable and not single line editor, it consumes Return
+    //   and Shift+Return.
+    //   Otherwise, editor doesn't consume the event.
+    reset("a");
+    synthesizeKey("VK_RETURN", { });
+    check(aDescription + "Return",
+          true, false, true, !aIsReadonly);
+    is(aElement.innerHTML, aIsReadonly ? "a" : "a<br><br>",
+       aDescription + "Return");
+
+    reset("a");
+    synthesizeKey("VK_RETURN", { shiftKey: true });
+    check(aDescription + "Shift+Return",
+          true, false, true, !aIsReadonly);
+    is(aElement.innerHTML, aIsReadonly ? "a" : "a<br><br>",
+       aDescription + "Shift+Return");
+
+    reset("a");
+    synthesizeKey("VK_RETURN", { ctrlKey: true });
+    check(aDescription + "Ctrl+Return", true, false, true, false);
+    is(aElement.innerHTML, "a", aDescription + "Ctrl+Return");
+
+    reset("a");
+    synthesizeKey("VK_RETURN", { altKey: true });
+    check(aDescription + "Alt+Return", true, false, true, false);
+    is(aElement.innerHTML, "a", aDescription + "Alt+Return");
+
+    reset("a");
+    synthesizeKey("VK_RETURN", { metaKey: true });
+    check(aDescription + "Meta+Return", true, false, true, false);
+    is(aElement.innerHTML, "a", aDescription + "Meta+Return");
+
+    // Enter key (same as Return key):
+    //   If editor is readonly, it doesn't consume.
+    //   If editor is editable and not single line editor, it consumes Return
+    //   and Shift+Return.
+    //   Otherwise, editor doesn't consume the event.
+    reset("a");
+    synthesizeKey("VK_ENTER", { });
+    check(aDescription + "Enter",
+          true, false, true, !aIsReadonly);
+    is(aElement.innerHTML, aIsReadonly ? "a" : "a<br><br>",
+       aDescription + "Enter");
+
+    reset("a");
+    synthesizeKey("VK_ENTER", { shiftKey: true });
+    check(aDescription + "Shift+Enter",
+          true, false, true, !aIsReadonly);
+    is(aElement.innerHTML, aIsReadonly ? "a" : "a<br><br>",
+       aDescription + "Shift+Enter");
+
+    reset("a");
+    synthesizeKey("VK_ENTER", { ctrlKey: true });
+    check(aDescription + "Ctrl+Enter", true, false, true, false);
+    is(aElement.innerHTML, "a", aDescription + "Ctrl+Enter");
+
+    reset("a");
+    synthesizeKey("VK_ENTER", { altKey: true });
+    check(aDescription + "Alt+Enter", true, false, true, false);
+    is(aElement.innerHTML, "a", aDescription + "Alt+Enter");
+
+    reset("a");
+    synthesizeKey("VK_ENTER", { metaKey: true });
+    check(aDescription + "Meta+Enter", true, false, true, false);
+    is(aElement.innerHTML, "a", aDescription + "Meta+Enter");
+
+    // Tab key:
+    //   If editor is tabbable, editor doesn't consume all tab key events.
+    //   Otherwise, editor consumes tab key event without any modifier keys.
+    reset("a");
+    synthesizeKey("VK_TAB", { });
+    check(aDescription + "Tab",
+          true, false, true, !aIsTabbable && !aIsReadonly);
+    is(aElement.innerHTML,
+       aIsTabbable || aIsReadonly ? "a" :
+         aIsPlaintext ? "a\t" : "a&nbsp;&nbsp;&nbsp; <br>",
+       aDescription + "Tab");
+    is(fm.focusedElement, aElement,
+       aDescription + "focus moved unexpectedly (Tab)");
+
+    reset("a");
+    synthesizeKey("VK_TAB", { shiftKey: true });
+    check(aDescription + "Shift+Tab", true, false, true, false);
+    is(aElement.innerHTML, "a", aDescription + "Shift+Tab");
+    is(fm.focusedElement, aElement,
+       aDescription + "focus moved unexpectedly (Shift+Tab)");
+
+    // Ctrl+Tab is consumed by tabbrowser.
+    reset("a");
+    synthesizeKey("VK_TAB", { ctrlKey: true });
+    check(aDescription + "Ctrl+Tab", true, true, true, true);
+    is(aElement.innerHTML, "a", aDescription + "Ctrl+Tab");
+    is(fm.focusedElement, aElement,
+       aDescription + "focus moved unexpectedly (Ctrl+Tab)");
+
+    reset("a");
+    synthesizeKey("VK_TAB", { altKey: true });
+    check(aDescription + "Alt+Tab", true, false, true, false);
+    is(aElement.innerHTML, "a", aDescription + "Alt+Tab");
+    is(fm.focusedElement, aElement,
+       aDescription + "focus moved unexpectedly (Alt+Tab)");
+
+    reset("a");
+    synthesizeKey("VK_TAB", { metaKey: true });
+    check(aDescription + "Meta+Tab", true, false, true, false);
+    is(aElement.innerHTML, "a", aDescription + "Meta+Tab");
+    is(fm.focusedElement, aElement,
+       aDescription + "focus moved unexpectedly (Meta+Tab)");
+
+    // Indent/Outdent tests:
+    // UL
+    resetForIndent("<ul><li id=\"target\">ul list item</li></ul>");
+    synthesizeKey("VK_TAB", { });
+    check(aDescription + "Tab on UL",
+          true, false, true, !aIsTabbable && !aIsReadonly);
+    is(aElement.innerHTML,
+       aIsReadonly || aIsTabbable ?
+         "<ul><li id=\"target\">ul list item</li></ul>" :
+         aIsPlaintext ? "<ul><li id=\"target\">ul list item\t</li></ul>" :
+           "<ul><ul><li id=\"target\">ul list item</li></ul></ul>",
+       aDescription + "Tab on UL");
+    is(fm.focusedElement, aElement,
+       aDescription + "focus moved unexpectedly (Tab on UL)");
+    synthesizeKey("VK_TAB", { shiftKey: true });
+    check(aDescription + "Shift+Tab after Tab on UL",
+          true, false, true, !aIsTabbable && !aIsReadonly && !aIsPlaintext);
+    // XXX why do we fail to outdent on non-tabbable HTML editor?
+    is(aElement.innerHTML,
+       aIsReadonly || aIsTabbable ?
+         "<ul><li id=\"target\">ul list item</li></ul>" :
+         aIsPlaintext ? "<ul><li id=\"target\">ul list item\t</li></ul>" :
+           "<ul><ul><li id=\"target\">ul list item</li></ul></ul>",
+       aDescription + "Shift+Tab after Tab on UL");
+    is(fm.focusedElement, aElement,
+       aDescription + "focus moved unexpectedly (Shift+Tab after Tab on UL)");
+
+    resetForIndent("<ul><li id=\"target\">ul list item</li></ul>");
+    synthesizeKey("VK_TAB", { shiftKey: true });
+    check(aDescription + "Shift+Tab on UL",
+          true, false, true, !aIsTabbable && !aIsReadonly && !aIsPlaintext);
+    is(aElement.innerHTML,
+       aIsReadonly || aIsTabbable ?
+         "<ul><li id=\"target\">ul list item</li></ul>" :
+         aIsPlaintext ? "<ul><li id=\"target\">ul list item</li></ul>" :
+           "<ul><li id=\"target\">ul list item</li></ul>",
+       aDescription + "Shift+Tab on UL");
+    is(fm.focusedElement, aElement,
+       aDescription + "focus moved unexpectedly (Shift+Tab on UL)");
+
+    // Ctrl+Tab is consumed by tabbrowser.
+    resetForIndent("<ul><li id=\"target\">ul list item</li></ul>");
+    synthesizeKey("VK_TAB", { ctrlKey: true });
+    check(aDescription + "Ctrl+Tab on UL", true, true, true, true);
+    is(aElement.innerHTML, "<ul><li id=\"target\">ul list item</li></ul>",
+       aDescription + "Ctrl+Tab on UL");
+    is(fm.focusedElement, aElement,
+       aDescription + "focus moved unexpectedly (Ctrl+Tab on UL)");
+
+    resetForIndent("<ul><li id=\"target\">ul list item</li></ul>");
+    synthesizeKey("VK_TAB", { altKey: true });
+    check(aDescription + "Alt+Tab on UL", true, false, true, false);
+    is(aElement.innerHTML, "<ul><li id=\"target\">ul list item</li></ul>",
+       aDescription + "Alt+Tab on UL");
+    is(fm.focusedElement, aElement,
+       aDescription + "focus moved unexpectedly (Alt+Tab on UL)");
+
+    resetForIndent("<ul><li id=\"target\">ul list item</li></ul>");
+    synthesizeKey("VK_TAB", { metaKey: true });
+    check(aDescription + "Meta+Tab on UL", true, false, true, false);
+    is(aElement.innerHTML, "<ul><li id=\"target\">ul list item</li></ul>",
+       aDescription + "Meta+Tab on UL");
+    is(fm.focusedElement, aElement,
+       aDescription + "focus moved unexpectedly (Meta+Tab on UL)");
+
+    // OL
+    resetForIndent("<ol><li id=\"target\">ol list item</li></ol>");
+    synthesizeKey("VK_TAB", { });
+    check(aDescription + "Tab on OL",
+          true, false, true, !aIsTabbable && !aIsReadonly);
+    is(aElement.innerHTML,
+       aIsReadonly || aIsTabbable ?
+         "<ol><li id=\"target\">ol list item</li></ol>" :
+         aIsPlaintext ? "<ol><li id=\"target\">ol list item\t</li></ol>" :
+           "<ol><ol><li id=\"target\">ol list item</li></ol></ol>",
+       aDescription + "Tab on OL");
+    is(fm.focusedElement, aElement,
+       aDescription + "focus moved unexpectedly (Tab on OL)");
+    synthesizeKey("VK_TAB", { shiftKey: true });
+    check(aDescription + "Shift+Tab after Tab on OL",
+          true, false, true, !aIsTabbable && !aIsReadonly && !aIsPlaintext);
+    // XXX why do we fail to outdent on non-tabbable HTML editor?
+    is(aElement.innerHTML,
+       aIsReadonly || aIsTabbable ?
+         "<ol><li id=\"target\">ol list item</li></ol>" :
+         aIsPlaintext ? "<ol><li id=\"target\">ol list item\t</li></ol>" :
+           "<ol><ol><li id=\"target\">ol list item</li></ol></ol>",
+       aDescription + "Shift+Tab after Tab on OL");
+    is(fm.focusedElement, aElement,
+       aDescription + "focus moved unexpectedly (Shift+Tab after Tab on OL)");
+
+    resetForIndent("<ol><li id=\"target\">ol list item</li></ol>");
+    synthesizeKey("VK_TAB", { shiftKey: true });
+    check(aDescription + "Shift+Tab on OL",
+          true, false, true, !aIsTabbable && !aIsReadonly && !aIsPlaintext);
+    is(aElement.innerHTML,
+       aIsReadonly || aIsTabbable ?
+         "<ol><li id=\"target\">ol list item</li></ol>" :
+         aIsPlaintext ? "<ol><li id=\"target\">ol list item</li></ol>" :
+           "<ol><li id=\"target\">ol list item</li></ol>",
+       aDescription + "Shfit+Tab on OL");
+    is(fm.focusedElement, aElement,
+       aDescription + "focus moved unexpectedly (Shift+Tab on OL)");
+
+    // Ctrl+Tab is consumed by tabbrowser.
+    resetForIndent("<ol><li id=\"target\">ol list item</li></ol>");
+    synthesizeKey("VK_TAB", { ctrlKey: true });
+    check(aDescription + "Ctrl+Tab on OL", true, true, true, true);
+    is(aElement.innerHTML, "<ol><li id=\"target\">ol list item</li></ol>",
+       aDescription + "Ctrl+Tab on OL");
+    is(fm.focusedElement, aElement,
+       aDescription + "focus moved unexpectedly (Ctrl+Tab on OL)");
+
+    resetForIndent("<ol><li id=\"target\">ol list item</li></ol>");
+    synthesizeKey("VK_TAB", { altKey: true });
+    check(aDescription + "Alt+Tab on OL", true, false, true, false);
+    is(aElement.innerHTML, "<ol><li id=\"target\">ol list item</li></ol>",
+       aDescription + "Alt+Tab on OL");
+    is(fm.focusedElement, aElement,
+       aDescription + "focus moved unexpectedly (Alt+Tab on OL)");
+
+    resetForIndent("<ol><li id=\"target\">ol list item</li></ol>");
+    synthesizeKey("VK_TAB", { metaKey: true });
+    check(aDescription + "Meta+Tab on OL", true, false, true, false);
+    is(aElement.innerHTML, "<ol><li id=\"target\">ol list item</li></ol>",
+       aDescription + "Meta+Tab on OL");
+    is(fm.focusedElement, aElement,
+       aDescription + "focus moved unexpectedly (Meta+Tab on OL)");
+
+    // TD
+    resetForIndent("<table><tr><td id=\"target\">td</td></tr></table>");
+    synthesizeKey("VK_TAB", { });
+    check(aDescription + "Tab on TD",
+          true, false, true, !aIsTabbable && !aIsReadonly);
+    is(aElement.innerHTML,
+       aIsTabbable || aIsReadonly ?
+         "<table><tbody><tr><td id=\"target\">td</td></tr></tbody></table>" :
+         aIsPlaintext ? "<table><tbody><tr><td id=\"target\">td\t</td></tr></tbody></table>" :
+           "<table><tbody><tr><td id=\"target\">td</td></tr><tr><td style=\"vertical-align: top;\"><br></td></tr></tbody></table>",
+       aDescription + "Tab on TD");
+    is(fm.focusedElement, aElement,
+       aDescription + "focus moved unexpectedly (Tab on TD)");
+    synthesizeKey("VK_TAB", { shiftKey: true });
+    check(aDescription + "Shift+Tab after Tab on TD",
+          true, false, true, !aIsTabbable && !aIsReadonly && !aIsPlaintext);
+    is(aElement.innerHTML,
+       aIsTabbable || aIsReadonly ?
+         "<table><tbody><tr><td id=\"target\">td</td></tr></tbody></table>" :
+         aIsPlaintext ? "<table><tbody><tr><td id=\"target\">td\t</td></tr></tbody></table>" :
+           "<table><tbody><tr><td id=\"target\">td</td></tr><tr><td style=\"vertical-align: top;\"><br></td></tr></tbody></table>",
+       aDescription + "Shift+Tab after Tab on TD");
+    is(fm.focusedElement, aElement,
+       aDescription + "focus moved unexpectedly (Shift+Tab after Tab on TD)");
+
+    resetForIndent("<table><tr><td id=\"target\">td</td></tr></table>");
+    synthesizeKey("VK_TAB", { shiftKey: true });
+    check(aDescription + "Shift+Tab on TD", true, false, true, false);
+    is(aElement.innerHTML,
+       "<table><tbody><tr><td id=\"target\">td</td></tr></tbody></table>",
+       aDescription + "Shift+Tab on TD");
+    is(fm.focusedElement, aElement,
+       aDescription + "focus moved unexpectedly (Shift+Tab on TD)");
+
+    // Ctrl+Tab is consumed by tabbrowser.
+    resetForIndent("<table><tr><td id=\"target\">td</td></tr></table>");
+    synthesizeKey("VK_TAB", { ctrlKey: true });
+    check(aDescription + "Ctrl+Tab on TD", true, true, true, true);
+    is(aElement.innerHTML,
+       "<table><tbody><tr><td id=\"target\">td</td></tr></tbody></table>",
+       aDescription + "Ctrl+Tab on TD");
+    is(fm.focusedElement, aElement,
+       aDescription + "focus moved unexpectedly (Ctrl+Tab on TD)");
+
+    resetForIndent("<table><tr><td id=\"target\">td</td></tr></table>");
+    synthesizeKey("VK_TAB", { altKey: true });
+    check(aDescription + "Alt+Tab on TD", true, false, true, false);
+    is(aElement.innerHTML,
+       "<table><tbody><tr><td id=\"target\">td</td></tr></tbody></table>",
+       aDescription + "Alt+Tab on TD");
+    is(fm.focusedElement, aElement,
+       aDescription + "focus moved unexpectedly (Alt+Tab on TD)");
+
+    resetForIndent("<table><tr><td id=\"target\">td</td></tr></table>");
+    synthesizeKey("VK_TAB", { metaKey: true });
+    check(aDescription + "Meta+Tab on TD", true, false, true, false);
+    is(aElement.innerHTML,
+       "<table><tbody><tr><td id=\"target\">td</td></tr></tbody></table>",
+       aDescription + "Meta+Tab on TD");
+    is(fm.focusedElement, aElement,
+       aDescription + "focus moved unexpectedly (Meta+Tab on TD)");
+
+    // TH
+    resetForIndent("<table><tr><th id=\"target\">th</th></tr></table>");
+    synthesizeKey("VK_TAB", { });
+    check(aDescription + "Tab on TH",
+          true, false, true, !aIsTabbable && !aIsReadonly);
+    is(aElement.innerHTML,
+       aIsTabbable || aIsReadonly ?
+         "<table><tbody><tr><th id=\"target\">th</th></tr></tbody></table>" :
+         aIsPlaintext ? "<table><tbody><tr><th id=\"target\">th\t</th></tr></tbody></table>" :
+           "<table><tbody><tr><th id=\"target\">th</th></tr><tr><td style=\"vertical-align: top;\"><br></td></tr></tbody></table>",
+       aDescription + "Tab on TH");
+    is(fm.focusedElement, aElement,
+       aDescription + "focus moved unexpectedly (Tab on TH)");
+    synthesizeKey("VK_TAB", { shiftKey: true });
+    check(aDescription + "Shift+Tab after Tab on TH",
+          true, false, true, !aIsTabbable && !aIsReadonly && !aIsPlaintext);
+    is(aElement.innerHTML,
+       aIsTabbable || aIsReadonly ?
+         "<table><tbody><tr><th id=\"target\">th</th></tr></tbody></table>" :
+         aIsPlaintext ? "<table><tbody><tr><th id=\"target\">th\t</th></tr></tbody></table>" :
+           "<table><tbody><tr><th id=\"target\">th</th></tr><tr><td style=\"vertical-align: top;\"><br></td></tr></tbody></table>",
+       aDescription + "Shift+Tab after Tab on TH");
+    is(fm.focusedElement, aElement,
+       aDescription + "focus moved unexpectedly (Shift+Tab after Tab on TH)");
+
+    resetForIndent("<table><tr><th id=\"target\">th</th></tr></table>");
+    synthesizeKey("VK_TAB", { shiftKey: true });
+    check(aDescription + "Shift+Tab on TH", true, false, true, false);
+    is(aElement.innerHTML,
+       "<table><tbody><tr><th id=\"target\">th</th></tr></tbody></table>",
+       aDescription + "Shift+Tab on TH");
+    is(fm.focusedElement, aElement,
+       aDescription + "focus moved unexpectedly (Shift+Tab on TH)");
+
+    // Ctrl+Tab is consumed by tabbrowser.
+    resetForIndent("<table><tr><th id=\"target\">th</th></tr></table>");
+    synthesizeKey("VK_TAB", { ctrlKey: true });
+    check(aDescription + "Ctrl+Tab on TH", true, true, true, true);
+    is(aElement.innerHTML,
+       "<table><tbody><tr><th id=\"target\">th</th></tr></tbody></table>",
+       aDescription + "Ctrl+Tab on TH");
+    is(fm.focusedElement, aElement,
+       aDescription + "focus moved unexpectedly (Ctrl+Tab on TH)");
+
+    resetForIndent("<table><tr><th id=\"target\">th</th></tr></table>");
+    synthesizeKey("VK_TAB", { altKey: true });
+    check(aDescription + "Alt+Tab on TH", true, false, true, false);
+    is(aElement.innerHTML,
+       "<table><tbody><tr><th id=\"target\">th</th></tr></tbody></table>",
+       aDescription + "Alt+Tab on TH");
+    is(fm.focusedElement, aElement,
+       aDescription + "focus moved unexpectedly (Alt+Tab on TH)");
+
+    resetForIndent("<table><tr><th id=\"target\">th</th></tr></table>");
+    synthesizeKey("VK_TAB", { metaKey: true });
+    check(aDescription + "Meta+Tab on TH", true, false, true, false);
+    is(aElement.innerHTML,
+       "<table><tbody><tr><th id=\"target\">th</th></tr></tbody></table>",
+       aDescription + "Meta+Tab on TH");
+    is(fm.focusedElement, aElement,
+       aDescription + "focus moved unexpectedly (Meta+Tab on TH)");
+
+    // Esc key:
+    //   In all cases, esc key events are not consumed
+    reset("abc");
+    synthesizeKey("VK_ESCAPE", { });
+    check(aDescription + "Esc", true, false, true, !aIsReadonly);
+
+    reset("abc");
+    synthesizeKey("VK_ESCAPE", { shiftKey: true });
+    check(aDescription + "Shift+Esc", true, false, true, !aIsReadonly);
+
+    reset("abc");
+    synthesizeKey("VK_ESCAPE", { ctrlKey: true });
+    check(aDescription + "Ctrl+Esc", true, false, true, !aIsReadonly);
+
+    reset("abc");
+    synthesizeKey("VK_ESCAPE", { altKey: true });
+    check(aDescription + "Alt+Esc", true, false, true, !aIsReadonly);
+
+    reset("abc");
+    synthesizeKey("VK_ESCAPE", { metaKey: true });
+    check(aDescription + "Meta+Esc", true, false, true, !aIsReadonly);
+
+    // typical typing tests:
+    reset("");
+    synthesizeKey("M", { shiftKey: true });
+    check(aDescription + "M", true, false, true, !aIsReadonly);
+    synthesizeKey("o", { });
+    check(aDescription + "o", true, false, true, !aIsReadonly);
+    synthesizeKey("z", { });
+    check(aDescription + "z", true, false, true, !aIsReadonly);
+    synthesizeKey("i", { });
+    check(aDescription + "i", true, false, true, !aIsReadonly);
+    synthesizeKey("l", { });
+    check(aDescription + "l", true, false, true, !aIsReadonly);
+    synthesizeKey("l", { });
+    check(aDescription + "l", true, false, true, !aIsReadonly);
+    synthesizeKey("a", { });
+    check(aDescription + "a", true, false, true, !aIsReadonly);
+    synthesizeKey(" ", { });
+    check(aDescription + "' '", true, false, true, !aIsReadonly);
+    is(aElement.innerHTML,
+       aIsReadonly ? "" : aIsPlaintext ? "Mozilla " : "Mozilla <br>",
+       aDescription + "typed \"Mozilla \"");
+  }
+
+  doTest(htmlEditor, "contenteditable=\"true\"", false, true, false);
+
+  const nsIPlaintextEditor = Components.interfaces.nsIPlaintextEditor;
+  var editor =
+    window.QueryInterface(Components.interfaces.nsIInterfaceRequestor).
+      getInterface(Components.interfaces.nsIWebNavigation).
+      QueryInterface(Components.interfaces.nsIEditorDocShell).editor;
+  var flags = editor.flags;
+  // readonly
+  editor.flags = flags | nsIPlaintextEditor.eEditorReadonlyMask;
+  doTest(htmlEditor, "readonly HTML editor", true, true, false);
+
+  // non-tabbable
+  editor.flags = flags & ~(nsIPlaintextEditor.eEditorAllowInteraction);
+  doTest(htmlEditor, "non-tabbable HTML editor", false, false, false);
+
+  // readonly and non-tabbable
+  editor.flags =
+    (flags | nsIPlaintextEditor.eEditorReadonlyMask) &
+      ~(nsIPlaintextEditor.eEditorAllowInteraction);
+  doTest(htmlEditor, "readonly and non-tabbable HTML editor",
+         true, false, false);
+
+  // plaintext
+  editor.flags = flags | nsIPlaintextEditor.eEditorPlaintextMask;
+  doTest(htmlEditor, "HTML editor but plaintext mode", false, true, true);
+
+  // plaintext and non-tabbable
+  editor.flags = (flags | nsIPlaintextEditor.eEditorPlaintextMask) &
+                 ~(nsIPlaintextEditor.eEditorAllowInteraction);
+  doTest(htmlEditor, "non-tabbable HTML editor but plaintext mode",
+         false, false, true);
+
+
+  // readonly and plaintext
+  editor.flags = flags | nsIPlaintextEditor.eEditorPlaintextMask |
+                         nsIPlaintextEditor.eEditorReadonlyMask;
+  doTest(htmlEditor, "readonly HTML editor but plaintext mode",
+         true, true, true);
+
+  // readonly, plaintext and non-tabbable
+  editor.flags = (flags | nsIPlaintextEditor.eEditorPlaintextMask |
+                          nsIPlaintextEditor.eEditorReadonlyMask) &
+                 ~(nsIPlaintextEditor.eEditorAllowInteraction);
+  doTest(htmlEditor, "readonly and non-tabbable HTML editor but plaintext mode",
+         true, false, true);
+
+  window.removeGroupedEventListener("keypress", listener, true, systemGroup);
+  window.removeGroupedEventListener("keypress", listener, false, systemGroup);
+
+  SimpleTest.finish();
+}
+
+</script>
+</body>
+
+</html>
--- a/editor/libeditor/text/nsPlaintextEditor.cpp
+++ b/editor/libeditor/text/nsPlaintextEditor.cpp
@@ -349,59 +349,94 @@ nsPlaintextEditor::GetIsDocumentEditable
   return NS_OK;
 }
 
 PRBool nsPlaintextEditor::IsModifiable()
 {
   return !IsReadonly();
 }
 
+nsresult
+nsPlaintextEditor::HandleKeyPressEvent(nsIDOMKeyEvent* aKeyEvent)
+{
+  // NOTE: When you change this method, you should also change:
+  //   * editor/libeditor/text/tests/test_texteditor_keyevent_handling.html
+  //   * editor/libeditor/html/tests/test_htmleditor_keyevent_handling.html
+  //
+  // And also when you add new key handling, you need to change the subclass's
+  // HandleKeyPressEvent()'s switch statement.
+
+  if (IsReadonly() || IsDisabled()) {
+    // When we're not editable, the events handled on nsEditor.
+    return nsEditor::HandleKeyPressEvent(aKeyEvent);
+  }
+
+  nsKeyEvent* nativeKeyEvent = GetNativeKeyEvent(aKeyEvent);
+  NS_ENSURE_TRUE(nativeKeyEvent, NS_ERROR_UNEXPECTED);
+  NS_ASSERTION(nativeKeyEvent->message == NS_KEY_PRESS,
+               "HandleKeyPressEvent gets non-keypress event");
+
+  switch (nativeKeyEvent->keyCode) {
+    case nsIDOMKeyEvent::DOM_VK_META:
+    case nsIDOMKeyEvent::DOM_VK_SHIFT:
+    case nsIDOMKeyEvent::DOM_VK_CONTROL:
+    case nsIDOMKeyEvent::DOM_VK_ALT:
+    case nsIDOMKeyEvent::DOM_VK_BACK_SPACE:
+    case nsIDOMKeyEvent::DOM_VK_DELETE:
+      // These keys are handled on nsEditor
+      return nsEditor::HandleKeyPressEvent(aKeyEvent);
+    case nsIDOMKeyEvent::DOM_VK_TAB: {
+      if (IsTabbable()) {
+        return NS_OK; // let it be used for focus switching
+      }
+
+      if (nativeKeyEvent->isShift || nativeKeyEvent->isControl ||
+          nativeKeyEvent->isAlt || nativeKeyEvent->isMeta) {
+        return NS_OK;
+      }
+
+      // else we insert the tab straight through
+      aKeyEvent->PreventDefault();
+      return TypedText(NS_LITERAL_STRING("\t"), eTypedText);
+    }
+    case nsIDOMKeyEvent::DOM_VK_RETURN:
+    case nsIDOMKeyEvent::DOM_VK_ENTER:
+      if (IsSingleLineEditor() || nativeKeyEvent->isControl ||
+          nativeKeyEvent->isAlt || nativeKeyEvent->isMeta) {
+        return NS_OK;
+      }
+      aKeyEvent->PreventDefault();
+      return TypedText(EmptyString(), eTypedBreak);
+    case nsIDOMKeyEvent::DOM_VK_ESCAPE:
+      // pass escape keypresses through as empty strings: needed for IME support
+      // XXX This might be broken, we should check the behavior, see bug 471322.
+      // XXX Even if this keypress event is handled, this doesn't consume the
+      // event. This is wrong behavior but we have serious problem,
+      // see bug 569988 and 570455.
+      return TypedText(EmptyString(), eTypedText);
+  }
+
+  // NOTE: On some keyboard layout, some characters are inputted with Control
+  // key or Alt key, but at that time, widget sets FALSE to these keys.
+  if (nativeKeyEvent->charCode == 0 || nativeKeyEvent->isControl ||
+      nativeKeyEvent->isAlt || nativeKeyEvent->isMeta) {
+    // we don't PreventDefault() here or keybindings like control-x won't work
+    return NS_OK;
+  }
+  aKeyEvent->PreventDefault();
+  nsAutoString str(nativeKeyEvent->charCode);
+  return TypedText(str, eTypedText);
+}
 
 #ifdef XP_MAC
 #pragma mark -
 #pragma mark  nsIHTMLEditor methods 
 #pragma mark -
 #endif
 
-NS_IMETHODIMP nsPlaintextEditor::HandleKeyPress(nsIDOMKeyEvent* aKeyEvent)
-{
-  PRUint32 keyCode, character;
-  PRBool   ctrlKey, altKey, metaKey;
-
-  if (!aKeyEvent) return NS_ERROR_NULL_POINTER;
-
-  if (NS_SUCCEEDED(aKeyEvent->GetKeyCode(&keyCode)) && 
-      NS_SUCCEEDED(aKeyEvent->GetCtrlKey(&ctrlKey)) &&
-      NS_SUCCEEDED(aKeyEvent->GetAltKey(&altKey)) &&
-      NS_SUCCEEDED(aKeyEvent->GetMetaKey(&metaKey)))
-  {
-    aKeyEvent->GetCharCode(&character);
-    if (keyCode == nsIDOMKeyEvent::DOM_VK_RETURN
-     || keyCode == nsIDOMKeyEvent::DOM_VK_ENTER)
-    {
-      nsString empty;
-      return TypedText(empty, eTypedBreak);
-    }
-    else if (keyCode == nsIDOMKeyEvent::DOM_VK_ESCAPE)
-    {
-      // pass escape keypresses through as empty strings: needed for ime support
-      nsString empty;
-      return TypedText(empty, eTypedText);
-    }
-    
-    if (character && !altKey && !ctrlKey && !metaKey)
-    {
-      aKeyEvent->PreventDefault();
-      nsAutoString key(character);
-      return TypedText(key, eTypedText);
-    }
-  }
-  return NS_ERROR_FAILURE;
-}
-
 /* This routine is needed to provide a bottleneck for typing for logging
    purposes.  Can't use HandleKeyPress() (above) for that since it takes
    a nsIDOMKeyEvent* parameter.  So instead we pass enough info through
    to TypedText() to determine what action to take, but without passing
    an event.
    */
 NS_IMETHODIMP nsPlaintextEditor::TypedText(const nsAString& aString,
                                       PRInt32 aAction)
--- a/editor/libeditor/text/nsPlaintextEditor.h
+++ b/editor/libeditor/text/nsPlaintextEditor.h
@@ -143,16 +143,18 @@ public:
 
   /** All editor operations which alter the doc should be followed
    *  with a call to EndOperation */
   NS_IMETHOD EndOperation();
 
   /** make the given selection span the entire document */
   NS_IMETHOD SelectEntireDocument(nsISelection *aSelection);
 
+  virtual nsresult HandleKeyPressEvent(nsIDOMKeyEvent* aKeyEvent);
+
   /* ------------ Utility Routines, not part of public API -------------- */
   NS_IMETHOD TypedText(const nsAString& aString, PRInt32 aAction);
 
   /** Returns the absolute position of the end points of aSelection
    * in the document as a text stream.
    * Invariant: aStartOffset <= aEndOffset.
    */
   nsresult GetTextSelectionOffsets(nsISelection *aSelection,
--- a/editor/libeditor/text/tests/Makefile.in
+++ b/editor/libeditor/text/tests/Makefile.in
@@ -43,11 +43,19 @@ relativesrcdir  = editor/libeditor/text/
 
 include $(DEPTH)/config/autoconf.mk
 include $(topsrcdir)/config/rules.mk
 
 _TEST_FILES = \
 		test_bug471722.html \
 		$(NULL)
 
+# disables the key handling test on gtk2 because gtk2 overrides some key events
+# on our editor, and the combinations depend on the system.
+ifneq ($(MOZ_WIDGET_TOOLKIT),gtk2)
+_TEST_FILES += \
+		test_texteditor_keyevent_handling.html \
+		$(NULL)
+endif
+
 libs:: $(_TEST_FILES)
 	$(INSTALL) $(foreach f,$^,"$f") $(DEPTH)/_tests/testing/mochitest/tests/$(relativesrcdir)
 
new file mode 100644
--- /dev/null
+++ b/editor/libeditor/text/tests/test_texteditor_keyevent_handling.html
@@ -0,0 +1,404 @@
+<html>
+<head>
+  <title>Test for key event handler of text editor</title>
+  <script type="text/javascript"
+          src="chrome://mochikit/content/MochiKit/packed.js"></script>
+  <script type="text/javascript"
+          src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="text/javascript"
+          src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+  <link rel="stylesheet" type="text/css"
+          href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+</head>
+<body>
+<div id="display">
+  <input type="text" id="inputField">
+  <input type="password" id="passwordField">
+  <textarea id="textarea"></textarea>
+</div>
+<div id="content" style="display: none">
+  
+</div>
+<pre id="test">
+</pre>
+
+<script class="testbody" type="application/javascript">
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.waitForFocus(runTests, window);
+
+var inputField = document.getElementById("inputField");
+var passwordField = document.getElementById("passwordField");
+var textarea = document.getElementById("textarea");
+
+const kIsMac = navigator.platform.indexOf("Mac") == 0;
+const kIsWin = navigator.platform.indexOf("Win") == 0;
+const kIsLinux = navigator.platform.indexOf("Linux") == 0;
+
+function runTests()
+{
+  netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
+
+  var fm = Components.classes["@mozilla.org/focus-manager;1"].
+    getService(Components.interfaces.nsIFocusManager);
+
+  var capturingPhase = { fired: false, prevented: false };
+  var bubblingPhase = { fired: false, prevented: false };
+
+  var listener = {
+    handleEvent: function _hv(aEvent)
+    {
+      is(aEvent.type, "keypress", "unexpected event is handled");
+      switch (aEvent.eventPhase) {
+      case aEvent.CAPTURING_PHASE:
+        capturingPhase.fired = true;
+        capturingPhase.prevented = aEvent.getPreventDefault();
+        break;
+      case aEvent.BUBBLING_PHASE:
+        bubblingPhase.fired = true;
+        bubblingPhase.prevented = aEvent.getPreventDefault();
+        aEvent.preventDefault(); // prevent the browser default behavior
+        break;
+      default:
+        ok(false, "event is handled in unexpected phase");
+      }
+    }
+  };
+
+  function check(aDescription,
+                 aFiredOnCapture, aPreventedOnCapture,
+                 aFiredOnBubbling, aPreventedOnBubbling)
+  {
+    function getDesciption(aExpected)
+    {
+      return aDescription + (aExpected ? " wasn't " : " was ");
+    }
+    is(capturingPhase.fired, aFiredOnCapture,
+       getDesciption(aFiredOnCapture) + "fired on capture phase");
+    is(capturingPhase.prevented, aPreventedOnCapture,
+       getDesciption(aPreventedOnCapture) + "prevented on capture phase");
+    is(bubblingPhase.fired, aFiredOnBubbling,
+       getDesciption(aFiredOnBubbling) + "fired on bubbling phase");
+    is(bubblingPhase.prevented, aPreventedOnBubbling,
+       getDesciption(aPreventedOnBubbling) + "prevented on bubbling phase");
+  }
+
+  var systemGroup =
+    Components.classes["@mozilla.org/eventlistenerservice;1"].
+      getService(Components.interfaces.nsIEventListenerService).
+      systemEventGroup;
+  var parentElement = document.getElementById("display");
+  parentElement.QueryInterface(Components.interfaces.nsIDOM3EventTarget);
+  parentElement.addGroupedEventListener("keypress", listener, true, systemGroup);
+  parentElement.addGroupedEventListener("keypress", listener, false, systemGroup);
+
+  function doTest(aElement, aDescription, aIsSingleLine, aIsReadonly,
+                  aIsTabbable)
+  {
+    function reset(aText)
+    {
+      capturingPhase.fired = false;
+      capturingPhase.prevented = false;
+      bubblingPhase.fired = false;
+      bubblingPhase.prevented = false;
+      aElement.value = aText;
+    }
+
+    if (document.activeElement) {
+      document.activeElement.blur();
+    }
+
+    aDescription += ": "
+
+    aElement.focus();
+    is(fm.focusedElement, aElement, aDescription + "failed to move focus");
+
+    // Modifier keys:
+    //   Only when editor is editable, it consumes.
+    reset("");
+    synthesizeKey("VK_META", { });
+    check(aDescription + "Meta", true, false, true, !aIsReadonly);
+
+    reset("");
+    synthesizeKey("VK_SHIFT", { });
+    check(aDescription + "Shift", true, false, true, !aIsReadonly);
+
+    reset("");
+    synthesizeKey("VK_CONTROL", { });
+    check(aDescription + "Control", true, false, true, !aIsReadonly);
+
+    // Alt key press event installs menubar key event listener, so,
+    // we should pass Alt key testing on Windows and Linux.
+    if (!kIsWin && !kIsLinux) {
+      reset("");
+      synthesizeKey("VK_ALT", { });
+      check(aDescription + "Alt", true, false, true, !aIsReadonly);
+    }
+
+    // Backspace key:
+    //   If editor is readonly, it doesn't consume.
+    //   If editor is editable, it consumes backspace and shift+backspace.
+    //   Otherwise, editor doesn't consume the event but the native key
+    //   bindings on nsTextControlFrame may consume it.
+    reset("");
+    synthesizeKey("VK_BACK_SPACE", { });
+    check(aDescription + "Backspace", true, false, true, true);
+
+    reset("");
+    synthesizeKey("VK_BACK_SPACE", { shiftKey: true });
+    check(aDescription + "Shift+Backspace", true, false, true, true);
+
+    reset("");
+    synthesizeKey("VK_BACK_SPACE", { ctrlKey: true });
+    // Win: cmd_deleteWordBackward
+    check(aDescription + "Ctrl+Backspace",
+          true, false, true, aIsReadonly || kIsWin);
+
+    reset("");
+    synthesizeKey("VK_BACK_SPACE", { altKey: true });
+    // Win: cmd_undo
+    // Mac: cmd_deleteWordBackward
+    check(aDescription + "Alt+Backspace",
+          true, false, true, aIsReadonly || kIsWin || kIsMac);
+
+    reset("");
+    synthesizeKey("VK_BACK_SPACE", { metaKey: true });
+    check(aDescription + "Meta+Backspace", true, false, true, aIsReadonly);
+
+    // Delete key:
+    //   If editor is readonly, it doesn't consume.
+    //   If editor is editable, delete is consumed.
+    //   Otherwise, editor doesn't consume the event but the native key
+    //   bindings on nsTextControlFrame may consume it.
+    reset("");
+    synthesizeKey("VK_DELETE", { });
+    // Linux: native handler
+    check(aDescription + "Delete",
+          true, false, true, !aIsReadonly || kIsLinux);
+
+    reset("");
+    // Win: cmd_cutOrDelete
+    // Linux: cmd_cut
+    synthesizeKey("VK_DELETE", { shiftKey: true });
+    check(aDescription + "Shift+Delete",
+          true, false, true, kIsWin || kIsLinux);
+
+    reset("");
+    synthesizeKey("VK_DELETE", { ctrlKey: true });
+    // Win: cmd_deleteWordForward
+    // Linux: cmd_copy
+    check(aDescription + "Ctrl+Delete",
+          true, false, true, kIsWin || kIsLinux);
+
+    reset("");
+    synthesizeKey("VK_DELETE", { altKey: true });
+    // Mac: cmd_deleteWordForward
+    check(aDescription + "Alt+Delete",
+          true, false, true, kIsMac);
+
+    reset("");
+    synthesizeKey("VK_DELETE", { metaKey: true });
+    // Linux: native handler consumed.
+    check(aDescription + "Meta+Delete",
+          true, false, true, kIsLinux);
+
+    // XXX input.value returns "\n" when it's empty, so, we should use dummy
+    // value ("a") for the following tests.
+
+    // Return key:
+    //   If editor is readonly, it doesn't consume.
+    //   If editor is editable and not single line editor, it consumes Return
+    //   and Shift+Return.
+    //   Otherwise, editor doesn't consume the event.
+    reset("a");
+    synthesizeKey("VK_RETURN", { });
+    check(aDescription + "Return",
+          true, false, true, !aIsSingleLine && !aIsReadonly);
+    is(aElement.value, !aIsSingleLine && !aIsReadonly ? "a\n" : "a",
+       aDescription + "Return");
+
+    reset("a");
+    synthesizeKey("VK_RETURN", { shiftKey: true });
+    check(aDescription + "Shift+Return",
+          true, false, true, !aIsSingleLine && !aIsReadonly);
+    is(aElement.value, !aIsSingleLine && !aIsReadonly ? "a\n" : "a",
+       aDescription + "Shift+Return");
+
+    reset("a");
+    synthesizeKey("VK_RETURN", { ctrlKey: true });
+    check(aDescription + "Ctrl+Return", true, false, true, false);
+    is(aElement.value, "a", aDescription + "Ctrl+Return");
+
+    reset("a");
+    synthesizeKey("VK_RETURN", { altKey: true });
+    check(aDescription + "Alt+Return", true, false, true, false);
+    is(aElement.value, "a", aDescription + "Alt+Return");
+
+    reset("a");
+    synthesizeKey("VK_RETURN", { metaKey: true });
+    check(aDescription + "Meta+Return", true, false, true, false);
+    is(aElement.value, "a", aDescription + "Meta+Return");
+
+    // Enter key (same as Return key):
+    //   If editor is readonly, it doesn't consume.
+    //   If editor is editable and not single line editor, it consumes Return
+    //   and Shift+Return.
+    //   Otherwise, editor doesn't consume the event.
+    reset("a");
+    synthesizeKey("VK_ENTER", { });
+    check(aDescription + "Enter",
+          true, false, true, !aIsSingleLine && !aIsReadonly);
+    is(aElement.value, !aIsSingleLine && !aIsReadonly ? "a\n" : "a",
+       aDescription + "Enter");
+
+    reset("a");
+    synthesizeKey("VK_ENTER", { shiftKey: true });
+    check(aDescription + "Shift+Enter",
+          true, false, true, !aIsSingleLine && !aIsReadonly);
+    is(aElement.value, !aIsSingleLine && !aIsReadonly ? "a\n" : "a",
+       aDescription + "Shift+Enter");
+
+    reset("a");
+    synthesizeKey("VK_ENTER", { ctrlKey: true });
+    check(aDescription + "Ctrl+Enter", true, false, true, false);
+    is(aElement.value, "a", aDescription + "Ctrl+Enter");
+
+    reset("a");
+    synthesizeKey("VK_ENTER", { altKey: true });
+    check(aDescription + "Alt+Enter", true, false, true, false);
+    is(aElement.value, "a", aDescription + "Alt+Enter");
+
+    reset("a");
+    synthesizeKey("VK_ENTER", { metaKey: true });
+    check(aDescription + "Meta+Enter", true, false, true, false);
+    is(aElement.value, "a", aDescription + "Meta+Enter");
+
+    // Tab key:
+    //   If editor is tabbable, editor doesn't consume all tab key events.
+    //   Otherwise, editor consumes tab key event without any modifier keys.
+    reset("a");
+    synthesizeKey("VK_TAB", { });
+    check(aDescription + "Tab",
+          true, false, true, !aIsTabbable && !aIsReadonly);
+    // The tab char is converted to 4 space characters because textarea/input
+    // elements are not preformatted editor.
+    is(aElement.value, !aIsTabbable && !aIsReadonly ? "a    " : "a",
+       aDescription + "Tab");
+    is(fm.focusedElement, aElement,
+       aDescription + "focus moved unexpectedly (Tab)");
+
+    reset("a");
+    synthesizeKey("VK_TAB", { shiftKey: true });
+    check(aDescription + "Shift+Tab", true, false, true, false);
+    is(aElement.value, "a", aDescription + "Shift+Tab");
+    is(fm.focusedElement, aElement,
+       aDescription + "focus moved unexpectedly (Shift+Tab)");
+
+    // Ctrl+Tab is consumed by tabbrowser.
+    reset("a");
+    synthesizeKey("VK_TAB", { ctrlKey: true });
+    check(aDescription + "Ctrl+Tab", true, true, true, true);
+    is(aElement.value, "a", aDescription + "Ctrl+Tab");
+    is(fm.focusedElement, aElement,
+       aDescription + "focus moved unexpectedly (Ctrl+Tab)");
+
+    reset("a");
+    synthesizeKey("VK_TAB", { altKey: true });
+    check(aDescription + "Alt+Tab", true, false, true, false);
+    is(aElement.value, "a", aDescription + "Alt+Tab");
+    is(fm.focusedElement, aElement,
+       aDescription + "focus moved unexpectedly (Alt+Tab)");
+
+    reset("a");
+    synthesizeKey("VK_TAB", { metaKey: true });
+    check(aDescription + "Meta+Tab", true, false, true, false);
+    is(aElement.value, "a", aDescription + "Meta+Tab");
+    is(fm.focusedElement, aElement,
+       aDescription + "focus moved unexpectedly (Meta+Tab)");
+
+    // Esc key:
+    //   In all cases, esc key events are not consumed
+    //   XXX ESC key events should be consumed when it's handled,
+    //       however, there is a serious problem, see bug 569988.
+    reset("abc");
+    synthesizeKey("VK_ESCAPE", { });
+    check(aDescription + "Esc", true, false, true, false);
+
+    reset("abc");
+    synthesizeKey("VK_ESCAPE", { shiftKey: true });
+    check(aDescription + "Shift+Esc", true, false, true, false);
+
+    reset("abc");
+    synthesizeKey("VK_ESCAPE", { ctrlKey: true });
+    check(aDescription + "Ctrl+Esc", true, false, true, false);
+
+    reset("abc");
+    synthesizeKey("VK_ESCAPE", { altKey: true });
+    check(aDescription + "Alt+Esc", true, false, true, false);
+
+    reset("abc");
+    synthesizeKey("VK_ESCAPE", { metaKey: true });
+    check(aDescription + "Meta+Esc", true, false, true, false);
+
+    // typical typing tests:
+    reset("");
+    synthesizeKey("M", { shiftKey: true });
+    check(aDescription + "M", true, false, true, !aIsReadonly);
+    synthesizeKey("o", { });
+    check(aDescription + "o", true, false, true, !aIsReadonly);
+    synthesizeKey("z", { });
+    check(aDescription + "z", true, false, true, !aIsReadonly);
+    synthesizeKey("i", { });
+    check(aDescription + "i", true, false, true, !aIsReadonly);
+    synthesizeKey("l", { });
+    check(aDescription + "l", true, false, true, !aIsReadonly);
+    synthesizeKey("l", { });
+    check(aDescription + "l", true, false, true, !aIsReadonly);
+    synthesizeKey("a", { });
+    check(aDescription + "a", true, false, true, !aIsReadonly);
+    synthesizeKey(" ", { });
+    check(aDescription + "' '", true, false, true, !aIsReadonly);
+    is(aElement.value, !aIsReadonly ? "Mozilla " : "",
+       aDescription + "typed \"Mozilla \"");
+  }
+
+  doTest(inputField, "<input type=\"text\">", true, false, true);
+
+  inputField.setAttribute("readonly", "readonly");
+  doTest(inputField, "<input type=\"text\" readonly>", true, true, true);
+
+  doTest(passwordField, "<input type=\"password\">", true, false, true);
+
+  passwordField.setAttribute("readonly", "readonly");
+  doTest(passwordField, "<input type=\"password\" readonly>", true, true, true);
+
+  doTest(textarea, "<textarea>", false, false, true);
+
+  textarea.setAttribute("readonly", "readonly");
+  doTest(textarea, "<textarea readonly>", false, true, true);
+
+  // make non-tabbable plaintext editor
+  textarea.removeAttribute("readonly");
+  const nsIPlaintextEditor = Components.interfaces.nsIPlaintextEditor;
+  const nsIDOMNSEditableElement = Components.interfaces.nsIDOMNSEditableElement;
+  var editor = textarea.QueryInterface(nsIDOMNSEditableElement).editor;
+  var flags = editor.flags;
+  editor.flags = flags & ~(nsIPlaintextEditor.eEditorWidgetMask |
+                           nsIPlaintextEditor.eEditorAllowInteraction);
+  doTest(textarea, "non-tabbable <textarea>", false, false, false);
+
+  textarea.setAttribute("readonly", "readonly");
+  doTest(textarea, "non-tabbable <textarea readonly>", false, true, false);
+
+  editor.flags = flags;
+
+  parentElement.removeGroupedEventListener("keypress", listener, true, systemGroup);
+  parentElement.removeGroupedEventListener("keypress", listener, false, systemGroup);
+
+  SimpleTest.finish();
+}
+
+</script>
+</body>
+
+</html>