Bug 1729115 - part 3: Make `IMEStateManager` check whether given focused content matches with null `sContent` in design mode r=m_kato
authorMasayuki Nakano <masayuki@d-toybox.com>
Tue, 12 Oct 2021 04:41:14 +0000
changeset 595438 d51a3f4602303979556ca1962d0fb271304e86fc
parent 595437 cfd34d4f5760b300c0a93d4ac56343cba390304c
child 595439 6e1011402c79e223af8038a38552fb538e6b9b94
push id38870
push userccozmuta@mozilla.com
push dateTue, 12 Oct 2021 09:32:00 +0000
treeherdermozilla-central@d51a3f460230 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersm_kato
bugs1729115
milestone95.0a1
first release with
nightly linux32
d51a3f460230 / 95.0a1 / 20211012093200 / files
nightly linux64
d51a3f460230 / 95.0a1 / 20211012093200 / files
nightly mac
d51a3f460230 / 95.0a1 / 20211012093200 / files
nightly win32
d51a3f460230 / 95.0a1 / 20211012093200 / files
nightly win64
d51a3f460230 / 95.0a1 / 20211012093200 / files
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
releases
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1729115 - part 3: Make `IMEStateManager` check whether given focused content matches with null `sContent` in design mode r=m_kato `nsFocusManager` does not send `focus` event in some cases, e.g., non-editable root element gets focus. However, the element may become editable later without a focus move. Therefore, even if `IMEStateManager::sContent` is `nullptr`, `IMEStateManager::UpdateIMEState()` and `IMEStateManager::FocusInEditor()` are called with focused content when the uncomposed document is in design mode. With this change, editor does not need to call `IMEStateManager` methods with `nullptr` when it's in `designMode`. Therefore, we can get rid of `GetFocusedContentForIME()` which just returns `nullptr` if focused content is in design mode. Differential Revision: https://phabricator.services.mozilla.com/D127612
dom/events/IMEStateManager.cpp
dom/events/IMEStateManager.h
editor/libeditor/EditorBase.cpp
editor/libeditor/EditorBase.h
editor/libeditor/EditorEventListener.cpp
editor/libeditor/EditorEventListener.h
editor/libeditor/HTMLEditor.cpp
editor/libeditor/HTMLEditor.h
editor/libeditor/tests/mochitest.ini
editor/libeditor/tests/test_focused_document_element_becoming_editable.html
--- a/dom/events/IMEStateManager.cpp
+++ b/dom/events/IMEStateManager.cpp
@@ -808,28 +808,65 @@ void IMEStateManager::OnClickInEditor(ns
           : InputContextAction::CAUSE_MOUSE;
 
   InputContextAction action(cause, InputContextAction::FOCUS_NOT_CHANGED);
   IMEState newState = GetNewIMEState(aPresContext, aContent);
   SetIMEState(newState, aPresContext, aContent, widget, action, sOrigin);
 }
 
 // static
