Bug 488420 IME enabled state is not modified when a focused editor's readonly attribute is changed r=smaug
authorMasayuki Nakano <masayuki@d-toybox.com>
Wed, 05 May 2010 02:40:39 +0900
changeset 41886 cbb8d8caed8a3d9e9581f37bf679f18bebf923c2
parent 41885 13bcf4386e12bc714a4ecbafec588e95285e1d72
child 41887 cee251f569c52d1cb9b06c4db1c99429cf300972
push idunknown
push userunknown
push dateunknown
reviewerssmaug
bugs488420
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
Bug 488420 IME enabled state is not modified when a focused editor's readonly attribute is changed r=smaug
content/base/public/nsIContent.h
content/base/src/nsGenericElement.cpp
content/events/src/nsIMEStateManager.cpp
content/events/src/nsIMEStateManager.h
editor/libeditor/base/Makefile.in
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_contenteditable_focus.html
widget/tests/test_imestate.html
--- a/content/base/public/nsIContent.h
+++ b/content/base/public/nsIContent.h
@@ -610,30 +610,17 @@ public:
     IME_STATUS_OPEN     = 0x0010,
     IME_STATUS_CLOSE    = 0x0020
   };
   enum {
     IME_STATUS_MASK_ENABLED = IME_STATUS_ENABLE | IME_STATUS_DISABLE |
                               IME_STATUS_PASSWORD | IME_STATUS_PLUGIN,
     IME_STATUS_MASK_OPENED  = IME_STATUS_OPEN | IME_STATUS_CLOSE
   };
-  virtual PRUint32 GetDesiredIMEState()
-  {
-    if (!IsEditableInternal())
-      return IME_STATUS_DISABLE;
-    nsIContent *editableAncestor = nsnull;
-    for (nsIContent* parent = GetParent();
-         parent && parent->HasFlag(NODE_IS_EDITABLE);
-         parent = parent->GetParent())
-      editableAncestor = parent;
-    // This is in another editable content, use the result of it.
-    if (editableAncestor)
-      return editableAncestor->GetDesiredIMEState();
-    return IME_STATUS_ENABLE;
-  }
+  virtual PRUint32 GetDesiredIMEState();
 
   /**
    * Gets content node with the binding (or native code, possibly on the
    * frame) responsible for our construction (and existence).  Used by
    * anonymous content (both XBL-generated and native-anonymous).
    *
    * null for all explicit content (i.e., content reachable from the top
    * of its GetParent() chain via child lists).
--- a/content/base/src/nsGenericElement.cpp
+++ b/content/base/src/nsGenericElement.cpp
@@ -120,16 +120,17 @@
 
 #include "nsNodeInfoManager.h"
 #include "nsICategoryManager.h"
 #include "nsIDOMNSFeatureFactory.h"
 #include "nsIDOMDocumentType.h"
 #include "nsIDOMUserDataHandler.h"
 #include "nsGenericHTMLElement.h"
 #include "nsIEditor.h"
+#include "nsIEditorIMESupport.h"
 #include "nsIEditorDocShell.h"
 #include "nsEventDispatcher.h"
 #include "nsContentCreatorFunctions.h"
 #include "nsIControllers.h"
 #include "nsLayoutUtils.h"
 #include "nsIView.h"
 #include "nsIViewManager.h"
 #include "nsIScrollableFrame.h"
@@ -631,16 +632,57 @@ nsIContent::GetFlattenedTreeParent() con
       if (insertionElement) {
         parent = insertionElement;
       }
     }
   }
   return parent;
 }
 
+PRUint32
+nsIContent::GetDesiredIMEState()
+{
+  if (!IsEditableInternal()) {
+    return IME_STATUS_DISABLE;
+  }
+  nsIContent *editableAncestor = nsnull;
+  for (nsIContent* parent = GetParent();
+       parent && parent->HasFlag(NODE_IS_EDITABLE);
+       parent = parent->GetParent()) {
+    editableAncestor = parent;
+  }
+  // This is in another editable content, use the result of it.
+  if (editableAncestor) {
+    return editableAncestor->GetDesiredIMEState();
+  }
+  nsIDocument* doc = GetCurrentDoc();
+  if (!doc) {
+    return IME_STATUS_DISABLE;
+  }
+  nsIPresShell* ps = doc->GetPrimaryShell();
+  if (!ps) {
+    return IME_STATUS_DISABLE;
+  }
+  nsPresContext* pc = ps->GetPresContext();
+  if (!pc) {
+    return IME_STATUS_DISABLE;
+  }
+  nsIEditor* editor = GetHTMLEditor(pc);
+  nsCOMPtr<nsIEditorIMESupport> imeEditor = do_QueryInterface(editor);
+  if (!imeEditor) {
+    return IME_STATUS_DISABLE;
+  }
+  // Use "enable" for the default value because IME is disabled unexpectedly,
+  // it makes serious a11y problem.
+  PRUint32 state = IME_STATUS_ENABLE;
+  nsresult rv = imeEditor->GetPreferredIMEState(&state);
+  NS_ENSURE_SUCCESS(rv, IME_STATUS_ENABLE);
+  return state;
+}
+
 //----------------------------------------------------------------------
 
 NS_IMPL_ADDREF(nsChildContentList)
 NS_IMPL_RELEASE(nsChildContentList)
 
 NS_INTERFACE_TABLE_HEAD(nsChildContentList)
   NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
   NS_NODELIST_OFFSET_AND_INTERFACE_TABLE_BEGIN(nsChildContentList)
--- a/content/events/src/nsIMEStateManager.cpp
+++ b/content/events/src/nsIMEStateManager.cpp
@@ -78,17 +78,17 @@ nsresult
 nsIMEStateManager::OnDestroyPresContext(nsPresContext* aPresContext)
 {
   NS_ENSURE_ARG_POINTER(aPresContext);
   if (aPresContext != sPresContext)
     return NS_OK;
   nsCOMPtr<nsIWidget> widget = GetWidget(sPresContext);
   if (widget) {
     PRUint32 newState = GetNewIMEState(sPresContext, nsnull);
-    SetIMEState(sPresContext, newState, widget);
+    SetIMEState(newState, widget);
   }
   sContent = nsnull;
   sPresContext = nsnull;
   OnTextStateBlur(nsnull, nsnull);
   return NS_OK;
 }
 
 nsresult
@@ -103,17 +103,17 @@ nsIMEStateManager::OnRemoveContent(nsPre
 
   // Current IME transaction should commit
   nsCOMPtr<nsIWidget> widget = GetWidget(sPresContext);
   if (widget) {
     nsresult rv = widget->CancelIMEComposition();
     if (NS_FAILED(rv))
       widget->ResetInputState();
     PRUint32 newState = GetNewIMEState(sPresContext, nsnull);
-    SetIMEState(sPresContext, newState, widget);
+    SetIMEState(newState, widget);
   }
 
   sContent = nsnull;
   sPresContext = nsnull;
 
   return NS_OK;
 }
 
@@ -157,32 +157,52 @@ nsIMEStateManager::OnChangeFocus(nsPresC
     else
       oldWidget = GetWidget(sPresContext);
     if (oldWidget)
       oldWidget->ResetInputState();
   }
 
   if (newState != nsIContent::IME_STATUS_NONE) {
     // Update IME state for new focus widget
-    SetIMEState(aPresContext, newState, widget);
+    SetIMEState(newState, widget);
   }
 
   sPresContext = aPresContext;
   sContent = aContent;
 
   return NS_OK;
 }
 
 void
 nsIMEStateManager::OnInstalledMenuKeyboardListener(PRBool aInstalling)
 {
   sInstalledMenuKeyboardListener = aInstalling;
   OnChangeFocus(sPresContext, sContent);
 }
 
+void
+nsIMEStateManager::ChangeIMEStateTo(PRUint32 aNewIMEState)
+{
+  if (!sPresContext) {
+    NS_WARNING("ISM doesn't know which editor has focus");
+    return;
+  }
+  NS_PRECONDITION(aNewIMEState != 0, "aNewIMEState doesn't specify new state.");
+  nsCOMPtr<nsIWidget> widget = GetWidget(sPresContext);
+  if (!widget) {
+    NS_WARNING("focused widget is not found");
+    return;
+  }
+
+  // commit current composition
+  widget->ResetInputState();
+
+  SetIMEState(aNewIMEState, widget);
+}
+
 PRUint32
 nsIMEStateManager::GetNewIMEState(nsPresContext* aPresContext,
                                   nsIContent*    aContent)
 {
   // On Printing or Print Preview, we don't need IME.
   if (aPresContext->Type() == nsPresContext::eContext_PrintPreview ||
       aPresContext->Type() == nsPresContext::eContext_Print) {
     return nsIContent::IME_STATUS_DISABLE;
@@ -199,28 +219,27 @@ nsIMEStateManager::GetNewIMEState(nsPres
       return nsIContent::IME_STATUS_ENABLE;
     return nsIContent::IME_STATUS_DISABLE;
   }
 
   return aContent->GetDesiredIMEState();
 }
 
 void
-nsIMEStateManager::SetIMEState(nsPresContext*     aPresContext,
-                               PRUint32           aState,
-                               nsIWidget*         aKB)
+nsIMEStateManager::SetIMEState(PRUint32 aState,
+                               nsIWidget* aWidget)
 {
   if (aState & nsIContent::IME_STATUS_MASK_ENABLED) {
     PRUint32 state =
       nsContentUtils::GetWidgetStatusFromIMEStatus(aState);
-    aKB->SetIMEEnabled(state);
+    aWidget->SetIMEEnabled(state);
   }
   if (aState & nsIContent::IME_STATUS_MASK_OPENED) {
     PRBool open = !!(aState & nsIContent::IME_STATUS_OPEN);
-    aKB->SetIMEOpenState(open);
+    aWidget->SetIMEOpenState(open);
   }
 }
 
 nsIWidget*
 nsIMEStateManager::GetWidget(nsPresContext* aPresContext)
 {
   nsIPresShell* shell = aPresContext->GetPresShell();
   NS_ENSURE_TRUE(shell, nsnull);
--- a/content/events/src/nsIMEStateManager.h
+++ b/content/events/src/nsIMEStateManager.h
@@ -75,20 +75,21 @@ public:
   // OnTextStateFocus should be called *after* NS_FOCUS_CONTENT fires
   // aPresContext is the nsPresContext receiving focus
   // aContent is the nsIContent receiving focus
   static nsresult OnTextStateFocus(nsPresContext* aPresContext,
                                    nsIContent* aContent);
   // Get the focused editor's selection and root
   static nsresult GetFocusSelectionAndRoot(nsISelection** aSel,
                                            nsIContent** aRoot);
+  // This method changes the current IME state forcedly.
+  // So, the caller should check whether you're focused or not.
+  static void ChangeIMEStateTo(PRUint32 aNewIMEState);
 protected:
-  static void SetIMEState(nsPresContext* aPresContext,
-                          PRUint32 aState,
-                          nsIWidget* aKB);
+  static void SetIMEState(PRUint32 aState, nsIWidget* aWidget);
   static PRUint32 GetNewIMEState(nsPresContext* aPresContext,
                                  nsIContent* aContent);
 
   static nsIWidget* GetWidget(nsPresContext* aPresContext);
 
   static nsIContent*    sContent;
   static nsPresContext* sPresContext;
   static PRBool         sInstalledMenuKeyboardListener;
--- a/editor/libeditor/base/Makefile.in
+++ b/editor/libeditor/base/Makefile.in
@@ -86,9 +86,10 @@ CPPSRCS		+=                          \
 
 # don't want the shared lib; force the creation of a static lib.
 FORCE_STATIC_LIB = 1
 
 include $(topsrcdir)/config/rules.mk
 
 INCLUDES	+= \
 		-I$(topsrcdir)/content/base/src \
+		-I$(topsrcdir)/content/events/src \
 		$(NULL)
--- a/editor/libeditor/base/nsEditor.cpp
+++ b/editor/libeditor/base/nsEditor.cpp
@@ -40,16 +40,18 @@
  * ***** END LICENSE BLOCK ***** */
 
 #include "pratom.h"
 #include "nsIDOMDocument.h"
 #include "nsIDOMHTMLDocument.h"
 #include "nsIDOMHTMLElement.h"
 #include "nsIDOMNSHTMLElement.h"
 #include "nsPIDOMEventTarget.h"
