Bug 1811487 - Clean-up popup hide / rollup APIs. r=cmartin,stransky

I'm about to extend them for bug 1811486, where I want to force in some
cases the rolled up popups to hide synchronously. These APIs use a ton
of boolean arguments that make them error prone, so refactor them a bit
to use strongly typed enums and flags.

Differential Revision: https://phabricator.services.mozilla.com/D167381
This commit is contained in:
Emilio Cobos Álvarez 2023-01-24 15:43:49 +00:00
parent 89a7e7fe31
commit 46dd8a0d40
20 changed files with 264 additions and 242 deletions

View File

@ -43,8 +43,8 @@ add_task(async function test_PanelMultiView_toggle_with_other_popup() {
eventTypeToWait: "mouseup", eventTypeToWait: "mouseup",
}); });
if (AppConstants.platform == "win") { // On Windows and macOS, the operation will close both popups.
// On Windows, the operation will close both popups. if (AppConstants.platform == "win" || AppConstants.platform == "macosx") {
await gCUITestUtils.hidePanelMultiView(PanelUI.panel, clickFn); await gCUITestUtils.hidePanelMultiView(PanelUI.panel, clickFn);
await new Promise(resolve => executeSoon(resolve)); await new Promise(resolve => executeSoon(resolve));

View File

@ -290,14 +290,11 @@ add_task(async function testTabOpenMenulist() {
await shown; await shown;
ok(gMainMenulist.open, "menulist open"); ok(gMainMenulist.open, "menulist open");
let menuHidden = BrowserTestUtils.waitForEvent(popup, "popuphidden"); let menuHidden = BrowserTestUtils.waitForEvent(popup, "popuphidden");
let panelHidden = BrowserTestUtils.waitForEvent(gPanel, "popuphidden");
EventUtils.synthesizeKey("KEY_Tab"); EventUtils.synthesizeKey("KEY_Tab");
await menuHidden; await menuHidden;
ok(!gMainMenulist.open, "menulist closed after Tab"); ok(!gMainMenulist.open, "menulist closed after Tab");
// Tab in an open menulist closes the menulist, but also dismisses the panel is(gPanel.state, "open", "Panel should be open");
// above it (bug 1566673). So, we just wait for the panel to hide rather than await hidePopup();
// using hidePopup().
await panelHidden;
}); });
if (AppConstants.platform == "macosx") { if (AppConstants.platform == "macosx") {
@ -327,14 +324,11 @@ if (AppConstants.platform == "macosx") {
); );
let menuHidden = BrowserTestUtils.waitForEvent(popup, "popuphidden"); let menuHidden = BrowserTestUtils.waitForEvent(popup, "popuphidden");
let panelHidden = BrowserTestUtils.waitForEvent(gPanel, "popuphidden");
EventUtils.synthesizeKey("KEY_Tab"); EventUtils.synthesizeKey("KEY_Tab");
await menuHidden; await menuHidden;
ok(!gMainMenulist.open, "menulist closed after Tab"); ok(!gMainMenulist.open, "menulist closed after Tab");
// Tab in an open menulist closes the menulist, but also dismisses the panel is(gPanel.state, "open", "Panel should be open");
// above it (bug 1566673). So, we just wait for the panel to hide rather than await hidePopup();
// using hidePopup().
await panelHidden;
}); });
} }

View File

@ -95,9 +95,9 @@ void XULButtonElement::HandleEnterKeyPress(WidgetEvent& aEvent) {
#ifdef XP_WIN #ifdef XP_WIN
if (XULPopupElement* popup = GetContainingPopupElement()) { if (XULPopupElement* popup = GetContainingPopupElement()) {
if (nsXULPopupManager* pm = nsXULPopupManager::GetInstance()) { if (nsXULPopupManager* pm = nsXULPopupManager::GetInstance()) {
pm->HidePopup(popup, /* aHideChain = */ true, pm->HidePopup(
/* aDeselectMenu = */ true, /* aAsynchronous = */ true, popup, {HidePopupOption::HideChain, HidePopupOption::DeselectMenu,
/* aIsCancel = */ false); HidePopupOption::Async});
} }
} }
#endif #endif
@ -211,7 +211,11 @@ void XULButtonElement::CloseMenuPopup(bool aDeselectMenu) {
return; return;
} }
if (auto* popup = GetMenuPopupContent()) { if (auto* popup = GetMenuPopupContent()) {
pm->HidePopup(popup, false, aDeselectMenu, true, false); HidePopupOptions options{HidePopupOption::Async};
if (aDeselectMenu) {
options += HidePopupOption::DeselectMenu;
}
pm->HidePopup(popup, options);
} }
} }

View File

