forked from mirrors/gecko-dev
		
	Bug 1807495 - part 1: Make Document notify HTMLEditor and IMEStateManager of pseudo focus change when editing state of focused editing host or document is changed r=smaug,m_kato
				
					
				
			`IMEStateManager` basically runs at focus change. However, when `designMode` is set to `"off"` or focused editing host becomes non-editable (removing `contenteditable` attribute or `contenteditable` attribute is set to `false`), IME may be disabled without a focus change. Therefore, `Document` needs to notify `IMEStateManager` of the timing. Additionally, `nsFocusManager` does not change focus when focused element becomes not focusable (bug 1807597). Therefore, `Document` needs to kick `focus` or `blur` event handler of `HTMLEditor` when active editing host becomes not editable. However, if an ancestor of focused element becomes editable, I think that `HTMLEditor` does not work well without focus move because it computes editing host with current editing state in a lot of places, but `Selection` and `nsFocusManager::GetFocusedElement` keeps working with previous focused element which is now a editable child of editing host. Therefore, this patch marks them as `todo` in the new tests. Differential Revision: https://phabricator.services.mozilla.com/D171196
This commit is contained in:
		
							parent
							
								
									2d577e4f51
								
							
						
					
					
						commit
						e4a81444c1
					
				
					 18 changed files with 975 additions and 57 deletions
				
			
		|  | @ -4540,6 +4540,12 @@ bool Document::HasFocus(ErrorResult& rv) const { | ||||||
|   return fm->IsSameOrAncestor(bc, fm->GetFocusedBrowsingContext()); |   return fm->IsSameOrAncestor(bc, fm->GetFocusedBrowsingContext()); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | bool Document::ThisDocumentHasFocus() const { | ||||||
|  |   nsFocusManager* fm = nsFocusManager::GetFocusManager(); | ||||||
|  |   return fm && fm->GetFocusedWindow() && | ||||||
|  |          fm->GetFocusedWindow()->GetExtantDoc() == this; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| void Document::GetDesignMode(nsAString& aDesignMode) { | void Document::GetDesignMode(nsAString& aDesignMode) { | ||||||
|   if (IsInDesignMode()) { |   if (IsInDesignMode()) { | ||||||
|     aDesignMode.AssignLiteral("on"); |     aDesignMode.AssignLiteral("on"); | ||||||
|  | @ -5959,6 +5965,20 @@ static bool HasPresShell(nsPIDOMWindowOuter* aWindow) { | ||||||
|   return docShell->GetPresShell() != nullptr; |   return docShell->GetPresShell() != nullptr; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | HTMLEditor* Document::GetHTMLEditor() const { | ||||||
|  |   nsPIDOMWindowOuter* window = GetWindow(); | ||||||
|  |   if (!window) { | ||||||
|  |     return nullptr; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   nsIDocShell* docshell = window->GetDocShell(); | ||||||
|  |   if (!docshell) { | ||||||
|  |     return nullptr; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   return docshell->GetHTMLEditor(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
| nsresult Document::EditingStateChanged() { | nsresult Document::EditingStateChanged() { | ||||||
|   if (mRemovedFromDocShell) { |   if (mRemovedFromDocShell) { | ||||||
|     return NS_OK; |     return NS_OK; | ||||||
|  | @ -5980,11 +6000,33 @@ nsresult Document::EditingStateChanged() { | ||||||
|     return NS_OK; |     return NS_OK; | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  |   const bool thisDocumentHasFocus = ThisDocumentHasFocus(); | ||||||
|   if (newState == EditingState::eOff) { |   if (newState == EditingState::eOff) { | ||||||
|     // Editing is being turned off.
 |     // Editing is being turned off.
 | ||||||
|     nsAutoScriptBlocker scriptBlocker; |     nsAutoScriptBlocker scriptBlocker; | ||||||
|  |     RefPtr<HTMLEditor> htmlEditor = GetHTMLEditor(); | ||||||
|     NotifyEditableStateChange(*this); |     NotifyEditableStateChange(*this); | ||||||
|     return TurnEditingOff(); |     nsresult rv = TurnEditingOff(); | ||||||
|  |     // If this document has focus and the editing state of this document
 | ||||||
|  |     // becomes "off", it means that HTMLEditor won't handle any inputs nor
 | ||||||
|  |     // modify the DOM tree.  However, HTMLEditor may not receive `blur`
 | ||||||
|  |     // event for this state change since this may occur without focus change.
 | ||||||
|  |     // Therefore, let's notify HTMLEditor of this editing state change.
 | ||||||
|  |     // Note that even if focusedElement is an editable text control element,
 | ||||||
|  |     // it becomes not editable from HTMLEditor point of view since text
 | ||||||
|  |     // control elements are manged by TextEditor.
 | ||||||
|  |     RefPtr<Element> focusedElement = | ||||||
|  |         nsFocusManager::GetFocusManager() | ||||||
|  |             ? nsFocusManager::GetFocusManager()->GetFocusedElement() | ||||||
|  |             : nullptr; | ||||||
|  |     DebugOnly<nsresult> rvIgnored = | ||||||
|  |         HTMLEditor::FocusedElementOrDocumentBecomesNotEditable( | ||||||
|  |             htmlEditor, *this, focusedElement); | ||||||
|  |     NS_WARNING_ASSERTION( | ||||||
|  |         NS_SUCCEEDED(rvIgnored), | ||||||
|  |         "HTMLEditor::FocusedElementOrDocumentBecomesNotEditable() failed, but " | ||||||
|  |         "ignored"); | ||||||
|  |     return rv; | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   // Flush out style changes on our _parent_ document, if any, so that
 |   // Flush out style changes on our _parent_ document, if any, so that
 | ||||||
|  | @ -6183,6 +6225,19 @@ nsresult Document::EditingStateChanged() { | ||||||
| 
 | 
 | ||||||
|   MaybeDispatchCheckKeyPressEventModelEvent(); |   MaybeDispatchCheckKeyPressEventModelEvent(); | ||||||
| 
 | 
 | ||||||
|  |   // If this document keeps having focus and the HTMLEditor is in the design
 | ||||||
|  |   // mode, it may not receive `focus` event for this editing state change since
 | ||||||
|  |   // this may occur without a focus change.  Therefore, let's notify HTMLEditor
 | ||||||
|  |   // of this editing state change.
 | ||||||
|  |   if (thisDocumentHasFocus && htmlEditor->IsInDesignMode() && | ||||||
|  |       ThisDocumentHasFocus()) { | ||||||
|  |     DebugOnly<nsresult> rvIgnored = | ||||||
|  |         htmlEditor->FocusedElementOrDocumentBecomesEditable(*this, nullptr); | ||||||
|  |     NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored), | ||||||
|  |                          "HTMLEditor::FocusedElementOrDocumentBecomesEditable()" | ||||||
|  |                          " failed, but ignored"); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|   return NS_OK; |   return NS_OK; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -6194,9 +6249,11 @@ class DeferredContentEditableCountChangeEvent : public Runnable { | ||||||
|         mDoc(aDoc), |         mDoc(aDoc), | ||||||
|         mElement(aElement) {} |         mElement(aElement) {} | ||||||
| 
 | 
 | ||||||
|   NS_IMETHOD Run() override { |   MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHOD Run() override { | ||||||
|     if (mElement && mElement->OwnerDoc() == mDoc) { |     if (mElement && mElement->OwnerDoc() == mDoc) { | ||||||
|       mDoc->DeferredContentEditableCountChange(mElement); |       RefPtr<Document> doc = std::move(mDoc); | ||||||
|  |       RefPtr<Element> element = std::move(mElement); | ||||||
|  |       doc->DeferredContentEditableCountChange(element); | ||||||
|     } |     } | ||||||
|     return NS_OK; |     return NS_OK; | ||||||
|   } |   } | ||||||
|  | @ -6217,6 +6274,38 @@ void Document::ChangeContentEditableCount(Element* aElement, int32_t aChange) { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void Document::DeferredContentEditableCountChange(Element* aElement) { | void Document::DeferredContentEditableCountChange(Element* aElement) { | ||||||
|  |   const RefPtr<nsFocusManager> fm = nsFocusManager::GetFocusManager(); | ||||||
|  |   const bool elementHasFocus = | ||||||
|  |       aElement && fm && fm->GetFocusedElement() == aElement; | ||||||
|  |   if (elementHasFocus) { | ||||||
|  |     MOZ_ASSERT(nsContentUtils::IsSafeToRunScript()); | ||||||
|  |     // When contenteditable of aElement is changed and HTMLEditor works with it
 | ||||||
|  |     // or needs to start working with it, HTMLEditor may not receive `focus`
 | ||||||
|  |     // event nor `blur` event because this may occur without a focus change.
 | ||||||
|  |     // Therefore, we need to notify HTMLEditor of this contenteditable attribute
 | ||||||
|  |     // change.
 | ||||||
|  |     RefPtr<HTMLEditor> htmlEditor = GetHTMLEditor(); | ||||||
|  |     if (aElement->HasFlag(NODE_IS_EDITABLE)) { | ||||||
|  |       if (htmlEditor) { | ||||||
|  |         DebugOnly<nsresult> rvIgnored = | ||||||
|  |             htmlEditor->FocusedElementOrDocumentBecomesEditable(*this, | ||||||
|  |                                                                 aElement); | ||||||
|  |         NS_WARNING_ASSERTION( | ||||||
|  |             NS_SUCCEEDED(rvIgnored), | ||||||
|  |             "HTMLEditor::FocusedElementOrDocumentBecomesEditable() failed, but " | ||||||
|  |             "ignored"); | ||||||
|  |       } | ||||||
|  |     } else { | ||||||
|  |       DebugOnly<nsresult> rvIgnored = | ||||||
|  |           HTMLEditor::FocusedElementOrDocumentBecomesNotEditable( | ||||||
|  |               htmlEditor, *this, aElement); | ||||||
|  |       NS_WARNING_ASSERTION( | ||||||
|  |           NS_SUCCEEDED(rvIgnored), | ||||||
|  |           "HTMLEditor::FocusedElementOrDocumentBecomesNotEditable() failed, " | ||||||
|  |           "but ignored"); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|   if (mParser || |   if (mParser || | ||||||
|       (mUpdateNestLevel > 0 && (mContentEditableCount > 0) != IsEditingOn())) { |       (mUpdateNestLevel > 0 && (mContentEditableCount > 0) != IsEditingOn())) { | ||||||
|     return; |     return; | ||||||
|  | @ -6232,18 +6321,7 @@ void Document::DeferredContentEditableCountChange(Element* aElement) { | ||||||
|     // We just changed the contentEditable state of a node, we need to reset
 |     // We just changed the contentEditable state of a node, we need to reset
 | ||||||
|     // the spellchecking state of that node.
 |     // the spellchecking state of that node.
 | ||||||
|     if (aElement) { |     if (aElement) { | ||||||
|       nsPIDOMWindowOuter* window = GetWindow(); |       if (RefPtr<HTMLEditor> htmlEditor = GetHTMLEditor()) { | ||||||
|       if (!window) { |  | ||||||
|         return; |  | ||||||
|       } |  | ||||||
| 
 |  | ||||||
|       nsIDocShell* docshell = window->GetDocShell(); |  | ||||||
|       if (!docshell) { |  | ||||||
|         return; |  | ||||||
|       } |  | ||||||
| 
 |  | ||||||
|       RefPtr<HTMLEditor> htmlEditor = docshell->GetHTMLEditor(); |  | ||||||
|       if (htmlEditor) { |  | ||||||
|         nsCOMPtr<nsIInlineSpellChecker> spellChecker; |         nsCOMPtr<nsIInlineSpellChecker> spellChecker; | ||||||
|         rv = htmlEditor->GetInlineSpellChecker(false, |         rv = htmlEditor->GetInlineSpellChecker(false, | ||||||
|                                                getter_AddRefs(spellChecker)); |                                                getter_AddRefs(spellChecker)); | ||||||
|  | @ -6267,6 +6345,21 @@ void Document::DeferredContentEditableCountChange(Element* aElement) { | ||||||
|       } |       } | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
|  | 
 | ||||||
|  |   // aElement causes creating new HTMLEditor and the element had and keep
 | ||||||
|  |   // having focus, the HTMLEditor won't receive `focus` event.  Therefore, we
 | ||||||
|  |   // need to notify HTMLEditor of it becomes editable.
 | ||||||
|  |   if (elementHasFocus && aElement->HasFlag(NODE_IS_EDITABLE) && | ||||||
|  |       fm->GetFocusedElement() == aElement) { | ||||||
|  |     if (RefPtr<HTMLEditor> htmlEditor = GetHTMLEditor()) { | ||||||
|  |       DebugOnly<nsresult> rvIgnored = | ||||||
|  |           htmlEditor->FocusedElementOrDocumentBecomesEditable(*this, aElement); | ||||||
|  |       NS_WARNING_ASSERTION( | ||||||
|  |           NS_SUCCEEDED(rvIgnored), | ||||||
|  |           "HTMLEditor::FocusedElementOrDocumentBecomesEditable() failed, but " | ||||||
|  |           "ignored"); | ||||||
|  |     } | ||||||
|  |   } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void Document::MaybeDispatchCheckKeyPressEventModelEvent() { | void Document::MaybeDispatchCheckKeyPressEventModelEvent() { | ||||||
|  |  | ||||||
|  | @ -1469,7 +1469,7 @@ class Document : public nsINode, | ||||||
|    * changed to true, -1 if it was changed to false. |    * changed to true, -1 if it was changed to false. | ||||||
|    */ |    */ | ||||||
|   void ChangeContentEditableCount(Element*, int32_t aChange); |   void ChangeContentEditableCount(Element*, int32_t aChange); | ||||||
|   void DeferredContentEditableCountChange(Element*); |   MOZ_CAN_RUN_SCRIPT void DeferredContentEditableCountChange(Element*); | ||||||
| 
 | 
 | ||||||
|   enum class EditingState : int8_t { |   enum class EditingState : int8_t { | ||||||
|     eTearingDown = -2, |     eTearingDown = -2, | ||||||
|  | @ -3405,7 +3405,16 @@ class Document : public nsINode, | ||||||
|   // they could want the IncludeChromeOnly::Yes version.
 |   // they could want the IncludeChromeOnly::Yes version.
 | ||||||
|   nsIContent* GetUnretargetedFocusedContent( |   nsIContent* GetUnretargetedFocusedContent( | ||||||
|       IncludeChromeOnly = IncludeChromeOnly::No) const; |       IncludeChromeOnly = IncludeChromeOnly::No) const; | ||||||
|  |   /**
 | ||||||
|  |    * Return true if this document or a subdocument has focus. | ||||||
|  |    */ | ||||||
|   bool HasFocus(ErrorResult& rv) const; |   bool HasFocus(ErrorResult& rv) const; | ||||||
|  | 
 | ||||||
|  |   /**
 | ||||||
|  |    * Return true if this document itself has focus. | ||||||
|  |    */ | ||||||
|  |   bool ThisDocumentHasFocus() const; | ||||||
|  | 
 | ||||||
|   void GetDesignMode(nsAString& aDesignMode); |   void GetDesignMode(nsAString& aDesignMode); | ||||||
|   void SetDesignMode(const nsAString& aDesignMode, |   void SetDesignMode(const nsAString& aDesignMode, | ||||||
|                      nsIPrincipal& aSubjectPrincipal, mozilla::ErrorResult& rv); |                      nsIPrincipal& aSubjectPrincipal, mozilla::ErrorResult& rv); | ||||||
|  | @ -3875,6 +3884,8 @@ class Document : public nsINode, | ||||||
|     return mShouldNotifyFormOrPasswordRemoved; |     return mShouldNotifyFormOrPasswordRemoved; | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  |   HTMLEditor* GetHTMLEditor() const; | ||||||
|  | 
 | ||||||
|   /**
 |   /**
 | ||||||
|    * Localization |    * Localization | ||||||
|    * |    * | ||||||
|  |  | ||||||
|  | @ -2669,7 +2669,11 @@ void nsFocusManager::Focus( | ||||||
|                              relatedTargetElement); |                              relatedTargetElement); | ||||||
|       } |       } | ||||||
|     } else { |     } else { | ||||||
|       IMEStateManager::OnChangeFocus(presContext, nullptr, |       // We should notify IMEStateManager of actual focused element even if it
 | ||||||
|  |       // won't get focus event because the other IMEStateManager users do not
 | ||||||
|  |       // want to depend on this check, but IMEStateManager wants to verify
 | ||||||
|  |       // passed focused element for avoidng to overrride nested calls.
 | ||||||
|  |       IMEStateManager::OnChangeFocus(presContext, elementToFocus, | ||||||
|                                      GetFocusMoveActionCause(aFlags)); |                                      GetFocusMoveActionCause(aFlags)); | ||||||
|       if (!aWindowRaised) { |       if (!aWindowRaised) { | ||||||
|         aWindow->UpdateCommands(u"focus"_ns, nullptr, 0); |         aWindow->UpdateCommands(u"focus"_ns, nullptr, 0); | ||||||
|  |  | ||||||
|  | @ -13,6 +13,7 @@ | ||||||
| #include "mozilla/EditorBase.h" | #include "mozilla/EditorBase.h" | ||||||
| #include "mozilla/EventListenerManager.h" | #include "mozilla/EventListenerManager.h" | ||||||
| #include "mozilla/EventStateManager.h" | #include "mozilla/EventStateManager.h" | ||||||
|  | #include "mozilla/HTMLEditor.h" | ||||||
| #include "mozilla/MouseEvents.h" | #include "mozilla/MouseEvents.h" | ||||||
| #include "mozilla/PresShell.h" | #include "mozilla/PresShell.h" | ||||||
| #include "mozilla/StaticPrefs_dom.h" | #include "mozilla/StaticPrefs_dom.h" | ||||||
|  | @ -100,6 +101,15 @@ void IMEStateManager::Shutdown() { | ||||||
|        "sPendingFocusedBrowserSwitchingData.isSome()=%s", |        "sPendingFocusedBrowserSwitchingData.isSome()=%s", | ||||||
|        sTextCompositions, sTextCompositions ? sTextCompositions->Length() : 0, |        sTextCompositions, sTextCompositions ? sTextCompositions->Length() : 0, | ||||||
|        GetBoolName(sPendingFocusedBrowserSwitchingData.isSome()))); |        GetBoolName(sPendingFocusedBrowserSwitchingData.isSome()))); | ||||||
|  |   MOZ_LOG(sISMLog, LogLevel::Debug, | ||||||
|  |           ("  Shutdown(), sFocusedElement=0x%p, sFocusedPresContext=0x%p, " | ||||||
|  |            "sTextInputHandlingWidget=0x%p, sFocusedIMEWidget=0x%p, " | ||||||
|  |            "sFocusedIMEBrowserParent=0x%p, sActiveInputContextWidget=0x%p, " | ||||||
|  |            "sActiveIMEContentObserver=0x%p", | ||||||
|  |            sFocusedElement.get(), sFocusedPresContext.get(), | ||||||
|  |            sTextInputHandlingWidget, sFocusedIMEWidget, | ||||||
|  |            sFocusedIMEBrowserParent.get(), sActiveInputContextWidget, | ||||||
|  |            sActiveIMEContentObserver.get())); | ||||||
| 
 | 
 | ||||||
|   sPendingFocusedBrowserSwitchingData.reset(); |   sPendingFocusedBrowserSwitchingData.reset(); | ||||||
|   MOZ_ASSERT(!sTextCompositions || !sTextCompositions->Length()); |   MOZ_ASSERT(!sTextCompositions || !sTextCompositions->Length()); | ||||||
|  | @ -400,6 +410,7 @@ nsresult IMEStateManager::OnRemoveContent(nsPresContext& aPresContext, | ||||||
|       !sFocusedElement->IsInclusiveDescendantOf(&aElement)) { |       !sFocusedElement->IsInclusiveDescendantOf(&aElement)) { | ||||||
|     return NS_OK; |     return NS_OK; | ||||||
|   } |   } | ||||||
|  |   MOZ_ASSERT(sFocusedPresContext == &aPresContext); | ||||||
| 
 | 
 | ||||||
|   MOZ_LOG( |   MOZ_LOG( | ||||||
|       sISMLog, LogLevel::Info, |       sISMLog, LogLevel::Info, | ||||||
|  | @ -410,22 +421,33 @@ nsresult IMEStateManager::OnRemoveContent(nsPresContext& aPresContext, | ||||||
| 
 | 
 | ||||||
|   DestroyIMEContentObserver(); |   DestroyIMEContentObserver(); | ||||||
| 
 | 
 | ||||||
|  |   // FYI: Don't clear sTextInputHandlingWidget and sFocusedPresContext because
 | ||||||
|  |   // the window/document keeps having focus.
 | ||||||
|  |   sFocusedElement = nullptr; | ||||||
|  | 
 | ||||||
|   // Current IME transaction should commit
 |   // Current IME transaction should commit
 | ||||||
|   if (sTextInputHandlingWidget) { |   if (!sTextInputHandlingWidget) { | ||||||
|     IMEState newState = GetNewIMEState(*sFocusedPresContext, nullptr); |     return NS_OK; | ||||||
|     InputContextAction action(InputContextAction::CAUSE_UNKNOWN, |  | ||||||
|                               InputContextAction::LOST_FOCUS); |  | ||||||
|     InputContext::Origin origin = |  | ||||||
|         BrowserParent::GetFocused() ? InputContext::ORIGIN_CONTENT : sOrigin; |  | ||||||
|     OwningNonNull<nsIWidget> textInputHandlingWidget = |  | ||||||
|         *sTextInputHandlingWidget; |  | ||||||
|     SetIMEState(newState, &aPresContext, nullptr, textInputHandlingWidget, |  | ||||||
|                 action, origin); |  | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   sTextInputHandlingWidget = nullptr; |   IMEState newState = GetNewIMEState(*sFocusedPresContext, nullptr); | ||||||
|   sFocusedElement = nullptr; |   InputContextAction action(InputContextAction::CAUSE_UNKNOWN, | ||||||
|   sFocusedPresContext = nullptr; |                             InputContextAction::LOST_FOCUS); | ||||||
|  |   InputContext::Origin origin = | ||||||
|  |       BrowserParent::GetFocused() ? InputContext::ORIGIN_CONTENT : sOrigin; | ||||||
|  |   OwningNonNull<nsIWidget> textInputHandlingWidget = *sTextInputHandlingWidget; | ||||||
|  |   SetIMEState(newState, &aPresContext, nullptr, textInputHandlingWidget, action, | ||||||
|  |               origin); | ||||||
|  |   if (sFocusedPresContext != &aPresContext || sFocusedElement) { | ||||||
|  |     return NS_OK;  // Some body must have set focus
 | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   if (IsIMEObserverNeeded(newState)) { | ||||||
|  |     if (RefPtr<HTMLEditor> htmlEditor = | ||||||
|  |             nsContentUtils::GetHTMLEditor(&aPresContext)) { | ||||||
|  |       CreateIMEContentObserver(*htmlEditor, nullptr); | ||||||
|  |     } | ||||||
|  |   } | ||||||
| 
 | 
 | ||||||
|   return NS_OK; |   return NS_OK; | ||||||
| } | } | ||||||
|  | @ -452,6 +474,11 @@ nsresult IMEStateManager::OnChangeFocus(nsPresContext* aPresContext, | ||||||
| nsresult IMEStateManager::OnChangeFocusInternal(nsPresContext* aPresContext, | nsresult IMEStateManager::OnChangeFocusInternal(nsPresContext* aPresContext, | ||||||
|                                                 Element* aElement, |                                                 Element* aElement, | ||||||
|                                                 InputContextAction aAction) { |                                                 InputContextAction aAction) { | ||||||
|  |   NS_ASSERTION(!aElement || aElement->GetPresContext( | ||||||
|  |                                 Element::PresContextFor::eForComposedDoc) == | ||||||
|  |                                 aPresContext, | ||||||
|  |                "aPresContext does not match with one of aElement"); | ||||||
|  | 
 | ||||||
|   bool remoteHasFocus = EventStateManager::IsRemoteTarget(aElement); |   bool remoteHasFocus = EventStateManager::IsRemoteTarget(aElement); | ||||||
|   // If we've handled focused content, we were inactive but now active,
 |   // If we've handled focused content, we were inactive but now active,
 | ||||||
|   // a remote process has focus, and setting focus to same content in the main
 |   // a remote process has focus, and setting focus to same content in the main
 | ||||||
|  | @ -1026,6 +1053,87 @@ void IMEStateManager::OnReFocus(nsPresContext& aPresContext, | ||||||
|               action, sOrigin); |               action, sOrigin); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | // static
 | ||||||
|  | void IMEStateManager::MaybeOnEditableStateDisabled(nsPresContext& aPresContext, | ||||||
|  |                                                    dom::Element* aElement) { | ||||||
|  |   MOZ_LOG( | ||||||
|  |       sISMLog, LogLevel::Info, | ||||||
|  |       ("MaybeOnEditableStateDisabled(aPresContext=0x%p, aElement=0x%p), " | ||||||
|  |        "sFocusedPresContext=0x%p (available: %s), " | ||||||
|  |        "sFocusedElement=0x%p, sTextInputHandlingWidget=0x%p (available: %s), " | ||||||
|  |        "sActiveIMEContentObserver=0x%p, sIsGettingNewIMEState=%s", | ||||||
|  |        &aPresContext, aElement, sFocusedPresContext.get(), | ||||||
|  |        GetBoolName(CanHandleWith(sFocusedPresContext)), sFocusedElement.get(), | ||||||
|  |        sTextInputHandlingWidget, | ||||||
|  |        GetBoolName(sTextInputHandlingWidget && | ||||||
|  |                    !sTextInputHandlingWidget->Destroyed()), | ||||||
|  |        sActiveIMEContentObserver.get(), GetBoolName(sIsGettingNewIMEState))); | ||||||
|  | 
 | ||||||
|  |   if (sIsGettingNewIMEState) { | ||||||
|  |     MOZ_LOG(sISMLog, LogLevel::Debug, | ||||||
|  |             ("  MaybeOnEditableStateDisabled(), " | ||||||
|  |              "does nothing because of called while getting new IME state")); | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   if (&aPresContext != sFocusedPresContext || aElement != sFocusedElement) { | ||||||
|  |     MOZ_LOG(sISMLog, LogLevel::Debug, | ||||||
|  |             ("  MaybeOnEditableStateDisabled(), " | ||||||
|  |              "does nothing because of another element already has focus")); | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   if (NS_WARN_IF(!sTextInputHandlingWidget) || | ||||||
|  |       NS_WARN_IF(sTextInputHandlingWidget->Destroyed())) { | ||||||
|  |     MOZ_LOG(sISMLog, LogLevel::Error, | ||||||
|  |             ("  MaybeOnEditableStateDisabled(), FAILED due to " | ||||||
|  |              "the widget for the managing the nsPresContext has gone")); | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   const OwningNonNull<nsIWidget> textInputHandlingWidget = | ||||||
|  |       *sTextInputHandlingWidget; | ||||||
|  | 
 | ||||||
|  | #ifdef DEBUG | ||||||
|  |   { | ||||||
|  |     nsCOMPtr<nsIWidget> currentTextInputHandlingWidget = | ||||||
|  |         aPresContext.GetTextInputHandlingWidget(); | ||||||
|  |     MOZ_ASSERT(!currentTextInputHandlingWidget || | ||||||
|  |                currentTextInputHandlingWidget == textInputHandlingWidget); | ||||||
|  |   } | ||||||
|  | #endif  // DEBUG
 | ||||||
|  | 
 | ||||||
|  |   const IMEState newIMEState = GetNewIMEState(aPresContext, aElement); | ||||||
|  |   // If IME state becomes editable, HTMLEditor should also be initialized with
 | ||||||
|  |   // same path as it gets focus.  Therefore, IMEStateManager::UpdateIMEState
 | ||||||
|  |   // should be called by HTMLEditor instead.
 | ||||||
|  |   if (newIMEState.IsEditable()) { | ||||||
|  |     MOZ_LOG(sISMLog, LogLevel::Debug, | ||||||
|  |             ("  MaybeOnEditableStateDisabled(), " | ||||||
|  |              "does nothing because IME state does not become disabled")); | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   // Otherwise, disable IME on the widget and destroy active IMEContentObserver
 | ||||||
|  |   // if there is.
 | ||||||
|  |   const InputContext inputContext = textInputHandlingWidget->GetInputContext(); | ||||||
|  |   if (inputContext.mIMEState.mEnabled == newIMEState.mEnabled) { | ||||||
|  |     MOZ_LOG(sISMLog, LogLevel::Debug, | ||||||
|  |             ("  MaybeOnEditableStateDisabled(), " | ||||||
|  |              "does nothing because IME state is not changed")); | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   if (sActiveIMEContentObserver) { | ||||||
|  |     DestroyIMEContentObserver(); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   InputContextAction action(InputContextAction::CAUSE_UNKNOWN, | ||||||
|  |                             InputContextAction::FOCUS_NOT_CHANGED); | ||||||
|  |   SetIMEState(newIMEState, &aPresContext, aElement, textInputHandlingWidget, | ||||||
|  |               action, sOrigin); | ||||||
|  | } | ||||||
|  | 
 | ||||||
| // static
 | // static
 | ||||||
| void IMEStateManager::UpdateIMEState(const IMEState& aNewIMEState, | void IMEStateManager::UpdateIMEState(const IMEState& aNewIMEState, | ||||||
|                                      Element* aElement, EditorBase& aEditorBase, |                                      Element* aElement, EditorBase& aEditorBase, | ||||||
|  | @ -1059,7 +1167,10 @@ void IMEStateManager::UpdateIMEState(const IMEState& aNewIMEState, | ||||||
|     return; |     return; | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   const RefPtr<nsPresContext> presContext = presShell->GetPresContext(); |   const RefPtr<nsPresContext> presContext = | ||||||
|  |       aElement | ||||||
|  |           ? aElement->GetPresContext(Element::PresContextFor::eForComposedDoc) | ||||||
|  |           : aEditorBase.GetPresContext(); | ||||||
|   if (NS_WARN_IF(!presContext)) { |   if (NS_WARN_IF(!presContext)) { | ||||||
|     MOZ_LOG(sISMLog, LogLevel::Error, |     MOZ_LOG(sISMLog, LogLevel::Error, | ||||||
|             ("  UpdateIMEState(), FAILED due to " |             ("  UpdateIMEState(), FAILED due to " | ||||||
|  | @ -1192,8 +1303,15 @@ void IMEStateManager::UpdateIMEState(const IMEState& aNewIMEState, | ||||||
|   if (updateIMEState) { |   if (updateIMEState) { | ||||||
|     InputContextAction action(InputContextAction::CAUSE_UNKNOWN, |     InputContextAction action(InputContextAction::CAUSE_UNKNOWN, | ||||||
|                               InputContextAction::FOCUS_NOT_CHANGED); |                               InputContextAction::FOCUS_NOT_CHANGED); | ||||||
|     SetIMEState(aNewIMEState, presContext, aElement, textInputHandlingWidget, |     RefPtr<nsPresContext> editorPresContext = aEditorBase.GetPresContext(); | ||||||
|                 action, sOrigin); |     if (NS_WARN_IF(!editorPresContext)) { | ||||||
|  |       MOZ_LOG(sISMLog, LogLevel::Error, | ||||||
|  |               ("  UpdateIMEState(), nsPresContext for editor has already been " | ||||||
|  |                "lost")); | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  |     SetIMEState(aNewIMEState, editorPresContext, aElement, | ||||||
|  |                 textInputHandlingWidget, action, sOrigin); | ||||||
|     if (NS_WARN_IF(textInputHandlingWidget->Destroyed()) || |     if (NS_WARN_IF(textInputHandlingWidget->Destroyed()) || | ||||||
|         NS_WARN_IF(sTextInputHandlingWidget != textInputHandlingWidget)) { |         NS_WARN_IF(sTextInputHandlingWidget != textInputHandlingWidget)) { | ||||||
|       MOZ_LOG(sISMLog, LogLevel::Error, |       MOZ_LOG(sISMLog, LogLevel::Error, | ||||||
|  | @ -2145,18 +2263,14 @@ bool IMEStateManager::IsIMEObserverNeeded(const IMEState& aState) { | ||||||
| 
 | 
 | ||||||
| // static
 | // static
 | ||||||
| void IMEStateManager::DestroyIMEContentObserver() { | void IMEStateManager::DestroyIMEContentObserver() { | ||||||
|   MOZ_LOG(sISMLog, LogLevel::Info, |  | ||||||
|           ("DestroyIMEContentObserver(), sActiveIMEContentObserver=0x%p", |  | ||||||
|            sActiveIMEContentObserver.get())); |  | ||||||
| 
 |  | ||||||
|   if (!sActiveIMEContentObserver) { |   if (!sActiveIMEContentObserver) { | ||||||
|     MOZ_LOG(sISMLog, LogLevel::Debug, |     MOZ_LOG(sISMLog, LogLevel::Verbose, | ||||||
|             ("  DestroyIMEContentObserver() does nothing")); |             ("DestroyIMEContentObserver() does nothing")); | ||||||
|     return; |     return; | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   MOZ_LOG(sISMLog, LogLevel::Debug, |   MOZ_LOG(sISMLog, LogLevel::Info, | ||||||
|           ("  DestroyIMEContentObserver(), destroying " |           ("DestroyIMEContentObserver(), destroying " | ||||||
|            "the active IMEContentObserver...")); |            "the active IMEContentObserver...")); | ||||||
|   RefPtr<IMEContentObserver> tsm = sActiveIMEContentObserver.get(); |   RefPtr<IMEContentObserver> tsm = sActiveIMEContentObserver.get(); | ||||||
|   sActiveIMEContentObserver = nullptr; |   sActiveIMEContentObserver = nullptr; | ||||||
|  |  | ||||||
|  | @ -158,7 +158,11 @@ class IMEStateManager { | ||||||
|   /**
 |   /**
 | ||||||
|    * OnChangeFocus() should be called when focused content is changed or |    * OnChangeFocus() should be called when focused content is changed or | ||||||
|    * IME enabled state is changed.  If nobody has focus, set both aPresContext |    * IME enabled state is changed.  If nobody has focus, set both aPresContext | ||||||
|    * and aContent nullptr.  E.g., all windows are deactivated. |    * and aContent nullptr.  E.g., all windows are deactivated.  Otherwise, | ||||||
|  |    * set focused element (even if it won't receive `focus`event) and | ||||||
|  |    * corresponding nsPresContext for it.  Then, IMEStateManager can avoid | ||||||
|  |    * handling delayed notifications from the others with verifying the | ||||||
|  |    * focused element. | ||||||
|    */ |    */ | ||||||
|   MOZ_CAN_RUN_SCRIPT static nsresult OnChangeFocus( |   MOZ_CAN_RUN_SCRIPT static nsresult OnChangeFocus( | ||||||
|       nsPresContext* aPresContext, dom::Element* aElement, |       nsPresContext* aPresContext, dom::Element* aElement, | ||||||
|  | @ -235,6 +239,12 @@ class IMEStateManager { | ||||||
|   MOZ_CAN_RUN_SCRIPT static void OnReFocus(nsPresContext& aPresContext, |   MOZ_CAN_RUN_SCRIPT static void OnReFocus(nsPresContext& aPresContext, | ||||||
|                                            dom::Element& aElement); |                                            dom::Element& aElement); | ||||||
| 
 | 
 | ||||||
|  |   // This method is called when designMode is set to "off" or an editing host
 | ||||||
|  |   // becomes not editable due to removing `contenteditable` attribute or setting
 | ||||||
|  |   // it to "false".
 | ||||||
|  |   MOZ_CAN_RUN_SCRIPT static void MaybeOnEditableStateDisabled( | ||||||
|  |       nsPresContext& aPresContext, dom::Element* aElement); | ||||||
|  | 
 | ||||||
|   /**
 |   /**
 | ||||||
|    * All composition events must be dispatched via DispatchCompositionEvent() |    * All composition events must be dispatched via DispatchCompositionEvent() | ||||||
|    * for storing the composition target and ensuring a set of composition |    * for storing the composition target and ensuring a set of composition | ||||||
|  |  | ||||||
|  | @ -26,8 +26,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=1128787 | ||||||
|       SimpleTest.executeSoon(function () { |       SimpleTest.executeSoon(function () { | ||||||
|         document.designMode = "off"; |         document.designMode = "off"; | ||||||
| 
 | 
 | ||||||
|         // XXX Should be fixed. |         is(utils.IMEStatus, utils.IME_STATUS_DISABLED, "IME should be disabled"); | ||||||
|         todo_is(utils.IMEStatus, utils.IME_STATUS_DISABLED, "IME should be disabled"); |  | ||||||
| 
 | 
 | ||||||
|         SimpleTest.finish(); |         SimpleTest.finish(); | ||||||
|       }); |       }); | ||||||
|  |  | ||||||
|  | @ -29,7 +29,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=1128787 | ||||||
|       is(document.designMode, "off", |       is(document.designMode, "off", | ||||||
|          "designMode should become \"off\" even if it's reset by the blur event handler caused by enabling designMode"); |          "designMode should become \"off\" even if it's reset by the blur event handler caused by enabling designMode"); | ||||||
| 
 | 
 | ||||||
|       todo_is(utils.IMEStatus, utils.IME_STATUS_DISABLED, "IME should be disabled"); |       is(utils.IMEStatus, utils.IME_STATUS_DISABLED, "IME should be disabled"); | ||||||
|       SimpleTest.finish(); |       SimpleTest.finish(); | ||||||
|     }, {once: true}); |     }, {once: true}); | ||||||
|     document.designMode = "on"; |     document.designMode = "on"; | ||||||
|  |  | ||||||
|  | @ -19,6 +19,8 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=687787 | ||||||
| <pre id="test"> | <pre id="test"> | ||||||
| <script class="testbody" type="text/javascript"> | <script class="testbody" type="text/javascript"> | ||||||
| 
 | 
 | ||||||
|  | SimpleTest.expectAssertions(1); // in IMEStateManager::OnChangeFocusInternal, bug 1820470 | ||||||
|  | 
 | ||||||
| var eventStack = []; | var eventStack = []; | ||||||
| 
 | 
 | ||||||
| function _callback(e){ | function _callback(e){ | ||||||
|  |  | ||||||
|  | @ -5613,11 +5613,14 @@ nsresult EditorBase::OnFocus(const nsINode& aOriginalEventTargetNode) { | ||||||
|     return NS_ERROR_EDITOR_DESTROYED; |     return NS_ERROR_EDITOR_DESTROYED; | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   RefPtr<nsPresContext> presContext = GetPresContext(); |   const RefPtr<Element> focusedElement = GetFocusedElement(); | ||||||
|  |   RefPtr<nsPresContext> presContext = | ||||||
|  |       focusedElement ? focusedElement->GetPresContext( | ||||||
|  |                            Element::PresContextFor::eForComposedDoc) | ||||||
|  |                      : GetPresContext(); | ||||||
|   if (NS_WARN_IF(!presContext)) { |   if (NS_WARN_IF(!presContext)) { | ||||||
|     return NS_ERROR_FAILURE; |     return NS_ERROR_FAILURE; | ||||||
|   } |   } | ||||||
|   RefPtr<Element> focusedElement = GetFocusedElement(); |  | ||||||
|   IMEStateManager::OnFocusInEditor(*presContext, focusedElement, *this); |   IMEStateManager::OnFocusInEditor(*presContext, focusedElement, *this); | ||||||
| 
 | 
 | ||||||
|   return NS_OK; |   return NS_OK; | ||||||
|  |  | ||||||
|  | @ -221,6 +221,14 @@ class EditorBase : public nsIEditor, | ||||||
|     return selection; |     return selection; | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  |   /**
 | ||||||
|  |    * @return Ancestor limiter of normal selection | ||||||
|  |    */ | ||||||
|  |   [[nodiscard]] nsIContent* GetSelectionAncestorLimiter() const { | ||||||
|  |     Selection* selection = GetSelection(SelectionType::eNormal); | ||||||
|  |     return selection ? selection->GetAncestorLimiter() : nullptr; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|   /**
 |   /**
 | ||||||
|    * Fast non-refcounting editor root element accessor |    * Fast non-refcounting editor root element accessor | ||||||
|    */ |    */ | ||||||
|  |  | ||||||
|  | @ -29,7 +29,8 @@ | ||||||
| #include "mozilla/ContentIterator.h" | #include "mozilla/ContentIterator.h" | ||||||
| #include "mozilla/DebugOnly.h" | #include "mozilla/DebugOnly.h" | ||||||
| #include "mozilla/EditorForwards.h" | #include "mozilla/EditorForwards.h" | ||||||
| #include "mozilla/Encoding.h"      // for Encoding
 | #include "mozilla/Encoding.h"  // for Encoding
 | ||||||
|  | #include "mozilla/IMEStateManager.h" | ||||||
| #include "mozilla/IntegerRange.h"  // for IntegerRange
 | #include "mozilla/IntegerRange.h"  // for IntegerRange
 | ||||||
| #include "mozilla/InternalMutationEvent.h" | #include "mozilla/InternalMutationEvent.h" | ||||||
| #include "mozilla/mozInlineSpellChecker.h" | #include "mozilla/mozInlineSpellChecker.h" | ||||||
|  | @ -737,6 +738,43 @@ void HTMLEditor::UpdateRootElement() { | ||||||
|   } |   } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | nsresult HTMLEditor::FocusedElementOrDocumentBecomesEditable( | ||||||
|  |     Document& aDocument, Element* aElement) { | ||||||
|  |   // If we should've already handled focus event, selection limiter should not
 | ||||||
|  |   // be set.  Therefore, if it's set, we should do nothing here.
 | ||||||
|  |   if (GetSelectionAncestorLimiter()) { | ||||||
|  |     return NS_OK; | ||||||
|  |   } | ||||||
|  |   // If we should be in the design mode, we want to handle focus event fired
 | ||||||
|  |   // on the document node.  Therefore, we should emulate it here.
 | ||||||
|  |   if (IsInDesignMode() && (!aElement || aElement->IsInDesignMode())) { | ||||||
|  |     MOZ_ASSERT(&aDocument == GetDocument()); | ||||||
|  |     nsresult rv = OnFocus(aDocument); | ||||||
|  |     NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "HTMLEditor::OnFocus() failed"); | ||||||
|  |     return rv; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   if (NS_WARN_IF(!aElement)) { | ||||||
|  |     return NS_ERROR_INVALID_ARG; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   // Otherwise, we should've already handled focus event on the element,
 | ||||||
|  |   // therefore, we need to emulate it here.
 | ||||||
|  |   MOZ_ASSERT(nsFocusManager::GetFocusManager()->GetFocusedElement() == | ||||||
|  |              aElement); | ||||||
|  |   nsresult rv = OnFocus(*aElement); | ||||||
|  |   NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "HTMLEditor::OnFocus() failed"); | ||||||
|  | 
 | ||||||
|  |   // Note that we don't need to call
 | ||||||
|  |   // IMEStateManager::MaybeOnEditableStateDisabled here because
 | ||||||
|  |   // EditorBase::OnFocus must have already been called IMEStateManager::OnFocus
 | ||||||
|  |   // if succeeded. And perhaps, it's okay that IME is not enabled when
 | ||||||
|  |   // HTMLEditor fails to start handling since nobody can handle composition
 | ||||||
|  |   // events anyway...
 | ||||||
|  | 
 | ||||||
|  |   return rv; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| nsresult HTMLEditor::OnFocus(const nsINode& aOriginalEventTargetNode) { | nsresult HTMLEditor::OnFocus(const nsINode& aOriginalEventTargetNode) { | ||||||
|   // Before doing anything, we should check whether the original target is still
 |   // Before doing anything, we should check whether the original target is still
 | ||||||
|   // valid focus event target because it may have already lost focus.
 |   // valid focus event target because it may have already lost focus.
 | ||||||
|  | @ -752,6 +790,66 @@ nsresult HTMLEditor::OnFocus(const nsINode& aOriginalEventTargetNode) { | ||||||
|   return EditorBase::OnFocus(aOriginalEventTargetNode); |   return EditorBase::OnFocus(aOriginalEventTargetNode); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | nsresult HTMLEditor::FocusedElementOrDocumentBecomesNotEditable( | ||||||
|  |     HTMLEditor* aHTMLEditor, Document& aDocument, Element* aElement) { | ||||||
|  |   nsresult rv = [&]() MOZ_CAN_RUN_SCRIPT { | ||||||
|  |     // If HTMLEditor has not been created yet, we just need to adjust
 | ||||||
|  |     // IMEStateManager.  So, don't return error.
 | ||||||
|  |     if (!aHTMLEditor) { | ||||||
|  |       return NS_OK; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     nsIContent* const limiter = aHTMLEditor->GetSelectionAncestorLimiter(); | ||||||
|  |     // The HTMLEditor has not received `focus` event so that it does not need to
 | ||||||
|  |     // emulate `blur`.
 | ||||||
|  |     if (!limiter) { | ||||||
|  |       return NS_OK; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     // If we should be in the design mode, we should treat it as blur from
 | ||||||
|  |     // the document node.
 | ||||||
|  |     if (aHTMLEditor->IsInDesignMode() && | ||||||
|  |         (!aElement || aElement->IsInDesignMode())) { | ||||||
|  |       MOZ_ASSERT(aHTMLEditor->GetDocument() == &aDocument); | ||||||
|  |       nsresult rv = aHTMLEditor->OnBlur(&aDocument); | ||||||
|  |       NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "HTMLEditor::OnBlur() failed"); | ||||||
|  |       return rv; | ||||||
|  |     } | ||||||
|  |     // If the HTMLEditor has already received `focus` event for different
 | ||||||
|  |     // element than aElement, we'll receive `blur` event later so that we need
 | ||||||
|  |     // to do nothing here.
 | ||||||
|  |     if (aElement != limiter) { | ||||||
|  |       return NS_OK; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     // Otherwise, even though the limiter keeps having focus but becomes not
 | ||||||
|  |     // editable.  From HTMLEditor point of view, this is equivalent to the
 | ||||||
|  |     // elements gets blurred.  Therefore, we should treat it as losing
 | ||||||
|  |     // focus.
 | ||||||
|  |     nsresult rv = aHTMLEditor->OnBlur(aElement); | ||||||
|  |     NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "HTMLEditor::OnBlur() failed"); | ||||||
|  |     return rv; | ||||||
|  |   }(); | ||||||
|  | 
 | ||||||
|  |   // If the element becomes not editable without focus change, IMEStateManager
 | ||||||
|  |   // does not have a chance to disable IME.  Therefore, (even if we fail to
 | ||||||
|  |   // handle the emulated blur above,) we should notify IMEStateManager of the
 | ||||||
|  |   // editing state change.
 | ||||||
|  |   RefPtr<Element> focusedElement = aElement ? aElement | ||||||
|  |                                    : aHTMLEditor | ||||||
|  |                                        ? aHTMLEditor->GetFocusedElement() | ||||||
|  |                                        : nullptr; | ||||||
|  |   RefPtr<nsPresContext> presContext = | ||||||
|  |       focusedElement ? focusedElement->GetPresContext( | ||||||
|  |                            Element::PresContextFor::eForComposedDoc) | ||||||
|  |                      : aDocument.GetPresContext(); | ||||||
|  |   if (presContext) { | ||||||
|  |     IMEStateManager::MaybeOnEditableStateDisabled(*presContext, focusedElement); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   return rv; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| nsresult HTMLEditor::OnBlur(const EventTarget* aEventTarget) { | nsresult HTMLEditor::OnBlur(const EventTarget* aEventTarget) { | ||||||
|   // check if something else is focused. If another element is focused, then
 |   // check if something else is focused. If another element is focused, then
 | ||||||
|   // we should not change the selection.
 |   // we should not change the selection.
 | ||||||
|  | @ -769,8 +867,6 @@ nsresult HTMLEditor::OnBlur(const EventTarget* aEventTarget) { | ||||||
|   // If it's in the designMode, and blur occurs, the target must be the
 |   // If it's in the designMode, and blur occurs, the target must be the
 | ||||||
|   // document node.  If a blur event is fired and the target is an element, it
 |   // document node.  If a blur event is fired and the target is an element, it
 | ||||||
|   // must be delayed blur event at initializing the `HTMLEditor`.
 |   // must be delayed blur event at initializing the `HTMLEditor`.
 | ||||||
|   // TODO: Add automated tests for checking the case that the target node
 |  | ||||||
|   //       is in a shadow DOM tree whose host is in design mode.
 |  | ||||||
|   if (IsInDesignMode() && Element::FromEventTargetOrNull(aEventTarget)) { |   if (IsInDesignMode() && Element::FromEventTargetOrNull(aEventTarget)) { | ||||||
|     return NS_OK; |     return NS_OK; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  | @ -207,6 +207,22 @@ class HTMLEditor final : public EditorBase, | ||||||
|   OnFocus(const nsINode& aOriginalEventTargetNode) final; |   OnFocus(const nsINode& aOriginalEventTargetNode) final; | ||||||
|   nsresult OnBlur(const dom::EventTarget* aEventTarget) final; |   nsresult OnBlur(const dom::EventTarget* aEventTarget) final; | ||||||
| 
 | 
 | ||||||
|  |   /**
 | ||||||
|  |    * Called when aDocument or aElement becomes editable without focus change. | ||||||
|  |    * E.g., when the design mode is enabled or the contenteditable attribute | ||||||
|  |    * is set to the focused element. | ||||||
|  |    */ | ||||||
|  |   MOZ_CAN_RUN_SCRIPT nsresult FocusedElementOrDocumentBecomesEditable( | ||||||
|  |       Document& aDocument, Element* aElement); | ||||||
|  | 
 | ||||||
|  |   /**
 | ||||||
|  |    * Called when aDocument or aElement becomes not editable without focus | ||||||
|  |    * change. E.g., when the design mode ends or the contenteditable attribute is | ||||||
|  |    * removed or set to "false". | ||||||
|  |    */ | ||||||
|  |   MOZ_CAN_RUN_SCRIPT static nsresult FocusedElementOrDocumentBecomesNotEditable( | ||||||
|  |       HTMLEditor* aHTMLEditor, Document& aDocument, Element* aElement); | ||||||
|  | 
 | ||||||
|   /**
 |   /**
 | ||||||
|    * GetBackgroundColorState() returns what the background color of the |    * GetBackgroundColorState() returns what the background color of the | ||||||
|    * selection. |    * selection. | ||||||
|  |  | ||||||
|  | @ -7,6 +7,9 @@ skip-if = | ||||||
|   os == "win" && bits == 32 && !debug # Bug 1759422 |   os == "win" && bits == 32 && !debug # Bug 1759422 | ||||||
|   os == "linux" # Bug 1792749 |   os == "linux" # Bug 1792749 | ||||||
| [browser_test_ContentCache.js] | [browser_test_ContentCache.js] | ||||||
|  | [browser_test_ime_state_on_editable_state_change_in_remote_content.js] | ||||||
|  | support-files = | ||||||
|  |   ../file_ime_state_test_helper.js | ||||||
| [browser_test_ime_state_on_focus_move_in_remote_content.js] | [browser_test_ime_state_on_focus_move_in_remote_content.js] | ||||||
| support-files = | support-files = | ||||||
|   file_ime_state_tests.html |   file_ime_state_tests.html | ||||||
|  |  | ||||||
|  | @ -107,11 +107,6 @@ add_task(async function() { | ||||||
|       await runIMEStateOnFocusMoveTests('in designMode="on"'); |       await runIMEStateOnFocusMoveTests('in designMode="on"'); | ||||||
|       await SpecialPowers.spawn(browser, [], async () => { |       await SpecialPowers.spawn(browser, [], async () => { | ||||||
|         content.document.designMode = "off"; |         content.document.designMode = "off"; | ||||||
|         // Hack for passing the first test.
 |  | ||||||
|         const input = content.document.createElement("input"); |  | ||||||
|         content.document.body.appendChild(input); |  | ||||||
|         input.focus(); |  | ||||||
|         input.remove(); |  | ||||||
|       }); |       }); | ||||||
|       await runIMEStateOnFocusMoveTests('in designMode="off"'); |       await runIMEStateOnFocusMoveTests('in designMode="off"'); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  | @ -0,0 +1,298 @@ | ||||||
|  | /* Any copyright is dedicated to the Public Domain. | ||||||
|  |    http://creativecommons.org/publicdomain/zero/1.0/ */
 | ||||||
|  | 
 | ||||||
|  | "use strict"; | ||||||
|  | 
 | ||||||
|  | /* import-globals-from ../file_ime_state_test_helper.js */ | ||||||
|  | 
 | ||||||
|  | Services.scriptloader.loadSubScript( | ||||||
|  |   "chrome://mochitests/content/browser/widget/tests/browser/file_ime_state_test_helper.js", | ||||||
|  |   this | ||||||
|  | ); | ||||||
|  | add_task(async function() { | ||||||
|  |   await BrowserTestUtils.withNewTab( | ||||||
|  |     "https://example.com/browser/toolkit/content/tests/browser/file_empty.html", | ||||||
|  |     async function(browser) { | ||||||
|  |       const tipWrapper = new TIPWrapper(window); | ||||||
|  |       ok( | ||||||
|  |         tipWrapper.isAvailable(), | ||||||
|  |         "TextInputProcessor should've been initialized" | ||||||
|  |       ); | ||||||
|  | 
 | ||||||
|  |       await SpecialPowers.spawn(browser, [], () => { | ||||||
|  |         content.wrappedJSObject.waitForIMEContentObserverSendingNotifications = () => { | ||||||
|  |           return new content.window.Promise(resolve => | ||||||
|  |             content.window.requestAnimationFrame(() => | ||||||
|  |               content.window.requestAnimationFrame(resolve) | ||||||
|  |             ) | ||||||
|  |           ); | ||||||
|  |         }; | ||||||
|  |         content.wrappedJSObject.resetIMEStateWithFocusMove = () => { | ||||||
|  |           const input = content.document.createElement("input"); | ||||||
|  |           content.document.body.appendChild(input); | ||||||
|  |           input.focus(); | ||||||
|  |           input.remove(); | ||||||
|  |           return content.wrappedJSObject.waitForIMEContentObserverSendingNotifications(); | ||||||
|  |         }; | ||||||
|  |         content.document.body.innerHTML = "<div></div>"; | ||||||
|  |       }); | ||||||
|  | 
 | ||||||
|  |       function resetIMEStateWithFocusMove() { | ||||||
|  |         return SpecialPowers.spawn(browser, [], () => { | ||||||
|  |           const input = content.document.createElement("input"); | ||||||
|  |           content.document.body.appendChild(input); | ||||||
|  |           input.focus(); | ||||||
|  |           input.remove(); | ||||||
|  |           return content.wrappedJSObject.waitForIMEContentObserverSendingNotifications(); | ||||||
|  |         }); | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       await (async function test_setting_contenteditable_of_focused_div() { | ||||||
|  |         await SpecialPowers.spawn(browser, [], () => { | ||||||
|  |           const div = content.document.querySelector("div"); | ||||||
|  |           div.setAttribute("tabindex", "0"); | ||||||
|  |           div.focus(); | ||||||
|  |           return content.wrappedJSObject.waitForIMEContentObserverSendingNotifications(); | ||||||
|  |         }); | ||||||
|  |         is( | ||||||
|  |           window.windowUtils.IMEStatus, | ||||||
|  |           Ci.nsIDOMWindowUtils.IME_STATUS_DISABLED, | ||||||
|  |           "test_setting_contenteditable_of_focused_div: IME should be disabled when non-editable <div> has focus" | ||||||
|  |         ); | ||||||
|  |         await SpecialPowers.spawn(browser, [], () => { | ||||||
|  |           content.document | ||||||
|  |             .querySelector("div") | ||||||
|  |             .setAttribute("contenteditable", ""); | ||||||
|  |           return content.wrappedJSObject.waitForIMEContentObserverSendingNotifications(); | ||||||
|  |         }); | ||||||
|  |         is( | ||||||
|  |           window.windowUtils.IMEStatus, | ||||||
|  |           Ci.nsIDOMWindowUtils.IME_STATUS_ENABLED, | ||||||
|  |           "test_setting_contenteditable_of_focused_div: IME should be enabled when contenteditable of focused <div> is set" | ||||||
|  |         ); | ||||||
|  |         ok( | ||||||
|  |           tipWrapper.IMEHasFocus, | ||||||
|  |           "test_setting_contenteditable_of_focused_div: IME should have focus when contenteditable of focused <div> is set" | ||||||
|  |         ); | ||||||
|  |         await SpecialPowers.spawn(browser, [], () => { | ||||||
|  |           content.document | ||||||
|  |             .querySelector("div") | ||||||
|  |             .removeAttribute("contenteditable"); | ||||||
|  |           return content.wrappedJSObject.waitForIMEContentObserverSendingNotifications(); | ||||||
|  |         }); | ||||||
|  |         is( | ||||||
|  |           window.windowUtils.IMEStatus, | ||||||
|  |           Ci.nsIDOMWindowUtils.IME_STATUS_DISABLED, | ||||||
|  |           "test_setting_contenteditable_of_focused_div: IME should be disabled when contenteditable of focused <div> is removed" | ||||||
|  |         ); | ||||||
|  |         ok( | ||||||
|  |           !tipWrapper.IMEHasFocus, | ||||||
|  |           "test_setting_contenteditable_of_focused_div: IME should not have focus when contenteditable of focused <div> is removed" | ||||||
|  |         ); | ||||||
|  |         await SpecialPowers.spawn(browser, [], () => { | ||||||
|  |           content.document.querySelector("div").removeAttribute("tabindex"); | ||||||
|  |         }); | ||||||
|  |       })(); | ||||||
|  | 
 | ||||||
|  |       await resetIMEStateWithFocusMove(); | ||||||
|  | 
 | ||||||
|  |       await (async function test_removing_contenteditable_of_non_last_editable_div() { | ||||||
|  |         await SpecialPowers.spawn(browser, [], async () => { | ||||||
|  |           const div = content.document.querySelector("div"); | ||||||
|  |           div.setAttribute("tabindex", "0"); | ||||||
|  |           div.setAttribute("contenteditable", ""); | ||||||
|  |           const anotherEditableDiv = content.document.createElement("div"); | ||||||
|  |           anotherEditableDiv.setAttribute("contenteditable", ""); | ||||||
|  |           div.parentElement.appendChild(anotherEditableDiv); | ||||||
|  |           div.focus(); | ||||||
|  |           await content.wrappedJSObject.waitForIMEContentObserverSendingNotifications(); | ||||||
|  |           div.removeAttribute("contenteditable"); | ||||||
|  |           return content.wrappedJSObject.waitForIMEContentObserverSendingNotifications(); | ||||||
|  |         }); | ||||||
|  |         is( | ||||||
|  |           window.windowUtils.IMEStatus, | ||||||
|  |           Ci.nsIDOMWindowUtils.IME_STATUS_DISABLED, | ||||||
|  |           "test_removing_contenteditable_of_non_last_editable_div: IME should be disabled when contenteditable of focused <div> is removed" | ||||||
|  |         ); | ||||||
|  |         ok( | ||||||
|  |           !tipWrapper.IMEHasFocus, | ||||||
|  |           "test_removing_contenteditable_of_non_last_editable_div: IME should not have focus when contenteditable of focused <div> is removed" | ||||||
|  |         ); | ||||||
|  |         await SpecialPowers.spawn(browser, [], () => { | ||||||
|  |           const divs = content.document.querySelectorAll("div"); | ||||||
|  |           divs[1].remove(); | ||||||
|  |           divs[0].removeAttribute("tabindex"); | ||||||
|  |         }); | ||||||
|  |       })(); | ||||||
|  | 
 | ||||||
|  |       await resetIMEStateWithFocusMove(); | ||||||
|  | 
 | ||||||
|  |       await (async function test_setting_designMode() { | ||||||
|  |         await SpecialPowers.spawn(browser, [], () => { | ||||||
|  |           content.window.focus(); | ||||||
|  |           content.document.designMode = "on"; | ||||||
|  |           return content.wrappedJSObject.waitForIMEContentObserverSendingNotifications(); | ||||||
|  |         }); | ||||||
|  |         is( | ||||||
|  |           window.windowUtils.IMEStatus, | ||||||
|  |           Ci.nsIDOMWindowUtils.IME_STATUS_ENABLED, | ||||||
|  |           'test_setting_designMode: IME should be enabled when designMode is set to "on"' | ||||||
|  |         ); | ||||||
|  |         ok( | ||||||
|  |           tipWrapper.IMEHasFocus, | ||||||
|  |           'test_setting_designMode: IME should have focus when designMode is set to "on"' | ||||||
|  |         ); | ||||||
|  |         await SpecialPowers.spawn(browser, [], () => { | ||||||
|  |           content.document.designMode = "off"; | ||||||
|  |           return content.wrappedJSObject.waitForIMEContentObserverSendingNotifications(); | ||||||
|  |         }); | ||||||
|  |         is( | ||||||
|  |           window.windowUtils.IMEStatus, | ||||||
|  |           Ci.nsIDOMWindowUtils.IME_STATUS_DISABLED, | ||||||
|  |           'test_setting_designMode: IME should be disabled when designMode is set to "off"' | ||||||
|  |         ); | ||||||
|  |         ok( | ||||||
|  |           !tipWrapper.IMEHasFocus, | ||||||
|  |           'test_setting_designMode: IME should not have focus when designMode is set to "off"' | ||||||
|  |         ); | ||||||
|  |       })(); | ||||||
|  | 
 | ||||||
|  |       await resetIMEStateWithFocusMove(); | ||||||
|  | 
 | ||||||
|  |       async function test_setting_content_editable_of_body_when_shadow_DOM_has_focus( | ||||||
|  |         aMode | ||||||
|  |       ) { | ||||||
|  |         await SpecialPowers.spawn(browser, [aMode], mode => { | ||||||
|  |           const div = content.document.querySelector("div"); | ||||||
|  |           const shadow = div.attachShadow({ mode }); | ||||||
|  |           content.wrappedJSObject.divInShadow = content.document.createElement( | ||||||
|  |             "div" | ||||||
|  |           ); | ||||||
|  |           content.wrappedJSObject.divInShadow.setAttribute("tabindex", "0"); | ||||||
|  |           shadow.appendChild(content.wrappedJSObject.divInShadow); | ||||||
|  |           content.wrappedJSObject.divInShadow.focus(); | ||||||
|  |           return content.wrappedJSObject.waitForIMEContentObserverSendingNotifications(); | ||||||
|  |         }); | ||||||
|  |         is( | ||||||
|  |           window.windowUtils.IMEStatus, | ||||||
|  |           Ci.nsIDOMWindowUtils.IME_STATUS_DISABLED, | ||||||
|  |           `test_setting_content_editable_of_body_when_shadow_DOM_has_focus(${aMode}): IME should be disabled when non-editable <div> in a shadow DOM has focus` | ||||||
|  |         ); | ||||||
|  |         await SpecialPowers.spawn(browser, [], () => { | ||||||
|  |           content.document.body.setAttribute("contenteditable", ""); | ||||||
|  |           return content.wrappedJSObject.waitForIMEContentObserverSendingNotifications(); | ||||||
|  |         }); | ||||||
|  |         // todo_is because of bug 1807597.  Gecko does not update focus when focused
 | ||||||
|  |         // element becomes an editable child.  Therefore, cannot initialize
 | ||||||
|  |         // HTMLEditor with the new editing host.
 | ||||||
|  |         todo_is( | ||||||
|  |           window.windowUtils.IMEStatus, | ||||||
|  |           Ci.nsIDOMWindowUtils.IME_STATUS_ENABLED, | ||||||
|  |           `test_setting_content_editable_of_body_when_shadow_DOM_has_focus(${aMode}): IME should be enabled when the <body> becomes editable` | ||||||
|  |         ); | ||||||
|  |         todo( | ||||||
|  |           tipWrapper.IMEHasFocus, | ||||||
|  |           `test_setting_content_editable_of_body_when_shadow_DOM_has_focus(${aMode}): IME should have focus when the <body> becomes editable` | ||||||
|  |         ); | ||||||
|  |         await SpecialPowers.spawn(browser, [], () => { | ||||||
|  |           content.document.body.removeAttribute("contenteditable"); | ||||||
|  |           return content.wrappedJSObject.waitForIMEContentObserverSendingNotifications(); | ||||||
|  |         }); | ||||||
|  |         is( | ||||||
|  |           window.windowUtils.IMEStatus, | ||||||
|  |           Ci.nsIDOMWindowUtils.IME_STATUS_DISABLED, | ||||||
|  |           `test_setting_content_editable_of_body_when_shadow_DOM_has_focus)${aMode}): IME should be disabled when the <body> becomes not editable` | ||||||
|  |         ); | ||||||
|  |         ok( | ||||||
|  |           !tipWrapper.IMEHasFocus, | ||||||
|  |           `test_setting_content_editable_of_body_when_shadow_DOM_has_focus)${aMode}): IME should not have focus when the <body> becomes not editable` | ||||||
|  |         ); | ||||||
|  |         await SpecialPowers.spawn(browser, [], () => { | ||||||
|  |           content.document.querySelector("div").remove(); | ||||||
|  |           content.document.body.appendChild( | ||||||
|  |             content.document.createElement("div") | ||||||
|  |           ); | ||||||
|  |         }); | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       async function test_setting_designMode_when_shadow_DOM_has_focus(aMode) { | ||||||
|  |         await SpecialPowers.spawn(browser, [aMode], mode => { | ||||||
|  |           const div = content.document.querySelector("div"); | ||||||
|  |           const shadow = div.attachShadow({ mode }); | ||||||
|  |           content.wrappedJSObject.divInShadow = content.document.createElement( | ||||||
|  |             "div" | ||||||
|  |           ); | ||||||
|  |           content.wrappedJSObject.divInShadow.setAttribute("tabindex", "0"); | ||||||
|  |           shadow.appendChild(content.wrappedJSObject.divInShadow); | ||||||
|  |           content.wrappedJSObject.divInShadow.focus(); | ||||||
|  |           return content.wrappedJSObject.waitForIMEContentObserverSendingNotifications(); | ||||||
|  |         }); | ||||||
|  |         is( | ||||||
|  |           window.windowUtils.IMEStatus, | ||||||
|  |           Ci.nsIDOMWindowUtils.IME_STATUS_DISABLED, | ||||||
|  |           `test_setting_designMode_when_shadow_DOM_has_focus(${aMode}): IME should be disabled when non-editable <div> in a shadow DOM has focus` | ||||||
|  |         ); | ||||||
|  |         await SpecialPowers.spawn(browser, [], () => { | ||||||
|  |           content.document.designMode = "on"; | ||||||
|  |           return content.wrappedJSObject.waitForIMEContentObserverSendingNotifications(); | ||||||
|  |         }); | ||||||
|  |         is( | ||||||
|  |           window.windowUtils.IMEStatus, | ||||||
|  |           Ci.nsIDOMWindowUtils.IME_STATUS_DISABLED, | ||||||
|  |           `test_setting_designMode_when_shadow_DOM_has_focus(${aMode}): IME should stay disabled when designMode is set` | ||||||
|  |         ); | ||||||
|  |         ok( | ||||||
|  |           !tipWrapper.IMEHasFocus, | ||||||
|  |           `test_setting_designMode_when_shadow_DOM_has_focus(${aMode}): IME should not have focus when designMode is set` | ||||||
|  |         ); | ||||||
|  |         await SpecialPowers.spawn(browser, [], () => { | ||||||
|  |           content.wrappedJSObject.divInShadow.setAttribute( | ||||||
|  |             "contenteditable", | ||||||
|  |             "" | ||||||
|  |           ); | ||||||
|  |           return content.wrappedJSObject.waitForIMEContentObserverSendingNotifications(); | ||||||
|  |         }); | ||||||
|  |         // todo_is because of bug 1807597.  Gecko does not update focus when focused
 | ||||||
|  |         // document is into the design mode.  Therefore, cannot initialize
 | ||||||
|  |         // HTMLEditor with the document node properly.
 | ||||||
|  |         todo_is( | ||||||
|  |           window.windowUtils.IMEStatus, | ||||||
|  |           Ci.nsIDOMWindowUtils.IME_STATUS_ENABLED, | ||||||
|  |           `test_setting_designMode_when_shadow_DOM_has_focus(${aMode}): IME should be enabled when focused <div> in a shadow DOM becomes editable` | ||||||
|  |         ); | ||||||
|  |         todo( | ||||||
|  |           tipWrapper.IMEHasFocus, | ||||||
|  |           `test_setting_designMode_when_shadow_DOM_has_focus(${aMode}): IME should have focus when focused <div> in a shadow DOM becomes editable` | ||||||
|  |         ); | ||||||
|  |         await SpecialPowers.spawn(browser, [], () => { | ||||||
|  |           content.document.designMode = "off"; | ||||||
|  |           return content.wrappedJSObject.waitForIMEContentObserverSendingNotifications(); | ||||||
|  |         }); | ||||||
|  |         is( | ||||||
|  |           window.windowUtils.IMEStatus, | ||||||
|  |           Ci.nsIDOMWindowUtils.IME_STATUS_DISABLED, | ||||||
|  |           `test_setting_designMode_when_shadow_DOM_has_focus(${aMode}): IME should be disabled when designMode is unset` | ||||||
|  |         ); | ||||||
|  |         ok( | ||||||
|  |           !tipWrapper.IMEHasFocus, | ||||||
|  |           `test_setting_designMode_when_shadow_DOM_has_focus(${aMode}): IME should not have focus when designMode is unset` | ||||||
|  |         ); | ||||||
|  |         await SpecialPowers.spawn(browser, [], () => { | ||||||
|  |           content.document.querySelector("div").remove(); | ||||||
|  |           content.document.body.appendChild( | ||||||
|  |             content.document.createElement("div") | ||||||
|  |           ); | ||||||
|  |         }); | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       for (const mode of ["open", "closed"]) { | ||||||
|  |         await test_setting_content_editable_of_body_when_shadow_DOM_has_focus( | ||||||
|  |           mode | ||||||
|  |         ); | ||||||
|  |         await resetIMEStateWithFocusMove(); | ||||||
|  |         await test_setting_designMode_when_shadow_DOM_has_focus(mode); | ||||||
|  |         await resetIMEStateWithFocusMove(); | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   ); | ||||||
|  | }); | ||||||
|  | @ -25,6 +25,9 @@ support-files = window_bug593307_offscreen.xhtml window_bug593307_centerscreen.x | ||||||
| support-files = window_wheeltransaction.xhtml | support-files = window_wheeltransaction.xhtml | ||||||
| [test_ime_state_in_parent.html] | [test_ime_state_in_parent.html] | ||||||
| support-files = window_imestate_iframes.html | support-files = window_imestate_iframes.html | ||||||
|  | [test_ime_state_on_editable_state_change_in_parent.html] | ||||||
|  | support-files = | ||||||
|  |   file_ime_state_test_helper.js | ||||||
| [test_ime_state_on_focus_move_in_parent.html] | [test_ime_state_on_focus_move_in_parent.html] | ||||||
| support-files = | support-files = | ||||||
|   file_ime_state_test_helper.js |   file_ime_state_test_helper.js | ||||||
|  |  | ||||||
|  | @ -475,8 +475,8 @@ function runComplexContenteditableTests() { | ||||||
|   container.removeAttribute("contenteditable"); |   container.removeAttribute("contenteditable"); | ||||||
|   todo_is(gFM.focusedElement, null, |   todo_is(gFM.focusedElement, null, | ||||||
|           description + "The container still has focus, the editor has been no editable"); |           description + "The container still has focus, the editor has been no editable"); | ||||||
|   todo_is(gUtils.IMEStatus, gUtils.IME_STATUS_DISABLED, |   is(gUtils.IMEStatus, gUtils.IME_STATUS_DISABLED, | ||||||
|           description + "IME is still enabled on the editor, the editor has been no editable"); |      description + "IME is still enabled on the editor, the editor has been no editable"); | ||||||
| 
 | 
 | ||||||
|   // a button which is in the editor has focus |   // a button which is in the editor has focus | ||||||
|   button.focus(); |   button.focus(); | ||||||
|  |  | ||||||
|  | @ -0,0 +1,263 @@ | ||||||
|  | <!doctype html> | ||||||
|  | <html> | ||||||
|  | <head> | ||||||
|  |   <meta charset="utf-8"> | ||||||
|  |   <title>Test for IME state management at changing editable state</title> | ||||||
|  |   <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> | ||||||
|  |   <script src="file_ime_state_test_helper.js"></script> | ||||||
|  |   <link rel="stylesheet" href="chrome://mochikit/content/tests/SimpleTest/test.css"> | ||||||
|  | </head> | ||||||
|  | <body> | ||||||
|  | <div></div> | ||||||
|  | <script> | ||||||
|  | "use strict"; | ||||||
|  | 
 | ||||||
|  | /* import-globals-from file_ime_state_test_helper.js */ | ||||||
|  | 
 | ||||||
|  | SimpleTest.waitForExplicitFinish(); | ||||||
|  | SimpleTest.waitForFocus(async () => { | ||||||
|  |   const tipWrapper = new TIPWrapper(window); | ||||||
|  | 
 | ||||||
|  |   function waitForIMEContentObserverSendingNotifications() { | ||||||
|  |     return new Promise( | ||||||
|  |       resolve => requestAnimationFrame( | ||||||
|  |         () => requestAnimationFrame(resolve) | ||||||
|  |       ) | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   function resetIMEStateWithFocusMove() { | ||||||
|  |     const input = document.createElement("input"); | ||||||
|  |     document.body.appendChild(input); | ||||||
|  |     input.focus(); | ||||||
|  |     input.remove(); | ||||||
|  |     return waitForIMEContentObserverSendingNotifications(); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   await (async function test_setting_contenteditable_of_focused_div() { | ||||||
|  |     const div = document.querySelector("div"); | ||||||
|  |     div.setAttribute("tabindex", "0"); | ||||||
|  |     div.focus(); | ||||||
|  |     is( | ||||||
|  |       window.windowUtils.IMEStatus, | ||||||
|  |       Ci.nsIDOMWindowUtils.IME_STATUS_DISABLED, | ||||||
|  |       "test_setting_contenteditable_of_focused_div: IME should be disabled when non-editable <div> has focus" | ||||||
|  |     ); | ||||||
|  |     div.setAttribute("contenteditable", ""); | ||||||
|  |     await waitForIMEContentObserverSendingNotifications(); | ||||||
|  |     // Sometimes, it's not enough waiting only 2 animation frames here to wait | ||||||
|  |     // for IME focus, perhaps, it may be related to HTMLEditor initialization. | ||||||
|  |     // Let's wait one more animation frame here. | ||||||
|  |     if (!tipWrapper.IMEHasFocus) { | ||||||
|  |       await new Promise(resolve => requestAnimationFrame(resolve)); | ||||||
|  |     } | ||||||
|  |     is( | ||||||
|  |       window.windowUtils.IMEStatus, | ||||||
|  |       Ci.nsIDOMWindowUtils.IME_STATUS_ENABLED, | ||||||
|  |       "test_setting_contenteditable_of_focused_div: IME should be enabled when contenteditable of focused <div> is set" | ||||||
|  |     ); | ||||||
|  |     ok( | ||||||
|  |       tipWrapper.IMEHasFocus, | ||||||
|  |       "test_setting_contenteditable_of_focused_div: IME should have focus when contenteditable of focused <div> is set" | ||||||
|  |     ) | ||||||
|  |     div.removeAttribute("contenteditable"); | ||||||
|  |     await waitForIMEContentObserverSendingNotifications(); | ||||||
|  |     is( | ||||||
|  |       window.windowUtils.IMEStatus, | ||||||
|  |       Ci.nsIDOMWindowUtils.IME_STATUS_DISABLED, | ||||||
|  |       "test_setting_contenteditable_of_focused_div: IME should be disabled when contenteditable of focused <div> is removed" | ||||||
|  |     ); | ||||||
|  |     ok( | ||||||
|  |       !tipWrapper.IMEHasFocus, | ||||||
|  |       "test_setting_contenteditable_of_focused_div: IME should not have focus when contenteditable of focused <div> is removed" | ||||||
|  |     ); | ||||||
|  |     div.removeAttribute("tabindex"); | ||||||
|  |   })(); | ||||||
|  | 
 | ||||||
|  |   await resetIMEStateWithFocusMove(); | ||||||
|  | 
 | ||||||
|  |   await (async function test_removing_contenteditable_of_non_last_editable_div() { | ||||||
|  |     const div = document.querySelector("div"); | ||||||
|  |     div.setAttribute("tabindex", "0"); | ||||||
|  |     div.setAttribute("contenteditable", ""); | ||||||
|  |     const anotherEditableDiv = document.createElement("div"); | ||||||
|  |     anotherEditableDiv.setAttribute("contenteditable", ""); | ||||||
|  |     div.parentElement.appendChild(anotherEditableDiv); | ||||||
|  |     div.focus(); | ||||||
|  |     await waitForIMEContentObserverSendingNotifications(); | ||||||
|  |     div.removeAttribute("contenteditable"); | ||||||
|  |     await waitForIMEContentObserverSendingNotifications(); | ||||||
|  |     is( | ||||||
|  |       window.windowUtils.IMEStatus, | ||||||
|  |       Ci.nsIDOMWindowUtils.IME_STATUS_DISABLED, | ||||||
|  |       "test_removing_contenteditable_of_non_last_editable_div: IME should be disabled when contenteditable of focused <div> is removed" | ||||||
|  |     ); | ||||||
|  |     ok( | ||||||
|  |       !tipWrapper.IMEHasFocus, | ||||||
|  |       "test_removing_contenteditable_of_non_last_editable_div: IME should not have focus when contenteditable of focused <div> is removed" | ||||||
|  |     ); | ||||||
|  |     anotherEditableDiv.remove(); | ||||||
|  |     div.removeAttribute("tabindex"); | ||||||
|  |   })(); | ||||||
|  | 
 | ||||||
|  |   await resetIMEStateWithFocusMove(); | ||||||
|  | 
 | ||||||
|  |   await (async function test_setting_designMode() { | ||||||
|  |     window.focus(); | ||||||
|  |     document.designMode = "on"; | ||||||
|  |     await waitForIMEContentObserverSendingNotifications(); | ||||||
|  |     is( | ||||||
|  |       window.windowUtils.IMEStatus, | ||||||
|  |       Ci.nsIDOMWindowUtils.IME_STATUS_ENABLED, | ||||||
|  |       'test_setting_designMode: IME should be enabled when designMode is set to "on"' | ||||||
|  |     ); | ||||||
|  |     ok( | ||||||
|  |       tipWrapper.IMEHasFocus, | ||||||
|  |       'test_setting_designMode: IME should have focus when designMode is set to "on"' | ||||||
|  |     ); | ||||||
|  |     document.designMode = "off"; | ||||||
|  |     await waitForIMEContentObserverSendingNotifications(); | ||||||
|  |     is( | ||||||
|  |       window.windowUtils.IMEStatus, | ||||||
|  |       Ci.nsIDOMWindowUtils.IME_STATUS_DISABLED, | ||||||
|  |       'test_setting_designMode: IME should be disabled when designMode is set to "off"' | ||||||
|  |     ); | ||||||
|  |     ok( | ||||||
|  |       !tipWrapper.IMEHasFocus, | ||||||
|  |       'test_setting_designMode: IME should not have focus when designMode is set to "off"' | ||||||
|  |     ); | ||||||
|  |   })(); | ||||||
|  | 
 | ||||||
|  |   await resetIMEStateWithFocusMove(); | ||||||
|  | 
 | ||||||
|  |   async function test_setting_content_editable_of_body_when_shadow_DOM_has_focus(aMode) { | ||||||
|  |     const div = document.querySelector("div"); | ||||||
|  |     const shadow = div.attachShadow({mode: aMode}); | ||||||
|  |     const divInShadow = document.createElement("div"); | ||||||
|  |     divInShadow.setAttribute("tabindex", "0"); | ||||||
|  |     shadow.appendChild(divInShadow); | ||||||
|  |     divInShadow.focus(); | ||||||
|  |     await waitForIMEContentObserverSendingNotifications(); | ||||||
|  |     is( | ||||||
|  |       window.windowUtils.IMEStatus, | ||||||
|  |       Ci.nsIDOMWindowUtils.IME_STATUS_DISABLED, | ||||||
|  |       `test_setting_content_editable_of_body_when_shadow_DOM_has_focus(${ | ||||||
|  |         aMode | ||||||
|  |       }): IME should be disabled when non-editable <div> in a shadow DOM has focus` | ||||||
|  |     ); | ||||||
|  |     document.body.setAttribute("contenteditable", ""); | ||||||
|  |     await waitForIMEContentObserverSendingNotifications(); | ||||||
|  |     // todo_is because of bug 1807597.  Gecko does not update focus when focused | ||||||
|  |     // element becomes an editable child.  Therefore, cannot initialize | ||||||
|  |     // HTMLEditor with the new editing host. | ||||||
|  |     todo_is( | ||||||
|  |       window.windowUtils.IMEStatus, | ||||||
|  |       Ci.nsIDOMWindowUtils.IME_STATUS_ENABLED, | ||||||
|  |       `test_setting_content_editable_of_body_when_shadow_DOM_has_focus(${ | ||||||
|  |         aMode | ||||||
|  |       }): IME should be enabled when the <body> becomes editable` | ||||||
|  |     ); | ||||||
|  |     todo( | ||||||
|  |       tipWrapper.IMEHasFocus, | ||||||
|  |       `test_setting_content_editable_of_body_when_shadow_DOM_has_focus(${ | ||||||
|  |         aMode | ||||||
|  |       }): IME should have focus when the <body> becomes editable` | ||||||
|  |     ); | ||||||
|  |     document.body.removeAttribute("contenteditable"); | ||||||
|  |     await waitForIMEContentObserverSendingNotifications(); | ||||||
|  |     is( | ||||||
|  |       window.windowUtils.IMEStatus, | ||||||
|  |       Ci.nsIDOMWindowUtils.IME_STATUS_DISABLED, | ||||||
|  |       `test_setting_content_editable_of_body_when_shadow_DOM_has_focus)${ | ||||||
|  |         aMode | ||||||
|  |       }): IME should be disabled when the <body> becomes not editable` | ||||||
|  |     ); | ||||||
|  |     ok( | ||||||
|  |       !tipWrapper.IMEHasFocus, | ||||||
|  |       `test_setting_content_editable_of_body_when_shadow_DOM_has_focus)${ | ||||||
|  |         aMode | ||||||
|  |       }): IME should not have focus when the <body> becomes not editable` | ||||||
|  |     ); | ||||||
|  |     div.remove(); | ||||||
|  |     document.body.appendChild(document.createElement("div")); | ||||||
|  |   }; | ||||||
|  | 
 | ||||||
|  |   async function test_setting_designMode_when_shadow_DOM_has_focus(aMode) { | ||||||
|  |     const div = document.querySelector("div"); | ||||||
|  |     const shadow = div.attachShadow({mode: aMode}); | ||||||
|  |     const divInShadow = document.createElement("div"); | ||||||
|  |     divInShadow.setAttribute("tabindex", "0"); | ||||||
|  |     shadow.appendChild(divInShadow); | ||||||
|  |     divInShadow.focus(); | ||||||
|  |     await waitForIMEContentObserverSendingNotifications(); | ||||||
|  |     is( | ||||||
|  |       window.windowUtils.IMEStatus, | ||||||
|  |       Ci.nsIDOMWindowUtils.IME_STATUS_DISABLED, | ||||||
|  |       `test_setting_designMode_when_shadow_DOM_has_focus(${ | ||||||
|  |         aMode | ||||||
|  |       }): IME should be disabled when non-editable <div> in a shadow DOM has focus` | ||||||
|  |     ); | ||||||
|  |     document.designMode = "on"; | ||||||
|  |     await waitForIMEContentObserverSendingNotifications(); | ||||||
|  |     is( | ||||||
|  |       window.windowUtils.IMEStatus, | ||||||
|  |       Ci.nsIDOMWindowUtils.IME_STATUS_DISABLED, | ||||||
|  |       `test_setting_designMode_when_shadow_DOM_has_focus(${ | ||||||
|  |         aMode | ||||||
|  |       }): IME should stay disabled when designMode is set` | ||||||
|  |     ); | ||||||
|  |     ok( | ||||||
|  |       !tipWrapper.IMEHasFocus, | ||||||
|  |       `test_setting_designMode_when_shadow_DOM_has_focus(${ | ||||||
|  |         aMode | ||||||
|  |       }): IME should not have focus when designMode is set` | ||||||
|  |     ); | ||||||
|  |     divInShadow.setAttribute("contenteditable", ""); | ||||||
|  |     await waitForIMEContentObserverSendingNotifications(); | ||||||
|  |     // todo_is because of bug 1807597.  Gecko does not update focus when focused | ||||||
|  |     // document is into the design mode.  Therefore, cannot initialize | ||||||
|  |     // HTMLEditor with the document node properly. | ||||||
|  |     todo_is( | ||||||
|  |       window.windowUtils.IMEStatus, | ||||||
|  |       Ci.nsIDOMWindowUtils.IME_STATUS_ENABLED, | ||||||
|  |       `test_setting_designMode_when_shadow_DOM_has_focus(${ | ||||||
|  |         aMode | ||||||
|  |       }): IME should be enabled when focused <div> in a shadow DOM becomes editable` | ||||||
|  |     ); | ||||||
|  |     todo( | ||||||
|  |       tipWrapper.IMEHasFocus, | ||||||
|  |       `test_setting_designMode_when_shadow_DOM_has_focus(${ | ||||||
|  |         aMode | ||||||
|  |       }): IME should have focus when focused <div> in a shadow DOM becomes editable` | ||||||
|  |     ); | ||||||
|  |     document.designMode = "off"; | ||||||
|  |     await waitForIMEContentObserverSendingNotifications(); | ||||||
|  |     is( | ||||||
|  |       window.windowUtils.IMEStatus, | ||||||
|  |       Ci.nsIDOMWindowUtils.IME_STATUS_DISABLED, | ||||||
|  |       `test_setting_designMode_when_shadow_DOM_has_focus(${ | ||||||
|  |         aMode | ||||||
|  |       }): IME should be disabled when designMode is unset` | ||||||
|  |     ); | ||||||
|  |     ok( | ||||||
|  |       !tipWrapper.IMEHasFocus, | ||||||
|  |       `test_setting_designMode_when_shadow_DOM_has_focus(${ | ||||||
|  |         aMode | ||||||
|  |       }): IME should not have focus when designMode is unset` | ||||||
|  |     ); | ||||||
|  |     div.remove(); | ||||||
|  |     document.body.appendChild(document.createElement("div")); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   for (const mode of ["open", "closed"]) { | ||||||
|  |     await test_setting_content_editable_of_body_when_shadow_DOM_has_focus(mode); | ||||||
|  |     await resetIMEStateWithFocusMove(); | ||||||
|  |     await test_setting_designMode_when_shadow_DOM_has_focus(mode); | ||||||
|  |     await resetIMEStateWithFocusMove(); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   SimpleTest.finish(); | ||||||
|  | }); | ||||||
|  | </script> | ||||||
|  | </body> | ||||||
|  | </html> | ||||||
		Loading…
	
		Reference in a new issue
	
	 Masayuki Nakano
						Masayuki Nakano