Bug 385275, handle tab navigation in popups properly, r+sr=roc

This commit is contained in:
enndeakin@sympatico.ca 2007-08-15 18:09:58 -07:00
Родитель 3a1335fceb
Коммит 239bab7b12
12 изменённых файлов: 357 добавлений и 51 удалений

Просмотреть файл

@ -98,7 +98,7 @@
onclick="checkForMiddleClick(this, event);"/>
<tooltip id="aHTMLTooltip" onpopupshowing="return FillInHTMLTooltip(document.tooltipNode);"/>
<panel type="autocomplete" chromedir="&locale.dir;" id="PopupAutoComplete"/>
<panel type="autocomplete" chromedir="&locale.dir;" id="PopupAutoComplete" noautofocus="true"/>
<popup id="toolbar-context-menu"
onpopupshowing="onViewToolbarsPopupShowing(event);">

Просмотреть файл

@ -426,6 +426,7 @@ GK_ATOM(isempty, "isempty")
GK_ATOM(isindex, "isindex")
GK_ATOM(ismap, "ismap")
GK_ATOM(kbd, "kbd")
GK_ATOM(noautofocus, "noautofocus")
GK_ATOM(key, "key")
GK_ATOM(keycode, "keycode")
GK_ATOM(keydown, "keydown")

Просмотреть файл

@ -93,6 +93,7 @@
#include "nsIContentViewer.h"
#include "nsIPrefBranch2.h"
#include "nsIObjectFrame.h"
#include "nsXULPopupManager.h"
#include "nsIServiceManager.h"
#include "nsIScriptSecurityManager.h"
@ -3378,6 +3379,33 @@ nsEventStateManager::ShiftFocusInternal(PRBool aForward, nsIContent* aStart)
}
}
// when a popup is open, we want to ensure that tab navigation occurs only
// within the most recently opened panel. If a popup is open, its frame will
// be stored in popupFrame.
nsIFrame* popupFrame = nsnull;
if (curFocusFrame) {
// check if the focus is currently inside a popup. Elements such as the
// autocomplete widget use the noautofocus attribute to allow the focus to
// remain outside the popup when it is opened.
popupFrame = nsLayoutUtils::GetClosestFrameOfType(curFocusFrame,
nsGkAtoms::menuPopupFrame);
}
else {
// if there is no focus, yet a panel is open, focus the
// first item in the popup
nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
if (pm) {
popupFrame = pm->GetTopPopup(ePopupTypePanel);
}
}
if (popupFrame) {
// Don't navigate outside of a popup, so pretend that the
// root content is the popup itself
rootContent = popupFrame->GetContent();
NS_ASSERTION(rootContent, "Popup frame doesn't have a content node");
}
nsCOMPtr<nsIContent> nextFocus;
nsIFrame* nextFocusFrame;
if (aForward || !docHasFocus || selectionFrame)
@ -3385,6 +3413,20 @@ nsEventStateManager::ShiftFocusInternal(PRBool aForward, nsIContent* aStart)
aForward, ignoreTabIndex || mCurrentTabIndex < 0,
getter_AddRefs(nextFocus), &nextFocusFrame);
if (popupFrame && !nextFocus) {
// if no content was found to focus, yet we are inside a popup, try again
// from the beginning or end of the popup. Set the current tab index to
// the beginning or end.
mCurrentTabIndex = aForward ? 1 : 0;
GetNextTabbableContent(rootContent, rootContent, nsnull,
aForward, ignoreTabIndex,
getter_AddRefs(nextFocus), &nextFocusFrame);
// if the same node was found, don't change the focus
if (startContent == nextFocus) {
nextFocus = nsnull;
}
}
// Clear out mCurrentTabIndex. It has a garbage value because of GetNextTabbableContent()'s side effects
// It will be set correctly when focus is changed via ChangeFocusWith()
mCurrentTabIndex = 0;

Просмотреть файл

