From dc7e7da10615b91a97c104169af8b19d58496418 Mon Sep 17 00:00:00 2001 From: Masayuki Nakano Date: Wed, 5 May 2010 02:40:39 +0900 Subject: [PATCH] Bug 488420 IME enabled state is not modified when a focused editor's readonly attribute is changed r=smaug --- content/base/public/nsIContent.h | 15 +- content/base/src/nsGenericElement.cpp | 42 ++ content/events/src/nsIMEStateManager.cpp | 35 +- content/events/src/nsIMEStateManager.h | 7 +- editor/libeditor/base/Makefile.in | 1 + editor/libeditor/base/nsEditor.cpp | 54 ++- editor/libeditor/base/nsEditor.h | 3 + .../libeditor/base/nsEditorEventListener.cpp | 14 + editor/libeditor/html/nsHTMLEditor.cpp | 77 ++++ editor/libeditor/html/nsHTMLEditor.h | 10 + editor/libeditor/html/tests/Makefile.in | 1 + .../tests/test_contenteditable_focus.html | 216 ++++++++++ widget/tests/test_imestate.html | 389 +++++++++++++++--- 13 files changed, 787 insertions(+), 77 deletions(-) create mode 100644 editor/libeditor/html/tests/test_contenteditable_focus.html diff --git a/content/base/public/nsIContent.h b/content/base/public/nsIContent.h index 22509533a18..9e29acbf2e5 100644 --- a/content/base/public/nsIContent.h +++ b/content/base/public/nsIContent.h @@ -615,20 +615,7 @@ public: IME_STATUS_PASSWORD | IME_STATUS_PLUGIN, IME_STATUS_MASK_OPENED = IME_STATUS_OPEN | IME_STATUS_CLOSE }; - virtual PRUint32 GetDesiredIMEState() - { - if (!IsEditableInternal()) - return IME_STATUS_DISABLE; - nsIContent *editableAncestor = nsnull; - for (nsIContent* parent = GetParent(); - parent && parent->HasFlag(NODE_IS_EDITABLE); - parent = parent->GetParent()) - editableAncestor = parent; - // This is in another editable content, use the result of it. - if (editableAncestor) - return editableAncestor->GetDesiredIMEState(); - return IME_STATUS_ENABLE; - } + virtual PRUint32 GetDesiredIMEState(); /** * Gets content node with the binding (or native code, possibly on the diff --git a/content/base/src/nsGenericElement.cpp b/content/base/src/nsGenericElement.cpp index 4c5d6d0be4e..d66dd63bf41 100644 --- a/content/base/src/nsGenericElement.cpp +++ b/content/base/src/nsGenericElement.cpp @@ -125,6 +125,7 @@ #include "nsIDOMUserDataHandler.h" #include "nsGenericHTMLElement.h" #include "nsIEditor.h" +#include "nsIEditorIMESupport.h" #include "nsIEditorDocShell.h" #include "nsEventDispatcher.h" #include "nsContentCreatorFunctions.h" @@ -636,6 +637,47 @@ nsIContent::GetFlattenedTreeParent() const return parent; } +PRUint32 +nsIContent::GetDesiredIMEState() +{ + if (!IsEditableInternal()) { + return IME_STATUS_DISABLE; + } + nsIContent *editableAncestor = nsnull; + for (nsIContent* parent = GetParent(); + parent && parent->HasFlag(NODE_IS_EDITABLE); + parent = parent->GetParent()) { + editableAncestor = parent; + } + // This is in another editable content, use the result of it. + if (editableAncestor) { + return editableAncestor->GetDesiredIMEState(); + } + nsIDocument* doc = GetCurrentDoc(); + if (!doc) { + return IME_STATUS_DISABLE; + } + nsIPresShell* ps = doc->GetPrimaryShell(); + if (!ps) { + return IME_STATUS_DISABLE; + } + nsPresContext* pc = ps->GetPresContext(); + if (!pc) { + return IME_STATUS_DISABLE; + } + nsIEditor* editor = GetHTMLEditor(pc); + nsCOMPtr imeEditor = do_QueryInterface(editor); + if (!imeEditor) { + return IME_STATUS_DISABLE; + } + // Use "enable" for the default value because IME is disabled unexpectedly, + // it makes serious a11y problem. + PRUint32 state = IME_STATUS_ENABLE; + nsresult rv = imeEditor->GetPreferredIMEState(&state); + NS_ENSURE_SUCCESS(rv, IME_STATUS_ENABLE); + return state; +} + //---------------------------------------------------------------------- NS_IMPL_ADDREF(nsChildContentList) diff --git a/content/events/src/nsIMEStateManager.cpp b/content/events/src/nsIMEStateManager.cpp index 39a4f36db1b..eb9942d66f3 100644 --- a/content/events/src/nsIMEStateManager.cpp +++ b/content/events/src/nsIMEStateManager.cpp @@ -83,7 +83,7 @@ nsIMEStateManager::OnDestroyPresContext(nsPresContext* aPresContext) nsCOMPtr widget = GetWidget(sPresContext); if (widget) { PRUint32 newState = GetNewIMEState(sPresContext, nsnull); - SetIMEState(sPresContext, newState, widget); + SetIMEState(newState, widget); } sContent = nsnull; sPresContext = nsnull; @@ -108,7 +108,7 @@ nsIMEStateManager::OnRemoveContent(nsPresContext* aPresContext, if (NS_FAILED(rv)) widget->ResetInputState(); PRUint32 newState = GetNewIMEState(sPresContext, nsnull); - SetIMEState(sPresContext, newState, widget); + SetIMEState(newState, widget); } sContent = nsnull; @@ -162,7 +162,7 @@ nsIMEStateManager::OnChangeFocus(nsPresContext* aPresContext, if (newState != nsIContent::IME_STATUS_NONE) { // Update IME state for new focus widget - SetIMEState(aPresContext, newState, widget); + SetIMEState(newState, widget); } sPresContext = aPresContext; @@ -178,6 +178,26 @@ nsIMEStateManager::OnInstalledMenuKeyboardListener(PRBool aInstalling) OnChangeFocus(sPresContext, sContent); } +void +nsIMEStateManager::ChangeIMEStateTo(PRUint32 aNewIMEState) +{ + if (!sPresContext) { + NS_WARNING("ISM doesn't know which editor has focus"); + return; + } + NS_PRECONDITION(aNewIMEState != 0, "aNewIMEState doesn't specify new state."); + nsCOMPtr widget = GetWidget(sPresContext); + if (!widget) { + NS_WARNING("focused widget is not found"); + return; + } + + // commit current composition + widget->ResetInputState(); + + SetIMEState(aNewIMEState, widget); +} + PRUint32 nsIMEStateManager::GetNewIMEState(nsPresContext* aPresContext, nsIContent* aContent) @@ -204,18 +224,17 @@ nsIMEStateManager::GetNewIMEState(nsPresContext* aPresContext, } void -nsIMEStateManager::SetIMEState(nsPresContext* aPresContext, - PRUint32 aState, - nsIWidget* aKB) +nsIMEStateManager::SetIMEState(PRUint32 aState, + nsIWidget* aWidget) { if (aState & nsIContent::IME_STATUS_MASK_ENABLED) { PRUint32 state = nsContentUtils::GetWidgetStatusFromIMEStatus(aState); - aKB->SetIMEEnabled(state); + aWidget->SetIMEEnabled(state); } if (aState & nsIContent::IME_STATUS_MASK_OPENED) { PRBool open = !!(aState & nsIContent::IME_STATUS_OPEN); - aKB->SetIMEOpenState(open); + aWidget->SetIMEOpenState(open); } } diff --git a/content/events/src/nsIMEStateManager.h b/content/events/src/nsIMEStateManager.h index be799b46dbc..66a3f02ec2f 100644 --- a/content/events/src/nsIMEStateManager.h +++ b/content/events/src/nsIMEStateManager.h @@ -80,10 +80,11 @@ public: // Get the focused editor's selection and root static nsresult GetFocusSelectionAndRoot(nsISelection** aSel, nsIContent** aRoot); + // This method changes the current IME state forcedly. + // So, the caller should check whether you're focused or not. + static void ChangeIMEStateTo(PRUint32 aNewIMEState); protected: - static void SetIMEState(nsPresContext* aPresContext, - PRUint32 aState, - nsIWidget* aKB); + static void SetIMEState(PRUint32 aState, nsIWidget* aWidget); static PRUint32 GetNewIMEState(nsPresContext* aPresContext, nsIContent* aContent); diff --git a/editor/libeditor/base/Makefile.in b/editor/libeditor/base/Makefile.in index b95df0a01d0..756fa2ce4a6 100644 --- a/editor/libeditor/base/Makefile.in +++ b/editor/libeditor/base/Makefile.in @@ -91,4 +91,5 @@ include $(topsrcdir)/config/rules.mk INCLUDES += \ -I$(topsrcdir)/content/base/src \ + -I$(topsrcdir)/content/events/src \ $(NULL) diff --git a/editor/libeditor/base/nsEditor.cpp b/editor/libeditor/base/nsEditor.cpp index 29772e34091..85c81189dea 100644 --- a/editor/libeditor/base/nsEditor.cpp +++ b/editor/libeditor/base/nsEditor.cpp @@ -45,6 +45,8 @@ #include "nsIDOMHTMLElement.h" #include "nsIDOMNSHTMLElement.h" #include "nsPIDOMEventTarget.h" +#include "nsIMEStateManager.h" +#include "nsFocusManager.h" #include "nsIPrefBranch.h" #include "nsIPrefService.h" #include "nsUnicharUtils.h" @@ -219,13 +221,17 @@ nsEditor::Init(nsIDOMDocument *aDoc, nsIPresShell* aPresShell, nsIContent *aRoot if ((nsnull==aDoc) || (nsnull==aPresShell)) return NS_ERROR_NULL_POINTER; + // First only set flags, but other stuff shouldn't be initialized now. + // Don't move this call after initializing mDocWeak and mPresShellWeak. + // SetFlags() can check whether it's called during initialization or not by + // them. Note that SetFlags() will be called by PostCreate(). + nsresult rv = SetFlags(aFlags); + NS_ASSERTION(NS_SUCCEEDED(rv), "SetFlags() failed"); + mDocWeak = do_GetWeakReference(aDoc); // weak reference to doc mPresShellWeak = do_GetWeakReference(aPresShell); // weak reference to pres shell mSelConWeak = do_GetWeakReference(aSelCon); // weak reference to selectioncontroller - nsresult rv = SetFlags(aFlags); - NS_ASSERTION(NS_SUCCEEDED(rv), "SetFlags() failed"); - nsCOMPtr ps = do_QueryReferent(mPresShellWeak); if (!ps) return NS_ERROR_NOT_INITIALIZED; @@ -279,8 +285,8 @@ nsEditor::Init(nsIDOMDocument *aDoc, nsIPresShell* aPresShell, nsIContent *aRoot NS_IMETHODIMP nsEditor::PostCreate() { - // Set up spellchecking - nsresult rv = SyncRealTimeSpell(); + // Synchronize some stuff for the flags + nsresult rv = SetFlags(mFlags); NS_ENSURE_SUCCESS(rv, rv); // Set up listeners @@ -437,8 +443,29 @@ nsEditor::SetFlags(PRUint32 aFlags) { mFlags = aFlags; + if (!mDocWeak || !mPresShellWeak) { + // If we're initializing, we shouldn't do anything now. + // SetFlags() will be called by PostCreate(), + // we should synchronize some stuff for the flags at that time. + return NS_OK; + } + // Changing the flags can change whether spellchecking is on, so re-sync it - SyncRealTimeSpell(); + nsresult rv = SyncRealTimeSpell(); + NS_ENSURE_SUCCESS(rv, rv); + + // Might be changing editable state, so, we need to reset current IME state + // if we're focused. + if (HasFocus()) { + // Use "enable" for the default value because if IME is disabled + // unexpectedly, it makes serious a11y problem. + PRUint32 newState = nsIContent::IME_STATUS_ENABLE; + rv = GetPreferredIMEState(&newState); + if (NS_SUCCEEDED(rv)) { + nsIMEStateManager::ChangeIMEStateTo(newState); + } + } + return NS_OK; } @@ -5155,3 +5182,18 @@ nsEditor::IsModifiableNode(nsIDOMNode *aNode) { return PR_TRUE; } + +PRBool +nsEditor::HasFocus() +{ + nsCOMPtr piTarget = GetPIDOMEventTarget(); + if (!piTarget) { + return PR_FALSE; + } + + nsFocusManager* fm = nsFocusManager::GetFocusManager(); + NS_ENSURE_TRUE(fm, PR_FALSE); + + nsCOMPtr content = fm->GetFocusedContent(); + return SameCOMIdentity(content, piTarget); +} diff --git a/editor/libeditor/base/nsEditor.h b/editor/libeditor/base/nsEditor.h index 8e0097af1fd..7d8932ebca4 100644 --- a/editor/libeditor/base/nsEditor.h +++ b/editor/libeditor/base/nsEditor.h @@ -647,6 +647,9 @@ public: return (mFlags & nsIPlaintextEditor::eEditorDontEchoPassword) != 0; } + // Whether the editor has focus or not. + virtual PRBool HasFocus(); + protected: PRUint32 mModCount; // number of modifications (for undo/redo stack) diff --git a/editor/libeditor/base/nsEditorEventListener.cpp b/editor/libeditor/base/nsEditorEventListener.cpp index 02ab24651e0..1f8b9ad19db 100644 --- a/editor/libeditor/base/nsEditorEventListener.cpp +++ b/editor/libeditor/base/nsEditorEventListener.cpp @@ -917,6 +917,9 @@ FindSelectionRoot(nsEditor *aEditor, nsIContent *aContent) return root; } + // XXX If the editor is HTML editor and has readonly flag, shouldn't return + // the element which has contenteditable="true"? However, such case isn't + // there without chrome permission script. if (aEditor->IsReadonly()) { // We still want to allow selection in a readonly editor. nsCOMPtr rootElement; @@ -936,6 +939,11 @@ FindSelectionRoot(nsEditor *aEditor, nsIContent *aContent) // For non-readonly editors we want to find the root of the editable subtree // containing aContent. + // XXX This is wrong in meaning of this method if the editor is form control. + // The editable form controls are also have NODE_IS_EDITABLE flag but it can + // be in contenteditable elements. So, at this time, this climbs up to the + // root editable element. But fortunately, we don't have any problem by + // another issue, see the XXX comment in focus event handler. nsIContent *parent, *content = aContent; while ((parent = content->GetParent()) && parent->HasFlag(NODE_IS_EDITABLE)) { content = parent; @@ -965,6 +973,12 @@ nsEditorEventListener::Focus(nsIDOMEvent* aEvent) PRBool targetIsEditableDoc = PR_FALSE; nsCOMPtr editableRoot; if (content) { + // XXX If the focus event target is a form control in contenteditable + // element, perhaps, the parent HTML editor should do nothing by this + // handler. However, FindSelectionRoot() returns the root element of the + // contenteditable editor. So, the editableRoot value is invalid for + // the plain text editor, and it will be set to the wrong limiter of + // the selection. However, fortunately, actual bugs are not found yet. editableRoot = FindSelectionRoot(mEditor, content); // make sure that the element is really focused in case an earlier diff --git a/editor/libeditor/html/nsHTMLEditor.cpp b/editor/libeditor/html/nsHTMLEditor.cpp index 785db48c077..e0d69384cf0 100644 --- a/editor/libeditor/html/nsHTMLEditor.cpp +++ b/editor/libeditor/html/nsHTMLEditor.cpp @@ -97,6 +97,8 @@ #include "SetDocTitleTxn.h" #include "nsGUIEvent.h" #include "nsTextFragment.h" +#include "nsFocusManager.h" +#include "nsPIDOMWindow.h" // netwerk #include "nsIURI.h" @@ -5632,3 +5634,78 @@ nsHTMLEditor::GetReturnInParagraphCreatesNewParagraph(PRBool *aCreatesNewParagra *aCreatesNewParagraph = mCRInParagraphCreatesParagraph; return NS_OK; } + +PRBool +nsHTMLEditor::HasFocus() +{ + NS_ENSURE_TRUE(mDocWeak, PR_FALSE); + + nsFocusManager* fm = nsFocusManager::GetFocusManager(); + NS_ENSURE_TRUE(fm, PR_FALSE); + + nsCOMPtr focusedContent = fm->GetFocusedContent(); + + nsCOMPtr doc = do_QueryReferent(mDocWeak); + PRBool inDesignMode = doc->HasFlag(NODE_IS_EDITABLE); + if (!focusedContent) { + // in designMode, nobody gets focus in most cases. + return inDesignMode ? OurWindowHasFocus() : PR_FALSE; + } + + if (inDesignMode) { + return OurWindowHasFocus() ? + nsContentUtils::ContentIsDescendantOf(focusedContent, doc) : PR_FALSE; + } + + // We're HTML editor for contenteditable + + // If the focused content isn't editable, or it has independent selection, + // we don't have focus. + if (!focusedContent->HasFlag(NODE_IS_EDITABLE) || + IsIndependentSelectionContent(focusedContent)) { + return PR_FALSE; + } + nsCOMPtr rootContent = do_QueryInterface(GetRoot()); + if (!rootContent) { + return PR_FALSE; + } + // If the focused content is a descendant of our editor root, we're focused. + return nsContentUtils::ContentIsDescendantOf(focusedContent, rootContent); +} + +PRBool +nsHTMLEditor::OurWindowHasFocus() +{ + NS_ENSURE_TRUE(mDocWeak, PR_FALSE); + nsIFocusManager* fm = nsFocusManager::GetFocusManager(); + NS_ENSURE_TRUE(fm, PR_FALSE); + nsCOMPtr focusedWindow; + fm->GetFocusedWindow(getter_AddRefs(focusedWindow)); + if (!focusedWindow) { + return PR_FALSE; + } + nsCOMPtr doc = do_QueryReferent(mDocWeak); + nsCOMPtr ourWindow = do_QueryInterface(doc->GetWindow()); + return ourWindow == focusedWindow; +} + +PRBool +nsHTMLEditor::IsIndependentSelectionContent(nsIContent* aContent) +{ + NS_PRECONDITION(aContent, "aContent must not be null"); + nsIFrame* frame = aContent->GetPrimaryFrame(); + return (frame && (frame->GetStateBits() & NS_FRAME_INDEPENDENT_SELECTION)); +} + +NS_IMETHODIMP +nsHTMLEditor::GetPreferredIMEState(PRUint32 *aState) +{ + if (IsReadonly() || IsDisabled()) { + *aState = nsIContent::IME_STATUS_DISABLE; + return NS_OK; + } + + // HTML editor don't prefer the CSS ime-mode because IE didn't do so too. + *aState = nsIContent::IME_STATUS_ENABLE; + return NS_OK; +} diff --git a/editor/libeditor/html/nsHTMLEditor.h b/editor/libeditor/html/nsHTMLEditor.h index 3ea86116de4..64884fb92cb 100644 --- a/editor/libeditor/html/nsHTMLEditor.h +++ b/editor/libeditor/html/nsHTMLEditor.h @@ -146,6 +146,10 @@ public: NS_IMETHODIMP HandleKeyPress(nsIDOMKeyEvent* aKeyEvent); NS_IMETHOD GetIsDocumentEditable(PRBool *aIsDocumentEditable); NS_IMETHODIMP BeginningOfDocument(); + virtual PRBool HasFocus(); + + /* ------------ nsIEditorIMESupport overrides ------------ */ + NS_IMETHOD GetPreferredIMEState(PRUint32 *aState); /* ------------ nsIHTMLEditor methods -------------- */ @@ -725,6 +729,12 @@ protected: nsresult HasStyleOrIdOrClass(nsIDOMElement * aElement, PRBool *aHasStyleOrIdOrClass); nsresult RemoveElementIfNoStyleOrIdOrClass(nsIDOMElement * aElement, nsIAtom * aTag); + // Whether the outer window of the DOM event target has focus or not. + PRBool OurWindowHasFocus(); + // Whether the content has independent selection or not. E.g., input field, + // password field and textarea element. At that time, this returns TRUE. + PRBool IsIndependentSelectionContent(nsIContent* aContent); + // Data members protected: diff --git a/editor/libeditor/html/tests/Makefile.in b/editor/libeditor/html/tests/Makefile.in index 743bc925a03..aac275544e8 100644 --- a/editor/libeditor/html/tests/Makefile.in +++ b/editor/libeditor/html/tests/Makefile.in @@ -54,6 +54,7 @@ _TEST_FILES = \ test_bug487524.html \ test_bug525389.html \ test_bug537046.html \ + test_contenteditable_focus.html \ test_select_all_without_body.html \ file_select_all_without_body.html \ $(NULL) diff --git a/editor/libeditor/html/tests/test_contenteditable_focus.html b/editor/libeditor/html/tests/test_contenteditable_focus.html new file mode 100644 index 00000000000..2da327ff405 --- /dev/null +++ b/editor/libeditor/html/tests/test_contenteditable_focus.html @@ -0,0 +1,216 @@ + + + Test for contenteditable focus + + + + + +
+ First text in this document.
+
+
+
+
+
+ editable contents.
+
+
+
+
+
+ span element in noneditable in editor
+
+
+
+
+
+ span element in editor
+
+
+ other editor. +
+
+ +
+
+ + + + + diff --git a/widget/tests/test_imestate.html b/widget/tests/test_imestate.html index 73b98367ddb..517111c9625 100644 --- a/widget/tests/test_imestate.html +++ b/widget/tests/test_imestate.html @@ -1,4 +1,4 @@ - + Test for IME state controling