Bug 544277 IME became unusable when switching focus on Gmail RTF Editor r=enn

This commit is contained in:
Masayuki Nakano 2010-04-21 22:13:08 +09:00
parent 2bd8f8211d
commit 189d5d4756
7 changed files with 174 additions and 17 deletions

View File

@ -2749,9 +2749,33 @@ nsEventStateManager::PostHandleEvent(nsPresContext* aPresContext,
activeContent = mCurrentTarget->GetContent();
}
nsIFrame* currFrame = mCurrentTarget;
// When a root content which isn't editable but has an editable HTML
// <body> element is clicked, we should redirect the focus to the
// the <body> element. E.g., when an user click bottom of the editor
// where is outside of the <body> element, the <body> should be focused
// and the user can edit immediately after that.
//
// NOTE: The newFocus isn't editable that also means it's not in
// designMode. In designMode, all contents are not focusable.
if (newFocus && !newFocus->IsEditable()) {
nsIDocument *doc = newFocus->GetCurrentDoc();
if (doc && newFocus == doc->GetRootContent()) {
nsIContent *bodyContent =
nsLayoutUtils::GetEditableRootContentByContentEditable(doc);
if (bodyContent) {
nsIFrame* bodyFrame = bodyContent->GetPrimaryFrame();
if (bodyFrame) {
currFrame = bodyFrame;
newFocus = bodyContent;
}
}
}
}
// When the mouse is pressed, the default action is to focus the
// target. Look for the nearest enclosing focusable frame.
nsIFrame* currFrame = mCurrentTarget;
while (currFrame) {
// If the mousedown happened inside a popup, don't
// try to set focus on one of its containing elements

View File

@ -1273,6 +1273,23 @@ nsFocusManager::IsWindowVisible(nsPIDOMWindow* aWindow)
return visible;
}
PRBool
nsFocusManager::IsNonFocusableRoot(nsIContent* aContent)
{
NS_PRECONDITION(aContent, "aContent must not be NULL");
NS_PRECONDITION(aContent->IsInDoc(), "aContent must be in a document");
// If aContent is in designMode, the root element is not focusable.
// NOTE: in designMode, most elements are not focusable, just the document is
// focusable.
// Also, if aContent is not editable but it isn't in designMode, it's not
// focusable.
nsIDocument* doc = aContent->GetCurrentDoc();
NS_ASSERTION(doc, "aContent must have current document");
return aContent == doc->GetRootContent() &&
(doc->HasFlag(NODE_IS_EDITABLE) || !aContent->IsEditable());
}
nsIContent*
nsFocusManager::CheckIfFocusable(nsIContent* aContent, PRUint32 aFlags)
{
@ -1396,9 +1413,11 @@ nsFocusManager::Blur(nsPIDOMWindow* aWindowToClear,
PRINTTAGF("**Element %s has been blurred\n", content);
#endif
PRBool isRootContent = content && content == content->GetCurrentDoc()->GetRootContent();
// Don't fire blur event on the root content which isn't editable.
PRBool sendBlurEvent =
content && content->IsInDoc() && !IsNonFocusableRoot(content);
if (content) {
if (!isRootContent) {
if (sendBlurEvent) {
// unusual to pass a content node to SetContentState on a blur,
// but we are just calling it to get the ContentStatesChanged notifications
nsPresContext* presContext = presShell->GetPresContext();
@ -1427,7 +1446,7 @@ nsFocusManager::Blur(nsPIDOMWindow* aWindowToClear,
}
PRBool result = PR_TRUE;
if (content && !isRootContent) {
if (sendBlurEvent) {
// if there is an active window, update commands. If there isn't an active
// window, then this was a blur caused by the active window being lowered,
// so there is no need to update the commands
@ -1589,11 +1608,9 @@ nsFocusManager::Focus(nsPIDOMWindow* aWindow,
mFocusedContent = aContent;
aWindow->SetFocusedNode(aContent, focusMethod);
// don't fire events on the root content
PRBool isRootContent = aContent &&
aContent->IsInDoc() &&
aContent == aContent->GetCurrentDoc()->GetRootContent();
if (!isRootContent) {
PRBool sendFocusEvent =
aContent && aContent->IsInDoc() && !IsNonFocusableRoot(aContent);
if (sendFocusEvent) {
// if the focused element changed, scroll it into view
if (aFocusChanged)
ScrollIntoView(presShell, aContent, aFlags);
@ -1628,6 +1645,13 @@ nsFocusManager::Focus(nsPIDOMWindow* aWindow,
aContent, aFlags & FOCUSMETHOD_MASK, aWindowRaised);
nsIMEStateManager::OnTextStateFocus(presContext, aContent);
} else {
nsPresContext* presContext = presShell->GetPresContext();
nsIMEStateManager::OnTextStateBlur(presContext, nsnull);
nsIMEStateManager::OnChangeFocus(presContext, nsnull);
if (!aWindowRaised) {
aWindow->UpdateCommands(NS_LITERAL_STRING("focus"));
}
}
}
else {
@ -2190,8 +2214,19 @@ nsFocusManager::DetermineElementToMoveFocus(nsPIDOMWindow* aWindow,
rootContent = popupFrame->GetContent();
NS_ASSERTION(rootContent, "Popup frame doesn't have a content node");
}
else if (!forward && startContent == rootContent) {
doNavigation = PR_FALSE;
else if (!forward) {
// If focus moves backward and when current focused node is root
// content or <body> element which is editable by contenteditable
// attribute, focus should move to its parent document.
if (startContent == rootContent) {
doNavigation = PR_FALSE;
} else {
nsIDocument* doc = startContent->GetCurrentDoc();
if (startContent ==
nsLayoutUtils::GetEditableRootContentByContentEditable(doc)) {
doNavigation = PR_FALSE;
}
}
}
}
else {
@ -2463,8 +2498,11 @@ nsFocusManager::GetNextTabbableContent(nsIPresShell* aPresShell,
NS_ENSURE_SUCCESS(rv, rv);
if (iterStartContent == aRootContent) {
if (!aForward)
if (!aForward) {
frameTraversal->Last();
} else if (aRootContent->IsFocusable()) {
frameTraversal->Next();
}
}
else if (getNextFrame &&
(!iterStartContent || iterStartContent->Tag() != nsGkAtoms::area ||
@ -2532,7 +2570,17 @@ nsFocusManager::GetNextTabbableContent(nsIPresShell* aPresShell,
// frame so that the canvas becomes focused.
nsCOMPtr<nsPIDOMWindow> subframe = subdoc->GetWindow();
if (subframe) {
*aResultContent = GetRootForFocus(subframe, subdoc, PR_FALSE, PR_TRUE);
// If the subframe body is editable by contenteditable,
// we should set the editor's root element rather than the
// actual root element. Otherwise, we should set the focus
// to the root content.
*aResultContent =
nsLayoutUtils::GetEditableRootContentByContentEditable(subdoc);
if (!*aResultContent ||
!((*aResultContent)->GetPrimaryFrame())) {
*aResultContent =
GetRootForFocus(subframe, subdoc, PR_FALSE, PR_TRUE);
}
if (*aResultContent) {
NS_ADDREF(*aResultContent);
return NS_OK;
@ -2739,10 +2787,8 @@ nsFocusManager::GetRootForFocus(nsPIDOMWindow* aWindow,
nsIContent *rootContent = aDocument->GetRootContent();
if (rootContent) {
if (aCheckVisibility) {
nsIPresShell* presShell = aDocument->GetPrimaryShell();
if (!presShell || !rootContent->GetPrimaryFrame())
return nsnull;
if (aCheckVisibility && !rootContent->GetPrimaryFrame()) {
return nsnull;
}
// Finally, check if this is a frameset

View File

@ -157,6 +157,15 @@ protected:
*/
PRBool IsWindowVisible(nsPIDOMWindow* aWindow);
/**
* Returns true if aContent is a root element and not focusable.
* I.e., even if aContent is editable root element, this returns true when
* the document is in designMode.
*
* @param aContent must not be null and must be in a document.
*/
PRBool IsNonFocusableRoot(nsIContent* aContent);
/**
* Checks and returns aContent if it may be focused, another content node if
* the focus should be retargeted at another node, or null if the node

View File

@ -43,6 +43,8 @@
#include "nsIFormControlFrame.h"
#include "nsPresContext.h"
#include "nsIContent.h"
#include "nsIDOMDocument.h"
#include "nsIDOMHTMLDocument.h"
#include "nsFrameList.h"
#include "nsGkAtoms.h"
#include "nsIAtom.h"
@ -3550,6 +3552,40 @@ nsLayoutUtils::SurfaceFromElement(nsIDOMElement *aElement,
return result;
}
/* static */
nsIContent*
nsLayoutUtils::GetEditableRootContentByContentEditable(nsIDocument* aDocument)
{
// If the document is in designMode we should return NULL.
if (!aDocument || aDocument->HasFlag(NODE_IS_EDITABLE)) {
return nsnull;
}
// contenteditable only works with HTML document.
// Note: Use nsIDOMHTMLDocument rather than nsIHTMLDocument for getting the
// body node because nsIDOMHTMLDocument::GetBody() does something
// additional work for some cases and nsEditor uses them.
nsCOMPtr<nsIDOMHTMLDocument> domHTMLDoc = do_QueryInterface(aDocument);
if (!domHTMLDoc) {
return nsnull;
}
nsIContent* rootContent = aDocument->GetRootContent();
if (rootContent && rootContent->IsEditable()) {
return rootContent;
}
// If there are no editable root element, check its <body> element.
// Note that the body element could be <frameset> element.
nsCOMPtr<nsIDOMHTMLElement> body;
nsresult rv = domHTMLDoc->GetBody(getter_AddRefs(body));
nsCOMPtr<nsIContent> content = do_QueryInterface(body);
if (NS_SUCCEEDED(rv) && content && content->IsEditable()) {
return content;
}
return nsnull;
}
nsSetAttrRunnable::nsSetAttrRunnable(nsIContent* aContent, nsIAtom* aAttrName,
const nsAString& aValue)
: mContent(aContent),

View File

@ -1157,6 +1157,29 @@ public:
static SurfaceFromElementResult SurfaceFromElement(nsIDOMElement *aElement,
PRUint32 aSurfaceFlags = 0);
/**
* When the document is editable by contenteditable attribute of its root
* content or body content.
*
* Be aware, this returns NULL if it's in designMode.
*
* For example:
*
* <html contenteditable="true"><body></body></html>
* returns the <html>.
*
* <html><body contenteditable="true"></body></html>
* <body contenteditable="true"></body>
* With these cases, this returns the <body>.
* NOTE: The latter case isn't created normally, however, it can be
* created by script with XHTML.
*
* <body><p contenteditable="true"></p></body>
* returns NULL because <body> isn't editable.
*/
static nsIContent*
GetEditableRootContentByContentEditable(nsIDocument* aDocument);
};
class nsSetAttrRunnable : public nsRunnable

View File

@ -69,6 +69,7 @@ _CHROME_FILES = test_bug343416.xul \
test_wheeltransaction.xul \
window_wheeltransaction.xul \
test_imestate.html \
window_imestate_iframes.html \
test_plugin_scroll_consistency.html \
test_composition_text_querycontent.xul \
window_composition_text_querycontent.xul \

View File

@ -477,6 +477,13 @@ function runReadonlyChangingTest()
}
}
function runEditableSubframeTests()
{
window.open("window_imestate_iframes.html", "_blank",
"width=600,height=600");
}
function runTests()
{
if (!kIMEEnabledSupported && !kIMEOpenSupported)
@ -519,6 +526,17 @@ function runTests()
// XXX currently, readonly attribute changing doesn't work fine. bug 488420.
// runReadonlyChangingTest();
runASyncTests();
}
function runASyncTests()
{
// The tests must call onFinish() method.
runEditableSubframeTests();
}
function onFinish()
{
SimpleTest.finish();
}