@ -106,9 +106,14 @@ void XULPopupElement::OpenPopupAtScreenRect(const nsAString& aPosition,
void XULPopupElement::HidePopup(bool aCancel) { void XULPopupElement::HidePopup(bool aCancel) {
nsXULPopupManager* pm = nsXULPopupManager::GetInstance(); nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
if (pm) { if (!pm) {
pm->HidePopup(this, false, true, false, aCancel); return;
} }
HidePopupOptions options{HidePopupOption::DeselectMenu};
if (aCancel) {
options += HidePopupOption::IsRollup;
}
pm->HidePopup(this, options);
} }
static Modifiers ConvertModifiers(const ActivateMenuItemOptions& aModifiers) { static Modifiers ConvertModifiers(const ActivateMenuItemOptions& aModifiers) {

View File

@ -170,7 +170,9 @@ void nsXULPopupListener::ClosePopup() {
// popup is hidden. Use asynchronous hiding just to be safe so we don't // popup is hidden. Use asynchronous hiding just to be safe so we don't
// fire events during destruction. // fire events during destruction.
nsXULPopupManager* pm = nsXULPopupManager::GetInstance(); nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
if (pm) pm->HidePopup(mPopupContent, false, true, true, false); if (pm)
pm->HidePopup(mPopupContent,
{HidePopupOption::DeselectMenu, HidePopupOption::Async});
mPopupContent = nullptr; // release the popup mPopupContent = nullptr; // release the popup
} }
} // ClosePopup } // ClosePopup

View File

@ -207,7 +207,7 @@ nsresult nsMenuBarListener::KeyUp(Event* aKeyEvent) {
// handle key events when menubar is active and IME should be // handle key events when menubar is active and IME should be
// disabled. // disabled.
if (nsXULPopupManager* pm = nsXULPopupManager::GetInstance()) { if (nsXULPopupManager* pm = nsXULPopupManager::GetInstance()) {
pm->Rollup(0, false, nullptr, nullptr); pm->Rollup({});
} }
// If menubar active state is changed or the menubar is destroyed // If menubar active state is changed or the menubar is destroyed
// during closing the popups, we should do nothing anymore. // during closing the popups, we should do nothing anymore.

View File

@ -62,6 +62,7 @@
#include <algorithm> #include <algorithm>
#include "X11UndefineNone.h" #include "X11UndefineNone.h"
#include "nsXULPopupManager.h"
using namespace mozilla; using namespace mozilla;
using mozilla::dom::Document; using mozilla::dom::Document;
@ -2460,7 +2461,8 @@ void nsMenuPopupFrame::CheckForAnchorChange(nsRect& aRect) {
if (pm) { if (pm) {
// As the caller will be iterating over the open popups, hide // As the caller will be iterating over the open popups, hide
// asyncronously. // asyncronously.
pm->HidePopup(mContent, false, true, true, false); pm->HidePopup(mContent,
{HidePopupOption::DeselectMenu, HidePopupOption::Async});
} }
return; return;

View File

@ -278,14 +278,12 @@ nsXULPopupManager* nsXULPopupManager::GetInstance() {
} }
bool nsXULPopupManager::RollupTooltips() { bool nsXULPopupManager::RollupTooltips() {
return RollupInternal(RollupKind::Tooltip, UINT32_MAX, false, nullptr, return RollupInternal(RollupKind::Tooltip, {}, nullptr);
nullptr);
} }
bool nsXULPopupManager::Rollup(uint32_t aCount, bool aFlush, bool nsXULPopupManager::Rollup(const RollupOptions& aOptions,
const LayoutDeviceIntPoint* aPos,
nsIContent** aLastRolledUp) { nsIContent** aLastRolledUp) {
return RollupInternal(RollupKind::Menu, aCount, aFlush, aPos, aLastRolledUp); return RollupInternal(RollupKind::Menu, aOptions, aLastRolledUp);
} }
bool nsXULPopupManager::RollupNativeMenu() { bool nsXULPopupManager::RollupNativeMenu() {
@ -296,9 +294,8 @@ bool nsXULPopupManager::RollupNativeMenu() {
return false; return false;
} }
bool nsXULPopupManager::RollupInternal(RollupKind aKind, uint32_t aCount, bool nsXULPopupManager::RollupInternal(RollupKind aKind,
bool aFlush, const RollupOptions& aOptions,
const LayoutDeviceIntPoint* pos,
nsIContent** aLastRolledUp) { nsIContent** aLastRolledUp) {
if (aLastRolledUp) { if (aLastRolledUp) {
*aLastRolledUp = nullptr; *aLastRolledUp = nullptr;
@ -349,7 +346,7 @@ bool nsXULPopupManager::RollupInternal(RollupKind aKind, uint32_t aCount,
// This would be used to allow adjusting the caret position in an // This would be used to allow adjusting the caret position in an
// autocomplete field without hiding the popup for example. // autocomplete field without hiding the popup for example.
bool noRollupOnAnchor = bool noRollupOnAnchor =
(!consume && pos && (!consume && aOptions.mPoint &&
item->Frame()->GetContent()->AsElement()->AttrValueIs( item->Frame()->GetContent()->AsElement()->AttrValueIs(
kNameSpaceID_None, nsGkAtoms::norolluponanchor, nsGkAtoms::_true, kNameSpaceID_None, nsGkAtoms::norolluponanchor, nsGkAtoms::_true,
eCaseMatters)); eCaseMatters));
@ -358,7 +355,7 @@ bool nsXULPopupManager::RollupInternal(RollupKind aKind, uint32_t aCount,
// when the click was over the anchor. This way, clicking on a menu doesn't // when the click was over the anchor. This way, clicking on a menu doesn't
// reopen the menu. // reopen the menu.
if ((consumeResult == ConsumeOutsideClicks_ParentOnly || noRollupOnAnchor) && if ((consumeResult == ConsumeOutsideClicks_ParentOnly || noRollupOnAnchor) &&
pos) { aOptions.mPoint) {
nsMenuPopupFrame* popupFrame = item->Frame(); nsMenuPopupFrame* popupFrame = item->Frame();
CSSIntRect anchorRect = [&] { CSSIntRect anchorRect = [&] {
if (popupFrame->IsAnchored()) { if (popupFrame->IsAnchored()) {
@ -399,7 +396,8 @@ bool nsXULPopupManager::RollupInternal(RollupKind aKind, uint32_t aCount,
// event will get consumed, so here only a quick coordinates check is // event will get consumed, so here only a quick coordinates check is
// done rather than a slower complete check of what is at that location. // done rather than a slower complete check of what is at that location.
nsPresContext* presContext = item->Frame()->PresContext(); nsPresContext* presContext = item->Frame()->PresContext();
CSSIntPoint posCSSPixels = presContext->DevPixelsToIntCSSPixels(*pos); CSSIntPoint posCSSPixels =
presContext->DevPixelsToIntCSSPixels(*aOptions.mPoint);
if (anchorRect.Contains(posCSSPixels)) { if (anchorRect.Contains(posCSSPixels)) {
if (consumeResult == ConsumeOutsideClicks_ParentOnly) { if (consumeResult == ConsumeOutsideClicks_ParentOnly) {
consume = true; consume = true;
@ -415,12 +413,13 @@ bool nsXULPopupManager::RollupInternal(RollupKind aKind, uint32_t aCount,
return false; return false;
} }
// if a number of popups to close has been specified, determine the last // If a number of popups to close has been specified, determine the last
// popup to close // popup to close.
nsIContent* lastPopup = nullptr; nsIContent* lastPopup = nullptr;
if (aCount != UINT32_MAX) { uint32_t count = aOptions.mCount;
if (count && count != UINT32_MAX) {
nsMenuChainItem* last = item; nsMenuChainItem* last = item;
while (--aCount && last->GetParent()) { while (--count && last->GetParent()) {
last = last->GetParent(); last = last->GetParent();
} }
if (last) { if (last) {
@ -432,9 +431,12 @@ bool nsXULPopupManager::RollupInternal(RollupKind aKind, uint32_t aCount,
RefPtr<nsViewManager> viewManager = RefPtr<nsViewManager> viewManager =
presContext->PresShell()->GetViewManager(); presContext->PresShell()->GetViewManager();
HidePopup(item->Content(), true, true, false, true, lastPopup); HidePopup(item->Content(),
{HidePopupOption::HideChain, HidePopupOption::DeselectMenu,
HidePopupOption::IsRollup},
lastPopup);
if (aFlush) { if (aOptions.mFlush == FlushViews::Yes) {
// The popup's visibility doesn't update until the minimize animation // The popup's visibility doesn't update until the minimize animation
// has finished, so call UpdateWidgetGeometry to update it right away. // has finished, so call UpdateWidgetGeometry to update it right away.
viewManager->UpdateWidgetGeometry(); viewManager->UpdateWidgetGeometry();
@ -950,7 +952,7 @@ void nsXULPopupManager::OnNativeMenuClosed() {
// menus. // menus.
// Close the non-native menus now. This matches the HidePopup call in // Close the non-native menus now. This matches the HidePopup call in
// nsXULMenuCommandEvent::Run. // nsXULMenuCommandEvent::Run.
HidePopup(mPopups->Content(), true, false, false, false); HidePopup(mPopups->Content(), {HidePopupOption::HideChain});
} }
} }
@ -1136,9 +1138,8 @@ nsMenuChainItem* nsXULPopupManager::FindPopup(nsIContent* aPopup) const {
return nullptr; return nullptr;
} }
void nsXULPopupManager::HidePopup(nsIContent* aPopup, bool aHideChain, void nsXULPopupManager::HidePopup(nsIContent* aPopup, HidePopupOptions aOptions,
bool aDeselectMenu, bool aAsynchronous, nsIContent* aLastPopup) {
bool aIsCancel, nsIContent* aLastPopup) {
if (mNativeMenu && mNativeMenu->Element() == aPopup) { if (mNativeMenu && mNativeMenu->Element() == aPopup) {
RefPtr<NativeMenu> menu = mNativeMenu; RefPtr<NativeMenu> menu = mNativeMenu;
(void)menu->Close(); (void)menu->Close();
@ -1152,7 +1153,6 @@ void nsXULPopupManager::HidePopup(nsIContent* aPopup, bool aHideChain,
nsMenuChainItem* foundPopup = FindPopup(aPopup); nsMenuChainItem* foundPopup = FindPopup(aPopup);
bool deselectMenu = false;
nsCOMPtr<nsIContent> popupToHide, nextPopup, lastPopup; nsCOMPtr<nsIContent> popupToHide, nextPopup, lastPopup;
if (foundPopup) { if (foundPopup) {
@ -1160,6 +1160,8 @@ void nsXULPopupManager::HidePopup(nsIContent* aPopup, bool aHideChain,
// If this is a noautohide panel, remove it but don't close any other // If this is a noautohide panel, remove it but don't close any other
// panels. // panels.
popupToHide = aPopup; popupToHide = aPopup;
// XXX This preserves behavior but why is it the right thing to do?
aOptions -= HidePopupOption::DeselectMenu;
} else { } else {
// At this point, foundPopup will be set to the found item in the list. If // 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 // foundPopup is the topmost menu, the one to remove, then there are no
@ -1189,14 +1191,15 @@ void nsXULPopupManager::HidePopup(nsIContent* aPopup, bool aHideChain,
} }
} }
deselectMenu = aDeselectMenu;
popupToHide = topMenu->Content(); popupToHide = topMenu->Content();
popupFrame = topMenu->Frame(); popupFrame = topMenu->Frame();
const bool hideChain = aOptions.contains(HidePopupOption::HideChain);
// Close up another popup if there is one, and we are either hiding the // 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. // entire chain or the item to hide isn't the topmost popup.
nsMenuChainItem* parent = topMenu->GetParent(); nsMenuChainItem* parent = topMenu->GetParent();
if (parent && (aHideChain || topMenu != foundPopup)) { if (parent && (hideChain || topMenu != foundPopup)) {
while (parent && parent->IsNoAutoHide()) { while (parent && parent->IsNoAutoHide()) {
parent = parent->GetParent(); parent = parent->GetParent();
} }
@ -1206,41 +1209,42 @@ void nsXULPopupManager::HidePopup(nsIContent* aPopup, bool aHideChain,
} }
} }
lastPopup = aLastPopup ? aLastPopup : (aHideChain ? nullptr : aPopup); lastPopup = aLastPopup ? aLastPopup : (hideChain ? nullptr : aPopup);
} }
} else if (popupFrame->PopupState() == ePopupPositioning) { } else if (popupFrame->PopupState() == ePopupPositioning) {
// When the popup is in the popuppositioning state, it will not be in the // 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 // mPopups list. We need another way to find it and make sure it does not
// continue the popup showing process. // continue the popup showing process.
deselectMenu = aDeselectMenu;
popupToHide = aPopup; popupToHide = aPopup;
} }
if (popupToHide) { if (!popupToHide) {
nsPopupState state = popupFrame->PopupState(); return;
// 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 nsPopupState state = popupFrame->PopupState();
// popup is invisible, otherwise nsMenuPopupFrame::HidePopup will // If the popup is already being hidden, don't attempt to hide it again
// run again. In the invisible state, we just want the events to fire. if (state == ePopupHiding) {
if (state != ePopupInvisible) { return;
popupFrame->SetPopupState(ePopupHiding); }
}
// For menus, popupToHide is always the frontmost item in the list to hide. // Change the popup state to hiding. Don't set the hiding state if the
if (aAsynchronous) { // popup is invisible, otherwise nsMenuPopupFrame::HidePopup will
nsCOMPtr<nsIRunnable> event = new nsXULPopupHidingEvent( // run again. In the invisible state, we just want the events to fire.
popupToHide, nextPopup, lastPopup, popupFrame->GetPopupType(), if (state != ePopupInvisible) {
deselectMenu, aIsCancel); popupFrame->SetPopupState(ePopupHiding);
aPopup->OwnerDoc()->Dispatch(TaskCategory::Other, event.forget()); }
} else {
RefPtr<nsPresContext> presContext = popupFrame->PresContext(); // For menus, popupToHide is always the frontmost item in the list to hide.
FirePopupHidingEvent(popupToHide, nextPopup, lastPopup, presContext, if (aOptions.contains(HidePopupOption::Async)) {
popupFrame->GetPopupType(), deselectMenu, aIsCancel); nsCOMPtr<nsIRunnable> event =
} new nsXULPopupHidingEvent(popupToHide, nextPopup, lastPopup,
popupFrame->GetPopupType(), aOptions);
aPopup->OwnerDoc()->Dispatch(TaskCategory::Other, event.forget());
} else {
RefPtr<nsPresContext> presContext = popupFrame->PresContext();
FirePopupHidingEvent(popupToHide, nextPopup, lastPopup, presContext,
popupFrame->GetPopupType(), aOptions);
} }
} }
@ -1259,7 +1263,7 @@ void nsXULPopupManager::HideMenu(nsIContent* aMenu) {
if (!popup) { if (!popup) {
return; return;
} }
HidePopup(popup, false, true, false, false); HidePopup(popup, {HidePopupOption::DeselectMenu});
} }
// This is used to hide the popup after a transition finishes. // This is used to hide the popup after a transition finishes.
@ -1272,13 +1276,13 @@ class TransitionEnder final : public nsIDOMEventListener {
virtual ~TransitionEnder() = default; virtual ~TransitionEnder() = default;
public: public:
bool mDeselectMenu; HidePopupOptions mOptions;
NS_DECL_CYCLE_COLLECTING_ISUPPORTS NS_DECL_CYCLE_COLLECTING_ISUPPORTS
NS_DECL_CYCLE_COLLECTION_CLASS(TransitionEnder) NS_DECL_CYCLE_COLLECTION_CLASS(TransitionEnder)
TransitionEnder(nsIContent* aContent, bool aDeselectMenu) TransitionEnder(nsIContent* aContent, HidePopupOptions aOptions)
: mContent(aContent), mDeselectMenu(aDeselectMenu) {} : mContent(aContent), mOptions(aOptions) {}
MOZ_CAN_RUN_SCRIPT NS_IMETHOD HandleEvent(Event* aEvent) override { MOZ_CAN_RUN_SCRIPT NS_IMETHOD HandleEvent(Event* aEvent) override {
mContent->RemoveSystemEventListener(u"transitionend"_ns, this, false); mContent->RemoveSystemEventListener(u"transitionend"_ns, this, false);
@ -1293,7 +1297,7 @@ class TransitionEnder final : public nsIDOMEventListener {
// the first one ending. // the first one ending.
if (RefPtr<nsXULPopupManager> pm = nsXULPopupManager::GetInstance()) { if (RefPtr<nsXULPopupManager> pm = nsXULPopupManager::GetInstance()) {
pm->HidePopupCallback(mContent, popupFrame, nullptr, nullptr, pm->HidePopupCallback(mContent, popupFrame, nullptr, nullptr,
popupFrame->GetPopupType(), mDeselectMenu); popupFrame->GetPopupType(), mOptions);
} }
return NS_OK; return NS_OK;
@ -1310,7 +1314,7 @@ NS_INTERFACE_MAP_END
NS_IMPL_CYCLE_COLLECTION(TransitionEnder, mContent); NS_IMPL_CYCLE_COLLECTION(TransitionEnder, mContent);
void nsXULPopupManager::HidePopupCallback( void nsXULPopupManager::HidePopupCallback(
nsIContent* aPopup, nsMenuPopupFrame* aPopupFrame, nsIContent* aNextPopup, nsIContent* aPopup, nsMenuPopupFrame* aPopupFrame, nsIContent* aNextPopup,
nsIContent* aLastPopup, PopupType aPopupType, bool aDeselectMenu) { nsIContent* aLastPopup, PopupType aPopupType, HidePopupOptions aOptions) {
if (mCloseTimer && mTimerMenu == aPopupFrame) { if (mCloseTimer && mTimerMenu == aPopupFrame) {
mCloseTimer->Cancel(); mCloseTimer->Cancel();
mCloseTimer = nullptr; mCloseTimer = nullptr;
@ -1331,7 +1335,8 @@ void nsXULPopupManager::HidePopupCallback(
} }
AutoWeakFrame weakFrame(aPopupFrame); AutoWeakFrame weakFrame(aPopupFrame);
aPopupFrame->HidePopup(aDeselectMenu, ePopupClosed); aPopupFrame->HidePopup(aOptions.contains(HidePopupOption::DeselectMenu),
ePopupClosed);
NS_ENSURE_TRUE_VOID(weakFrame.IsAlive()); NS_ENSURE_TRUE_VOID(weakFrame.IsAlive());
// send the popuphidden event synchronously. This event has no default // send the popuphidden event synchronously. This event has no default
@ -1369,7 +1374,7 @@ void nsXULPopupManager::HidePopupCallback(
RefPtr<nsPresContext> presContext = popupFrame->PresContext(); RefPtr<nsPresContext> presContext = popupFrame->PresContext();
FirePopupHidingEvent(popupToHide, nextPopup, aLastPopup, presContext, FirePopupHidingEvent(popupToHide, nextPopup, aLastPopup, presContext,
foundMenu->GetPopupType(), aDeselectMenu, false); foundMenu->GetPopupType(), aOptions);
} }
} }
} }
@ -1626,10 +1631,12 @@ void nsXULPopupManager::BeginShowingPopup(const PendingPopup& aPendingPopup,
} }
} }
void nsXULPopupManager::FirePopupHidingEvent( void nsXULPopupManager::FirePopupHidingEvent(nsIContent* aPopup,
nsIContent* aPopup, nsIContent* aNextPopup, nsIContent* aLastPopup, nsIContent* aNextPopup,
nsPresContext* aPresContext, PopupType aPopupType, bool aDeselectMenu, nsIContent* aLastPopup,
bool aIsCancel) { nsPresContext* aPresContext,
PopupType aPopupType,
HidePopupOptions aOptions) {
nsCOMPtr<nsIContent> popup = aPopup; nsCOMPtr<nsIContent> popup = aPopup;
RefPtr<PresShell> presShell = aPresContext->PresShell(); RefPtr<PresShell> presShell = aPresContext->PresShell();
Unused << presShell; // This presShell may be keeping things alive Unused << presShell; // This presShell may be keeping things alive
@ -1700,7 +1707,8 @@ void nsXULPopupManager::FirePopupHidingEvent(
} }
// If animate="cancel", only show the transition if cancelling the popup // If animate="cancel", only show the transition if cancelling the popup
// or rolling up. // or rolling up.
if (animate.EqualsLiteral("cancel") && !aIsCancel) { if (animate.EqualsLiteral("cancel") &&
!aOptions.contains(HidePopupOption::IsRollup)) {
return false; return false;
} }
return true; return true;
@ -1711,13 +1719,13 @@ void nsXULPopupManager::FirePopupHidingEvent(
// view will be hidden and you won't be able to see it. // view will be hidden and you won't be able to see it.
if (shouldAnimate && AnimationUtils::HasCurrentTransitions( if (shouldAnimate && AnimationUtils::HasCurrentTransitions(
aPopup->AsElement(), PseudoStyleType::NotPseudo)) { aPopup->AsElement(), PseudoStyleType::NotPseudo)) {
RefPtr<TransitionEnder> ender = new TransitionEnder(aPopup, aDeselectMenu); RefPtr<TransitionEnder> ender = new TransitionEnder(aPopup, aOptions);
aPopup->AddSystemEventListener(u"transitionend"_ns, ender, false, false); aPopup->AddSystemEventListener(u"transitionend"_ns, ender, false, false);
return; return;
} }
HidePopupCallback(aPopup, popupFrame, aNextPopup, aLastPopup, aPopupType, HidePopupCallback(aPopup, popupFrame, aNextPopup, aLastPopup, aPopupType,
aDeselectMenu); aOptions);
} }
bool nsXULPopupManager::IsPopupOpen(nsIContent* aPopup) { bool nsXULPopupManager::IsPopupOpen(nsIContent* aPopup) {
@ -1939,7 +1947,7 @@ void nsXULPopupManager::PopupDestroyed(nsMenuPopupFrame* aPopup) {
} else { } else {
// HidePopup will take care of hiding any of its children, so // HidePopup will take care of hiding any of its children, so
// break out afterwards // break out afterwards
HidePopup(child->Content(), false, false, true, false); HidePopup(child->Content(), {HidePopupOption::Async});
break; break;
} }
} }
@ -2141,7 +2149,7 @@ void nsXULPopupManager::KillMenuTimer() {
mCloseTimer = nullptr; mCloseTimer = nullptr;
if (mTimerMenu->IsOpen()) { if (mTimerMenu->IsOpen()) {
HidePopup(mTimerMenu->GetContent(), false, false, true, false); HidePopup(mTimerMenu->GetContent(), {HidePopupOption::Async});
} }
} }
@ -2378,9 +2386,7 @@ bool nsXULPopupManager::HandleKeyboardNavigationInPopup(
// close a submenu when Left is pressed // close a submenu when Left is pressed
if (nsMenuPopupFrame* popupFrame = if (nsMenuPopupFrame* popupFrame =
currentItem->GetMenuPopup(FlushType::None)) { currentItem->GetMenuPopup(FlushType::None)) {
HidePopup(popupFrame->GetContent(), /* aHideChain = */ false, HidePopup(popupFrame->GetContent(), {});
/* aDeselectMenu = */ false, /* aAsynchronous = */ false,
/* aIsCancel = */ false);
} }
return true; return true;
} }
@ -2396,7 +2402,7 @@ bool nsXULPopupManager::HandleKeyboardEventWithKeyCode(
if (aTopVisibleMenuItem && if (aTopVisibleMenuItem &&
aTopVisibleMenuItem->GetPopupType() != PopupType::Menu) { aTopVisibleMenuItem->GetPopupType() != PopupType::Menu) {
if (keyCode == KeyboardEvent_Binding::DOM_VK_ESCAPE) { if (keyCode == KeyboardEvent_Binding::DOM_VK_ESCAPE) {
HidePopup(aTopVisibleMenuItem->Content(), false, false, false, true); HidePopup(aTopVisibleMenuItem->Content(), {HidePopupOption::IsRollup});
aKeyEvent->StopPropagation(); aKeyEvent->StopPropagation();
aKeyEvent->StopCrossProcessForwarding(); aKeyEvent->StopCrossProcessForwarding();
aKeyEvent->PreventDefault(); aKeyEvent->PreventDefault();
@ -2412,7 +2418,7 @@ bool nsXULPopupManager::HandleKeyboardEventWithKeyCode(
// roll up the popup when alt+up/down are pressed within a menulist. // roll up the popup when alt+up/down are pressed within a menulist.
if (aKeyEvent->AltKey() && aTopVisibleMenuItem && if (aKeyEvent->AltKey() && aTopVisibleMenuItem &&
aTopVisibleMenuItem->Frame()->IsMenuList()) { aTopVisibleMenuItem->Frame()->IsMenuList()) {
Rollup(0, false, nullptr, nullptr); Rollup({});
break; break;
} }
[[fallthrough]]; [[fallthrough]];
@ -2439,7 +2445,7 @@ bool nsXULPopupManager::HandleKeyboardEventWithKeyCode(
// though in this latter case, a menu didn't actually close, the effect // though in this latter case, a menu didn't actually close, the effect
// ends up being the same. Similar for the tab key below. // ends up being the same. Similar for the tab key below.
if (aTopVisibleMenuItem) { if (aTopVisibleMenuItem) {
HidePopup(aTopVisibleMenuItem->Content(), false, false, false, true); HidePopup(aTopVisibleMenuItem->Content(), {HidePopupOption::IsRollup});
} else if (mActiveMenuBar) { } else if (mActiveMenuBar) {
mActiveMenuBar->MenuClosed(); mActiveMenuBar->MenuClosed();
} }
@ -2454,7 +2460,7 @@ bool nsXULPopupManager::HandleKeyboardEventWithKeyCode(
kNameSpaceID_None, nsGkAtoms::activateontab, nsGkAtoms::_true, kNameSpaceID_None, nsGkAtoms::activateontab, nsGkAtoms::_true,
eCaseMatters)) { eCaseMatters)) {
// Close popups or deactivate menubar when Tab or F10 are pressed // Close popups or deactivate menubar when Tab or F10 are pressed
Rollup(0, false, nullptr, nullptr); Rollup({});
break; break;
} else if (mActiveMenuBar) { } else if (mActiveMenuBar) {
mActiveMenuBar->MenuClosed(); mActiveMenuBar->MenuClosed();
@ -2600,7 +2606,7 @@ nsresult nsXULPopupManager::KeyDown(KeyboardEvent* aKeyEvent) {
// modifiers are already down. // modifiers are already down.
nsMenuChainItem* item = GetTopVisibleMenu(); nsMenuChainItem* item = GetTopVisibleMenu();
if (item && !item->Frame()->IsMenuList()) { if (item && !item->Frame()->IsMenuList()) {
Rollup(0, false, nullptr, nullptr); Rollup({});
} else if (mActiveMenuBar) { } else if (mActiveMenuBar) {
mActiveMenuBar->MenuClosed(); mActiveMenuBar->MenuClosed();
} }
@ -2663,7 +2669,7 @@ nsXULPopupHidingEvent::Run() {
nsCOMPtr<nsIContent> nextPopup = mNextPopup; nsCOMPtr<nsIContent> nextPopup = mNextPopup;
nsCOMPtr<nsIContent> lastPopup = mLastPopup; nsCOMPtr<nsIContent> lastPopup = mLastPopup;
pm->FirePopupHidingEvent(popup, nextPopup, lastPopup, presContext, pm->FirePopupHidingEvent(popup, nextPopup, lastPopup, presContext,
mPopupType, mDeselectMenu, mIsRollup); mPopupType, mOptions);
} }
} }
return NS_OK; return NS_OK;
@ -2816,8 +2822,11 @@ nsXULMenuCommandEvent::Run() {
if (mCloseMenuMode != CloseMenuMode_None) { if (mCloseMenuMode != CloseMenuMode_None) {
if (RefPtr popup = menu->GetContainingPopupElement()) { if (RefPtr popup = menu->GetContainingPopupElement()) {
pm->HidePopup(popup, mCloseMenuMode == CloseMenuMode_Auto, true, false, HidePopupOptions options{HidePopupOption::DeselectMenu};
false); if (mCloseMenuMode == CloseMenuMode_Auto) {
options += HidePopupOption::HideChain;
}
pm->HidePopup(popup, options);
} }
} }

View File

@ -161,6 +161,21 @@ enum nsIgnoreKeys {
eIgnoreKeys_Shortcuts, eIgnoreKeys_Shortcuts,
}; };
enum class HidePopupOption : uint8_t {
// If the entire chain of menus should be closed.
HideChain,
// If the parent <menu> of the popup should not be deselected. This will not
// be set when the menu is closed by pressing the Escape key.
DeselectMenu,
// If the first popuphiding event should be sent asynchrously. This should
// be set if HidePopup is called from a frame.
Async,
// If this popup is hiding due to being cancelled.
IsRollup,
};
using HidePopupOptions = mozilla::EnumSet<HidePopupOption>;
#define NS_DIRECTION_IS_INLINE(dir) \ #define NS_DIRECTION_IS_INLINE(dir) \
(dir == eNavigationDirection_Start || dir == eNavigationDirection_End) (dir == eNavigationDirection_Start || dir == eNavigationDirection_End)
#define NS_DIRECTION_IS_BLOCK(dir) \ #define NS_DIRECTION_IS_BLOCK(dir) \
@ -280,14 +295,13 @@ class nsXULPopupHidingEvent : public mozilla::Runnable {
public: public:
nsXULPopupHidingEvent(nsIContent* aPopup, nsIContent* aNextPopup, nsXULPopupHidingEvent(nsIContent* aPopup, nsIContent* aNextPopup,
nsIContent* aLastPopup, PopupType aPopupType, nsIContent* aLastPopup, PopupType aPopupType,
bool aDeselectMenu, bool aIsCancel) HidePopupOptions aOptions)
: mozilla::Runnable("nsXULPopupHidingEvent"), : mozilla::Runnable("nsXULPopupHidingEvent"),
mPopup(aPopup), mPopup(aPopup),
mNextPopup(aNextPopup), mNextPopup(aNextPopup),
mLastPopup(aLastPopup), mLastPopup(aLastPopup),
mPopupType(aPopupType), mPopupType(aPopupType),
mDeselectMenu(aDeselectMenu), mOptions(aOptions) {
mIsRollup(aIsCancel) {
NS_ASSERTION(aPopup, NS_ASSERTION(aPopup,
"null popup supplied to nsXULPopupHidingEvent constructor"); "null popup supplied to nsXULPopupHidingEvent constructor");
// aNextPopup and aLastPopup may be null // aNextPopup and aLastPopup may be null
@ -300,8 +314,7 @@ class nsXULPopupHidingEvent : public mozilla::Runnable {
nsCOMPtr<nsIContent> mNextPopup; nsCOMPtr<nsIContent> mNextPopup;
nsCOMPtr<nsIContent> mLastPopup; nsCOMPtr<nsIContent> mLastPopup;
PopupType mPopupType; PopupType mPopupType;
bool mDeselectMenu; HidePopupOptions mOptions;
bool mIsRollup;
}; };
// this class is used for dispatching popuppositioned events asynchronously. // this class is used for dispatching popuppositioned events asynchronously.
@ -375,9 +388,8 @@ class nsXULPopupManager final : public nsIDOMEventListener,
// nsIRollupListener // nsIRollupListener
MOZ_CAN_RUN_SCRIPT_BOUNDARY MOZ_CAN_RUN_SCRIPT_BOUNDARY
bool Rollup(uint32_t aCount, bool aFlush, bool Rollup(const RollupOptions&,
const mozilla::LayoutDeviceIntPoint* aPos, nsIContent** aLastRolledUp = nullptr) override;
nsIContent** aLastRolledUp) override;
bool ShouldRollupOnMouseWheelEvent() override; bool ShouldRollupOnMouseWheelEvent() override;
bool ShouldConsumeOnMouseWheelEvent() override; bool ShouldConsumeOnMouseWheelEvent() override;
bool ShouldRollupOnMouseActivate() override; bool ShouldRollupOnMouseActivate() override;
@ -389,8 +401,7 @@ class nsXULPopupManager final : public nsIDOMEventListener,
enum class RollupKind { Tooltip, Menu }; enum class RollupKind { Tooltip, Menu };
MOZ_CAN_RUN_SCRIPT MOZ_CAN_RUN_SCRIPT
bool RollupInternal(RollupKind, uint32_t aCount, bool aFlush, bool RollupInternal(RollupKind, const RollupOptions&,
const mozilla::LayoutDeviceIntPoint* pos,
nsIContent** aLastRolledUp); nsIContent** aLastRolledUp);
// NativeMenu::Observer // NativeMenu::Observer
@ -507,21 +518,10 @@ class nsXULPopupManager final : public nsIDOMEventListener,
/* /*
* Hide a popup aPopup. If the popup is in a <menu>, then also inform the * Hide a popup aPopup. If the popup is in a <menu>, then also inform the
* menu that the popup is being hidden. * menu that the popup is being hidden.
*
* aHideChain - true if the entire chain of menus should be closed. If false,
* only this popup is closed.
* aDeselectMenu - true if the parent <menu> of the popup should be
* deselected. This will be false when the menu is closed by
* pressing the Escape key.
* aAsynchronous - true if the first popuphiding event should be sent
* asynchrously. This should be true if HidePopup is called
* from a frame.
* aIsCancel - true if this popup is hiding due to being cancelled.
* aLastPopup - optional popup to close last when hiding a chain of menus. * aLastPopup - optional popup to close last when hiding a chain of menus.
* If null, then all popups will be closed. * If null, then all popups will be closed.
*/ */
void HidePopup(nsIContent* aPopup, bool aHideChain, bool aDeselectMenu, void HidePopup(nsIContent* aPopup, HidePopupOptions,
bool aAsynchronous, bool aIsCancel,
nsIContent* aLastPopup = nullptr); nsIContent* aLastPopup = nullptr);
/* /*
@ -754,7 +754,7 @@ class nsXULPopupManager final : public nsIDOMEventListener,
bool aSelectFirstItem); bool aSelectFirstItem);
MOZ_CAN_RUN_SCRIPT void HidePopupCallback( MOZ_CAN_RUN_SCRIPT void HidePopupCallback(
nsIContent* aPopup, nsMenuPopupFrame* aPopupFrame, nsIContent* aNextPopup, nsIContent* aPopup, nsMenuPopupFrame* aPopupFrame, nsIContent* aNextPopup,
nsIContent* aLastPopup, PopupType aPopupType, bool aDeselectMenu); nsIContent* aLastPopup, PopupType aPopupType, HidePopupOptions);
/** /**
* Trigger frame construction and reflow in the popup, fire a popupshowing * Trigger frame construction and reflow in the popup, fire a popupshowing
@ -786,14 +786,13 @@ class nsXULPopupManager final : public nsIDOMEventListener,
* aLastPopup - the last popup in the chain to hide * aLastPopup - the last popup in the chain to hide
* aPresContext - nsPresContext for the popup's frame * aPresContext - nsPresContext for the popup's frame
* aPopupType - the PopupType of the frame. * aPopupType - the PopupType of the frame.
* aDeselectMenu - true to unhighlight the menu when hiding it * aOptions - the relevant options to hide the popup. Only a subset is looked
* aIsCancel - true if this popup is hiding due to being cancelled. * at.
*/ */
MOZ_CAN_RUN_SCRIPT_BOUNDARY MOZ_CAN_RUN_SCRIPT_BOUNDARY
void FirePopupHidingEvent(nsIContent* aPopup, nsIContent* aNextPopup, void FirePopupHidingEvent(nsIContent* aPopup, nsIContent* aNextPopup,
nsIContent* aLastPopup, nsPresContext* aPresContext, nsIContent* aLastPopup, nsPresContext* aPresContext,
PopupType aPopupType, bool aDeselectMenu, PopupType aPopupType, HidePopupOptions aOptions);
bool aIsCancel);
/** /**
* Handle keyboard navigation within a menu popup specified by aItem. * Handle keyboard navigation within a menu popup specified by aItem.

View File

@ -474,7 +474,7 @@ void nsXULTooltipListener::LaunchTooltip() {
nsresult nsXULTooltipListener::HideTooltip() { nsresult nsXULTooltipListener::HideTooltip() {
if (nsCOMPtr<nsIContent> currentTooltip = do_QueryReferent(mCurrentTooltip)) { if (nsCOMPtr<nsIContent> currentTooltip = do_QueryReferent(mCurrentTooltip)) {
if (nsXULPopupManager* pm = nsXULPopupManager::GetInstance()) { if (nsXULPopupManager* pm = nsXULPopupManager::GetInstance()) {
pm->HidePopup(currentTooltip, false, false, false, false); pm->HidePopup(currentTooltip, {});
} }
} }

View File

@ -1058,9 +1058,8 @@ void nsView::DynamicToolbarOffsetChanged(ScreenIntCoord aOffset) {
bool nsView::RequestWindowClose(nsIWidget* aWidget) { bool nsView::RequestWindowClose(nsIWidget* aWidget) {
if (mFrame && IsPopupWidget(aWidget) && mFrame->IsMenuPopupFrame()) { if (mFrame && IsPopupWidget(aWidget) && mFrame->IsMenuPopupFrame()) {
nsXULPopupManager* pm = nsXULPopupManager::GetInstance(); if (nsXULPopupManager* pm = nsXULPopupManager::GetInstance()) {
if (pm) { pm->HidePopup(mFrame->GetContent(), {HidePopupOption::DeselectMenu});
pm->HidePopup(mFrame->GetContent(), false, true, false, false);
return true; return true;
} }
} }

View File

@ -2397,56 +2397,57 @@ NSEvent* gLastDragMouseDownEvent = nil; // [strong]
} }
nsCOMPtr<nsIWidget> rollupWidget = rollupListener->GetRollupWidget(); nsCOMPtr<nsIWidget> rollupWidget = rollupListener->GetRollupWidget();
if (rollupWidget) { if (!rollupWidget) {
NSWindow* currentPopup = static_cast<NSWindow*>(rollupWidget->GetNativeData(NS_NATIVE_WINDOW)); return consumeEvent;
if (!nsCocoaUtils::IsEventOverWindow(theEvent, currentPopup)) { }
// event is not over the rollup window, default is to roll up
bool shouldRollup = true;
// check to see if scroll/zoom events should roll up the popup NSWindow* currentPopup = static_cast<NSWindow*>(rollupWidget->GetNativeData(NS_NATIVE_WINDOW));
if (isWheelTypeEvent) { if (nsCocoaUtils::IsEventOverWindow(theEvent, currentPopup)) {
shouldRollup = rollupListener->ShouldRollupOnMouseWheelEvent(); return consumeEvent;
// consume scroll events that aren't over the popup }
// unless the popup is an arrow panel
consumeEvent = rollupListener->ShouldConsumeOnMouseWheelEvent();
}
// if we're dealing with menus, we probably have submenus and // Check to see if scroll/zoom events should roll up the popup
// we don't want to rollup if the click is in a parent menu of if (isWheelTypeEvent) {
// the current submenu // consume scroll events that aren't over the popup unless the popup is an
uint32_t popupsToRollup = UINT32_MAX; // arrow panel.
AutoTArray<nsIWidget*, 5> widgetChain; consumeEvent = rollupListener->ShouldConsumeOnMouseWheelEvent();
uint32_t sameTypeCount = rollupListener->GetSubmenuWidgetChain(&widgetChain); if (!rollupListener->ShouldRollupOnMouseWheelEvent()) {
for (uint32_t i = 0; i < widgetChain.Length(); i++) { return consumeEvent;
nsIWidget* widget = widgetChain[i];
NSWindow* currWindow = (NSWindow*)widget->GetNativeData(NS_NATIVE_WINDOW);
if (nsCocoaUtils::IsEventOverWindow(theEvent, currWindow)) {
// don't roll up if the mouse event occurred within a menu of the
// same type. If the mouse event occurred in a menu higher than
// that, roll up, but pass the number of popups to Rollup so
// that only those of the same type close up.
if (i < sameTypeCount) {
shouldRollup = false;
} else {
popupsToRollup = sameTypeCount;
}
break;
}
}
if (shouldRollup) {
if ([theEvent type] == NSEventTypeLeftMouseDown) {
NSPoint point = [NSEvent mouseLocation];
FlipCocoaScreenCoordinate(point);
LayoutDeviceIntPoint devPoint = mGeckoChild->CocoaPointsToDevPixels(point);
consumeEvent = (BOOL)rollupListener->Rollup(popupsToRollup, true, &devPoint, nullptr);
} else {
consumeEvent = (BOOL)rollupListener->Rollup(popupsToRollup, true, nullptr, nullptr);
}
}
} }
} }
// if we're dealing with menus, we probably have submenus and
// we don't want to rollup if the click is in a parent menu of
// the current submenu
uint32_t popupsToRollup = UINT32_MAX;
AutoTArray<nsIWidget*, 5> widgetChain;
uint32_t sameTypeCount = rollupListener->GetSubmenuWidgetChain(&widgetChain);
for (uint32_t i = 0; i < widgetChain.Length(); i++) {
nsIWidget* widget = widgetChain[i];
NSWindow* currWindow = (NSWindow*)widget->GetNativeData(NS_NATIVE_WINDOW);
if (nsCocoaUtils::IsEventOverWindow(theEvent, currWindow)) {
// don't roll up if the mouse event occurred within a menu of the
// same type. If the mouse event occurred in a menu higher than
// that, roll up, but pass the number of popups to Rollup so
// that only those of the same type close up.
if (i < sameTypeCount) {
return consumeEvent;
}
popupsToRollup = sameTypeCount;
break;
}
}
LayoutDeviceIntPoint devPoint;
nsIRollupListener::RollupOptions rollupOptions{popupsToRollup,
nsIRollupListener::FlushViews::Yes};
if ([theEvent type] == NSEventTypeLeftMouseDown) {
NSPoint point = [NSEvent mouseLocation];
FlipCocoaScreenCoordinate(point);
devPoint = mGeckoChild->CocoaPointsToDevPixels(point);
rollupOptions.mPoint = &devPoint;
}
consumeEvent = (BOOL)rollupListener->Rollup(rollupOptions);
return consumeEvent; return consumeEvent;
NS_OBJC_END_TRY_BLOCK_RETURN(NO); NS_OBJC_END_TRY_BLOCK_RETURN(NO);

View File

@ -120,7 +120,7 @@ static void RollUpPopups() {
nsCOMPtr<nsIWidget> rollupWidget = rollupListener->GetRollupWidget(); nsCOMPtr<nsIWidget> rollupWidget = rollupListener->GetRollupWidget();
if (!rollupWidget) return; if (!rollupWidget) return;
rollupListener->Rollup(0, true, nullptr, nullptr); rollupListener->Rollup({0, nsIRollupListener::FlushViews::Yes});
} }
nsCocoaWindow::nsCocoaWindow() nsCocoaWindow::nsCocoaWindow()

View File

@ -1159,7 +1159,7 @@ void nsMenuX::Dump(uint32_t aIndent) const {
if (rollupListener) { if (rollupListener) {
nsCOMPtr<nsIWidget> rollupWidget = rollupListener->GetRollupWidget(); nsCOMPtr<nsIWidget> rollupWidget = rollupListener->GetRollupWidget();
if (rollupWidget) { if (rollupWidget) {
rollupListener->Rollup(0, true, nullptr, nullptr); rollupListener->Rollup({0, nsIRollupListener::FlushViews::Yes});
[menu cancelTracking]; [menu cancelTracking];
return; return;
} }

View File

@ -179,7 +179,7 @@ void nsToolkit::MonitorAllProcessMouseEvents() {
return; return;
} }
rollupListener->Rollup(0, false, nullptr, nullptr); rollupListener->Rollup({});
}]; }];
} }

View File

@ -625,7 +625,7 @@ void nsWindow::Destroy() {
if (rollupListener) { if (rollupListener) {
nsCOMPtr<nsIWidget> rollupWidget = rollupListener->GetRollupWidget(); nsCOMPtr<nsIWidget> rollupWidget = rollupListener->GetRollupWidget();
if (static_cast<nsIWidget*>(this) == rollupWidget) { if (static_cast<nsIWidget*>(this) == rollupWidget) {
rollupListener->Rollup(0, false, nullptr, nullptr); rollupListener->Rollup({});
} }
} }
@ -4017,7 +4017,7 @@ gboolean nsWindow::OnConfigureEvent(GtkWidget* aWidget,
// This check avoids unwanted rollup on spurious configure events from // This check avoids unwanted rollup on spurious configure events from
// Cygwin/X (bug 672103). // Cygwin/X (bug 672103).
if (mBounds.x != screenBounds.x || mBounds.y != screenBounds.y) { if (mBounds.x != screenBounds.x || mBounds.y != screenBounds.y) {
CheckForRollup(0, 0, false, true); RollupAllMenus();
} }
} }
@ -4782,7 +4782,7 @@ void nsWindow::OnContainerFocusOutEvent(GdkEventFocus* aEvent) {
}(); }();
if (shouldRollupMenus) { if (shouldRollupMenus) {
CheckForRollup(0, 0, false, true); RollupAllMenus();
} }
if (RefPtr pm = nsXULPopupManager::GetInstance()) { if (RefPtr pm = nsXULPopupManager::GetInstance()) {
@ -7407,53 +7407,52 @@ bool nsWindow::CheckForRollup(gdouble aMouseX, gdouble aMouseY, bool aIsWheel,
return false; return false;
} }
bool retVal = false;
auto* currentPopup = auto* currentPopup =
(GdkWindow*)rollupWidget->GetNativeData(NS_NATIVE_WINDOW); (GdkWindow*)rollupWidget->GetNativeData(NS_NATIVE_WINDOW);
if (aAlwaysRollup || !is_mouse_in_window(currentPopup, aMouseX, aMouseY)) { if (!aAlwaysRollup && is_mouse_in_window(currentPopup, aMouseX, aMouseY)) {
bool rollup = true; return false;
if (aIsWheel) { }
rollup = rollupListener->ShouldRollupOnMouseWheelEvent(); bool retVal = false;
retVal = rollupListener->ShouldConsumeOnMouseWheelEvent(); if (aIsWheel) {
retVal = rollupListener->ShouldConsumeOnMouseWheelEvent();
if (!rollupListener->ShouldRollupOnMouseWheelEvent()) {
return retVal;
} }
// if we're dealing with menus, we probably have submenus and }
// we don't want to rollup if the click is in a parent menu of LayoutDeviceIntPoint point;
// the current submenu nsIRollupListener::RollupOptions options{0,
uint32_t popupsToRollup = UINT32_MAX; nsIRollupListener::FlushViews::Yes};
if (!aAlwaysRollup) { // if we're dealing with menus, we probably have submenus and
AutoTArray<nsIWidget*, 5> widgetChain; // we don't want to rollup if the click is in a parent menu of
uint32_t sameTypeCount = // the current submenu
rollupListener->GetSubmenuWidgetChain(&widgetChain); if (!aAlwaysRollup) {
for (unsigned long i = 0; i < widgetChain.Length(); ++i) { AutoTArray<nsIWidget*, 5> widgetChain;
nsIWidget* widget = widgetChain[i]; uint32_t sameTypeCount =
auto* currWindow = (GdkWindow*)widget->GetNativeData(NS_NATIVE_WINDOW); rollupListener->GetSubmenuWidgetChain(&widgetChain);
if (is_mouse_in_window(currWindow, aMouseX, aMouseY)) { for (unsigned long i = 0; i < widgetChain.Length(); ++i) {
// don't roll up if the mouse event occurred within a nsIWidget* widget = widgetChain[i];
// menu of the same type. If the mouse event occurred auto* currWindow = (GdkWindow*)widget->GetNativeData(NS_NATIVE_WINDOW);
// in a menu higher than that, roll up, but pass the if (is_mouse_in_window(currWindow, aMouseX, aMouseY)) {
// number of popups to Rollup so that only those of the // Don't roll up if the mouse event occurred within a menu of the same
// same type close up. // type.
if (i < sameTypeCount) { // If the mouse event occurred in a menu higher than that, roll up, but
rollup = false; // pass the number of popups to Rollup so that only those of the same
} else { // type close up.
popupsToRollup = sameTypeCount; if (i < sameTypeCount) {
} return retVal;
break;
} }
} // foreach parent menu widget options.mCount = sameTypeCount;
} // if rollup listener knows about menus break;
}
// if we've determined that we should still rollup, do it. } // foreach parent menu widget
bool usePoint = !aIsWheel && !aAlwaysRollup; if (!aIsWheel) {
LayoutDeviceIntPoint point;
if (usePoint) {
point = GdkEventCoordsToDevicePixels(aMouseX, aMouseY); point = GdkEventCoordsToDevicePixels(aMouseX, aMouseY);
options.mPoint = &point;
} }
if (rollup && }
rollupListener->Rollup(popupsToRollup, true,
usePoint ? &point : nullptr, nullptr)) { if (rollupListener->Rollup(options)) {
retVal = true; retVal = true;
}
} }
return retVal; return retVal;
} }

View File

@ -504,7 +504,8 @@ class nsWindow final : public nsBaseWidget {
const mozilla::LayoutDeviceIntPoint& aRefPoint); const mozilla::LayoutDeviceIntPoint& aRefPoint);
bool CheckForRollup(gdouble aMouseX, gdouble aMouseY, bool aIsWheel, bool CheckForRollup(gdouble aMouseX, gdouble aMouseY, bool aIsWheel,
bool aAlwaysRollup); bool aAlwaysRollup);
void CheckForRollupDuringGrab() { CheckForRollup(0, 0, false, true); } void RollupAllMenus() { CheckForRollup(0, 0, false, true); }
void CheckForRollupDuringGrab() { RollupAllMenus(); }
bool GetDragInfo(mozilla::WidgetMouseEvent* aMouseEvent, GdkWindow** aWindow, bool GetDragInfo(mozilla::WidgetMouseEvent* aMouseEvent, GdkWindow** aWindow,
gint* aButton, gint* aRootX, gint* aRootY); gint* aButton, gint* aRootX, gint* aRootY);

View File

@ -567,7 +567,7 @@ nsBaseDragService::EndDragSession(bool aDoneDrag, uint32_t aKeyModifiers) {
if (mDragPopup) { if (mDragPopup) {
nsXULPopupManager* pm = nsXULPopupManager::GetInstance(); nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
if (pm) { if (pm) {
pm->HidePopup(mDragPopup, false, true, false, false); pm->HidePopup(mDragPopup, {HidePopupOption::DeselectMenu});
} }
} }

View File

@ -16,26 +16,29 @@ class nsIWidget;
class nsIRollupListener { class nsIRollupListener {
public: public:
enum class FlushViews : bool { No, Yes };
struct RollupOptions {
// aCount is the number of popups in a chain to close. If this is
// zero, then all popups are closed.
uint32_t mCount = 0;
// If this is true, then views should be flushed after the rollup.
FlushViews mFlush = FlushViews::No;
// This is the mouse pointer position where the event that triggered the
// rollup occurred, which may be nullptr.
const mozilla::LayoutDeviceIntPoint* mPoint = nullptr;
};
/** /**
* Notifies the object to rollup, optionally returning the node that * Notifies the object to rollup, optionally returning the node that
* was just rolled up. * was just rolled up in aLastRolledUp, if non-null.
* *
* If aFlush is true, then views should be flushed after the rollup. * aLastRolledUp is not addrefed.
*
* aPoint is the mouse pointer position where the event that triggered the
* rollup occurred, which may be nullptr.
*
* aCount is the number of popups in a chain to close. If this is
* UINT32_MAX, then all popups are closed.
* If aLastRolledUp is non-null, it will be set to the last rolled up popup,
* if this is supported. aLastRolledUp is not addrefed.
* *
* Returns true if the event that the caller is processing should be consumed. * Returns true if the event that the caller is processing should be consumed.
*/ */
MOZ_CAN_RUN_SCRIPT_BOUNDARY MOZ_CAN_RUN_SCRIPT_BOUNDARY
virtual bool Rollup(uint32_t aCount, bool aFlush, virtual bool Rollup(const RollupOptions&,
const mozilla::LayoutDeviceIntPoint* aPoint, nsIContent** aLastRolledUp = nullptr) = 0;
nsIContent** aLastRolledUp) = 0;
/** /**
* Asks the RollupListener if it should rollup on mouse wheel events * Asks the RollupListener if it should rollup on mouse wheel events

View File

@ -7415,7 +7415,7 @@ void nsWindow::OnDestroy() {
rollupWidget = rollupListener->GetRollupWidget(); rollupWidget = rollupListener->GetRollupWidget();
} }
if (this == rollupWidget) { if (this == rollupWidget) {
if (rollupListener) rollupListener->Rollup(0, false, nullptr, nullptr); rollupListener->Rollup({});
CaptureRollupEvents(false); CaptureRollupEvents(false);
} }
@ -8410,6 +8410,11 @@ bool nsWindow::DealWithPopups(HWND aWnd, UINT aMessage, WPARAM aWParam,
// Only need to deal with the last rollup for left mouse down events. // Only need to deal with the last rollup for left mouse down events.
NS_ASSERTION(!nsAutoRollup::GetLastRollup(), "last rollup is null"); NS_ASSERTION(!nsAutoRollup::GetLastRollup(), "last rollup is null");
nsIRollupListener::RollupOptions rollupOptions{
popupsToRollup,
nsIRollupListener::FlushViews::Yes,
};
if (nativeMessage == WM_TOUCH || nativeMessage == WM_LBUTTONDOWN || if (nativeMessage == WM_TOUCH || nativeMessage == WM_LBUTTONDOWN ||
nativeMessage == WM_POINTERDOWN) { nativeMessage == WM_POINTERDOWN) {
LayoutDeviceIntPoint pos; LayoutDeviceIntPoint pos;
@ -8427,13 +8432,12 @@ bool nsWindow::DealWithPopups(HWND aWnd, UINT aMessage, WPARAM aWParam,
pos = LayoutDeviceIntPoint(pt.x, pt.y); pos = LayoutDeviceIntPoint(pt.x, pt.y);
} }
nsIContent* lastRollup; rollupOptions.mPoint = &pos;
consumeRollupEvent = nsIContent* lastRollup = nullptr;
rollupListener->Rollup(popupsToRollup, true, &pos, &lastRollup); consumeRollupEvent = rollupListener->Rollup(rollupOptions, &lastRollup);
nsAutoRollup::SetLastRollup(lastRollup); nsAutoRollup::SetLastRollup(lastRollup);
} else { } else {
consumeRollupEvent = consumeRollupEvent = rollupListener->Rollup(rollupOptions);
rollupListener->Rollup(popupsToRollup, true, nullptr, nullptr);
} }
// Tell hook to stop processing messages // Tell hook to stop processing messages