Bug 653230, add panels with focusable elements into the document tab navigation order, r=smaug

This commit is contained in:
Neil Deakin 2012-02-23 16:02:33 -05:00
parent ef8516d8a0
commit 463ad224a7
6 changed files with 332 additions and 47 deletions

View File

@ -2343,8 +2343,17 @@ nsFocusManager::DetermineElementToMoveFocus(nsPIDOMWindow* aWindow,
return NS_OK;
nsCOMPtr<nsIContent> 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<nsPIDOMWindow> focusedWindow;
startContent = GetFocusedDescendant(aWindow, true, getter_AddRefs(focusedWindow));
}
else {
startContent = aWindow->GetFocusedNode();
}
}
nsCOMPtr<nsIDocument> 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<nsIFrame *> 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<nsIContent> 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<nsIDocument> doc;
nsCOMPtr<nsIDocShellTreeItem> 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<nsIWebNavigation> 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<nsIDocShellTreeItem> curItem = startItem;
nsCOMPtr<nsIDocShellTreeItem> 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<nsIDocShellTreeItem> 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<nsPIDOMWindow> 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<nsPIDOMWindow> 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<nsIDocShellTreeItem> 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<nsIDocument> 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<nsIContent> 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<nsIContent> 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;

View File

@ -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.

View File

@ -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))

View File

@ -3,7 +3,8 @@
<script type="application/javascript"
src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
<script>
SimpleTest.waitForFocus(function () opener.framesetWindowLoaded(window));
if (opener)
SimpleTest.waitForFocus(function () opener.framesetWindowLoaded(window));
</script>
<frameset rows="30%, 70%">

View File

@ -0,0 +1,28 @@
<?xml version="1.0"?>
<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?>
<window onload="runTest();"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<script type="application/javascript"
src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
<script>
SimpleTest.waitForExplicitFinish();
function runTest()
{
window.open("window_focus_docnav.xul", "_blank", "chrome,width=600,height=550");
}
</script>
<body xmlns="http://www.w3.org/1999/xhtml">
<p id="display">
</p>
<div id="content" style="display: none">
</div>
<pre id="test">
</pre>
</body>
</window>

View File

@ -0,0 +1,104 @@
<?xml version="1.0"?>
<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
<window onload="start()"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<script type="application/javascript"
src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/>
<textbox id="textbox"/>
<panel id="panel2" onpopupshown="runTests(this, 2);" onpopuphidden="document.getElementById('panel').hidePopup()">
<textbox id="p2textbox" value="Popup2"/>
</panel>
<panel id="panel" onpopupshown="runTests(this, 1);"
onpopuphidden="done()">
<textbox id="p1textbox" value="Popup1"/>
</panel>
<browser id="browser" type="content" src="focus_frameset.html" width="500" height="400"/>
<script type="application/javascript">
<![CDATA[
var fm = Components.classes["@mozilla.org/focus-manager;1"].
getService(Components.interfaces.nsIFocusManager);
function is(l, r, n) { window.opener.wrappedJSObject.SimpleTest.is(l,r,n); }
function ok(v, n) { window.opener.wrappedJSObject.SimpleTest.ok(v,n); }
function done()
{
var opener = window.opener;
window.close();
opener.wrappedJSObject.SimpleTest.finish();
}
function previous(expectedWindow, expectedElement, desc)
{
synthesizeKey("VK_F6", { shiftKey: true });
is(fm.focusedWindow, expectedWindow, desc);
is(fm.focusedElement, expectedElement, desc + " element");
}
function next(expectedWindow, expectedElement, desc)
{
synthesizeKey("VK_F6", { });
is(fm.focusedWindow, expectedWindow, desc);
is(fm.focusedElement, expectedElement, desc + " element" + "::" + (fm.focusedElement ? fm.focusedElement.parentNode.id : "<none>"));
}
// This test runs through three cases. Document navigation forward and
// backward using the F6 key when no popups are open, with one popup open and
// with two popups open.
function runTests(panel, popupCount)
{
if (!popupCount || popupCount > 2)
popupCount = 0;
fm.clearFocus(window);
var childwin = document.getElementById("browser").contentWindow;
if (popupCount) {
if (popupCount == 2) {
next(window, document.getElementById("p2textbox").inputField, "First into popup 2 with " + popupCount);
}
next(window, document.getElementById("p1textbox").inputField, "First into popup 1 with " + popupCount);
}
next(childwin.frames[0], childwin.frames[0].document.documentElement, "First with " + popupCount);
next(childwin.frames[1], childwin.frames[1].document.documentElement, "Second with " + popupCount);
previous(childwin.frames[0], childwin.frames[0].document.documentElement, "Second back with " + popupCount);
if (popupCount) {
previous(window, document.getElementById("p1textbox").inputField, "First back from popup 1 with " + popupCount);
if (popupCount == 2) {
previous(window, document.getElementById("p2textbox").inputField, "First back from popup 2 with " + popupCount);
}
}
previous(window, document.getElementById("textbox").inputField, "First back with " + popupCount);
if (panel == document.getElementById("panel"))
document.getElementById("panel2").openPopup(null, "after_start", 100, 20);
else if (panel == document.getElementById("panel2"))
panel.hidePopup();
else
document.getElementById("panel").openPopup(null, "after_start");
}
function start()
{
window.opener.wrappedJSObject.SimpleTest.waitForExplicitFinish();
window.opener.wrappedJSObject.SimpleTest.waitForFocus(
function() { runTests(null, 0); },
document.getElementById("browser").contentWindow);
}
]]></script>
</window>