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:
Masayuki Nakano 2023-04-11 23:26:03 +00:00
parent 2d577e4f51
commit e4a81444c1
18 changed files with 975 additions and 57 deletions

View file

@ -4540,6 +4540,12 @@ bool Document::HasFocus(ErrorResult& rv) const {
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) {
if (IsInDesignMode()) {
aDesignMode.AssignLiteral("on");
@ -5959,6 +5965,20 @@ static bool HasPresShell(nsPIDOMWindowOuter* aWindow) {
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() {
if (mRemovedFromDocShell) {
return NS_OK;
@ -5980,11 +6000,33 @@ nsresult Document::EditingStateChanged() {
return NS_OK;
}
const bool thisDocumentHasFocus = ThisDocumentHasFocus();
if (newState == EditingState::eOff) {
// Editing is being turned off.
nsAutoScriptBlocker scriptBlocker;
RefPtr<HTMLEditor> htmlEditor = GetHTMLEditor();
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
@ -6183,6 +6225,19 @@ nsresult Document::EditingStateChanged() {
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;
}
@ -6194,9 +6249,11 @@ class DeferredContentEditableCountChangeEvent : public Runnable {
mDoc(aDoc),
mElement(aElement) {}
NS_IMETHOD Run() override {
MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHOD Run() override {
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;
}
@ -6217,6 +6274,38 @@ void Document::ChangeContentEditableCount(Element* aElement, int32_t aChange) {
}
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 ||
(mUpdateNestLevel > 0 && (mContentEditableCount > 0) != IsEditingOn())) {
return;
@ -6232,18 +6321,7 @@ void Document::DeferredContentEditableCountChange(Element* aElement) {
// We just changed the contentEditable state of a node, we need to reset
// the spellchecking state of that node.
if (aElement) {
nsPIDOMWindowOuter* window = GetWindow();
if (!window) {
return;
}
nsIDocShell* docshell = window->GetDocShell();
if (!docshell) {
return;
}
RefPtr<HTMLEditor> htmlEditor = docshell->GetHTMLEditor();
if (htmlEditor) {
if (RefPtr<HTMLEditor> htmlEditor = GetHTMLEditor()) {
nsCOMPtr<nsIInlineSpellChecker> spellChecker;
rv = htmlEditor->GetInlineSpellChecker(false,
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() {

View file

@ -1469,7 +1469,7 @@ class Document : public nsINode,
* changed to true, -1 if it was changed to false.
*/
void ChangeContentEditableCount(Element*, int32_t aChange);
void DeferredContentEditableCountChange(Element*);
MOZ_CAN_RUN_SCRIPT void DeferredContentEditableCountChange(Element*);
enum class EditingState : int8_t {
eTearingDown = -2,
@ -3405,7 +3405,16 @@ class Document : public nsINode,
// they could want the IncludeChromeOnly::Yes version.
nsIContent* GetUnretargetedFocusedContent(
IncludeChromeOnly = IncludeChromeOnly::No) const;
/**
* Return true if this document or a subdocument has focus.
*/
bool HasFocus(ErrorResult& rv) const;
/**
* Return true if this document itself has focus.
*/
bool ThisDocumentHasFocus() const;
void GetDesignMode(nsAString& aDesignMode);
void SetDesignMode(const nsAString& aDesignMode,
nsIPrincipal& aSubjectPrincipal, mozilla::ErrorResult& rv);
@ -3875,6 +3884,8 @@ class Document : public nsINode,
return mShouldNotifyFormOrPasswordRemoved;
}
HTMLEditor* GetHTMLEditor() const;
/**
* Localization
*

View file

@ -2669,7 +2669,11 @@ void nsFocusManager::Focus(
relatedTargetElement);
}
} 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));
if (!aWindowRaised) {
aWindow->UpdateCommands(u"focus"_ns, nullptr, 0);

View file

@ -13,6 +13,7 @@
#include "mozilla/EditorBase.h"
#include "mozilla/EventListenerManager.h"
#include "mozilla/EventStateManager.h"
#include "mozilla/HTMLEditor.h"
#include "mozilla/MouseEvents.h"
#include "mozilla/PresShell.h"
#include "mozilla/StaticPrefs_dom.h"
@ -100,6 +101,15 @@ void IMEStateManager::Shutdown() {
"sPendingFocusedBrowserSwitchingData.isSome()=%s",
sTextCompositions, sTextCompositions ? sTextCompositions->Length() : 0,
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();
MOZ_ASSERT(!sTextCompositions || !sTextCompositions->Length());
@ -400,6 +410,7 @@ nsresult IMEStateManager::OnRemoveContent(nsPresContext& aPresContext,
!sFocusedElement->IsInclusiveDescendantOf(&aElement)) {
return NS_OK;
}
MOZ_ASSERT(sFocusedPresContext == &aPresContext);
MOZ_LOG(
sISMLog, LogLevel::Info,
@ -410,22 +421,33 @@ nsresult IMEStateManager::OnRemoveContent(nsPresContext& aPresContext,
DestroyIMEContentObserver();
// FYI: Don't clear sTextInputHandlingWidget and sFocusedPresContext because
// the window/document keeps having focus.
sFocusedElement = nullptr;
// Current IME transaction should commit
if (sTextInputHandlingWidget) {
IMEState newState = GetNewIMEState(*sFocusedPresContext, nullptr);
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);
if (!sTextInputHandlingWidget) {
return NS_OK;
}
sTextInputHandlingWidget = nullptr;
sFocusedElement = nullptr;
sFocusedPresContext = nullptr;
IMEState newState = GetNewIMEState(*sFocusedPresContext, nullptr);
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);
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;
}
@ -452,6 +474,11 @@ nsresult IMEStateManager::OnChangeFocus(nsPresContext* aPresContext,
nsresult IMEStateManager::OnChangeFocusInternal(nsPresContext* aPresContext,
Element* aElement,
InputContextAction aAction) {
NS_ASSERTION(!aElement || aElement->GetPresContext(
Element::PresContextFor::eForComposedDoc) ==
aPresContext,
"aPresContext does not match with one of aElement");
bool remoteHasFocus = EventStateManager::IsRemoteTarget(aElement);
// 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
@ -1026,6 +1053,87 @@ void IMEStateManager::OnReFocus(nsPresContext& aPresContext,
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
void IMEStateManager::UpdateIMEState(const IMEState& aNewIMEState,
Element* aElement, EditorBase& aEditorBase,
@ -1059,7 +1167,10 @@ void IMEStateManager::UpdateIMEState(const IMEState& aNewIMEState,
return;
}
const RefPtr<nsPresContext> presContext = presShell->GetPresContext();
const RefPtr<nsPresContext> presContext =
aElement
? aElement->GetPresContext(Element::PresContextFor::eForComposedDoc)
: aEditorBase.GetPresContext();
if (NS_WARN_IF(!presContext)) {
MOZ_LOG(sISMLog, LogLevel::Error,
(" UpdateIMEState(), FAILED due to "
@ -1192,8 +1303,15 @@ void IMEStateManager::UpdateIMEState(const IMEState& aNewIMEState,
if (updateIMEState) {
InputContextAction action(InputContextAction::CAUSE_UNKNOWN,
InputContextAction::FOCUS_NOT_CHANGED);
SetIMEState(aNewIMEState, presContext, aElement, textInputHandlingWidget,
action, sOrigin);
RefPtr<nsPresContext> editorPresContext = aEditorBase.GetPresContext();
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()) ||
NS_WARN_IF(sTextInputHandlingWidget != textInputHandlingWidget)) {
MOZ_LOG(sISMLog, LogLevel::Error,
@ -2145,18 +2263,14 @@ bool IMEStateManager::IsIMEObserverNeeded(const IMEState& aState) {
// static
void IMEStateManager::DestroyIMEContentObserver() {
MOZ_LOG(sISMLog, LogLevel::Info,
("DestroyIMEContentObserver(), sActiveIMEContentObserver=0x%p",
sActiveIMEContentObserver.get()));
if (!sActiveIMEContentObserver) {
MOZ_LOG(sISMLog, LogLevel::Debug,
(" DestroyIMEContentObserver() does nothing"));
MOZ_LOG(sISMLog, LogLevel::Verbose,
("DestroyIMEContentObserver() does nothing"));
return;
}
MOZ_LOG(sISMLog, LogLevel::Debug,
(" DestroyIMEContentObserver(), destroying "
MOZ_LOG(sISMLog, LogLevel::Info,
("DestroyIMEContentObserver(), destroying "
"the active IMEContentObserver..."));
RefPtr<IMEContentObserver> tsm = sActiveIMEContentObserver.get();
sActiveIMEContentObserver = nullptr;

View file

@ -158,7 +158,11 @@ class IMEStateManager {
/**
* OnChangeFocus() should be called when focused content is changed or
* 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(
nsPresContext* aPresContext, dom::Element* aElement,
@ -235,6 +239,12 @@ class IMEStateManager {
MOZ_CAN_RUN_SCRIPT static void OnReFocus(nsPresContext& aPresContext,
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()
* for storing the composition target and ensuring a set of composition

View file

@ -26,8 +26,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=1128787
SimpleTest.executeSoon(function () {
document.designMode = "off";
// XXX Should be fixed.
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();
});

View file

@ -29,7 +29,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=1128787
is(document.designMode, "off",
"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();
}, {once: true});
document.designMode = "on";

View file

@ -19,6 +19,8 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=687787
<pre id="test">
<script class="testbody" type="text/javascript">
SimpleTest.expectAssertions(1); // in IMEStateManager::OnChangeFocusInternal, bug 1820470
var eventStack = [];
function _callback(e){

View file

@ -5613,11 +5613,14 @@ nsresult EditorBase::OnFocus(const nsINode& aOriginalEventTargetNode) {
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)) {
return NS_ERROR_FAILURE;
}
RefPtr<Element> focusedElement = GetFocusedElement();
IMEStateManager::OnFocusInEditor(*presContext, focusedElement, *this);
return NS_OK;

View file

@ -221,6 +221,14 @@ class EditorBase : public nsIEditor,
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
*/

View file

@ -29,7 +29,8 @@
#include "mozilla/ContentIterator.h"
#include "mozilla/DebugOnly.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/InternalMutationEvent.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) {
// Before doing anything, we should check whether the original target is still
// 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);
}
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) {
// check if something else is focused. If another element is focused, then
// 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
// document node. If a blur event is fired and the target is an element, it
// 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)) {
return NS_OK;
}

View file

@ -207,6 +207,22 @@ class HTMLEditor final : public EditorBase,
OnFocus(const nsINode& aOriginalEventTargetNode) 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
* selection.

View file

@ -7,6 +7,9 @@ skip-if =
os == "win" && bits == 32 && !debug # Bug 1759422
os == "linux" # Bug 1792749
[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]
support-files =
file_ime_state_tests.html

View file

@ -107,11 +107,6 @@ add_task(async function() {
await runIMEStateOnFocusMoveTests('in designMode="on"');
await SpecialPowers.spawn(browser, [], async () => {
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"');
}

View file

@ -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();
}
}
);
});

View file

@ -25,6 +25,9 @@ support-files = window_bug593307_offscreen.xhtml window_bug593307_centerscreen.x
support-files = window_wheeltransaction.xhtml
[test_ime_state_in_parent.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]
support-files =
file_ime_state_test_helper.js

View file

@ -475,8 +475,8 @@ function runComplexContenteditableTests() {
container.removeAttribute("contenteditable");
todo_is(gFM.focusedElement, null,
description + "The container still has focus, the editor has been no editable");
todo_is(gUtils.IMEStatus, gUtils.IME_STATUS_DISABLED,
description + "IME is still enabled on the editor, the editor has been no editable");
is(gUtils.IMEStatus, gUtils.IME_STATUS_DISABLED,
description + "IME is still enabled on the editor, the editor has been no editable");
// a button which is in the editor has focus
button.focus();

View file

@ -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>