Bug 1318705, combine the two lists of open popups in nsXULPopupManager into a single list to make iterating and managing them easier. Instead a noautohide flag is used to distinguish what used to be the difference between the lists, r=ksteuber

This commit is contained in:
Neil Deakin 2017-03-13 09:44:02 -04:00
Родитель 61a148b36e
Коммит 1dba2e9287
2 изменённых файлов: 142 добавлений и 228 удалений

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

@ -140,7 +140,6 @@ nsXULPopupManager::nsXULPopupManager() :
mCachedModifiers(0),
mActiveMenuBar(nullptr),
mPopups(nullptr),
mNoHidePanels(nullptr),
mTimerMenu(nullptr)
{
nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
@ -153,7 +152,7 @@ nsXULPopupManager::nsXULPopupManager() :
nsXULPopupManager::~nsXULPopupManager()
{
NS_ASSERTION(!mPopups && !mNoHidePanels, "XUL popups still open");
NS_ASSERTION(!mPopups, "XUL popups still open");
}
nsresult
@ -434,12 +433,12 @@ nsXULPopupManager::AdjustPopupsOnWindowChange(nsPIDOMWindowOuter* aWindow)
// The items are added to a list so that they can be adjusted bottom to top.
nsTArray<nsMenuPopupFrame *> list;
nsMenuChainItem* item = mNoHidePanels;
nsMenuChainItem* item = mPopups;
while (item) {
// only move popups that are within the same window and where auto
// positioning has not been disabled
nsMenuPopupFrame* frame = item->Frame();
if (frame->GetAutoPosition()) {
if (item->IsNoAutoHide() && frame->GetAutoPosition()) {
nsIContent* popup = frame->GetContent();
if (popup) {
nsIDocument* document = popup->GetUncomposedDoc();
@ -580,9 +579,14 @@ nsMenuChainItem*
nsXULPopupManager::GetTopVisibleMenu()
{
nsMenuChainItem* item = mPopups;
while (item && item->Frame()->PopupState() == ePopupInvisible)
while (item) {
if (!item->IsNoAutoHide() && item->Frame()->PopupState() != ePopupInvisible) {
return item;
}
item = item->GetParent();
return item;
}
return nullptr;
}
void
@ -910,8 +914,14 @@ nsXULPopupManager::ShowPopupCallback(nsIContent* aPopup,
nsPopupType popupType = aPopupFrame->PopupType();
bool ismenu = (popupType == ePopupTypeMenu);
// Popups normally hide when an outside click occurs. Panels may use
// the noautohide attribute to disable this behaviour. It is expected
// that the application will hide these popups manually. The tooltip
// listener will handle closing the tooltip also.
bool isNoAutoHide = aPopupFrame->IsNoAutoHide() || popupType == ePopupTypeTooltip;
nsMenuChainItem* item =
new nsMenuChainItem(aPopupFrame, aIsContextMenu, popupType);
new nsMenuChainItem(aPopupFrame, isNoAutoHide, aIsContextMenu, popupType);
if (!item)
return;
@ -944,18 +954,13 @@ nsXULPopupManager::ShowPopupCallback(nsIContent* aPopup,
// the noautohide attribute to disable this behaviour. It is expected
// that the application will hide these popups manually. The tooltip
// listener will handle closing the tooltip also.
if (aPopupFrame->IsNoAutoHide() || popupType == ePopupTypeTooltip) {
item->SetParent(mNoHidePanels);
mNoHidePanels = item;
}
else {
nsIContent* oldmenu = nullptr;
if (mPopups)
oldmenu = mPopups->Content();
item->SetParent(mPopups);
mPopups = item;
SetCaptureState(oldmenu);
nsIContent* oldmenu = nullptr;
if (mPopups) {
oldmenu = mPopups->Content();
}
item->SetParent(mPopups);
mPopups = item;
SetCaptureState(oldmenu);
item->UpdateFollowAnchor();
@ -980,120 +985,101 @@ nsXULPopupManager::HidePopup(nsIContent* aPopup,
bool aIsCancel,
nsIContent* aLastPopup)
{
// if the popup is on the nohide panels list, remove it but don't close any
// other panels
nsMenuPopupFrame* popupFrame = nullptr;
bool foundPanel = false;
nsMenuChainItem* item = mNoHidePanels;
while (item) {
if (item->Content() == aPopup) {
foundPanel = true;
popupFrame = item->Frame();
break;
}
item = item->GetParent();
nsMenuPopupFrame* popupFrame = do_QueryFrame(aPopup->GetPrimaryFrame());
if (!popupFrame) {
return;
}
// when removing a menu, all of the child popups must be closed
nsMenuChainItem* foundMenu = nullptr;
item = mPopups;
while (item) {
if (item->Content() == aPopup) {
foundMenu = item;
nsMenuChainItem* foundPopup = mPopups;
while (foundPopup) {
if (foundPopup->Content() == aPopup) {
break;
}
item = item->GetParent();
foundPopup = foundPopup->GetParent();
}
nsPopupType type = ePopupTypePanel;
bool deselectMenu = false;
nsCOMPtr<nsIContent> popupToHide, nextPopup, lastPopup;
if (foundMenu) {
// at this point, foundMenu will be set to the found item in the list. If
// foundMenu is the topmost menu, the one to remove, then there are no other
// popups to hide. If foundMenu is not the topmost menu, then there may be
// open submenus below it. In this case, we need to make sure that those
// submenus are closed up first. To do this, we scan up the menu list to
// find the topmost popup with only menus between it and foundMenu and
// close that menu first. In synchronous mode, the FirePopupHidingEvent
// method will be called which in turn calls HidePopupCallback to close up
// the next popup in the chain. These two methods will be called in
// sequence recursively to close up all the necessary popups. In
// asynchronous mode, a similar process occurs except that the
// FirePopupHidingEvent method is called asynchronously. In either case,
// nextPopup is set to the content node of the next popup to close, and
// lastPopup is set to the last popup in the chain to close, which will be
// aPopup, or null to close up all menus.
nsMenuChainItem* topMenu = foundMenu;
// Use IsMenu to ensure that foundMenu is a menu and scan down the child
// list until a non-menu is found. If foundMenu isn't a menu at all, don't
// scan and just close up this menu.
if (foundMenu->IsMenu()) {
item = topMenu->GetChild();
while (item && item->IsMenu()) {
topMenu = item;
item = item->GetChild();
if (foundPopup) {
if (foundPopup->IsNoAutoHide()) {
// If this is a noautohide panel, remove it but don't close any other panels.
popupToHide = aPopup;
} else {
// At this point, foundPopup will be set to the found item in the list. If
// foundPopup is the topmost menu, the one to remove, then there are no other
// popups to hide. If foundPopup is not the topmost menu, then there may be
// open submenus below it. In this case, we need to make sure that those
// submenus are closed up first. To do this, we scan up the menu list to
// find the topmost popup with only menus between it and foundPopup and
// close that menu first. In synchronous mode, the FirePopupHidingEvent
// method will be called which in turn calls HidePopupCallback to close up
// the next popup in the chain. These two methods will be called in
// sequence recursively to close up all the necessary popups. In
// asynchronous mode, a similar process occurs except that the
// FirePopupHidingEvent method is called asynchronously. In either case,
// nextPopup is set to the content node of the next popup to close, and
// lastPopup is set to the last popup in the chain to close, which will be
// aPopup, or null to close up all menus.
nsMenuChainItem* topMenu = foundPopup;
// Use IsMenu to ensure that foundPopup is a menu and scan down the child
// list until a non-menu is found. If foundPopup isn't a menu at all, don't
// scan and just close up this menu.
if (foundPopup->IsMenu()) {
nsMenuChainItem* child = foundPopup->GetChild();
while (child && child->IsMenu()) {
topMenu = child;
child = child->GetChild();
}
}
deselectMenu = aDeselectMenu;
popupToHide = topMenu->Content();
popupFrame = topMenu->Frame();
// Close up another popup if there is one, and we are either hiding the
// entire chain or the item to hide isn't the topmost popup.
nsMenuChainItem* parent = topMenu->GetParent();
if (parent && (aHideChain || topMenu != foundPopup)) {
nextPopup = parent->Content();
}
lastPopup = aLastPopup ? aLastPopup : (aHideChain ? nullptr : aPopup);
}
deselectMenu = aDeselectMenu;
popupToHide = topMenu->Content();
popupFrame = topMenu->Frame();
type = popupFrame->PopupType();
nsMenuChainItem* parent = topMenu->GetParent();
// close up another popup if there is one, and we are either hiding the
// entire chain or the item to hide isn't the topmost popup.
if (parent && (aHideChain || topMenu != foundMenu))
nextPopup = parent->Content();
lastPopup = aLastPopup ? aLastPopup : (aHideChain ? nullptr : aPopup);
}
else if (foundPanel) {
popupToHide = aPopup;
} else {
} else if (popupFrame->PopupState() == ePopupPositioning) {
// When the popup is in the popuppositioning state, it will not be in the
// mPopups list. We need another way to find it and make sure it does not
// continue the popup showing process.
popupFrame = do_QueryFrame(aPopup->GetPrimaryFrame());
if (popupFrame) {
if (popupFrame->PopupState() == ePopupPositioning) {
// Do basically the same thing we would have done if we had found the
// popup in the mPopups list.
deselectMenu = aDeselectMenu;
popupToHide = aPopup;
type = popupFrame->PopupType();
} else {
// The popup is not positioning. If we were supposed to have handled
// closing it, it should have been in mPopups or mNoHidePanels
popupFrame = nullptr;
}
}
deselectMenu = aDeselectMenu;
popupToHide = aPopup;
}
if (popupFrame) {
if (popupToHide) {
nsPopupState state = popupFrame->PopupState();
// if the popup is already being hidden, don't attempt to hide it again
if (state == ePopupHiding)
// If the popup is already being hidden, don't attempt to hide it again
if (state == ePopupHiding) {
return;
// change the popup state to hiding. Don't set the hiding state if the
}
// Change the popup state to hiding. Don't set the hiding state if the
// popup is invisible, otherwise nsMenuPopupFrame::HidePopup will
// run again. In the invisible state, we just want the events to fire.
if (state != ePopupInvisible)
if (state != ePopupInvisible) {
popupFrame->SetPopupState(ePopupHiding);
}
// for menus, popupToHide is always the frontmost item in the list to hide.
// For menus, popupToHide is always the frontmost item in the list to hide.
if (aAsynchronous) {
nsCOMPtr<nsIRunnable> event =
new nsXULPopupHidingEvent(popupToHide, nextPopup, lastPopup,
type, deselectMenu, aIsCancel);
popupFrame->PopupType(), deselectMenu, aIsCancel);
NS_DispatchToCurrentThread(event);
}
else {
FirePopupHidingEvent(popupToHide, nextPopup, lastPopup,
popupFrame->PresContext(), type, deselectMenu, aIsCancel);
popupFrame->PresContext(), popupFrame->PopupType(),
deselectMenu, aIsCancel);
}
}
}
@ -1164,27 +1150,16 @@ nsXULPopupManager::HidePopupCallback(nsIContent* aPopup,
// possible someone added another item (attempted to open another popup)
// or removed a popup frame during the event processing so the item isn't at
// the front anymore.
nsMenuChainItem* item = mNoHidePanels;
nsMenuChainItem* item = mPopups;
while (item) {
if (item->Content() == aPopup) {
item->Detach(&mNoHidePanels);
item->Detach(&mPopups);
SetCaptureState(aPopup);
break;
}
item = item->GetParent();
}
if (!item) {
item = mPopups;
while (item) {
if (item->Content() == aPopup) {
item->Detach(&mPopups);
SetCaptureState(aPopup);
break;
}
item = item->GetParent();
}
}
delete item;
AutoWeakFrame weakFrame(aPopupFrame);
@ -1288,33 +1263,19 @@ void
nsXULPopupManager::EnableRollup(nsIContent* aPopup, bool aShouldRollup)
{
#ifndef MOZ_GTK
if (aShouldRollup) {
nsMenuChainItem* item = mNoHidePanels;
while (item) {
if (item->Content() == aPopup) {
item->Detach(&mNoHidePanels);
nsIContent* oldmenu = nullptr;
if (mPopups)
oldmenu = mPopups->Content();
item->SetParent(mPopups);
mPopups = item;
SetCaptureState(oldmenu);
return;
nsMenuChainItem* item = mPopups;
while (item) {
if (item->Content() == aPopup) {
nsIContent* oldmenu = nullptr;
if (mPopups) {
oldmenu = mPopups->Content();
}
item = item->GetParent();
}
} else {
nsMenuChainItem* item = mPopups;
while (item) {
if (item->Content() == aPopup) {
item->Detach(&mPopups);
item->SetParent(mNoHidePanels);
mNoHidePanels = item;
SetCaptureState(nullptr);
return;
}
item = item->GetParent();
item->SetNoAutoHide(!aShouldRollup);
SetCaptureState(oldmenu);
return;
}
item = item->GetParent();
}
#endif
}
@ -1354,55 +1315,33 @@ nsXULPopupManager::HidePopupsInDocShell(nsIDocShellTreeItem* aDocShellToHide)
item = parent;
}
// now look for panels to hide
item = mNoHidePanels;
while (item) {
nsMenuChainItem* parent = item->GetParent();
if (item->Frame()->PopupState() != ePopupInvisible &&
IsChildOfDocShell(item->Content()->OwnerDoc(), aDocShellToHide)) {
nsMenuPopupFrame* frame = item->Frame();
item->Detach(&mNoHidePanels);
delete item;
popupsToHide.AppendElement(frame);
}
item = parent;
}
HidePopupsInList(popupsToHide);
}
void
nsXULPopupManager::UpdatePopupPositions(nsRefreshDriver* aRefreshDriver)
{
if (!mPopups && !mNoHidePanels) {
return;
}
for (int32_t i = 0; i < 2; i++) {
nsMenuChainItem* item = i == 0 ? mPopups : mNoHidePanels;
while (item) {
if (item->Frame()->PresContext()->RefreshDriver() == aRefreshDriver) {
item->CheckForAnchorChange();
}
item = item->GetParent();
nsMenuChainItem* item = mPopups;
while (item) {
if (item->Frame()->PresContext()->RefreshDriver() == aRefreshDriver) {
item->CheckForAnchorChange();
}
item = item->GetParent();
}
}
void
nsXULPopupManager::UpdateFollowAnchor(nsMenuPopupFrame* aPopup)
{
for (int32_t i = 0; i < 2; i++) {
nsMenuChainItem* item = i == 0 ? mPopups : mNoHidePanels;
while (item) {
if (item->Frame() == aPopup) {
item->UpdateFollowAnchor();
break;
}
item = item->GetParent();
nsMenuChainItem* item = mPopups;
while (item) {
if (item->Frame() == aPopup) {
item->UpdateFollowAnchor();
break;
}
item = item->GetParent();
}
}
@ -1679,18 +1618,6 @@ nsXULPopupManager::IsPopupOpen(nsIContent* aPopup)
item = item->GetParent();
}
item = mNoHidePanels;
while (item) {
if (item->Content() == aPopup) {
NS_ASSERTION(item->Frame()->IsOpen() ||
item->Frame()->PopupState() == ePopupHiding ||
item->Frame()->PopupState() == ePopupInvisible,
"popup in open list not actually open");
return true;
}
item = item->GetParent();
}
return false;
}
@ -1715,13 +1642,13 @@ nsXULPopupManager::IsPopupOpenForMenuParent(nsMenuParent* aMenuParent)
nsIFrame*
nsXULPopupManager::GetTopPopup(nsPopupType aType)
{
if ((aType == ePopupTypePanel || aType == ePopupTypeTooltip) && mNoHidePanels)
return mNoHidePanels->Frame();
nsMenuChainItem* item = GetTopVisibleMenu();
nsMenuChainItem* item = mPopups;
while (item) {
if (item->PopupType() == aType || aType == ePopupTypeAny)
if (item->Frame()->IsVisible() &&
(item->PopupType() == aType || aType == ePopupTypeAny)) {
return item->Frame();
}
item = item->GetParent();
}
@ -1733,20 +1660,15 @@ nsXULPopupManager::GetVisiblePopups(nsTArray<nsIFrame *>& aPopups)
{
aPopups.Clear();
// Iterate over both lists of popups
nsMenuChainItem* item = mPopups;
for (int32_t list = 0; list < 2; list++) {
while (item) {
// Skip panels which are not visible as well as popups that
// are transparent to mouse events.
if (item->Frame()->IsVisible() && !item->Frame()->IsMouseTransparent()) {
aPopups.AppendElement(item->Frame());
}
item = item->GetParent();
while (item) {
// Skip panels which are not visible as well as popups that
// are transparent to mouse events.
if (item->Frame()->IsVisible() && !item->Frame()->IsMouseTransparent()) {
aPopups.AppendElement(item->Frame());
}
item = mNoHidePanels;
item = item->GetParent();
}
}
@ -1766,7 +1688,7 @@ nsXULPopupManager::GetLastTriggerNode(nsIDocument* aDocument, bool aIsTooltip)
node = do_QueryInterface(nsMenuPopupFrame::GetTriggerContent(GetPopupFrameForContent(mOpeningPopup, false)));
}
else {
nsMenuChainItem* item = aIsTooltip ? mNoHidePanels : mPopups;
nsMenuChainItem* item = mPopups;
while (item) {
// look for a popup of the same type and document.
if ((item->PopupType() == ePopupTypeTooltip) == aIsTooltip &&
@ -1887,23 +1809,14 @@ nsXULPopupManager::PopupDestroyed(nsMenuPopupFrame* aPopup)
mTimerMenu = nullptr;
}
nsMenuChainItem* item = mNoHidePanels;
while (item) {
if (item->Frame() == aPopup) {
item->Detach(&mNoHidePanels);
delete item;
break;
}
item = item->GetParent();
}
nsTArray<nsMenuPopupFrame *> popupsToHide;
item = mPopups;
nsMenuChainItem* item = mPopups;
while (item) {
nsMenuPopupFrame* frame = item->Frame();
if (frame == aPopup) {
if (frame->PopupState() != ePopupInvisible) {
// XXXndeakin shouldn't this only happen for menus?
if (!item->IsNoAutoHide() && frame->PopupState() != ePopupInvisible) {
// Iterate through any child menus and hide them as well, since the
// parent is going away. We won't remove them from the list yet, just
// hide them, as they will be removed from the list when this function
@ -2379,7 +2292,7 @@ nsXULPopupManager::HandleKeyboardEventWithKeyCode(
return true;
}
bool consume = (mPopups || mActiveMenuBar);
bool consume = (aTopVisibleMenuItem || mActiveMenuBar);
switch (keyCode) {
case nsIDOMKeyEvent::DOM_VK_UP:
case nsIDOMKeyEvent::DOM_VK_DOWN:
@ -2744,7 +2657,7 @@ nsXULPopupManager::KeyDown(nsIDOMKeyEvent* aKeyEvent)
// The access key just went down and no other
// modifiers are already down.
nsMenuChainItem* item = GetTopVisibleMenu();
if (mPopups && item && !item->Frame()->IsMenuList()) {
if (item && !item->Frame()->IsMenuList()) {
Rollup(0, false, nullptr, nullptr);
} else if (mActiveMenuBar) {
mActiveMenuBar->MenuClosed();
@ -2776,7 +2689,7 @@ nsXULPopupManager::KeyPress(nsIDOMKeyEvent* aKeyEvent)
nsCOMPtr<nsIDOMKeyEvent> keyEvent = do_QueryInterface(aKeyEvent);
NS_ENSURE_TRUE(keyEvent, NS_ERROR_UNEXPECTED);
// if a menu is open or a menubar is active, it consumes the key event
bool consume = (mPopups || mActiveMenuBar);
bool consume = (item || mActiveMenuBar);
WidgetInputEvent* evt = aKeyEvent->AsEvent()->WidgetEventPtr()->AsInputEvent();
bool isAccel = evt && evt->IsAccel();

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

@ -40,8 +40,7 @@
* type is used by tooltips.
*
* When a new popup is opened, it is appended to the popup chain, stored in a
* linked list in mPopups for dismissable menus and panels or mNoHidePanels
* for tooltips and panels with noautohide="true".
* linked list in mPopups.
* Popups are stored in this list linked from newest to oldest. When a click
* occurs outside one of the open dismissable popups, the chain is closed by
* calling Rollup.
@ -135,6 +134,7 @@ class nsMenuChainItem
private:
nsMenuPopupFrame* mFrame; // the popup frame
nsPopupType mPopupType; // the popup type of the frame
bool mNoAutoHide; // true for noautohide panels
bool mIsContext; // true for context menus
bool mOnMenuBar; // true if the menu is on a menu bar
nsIgnoreKeys mIgnoreKeys; // indicates how keyboard listeners should be used
@ -150,9 +150,11 @@ private:
nsMenuChainItem* mChild;
public:
nsMenuChainItem(nsMenuPopupFrame* aFrame, bool aIsContext, nsPopupType aPopupType)
nsMenuChainItem(nsMenuPopupFrame* aFrame, bool aNoAutoHide, bool aIsContext,
nsPopupType aPopupType)
: mFrame(aFrame),
mPopupType(aPopupType),
mNoAutoHide(aNoAutoHide),
mIsContext(aIsContext),
mOnMenuBar(false),
mIgnoreKeys(eIgnoreKeys_False),
@ -172,6 +174,8 @@ public:
nsIContent* Content();
nsMenuPopupFrame* Frame() { return mFrame; }
nsPopupType PopupType() { return mPopupType; }
bool IsNoAutoHide() { return mNoAutoHide; }
void SetNoAutoHide(bool aNoAutoHide) { mNoAutoHide = aNoAutoHide; }
bool IsMenu() { return mPopupType == ePopupTypeMenu; }
bool IsContextMenu() { return mIsContext; }
nsIgnoreKeys IgnoreKeys() { return mIgnoreKeys; }
@ -567,7 +571,7 @@ public:
/**
* Return the frame for the topmost open popup of a given type, or null if
* no popup of that type is open. If aType is ePopupTypeAny, a menu of any
* type is returned, except for popups in the mNoHidePanels list.
* type is returned.
*/
nsIFrame* GetTopPopup(nsPopupType aType);
@ -829,9 +833,6 @@ protected:
// linked list of normal menus and panels.
nsMenuChainItem* mPopups;
// linked list of noautohide panels and tooltips.
nsMenuChainItem* mNoHidePanels;
// timer used for HidePopupAfterDelay
nsCOMPtr<nsITimer> mCloseTimer;