зеркало из https://github.com/mozilla/gecko-dev.git
Bug 385275, handle tab navigation in popups properly, r+sr=roc
This commit is contained in:
Родитель
3a1335fceb
Коммит
239bab7b12
|
@ -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"/>
|
||||
|
|
Загрузка…
Ссылка в новой задаче