+#include "nsIMEStateManager.h"
+#include "nsFocusManager.h"
 #include "nsIPrefBranch.h"
 #include "nsIPrefService.h"
 #include "nsUnicharUtils.h"
 #include "nsReadableUtils.h"
 
 #include "nsIDOMText.h"
 #include "nsIDOMElement.h"
 #include "nsIDOMAttr.h"
@@ -214,23 +216,27 @@ NS_IMPL_CYCLE_COLLECTING_RELEASE_AMBIGUO
 
 NS_IMETHODIMP
 nsEditor::Init(nsIDOMDocument *aDoc, nsIPresShell* aPresShell, nsIContent *aRoot, nsISelectionController *aSelCon, PRUint32 aFlags)
 {
   NS_PRECONDITION(nsnull!=aDoc && nsnull!=aPresShell, "bad arg");
   if ((nsnull==aDoc) || (nsnull==aPresShell))
     return NS_ERROR_NULL_POINTER;
 
+  // First only set flags, but other stuff shouldn't be initialized now.
+  // Don't move this call after initializing mDocWeak and mPresShellWeak.
+  // SetFlags() can check whether it's called during initialization or not by
+  // them.  Note that SetFlags() will be called by PostCreate().
+  nsresult rv = SetFlags(aFlags);
+  NS_ASSERTION(NS_SUCCEEDED(rv), "SetFlags() failed");
+
   mDocWeak = do_GetWeakReference(aDoc);  // weak reference to doc
   mPresShellWeak = do_GetWeakReference(aPresShell);   // weak reference to pres shell
   mSelConWeak = do_GetWeakReference(aSelCon);   // weak reference to selectioncontroller
 
-  nsresult rv = SetFlags(aFlags);
-  NS_ASSERTION(NS_SUCCEEDED(rv), "SetFlags() failed");
-
   nsCOMPtr<nsIPresShell> ps = do_QueryReferent(mPresShellWeak);
   if (!ps) return NS_ERROR_NOT_INITIALIZED;
   
   //set up root element if we are passed one.  
   if (aRoot)
     mRootElement = do_QueryInterface(aRoot);
 
   nsCOMPtr<nsINode> document = do_QueryInterface(aDoc);
@@ -274,18 +280,18 @@ nsEditor::Init(nsIDOMDocument *aDoc, nsI
 
   return NS_OK;
 }
 
 
 NS_IMETHODIMP
 nsEditor::PostCreate()
 {
-  // Set up spellchecking
-  nsresult rv = SyncRealTimeSpell();
+  // Synchronize some stuff for the flags
+  nsresult rv = SetFlags(mFlags);
   NS_ENSURE_SUCCESS(rv, rv);
 
   // Set up listeners
   rv = CreateEventListeners();
   if (NS_FAILED(rv))
   {
     RemoveEventListeners();
 
@@ -432,18 +438,39 @@ nsEditor::GetFlags(PRUint32 *aFlags)
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsEditor::SetFlags(PRUint32 aFlags)
 {
   mFlags = aFlags;
 
+  if (!mDocWeak || !mPresShellWeak) {
+    // If we're initializing, we shouldn't do anything now.
+    // SetFlags() will be called by PostCreate(),
+    // we should synchronize some stuff for the flags at that time.
+    return NS_OK;
+  }
+
   // Changing the flags can change whether spellchecking is on, so re-sync it
-  SyncRealTimeSpell();
+  nsresult rv = SyncRealTimeSpell();
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  // Might be changing editable state, so, we need to reset current IME state
+  // if we're focused.
+  if (HasFocus()) {
+    // Use "enable" for the default value because if IME is disabled
+    // unexpectedly, it makes serious a11y problem.
+    PRUint32 newState = nsIContent::IME_STATUS_ENABLE;
+    rv = GetPreferredIMEState(&newState);
+    if (NS_SUCCEEDED(rv)) {
+      nsIMEStateManager::ChangeIMEStateTo(newState);
+    }
+  }
+
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsEditor::GetIsDocumentEditable(PRBool *aIsDocumentEditable)
 {
   NS_ENSURE_ARG_POINTER(aIsDocumentEditable);
   nsCOMPtr<nsIDOMDocument> doc;
@@ -5150,8 +5177,23 @@ nsEditor::DumpNode(nsIDOMNode *aNode, PR
 }
 #endif
 
 PRBool
 nsEditor::IsModifiableNode(nsIDOMNode *aNode)
 {
   return PR_TRUE;
 }
+
+PRBool
+nsEditor::HasFocus()
+{
+  nsCOMPtr<nsPIDOMEventTarget> piTarget = GetPIDOMEventTarget();
+  if (!piTarget) {
+    return PR_FALSE;
+  }
+
+  nsFocusManager* fm = nsFocusManager::GetFocusManager();
+  NS_ENSURE_TRUE(fm, PR_FALSE);
+
+  nsCOMPtr<nsIContent> content = fm->GetFocusedContent();
+  return SameCOMIdentity(content, piTarget);
+}
--- a/editor/libeditor/base/nsEditor.h
+++ b/editor/libeditor/base/nsEditor.h
@@ -642,16 +642,19 @@ public:
     return (mFlags & nsIPlaintextEditor::eEditorAllowInteraction) != 0;
   }
 
   PRBool DontEchoPassword() const
   {
     return (mFlags & nsIPlaintextEditor::eEditorDontEchoPassword) != 0;
   }
 
+  // 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.
   
   nsWeakPtr       mPresShellWeak;   // weak reference to the nsIPresShell
   nsWeakPtr       mSelConWeak;   // weak reference to the nsISelectionController
   nsIViewManager *mViewManager;
--- a/editor/libeditor/base/nsEditorEventListener.cpp
+++ b/editor/libeditor/base/nsEditorEventListener.cpp
@@ -912,16 +912,19 @@ FindSelectionRoot(nsEditor *aEditor, nsI
 
   nsIContent *root;
   if (document->HasFlag(NODE_IS_EDITABLE)) {
     NS_IF_ADDREF(root = document->GetRootElement());
 
     return root;
   }
 
+  // XXX If the editor is HTML editor and has readonly flag, shouldn't return
+  // the element which has contenteditable="true"?  However, such case isn't
+  // there without chrome permission script.
   if (aEditor->IsReadonly()) {
     // We still want to allow selection in a readonly editor.
     nsCOMPtr<nsIDOMElement> rootElement;
     aEditor->GetRootElement(getter_AddRefs(rootElement));
     if (!rootElement) {
       return nsnull;
     }
 
@@ -931,16 +934,21 @@ FindSelectionRoot(nsEditor *aEditor, nsI
   }
 
   if (!aContent->HasFlag(NODE_IS_EDITABLE)) {
     return nsnull;
   }
 
   // For non-readonly editors we want to find the root of the editable subtree
   // containing aContent.
+  // XXX This is wrong in meaning of this method if the editor is form control.
+  // The editable form controls are also have NODE_IS_EDITABLE flag but it can
+  // be in contenteditable elements.  So, at this time, this climbs up to the
+  // root editable element.  But fortunately, we don't have any problem by
+  // another issue, see the XXX comment in focus event handler.
   nsIContent *parent, *content = aContent;
   while ((parent = content->GetParent()) && parent->HasFlag(NODE_IS_EDITABLE)) {
     content = parent;
   }
 
   NS_IF_ADDREF(content);
 
   return content;
@@ -960,16 +968,22 @@ nsEditorEventListener::Focus(nsIDOMEvent
     return NS_OK;
   }
 
   nsCOMPtr<nsIContent> content = do_QueryInterface(target);
 
   PRBool targetIsEditableDoc = PR_FALSE;
   nsCOMPtr<nsIContent> editableRoot;
   if (content) {
+    // XXX If the focus event target is a form control in contenteditable
+    // element, perhaps, the parent HTML editor should do nothing by this
+    // handler.  However, FindSelectionRoot() returns the root element of the
+    // contenteditable editor.  So, the editableRoot value is invalid for
+    // the plain text editor, and it will be set to the wrong limiter of
+    // the selection.  However, fortunately, actual bugs are not found yet.
     editableRoot = FindSelectionRoot(mEditor, content);
 
     // make sure that the element is really focused in case an earlier
     // listener in the chain changed the focus.
     if (editableRoot) {
       nsIFocusManager* fm = nsFocusManager::GetFocusManager();
       NS_ENSURE_TRUE(fm, NS_OK);
 
--- a/editor/libeditor/html/nsHTMLEditor.cpp
+++ b/editor/libeditor/html/nsHTMLEditor.cpp
@@ -92,16 +92,18 @@
 #include "nsIDOMDocumentFragment.h"
 #include "nsIPresShell.h"
 #include "nsPresContext.h"
 #include "nsXPCOM.h"
 #include "nsISupportsPrimitives.h"
 #include "SetDocTitleTxn.h"
 #include "nsGUIEvent.h"
 #include "nsTextFragment.h"
+#include "nsFocusManager.h"
+#include "nsPIDOMWindow.h"
 
 // netwerk
 #include "nsIURI.h"
 #include "nsNetUtil.h"
 
 // Drag & Drop, Clipboard
 #include "nsIClipboard.h"
 #include "nsITransferable.h"
@@ -5627,8 +5629,83 @@ nsHTMLEditor::SetReturnInParagraphCreate
 }
 
 nsresult
 nsHTMLEditor::GetReturnInParagraphCreatesNewParagraph(PRBool *aCreatesNewParagraph)
 {
   *aCreatesNewParagraph = mCRInParagraphCreatesParagraph;
   return NS_OK;
 }
+
+PRBool
+nsHTMLEditor::HasFocus()
+{
+  NS_ENSURE_TRUE(mDocWeak, PR_FALSE);
+
+  nsFocusManager* fm = nsFocusManager::GetFocusManager();
+  NS_ENSURE_TRUE(fm, PR_FALSE);
+
+  nsCOMPtr<nsIContent> focusedContent = fm->GetFocusedContent();
+
+  nsCOMPtr<nsIDocument> doc = do_QueryReferent(mDocWeak);
+  PRBool inDesignMode = doc->HasFlag(NODE_IS_EDITABLE);
+  if (!focusedContent) {
+    // in designMode, nobody gets focus in most cases.
+    return inDesignMode ? OurWindowHasFocus() : PR_FALSE;
+  }
+
+  if (inDesignMode) {
+    return OurWindowHasFocus() ?
+      nsContentUtils::ContentIsDescendantOf(focusedContent, doc) : PR_FALSE;
+  }
+
+  // We're HTML editor for contenteditable
+
+  // If the focused content isn't editable, or it has independent selection,
+  // we don't have focus.
+  if (!focusedContent->HasFlag(NODE_IS_EDITABLE) ||
+      IsIndependentSelectionContent(focusedContent)) {
+    return PR_FALSE;
+  }
+  nsCOMPtr<nsIContent> rootContent = do_QueryInterface(GetRoot());
+  if (!rootContent) {
+    return PR_FALSE;
+  }
+  // If the focused content is a descendant of our editor root, we're focused.
+  return nsContentUtils::ContentIsDescendantOf(focusedContent, rootContent);
+}
+
+PRBool
+nsHTMLEditor::OurWindowHasFocus()
+{
+  NS_ENSURE_TRUE(mDocWeak, PR_FALSE);
+  nsIFocusManager* fm = nsFocusManager::GetFocusManager();
+  NS_ENSURE_TRUE(fm, PR_FALSE);
+  nsCOMPtr<nsIDOMWindow> focusedWindow;
+  fm->GetFocusedWindow(getter_AddRefs(focusedWindow));
+  if (!focusedWindow) {
+    return PR_FALSE;
+  }
+  nsCOMPtr<nsIDocument> doc = do_QueryReferent(mDocWeak);
+  nsCOMPtr<nsIDOMWindow> ourWindow = do_QueryInterface(doc->GetWindow());
+  return ourWindow == focusedWindow;
+}
+
+PRBool
+nsHTMLEditor::IsIndependentSelectionContent(nsIContent* aContent)
+{
+  NS_PRECONDITION(aContent, "aContent must not be null");
+  nsIFrame* frame = aContent->GetPrimaryFrame();
+  return (frame && (frame->GetStateBits() & NS_FRAME_INDEPENDENT_SELECTION));
+}
+
+NS_IMETHODIMP
+nsHTMLEditor::GetPreferredIMEState(PRUint32 *aState)
+{
+  if (IsReadonly() || IsDisabled()) {
+    *aState = nsIContent::IME_STATUS_DISABLE;
+    return NS_OK;
+  }
+
+  // HTML editor don't prefer the CSS ime-mode because IE didn't do so too.
+  *aState = nsIContent::IME_STATUS_ENABLE;
+  return NS_OK;
+}
--- a/editor/libeditor/html/nsHTMLEditor.h
+++ b/editor/libeditor/html/nsHTMLEditor.h
@@ -141,16 +141,20 @@ public:
 
            nsHTMLEditor();
   virtual  ~nsHTMLEditor();
 
   /* ------------ nsPlaintextEditor overrides -------------- */
   NS_IMETHODIMP HandleKeyPress(nsIDOMKeyEvent* aKeyEvent);
   NS_IMETHOD GetIsDocumentEditable(PRBool *aIsDocumentEditable);
   NS_IMETHODIMP BeginningOfDocument();
+  virtual PRBool HasFocus();
+
+  /* ------------ nsIEditorIMESupport overrides ------------ */
+  NS_IMETHOD GetPreferredIMEState(PRUint32 *aState);
 
   /* ------------ nsIHTMLEditor methods -------------- */
 
   NS_DECL_NSIHTMLEDITOR
 
   /* ------------ nsIHTMLObjectResizer methods -------------- */
   /* -------- Implemented in nsHTMLObjectResizer.cpp -------- */
   NS_DECL_NSIHTMLOBJECTRESIZER
@@ -720,16 +724,22 @@ protected:
                              PRBool *aFirst, 
                              PRBool *aAny, 
                              PRBool *aAll,
                              nsAString *outValue,
                              PRBool aCheckDefaults = PR_TRUE);
   nsresult HasStyleOrIdOrClass(nsIDOMElement * aElement, PRBool *aHasStyleOrIdOrClass);
   nsresult RemoveElementIfNoStyleOrIdOrClass(nsIDOMElement * aElement, nsIAtom * aTag);
 
+  // Whether the outer window of the DOM event target has focus or not.
+  PRBool   OurWindowHasFocus();
+  // Whether the content has independent selection or not.  E.g., input field,
+  // password field and textarea element.  At that time, this returns TRUE.
+  PRBool IsIndependentSelectionContent(nsIContent* aContent);
+
 // Data members
 protected:
 
   nsCOMArray<nsIContentFilter> mContentFilters;
 
   TypeInState*         mTypeInState;
 
   nsCOMPtr<nsIDOMNode> mCachedNode;
--- a/editor/libeditor/html/tests/Makefile.in
+++ b/editor/libeditor/html/tests/Makefile.in
@@ -49,16 +49,17 @@ include $(topsrcdir)/config/rules.mk
 		test_bug432225.html \
 		test_bug455992.html \
 		test_bug456244.html \
 		test_bug478725.html \
 		test_bug480972.html \
 		test_bug487524.html \
 		test_bug525389.html \
 		test_bug537046.html \
+		test_contenteditable_focus.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) | (cd $(DEPTH)/_tests/testing/mochitest/tests/$(relativesrcdir) && tar -xf -)
 
new file mode 100644
--- /dev/null
+++ b/editor/libeditor/html/tests/test_contenteditable_focus.html
@@ -0,0 +1,216 @@
+<html>
+<head>
+  <title>Test for contenteditable focus</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>
+  <link rel="stylesheet" type="text/css"
+          href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+</head>
+<body>
+<div id="display">
+  First text in this document.<br>
+  <input id="inputText" type="text"><br>
+  <input id="inputTextReadonly" type="text" readonly><br>
+  <input id="inputButton" type="button" value="input[type=button]"><br>
+  <button id="button">button</button><br>
+  <div id="editor" contenteditable="true">
+    editable contents.<br>
+    <input id="inputTextInEditor" type="text"><br>
+    <input id="inputTextReadonlyInEditor" type="text" readonly><br>
+    <input id="inputButtonInEditor" type="button" value="input[type=button]"><br>
+    <button id="buttonInEditor">button</button><br>
+    <div id="noeditableInEditor" contenteditable="false">
+      <span id="spanInNoneditableInEditor">span element in noneditable in editor</span><br>
+      <input id="inputTextInNoneditableInEditor" type="text"><br>
+      <input id="inputTextReadonlyInNoneditableInEditor" type="text" readonly><br>
+      <input id="inputButtonInNoneditableInEditor" type="button" value="input[type=button]"><br>
+      <button id="buttonInNoneditableInEditor">button</button><br>
+    </div>
+    <span id="spanInEditor">span element in editor</span><br>
+  </div>
+  <div id="otherEditor" contenteditable="true">
+    other editor.
+  </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);
+
+function runTests()
+{
+  try {
+    runTestsInternal();
+  } catch (e) {
+    ok(false, "Unexpected error happened: " + e);
+  }
+  SimpleTest.finish();
+}
+
+function runTestsInternal()
+{
+  netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
+  var fm = Components.classes["@mozilla.org/focus-manager;1"].
+             getService(Components.interfaces.nsIFocusManager);
+  // XXX using selCon for checking the visibility of the caret, however,
+  // selCon is shared in document, cannot get the element of owner of the
+  // caret from javascript?
+  var selCon =
+      window.QueryInterface(Components.interfaces.nsIInterfaceRequestor).
+        getInterface(Components.interfaces.nsIWebNavigation).
+        QueryInterface(Components.interfaces.nsIInterfaceRequestor).
+        getInterface(Components.interfaces.nsISelectionDisplay).
+        QueryInterface(Components.interfaces.nsISelectionController);
+  var selection = window.getSelection();
+
+  var inputText = document.getElementById("inputText");
+  var inputTextReadonly = document.getElementById("inputTextReadonly");
+  var inputButton = document.getElementById("inputButton");
+  var button = document.getElementById("button");
+  var editor = document.getElementById("editor");
+  var inputTextInEditor = document.getElementById("inputTextInEditor");
+  var inputTextReadonlyInEditor = document.getElementById("inputTextReadonlyInEditor");
+  var inputButtonInEditor = document.getElementById("inputButtonInEditor");
+  var noeditableInEditor = document.getElementById("noeditableInEditor");
+  var spanInNoneditableInEditor = document.getElementById("spanInNoneditableInEditor");
+  var inputTextInNoneditableInEditor = document.getElementById("inputTextInNoneditableInEditor");
+  var inputTextReadonlyInNoneditableInEditor = document.getElementById("inputTextReadonlyInNoneditableInEditor");
+  var inputButtonInNoneditableInEditor = document.getElementById("inputButtonInNoneditableInEditor");
+  var buttonInNoneditableInEditor = document.getElementById("buttonInNoneditableInEditor");
+  var spanInEditor = document.getElementById("spanInEditor");
+  var otherEditor = document.getElementById("otherEditor");
+
+  // XXX if there is a contenteditable element, HTML editor sets dom selection
+  // to first editable node, but this makes inconsistency with normal document
+  // behavior.
+  todo_is(selection.rangeCount, 0, "unexpected selection range is there");
+  ok(!selCon.caretVisible, "caret is visible in the document");
+  // Move focus to inputTextInEditor
+  inputTextInEditor.focus();
+  is(fm.focusedElement, inputTextInEditor,
+     "inputTextInEditor didn't get focus");
+  todo_is(selection.rangeCount, 0, "unexpected selection range is there");
+  ok(selCon.caretVisible, "caret isn't visible in the inputTextInEditor");
+  // Move focus to the editor
+  editor.focus();
+  is(fm.focusedElement, editor,
+     "editor didn't get focus");
+  is(selection.rangeCount, 1,
+     "there is no selection range when editor has focus");
+  var range = selection.getRangeAt(0);
+  ok(range.collapsed, "the selection range isn't collapsed");
+  var startNode = range.startContainer;
+  is(startNode.nodeType, 1, "the caret isn't set to the div node");
+  is(startNode, editor, "the caret isn't set to the editor");
+  ok(selCon.caretVisible, "caret isn't visible in the editor");
+  // Move focus to other editor
+  otherEditor.focus();
+  is(fm.focusedElement, otherEditor,
+     "the other editor didn't get focus");
+  is(selection.rangeCount, 1,
+     "there is no selection range when the other editor has focus");
+  range = selection.getRangeAt(0);
+  ok(range.collapsed, "the selection range isn't collapsed");
+  var startNode = range.startContainer;
+  is(startNode.nodeType, 1, "the caret isn't set to the div node");
+  is(startNode, otherEditor, "the caret isn't set to the other editor");
+  ok(selCon.caretVisible, "caret isn't visible in the other editor");
+  // Move focus to inputTextInEditor
+  inputTextInEditor.focus();
+  is(fm.focusedElement, inputTextInEditor,
+     "inputTextInEditor didn't get focus #2");
+  is(selection.rangeCount, 1, "selection range is lost from the document");
+  range = selection.getRangeAt(0);
+  ok(range.collapsed, "the selection range isn't collapsed");
+  var startNode = range.startContainer;
+  is(startNode.nodeType, 1, "the caret isn't set to the div node");
+  // XXX maybe, the caret can stay on the other editor if it's better.
+  is(startNode, editor,
+     "the caret should stay on the other editor");
+  ok(selCon.caretVisible,
+     "caret isn't visible in the inputTextInEditor");
+  // Move focus to the other editor again
+  otherEditor.focus();
+  is(fm.focusedElement, otherEditor,
+     "the other editor didn't get focus #2");
+  // Set selection to the span element in the editor (unfocused)
+  range = document.createRange();
+  range.setStart(spanInEditor.firstChild, 5);
+  selection.removeAllRanges();
+  selection.addRange(range);
+  is(selection.rangeCount, 1, "selection range is lost from the document");
+  is(fm.focusedElement, otherEditor,
+     "the other editor shouldn't lose focus by selection range change");
+  ok(selCon.caretVisible, "caret isn't visible in inputTextInEditor");
+  // Move focus to the editor
+  editor.focus();
+  is(fm.focusedElement, editor,
+     "the editor didn't get focus #2");
+  is(selection.rangeCount, 1, "selection range is lost from the document");
+  range = selection.getRangeAt(0);
+  ok(range.collapsed, "the selection range isn't collapsed");
+  is(range.startOffset, 5,
+     "the caret is moved when the editor was focused (offset)");
+  var startNode = range.startContainer;
+  is(startNode.nodeType, 3, "the caret isn't in text node");
+  is(startNode.parentNode, spanInEditor,
+     "the caret is moved when the editor was focused (node)");
+  ok(selCon.caretVisible, "caret isn't visible in the editor (spanInEditor)");
+
+  // Move focus to each focusable element in the editor.
+  function testFocusMove(aSetFocusElementID, aFocusable, aCaretVisible)
+  {
+    editor.focus();
+    is(fm.focusedElement, editor,
+       "testFocusMove: the editor didn't get focus at initializing (" +
+       aSetFocusElementID + ")");
+    var setFocusElement = document.getElementById(aSetFocusElementID);
+    setFocusElement.focus();
+    if (aFocusable) {
+      is(fm.focusedElement, setFocusElement,
+         "testFocusMove: the " + aSetFocusElementID +
+         " didn't get focus");
+    } else {
+      is(fm.focusedElement, editor,
+         "testFocusMove: the editor lost focus by focus() of the " +
+         aSetFocusElementID);
+    }
+    if (aCaretVisible) {
+      ok(selCon.caretVisible,
+         "testFocusMove: caret isn't visible when the " +
+         aSetFocusElementID + " has focus");
+    } else {
+      ok(!selCon.caretVisible,
+         "testFocusMove: caret is visible when the " +
+         aSetFocusElementID + " has focus");
+    }
+  }
+  testFocusMove("inputTextInEditor", true, true);
+  testFocusMove("inputTextReadonlyInEditor", true, true);
+  // XXX shouldn't the caret become invisible?
+  testFocusMove("inputButtonInEditor", true, true);
+  testFocusMove("noeditableInEditor", false, true);
+  testFocusMove("spanInNoneditableInEditor", false, true);
+  testFocusMove("inputTextInNoneditableInEditor", true, true);
+  testFocusMove("inputTextReadonlyInNoneditableInEditor", true, true);
+  testFocusMove("inputButtonInNoneditableInEditor", true, false);
+  testFocusMove("buttonInNoneditableInEditor", true, false);
+  testFocusMove("spanInEditor", false, true);
+  testFocusMove("inputText", true, true);
+  testFocusMove("inputTextReadonly", true, true);
+  testFocusMove("inputButton", true, false);
+  testFocusMove("button", true, false);
+}
+
+</script>
+</body>
+
+</html>
--- a/widget/tests/test_imestate.html
+++ b/widget/tests/test_imestate.html
@@ -1,20 +1,20 @@
-<html>
+<html style="ime-mode: disabled;">
 <head>
   <title>Test for IME state controling</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>
   <link rel="stylesheet" type="text/css"
           href="chrome://mochikit/content/tests/SimpleTest/test.css" />
 </head>
-<body onload="setTimeout(runTests, 0);">
-<p id="display">
+<body onload="setTimeout(runTests, 0);" style="ime-mode: disabled;">
+<div id="display" style="ime-mode: disabled;">
   <!-- input elements -->
   <input type="text"     id="text"/><br/>
   <input type="text"     id="text_readonly" readonly="readonly"/><br/>
   <input type="password" id="password"/><br/>
   <input type="password" id="password_readonly" readonly="readonly"/><br/>
   <input type="checkbox" id="checkbox"/><br/>
   <input type="radio"    id="radio"/><br/>
   <input type="submit"   id="submit"/><br/>
@@ -31,17 +31,17 @@
     <option value="option" selected="selected"/>
   </select><br/>
   <select id="select_multiple" multiple="multiple">
     <option value="option" selected="selected"/>
   </select><br/>
   <isindex id="isindex" prompt="isindex"/><br/>
 
   <!-- a element -->
-  <a id="a_href" href="">a[href]</a><br/>
+  <a id="a_href" href="about:blank">a[href]</a><br/>
 
   <!-- ime-mode test -->
   <input type="text" id="ime_mode_auto"     style="ime-mode: auto;"/><br/>
   <input type="text" id="ime_mode_normal"   style="ime-mode: normal;"/><br/>
   <input type="text" id="ime_mode_active"   style="ime-mode: active;"/><br/>
   <input type="text" id="ime_mode_inactive" style="ime-mode: inactive;"/><br/>
   <input type="text" id="ime_mode_disabled" style="ime-mode: disabled;"/><br/>
   <input type="password" id="ime_mode_auto_p"     style="ime-mode: auto;"/><br/>
@@ -52,208 +52,295 @@
   <textarea id="ime_mode_auto_t"     style="ime-mode: auto;">textarea</textarea><br/>
   <textarea id="ime_mode_normal_t"   style="ime-mode: normal;">textarea</textarea><br/>
   <textarea id="ime_mode_active_t"   style="ime-mode: active;">textarea</textarea><br/>
   <textarea id="ime_mode_inactive_t" style="ime-mode: inactive;">textarea</textarea><br/>
   <textarea id="ime_mode_disabled_t" style="ime-mode: disabled;">textarea</textarea><br/>
 
   <!-- plugin -->
   <object type="application/x-test" id="plugin"></object><br/>
-</p>
+
+  <!-- contenteditable editor -->
+  <div id="contenteditableEditor" contenteditable="true"></div>
+
+  <!-- designMode editor -->
+  <iframe id="designModeEditor"
+   onload="document.getElementById('designModeEditor').contentDocument.designMode = 'on';"
+   src="data:text/html,<html><body></body></html>"></iframe><br/>
+</div>
 <div id="content" style="display: none">
   
 </div>
 <pre id="test">
 </pre>
 
 <script class="testbody" type="application/javascript">
 
 SimpleTest.waitForExplicitFinish();
 
 var gUtils = window.
       QueryInterface(Components.interfaces.nsIInterfaceRequestor).
       getInterface(Components.interfaces.nsIDOMWindowUtils);
+var gFM = Components.classes["@mozilla.org/focus-manager;1"].
+      getService(Components.interfaces.nsIFocusManager);
 const kIMEEnabledSupported = navigator.platform.indexOf("Mac") == 0 ||
                              navigator.platform.indexOf("Win") == 0 ||
                              navigator.platform.indexOf("Linux") == 0;
 
 // We support to control IME open state on Windows and Mac actually.  However,
 // we cannot test it on Mac if the current keyboard layout is not CJK. And also
 // we cannot test it on Win32 if the system didn't be installed IME. So,
 // currently we should not run the open state testing.
 const kIMEOpenSupported = false;
 
 function runBasicTest(aIsEditable, aInDesignMode, aDescription)
 {
-  var defaultEnabledState =
-        aIsEditable ? gUtils.IME_STATUS_ENABLED : gUtils.IME_STATUS_DISABLED;
-
-  var nonFormControlNodeEnabledState =
-        aInDesignMode ? gUtils.IME_STATUS_ENABLED : gUtils.IME_STATUS_DISABLED;
-
   function test(aTest)
   {
     function moveFocus(aTest)
     {
-      if (aTest.expectedEnabled == gUtils.IME_STATUS_DISABLED) {
-        document.getElementById("text").focus();
+      if (aInDesignMode) {
+        if (document.activeElement) {
+          document.activeElement.blur();
+        }
+      } else if (aIsEditable) {
+        document.getElementById("display").focus();
+      } else if (aTest.expectedEnabled == gUtils.IME_STATUS_ENABLED) {
+        document.getElementById("password").focus();
       } else {
-        document.activeElement.blur();
+        document.getElementById("text").focus();
+      }
+      var previousFocusedElement = gFM.focusedElement;
+      var element = document.getElementById(aTest.id);
+      if (element.contentDocument) {
+        element = element.contentDocument.documentElement;
       }
-      document.getElementById(aTest.id).focus();
+      element.focus();
+      var focusedElement = gFM.focusedElement;
+      if (focusedElement) {
+        var bindingParent = document.getBindingParent(focusedElement);
+        if (bindingParent) {
+          focusedElement = bindingParent;
+        }
+      }
+      if (aTest.focusable) {
+        is(focusedElement, element,
+           aDescription + ": " + aTest.description + ", focus didn't move");
+        return (element == focusedElement);
+      }
+      is(focusedElement, previousFocusedElement,
+         aDescription + ": " + aTest.description + ", focus moved as unexpected");
+      return (previousFocusedElement == focusedElement);
     }
 
     function testOpened(aTest, aOpened)
     {
       document.getElementById("text").focus();
       gUtils.IMEIsOpen = aOpened;
-      moveFocus(aTest);
+      if (!moveFocus(aTest)) {
+        return;
+      }
       var message = aDescription + ": " + aTest.description +
                                             ", wrong opened state";
       is(gUtils.IMEIsOpen,
          aTest.changeOpened ? aTest.expectedOpened : aOpened, message);
     }
 
     // IME Enabled state testing
     var enabled = gUtils.IME_STATUS_ENABLED;
     if (kIMEEnabledSupported) {
-      moveFocus(aTest);
+      if (!moveFocus(aTest)) {
+        return;
+      }
       enabled = gUtils.IMEStatus;
-      // no focusing of html elements is possible in design mode so ime should
-      // always be enabled for the document
-      var expected = aInDesignMode ? gUtils.IME_STATUS_ENABLED : aTest.expectedEnabled;
-      is(enabled, expected,
+      is(enabled, aTest.expectedEnabled,
          aDescription + ": " + aTest.description + ", wrong enabled state");
     }
 
     if (!kIMEOpenSupported || enabled != gUtils.IME_STATUS_ENABLED ||
         aTest.expectedEnabled != gUtils.IME_STATUS_ENABLED) {
       return;
     }
 
     // IME Open state testing
     testOpened(aTest, false);
     testOpened(aTest, true);
   }
 
   if (kIMEEnabledSupported) {
+    // make sure there is an active element
+    document.getElementById("text").focus();
     document.activeElement.blur();
-    is(gUtils.IMEStatus, nonFormControlNodeEnabledState,
+    is(gUtils.IMEStatus,
+       aInDesignMode ? gUtils.IME_STATUS_ENABLED : gUtils.IME_STATUS_DISABLED,
        aDescription + ": unexpected enabled state when no element has focus");
   }
 
+  // Form controls except text editable elements are "disable" in normal
+  // condition, however, if they are editable, they are "enabled".
+  // XXX Probably there are some bugs: If the form controls editable, they
+  //     shouldn't be focusable.
+  const kEnabledStateOnNonEditableElement =
+    (aInDesignMode || aIsEditable) ? gUtils.IME_STATUS_ENABLED :
+                                     gUtils.IME_STATUS_DISABLED;
+  const kEnabledStateOnPasswordField =
+    aInDesignMode ? gUtils.IME_STATUS_ENABLED : gUtils.IME_STATUS_PASSWORD;
+  const kEnabledStateOnReadonlyField =
+    aInDesignMode ? gUtils.IME_STATUS_ENABLED : gUtils.IME_STATUS_DISABLED;
   const kTests = [
     { id: "text",
       description: "input[type=text]",
+      focusable: !aInDesignMode,
       expectedEnabled: gUtils.IME_STATUS_ENABLED },
     { id: "text_readonly",
       description: "input[type=text][readonly]",
-      expectedEnabled: gUtils.IME_STATUS_DISABLED },
+      focusable: !aInDesignMode,
+      expectedEnabled: kEnabledStateOnReadonlyField },
     { id: "password",
       description: "input[type=password]",
-      expectedEnabled: gUtils.IME_STATUS_PASSWORD },
+      focusable: !aInDesignMode,
+      expectedEnabled: kEnabledStateOnPasswordField },
     { id: "password_readonly",
       description: "input[type=password][readonly]",
-      expectedEnabled: gUtils.IME_STATUS_DISABLED },
+      focusable: !aInDesignMode,
+      expectedEnabled: kEnabledStateOnReadonlyField },
     { id: "checkbox",
       description: "input[type=checkbox]",
-      expectedEnabled: defaultEnabledState },
+      focusable: !aInDesignMode,
+      expectedEnabled: kEnabledStateOnNonEditableElement },
     { id: "radio",
       description: "input[type=radio]",
-      expectedEnabled: defaultEnabledState },
+      focusable: !aInDesignMode,
+      expectedEnabled: kEnabledStateOnNonEditableElement },
     { id: "submit",
       description: "input[type=submit]",
-      expectedEnabled: defaultEnabledState },
+      focusable: !aInDesignMode,
+      expectedEnabled: kEnabledStateOnNonEditableElement },
     { id: "reset",
       description: "input[type=reset]",
-      expectedEnabled: defaultEnabledState },
+      focusable: !aInDesignMode,
+      expectedEnabled: kEnabledStateOnNonEditableElement },
     { id: "file",
       description: "input[type=file]",
-      expectedEnabled: defaultEnabledState },
+      focusable: !aInDesignMode,
+      expectedEnabled: kEnabledStateOnNonEditableElement },
     { id: "button",
       description: "input[type=button]",
-      expectedEnabled: defaultEnabledState },
+      focusable: !aInDesignMode,
+      expectedEnabled: kEnabledStateOnNonEditableElement },
     { id: "image",
       description: "input[type=image]",
-      expectedEnabled: defaultEnabledState },
+      focusable: !aInDesignMode,
+      expectedEnabled: kEnabledStateOnNonEditableElement },
 
     // form controls
     { id: "button",
       description: "button",
-      expectedEnabled: defaultEnabledState },
+      focusable: !aInDesignMode,
+      expectedEnabled: kEnabledStateOnNonEditableElement },
     { id: "textarea",
       description: "textarea",
+      focusable: !aInDesignMode,
       expectedEnabled: gUtils.IME_STATUS_ENABLED },
     { id: "textarea_readonly",
       description: "textarea[readonly]",
-      expectedEnabled: gUtils.IME_STATUS_DISABLED },
+      focusable: !aInDesignMode,
+      expectedEnabled: kEnabledStateOnReadonlyField },
     { id: "select",
       description: "select (dropdown list)",
-      expectedEnabled: defaultEnabledState },
+      focusable: !aInDesignMode,
+      expectedEnabled: kEnabledStateOnNonEditableElement },
     { id: "select_multiple",
       description: "select (list box)",
-      expectedEnabled: defaultEnabledState },
+      focusable: !aInDesignMode,
+      expectedEnabled: kEnabledStateOnNonEditableElement },
 
     // a element
     { id: "a_href",
       description: "a[href]",
-      expectedEnabled: nonFormControlNodeEnabledState },
+      focusable: !aIsEditable && !aInDesignMode,
+      expectedEnabled: kEnabledStateOnNonEditableElement },
 
     // ime-mode
     { id: "ime_mode_auto",
       description: "input[type=text][style=\"ime-mode: auto;\"]",
+      focusable: !aInDesignMode,
       expectedEnabled: gUtils.IME_STATUS_ENABLED },
     { id: "ime_mode_normal",
       description: "input[type=text][style=\"ime-mode: normal;\"]",
+      focusable: !aInDesignMode,
       expectedEnabled: gUtils.IME_STATUS_ENABLED },
     { id: "ime_mode_active",
       description: "input[type=text][style=\"ime-mode: active;\"]",
       expectedEnabled: gUtils.IME_STATUS_ENABLED,
+      focusable: !aInDesignMode,
       changeOpened: true, expectedOpened: true },
     { id: "ime_mode_inactive",
       description: "input[type=text][style=\"ime-mode: inactive;\"]",
       expectedEnabled: gUtils.IME_STATUS_ENABLED,
+      focusable: !aInDesignMode,
       changeOpened: true, expectedOpened: false },
     { id: "ime_mode_disabled",
       description: "input[type=text][style=\"ime-mode: disabled;\"]",
-      expectedEnabled: gUtils.IME_STATUS_PASSWORD },
+      focusable: !aInDesignMode,
+      expectedEnabled: kEnabledStateOnPasswordField },
     { id: "ime_mode_auto_p",
       description: "input[type=password][style=\"ime-mode: auto;\"]",
-      expectedEnabled: gUtils.IME_STATUS_PASSWORD },
+      focusable: !aInDesignMode,
+      expectedEnabled: kEnabledStateOnPasswordField },
     { id: "ime_mode_normal_p",
       description: "input[type=password][style=\"ime-mode: normal;\"]",
+      focusable: !aInDesignMode,
       expectedEnabled: gUtils.IME_STATUS_ENABLED },
     { id: "ime_mode_active_p",
       description: "input[type=password][style=\"ime-mode: active;\"]",
       expectedEnabled: gUtils.IME_STATUS_ENABLED,
+      focusable: !aInDesignMode,
       changeOpened: true, expectedOpened: true },
     { id: "ime_mode_inactive_p",
       description: "input[type=password][style=\"ime-mode: inactive;\"]",
       expectedEnabled: gUtils.IME_STATUS_ENABLED,
+      focusable: !aInDesignMode,
       changeOpened: true, expectedOpened: false },
     { id: "ime_mode_disabled_p",
       description: "input[type=password][style=\"ime-mode: disabled;\"]",
-      expectedEnabled: gUtils.IME_STATUS_PASSWORD },
+      focusable: !aInDesignMode,
+      expectedEnabled: kEnabledStateOnPasswordField },
     { id: "ime_mode_auto",
       description: "textarea[style=\"ime-mode: auto;\"]",
+      focusable: !aInDesignMode,
       expectedEnabled: gUtils.IME_STATUS_ENABLED },
     { id: "ime_mode_normal",
       description: "textarea[style=\"ime-mode: normal;\"]",
+      focusable: !aInDesignMode,
       expectedEnabled: gUtils.IME_STATUS_ENABLED },
     { id: "ime_mode_active",
       description: "textarea[style=\"ime-mode: active;\"]",
+      focusable: !aInDesignMode,
       expectedEnabled: gUtils.IME_STATUS_ENABLED,
       changeOpened: true, expectedOpened: true },
     { id: "ime_mode_inactive",
       description: "textarea[style=\"ime-mode: inactive;\"]",
+      focusable: !aInDesignMode,
       expectedEnabled: gUtils.IME_STATUS_ENABLED,
       changeOpened: true, expectedOpened: false },
     { id: "ime_mode_disabled",
       description: "textarea[style=\"ime-mode: disabled;\"]",
-      expectedEnabled: gUtils.IME_STATUS_PASSWORD }
+      focusable: !aInDesignMode,
+      expectedEnabled: kEnabledStateOnPasswordField },
+
+    // HTML editors
+    { id: "contenteditableEditor",
+      description: "div[contenteditable=\"true\"]",
+      focusable: !aIsEditable && !aInDesignMode,
+      expectedEnabled: gUtils.IME_STATUS_ENABLED },
+    { id: "designModeEditor",
+      description: "designMode editor",
+      focusable: true,
+      expectedEnabled: gUtils.IME_STATUS_ENABLED },
   ];
 
   for (var i = 0; i < kTests.length; i++) {
     test(kTests[i]);
   }
 }
 
 function runPluginTest()
