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());
|
||||
}
|
||||
|
||||
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() {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
*
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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";
|
||||
|
|
|
|||
|
|
@ -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){
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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"');
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
[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
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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