+bool IMEStateManager::IsFocusedContent(const nsPresContext* aPresContext,
+                                       const nsIContent* aFocusedContent) {
+  if (!aPresContext || !sPresContext || aPresContext != sPresContext) {
+    return false;
+  }
+
+  if (sContent == aFocusedContent) {
+    return true;
+  }
+
+  // If sContent is not nullptr, but aFocusedContent is nullptr, it does not
+  // have focus from point of view of IMEStateManager.
+  if (sContent) {
+    return false;
+  }
+
+  // If the caller does not think that nobody has focus, but we know there is
+  // a focused content, the caller must be called with wrong content.
+  if (!aFocusedContent) {
+    return false;
+  }
+
+  // If the aFocusedContent is in design mode, sContent may be nullptr.
+  if (aFocusedContent->IsInDesignMode()) {
+    MOZ_ASSERT(aPresContext == sPresContext && !sContent);
+    return true;
+  }
+
+  // Otherwise, only when aFocusedContent is the root element, it can have
+  // focus, but IMEStateManager::OnChangeFocus is called with nullptr for
+  // aContent if it was not editable.
+  // XXX In this case, should the caller update sContent?
+  return aFocusedContent->IsEditable() &&
+         sPresContext->Document()->GetRootElement() == aFocusedContent;
+}
+
+// static
 void IMEStateManager::OnFocusInEditor(nsPresContext* aPresContext,
                                       nsIContent* aContent,
                                       EditorBase& aEditorBase) {
   MOZ_LOG(sISMLog, LogLevel::Info,
           ("OnFocusInEditor(aPresContext=0x%p (available: %s), aContent=0x%p, "
            "aEditorBase=0x%p), sPresContext=0x%p, sContent=0x%p, "
            "sActiveIMEContentObserver=0x%p",
            aPresContext, GetBoolName(CanHandleWith(aPresContext)), aContent,
            &aEditorBase, sPresContext.get(), sContent.get(),
            sActiveIMEContentObserver.get()));
 
-  if (sPresContext != aPresContext || sContent != aContent) {
+  if (!IsFocusedContent(aPresContext, aContent)) {
     MOZ_LOG(sISMLog, LogLevel::Debug,
             ("  OnFocusInEditor(), "
              "an editor not managed by ISM gets focus"));
     return;
   }
 
   // If the IMEContentObserver instance isn't managing the editor actually,
   // we need to recreate the instance.
@@ -846,17 +883,17 @@ void IMEStateManager::OnFocusInEditor(ns
     // TryToFlushPendingNotifications call must make it initialized.
     if (!sActiveIMEContentObserver->IsBeingInitializedFor(aPresContext,
                                                           aContent)) {
       DestroyIMEContentObserver();
     }
   }
 
   if (!sActiveIMEContentObserver) {
-    CreateIMEContentObserver(aEditorBase);
+    CreateIMEContentObserver(aEditorBase, aContent);
     if (sActiveIMEContentObserver) {
       MOZ_LOG(sISMLog, LogLevel::Debug,
               ("  OnFocusInEditor(), new IMEContentObserver is created (0x%p)",
                sActiveIMEContentObserver.get()));
     }
   }
 
   if (sActiveIMEContentObserver) {
@@ -1087,25 +1124,24 @@ void IMEStateManager::UpdateIMEState(con
     if (NS_WARN_IF(widget->Destroyed())) {
       MOZ_LOG(
           sISMLog, LogLevel::Error,
           ("  UpdateIMEState(), widget has gone during setting input context"));
       return;
     }
   }
 
-  // XXX Update sContent when aContent is focused content?
-  NS_ASSERTION(sContent.get() == aContent,
-               "sContent and aContent are mismatched.");
+  NS_ASSERTION(IsFocusedContent(presContext, aContent),
+               "aContent does not match with sContent");
 
   if (createTextStateManager) {
     // XXX In this case, it might not be enough safe to notify IME of anything.
     //     So, don't try to flush pending notifications of IMEContentObserver
     //     here.
-    CreateIMEContentObserver(aEditorBase);
+    CreateIMEContentObserver(aEditorBase, aContent);
   }
 }
 
 // static
 IMEState IMEStateManager::GetNewIMEState(nsPresContext* aPresContext,
                                          nsIContent* aContent) {
   MOZ_LOG(
       sISMLog, LogLevel::Info,
@@ -1147,16 +1183,25 @@ IMEState IMEStateManager::GetNewIMEState
       return IMEState(IMEEnabled::Enabled);
     }
     MOZ_LOG(sISMLog, LogLevel::Debug,
             ("  GetNewIMEState() returns IMEEnabled::Disabled because "
              "no content has focus"));
     return IMEState(IMEEnabled::Disabled);
   }
 
+  // If aContent is in designMode, aContent should be the root node of the
+  // document.
+  if (aContent && aContent->IsInDesignMode()) {
+    MOZ_LOG(sISMLog, LogLevel::Debug,
+            ("  GetNewIMEState() returns IMEEnabled::Enabled because "
+             "a content node in design mode editor has focus"));
+    return IMEState(IMEEnabled::Enabled);
+  }
+
   // nsIContent::GetDesiredIMEState() may cause a call of UpdateIMEState()
   // from EditorBase::PostCreate() because GetDesiredIMEState() needs to
   // retrieve an editor instance for the element if it's editable element.
   // For avoiding such nested IME state updates, we should set
   // sIsGettingNewIMEState here and UpdateIMEState() should check it.
   GettingNewIMEStateBlocker blocker;
 
   IMEState newIMEState = aContent->GetDesiredIMEState();
@@ -1923,28 +1968,36 @@ bool IMEStateManager::IsEditable(nsINode
   if (node->IsElement() &&
       node->AsElement()->State().HasState(NS_EVENT_STATE_READWRITE)) {
     return true;
   }
   return false;
 }
 
 // static
-nsINode* IMEStateManager::GetRootEditableNode(nsPresContext* aPresContext,
-                                              nsIContent* aContent) {
+nsINode* IMEStateManager::GetRootEditableNode(const nsPresContext* aPresContext,
+                                              const nsIContent* aContent) {
   if (aContent) {
+    // If the focused content is in design mode, return is composed document
+    // because aContent may be in UA widget shadow tree.
+    if (aContent->IsInDesignMode()) {
+      return aContent->GetComposedDoc();
+    }
+
     nsINode* root = nullptr;
-    nsINode* node = aContent;
+    nsINode* node = const_cast<nsIContent*>(aContent);
     while (node && IsEditable(node)) {
       // If the node has independent selection like <input type="text"> or
       // <textarea>, the node should be the root editable node for aContent.
       // FYI: <select> element also has independent selection but IsEditable()
       //      returns false.
       // XXX: If somebody adds new editable element which has independent
       //      selection but doesn't own editor, we'll need more checks here.
+      // XXX: If aContent is not in native anonymous subtree, checking
+      //      independent selection must be wrong, see bug 1731005.
       if (node->IsContent() && node->AsContent()->HasIndependentSelection()) {
         return node;
       }
       root = node;
       node = node->GetParentNode();
     }
     return root;
   }
@@ -1976,24 +2029,25 @@ void IMEStateManager::DestroyIMEContentO
           ("  DestroyIMEContentObserver(), destroying "
            "the active IMEContentObserver..."));
   RefPtr<IMEContentObserver> tsm = sActiveIMEContentObserver.get();
   sActiveIMEContentObserver = nullptr;
   tsm->Destroy();
 }
 
 // static
-void IMEStateManager::CreateIMEContentObserver(EditorBase& aEditorBase) {
+void IMEStateManager::CreateIMEContentObserver(EditorBase& aEditorBase,
+                                               nsIContent* aFocusedContent) {
   MOZ_LOG(sISMLog, LogLevel::Info,
-          ("CreateIMEContentObserver(aEditorBase=0x%p), "
+          ("CreateIMEContentObserver(aEditorBase=0x%p, aFocusedContent=0x%p), "
            "sPresContext=0x%p, sContent=0x%p, sWidget=0x%p (available: %s), "
            "sActiveIMEContentObserver=0x%p, "
            "sActiveIMEContentObserver->IsManaging(sPresContext, sContent)=%s",
-           &aEditorBase, sPresContext.get(), sContent.get(), sWidget,
-           GetBoolName(sWidget && !sWidget->Destroyed()),
+           &aEditorBase, aFocusedContent, sPresContext.get(), sContent.get(),
+           sWidget, GetBoolName(sWidget && !sWidget->Destroyed()),
            sActiveIMEContentObserver.get(),
            GetBoolName(sActiveIMEContentObserver
                            ? sActiveIMEContentObserver->IsManaging(sPresContext,
                                                                    sContent)
                            : false)));
 
   if (NS_WARN_IF(sActiveIMEContentObserver)) {
     MOZ_LOG(sISMLog, LogLevel::Error,
@@ -2047,17 +2101,17 @@ void IMEStateManager::CreateIMEContentOb
   sActiveIMEContentObserver = new IMEContentObserver();
 
   // IMEContentObserver::Init() might create another IMEContentObserver
   // instance.  So, sActiveIMEContentObserver would be replaced with new one.
   // We should hold the current instance here.
   RefPtr<IMEContentObserver> activeIMEContentObserver(
       sActiveIMEContentObserver);
   OwningNonNull<nsPresContext> presContext(*sPresContext);
-  RefPtr<nsIContent> content = sContent;
+  nsCOMPtr<nsIContent> content = aFocusedContent;
   activeIMEContentObserver->Init(widget, presContext, content, aEditorBase);
 }
 
 // static
 nsresult IMEStateManager::GetFocusSelectionAndRoot(Selection** aSelection,
                                                    nsIContent** aRootContent) {
   if (!sActiveIMEContentObserver) {
     return NS_ERROR_NOT_AVAILABLE;
--- a/dom/events/IMEStateManager.h
+++ b/dom/events/IMEStateManager.h
@@ -286,18 +286,18 @@ class IMEStateManager {
   static nsresult NotifyIME(const IMENotification& aNotification,
                             nsIWidget* aWidget,
                             BrowserParent* aBrowserParent = nullptr);
   static nsresult NotifyIME(IMEMessage aMessage, nsIWidget* aWidget,
                             BrowserParent* aBrowserParent = nullptr);
   static nsresult NotifyIME(IMEMessage aMessage, nsPresContext* aPresContext,
                             BrowserParent* aBrowserParent = nullptr);
 
-  static nsINode* GetRootEditableNode(nsPresContext* aPresContext,
-                                      nsIContent* aContent);
+  static nsINode* GetRootEditableNode(const nsPresContext* aPresContext,
+                                      const nsIContent* aContent);
 
   /**
    * Returns active IMEContentObserver but may be nullptr if focused content
    * isn't editable or focus in a remote process.
    */
   static IMEContentObserver* GetActiveContentObserver();
 
  protected:
@@ -315,17 +315,24 @@ class IMEStateManager {
                                  nsIContent* aContent);
 
   static void EnsureTextCompositionArray();
 
   // XXX Changing this to MOZ_CAN_RUN_SCRIPT requires too many callers to be
   //     marked too.  Probably, we should initialize IMEContentObserver
   //     asynchronously.
   MOZ_CAN_RUN_SCRIPT_BOUNDARY static void CreateIMEContentObserver(
-      EditorBase& aEditorBase);
+      EditorBase& aEditorBase, nsIContent* aFocusedContent);
+
+  /**
+   * Check whether the content matches or does not match with focus information
+   * which is previously notified via OnChangeFocus();
+   */
+  static bool IsFocusedContent(const nsPresContext* aPresContext,
+                               const nsIContent* aFocusedContent);
 
   static void DestroyIMEContentObserver();
 
   static bool IsEditable(nsINode* node);
 
   static bool IsIMEObserverNeeded(const IMEState& aState);
 
   static nsIContent* GetRootContent(nsPresContext* aPresContext);
--- a/editor/libeditor/EditorBase.cpp
+++ b/editor/libeditor/EditorBase.cpp
@@ -454,19 +454,17 @@ nsresult EditorBase::PostCreateInternal(
     }
 
     IMEState newState;
     nsresult rv = GetPreferredIMEState(&newState);
     if (NS_FAILED(rv)) {
       NS_WARNING("EditorBase::GetPreferredIMEState() failed");
       return NS_OK;
     }
-    // May be null in design mode
-    nsCOMPtr<nsIContent> content = GetFocusedContentForIME();
-    IMEStateManager::UpdateIMEState(newState, content, *this);
+    IMEStateManager::UpdateIMEState(newState, focusedContent, *this);
   }
 
   // FYI: This call might cause destroying this editor.
   IMEStateManager::OnEditorInitialized(*this);
 
   return NS_OK;
 }
 
@@ -702,17 +700,17 @@ NS_IMETHODIMP EditorBase::SetFlags(uint3
     IMEState newState;
     nsresult rv = GetPreferredIMEState(&newState);
     NS_WARNING_ASSERTION(
         NS_SUCCEEDED(rv),
         "EditorBase::GetPreferredIMEState() failed, but ignored");
     if (NS_SUCCEEDED(rv)) {
       // NOTE: When the enabled state isn't going to be modified, this method
       // is going to do nothing.
-      nsCOMPtr<nsIContent> content = GetFocusedContentForIME();
+      nsCOMPtr<nsIContent> content = GetFocusedContent();
       IMEStateManager::UpdateIMEState(newState, content, *this);
     }
   }
 
   return NS_OK;
 }
 
 NS_IMETHODIMP EditorBase::GetIsSelectionEditable(bool* aIsSelectionEditable) {
@@ -5342,17 +5340,17 @@ void EditorBase::ReinitializeSelection(E
   // If previous focused editor turn on spellcheck and this editor doesn't
   // turn on it, spellcheck state is mismatched.  So we need to re-sync it.
   SyncRealTimeSpell();
 
   RefPtr<nsPresContext> presContext = GetPresContext();
   if (NS_WARN_IF(!presContext)) {
     return;
   }
-  nsCOMPtr<nsIContent> focusedContent = GetFocusedContentForIME();
+  nsCOMPtr<nsIContent> focusedContent = GetFocusedContent();
   IMEStateManager::OnFocusInEditor(presContext, focusedContent, *this);
 }
 
 Element* EditorBase::GetEditorRoot() const { return GetRoot(); }
 
 Element* EditorBase::GetExposedRoot() const {
   Element* rootElement = GetRoot();
   if (!rootElement || !rootElement->IsInNativeAnonymousSubtree()) {
@@ -5520,20 +5518,16 @@ nsIContent* EditorBase::GetFocusedConten
   }
 
   nsIContent* content = focusManager->GetFocusedElement();
   MOZ_ASSERT((content == piTarget) == SameCOMIdentity(content, piTarget));
 
   return (content == piTarget) ? content : nullptr;
 }
 
-nsIContent* EditorBase::GetFocusedContentForIME() const {
-  return GetFocusedContent();
-}
-
 bool EditorBase::IsActiveInDOMWindow() const {
   EventTarget* piTarget = GetDOMEventTarget();
   if (!piTarget) {
     return false;
   }
 
   nsFocusManager* focusManager = nsFocusManager::GetFocusManager();
   if (NS_WARN_IF(!focusManager)) {
--- a/editor/libeditor/EditorBase.h
+++ b/editor/libeditor/EditorBase.h
@@ -597,22 +597,16 @@ class EditorBase : public nsIEditor,
   }
 
   /**
    * Get the focused content, if we're focused.  Returns null otherwise.
    */
   virtual nsIContent* GetFocusedContent() const;
 
   /**
-   * Get the focused content for the argument of some IMEStateManager's
-   * methods.
-   */
-  virtual nsIContent* GetFocusedContentForIME() const;
-
-  /**
    * Whether the aGUIEvent should be handled by this editor or not.  When this
    * returns false, The aGUIEvent shouldn't be handled on this editor,
    * i.e., The aGUIEvent should be handled by another inner editor or ancestor
    * elements.
    */
   virtual bool IsAcceptableInputEvent(WidgetGUIEvent* aGUIEvent) const;
 
   /**
--- a/editor/libeditor/EditorEventListener.cpp
+++ b/editor/libeditor/EditorEventListener.cpp
@@ -270,34 +270,16 @@ PresShell* EditorEventListener::GetPresS
   return mEditorBase->GetPresShell();
 }
 
 nsPresContext* EditorEventListener::GetPresContext() const {
   PresShell* presShell = GetPresShell();
   return presShell ? presShell->GetPresContext() : nullptr;
 }
 
-nsIContent* EditorEventListener::GetFocusedRootContent() {
-  MOZ_ASSERT(!DetachedFromEditor());
-  nsCOMPtr<nsIContent> focusedContent = mEditorBase->GetFocusedContent();
-  if (!focusedContent) {
-    return nullptr;
-  }
-
-  if (MOZ_UNLIKELY(NS_WARN_IF(!focusedContent->IsInComposedDoc()))) {
-    return nullptr;
-  }
-
-  if (focusedContent->IsInDesignMode()) {
-    return nullptr;
-  }
-
-  return focusedContent;
-}
-
 bool EditorEventListener::EditorHasFocus() {
   MOZ_ASSERT(!DetachedFromEditor());
   nsCOMPtr<nsIContent> focusedContent = mEditorBase->GetFocusedContent();
   if (!focusedContent) {
     return false;
   }
   return !!focusedContent->GetComposedDoc();
 }
@@ -675,17 +657,18 @@ nsresult EditorEventListener::MouseClick
     return NS_OK;
   }
 
   // Notifies clicking on editor to IMEStateManager even when the event was
   // consumed.
   if (EditorHasFocus()) {
     RefPtr<nsPresContext> presContext = GetPresContext();
     if (presContext) {
-      IMEStateManager::OnClickInEditor(presContext, GetFocusedRootContent(),
+      nsCOMPtr<nsIContent> focusedContent = mEditorBase->GetFocusedContent();
+      IMEStateManager::OnClickInEditor(presContext, focusedContent,
                                        aMouseClickEvent);
       if (DetachedFromEditor()) {
         return NS_OK;
       }
     }
   }
 
   if (DetachedFromEditorOrDefaultPrevented(aMouseClickEvent)) {
@@ -746,19 +729,19 @@ bool EditorEventListener::NotifyIMEOfMou
   if (!EditorHasFocus()) {
     return false;
   }
 
   RefPtr<nsPresContext> presContext = GetPresContext();
   if (NS_WARN_IF(!presContext)) {
     return false;
   }
-  nsCOMPtr<nsIContent> focusedRootContent = GetFocusedRootContent();
+  nsCOMPtr<nsIContent> focusedContent = mEditorBase->GetFocusedContent();
   return IMEStateManager::OnMouseButtonEventInEditor(
-      presContext, focusedRootContent, aMouseEvent);
+      presContext, focusedContent, aMouseEvent);
 }
 
 nsresult EditorEventListener::MouseDown(MouseEvent* aMouseEvent) {
   // FYI: We don't need to check if it's already consumed here because
   //      we need to commit composition at mouse button operation.
   // FYI: This may be called by HTMLEditorEventListener::MouseDown() even
   //      when the event is not acceptable for committing composition.
   if (DetachedFromEditor()) {
@@ -1165,17 +1148,17 @@ nsresult EditorEventListener::Focus(Inte
   if (DetachedFromEditorOrDefaultPrevented(aFocusEvent)) {
     return NS_OK;
   }
 
   RefPtr<nsPresContext> presContext = GetPresContext();
   if (NS_WARN_IF(!presContext)) {
     return NS_OK;
   }
-  nsCOMPtr<nsIContent> focusedContent = editorBase->GetFocusedContentForIME();
+  nsCOMPtr<nsIContent> focusedContent = editorBase->GetFocusedContent();
   IMEStateManager::OnFocusInEditor(presContext, focusedContent, *editorBase);
 
   return NS_OK;
 }
 
 nsresult EditorEventListener::Blur(InternalFocusEvent* aBlurEvent) {
   if (NS_WARN_IF(!aBlurEvent) || DetachedFromEditor()) {
     return NS_OK;
--- a/editor/libeditor/EditorEventListener.h
+++ b/editor/libeditor/EditorEventListener.h
@@ -82,17 +82,16 @@ class EditorEventListener : public nsIDO
 
   void RefuseToDropAndHideCaret(dom::DragEvent* aDragEvent);
   bool DragEventHasSupportingData(dom::DragEvent* aDragEvent) const;
   MOZ_CAN_RUN_SCRIPT bool CanInsertAtDropPosition(dom::DragEvent* aDragEvent);
   void InitializeDragDropCaret();
   void CleanupDragDropCaret();
   PresShell* GetPresShell() const;
   nsPresContext* GetPresContext() const;
-  nsIContent* GetFocusedRootContent();
   // Returns true if IME consumes the mouse event.
   MOZ_CAN_RUN_SCRIPT bool NotifyIMEOfMouseButtonEvent(
       WidgetMouseEvent* aMouseEvent);
   bool EditorHasFocus();
   bool IsFileControlTextBox();
   bool ShouldHandleNativeKeyBindings(WidgetKeyboardEvent* aKeyboardEvent);
 
   /**
--- a/editor/libeditor/HTMLEditor.cpp
+++ b/editor/libeditor/HTMLEditor.cpp
@@ -5780,29 +5780,16 @@ nsIContent* HTMLEditor::GetFocusedConten
   if (!focusedContent->HasFlag(NODE_IS_EDITABLE) ||
       focusedContent->HasIndependentSelection()) {
     return nullptr;
   }
   // If our window is focused, we're focused.
   return OurWindowHasFocus() ? focusedContent : nullptr;
 }
 
-nsIContent* HTMLEditor::GetFocusedContentForIME() const {
-  nsIContent* focusedContent = GetFocusedContent();
-  if (!focusedContent) {
-    return nullptr;
-  }
-
-  Document* document = GetDocument();
-  if (NS_WARN_IF(!document)) {
-    return nullptr;
-  }
-  return IsInDesignMode() ? nullptr : focusedContent;
-}
-
 bool HTMLEditor::IsActiveInDOMWindow() const {
   nsFocusManager* focusManager = nsFocusManager::GetFocusManager();
   if (NS_WARN_IF(!focusManager)) {
     return false;
   }
 
   Document* document = GetDocument();
   if (NS_WARN_IF(!document)) {
--- a/editor/libeditor/HTMLEditor.h
+++ b/editor/libeditor/HTMLEditor.h
@@ -214,17 +214,16 @@ class HTMLEditor final : public EditorBa
    * and/or scroll position.
    */
   void PreHandleSelectionChangeCommand(Command aCommand);
   void PostHandleSelectionChangeCommand(Command aCommand);
 
   MOZ_CAN_RUN_SCRIPT nsresult
   HandleKeyPressEvent(WidgetKeyboardEvent* aKeyboardEvent) final;
   nsIContent* GetFocusedContent() const final;
-  nsIContent* GetFocusedContentForIME() const final;
   bool IsActiveInDOMWindow() const final;
   dom::EventTarget* GetDOMEventTarget() const final;
   Element* FindSelectionRoot(nsINode* aNode) const final;
   bool IsAcceptableInputEvent(WidgetGUIEvent* aGUIEvent) const final;
   nsresult GetPreferredIMEState(widget::IMEState* aState) final;
 
   /**
    * GetBackgroundColorState() returns what the background color of the
--- a/editor/libeditor/tests/mochitest.ini
+++ b/editor/libeditor/tests/mochitest.ini
@@ -234,16 +234,17 @@ skip-if = os != 'mac' # bug 574005
 [test_composition_event_created_in_chrome.html]
 [test_contenteditable_focus.html]
 [test_cut_copy_delete_command_enabled.html]
 [test_cut_copy_password.html]
 [test_dom_input_event_on_htmleditor.html]
 [test_dom_input_event_on_texteditor.html]
 [test_dragdrop.html]
 skip-if = os == 'android'
+[test_focused_document_element_becoming_editable.html]
 [test_handle_new_lines.html]
 [test_htmleditor_tab_key_handling.html]
 [test_initial_selection_and_caret_of_designMode.html]
 [test_inline_style_cache.html]
 [test_inlineTableEditing.html]
 [test_insertParagraph_in_inline_editing_host.html]
 [test_keypress_untrusted_event.html]
 [test_label_contenteditable.html]
new file mode 100644
--- /dev/null
+++ b/editor/libeditor/tests/test_focused_document_element_becoming_editable.html
@@ -0,0 +1,157 @@
+<!doctype html>
+<html>
+<head>
+<meta chareset="utf-8">
+<title>Testing non-editable root becomes editable after getting focus</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" href="/tests/SimpleTest/test.css">
+</head>
+<body>
+<script>
+SimpleTest.waitForExplicitFinish();
+addEventListener("load", async () => {
+  await SimpleTest.promiseFocus(window);
+
+  await (async () => {
+    const iframe = document.createElement("iframe");
+    document.body.appendChild(iframe);
+    await new Promise(resolve => {
+      iframe.addEventListener("load", async () => {
+        const doc = iframe.contentDocument;
+        const win = iframe.contentWindow;
+        win.focus();
+        doc.documentElement.focus();
+        doc.designMode = "on";
+        await new Promise(r => win.requestAnimationFrame(() => win.requestAnimationFrame(r)));
+        is(
+          SpecialPowers.getDOMWindowUtils(win).IMEStatus,
+          SpecialPowers.Ci.nsIDOMWindowUtils.IME_STATUS_ENABLED,
+          "IME should be enabled in the design mode document"
+        );
+        is(
+          SpecialPowers.unwrap(SpecialPowers.getDOMWindowUtils(win).nodeObservedByIMEContentObserver),
+          doc.body,
+          "The <body> should be observed by IMEContentObserver in design mode"
+        );
+        doc.designMode = "off";
+        iframe.remove();
+        resolve();
+      }, {once: true});
+      info("Waiting for load of sub-document for testing design mode");
+      iframe.srcdoc = "<!doctype html><html><meta charset=\"utf-8\"></head><body></body></html>";
+    });
+  })();
+
+  await (async () => {
+    const iframe = document.createElement("iframe");
+    document.body.appendChild(iframe);
+    await new Promise(resolve => {
+      iframe.addEventListener("load", async () => {
+        const doc = iframe.contentDocument;
+        const win = iframe.contentWindow;
+        win.focus()
+        doc.documentElement.focus();
+        doc.documentElement.contentEditable = "true";
+        await new Promise(r => win.requestAnimationFrame(() => win.requestAnimationFrame(r)));
+        is(
+          SpecialPowers.getDOMWindowUtils(win).IMEStatus,
+          SpecialPowers.Ci.nsIDOMWindowUtils.IME_STATUS_ENABLED,
+          "IME should be enabled when the <html> element whose contenteditable is set to true"
+        );
+        is(
+          SpecialPowers.unwrap(SpecialPowers.getDOMWindowUtils(win).nodeObservedByIMEContentObserver),
+          doc.documentElement,
+          "The <html> should be observed by IMEContentObserver when <html contenteditable=\"true\">"
+        );
+        iframe.remove();
+        resolve();
+      }, {once: true});
+      info("Waiting for load of sub-document for testing <html> element becomes editable");
+      iframe.srcdoc = "<!doctype html><html><meta charset=\"utf-8\"></head><body></body></html>";
+    });
+  })();
+
+  await (async () => {
+    const iframe = document.createElement("iframe");
+    document.body.appendChild(iframe);
+    await new Promise(resolve => {
+      iframe.addEventListener("load", async () => {
+        const doc = iframe.contentDocument;
+        const win = iframe.contentWindow;
+        win.focus();
+        doc.body.focus();
+        doc.body.contentEditable = "true";
+        await new Promise(r => win.requestAnimationFrame(() => win.requestAnimationFrame(r)));
+        if (doc.activeElement === doc.body && doc.hasFocus()) {
+          todo_is(
+            SpecialPowers.getDOMWindowUtils(win).IMEStatus,
+            SpecialPowers.Ci.nsIDOMWindowUtils.IME_STATUS_ENABLED,
+            "IME should be enabled when the <body> element whose contenteditable is set to true and it has focus"
+          );
+          todo_is(
+            SpecialPowers.unwrap(SpecialPowers.getDOMWindowUtils(win).nodeObservedByIMEContentObserver),
+            doc.body,
+            "The <body> should be observed by IMEContentObserver when <body contenteditable=\"true\"> and it has focus"
+          );
+        } else {
+          is(
+            SpecialPowers.getDOMWindowUtils(win).IMEStatus,
+            SpecialPowers.Ci.nsIDOMWindowUtils.IME_STATUS_DISABLED,
+            "IME should be disabled when the <body> element whose contenteditable is set to true but it does not have focus"
+          );
+          is(
+            SpecialPowers.unwrap(SpecialPowers.getDOMWindowUtils(win).nodeObservedByIMEContentObserver),
+            null,
+            "Nobody should be observed by IMEContentObserver when <body contenteditable=\"true\"> but it does not have focus"
+          );
+        }
+        iframe.remove();
+        resolve();
+      }, {once: true});
+      info("Waiting for load of sub-document for testing <body> element becomes editable");
+      iframe.srcdoc = "<!doctype html><html><meta charset=\"utf-8\"></head><body></body></html>";
+    });
+  })();
+
+  await (async () => {
+    const iframe = document.createElement("iframe");
+    document.body.appendChild(iframe);
+    await new Promise(resolve => {
+      iframe.addEventListener("load", async () => {
+        const doc = iframe.contentDocument;
+        const win = iframe.contentWindow;
+        win.focus();
+        const editingHost = doc.createElement("div");
+        doc.documentElement.remove();
+        doc.appendChild(editingHost);
+        editingHost.focus();
+        is(
+          SpecialPowers.unwrap(SpecialPowers.focusManager.focusedElement),
+          editingHost,
+          "The <div contenteditable> should have focus because of only child of the Document node"
+        );
+        editingHost.contentEditable = "true";
+        await new Promise(r => win.requestAnimationFrame(() => win.requestAnimationFrame(r)));
+        is(
+          SpecialPowers.getDOMWindowUtils(win).IMEStatus,
+          SpecialPowers.Ci.nsIDOMWindowUtils.IME_STATUS_ENABLED,
+          "IME should be enabled in the root element"
+        );
+        is(
+          SpecialPowers.unwrap(SpecialPowers.getDOMWindowUtils(win).nodeObservedByIMEContentObserver),
+          editingHost,
+          "The <div contenteditable> should be observed by IMEContentObserver"
+        );
+        iframe.srcdoc = "";
+        resolve();
+      }, {once: true});
+      info("Waiting for load of sub-document for testing root <div> element becomes editable");
+      iframe.srcdoc = "<!doctype html><html><meta charset=\"utf-8\"></head><body></body></html>";
+    });
+  })();
+
+  SimpleTest.finish();
+}, false);
+</script>
+</body>
+</html>