@@ -346,17 +433,17 @@ function runTypeChangingTest()
       description: "[type=\"text\"][ime-mode: active;]" },
     { id: "ime_mode_inactive",
       type: "text",     expected: gUtils.IME_STATUS_ENABLED,  imeMode:  true,
       description: "[type=\"text\"][ime-mode: inactive;]" },
     { id: "ime_mode_disabled",
       type: "text",     expected: gUtils.IME_STATUS_PASSWORD, imeMode:  true,
       description: "[type=\"text\"][ime-mode: disabled;]" },
     { id: "ime_mode_auto_p",
-      type: "password", expected: gUtils.IME_STATUS_ENABLED,  imeMode:  true,
+      type: "password", expected: gUtils.IME_STATUS_PASSWORD,  imeMode:  true,
       description: "[type=\"password\"][ime-mode: auto;]" },
     { id: "ime_mode_normal_p",
       type: "password", expected: gUtils.IME_STATUS_ENABLED,  imeMode:  true,
       description: "[type=\"password\"][ime-mode: normal;]" },
     { id: "ime_mode_active_p",
       type: "password", expected: gUtils.IME_STATUS_ENABLED,  imeMode:  true,
       description: "[type=\"password\"][ime-mode: active;]" },
     { id: "ime_mode_inactive_p",
@@ -378,17 +465,17 @@ function runTypeChangingTest()
     { type: "file",     expected: gUtils.IME_STATUS_DISABLED },
     { type: "button",   expected: gUtils.IME_STATUS_DISABLED },
     { type: "image",    expected: gUtils.IME_STATUS_DISABLED }
   ];
 
   function getExpectedIMEEnabled(aNewType, aInputControl)
   {
     if (aNewType.expected == gUtils.IME_STATUS_DISABLED ||
-        aInputControl.isReadOnly)
+        aInputControl.isReadonly)
       return gUtils.IME_STATUS_DISABLED;
     return aInputControl.imeMode ? aInputControl.expected : aNewType.expected;
   }
 
   const kOpenedState = [ true, false ];
 
   for (var i = 0; i < kOpenedState.length; i++) {
     const kOpened = kOpenedState[i];
@@ -472,16 +559,224 @@ function runReadonlyChangingTest()
            kInput.type);
       }
     }
     if (!kIMEOpenSupported)
       break;
   }
 }
 
