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) {
|
||||||
|
return NS_OK;
|
||||||
|
}
|
||||||
|
|
||||||
IMEState newState = GetNewIMEState(*sFocusedPresContext, nullptr);
|
IMEState newState = GetNewIMEState(*sFocusedPresContext, nullptr);
|
||||||
InputContextAction action(InputContextAction::CAUSE_UNKNOWN,
|
InputContextAction action(InputContextAction::CAUSE_UNKNOWN,
|
||||||
InputContextAction::LOST_FOCUS);
|
InputContextAction::LOST_FOCUS);
|
||||||
InputContext::Origin origin =
|
InputContext::Origin origin =
|
||||||
BrowserParent::GetFocused() ? InputContext::ORIGIN_CONTENT : sOrigin;
|
BrowserParent::GetFocused() ? InputContext::ORIGIN_CONTENT : sOrigin;
|
||||||
OwningNonNull<nsIWidget> textInputHandlingWidget =
|
OwningNonNull<nsIWidget> textInputHandlingWidget = *sTextInputHandlingWidget;
|
||||||
*sTextInputHandlingWidget;
|
SetIMEState(newState, &aPresContext, nullptr, textInputHandlingWidget, action,
|
||||||
SetIMEState(newState, &aPresContext, nullptr, textInputHandlingWidget,
|
origin);
|
||||||
action, origin);
|
if (sFocusedPresContext != &aPresContext || sFocusedElement) {
|
||||||
|
return NS_OK; // Some body must have set focus
|
||||||
}
|
}
|
||||||
|
|
||||||
sTextInputHandlingWidget = nullptr;
|
if (IsIMEObserverNeeded(newState)) {
|
||||||
sFocusedElement = nullptr;
|
if (RefPtr<HTMLEditor> htmlEditor =
|
||||||
sFocusedPresContext = nullptr;
|
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,17 +2263,13 @@ 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();
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,7 @@
|
||||||
#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,7 +475,7 @@ 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
|
||||||
|
|
|
||||||
|
|
@ -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