From 463ad224a7cd3d4c71e7c33540e8da43c3b21535 Mon Sep 17 00:00:00 2001 From: Neil Deakin Date: Thu, 23 Feb 2012 16:02:33 -0500 Subject: [PATCH] Bug 653230, add panels with focusable elements into the document tab navigation order, r=smaug --- dom/base/nsFocusManager.cpp | 215 ++++++++++++++---- dom/base/nsFocusManager.h | 27 ++- dom/tests/mochitest/chrome/Makefile.in | 2 + .../mochitest/chrome/focus_frameset.html | 3 +- .../mochitest/chrome/test_focus_docnav.xul | 28 +++ .../mochitest/chrome/window_focus_docnav.xul | 104 +++++++++ 6 files changed, 332 insertions(+), 47 deletions(-) create mode 100644 dom/tests/mochitest/chrome/test_focus_docnav.xul create mode 100644 dom/tests/mochitest/chrome/window_focus_docnav.xul diff --git a/dom/base/nsFocusManager.cpp b/dom/base/nsFocusManager.cpp index 431064971a3a..275b42ec2a3c 100644 --- a/dom/base/nsFocusManager.cpp +++ b/dom/base/nsFocusManager.cpp @@ -2343,8 +2343,17 @@ nsFocusManager::DetermineElementToMoveFocus(nsPIDOMWindow* aWindow, return NS_OK; nsCOMPtr startContent = aStartContent; - if (!startContent && aType != MOVEFOCUS_CARET) - startContent = aWindow->GetFocusedNode(); + if (!startContent && aType != MOVEFOCUS_CARET) { + if (aType == MOVEFOCUS_FORWARDDOC || aType == MOVEFOCUS_BACKWARDDOC) { + // When moving between documents, make sure to get the right + // starting content in a descendant. + nsCOMPtr focusedWindow; + startContent = GetFocusedDescendant(aWindow, true, getter_AddRefs(focusedWindow)); + } + else { + startContent = aWindow->GetFocusedNode(); + } + } nsCOMPtr doc; if (startContent) @@ -2362,11 +2371,11 @@ nsFocusManager::DetermineElementToMoveFocus(nsPIDOMWindow* aWindow, return NS_OK; } if (aType == MOVEFOCUS_FORWARDDOC) { - NS_IF_ADDREF(*aNextContent = GetNextTabbableDocument(true)); + NS_IF_ADDREF(*aNextContent = GetNextTabbableDocument(startContent, true)); return NS_OK; } if (aType == MOVEFOCUS_BACKWARDDOC) { - NS_IF_ADDREF(*aNextContent = GetNextTabbableDocument(false)); + NS_IF_ADDREF(*aNextContent = GetNextTabbableDocument(startContent, false)); return NS_OK; } @@ -3136,66 +3145,192 @@ nsFocusManager::GetPreviousDocShell(nsIDocShellTreeItem* aItem, } nsIContent* -nsFocusManager::GetNextTabbableDocument(bool aForward) +nsFocusManager::GetNextTabbablePanel(nsIDocument* aDocument, nsIFrame* aCurrentPopup, bool aForward) { + nsXULPopupManager* pm = nsXULPopupManager::GetInstance(); + if (!pm) + return nsnull; + + // Iterate through the array backwards if aForward is false. + nsTArray popups = pm->GetVisiblePopups(); + PRInt32 i = aForward ? 0 : popups.Length() - 1; + PRInt32 end = aForward ? popups.Length() : -1; + + for (; i != end; aForward ? i++ : i--) { + nsIFrame* popupFrame = popups[i]; + if (aCurrentPopup) { + // If the current popup is set, then we need to skip over this popup and + // wait until the currently focused popup is found. Once found, the + // current popup will be cleared so that the next popup is used. + if (aCurrentPopup == popupFrame) + aCurrentPopup = nsnull; + continue; + } + + // Skip over non-panels + if (popupFrame->GetContent()->Tag() != nsGkAtoms::panel || + (aDocument && popupFrame->GetContent()->GetCurrentDoc() != aDocument)) { + continue; + } + + // Find the first focusable content within the popup. If there isn't any + // focusable content in the popup, skip to the next popup. + nsIPresShell* presShell = popupFrame->PresContext()->GetPresShell(); + if (presShell) { + nsCOMPtr nextFocus; + nsIContent* popup = popupFrame->GetContent(); + nsresult rv = GetNextTabbableContent(presShell, popup, + nsnull, popup, + true, 1, false, + getter_AddRefs(nextFocus)); + if (NS_SUCCEEDED(rv) && nextFocus) { + return nextFocus.get(); + } + } + } + + return nsnull; +} + +nsIContent* +nsFocusManager::GetNextTabbableDocument(nsIContent* aStartContent, bool aForward) +{ + // If currentPopup is set, then the starting content is in a panel. + nsIFrame* currentPopup = nsnull; + nsCOMPtr doc; nsCOMPtr startItem; - if (mFocusedWindow) { + + if (aStartContent) { + doc = aStartContent->GetCurrentDoc(); + if (doc) { + startItem = do_QueryInterface(doc->GetWindow()->GetDocShell()); + } + + // Check if the starting content is inside a panel. Document navigation + // must start from this panel instead of the document root. + nsIContent* content = aStartContent; + while (content) { + if (content->NodeInfo()->Equals(nsGkAtoms::panel, kNameSpaceID_XUL)) { + currentPopup = content->GetPrimaryFrame(); + break; + } + content = content->GetParent(); + } + } + else if (mFocusedWindow) { startItem = do_QueryInterface(mFocusedWindow->GetDocShell()); + doc = do_QueryInterface(mFocusedWindow->GetExtantDocument()); } else { nsCOMPtr webnav = do_GetInterface(mActiveWindow); startItem = do_QueryInterface(webnav); + + if (mActiveWindow) { + doc = do_QueryInterface(mActiveWindow->GetExtantDocument()); + } } + if (!startItem) return nsnull; // perform a depth first search (preorder) of the docshell tree // looking for an HTML Frame or a chrome document - nsIContent* content = nsnull; + nsIContent* content = aStartContent; nsCOMPtr curItem = startItem; nsCOMPtr nextItem; do { - if (aForward) { - GetNextDocShell(curItem, getter_AddRefs(nextItem)); - if (!nextItem) { - // wrap around to the beginning, which is the top of the tree - startItem->GetRootTreeItem(getter_AddRefs(nextItem)); - } - } - else { - GetPreviousDocShell(curItem, getter_AddRefs(nextItem)); - if (!nextItem) { - // wrap around to the end, which is the last item in the tree - nsCOMPtr rootItem; - startItem->GetRootTreeItem(getter_AddRefs(rootItem)); - GetLastDocShell(rootItem, getter_AddRefs(nextItem)); + // If moving forward, check for a panel in the starting document. If one + // exists with focusable content, return that content instead of the next + // document. If currentPopup is set, then, another panel may exist. If no + // such panel exists, then continue on to check the next document. + // When moving backwards, and the starting content is in a panel, then + // check for additional panels in the starting document. If the starting + // content is not in a panel, move back to the previous document and check + // for panels there. + + bool checkPopups = false; + nsCOMPtr nextFrame = nsnull; + + if (doc && (aForward || currentPopup)) { + nsIContent* popupContent = GetNextTabbablePanel(doc, currentPopup, aForward); + if (popupContent) + return popupContent; + + if (!aForward && currentPopup) { + // The starting content was in a popup, yet no other popups were + // found. Move onto the starting content's document. + nextFrame = doc->GetWindow(); } } - curItem = nextItem; - nsCOMPtr nextFrame = do_GetInterface(nextItem); + // Look for the next or previous document. + if (!nextFrame) { + if (aForward) { + GetNextDocShell(curItem, getter_AddRefs(nextItem)); + if (!nextItem) { + // wrap around to the beginning, which is the top of the tree + startItem->GetRootTreeItem(getter_AddRefs(nextItem)); + } + } + else { + GetPreviousDocShell(curItem, getter_AddRefs(nextItem)); + if (!nextItem) { + // wrap around to the end, which is the last item in the tree + nsCOMPtr rootItem; + startItem->GetRootTreeItem(getter_AddRefs(rootItem)); + GetLastDocShell(rootItem, getter_AddRefs(nextItem)); + } + + // When going back to the previous document, check for any focusable + // popups in that previous document first. + checkPopups = true; + } + + curItem = nextItem; + nextFrame = do_GetInterface(nextItem); + } + if (!nextFrame) return nsnull; - nsCOMPtr doc = do_QueryInterface(nextFrame->GetExtantDocument()); - if (doc && !doc->EventHandlingSuppressed()) { - content = GetRootForFocus(nextFrame, doc, true, true); - if (content && !GetRootForFocus(nextFrame, doc, false, false)) { - // if the found content is in a chrome shell or a frameset, navigate - // forward one tabbable item so that the first item is focused. Note - // that we always go forward and not back here. - nsCOMPtr nextFocus; - Element* rootElement = doc->GetRootElement(); - nsIPresShell* presShell = doc->GetShell(); - if (presShell) { - nsresult rv = GetNextTabbableContent(presShell, rootElement, - nsnull, rootElement, - true, 1, false, - getter_AddRefs(nextFocus)); - return NS_SUCCEEDED(rv) ? nextFocus.get() : nsnull; - } + // Clear currentPopup for the next iteration + currentPopup = nsnull; + + // If event handling is suppressed, move on to the next document. Set + // content to null so that the popup check will be skipped on the next + // loop iteration. + doc = do_QueryInterface(nextFrame->GetExtantDocument()); + if (!doc || doc->EventHandlingSuppressed()) { + content = nsnull; + continue; + } + + if (checkPopups) { + // When iterating backwards, check the panels of the previous document + // first. If a panel exists that has focusable content, focus that. + // Otherwise, continue on to focus the document. + nsIContent* popupContent = GetNextTabbablePanel(doc, nsnull, false); + if (popupContent) + return popupContent; + } + + content = GetRootForFocus(nextFrame, doc, true, true); + if (content && !GetRootForFocus(nextFrame, doc, false, false)) { + // if the found content is in a chrome shell or a frameset, navigate + // forward one tabbable item so that the first item is focused. Note + // that we always go forward and not back here. + nsCOMPtr nextFocus; + Element* rootElement = doc->GetRootElement(); + nsIPresShell* presShell = doc->GetShell(); + if (presShell) { + nsresult rv = GetNextTabbableContent(presShell, rootElement, + nsnull, rootElement, + true, 1, false, + getter_AddRefs(nextFocus)); + return NS_SUCCEEDED(rv) ? nextFocus.get() : nsnull; } } + } while (!content); return content; diff --git a/dom/base/nsFocusManager.h b/dom/base/nsFocusManager.h index 3fd01094a39e..4fe8a04cd1e8 100644 --- a/dom/base/nsFocusManager.h +++ b/dom/base/nsFocusManager.h @@ -459,16 +459,31 @@ protected: nsIDocShellTreeItem** aResult); /** - * Get the tabbable next document from the currently focused frame if - * aForward is true, or the previously tabbable document if aForward is - * false. If this document is a chrome or frameset document, returns - * the first focusable element within this document, otherwise, returns - * the root node of the document. + * Determine the first panel with focusable content in document tab order + * from the given document. aForward indicates the direction to scan. If + * aCurrentPopup is set to a panel, the next or previous popup after + * aCurrentPopup after it is used. If aCurrentPopup is null, then the first + * or last popup is used. If a panel has no focusable content, it is skipped. + * Null is returned if no panel is open or no open panel contains a focusable + * element. + */ + nsIContent* GetNextTabbablePanel(nsIDocument* aDocument, nsIFrame* aCurrentPopup, bool aForward); + + /** + * Get the tabbable next document from aStartContent or, if null, the + * currently focused frame if aForward is true, or the previously tabbable + * document if aForward is false. If this document is a chrome or frameset + * document, returns the first focusable element within this document, + * otherwise, returns the root node of the document. + * + * + * Panels with focusable content are also placed in the cycling order, just + * after the document containing that panel. * * This method would be used for document navigation, which is typically * invoked by pressing F6. */ - nsIContent* GetNextTabbableDocument(bool aForward); + nsIContent* GetNextTabbableDocument(nsIContent* aStartContent, bool aForward); /** * Retreives a focusable element within the current selection of aWindow. diff --git a/dom/tests/mochitest/chrome/Makefile.in b/dom/tests/mochitest/chrome/Makefile.in index 917ef72f9d3c..2a32762664bf 100644 --- a/dom/tests/mochitest/chrome/Makefile.in +++ b/dom/tests/mochitest/chrome/Makefile.in @@ -74,6 +74,8 @@ _TEST_FILES = \ test_moving_xhr.xul \ test_nodesFromRect.html \ 489127.html \ + test_focus_docnav.xul \ + window_focus_docnav.xul \ $(NULL) ifeq (WINNT,$(OS_ARCH)) diff --git a/dom/tests/mochitest/chrome/focus_frameset.html b/dom/tests/mochitest/chrome/focus_frameset.html index 50792f8dc0cb..ace3d1575302 100644 --- a/dom/tests/mochitest/chrome/focus_frameset.html +++ b/dom/tests/mochitest/chrome/focus_frameset.html @@ -3,7 +3,8 @@ diff --git a/dom/tests/mochitest/chrome/test_focus_docnav.xul b/dom/tests/mochitest/chrome/test_focus_docnav.xul new file mode 100644 index 000000000000..48ce66facdfb --- /dev/null +++ b/dom/tests/mochitest/chrome/test_focus_docnav.xul @@ -0,0 +1,28 @@ + + + + + + + + + +

+

+ +
+
+ + +
diff --git a/dom/tests/mochitest/chrome/window_focus_docnav.xul b/dom/tests/mochitest/chrome/window_focus_docnav.xul new file mode 100644 index 000000000000..b51ae340cded --- /dev/null +++ b/dom/tests/mochitest/chrome/window_focus_docnav.xul @@ -0,0 +1,104 @@ + + + + + + + +