+function runComplexContenteditableTests()
+{
+  if (!kIMEEnabledSupported) {
+    return;
+  }
+
+  var description = "runReadonlyChangingOnContenteditable: ";
+
+  var container = document.getElementById("display");
+  var button = document.getElementById("button");
+
+  // the editor has focus directly.
+  container.setAttribute("contenteditable", "true");
+  container.focus();
+
+  is(gFM.focusedElement, container,
+     description + "The editor doesn't get focus");
+  is(gUtils.IMEStatus, gUtils.IME_STATUS_ENABLED,
+     description + "IME isn't enabled on HTML editor");
+  const kReadonly =
+    Components.interfaces.nsIPlaintextEditor.eEditorReadonlyMask;
+  var editor =
+    window.QueryInterface(Components.interfaces.nsIInterfaceRequestor).
+      getInterface(Components.interfaces.nsIWebNavigation).
+      QueryInterface(Components.interfaces.nsIEditorDocShell).editor;
+  var flags = editor.flags;
+  editor.flags = flags | kReadonly;
+  is(gFM.focusedElement, container,
+     description + "The editor loses focus by flag change");
+  is(gUtils.IMEStatus, gUtils.IME_STATUS_DISABLED,
+     description + "IME is still enabled on readonly HTML editor");
+  editor.flags = flags;
+  is(gFM.focusedElement, container,
+     description + "The editor loses focus by flag change #2");
+  is(gUtils.IMEStatus, gUtils.IME_STATUS_ENABLED,
+     description + "IME is still disabled, the editor isn't readonly now");
+  container.removeAttribute("contenteditable");
+  todo_is(gFM.focusedElement, null,
+          description + "The container still has focus, the editor has been no editable");
+  todo_is(gUtils.IMEStatus, gUtils.IME_STATUS_DISABLED,
+          description + "IME is still enabled on the editor, the editor has been no editable");
+
+  // a button which is in the editor has focus
+  button.focus();
+  is(gFM.focusedElement, button,
+     description + "The button doesn't get focus");
+  is(gUtils.IMEStatus, gUtils.IME_STATUS_DISABLED,
+     description + "IME is enabled on the button");
+  container.setAttribute("contenteditable", "true");
+  is(gFM.focusedElement, button,
+     description + "The button loses focus, the container is editable now");
+  todo_is(gUtils.IMEStatus, gUtils.IME_STATUS_ENABLED,
+          description + "IME is still disabled on the button, the container is editable now");
+  editor =
+    window.QueryInterface(Components.interfaces.nsIInterfaceRequestor).
+      getInterface(Components.interfaces.nsIWebNavigation).
+      QueryInterface(Components.interfaces.nsIEditorDocShell).editor;
+  flags = editor.flags;
+  editor.flags = flags | kReadonly;
+  is(gFM.focusedElement, button,
+     description + "The button loses focus by changing editor flags");
+  is(gUtils.IMEStatus, gUtils.IME_STATUS_DISABLED,
+     description + "IME is still enabled on the button, the container is readonly now");
+  editor.flags = flags;
+  is(gFM.focusedElement, button,
+     description + "The button loses focus by changing editor flags #2");
+  is(gUtils.IMEStatus, gUtils.IME_STATUS_ENABLED,
+     description + "IME is still disabled on the button, the container isn't readonly now");
+  container.removeAttribute("contenteditable");
+  is(gFM.focusedElement, button,
+     description + "The button loses focus, the container has been no editable");
+  todo_is(gUtils.IMEStatus, gUtils.IME_STATUS_DISABLED,
+          description + "IME is still enabled on the button, the container has been no editable");
+
+  description = "testOnIndependentEditor: ";
+  function testOnIndependentEditor(aEditor, aEditorDescription)
+  {
+    var isReadonly = aEditor.readOnly;
+    var expectedState =
+      aEditor.readOnly ? gUtils.IME_STATUS_DISABLED : gUtils.IME_STATUS_ENABLED;
+    var unexpectedStateDescription =
+      expectedState != gUtils.IME_STATUS_ENABLED ? "enabled" : "disabled";
+    aEditor.focus();
+    is(gFM.focusedElement, aEditor,
+       description + "The " + aEditorDescription + " doesn't get focus");
+    is(gUtils.IMEStatus, expectedState,
+       description + "IME is " + unexpectedStateDescription +
+         " on the " + aEditorDescription);
+    container.setAttribute("contenteditable", "true");
+    is(gFM.focusedElement, aEditor,
+       description + "The " + aEditorDescription +
+         " loses focus, the container is editable now");
+    is(gUtils.IMEStatus, expectedState,
+       description + "IME becomes " + unexpectedStateDescription +
+         " on the " + aEditorDescription + ", the container is editable now");
+    editor =
+      window.QueryInterface(Components.interfaces.nsIInterfaceRequestor).
+        getInterface(Components.interfaces.nsIWebNavigation).
+        QueryInterface(Components.interfaces.nsIEditorDocShell).editor;
+    flags = editor.flags;
+    editor.flags = flags | kReadonly;
+    is(gFM.focusedElement, aEditor,
+       description + "The " + aEditorDescription +
+         " loses focus by changing editor flags");
+    is(gUtils.IMEStatus, expectedState,
+       description + "IME becomes " + unexpectedStateDescription + " on the " +
+         aEditorDescription + ", the container is readonly now");
+    editor.flags = flags;
+    is(gFM.focusedElement, aEditor,
+       description + "The " + aEditorDescription +
+         " loses focus by changing editor flags #2");
+    is(gUtils.IMEStatus, expectedState,
+       description + "IME becomes " + unexpectedStateDescription + " on the " +
+         aEditorDescription + ", the container isn't readonly now");
+    container.removeAttribute("contenteditable");
+    is(gFM.focusedElement, aEditor,
+       description + "The " + aEditorDescription +
+         " loses focus, the container has been no editable");
+    is(gUtils.IMEStatus, expectedState,
+       description + "IME becomes " + unexpectedStateDescription + " on the " +
+         aEditorDescription + ", the container has been no editable");
+  }
+
+  // a textarea which is in the editor has focus
+  testOnIndependentEditor(document.getElementById("textarea"),
+                          "textarea");
+  // a readonly textarea which is in the editor has focus
+  testOnIndependentEditor(document.getElementById("textarea_readonly"),
+                          "textarea[readonly]");
+  // an input field which is in the editor has focus
+  testOnIndependentEditor(document.getElementById("text"),
+                          "input[type=\"text\"]");
+  // a readonly input field which is in the editor has focus
+  testOnIndependentEditor(document.getElementById("text_readonly"),
+                          "input[type=\"text\"][readonly]");
+
+  description = "testOnOutsideOfEditor: ";
+  function testOnOutsideOfEditor(aFocusNode, aFocusNodeDescription, aEditor)
+  {
+    if (aFocusNode) {
+      aFocusNode.focus();
+      is(gFM.focusedElement, aFocusNode,
+         description + "The " + aFocusNodeDescription + " doesn't get focus");
+    } else {
+      if (document.activeElement) {
+        document.activeElement.blur();
+      }
+      is(gFM.focusedElement, null,
+         description + "Unexpected element has focus");
+    }
+    var expectedState =
+      aFocusNode ? gUtils.IMEStatus : gUtils.IME_STATUS_DISABLED;
+    var unexpectedStateDescription =
+      expectedState != gUtils.IME_STATUS_ENABLED ? "enabled" : "disabled";
+
+    aEditor.setAttribute("contenteditable", "true");
+    is(gFM.focusedElement, aFocusNode,
+       description + "The " + aFocusNodeDescription +
+         " loses focus, a HTML editor is editable now");
+    is(gUtils.IMEStatus, expectedState,
+       description + "IME becomes " + unexpectedStateDescription +
+         " on the " + aFocusNodeDescription +
+         ", the HTML editor is editable now");
+    editor =
+      window.QueryInterface(Components.interfaces.nsIInterfaceRequestor).
+        getInterface(Components.interfaces.nsIWebNavigation).
+        QueryInterface(Components.interfaces.nsIEditorDocShell).editor;
+    flags = editor.flags;
+    editor.flags = flags | kReadonly;
+    is(gFM.focusedElement, aFocusNode,
+       description + aFocusNodeDescription +
+         " loses focus by changing HTML editor flags");
+    is(gUtils.IMEStatus, expectedState,
+       description + "IME becomes " + unexpectedStateDescription + " on " +
+         aFocusNodeDescription + ", the HTML editor is readonly now");
+    editor.flags = flags;
+    is(gFM.focusedElement, aFocusNode,
+       description + aFocusNodeDescription +
+         " loses focus by changing HTML editor flags #2");
+    is(gUtils.IMEStatus, expectedState,
+       description + "IME becomes " + unexpectedStateDescription + " on " +
+         aFocusNodeDescription + ", the HTML editor isn't readonly now");
+    container.removeAttribute("contenteditable");
+    is(gFM.focusedElement, aFocusNode,
+       description + aFocusNodeDescription +
+         " loses focus, the HTML editor has been no editable");
+    is(gUtils.IMEStatus, expectedState,
+       description + "IME becomes " + unexpectedStateDescription + " on " +
+         aFocusNodeDescription + ", the HTML editor has been no editable");
+  }
+
+  var div = document.getElementById("contenteditableEditor");
+  // a textarea which is outside of the editor has focus
+  testOnOutsideOfEditor(document.getElementById("textarea"), "textarea", div);
+  // a readonly textarea which is outside of the editor has focus
+  testOnOutsideOfEditor(document.getElementById("textarea_readonly"),
+                        "textarea[readonly]", div);
+  // an input field which is outside of the editor has focus
+  testOnOutsideOfEditor(document.getElementById("text"),
+                        "input[type=\"text\"]", div);
+  // a readonly input field which outside of the editor has focus
+  testOnOutsideOfEditor(document.getElementById("text_readonly"),
+                        "input[type=\"text\"][readonly]", div);
+  // a readonly input field which outside of the editor has focus
+  testOnOutsideOfEditor(document.getElementById("button"), "button", div);
+  // nobody has focus.
+  testOnOutsideOfEditor(null, "nobody", div);
+}
 
 function runEditableSubframeTests()
 {
   window.open("window_imestate_iframes.html", "_blank",
               "width=600,height=600");
 }
 
 function runTests()
@@ -514,22 +809,24 @@ function runTests()
   // test designMode
   document.designMode = "on";
   runBasicTest(true, true, "Testing designMode=\"on\"");
   document.designMode = "off";
   document.getElementById("text").focus();
   runBasicTest(false, false, "Testing designMode=\"off\"");
 
   // changing input[type] values
-  // XXX currently, type attribute changing doesn't work fine. bug 488420.
+  // XXX currently, type attribute changing doesn't work fine. bug 559728.
   // runTypeChangingTest();
 
   // changing readonly attribute
-  // XXX currently, readonly attribute changing doesn't work fine. bug 488420.
-  // runReadonlyChangingTest();
+  runReadonlyChangingTest();
+
+  // complex contenteditable editor's tests
+  runComplexContenteditableTests();
 
   runASyncTests();
 }
 
 function runASyncTests()
 {
   // The tests must call onFinish() method.
   runEditableSubframeTests();