@ -92,6 +92,8 @@ protected:
*/
nsIFrame* GetParentFrame(nsIFrame* aFrame);
// like GetParentFrame but returns null once a popup frame is reached
nsIFrame* GetParentFrameNotPopup(nsIFrame* aFrame);
nsIFrame* GetFirstChild(nsIFrame* aFrame);
nsIFrame* GetLastChild(nsIFrame* aFrame);
@ -278,9 +280,13 @@ nsFrameIterator::Last()
{
nsIFrame* result;
nsIFrame* parent = getCurrent();
while (!IsRootFrame(parent) && (result = GetParentFrame(parent)))
parent = result;
// If the current frame is a popup, don't move farther up the tree.
// Otherwise, get the nearest root frame or popup.
if (parent->GetType() != nsGkAtoms::menuPopupFrame) {
while (!IsRootFrame(parent) && (result = GetParentFrameNotPopup(parent)))
parent = result;
}
while ((result = GetLastChild(parent))) {
parent = result;
}
@ -327,7 +333,7 @@ nsFrameIterator::Next()
break;
}
else {
result = GetParentFrame(parent);
result = GetParentFrameNotPopup(parent);
if (!result || IsRootFrame(result) ||
(mLockScroll && result->GetType() == nsGkAtoms::scrollFrame)) {
result = nsnull;
@ -383,7 +389,7 @@ nsFrameIterator::Prev()
}
break;
} else {
result = GetParentFrame(parent);
result = GetParentFrameNotPopup(parent);
if (!result || IsRootFrame(result) ||
(mLockScroll && result->GetType() == nsGkAtoms::scrollFrame)) {
result = nsnull;
@ -415,6 +421,20 @@ nsFrameIterator::GetParentFrame(nsIFrame* aFrame)
return nsnull;
}
nsIFrame*
nsFrameIterator::GetParentFrameNotPopup(nsIFrame* aFrame)
{
if (mFollowOOFs)
aFrame = GetPlaceholderFrame(aFrame);
if (aFrame) {
nsIFrame* parent = aFrame->GetParent();
if (!IsPopupFrame(parent))
return parent;
}
return nsnull;
}
nsIFrame*
nsFrameIterator::GetFirstChild(nsIFrame* aFrame)
{
@ -422,7 +442,7 @@ nsFrameIterator::GetFirstChild(nsIFrame* aFrame)
if (result && mFollowOOFs) {
result = nsPlaceholderFrame::GetRealFrameFor(result);
if (result && IsPopupFrame(result))
if (IsPopupFrame(result))
result = GetNextSibling(result);
}
return result;
@ -435,7 +455,7 @@ nsFrameIterator::GetLastChild(nsIFrame* aFrame)
if (result && mFollowOOFs) {
result = nsPlaceholderFrame::GetRealFrameFor(result);
if (result && IsPopupFrame(result))
if (IsPopupFrame(result))
result = GetPrevSibling(result);
}
return result;
@ -453,7 +473,7 @@ nsFrameIterator::GetNextSibling(nsIFrame* aFrame)
result = nsPlaceholderFrame::GetRealFrameFor(result);
}
if (result && mFollowOOFs && IsPopupFrame(result))
if (mFollowOOFs && IsPopupFrame(result))
result = GetNextSibling(result);
return result;
@ -471,7 +491,7 @@ nsFrameIterator::GetPrevSibling(nsIFrame* aFrame)
result = nsPlaceholderFrame::GetRealFrameFor(result);
}
if (result && mFollowOOFs && IsPopupFrame(result))
if (mFollowOOFs && IsPopupFrame(result))
result = GetPrevSibling(result);
return result;
@ -527,7 +547,8 @@ nsFrameIterator::GetPlaceholderFrame(nsIFrame* aFrame)
PRBool
nsFrameIterator::IsPopupFrame(nsIFrame* aFrame)
{
return (aFrame->GetStyleDisplay()->mDisplay == NS_STYLE_DISPLAY_POPUP);
return (aFrame &&
aFrame->GetStyleDisplay()->mDisplay == NS_STYLE_DISPLAY_POPUP);
}
// nsVisualIterator implementation

Просмотреть файл

@ -76,6 +76,12 @@ class nsMenuBarFrame;
class nsIMenuParent;
class nsIDOMKeyEvent;
enum nsPopupType {
ePopupTypePanel,
ePopupTypeMenu,
ePopupTypeTooltip
};
/**
* nsNavigationDirection: an enum expressing navigation through the menus in
* terms which are independent of the directionality of the chrome. The
@ -153,7 +159,7 @@ class nsMenuChainItem
{
private:
nsMenuPopupFrame* mFrame; // the popup frame
PRPackedBool mIsMenu; // true if the popup is a menu, false for a panel
nsPopupType mPopupType; // the popup type of the frame
PRPackedBool mIsContext; // true for context menus
PRPackedBool mOnMenuBar; // true if the menu is on a menu bar
PRPackedBool mIgnoreKeys; // true if keyboard listeners should not be used
@ -162,12 +168,12 @@ private:
nsMenuChainItem* mChild;
public:
nsMenuChainItem(nsMenuPopupFrame* aFrame, PRBool aIsContext, PRBool aIsMenu)
nsMenuChainItem(nsMenuPopupFrame* aFrame, PRBool aIsContext, nsPopupType aPopupType)
: mFrame(aFrame),
mIsMenu(aIsMenu),
mPopupType(aPopupType),
mIsContext(aIsContext),
mOnMenuBar(PR_FALSE),
mIgnoreKeys(!aIsMenu), // always ignore keys on non-menus
mIgnoreKeys(aPopupType != ePopupTypeMenu), // always ignore keys on non-menus
mParent(nsnull),
mChild(nsnull)
{
@ -182,7 +188,8 @@ public:
nsIContent* Content();
nsMenuPopupFrame* Frame() { return mFrame; }
PRBool IsMenu() { return mIsMenu; }
nsPopupType PopupType() { return mPopupType; }
PRBool IsMenu() { return mPopupType == ePopupTypeMenu; }
PRBool IsContextMenu() { return mIsContext; }
PRBool IgnoreKeys() { return mIgnoreKeys; }
PRBool IsOnMenuBar() { return mOnMenuBar; }
@ -235,12 +242,12 @@ public:
nsXULPopupHidingEvent(nsIContent *aPopup,
nsIContent* aNextPopup,
nsIContent* aLastPopup,
PRBool aIsMenu,
nsPopupType aPopupType,
PRBool aDeselectMenu)
: mPopup(aPopup),
mNextPopup(aNextPopup),
mLastPopup(aLastPopup),
mIsMenu(aIsMenu),
mPopupType(aPopupType),
mDeselectMenu(aDeselectMenu)
{
NS_ASSERTION(aPopup, "null popup supplied to nsXULPopupHidingEvent constructor");
@ -253,7 +260,7 @@ private:
nsCOMPtr<nsIContent> mPopup;
nsCOMPtr<nsIContent> mNextPopup;
nsCOMPtr<nsIContent> mLastPopup;
PRBool mIsMenu;
nsPopupType mPopupType;
PRBool mDeselectMenu;
};
@ -463,6 +470,12 @@ public:
*/
PRBool IsPopupOpenForMenuParent(nsIMenuParent* aMenuParent);
/**
* Return the frame for the topmost open popup of a given type, or null if
* no popup of that type is open.
*/
nsIFrame* GetTopPopup(nsPopupType aType);
/**
* Return an array of all the open popup frames for menus, in order from
* top to bottom.
@ -573,7 +586,7 @@ protected:
nsMenuPopupFrame* aPopupFrame,
nsIContent* aNextPopup,
nsIContent* aLastPopup,
PRBool aIsMenu,
nsPopupType aPopupType,
PRBool aDeselectMenu);
/**
@ -585,12 +598,14 @@ protected:
* aMenu - should be set to the parent menu if this is a popup associated
* with a menu. Otherwise, should be null.
* aPresContext - the prescontext
* aPopupType - the popup frame's PopupType
* aIsContextMenu - true for context menus
* aSelectFirstItem - true to select the first item in the menu
*/
void FirePopupShowingEvent(nsIContent* aPopup,
nsIContent* aMenu,
nsPresContext* aPresContext,
nsPopupType aPopupType,
PRBool aIsContextMenu,
PRBool aSelectFirstItem);
@ -598,9 +613,10 @@ protected:
* Fire a popuphiding event and then hide the popup. This will be called
* recursively if aNextPopup and aLastPopup are set in order to hide a chain
* of open menus. If these are not set, only one popup is closed. However,
* if aIsMenu is true, yet the next popup is not a menu, then this ends the
* closing of popups. This allows a menulist inside a non-menu to close up
* the menu but not close up the panel it is contained within.
* if the popup type indicates a menu, yet the next popup is not a menu,
* then this ends the closing of popups. This allows a menulist inside a
* non-menu to close up the menu but not close up the panel it is contained
* within.
*
* The caller must keep a strong reference to aPopup, aNextPopup and aLastPopup.
*
@ -608,14 +624,14 @@ protected:
* aNextPopup - the next popup to hide
* aLastPopup - the last popup in the chain to hide
* aPresContext - nsPresContext for the popup's frame
* aIsMenu - true if aPopup is a menu.
* aPopupType - the PopupType of the frame.
* aDeselectMenu - true to unhighlight the menu when hiding it
*/
void FirePopupHidingEvent(nsIContent* aPopup,
nsIContent* aNextPopup,
nsIContent* aLastPopup,
nsPresContext *aPresContext,
PRBool aIsMenu,
nsPopupType aPopupType,
PRBool aDeselectMenu);
/**

Просмотреть файл

@ -58,12 +58,6 @@
#include "nsITimer.h"
enum nsPopupType {
ePopupTypePanel,
ePopupTypeMenu,
ePopupTypeTooltip
};
// XUL popups can be in several different states. When opening a popup, the
// state changes as follows:
// ePopupClosed - initial state
@ -229,7 +223,7 @@ public:
// FindMenuWithShortcut
nsMenuFrame* Enter();
PRInt32 PopupType() const { return mPopupType; }
nsPopupType PopupType() const { return mPopupType; }
PRBool IsMenu() { return mPopupType == ePopupTypeMenu; }
PRBool IsOpen() { return mPopupState == ePopupOpen || mPopupState == ePopupOpenAndVisible; }
PRBool HasOpenChanged() { return mIsOpenChanged; }

Просмотреть файл

@ -350,7 +350,7 @@ nsXULPopupManager::ShowMenu(nsIContent *aMenu,
else {
nsCOMPtr<nsIContent> popupContent = popupFrame->GetContent();
FirePopupShowingEvent(popupContent, aMenu,
popupFrame->PresContext(),
popupFrame->PresContext(), popupFrame->PopupType(),
parentIsContextMenu, aSelectFirstItem);
}
}
@ -372,7 +372,7 @@ nsXULPopupManager::ShowPopup(nsIContent* aPopup,
aAttributesOverride);
FirePopupShowingEvent(aPopup, nsnull, popupFrame->PresContext(),
aIsContextMenu, aSelectFirstItem);
popupFrame->PopupType(), aIsContextMenu, aSelectFirstItem);
}
void
@ -387,7 +387,7 @@ nsXULPopupManager::ShowPopupAtScreen(nsIContent* aPopup,
popupFrame->InitializePopupAtScreen(aXPos, aYPos);
FirePopupShowingEvent(aPopup, nsnull, popupFrame->PresContext(),
aIsContextMenu, PR_FALSE);
popupFrame->PopupType(), aIsContextMenu, PR_FALSE);
}
void
@ -406,7 +406,7 @@ nsXULPopupManager::ShowPopupWithAnchorAlign(nsIContent* aPopup,
aAlign, aXPos, aYPos);
FirePopupShowingEvent(aPopup, nsnull, popupFrame->PresContext(),
aIsContextMenu, PR_FALSE);
popupFrame->PopupType(), aIsContextMenu, PR_FALSE);
}
void
@ -419,11 +419,11 @@ nsXULPopupManager::ShowPopupCallback(nsIContent* aPopup,
mRangeParent = nsnull;
mRangeOffset = 0;
PRInt32 popupType = aPopupFrame->PopupType();
nsPopupType popupType = aPopupFrame->PopupType();
PRBool ismenu = (popupType == ePopupTypeMenu);
nsMenuChainItem* item =
new nsMenuChainItem(aPopupFrame, aIsContextMenu, ismenu);
new nsMenuChainItem(aPopupFrame, aIsContextMenu, popupType);
if (!item)
return;
@ -513,7 +513,7 @@ nsXULPopupManager::HidePopup(nsIContent* aPopup,
item = item->GetParent();
}
PRBool ismenu = PR_FALSE;
nsPopupType type = ePopupTypePanel;
PRBool deselectMenu = PR_FALSE;
nsCOMPtr<nsIContent> popupToHide, nextPopup, lastPopup;
if (foundMenu) {
@ -533,7 +533,7 @@ nsXULPopupManager::HidePopup(nsIContent* aPopup,
deselectMenu = aDeselectMenu;
popupToHide = mCurrentMenu->Content();
popupFrame = mCurrentMenu->Frame();
ismenu = mCurrentMenu->IsMenu();
type = popupFrame->PopupType();
nsMenuChainItem* parent = mCurrentMenu->GetParent();
@ -563,12 +563,12 @@ nsXULPopupManager::HidePopup(nsIContent* aPopup,
if (aAsynchronous) {
nsCOMPtr<nsIRunnable> event =
new nsXULPopupHidingEvent(popupToHide, nextPopup, lastPopup,
ismenu, deselectMenu);
type, deselectMenu);
NS_DispatchToCurrentThread(event);
}
else {
FirePopupHidingEvent(popupToHide, nextPopup, lastPopup,
popupFrame->PresContext(), ismenu, deselectMenu);
popupFrame->PresContext(), type, deselectMenu);
}
}
}
@ -578,7 +578,7 @@ nsXULPopupManager::HidePopupCallback(nsIContent* aPopup,
nsMenuPopupFrame* aPopupFrame,
nsIContent* aNextPopup,
nsIContent* aLastPopup,
PRBool aIsMenu,
nsPopupType aPopupType,
PRBool aDeselectMenu)
{
if (mCloseTimer) {
@ -641,8 +641,8 @@ nsXULPopupManager::HidePopupCallback(nsIContent* aPopup,
// is reached, or until a popup of a different type is reached. This
// last check is needed so that a menulist inside a non-menu panel only
// closes the menu and not the panel as well.
if (foundMenu && (aLastPopup || aIsMenu == foundMenu->IsMenu())) {
PRBool ismenu = foundMenu->IsMenu();
if (foundMenu &&
(aLastPopup || aPopupType == foundMenu->PopupType())) {
nsCOMPtr<nsIContent> popupToHide = item->Content();
nsMenuChainItem* parent = item->GetParent();
@ -658,7 +658,8 @@ nsXULPopupManager::HidePopupCallback(nsIContent* aPopup,
popupFrame->SetPopupState(ePopupHiding);
FirePopupHidingEvent(popupToHide, nextPopup, aLastPopup,
popupFrame->PresContext(), ismenu, aDeselectMenu);
popupFrame->PresContext(),
foundMenu->PopupType(), aDeselectMenu);
}
}
}
@ -762,6 +763,7 @@ void
nsXULPopupManager::FirePopupShowingEvent(nsIContent* aPopup,
nsIContent* aMenu,
nsPresContext* aPresContext,
nsPopupType aPopupType,
PRBool aIsContextMenu,
PRBool aSelectFirstItem)
{
@ -787,6 +789,29 @@ nsXULPopupManager::FirePopupShowingEvent(nsIContent* aPopup,
nsEventDispatcher::Dispatch(aPopup, aPresContext, &event, nsnull, &status);
mCachedMousePoint = nsPoint(0, 0);
// if a panel, blur whatever has focus so that the panel can take the focus.
// This is done after the popupshowing event in case that event is cancelled.
// Using noautofocus="true" will disable this behaviour, which is needed for
// the autocomplete widget as it manages focus itself.
if (aPopupType == ePopupTypePanel &&
!aPopup->AttrValueIs(kNameSpaceID_None, nsGkAtoms::noautofocus,
nsGkAtoms::_true, eCaseMatters)) {
nsIEventStateManager* esm = presShell->GetPresContext()->EventStateManager();
// Only remove the focus if the currently focused item is ouside the
// popup. It isn't a big deal if the current focus is in a child popup
// inside the popup as that shouldn't be visible. This check ensures that
// a node inside the popup that is focused during a popupshowing event
// remains focused.
nsCOMPtr<nsIContent> currentFocus;
esm->GetFocusedContent(getter_AddRefs(currentFocus));
if (currentFocus &&
!nsContentUtils::ContentIsDescendantOf(currentFocus, aPopup)) {
esm->SetContentState(nsnull, NS_EVENT_STATE_FOCUS);
esm->SetFocusedContent(nsnull);
}
}
// it is common to append content to the menu during the popupshowing event.
// Flush the notifications so that the frames are up to date before showing
// the popup, otherwise the new frames will reflow after the popup appears,
@ -817,7 +842,7 @@ nsXULPopupManager::FirePopupHidingEvent(nsIContent* aPopup,
nsIContent* aNextPopup,
nsIContent* aLastPopup,
nsPresContext *aPresContext,
PRBool aIsMenu,
nsPopupType aPopupType,
PRBool aDeselectMenu)
{
nsCOMPtr<nsIPresShell> presShell = aPresContext->PresShell();
@ -826,6 +851,22 @@ nsXULPopupManager::FirePopupHidingEvent(nsIContent* aPopup,
nsMouseEvent event(PR_TRUE, NS_XUL_POPUP_HIDING, nsnull, nsMouseEvent::eReal);
nsEventDispatcher::Dispatch(aPopup, aPresContext, &event, nsnull, &status);
// when a panel is closed, blur whatever has focus inside the popup
if (aPopupType == ePopupTypePanel &&
!aPopup->AttrValueIs(kNameSpaceID_None, nsGkAtoms::noautofocus,
nsGkAtoms::_true, eCaseMatters)) {
nsIEventStateManager* esm = presShell->GetPresContext()->EventStateManager();
// Remove the focus from the focused node only if it is inside the popup.
nsCOMPtr<nsIContent> currentFocus;
esm->GetFocusedContent(getter_AddRefs(currentFocus));
if (currentFocus &&
nsContentUtils::ContentIsDescendantOf(currentFocus, aPopup)) {
esm->SetContentState(nsnull, NS_EVENT_STATE_FOCUS);
esm->SetFocusedContent(nsnull);
}
}
// get frame again in case it went away
nsIFrame* frame = presShell->GetPrimaryFrameFor(aPopup);
if (frame && frame->GetType() == nsGkAtoms::menuPopupFrame) {
@ -840,7 +881,7 @@ nsXULPopupManager::FirePopupHidingEvent(nsIContent* aPopup,
}
else {
HidePopupCallback(aPopup, popupFrame, aNextPopup, aLastPopup,
aIsMenu, aDeselectMenu);
aPopupType, aDeselectMenu);
}
}
}
@ -898,6 +939,22 @@ nsXULPopupManager::IsPopupOpenForMenuParent(nsIMenuParent* aMenuParent)
return PR_FALSE;
}
nsIFrame*
nsXULPopupManager::GetTopPopup(nsPopupType aType)
{
if (aType == ePopupTypePanel && mPanels)
return mPanels->Frame();
nsMenuChainItem* item = GetTopVisibleMenu();
while (item) {
if (item->PopupType() == aType)
return item->Frame();
item = item->GetParent();
}
return nsnull;
}
nsTArray<nsIFrame *>
nsXULPopupManager::GetOpenPopups()
{
@ -1693,7 +1750,9 @@ nsXULPopupShowingEvent::Run()
nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
nsPresContext* context = GetPresContextFor(mPopup);
if (pm && context) {
pm->FirePopupShowingEvent(mPopup, mMenu, context,
// the popupshowing event should only be fired asynchronously
// for menus, so just use ePopupTypeMenu as the type
pm->FirePopupShowingEvent(mPopup, mMenu, context, ePopupTypeMenu,
mIsContextMenu, mSelectFirstItem);
}
@ -1707,7 +1766,7 @@ nsXULPopupHidingEvent::Run()
nsPresContext* context = GetPresContextFor(mPopup);
if (pm && context) {
pm->FirePopupHidingEvent(mPopup, mNextPopup, mLastPopup,
context, mIsMenu, mDeselectMenu);
context, mPopupType, mDeselectMenu);
}
return NS_OK;

Просмотреть файл

@ -58,6 +58,8 @@ _TEST_FILES = bug288254_window.xul \
window_popup_preventdefault_chrome.xul \
test_largemenu.xul \
window_largemenu.xul \
test_panel_focus.xul \
window_panel_focus.xul \
$(NULL)
libs:: $(_TEST_FILES)

Просмотреть файл

@ -0,0 +1,36 @@
<?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 title="Panel Focus Tests"
onload="setTimeout(runTest, 0);"
xmlns:html="http://www.w3.org/1999/xhtml"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<title>Panel Focus Tests</title>
<script type="application/javascript"
src="chrome://mochikit/content/MochiKit/packed.js"/>
<script type="application/javascript"
src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
<script>
// use a chrome window for this test as the focus in content windows can be
// adjusted by the current selection position
SimpleTest.waitForExplicitFinish();
function runTest()
{
window.open("window_panel_focus.xul", "_new", "chrome,width=600,height=600");
}
</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>

Просмотреть файл

@ -0,0 +1,133 @@
<?xml version="1.0"?>
<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
<?xml-stylesheet href="/tests/SimpleTest/test.css" type="text/css"?>
<window title="Panel Focus Tests"
onload="setTimeout(showPanel, 0)"
xmlns:html="http://www.w3.org/1999/xhtml"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<script type="application/javascript"
src="chrome://mochikit/content/MochiKit/packed.js"/>
<script type="application/javascript"
src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/>
<title>Panel Focus Tests</title>
<checkbox id="b1" label="Item 1"/>
<!-- Focus should be in this order: 2 6 3 8 1 4 5 7 9 -->
<panel id="panel" onpopupshown="panelShown()" onpopuphidden="panelHidden()">
<button id="t1" label="Button One"/>
<button id="t2" tabindex="1" label="Button Two" onblur="gButtonBlur++;"/>
<button id="t3" tabindex="2" label="Button Three"/>
<button id="t4" tabindex="0" label="Button Four"/>
<button id="t5" label="Button Five"/>
<button id="t6" tabindex="1" label="Button Six"/>
<button id="t7" label="Button Seven"/>
<button id="t8" tabindex="4" label="Button Eight"/>
<button id="t9" label="Button Nine"/>
</panel>
<panel id="noautofocusPanel" noautofocus="true"
onpopupshown="noautofocusPanelShown()" onpopuphidden="noautofocusPanelHidden()">
<textbox id="tb3"/>
</panel>
<checkbox id="b2" label="Item 2" popup="panel" onblur="gButtonBlur++;"/>
<script class="testbody" type="application/javascript">
<![CDATA[
var gButtonBlur = 0;
function showPanel()
{
// focus the button
synthesizeKeyExpectEvent("VK_TAB", { }, $("b1"), "focus", "button focus");
// tabbing again should skip the popup
synthesizeKeyExpectEvent("VK_TAB", { }, $("b2"), "focus", "popup skipped in focus navigation");
$("panel").openPopup(null, "", 10, 10, false, false);
}
function panelShown()
{
// the focus on the button should have been removed when the popup was opened
is(gButtonBlur, 1, "focus removed when popup opened");
// press tab numerous times to cycle through the buttons. The t2 button will
// be blurred twice, so gButtonBlur will be 3 afterwards.
synthesizeKeyExpectEvent("VK_TAB", { }, $("t2"), "focus", "tabindex 1");
synthesizeKeyExpectEvent("VK_TAB", { }, $("t6"), "focus", "tabindex 2");
synthesizeKeyExpectEvent("VK_TAB", { }, $("t3"), "focus", "tabindex 3");
synthesizeKeyExpectEvent("VK_TAB", { }, $("t8"), "focus", "tabindex 4");
synthesizeKeyExpectEvent("VK_TAB", { }, $("t1"), "focus", "tabindex 5");
synthesizeKeyExpectEvent("VK_TAB", { }, $("t4"), "focus", "tabindex 6");
synthesizeKeyExpectEvent("VK_TAB", { }, $("t5"), "focus", "tabindex 7");
synthesizeKeyExpectEvent("VK_TAB", { }, $("t7"), "focus", "tabindex 8");
synthesizeKeyExpectEvent("VK_TAB", { }, $("t9"), "focus", "tabindex 9");
synthesizeKeyExpectEvent("VK_TAB", { }, $("t2"), "focus", "tabindex 10");
synthesizeKeyExpectEvent("VK_TAB", { shiftKey: true }, $("t9"), "focus", "back tabindex 1");
synthesizeKeyExpectEvent("VK_TAB", { shiftKey: true }, $("t7"), "focus", "back tabindex 2");
synthesizeKeyExpectEvent("VK_TAB", { shiftKey: true }, $("t5"), "focus", "back tabindex 3");
synthesizeKeyExpectEvent("VK_TAB", { shiftKey: true }, $("t4"), "focus", "back tabindex 4");
synthesizeKeyExpectEvent("VK_TAB", { shiftKey: true }, $("t1"), "focus", "back tabindex 5");
synthesizeKeyExpectEvent("VK_TAB", { shiftKey: true }, $("t8"), "focus", "back tabindex 6");
synthesizeKeyExpectEvent("VK_TAB", { shiftKey: true }, $("t3"), "focus", "back tabindex 7");
synthesizeKeyExpectEvent("VK_TAB", { shiftKey: true }, $("t6"), "focus", "back tabindex 8");
synthesizeKeyExpectEvent("VK_TAB", { shiftKey: true }, $("t2"), "focus", "back tabindex 9");
is(gButtonBlur, 3, "blur events fired within popup");
$("panel").hidePopup();
}
function ok(condition, message) {
window.opener.wrappedJSObject.SimpleTest.ok(condition, message);
}
function is(left, right, message) {
window.opener.wrappedJSObject.SimpleTest.is(left, right, message);
}
function panelHidden()
{
// closing the popup should have blurred the focused element
is(gButtonBlur, 4, "focus removed when popup closed");
// now that the panel is hidden, pressing tab should focus the elements in
// the main window again
synthesizeKeyExpectEvent("VK_TAB", { }, $("b1"), "focus", "focus after popup closed");
$("noautofocusPanel").openPopup(null, "", 10, 10, false, false);
}
function noautofocusPanelShown()
{
// with noautofocus="true", the focus should not be removed when the panel is
// opened, so key events should still be fired at the checkbox.
synthesizeKeyExpectEvent("VK_SPACE", { }, $("b1"), "command", "noautofocus");
$("noautofocusPanel").hidePopup();
}
function noautofocusPanelHidden()
{
window.close();
window.opener.wrappedJSObject.SimpleTest.finish();
}
]]>
</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>

Просмотреть файл

@ -112,6 +112,7 @@
if (!popup) {
popup = document.createElement("panel");
popup.setAttribute("type", "autocomplete");
popup.setAttribute("noautofocus", "true");
var popupset = document.getAnonymousElementByAttribute(this, "anonid", "popupset");
popupset.appendChild(popup);

Просмотреть файл

@ -31,7 +31,8 @@
xbl:inherits="open,hidden=disablehistory" anonid="historydropmarker"/>
<xul:popupset>
<xul:panel type="autocomplete" anonid="popup" xbl:inherits="for=id,nomatch"/>
<xul:panel type="autocomplete" anonid="popup" noautofocus="true"
xbl:inherits="for=id,nomatch"/>
</xul:popupset>
<children includes="menupopup"/>