1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "nsMenuPopupFrame.h"
8 #include "LayoutConstants.h"
9 #include "XULButtonElement.h"
10 #include "XULPopupElement.h"
11 #include "mozilla/dom/XULPopupElement.h"
12 #include "nsGkAtoms.h"
13 #include "nsIContent.h"
14 #include "nsIFrameInlines.h"
16 #include "nsPresContext.h"
17 #include "mozilla/ComputedStyle.h"
18 #include "nsCSSRendering.h"
19 #include "nsNameSpaceManager.h"
20 #include "nsIFrameInlines.h"
21 #include "nsViewManager.h"
22 #include "nsWidgetsCID.h"
23 #include "nsPIDOMWindow.h"
24 #include "nsFrameManager.h"
25 #include "mozilla/dom/Document.h"
27 #include "nsIScrollableFrame.h"
28 #include "nsIPopupContainer.h"
29 #include "nsIDocShell.h"
30 #include "nsReadableUtils.h"
31 #include "nsUnicharUtils.h"
32 #include "nsLayoutUtils.h"
33 #include "nsContentUtils.h"
34 #include "nsCSSFrameConstructor.h"
35 #include "nsPIWindowRoot.h"
36 #include "nsIReflowCallback.h"
37 #include "nsIDocShellTreeOwner.h"
38 #include "nsIBaseWindow.h"
40 #include "nsIScreenManager.h"
41 #include "nsServiceManagerUtils.h"
42 #include "nsStyleConsts.h"
43 #include "nsStyleStructInlines.h"
44 #include "nsTransitionManager.h"
45 #include "nsDisplayList.h"
46 #include "nsIDOMXULSelectCntrlEl.h"
47 #include "mozilla/widget/ScreenManager.h"
48 #include "mozilla/AnimationUtils.h"
49 #include "mozilla/BasePrincipal.h"
50 #include "mozilla/EventDispatcher.h"
51 #include "mozilla/EventStateManager.h"
52 #include "mozilla/Preferences.h"
53 #include "mozilla/LookAndFeel.h"
54 #include "mozilla/MouseEvents.h"
55 #include "mozilla/PresShell.h"
56 #include "mozilla/Services.h"
57 #include "mozilla/dom/BrowserParent.h"
58 #include "mozilla/dom/Element.h"
59 #include "mozilla/dom/Event.h"
60 #include "mozilla/dom/KeyboardEvent.h"
61 #include "mozilla/dom/KeyboardEventBinding.h"
64 #include "X11UndefineNone.h"
65 #include "nsXULPopupManager.h"
67 using namespace mozilla
;
68 using mozilla::dom::Document
;
69 using mozilla::dom::Element
;
70 using mozilla::dom::Event
;
71 using mozilla::dom::XULButtonElement
;
73 int8_t nsMenuPopupFrame::sDefaultLevelIsTop
= -1;
75 TimeStamp
nsMenuPopupFrame::sLastKeyTime
;
78 # include "mozilla/WidgetUtilsGtk.h"
79 # define IS_WAYLAND_DISPLAY() mozilla::widget::GdkIsWaylandDisplay()
80 extern mozilla::LazyLogModule gWidgetPopupLog
;
81 # define LOG_WAYLAND(...) \
82 MOZ_LOG(gWidgetPopupLog, mozilla::LogLevel::Debug, (__VA_ARGS__))
84 # define IS_WAYLAND_DISPLAY() false
85 # define LOG_WAYLAND(...)
88 // NS_NewMenuPopupFrame
90 // Wrapper for creating a new menu popup container
92 nsIFrame
* NS_NewMenuPopupFrame(PresShell
* aPresShell
, ComputedStyle
* aStyle
) {
93 return new (aPresShell
)
94 nsMenuPopupFrame(aStyle
, aPresShell
->GetPresContext());
97 NS_IMPL_FRAMEARENA_HELPERS(nsMenuPopupFrame
)
99 NS_QUERYFRAME_HEAD(nsMenuPopupFrame
)
100 NS_QUERYFRAME_ENTRY(nsMenuPopupFrame
)
101 NS_QUERYFRAME_TAIL_INHERITING(nsBlockFrame
)
104 // nsMenuPopupFrame ctor
106 nsMenuPopupFrame::nsMenuPopupFrame(ComputedStyle
* aStyle
,
107 nsPresContext
* aPresContext
)
108 : nsBlockFrame(aStyle
, aPresContext
, kClassID
) {
109 // the preference name is backwards here. True means that the 'top' level is
110 // the default, and false means that the 'parent' level is the default.
111 if (sDefaultLevelIsTop
>= 0) return;
113 Preferences::GetBool("ui.panel.default_level_parent", false);
116 nsMenuPopupFrame::~nsMenuPopupFrame() = default;
118 static bool IsMouseTransparent(const ComputedStyle
& aStyle
) {
119 // If pointer-events: none; is set on the popup, then the widget should
120 // ignore mouse events, passing them through to the content behind.
121 return aStyle
.PointerEvents() == StylePointerEvents::None
;
124 static nsIWidget::InputRegion
ComputeInputRegion(const ComputedStyle
& aStyle
,
125 const nsPresContext
& aPc
) {
126 return {IsMouseTransparent(aStyle
),
127 (aStyle
.StyleUIReset()->mMozWindowInputRegionMargin
.ToCSSPixels() *
128 aPc
.CSSToDevPixelScale())
132 bool nsMenuPopupFrame::ShouldCreateWidgetUpfront() const {
133 if (mPopupType
!= PopupType::Menu
) {
134 // Any panel with a type attribute, such as the autocomplete popup, is
135 // always generated right away.
136 return mContent
->AsElement()->HasAttr(nsGkAtoms::type
);
139 // Generate the widget up-front if the parent menu is a <menulist> unless its
140 // sizetopopup is set to "none".
141 return ShouldExpandToInflowParentOrAnchor();
144 void nsMenuPopupFrame::Init(nsIContent
* aContent
, nsContainerFrame
* aParent
,
145 nsIFrame
* aPrevInFlow
) {
146 nsBlockFrame::Init(aContent
, aParent
, aPrevInFlow
);
150 // XXX Hack. The popup's view should float above all other views,
151 // so we use the nsView::SetFloating() to tell the view manager
152 // about that constraint.
153 nsView
* ourView
= GetView();
154 nsViewManager
* viewManager
= ourView
->GetViewManager();
155 viewManager
->SetViewFloating(ourView
, true);
157 const auto& el
= PopupElement();
158 mPopupType
= PopupType::Panel
;
160 mPopupType
= PopupType::Menu
;
161 } else if (el
.IsXULElement(nsGkAtoms::tooltip
)) {
162 mPopupType
= PopupType::Tooltip
;
165 if (PresContext()->IsChrome()) {
166 mInContentShell
= false;
169 // Support incontentshell=false attribute to allow popups to be displayed
170 // outside of the content shell. Chrome only.
171 if (el
.NodePrincipal()->IsSystemPrincipal()) {
172 if (el
.GetXULBoolAttr(nsGkAtoms::incontentshell
)) {
173 mInContentShell
= true;
174 } else if (el
.AttrValueIs(kNameSpaceID_None
, nsGkAtoms::incontentshell
,
175 nsGkAtoms::_false
, eCaseMatters
)) {
176 mInContentShell
= false;
180 // To improve performance, create the widget for the popup if needed. Popups
181 // such as menus will create their widgets later when the popup opens.
183 // FIXME(emilio): Doing this up-front for all menupopups causes a bunch of
184 // assertions, while it's supposed to be just an optimization.
185 if (!ourView
->HasWidget() && ShouldCreateWidgetUpfront()) {
186 CreateWidgetForView(ourView
);
189 AddStateBits(NS_FRAME_IN_POPUP
);
192 bool nsMenuPopupFrame::HasRemoteContent() const {
193 return !mInContentShell
&& mPopupType
== PopupType::Panel
&&
194 mContent
->AsElement()->AttrValueIs(kNameSpaceID_None
,
195 nsGkAtoms::remote
, nsGkAtoms::_true
,
199 bool nsMenuPopupFrame::IsNoAutoHide() const {
200 // Panels with noautohide="true" don't hide when the mouse is clicked
201 // outside of them, or when another application is made active. Non-autohide
202 // panels cannot be used in content windows.
203 return !mInContentShell
&& mPopupType
== PopupType::Panel
&&
204 mContent
->AsElement()->AttrValueIs(kNameSpaceID_None
,
205 nsGkAtoms::noautohide
,
206 nsGkAtoms::_true
, eIgnoreCase
);
209 widget::PopupLevel
nsMenuPopupFrame::GetPopupLevel(bool aIsNoAutoHide
) const {
210 // The popup level is determined as follows, in this order:
211 // 1. non-panels (menus and tooltips) are always topmost
212 // 2. any specified level attribute
213 // 3. if a titlebar attribute is set, use the 'floating' level
214 // 4. if this is a noautohide panel, use the 'parent' level
215 // 5. use the platform-specific default level
217 // If this is not a panel, this is always a top-most popup.
218 if (mPopupType
!= PopupType::Panel
) {
219 return PopupLevel::Top
;
222 // If the level attribute has been set, use that.
223 static Element::AttrValuesArray strings
[] = {
224 nsGkAtoms::top
, nsGkAtoms::parent
, nsGkAtoms::floating
, nullptr};
225 switch (mContent
->AsElement()->FindAttrValueIn(
226 kNameSpaceID_None
, nsGkAtoms::level
, strings
, eCaseMatters
)) {
228 return PopupLevel::Top
;
230 return PopupLevel::Parent
;
232 return PopupLevel::Floating
;
235 // Panels with titlebars most likely want to be floating popups.
236 if (mContent
->AsElement()->HasAttr(nsGkAtoms::titlebar
)) {
237 return PopupLevel::Floating
;
240 // If this panel is a noautohide panel, the default is the parent level.
242 return PopupLevel::Parent
;
245 // Otherwise, the result depends on the platform.
246 return sDefaultLevelIsTop
? PopupLevel::Top
: PopupLevel::Parent
;
249 void nsMenuPopupFrame::PrepareWidget(bool aRecreate
) {
250 nsView
* ourView
= GetView();
252 if (auto* widget
= GetWidget()) {
253 // Widget's WebRender resources needs to be cleared before creating new
255 widget
->ClearCachedWebrenderResources();
257 ourView
->DestroyWidget();
259 if (!ourView
->HasWidget()) {
260 CreateWidgetForView(ourView
);
262 PropagateStyleToWidget();
266 nsresult
nsMenuPopupFrame::CreateWidgetForView(nsView
* aView
) {
267 // Create a widget for ourselves.
268 widget::InitData widgetData
;
269 widgetData
.mWindowType
= widget::WindowType::Popup
;
270 widgetData
.mBorderStyle
= widget::BorderStyle::Default
;
271 widgetData
.mForMenupopupFrame
= true;
272 widgetData
.mClipSiblings
= true;
273 widgetData
.mPopupHint
= mPopupType
;
274 widgetData
.mNoAutoHide
= IsNoAutoHide();
276 if (!mInContentShell
) {
277 // A drag popup may be used for non-static translucent drag feedback
278 if (mPopupType
== PopupType::Panel
&&
279 mContent
->AsElement()->AttrValueIs(kNameSpaceID_None
, nsGkAtoms::type
,
280 nsGkAtoms::drag
, eIgnoreCase
)) {
281 widgetData
.mIsDragPopup
= true;
286 if (widgetData
.mNoAutoHide
&&
287 mContent
->AsElement()->AttrValueIs(kNameSpaceID_None
, nsGkAtoms::titlebar
,
288 nsGkAtoms::normal
, eCaseMatters
)) {
289 widgetData
.mBorderStyle
= widget::BorderStyle::Title
;
291 mContent
->AsElement()->GetAttr(nsGkAtoms::label
, title
);
292 if (mContent
->AsElement()->AttrValueIs(kNameSpaceID_None
, nsGkAtoms::close
,
293 nsGkAtoms::_true
, eCaseMatters
)) {
294 widgetData
.mBorderStyle
=
295 widgetData
.mBorderStyle
| widget::BorderStyle::Close
;
299 bool remote
= HasRemoteContent();
301 const auto mode
= nsLayoutUtils::GetFrameTransparency(this, this);
302 widgetData
.mHasRemoteContent
= remote
;
303 widgetData
.mTransparencyMode
= mode
;
304 widgetData
.mPopupLevel
= GetPopupLevel(widgetData
.mNoAutoHide
);
306 // Panels which have a parent level need a parent widget. This allows them to
307 // always appear in front of the parent window but behind other windows that
308 // should be in front of it.
309 nsCOMPtr
<nsIWidget
> parentWidget
;
310 if (widgetData
.mPopupLevel
!= PopupLevel::Top
) {
311 nsCOMPtr
<nsIDocShellTreeItem
> dsti
= PresContext()->GetDocShell();
312 if (!dsti
) return NS_ERROR_FAILURE
;
314 nsCOMPtr
<nsIDocShellTreeOwner
> treeOwner
;
315 dsti
->GetTreeOwner(getter_AddRefs(treeOwner
));
316 if (!treeOwner
) return NS_ERROR_FAILURE
;
318 nsCOMPtr
<nsIBaseWindow
> baseWindow(do_QueryInterface(treeOwner
));
319 if (baseWindow
) baseWindow
->GetMainWidget(getter_AddRefs(parentWidget
));
322 nsresult rv
= aView
->CreateWidgetForPopup(&widgetData
, parentWidget
);
327 nsIWidget
* widget
= aView
->GetWidget();
328 widget
->SetTransparencyMode(mode
);
330 PropagateStyleToWidget();
332 // most popups don't have a title so avoid setting the title if there isn't
334 if (!title
.IsEmpty()) {
335 widget
->SetTitle(title
);
341 void nsMenuPopupFrame::PropagateStyleToWidget(WidgetStyleFlags aFlags
) const {
342 if (aFlags
.isEmpty()) {
346 nsIWidget
* widget
= GetWidget();
351 if (aFlags
.contains(WidgetStyle::ColorScheme
)) {
352 widget
->SetColorScheme(Some(LookAndFeel::ColorSchemeForFrame(this)));
354 if (aFlags
.contains(WidgetStyle::InputRegion
)) {
355 widget
->SetInputRegion(ComputeInputRegion(*Style(), *PresContext()));
357 if (aFlags
.contains(WidgetStyle::Opacity
)) {
358 widget
->SetWindowOpacity(StyleUIReset()->mWindowOpacity
);
360 if (aFlags
.contains(WidgetStyle::Shadow
)) {
361 widget
->SetWindowShadowStyle(GetShadowStyle());
363 if (aFlags
.contains(WidgetStyle::Transform
)) {
364 widget
->SetWindowTransform(ComputeWidgetTransform());
368 bool nsMenuPopupFrame::IsMouseTransparent() const {
369 return ::IsMouseTransparent(*Style());
372 StyleWindowShadow
nsMenuPopupFrame::GetShadowStyle() const {
373 StyleWindowShadow shadow
= StyleUIReset()->mWindowShadow
;
374 if (shadow
!= StyleWindowShadow::Default
) {
378 switch (StyleDisplay()->EffectiveAppearance()) {
379 case StyleAppearance::Tooltip
:
380 return StyleWindowShadow::Tooltip
;
381 case StyleAppearance::Menupopup
:
382 return StyleWindowShadow::Menu
;
384 return StyleWindowShadow::Default
;
388 void nsMenuPopupFrame::SetPopupState(nsPopupState aState
) {
389 mPopupState
= aState
;
391 // Work around https://gitlab.gnome.org/GNOME/gtk/-/issues/4166
392 if (aState
== ePopupShown
&& IS_WAYLAND_DISPLAY()) {
393 if (nsIWidget
* widget
= GetWidget()) {
394 widget
->SetInputRegion(ComputeInputRegion(*Style(), *PresContext()));
399 // TODO: Convert this to MOZ_CAN_RUN_SCRIPT (bug 1415230, bug 1535398)
400 MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHODIMP
nsXULPopupShownEvent::Run() {
401 nsMenuPopupFrame
* popup
= do_QueryFrame(mPopup
->GetPrimaryFrame());
402 // Set the state to visible if the popup is still open.
403 if (popup
&& popup
->IsOpen()) {
404 popup
->SetPopupState(ePopupShown
);
407 if (!mPopup
->IsXULElement(nsGkAtoms::tooltip
)) {
408 nsCOMPtr
<nsIObserverService
> obsService
=
409 mozilla::services::GetObserverService();
411 obsService
->NotifyObservers(mPopup
, "popup-shown", nullptr);
414 WidgetMouseEvent
event(true, eXULPopupShown
, nullptr,
415 WidgetMouseEvent::eReal
);
416 return EventDispatcher::Dispatch(mPopup
, mPresContext
, &event
);
419 NS_IMETHODIMP
nsXULPopupShownEvent::HandleEvent(Event
* aEvent
) {
420 nsMenuPopupFrame
* popup
= do_QueryFrame(mPopup
->GetPrimaryFrame());
421 // Ignore events not targeted at the popup itself (ie targeted at
423 if (mPopup
!= aEvent
->GetTarget()) {
427 // ResetPopupShownDispatcher will delete the reference to this, so keep
428 // another one until Run is finished.
429 RefPtr
<nsXULPopupShownEvent
> event
= this;
430 // Only call Run if it the dispatcher was assigned. This avoids calling the
431 // Run method if the transitionend event fires multiple times.
432 if (popup
->ClearPopupShownDispatcher()) {
441 void nsXULPopupShownEvent::CancelListener() {
442 mPopup
->RemoveSystemEventListener(u
"transitionend"_ns
, this, false);
445 NS_IMPL_ISUPPORTS_INHERITED(nsXULPopupShownEvent
, Runnable
,
446 nsIDOMEventListener
);
448 void nsMenuPopupFrame::DidSetComputedStyle(ComputedStyle
* aOldStyle
) {
449 nsBlockFrame::DidSetComputedStyle(aOldStyle
);
455 WidgetStyleFlags flags
;
457 if (aOldStyle
->StyleUI()->mColorScheme
!= StyleUI()->mColorScheme
) {
458 flags
+= WidgetStyle::ColorScheme
;
461 auto& newUI
= *StyleUIReset();
462 auto& oldUI
= *aOldStyle
->StyleUIReset();
463 if (newUI
.mWindowOpacity
!= oldUI
.mWindowOpacity
) {
464 flags
+= WidgetStyle::Opacity
;
467 if (newUI
.mMozWindowTransform
!= oldUI
.mMozWindowTransform
) {
468 flags
+= WidgetStyle::Transform
;
471 if (newUI
.mWindowShadow
!= oldUI
.mWindowShadow
) {
472 flags
+= WidgetStyle::Shadow
;
475 const auto& pc
= *PresContext();
476 auto oldRegion
= ComputeInputRegion(*aOldStyle
, pc
);
477 auto newRegion
= ComputeInputRegion(*Style(), pc
);
478 if (oldRegion
.mFullyTransparent
!= newRegion
.mFullyTransparent
||
479 oldRegion
.mMargin
!= newRegion
.mMargin
) {
480 flags
+= WidgetStyle::InputRegion
;
483 PropagateStyleToWidget(flags
);
486 void nsMenuPopupFrame::TweakMinPrefISize(nscoord
& aSize
) {
487 if (!ShouldExpandToInflowParentOrAnchor()) {
490 // Make sure to accommodate for our scrollbar if needed. Do it only for
491 // menulists to match previous behavior.
493 // NOTE(emilio): This is somewhat hacky. The "right" fix (which would be
494 // using scrollbar-gutter: stable on the scroller) isn't great, because even
495 // though we want a stable gutter, we want to draw on top of the gutter when
496 // there's no scrollbar, otherwise it looks rather weird.
498 // Automatically accommodating for the scrollbar otherwise would be bug
499 // 764076, but that has its own set of problems.
500 if (nsIScrollableFrame
* sf
= GetScrollFrame()) {
501 aSize
+= sf
->GetDesiredScrollbarSizes().LeftRight();
504 nscoord menuListOrAnchorWidth
= 0;
505 if (nsIFrame
* menuList
= GetInFlowParent()) {
506 menuListOrAnchorWidth
= menuList
->GetRect().width
;
508 if (mAnchorType
== MenuPopupAnchorType_Rect
) {
509 menuListOrAnchorWidth
= std::max(menuListOrAnchorWidth
, mScreenRect
.width
);
511 // Input margin doesn't have contents, so account for it for popup sizing
513 menuListOrAnchorWidth
+=
514 2 * StyleUIReset()->mMozWindowInputRegionMargin
.ToAppUnits();
515 aSize
= std::max(aSize
, menuListOrAnchorWidth
);
518 nscoord
nsMenuPopupFrame::GetMinISize(gfxContext
* aRC
) {
520 DISPLAY_PREF_INLINE_SIZE(this, result
);
522 result
= nsBlockFrame::GetMinISize(aRC
);
523 TweakMinPrefISize(result
);
527 nscoord
nsMenuPopupFrame::GetPrefISize(gfxContext
* aRC
) {
529 DISPLAY_PREF_INLINE_SIZE(this, result
);
531 result
= nsBlockFrame::GetPrefISize(aRC
);
532 TweakMinPrefISize(result
);
536 void nsMenuPopupFrame::Reflow(nsPresContext
* aPresContext
,
537 ReflowOutput
& aDesiredSize
,
538 const ReflowInput
& aReflowInput
,
539 nsReflowStatus
& aStatus
) {
541 DO_GLOBAL_REFLOW_COUNT("nsMenuPopupFrame");
542 DISPLAY_REFLOW(aPresContext
, this, aReflowInput
, aDesiredSize
, aStatus
);
543 MOZ_ASSERT(aStatus
.IsEmpty(), "Caller should pass a fresh reflow status!");
545 const auto wm
= GetWritingMode();
546 // Default to preserving our bounds.
547 aDesiredSize
.SetSize(wm
, GetLogicalSize(wm
));
549 LayoutPopup(aPresContext
, aDesiredSize
, aReflowInput
, aStatus
);
551 aDesiredSize
.SetBlockStartAscent(aDesiredSize
.BSize(wm
));
552 aDesiredSize
.SetOverflowAreasToDesiredBounds();
553 FinishAndStoreOverflow(&aDesiredSize
, aReflowInput
.mStyleDisplay
);
556 void nsMenuPopupFrame::EnsureActiveMenuListItemIsVisible() {
557 if (!IsMenuList() || !IsOpen()) {
560 nsIFrame
* frame
= GetCurrentMenuItemFrame();
564 RefPtr
<mozilla::PresShell
> presShell
= PresShell();
565 presShell
->ScrollFrameIntoView(
566 frame
, Nothing(), ScrollAxis(), ScrollAxis(),
567 ScrollFlags::ScrollOverflowHidden
| ScrollFlags::ScrollFirstAncestorOnly
);
570 void nsMenuPopupFrame::LayoutPopup(nsPresContext
* aPresContext
,
571 ReflowOutput
& aDesiredSize
,
572 const ReflowInput
& aReflowInput
,
573 nsReflowStatus
& aStatus
) {
574 if (IsNativeMenu()) {
580 const bool isOpen
= IsOpen();
582 // If the popup is not open, only do layout while showing or if we're a
585 // This is needed because the SelectParent code wants to limit the height of
586 // the popup before opening it.
588 // TODO(emilio): We should consider adding a way to do that more reliably
589 // instead, but this preserves existing behavior.
590 const bool needsLayout
= mPopupState
== ePopupShowing
||
591 mPopupState
== ePopupPositioning
|| IsMenuList();
593 RemoveStateBits(NS_FRAME_FIRST_REFLOW
);
598 // Do a first reflow, with all our content, in order to find our preferred
599 // size. Then, we do a second reflow with the updated dimensions.
600 const bool needsPrefSize
= mPrefSize
== nsSize(-1, -1) || IsSubtreeDirty();
602 // Get the preferred, minimum and maximum size. If the menu is sized to the
603 // popup, then the popup's width is the menu's width.
604 ReflowOutput
preferredSize(aReflowInput
);
605 nsBlockFrame::Reflow(aPresContext
, preferredSize
, aReflowInput
, aStatus
);
606 mPrefSize
= preferredSize
.PhysicalSize();
609 // Get our desired position and final size, now that we have a preferred size.
610 auto constraints
= GetRects(mPrefSize
);
611 const auto finalSize
= constraints
.mUsedRect
.Size();
613 // We need to do an extra reflow if we haven't reflowed, our size doesn't
614 // match with our final intended size, or our bsize is unconstrained (in which
615 // case we need to specify the final size so that percentages work).
616 const bool needDefiniteReflow
=
617 aReflowInput
.ComputedBSize() == NS_UNCONSTRAINEDSIZE
|| !needsPrefSize
||
618 finalSize
!= mPrefSize
;
620 if (needDefiniteReflow
) {
621 ReflowInput
constrainedReflowInput(aReflowInput
);
622 const auto& bp
= aReflowInput
.ComputedPhysicalBorderPadding();
623 // TODO: writing-mode handling not terribly correct, but it doesn't matter.
624 const nsSize
finalContentSize(finalSize
.width
- bp
.LeftRight(),
625 finalSize
.height
- bp
.TopBottom());
626 constrainedReflowInput
.SetComputedISize(finalContentSize
.width
);
627 constrainedReflowInput
.SetComputedBSize(finalContentSize
.height
);
628 constrainedReflowInput
.SetIResize(finalSize
.width
!= mPrefSize
.width
);
629 constrainedReflowInput
.SetBResize([&] {
630 if (finalSize
.height
!= mPrefSize
.height
) {
634 aReflowInput
.ComputedBSize() == NS_UNCONSTRAINEDSIZE
&&
635 aReflowInput
.ComputedMaxBSize() == finalContentSize
.height
) {
636 // If we have measured, and maybe clamped our children via max-height,
637 // they might need to get percentages in the block axis re-resolved.
644 nsBlockFrame::Reflow(aPresContext
, aDesiredSize
, constrainedReflowInput
,
648 // Set our size, since nsAbsoluteContainingBlock won't.
649 SetRect(constraints
.mUsedRect
);
651 nsView
* view
= GetView();
653 nsViewManager
* viewManager
= view
->GetViewManager();
654 viewManager
->ResizeView(view
,
655 nsRect(nsPoint(), constraints
.mUsedRect
.Size()));
656 if (mPopupState
== ePopupOpening
) {
657 mPopupState
= ePopupVisible
;
660 viewManager
->SetViewVisibility(view
, ViewVisibility::Show
);
661 SyncFrameViewProperties(view
);
664 // Perform our move now. That will position the view and so on.
665 PerformMove(constraints
);
667 // finally, if the popup just opened, send a popupshown event
668 bool openChanged
= mIsOpenChanged
;
670 mIsOpenChanged
= false;
672 // Make sure the current selection in a menulist is visible.
673 EnsureActiveMenuListItemIsVisible();
675 // If the animate attribute is set to open, check for a transition and wait
676 // for it to finish before firing the popupshown event.
677 if (LookAndFeel::GetInt(LookAndFeel::IntID::PanelAnimations
) &&
678 mContent
->AsElement()->AttrValueIs(kNameSpaceID_None
,
679 nsGkAtoms::animate
, nsGkAtoms::open
,
681 AnimationUtils::HasCurrentTransitions(mContent
->AsElement(),
682 PseudoStyleType::NotPseudo
)) {
683 mPopupShownDispatcher
= new nsXULPopupShownEvent(mContent
, aPresContext
);
684 mContent
->AddSystemEventListener(u
"transitionend"_ns
,
685 mPopupShownDispatcher
, false, false);
689 // If there are no transitions, fire the popupshown event right away.
690 nsCOMPtr
<nsIRunnable
> event
=
691 new nsXULPopupShownEvent(GetContent(), aPresContext
);
692 mContent
->OwnerDoc()->Dispatch(event
.forget());
696 bool nsMenuPopupFrame::IsMenuList() const {
697 return PopupElement().IsInMenuList();
700 bool nsMenuPopupFrame::ShouldExpandToInflowParentOrAnchor() const {
701 return IsMenuList() && !mContent
->GetParent()->AsElement()->AttrValueIs(
702 kNameSpaceID_None
, nsGkAtoms::sizetopopup
,
703 nsGkAtoms::none
, eCaseMatters
);
706 nsIContent
* nsMenuPopupFrame::GetTriggerContent(
707 nsMenuPopupFrame
* aMenuPopupFrame
) {
708 while (aMenuPopupFrame
) {
709 if (aMenuPopupFrame
->mTriggerContent
) {
710 return aMenuPopupFrame
->mTriggerContent
;
713 auto* button
= XULButtonElement::FromNodeOrNull(
714 aMenuPopupFrame
->GetContent()->GetParent());
715 if (!button
|| !button
->IsMenu()) {
719 auto* popup
= button
->GetContainingPopupElement();
724 // check up the menu hierarchy until a popup with a trigger node is found
725 aMenuPopupFrame
= do_QueryFrame(popup
->GetPrimaryFrame());
731 void nsMenuPopupFrame::InitPositionFromAnchorAlign(const nsAString
& aAnchor
,
732 const nsAString
& aAlign
) {
733 mTriggerContent
= nullptr;
735 if (aAnchor
.EqualsLiteral("topleft"))
736 mPopupAnchor
= POPUPALIGNMENT_TOPLEFT
;
737 else if (aAnchor
.EqualsLiteral("topright"))
738 mPopupAnchor
= POPUPALIGNMENT_TOPRIGHT
;
739 else if (aAnchor
.EqualsLiteral("bottomleft"))
740 mPopupAnchor
= POPUPALIGNMENT_BOTTOMLEFT
;
741 else if (aAnchor
.EqualsLiteral("bottomright"))
742 mPopupAnchor
= POPUPALIGNMENT_BOTTOMRIGHT
;
743 else if (aAnchor
.EqualsLiteral("leftcenter"))
744 mPopupAnchor
= POPUPALIGNMENT_LEFTCENTER
;
745 else if (aAnchor
.EqualsLiteral("rightcenter"))
746 mPopupAnchor
= POPUPALIGNMENT_RIGHTCENTER
;
747 else if (aAnchor
.EqualsLiteral("topcenter"))
748 mPopupAnchor
= POPUPALIGNMENT_TOPCENTER
;
749 else if (aAnchor
.EqualsLiteral("bottomcenter"))
750 mPopupAnchor
= POPUPALIGNMENT_BOTTOMCENTER
;
752 mPopupAnchor
= POPUPALIGNMENT_NONE
;
754 if (aAlign
.EqualsLiteral("topleft"))
755 mPopupAlignment
= POPUPALIGNMENT_TOPLEFT
;
756 else if (aAlign
.EqualsLiteral("topright"))
757 mPopupAlignment
= POPUPALIGNMENT_TOPRIGHT
;
758 else if (aAlign
.EqualsLiteral("bottomleft"))
759 mPopupAlignment
= POPUPALIGNMENT_BOTTOMLEFT
;
760 else if (aAlign
.EqualsLiteral("bottomright"))
761 mPopupAlignment
= POPUPALIGNMENT_BOTTOMRIGHT
;
762 else if (aAlign
.EqualsLiteral("leftcenter"))
763 mPopupAlignment
= POPUPALIGNMENT_LEFTCENTER
;
764 else if (aAlign
.EqualsLiteral("rightcenter"))
765 mPopupAlignment
= POPUPALIGNMENT_RIGHTCENTER
;
766 else if (aAlign
.EqualsLiteral("topcenter"))
767 mPopupAlignment
= POPUPALIGNMENT_TOPCENTER
;
768 else if (aAlign
.EqualsLiteral("bottomcenter"))
769 mPopupAlignment
= POPUPALIGNMENT_BOTTOMCENTER
;
771 mPopupAlignment
= POPUPALIGNMENT_NONE
;
773 mPosition
= POPUPPOSITION_UNKNOWN
;
776 void nsMenuPopupFrame::InitializePopup(nsIContent
* aAnchorContent
,
777 nsIContent
* aTriggerContent
,
778 const nsAString
& aPosition
,
779 int32_t aXPos
, int32_t aYPos
,
780 MenuPopupAnchorType aAnchorType
,
781 bool aAttributesOverride
) {
782 auto* widget
= GetWidget();
783 bool recreateWidget
= widget
&& widget
->NeedsRecreateToReshow();
784 PrepareWidget(recreateWidget
);
786 mPopupState
= ePopupShowing
;
787 mAnchorContent
= aAnchorContent
;
788 mTriggerContent
= aTriggerContent
;
791 mIsNativeMenu
= false;
792 mIsTopLevelContextMenu
= false;
795 mConstrainedByLayout
= false;
796 mAlignmentOffset
= 0;
797 mPositionedOffset
= 0;
798 mPositionedByMoveToRect
= false;
800 mAnchorType
= aAnchorType
;
802 // if aAttributesOverride is true, then the popupanchor, popupalign and
803 // position attributes on the <menupopup> override those values passed in.
804 // If false, those attributes are only used if the values passed in are empty
805 if (aAnchorContent
|| aAnchorType
== MenuPopupAnchorType_Rect
) {
806 nsAutoString anchor
, align
, position
, flip
;
807 mContent
->AsElement()->GetAttr(nsGkAtoms::popupanchor
, anchor
);
808 mContent
->AsElement()->GetAttr(nsGkAtoms::popupalign
, align
);
809 mContent
->AsElement()->GetAttr(nsGkAtoms::position
, position
);
810 mContent
->AsElement()->GetAttr(nsGkAtoms::flip
, flip
);
812 if (aAttributesOverride
) {
813 // if the attributes are set, clear the offset position. Otherwise,
814 // the offset is used to adjust the position from the anchor point
815 if (anchor
.IsEmpty() && align
.IsEmpty() && position
.IsEmpty())
816 position
.Assign(aPosition
);
819 } else if (!aPosition
.IsEmpty()) {
820 position
.Assign(aPosition
);
823 if (flip
.EqualsLiteral("none")) {
824 mFlip
= FlipType_None
;
825 } else if (flip
.EqualsLiteral("both")) {
826 mFlip
= FlipType_Both
;
827 } else if (flip
.EqualsLiteral("slide")) {
828 mFlip
= FlipType_Slide
;
831 position
.CompressWhitespace();
832 int32_t spaceIdx
= position
.FindChar(' ');
833 // if there is a space in the position, assume it is the anchor and
834 // alignment as two separate tokens.
836 InitPositionFromAnchorAlign(Substring(position
, 0, spaceIdx
),
837 Substring(position
, spaceIdx
+ 1));
838 } else if (position
.EqualsLiteral("before_start")) {
839 mPopupAnchor
= POPUPALIGNMENT_TOPLEFT
;
840 mPopupAlignment
= POPUPALIGNMENT_BOTTOMLEFT
;
841 mPosition
= POPUPPOSITION_BEFORESTART
;
842 } else if (position
.EqualsLiteral("before_end")) {
843 mPopupAnchor
= POPUPALIGNMENT_TOPRIGHT
;
844 mPopupAlignment
= POPUPALIGNMENT_BOTTOMRIGHT
;
845 mPosition
= POPUPPOSITION_BEFOREEND
;
846 } else if (position
.EqualsLiteral("after_start")) {
847 mPopupAnchor
= POPUPALIGNMENT_BOTTOMLEFT
;
848 mPopupAlignment
= POPUPALIGNMENT_TOPLEFT
;
849 mPosition
= POPUPPOSITION_AFTERSTART
;
850 } else if (position
.EqualsLiteral("after_end")) {
851 mPopupAnchor
= POPUPALIGNMENT_BOTTOMRIGHT
;
852 mPopupAlignment
= POPUPALIGNMENT_TOPRIGHT
;
853 mPosition
= POPUPPOSITION_AFTEREND
;
854 } else if (position
.EqualsLiteral("start_before")) {
855 mPopupAnchor
= POPUPALIGNMENT_TOPLEFT
;
856 mPopupAlignment
= POPUPALIGNMENT_TOPRIGHT
;
857 mPosition
= POPUPPOSITION_STARTBEFORE
;
858 } else if (position
.EqualsLiteral("start_after")) {
859 mPopupAnchor
= POPUPALIGNMENT_BOTTOMLEFT
;
860 mPopupAlignment
= POPUPALIGNMENT_BOTTOMRIGHT
;
861 mPosition
= POPUPPOSITION_STARTAFTER
;
862 } else if (position
.EqualsLiteral("end_before")) {
863 mPopupAnchor
= POPUPALIGNMENT_TOPRIGHT
;
864 mPopupAlignment
= POPUPALIGNMENT_TOPLEFT
;
865 mPosition
= POPUPPOSITION_ENDBEFORE
;
866 } else if (position
.EqualsLiteral("end_after")) {
867 mPopupAnchor
= POPUPALIGNMENT_BOTTOMRIGHT
;
868 mPopupAlignment
= POPUPALIGNMENT_BOTTOMLEFT
;
869 mPosition
= POPUPPOSITION_ENDAFTER
;
870 } else if (position
.EqualsLiteral("overlap")) {
871 mPopupAnchor
= POPUPALIGNMENT_TOPLEFT
;
872 mPopupAlignment
= POPUPALIGNMENT_TOPLEFT
;
873 mPosition
= POPUPPOSITION_OVERLAP
;
874 } else if (position
.EqualsLiteral("after_pointer")) {
875 mPopupAnchor
= POPUPALIGNMENT_TOPLEFT
;
876 mPopupAlignment
= POPUPALIGNMENT_TOPLEFT
;
877 mPosition
= POPUPPOSITION_AFTERPOINTER
;
878 // XXXndeakin this is supposed to anchor vertically after, but with the
879 // horizontal position as the mouse pointer.
881 } else if (position
.EqualsLiteral("selection")) {
882 mPopupAnchor
= POPUPALIGNMENT_BOTTOMLEFT
;
883 mPopupAlignment
= POPUPALIGNMENT_TOPLEFT
;
884 mPosition
= POPUPPOSITION_SELECTION
;
886 InitPositionFromAnchorAlign(anchor
, align
);
889 // When converted back to CSSIntRect it is (-1, -1, 0, 0) - as expected in
890 // nsXULPopupManager::Rollup
891 mScreenRect
= nsRect(-AppUnitsPerCSSPixel(), -AppUnitsPerCSSPixel(), 0, 0);
893 if (aAttributesOverride
) {
894 // Use |left| and |top| dimension attributes to position the popup if
895 // present, as they may have been persisted.
896 nsAutoString left
, top
;
897 mContent
->AsElement()->GetAttr(nsGkAtoms::left
, left
);
898 mContent
->AsElement()->GetAttr(nsGkAtoms::top
, top
);
901 if (!left
.IsEmpty()) {
902 int32_t x
= left
.ToInteger(&err
);
903 if (NS_SUCCEEDED(err
)) {
904 mScreenRect
.x
= CSSPixel::ToAppUnits(x
);
907 if (!top
.IsEmpty()) {
908 int32_t y
= top
.ToInteger(&err
);
909 if (NS_SUCCEEDED(err
)) {
910 mScreenRect
.y
= CSSPixel::ToAppUnits(y
);
916 void nsMenuPopupFrame::InitializePopupAtScreen(nsIContent
* aTriggerContent
,
917 int32_t aXPos
, int32_t aYPos
,
918 bool aIsContextMenu
) {
919 auto* widget
= GetWidget();
920 bool recreateWidget
= widget
&& widget
->NeedsRecreateToReshow();
921 PrepareWidget(recreateWidget
);
923 mPopupState
= ePopupShowing
;
924 mAnchorContent
= nullptr;
925 mTriggerContent
= aTriggerContent
;
927 nsRect(CSSPixel::ToAppUnits(aXPos
), CSSPixel::ToAppUnits(aYPos
), 0, 0);
930 mFlip
= FlipType_Default
;
931 mPopupAnchor
= POPUPALIGNMENT_NONE
;
932 mPopupAlignment
= POPUPALIGNMENT_NONE
;
933 mPosition
= POPUPPOSITION_UNKNOWN
;
934 mIsContextMenu
= aIsContextMenu
;
935 mIsTopLevelContextMenu
= aIsContextMenu
;
936 mIsNativeMenu
= false;
937 mAnchorType
= MenuPopupAnchorType_Point
;
938 mPositionedOffset
= 0;
939 mPositionedByMoveToRect
= false;
942 void nsMenuPopupFrame::InitializePopupAsNativeContextMenu(
943 nsIContent
* aTriggerContent
, int32_t aXPos
, int32_t aYPos
) {
944 mTriggerContent
= aTriggerContent
;
945 mPopupState
= ePopupShowing
;
946 mAnchorContent
= nullptr;
948 nsRect(CSSPixel::ToAppUnits(aXPos
), CSSPixel::ToAppUnits(aYPos
), 0, 0);
951 mFlip
= FlipType_Default
;
952 mPopupAnchor
= POPUPALIGNMENT_NONE
;
953 mPopupAlignment
= POPUPALIGNMENT_NONE
;
954 mPosition
= POPUPPOSITION_UNKNOWN
;
955 mIsContextMenu
= true;
956 mIsTopLevelContextMenu
= true;
957 mIsNativeMenu
= true;
958 mAnchorType
= MenuPopupAnchorType_Point
;
959 mPositionedOffset
= 0;
960 mPositionedByMoveToRect
= false;
963 void nsMenuPopupFrame::InitializePopupAtRect(nsIContent
* aTriggerContent
,
964 const nsAString
& aPosition
,
965 const nsIntRect
& aRect
,
966 bool aAttributesOverride
) {
967 InitializePopup(nullptr, aTriggerContent
, aPosition
, 0, 0,
968 MenuPopupAnchorType_Rect
, aAttributesOverride
);
969 mScreenRect
= ToAppUnits(aRect
, AppUnitsPerCSSPixel());
972 void nsMenuPopupFrame::ShowPopup(bool aIsContextMenu
) {
973 mIsContextMenu
= aIsContextMenu
;
975 InvalidateFrameSubtree();
977 if (mPopupState
== ePopupShowing
|| mPopupState
== ePopupPositioning
) {
978 mPopupState
= ePopupOpening
;
979 mIsOpenChanged
= true;
981 // Clear mouse capture when a popup is opened.
982 if (mPopupType
== PopupType::Menu
) {
983 if (auto* activeESM
= EventStateManager::GetActiveEventStateManager()) {
984 EventStateManager::ClearGlobalActiveContent(activeESM
);
987 PresShell::ReleaseCapturingContent();
990 if (RefPtr menu
= PopupElement().GetContainingMenu()) {
994 // We skip laying out children if we're closed, so make sure that we do a
995 // full dirty reflow when opening to pick up any potential change.
996 PresShell()->FrameNeedsReflow(
997 this, IntrinsicDirty::FrameAncestorsAndDescendants
, NS_FRAME_IS_DIRTY
);
999 if (mPopupType
== PopupType::Menu
) {
1000 nsCOMPtr
<nsISound
> sound(do_GetService("@mozilla.org/sound;1"));
1001 if (sound
) sound
->PlayEventSound(nsISound::EVENT_MENU_POPUP
);
1006 void nsMenuPopupFrame::ClearTriggerContentIncludingDocument() {
1007 // clear the trigger content if the popup is being closed. But don't clear
1008 // it if the popup is just being made invisible as a popuphiding or command
1009 if (mTriggerContent
) {
1010 // if the popup had a trigger node set, clear the global window popup node
1012 Document
* doc
= mContent
->GetUncomposedDoc();
1014 if (nsPIDOMWindowOuter
* win
= doc
->GetWindow()) {
1015 nsCOMPtr
<nsPIWindowRoot
> root
= win
->GetTopWindowRoot();
1017 root
->SetPopupNode(nullptr);
1022 mTriggerContent
= nullptr;
1025 void nsMenuPopupFrame::HidePopup(bool aDeselectMenu
, nsPopupState aNewState
,
1026 bool aFromFrameDestruction
) {
1027 NS_ASSERTION(aNewState
== ePopupClosed
|| aNewState
== ePopupInvisible
,
1028 "popup being set to unexpected state");
1030 ClearPopupShownDispatcher();
1032 // don't hide the popup when it isn't open
1033 if (mPopupState
== ePopupClosed
|| mPopupState
== ePopupShowing
||
1034 mPopupState
== ePopupPositioning
) {
1038 if (aNewState
== ePopupClosed
) {
1039 // clear the trigger content if the popup is being closed. But don't clear
1040 // it if the popup is just being made invisible as a popuphiding or command
1041 // event may want to retrieve it.
1042 ClearTriggerContentIncludingDocument();
1043 mAnchorContent
= nullptr;
1046 // when invisible and about to be closed, HidePopup has already been called,
1047 // so just set the new state to closed and return
1048 if (mPopupState
== ePopupInvisible
) {
1049 if (aNewState
== ePopupClosed
) {
1050 mPopupState
= ePopupClosed
;
1055 mPopupState
= aNewState
;
1057 mIncrementalString
.Truncate();
1059 mIsOpenChanged
= false;
1060 mHFlip
= mVFlip
= false;
1061 mConstrainedByLayout
= false;
1063 if (auto* widget
= GetWidget()) {
1064 // Ideally we should call ClearCachedWebrenderResources but there are
1065 // intermittent failures (see bug 1748788), so we currently call
1066 // ClearWebrenderAnimationResources instead.
1067 widget
->ClearWebrenderAnimationResources();
1070 nsView
* view
= GetView();
1071 nsViewManager
* viewManager
= view
->GetViewManager();
1072 viewManager
->SetViewVisibility(view
, ViewVisibility::Hide
);
1074 RefPtr popup
= &PopupElement();
1075 // XXX, bug 137033, In Windows, if mouse is outside the window when the
1076 // menupopup closes, no mouse_enter/mouse_exit event will be fired to clear
1077 // current hover state, we should clear it manually. This code may not the
1078 // best solution, but we can leave it here until we find the better approach.
1079 if (!aFromFrameDestruction
&&
1080 popup
->State().HasState(dom::ElementState::HOVER
)) {
1081 EventStateManager
* esm
= PresContext()->EventStateManager();
1082 esm
->SetContentState(nullptr, dom::ElementState::HOVER
);
1084 popup
->PopupClosed(aDeselectMenu
);
1087 nsPoint
nsMenuPopupFrame::AdjustPositionForAnchorAlign(
1088 nsRect
& anchorRect
, const nsSize
& aPrefSize
, FlipStyle
& aHFlip
,
1089 FlipStyle
& aVFlip
) const {
1090 // flip the anchor and alignment for right-to-left
1091 int8_t popupAnchor(mPopupAnchor
);
1092 int8_t popupAlign(mPopupAlignment
);
1093 if (IsDirectionRTL()) {
1094 // no need to flip the centered anchor types vertically
1095 if (popupAnchor
<= POPUPALIGNMENT_LEFTCENTER
) {
1096 popupAnchor
= -popupAnchor
;
1098 popupAlign
= -popupAlign
;
1101 nsRect
originalAnchorRect(anchorRect
);
1103 // first, determine at which corner of the anchor the popup should appear
1105 switch (popupAnchor
) {
1106 case POPUPALIGNMENT_LEFTCENTER
:
1107 pnt
= nsPoint(anchorRect
.x
, anchorRect
.y
+ anchorRect
.height
/ 2);
1108 anchorRect
.y
= pnt
.y
;
1109 anchorRect
.height
= 0;
1111 case POPUPALIGNMENT_RIGHTCENTER
:
1112 pnt
= nsPoint(anchorRect
.XMost(), anchorRect
.y
+ anchorRect
.height
/ 2);
1113 anchorRect
.y
= pnt
.y
;
1114 anchorRect
.height
= 0;
1116 case POPUPALIGNMENT_TOPCENTER
:
1117 pnt
= nsPoint(anchorRect
.x
+ anchorRect
.width
/ 2, anchorRect
.y
);
1118 anchorRect
.x
= pnt
.x
;
1119 anchorRect
.width
= 0;
1121 case POPUPALIGNMENT_BOTTOMCENTER
:
1122 pnt
= nsPoint(anchorRect
.x
+ anchorRect
.width
/ 2, anchorRect
.YMost());
1123 anchorRect
.x
= pnt
.x
;
1124 anchorRect
.width
= 0;
1126 case POPUPALIGNMENT_TOPRIGHT
:
1127 pnt
= anchorRect
.TopRight();
1129 case POPUPALIGNMENT_BOTTOMLEFT
:
1130 pnt
= anchorRect
.BottomLeft();
1132 case POPUPALIGNMENT_BOTTOMRIGHT
:
1133 pnt
= anchorRect
.BottomRight();
1135 case POPUPALIGNMENT_TOPLEFT
:
1137 pnt
= anchorRect
.TopLeft();
1141 // If the alignment is on the right edge of the popup, move the popup left
1142 // by the width. Similarly, if the alignment is on the bottom edge of the
1143 // popup, move the popup up by the height. In addition, account for the
1144 // margins of the popup on the edge on which it is aligned.
1145 nsMargin margin
= GetMargin();
1146 switch (popupAlign
) {
1147 case POPUPALIGNMENT_LEFTCENTER
:
1148 pnt
.MoveBy(margin
.left
, -aPrefSize
.height
/ 2);
1150 case POPUPALIGNMENT_RIGHTCENTER
:
1151 pnt
.MoveBy(-aPrefSize
.width
- margin
.right
, -aPrefSize
.height
/ 2);
1153 case POPUPALIGNMENT_TOPCENTER
:
1154 pnt
.MoveBy(-aPrefSize
.width
/ 2, margin
.top
);
1156 case POPUPALIGNMENT_BOTTOMCENTER
:
1157 pnt
.MoveBy(-aPrefSize
.width
/ 2, -aPrefSize
.height
- margin
.bottom
);
1159 case POPUPALIGNMENT_TOPRIGHT
:
1160 pnt
.MoveBy(-aPrefSize
.width
- margin
.right
, margin
.top
);
1162 case POPUPALIGNMENT_BOTTOMLEFT
:
1163 pnt
.MoveBy(margin
.left
, -aPrefSize
.height
- margin
.bottom
);
1165 case POPUPALIGNMENT_BOTTOMRIGHT
:
1166 pnt
.MoveBy(-aPrefSize
.width
- margin
.right
,
1167 -aPrefSize
.height
- margin
.bottom
);
1169 case POPUPALIGNMENT_TOPLEFT
:
1171 pnt
.MoveBy(margin
.left
, margin
.top
);
1175 // If we aligning to the selected item in the popup, adjust the vertical
1176 // position by the height of the menulist label and the selected item's
1178 if (mPosition
== POPUPPOSITION_SELECTION
) {
1179 MOZ_ASSERT(popupAnchor
== POPUPALIGNMENT_BOTTOMLEFT
||
1180 popupAnchor
== POPUPALIGNMENT_BOTTOMRIGHT
);
1181 MOZ_ASSERT(popupAlign
== POPUPALIGNMENT_TOPLEFT
||
1182 popupAlign
== POPUPALIGNMENT_TOPRIGHT
);
1184 // Only adjust the popup if it just opened, otherwise the popup will move
1185 // around if its gets resized or the selection changed. Cache the value in
1186 // mPositionedOffset and use that instead for any future calculations.
1187 if (mIsOpenChanged
) {
1188 if (nsIFrame
* selectedItemFrame
= GetSelectedItemForAlignment()) {
1189 const nscoord itemHeight
= selectedItemFrame
->GetRect().height
;
1190 const nscoord itemOffset
=
1191 selectedItemFrame
->GetOffsetToIgnoringScrolling(this).y
;
1192 // We want to line-up the anchor rect with the selected item, but if the
1193 // selected item is outside of our bounds, we don't want to shift the
1194 // popup up in a way that our box would no longer intersect with the
1196 nscoord maxOffset
= aPrefSize
.height
- itemHeight
;
1197 if (const nsIScrollableFrame
* sf
= GetScrollFrame()) {
1198 // HACK: We ideally would want to use the offset from the bottom
1199 // bottom of our scroll-frame to the bottom of our frame (so as to
1200 // ensure that the bottom of the scrollport is inside the anchor
1203 // But at this point of the code, the scroll frame may not be laid out
1204 // with a definite size (might be overflowing us).
1206 // So, we assume the offset from the bottom is symmetric to the offset
1207 // from the top. This holds for all the popups where this matters
1208 // (menulists on macOS, effectively), and seems better than somehow
1209 // moving the popup after the fact as we used to do.
1210 const nsIFrame
* f
= do_QueryFrame(sf
);
1211 maxOffset
-= f
->GetOffsetTo(this).y
;
1214 originalAnchorRect
.height
+ std::min(itemOffset
, maxOffset
);
1218 pnt
.y
-= mPositionedOffset
;
1221 // Flipping horizontally is allowed as long as the popup is above or below
1222 // the anchor. This will happen if both the anchor and alignment are top or
1223 // both are bottom, but different values. Similarly, flipping vertically is
1224 // allowed if the popup is to the left or right of the anchor. In this case,
1225 // the values of the constants are such that both must be positive or both
1226 // must be negative. A special case, used for overlap, allows flipping
1227 // vertically as well.
1228 // If we are flipping in both directions, we want to set a flip style both
1229 // horizontally and vertically. However, we want to flip on the inside edge
1230 // of the anchor. Consider the example of a typical dropdown menu.
1231 // Vertically, we flip the popup on the outside edges of the anchor menu,
1232 // however horizontally, we want to to use the inside edges so the popup
1233 // still appears underneath the anchor menu instead of floating off the
1234 // side of the menu.
1235 switch (popupAnchor
) {
1236 case POPUPALIGNMENT_LEFTCENTER
:
1237 case POPUPALIGNMENT_RIGHTCENTER
:
1238 aHFlip
= FlipStyle_Outside
;
1239 aVFlip
= FlipStyle_Inside
;
1241 case POPUPALIGNMENT_TOPCENTER
:
1242 case POPUPALIGNMENT_BOTTOMCENTER
:
1243 aHFlip
= FlipStyle_Inside
;
1244 aVFlip
= FlipStyle_Outside
;
1247 FlipStyle anchorEdge
=
1248 mFlip
== FlipType_Both
? FlipStyle_Inside
: FlipStyle_None
;
1249 aHFlip
= (popupAnchor
== -popupAlign
) ? FlipStyle_Outside
: anchorEdge
;
1250 if (((popupAnchor
> 0) == (popupAlign
> 0)) ||
1251 (popupAnchor
== POPUPALIGNMENT_TOPLEFT
&&
1252 popupAlign
== POPUPALIGNMENT_TOPLEFT
))
1253 aVFlip
= FlipStyle_Outside
;
1255 aVFlip
= anchorEdge
;
1263 nsIFrame
* nsMenuPopupFrame::GetSelectedItemForAlignment() const {
1264 // This method adjusts a menulist's popup such that the selected item is under
1265 // the cursor, aligned with the menulist label.
1266 nsCOMPtr
<nsIDOMXULSelectControlElement
> select
;
1267 if (mAnchorContent
) {
1268 select
= mAnchorContent
->AsElement()->AsXULSelectControl();
1272 // If there isn't an anchor, then try just getting the parent of the popup.
1273 select
= mContent
->GetParent()->AsElement()->AsXULSelectControl();
1279 nsCOMPtr
<Element
> selectedElement
;
1280 select
->GetSelectedItem(getter_AddRefs(selectedElement
));
1281 return selectedElement
? selectedElement
->GetPrimaryFrame() : nullptr;
1284 nscoord
nsMenuPopupFrame::SlideOrResize(nscoord
& aScreenPoint
, nscoord aSize
,
1285 nscoord aScreenBegin
,
1287 nscoord
* aOffset
) const {
1288 // The popup may be positioned such that either the left/top or bottom/right
1289 // is outside the screen - but never both.
1291 std::max(aScreenBegin
, std::min(aScreenEnd
- aSize
, aScreenPoint
));
1292 *aOffset
= newPos
- aScreenPoint
;
1293 aScreenPoint
= newPos
;
1294 return std::min(aSize
, aScreenEnd
- aScreenPoint
);
1297 nscoord
nsMenuPopupFrame::FlipOrResize(nscoord
& aScreenPoint
, nscoord aSize
,
1298 nscoord aScreenBegin
, nscoord aScreenEnd
,
1299 nscoord aAnchorBegin
, nscoord aAnchorEnd
,
1300 nscoord aMarginBegin
, nscoord aMarginEnd
,
1301 FlipStyle aFlip
, bool aEndAligned
,
1302 bool* aFlipSide
) const {
1303 // The flip side argument will be set to true if there wasn't room and we
1304 // flipped to the opposite side.
1307 // all of the coordinates used here are in app units relative to the screen
1308 nscoord popupSize
= aSize
;
1309 if (aScreenPoint
< aScreenBegin
) {
1310 // at its current position, the popup would extend past the left or top
1311 // edge of the screen, so it will have to be moved or resized.
1313 // for inside flips, we flip on the opposite side of the anchor
1314 nscoord startpos
= aFlip
== FlipStyle_Outside
? aAnchorBegin
: aAnchorEnd
;
1315 nscoord endpos
= aFlip
== FlipStyle_Outside
? aAnchorEnd
: aAnchorBegin
;
1317 // check whether there is more room to the left and right (or top and
1318 // bottom) of the anchor and put the popup on the side with more room.
1319 if (startpos
- aScreenBegin
>= aScreenEnd
- endpos
) {
1320 aScreenPoint
= aScreenBegin
;
1321 popupSize
= startpos
- aScreenPoint
- aMarginEnd
;
1322 *aFlipSide
= !aEndAligned
;
1324 // If the newly calculated position is different than the existing
1325 // position, flip such that the popup is to the right or bottom of the
1326 // anchor point instead . However, when flipping use the same margin
1328 nscoord newScreenPoint
= endpos
+ aMarginEnd
;
1329 if (newScreenPoint
!= aScreenPoint
) {
1330 *aFlipSide
= aEndAligned
;
1331 aScreenPoint
= newScreenPoint
;
1332 // check if the new position is still off the right or bottom edge of
1333 // the screen. If so, resize the popup.
1334 if (aScreenPoint
+ aSize
> aScreenEnd
) {
1335 popupSize
= aScreenEnd
- aScreenPoint
;
1340 aScreenPoint
= aScreenBegin
;
1342 } else if (aScreenPoint
+ aSize
> aScreenEnd
) {
1343 // at its current position, the popup would extend past the right or
1344 // bottom edge of the screen, so it will have to be moved or resized.
1346 // for inside flips, we flip on the opposite side of the anchor
1347 nscoord startpos
= aFlip
== FlipStyle_Outside
? aAnchorBegin
: aAnchorEnd
;
1348 nscoord endpos
= aFlip
== FlipStyle_Outside
? aAnchorEnd
: aAnchorBegin
;
1350 // check whether there is more room to the left and right (or top and
1351 // bottom) of the anchor and put the popup on the side with more room.
1352 if (aScreenEnd
- endpos
>= startpos
- aScreenBegin
) {
1353 *aFlipSide
= aEndAligned
;
1354 if (mIsContextMenu
) {
1355 aScreenPoint
= aScreenEnd
- aSize
;
1357 aScreenPoint
= endpos
+ aMarginBegin
;
1358 popupSize
= aScreenEnd
- aScreenPoint
;
1361 // if the newly calculated position is different than the existing
1362 // position, we flip such that the popup is to the left or top of the
1363 // anchor point instead.
1364 nscoord newScreenPoint
= startpos
- aSize
- aMarginBegin
;
1365 if (newScreenPoint
!= aScreenPoint
) {
1366 *aFlipSide
= !aEndAligned
;
1367 aScreenPoint
= newScreenPoint
;
1369 // check if the new position is still off the left or top edge of the
1370 // screen. If so, resize the popup.
1371 if (aScreenPoint
< aScreenBegin
) {
1372 aScreenPoint
= aScreenBegin
;
1373 if (!mIsContextMenu
) {
1374 popupSize
= startpos
- aScreenPoint
- aMarginBegin
;
1380 aScreenPoint
= aScreenEnd
- aSize
;
1384 // Make sure that the point is within the screen boundaries and that the
1385 // size isn't off the edge of the screen. This can happen when a large
1386 // positive or negative margin is used.
1387 if (aScreenPoint
< aScreenBegin
) {
1388 aScreenPoint
= aScreenBegin
;
1390 if (aScreenPoint
> aScreenEnd
) {
1391 aScreenPoint
= aScreenEnd
- aSize
;
1394 // If popupSize ended up being negative, or the original size was actually
1395 // smaller than the calculated popup size, just use the original size instead.
1396 if (popupSize
<= 0 || aSize
< popupSize
) {
1400 return std::min(popupSize
, aScreenEnd
- aScreenPoint
);
1403 nsRect
nsMenuPopupFrame::ComputeAnchorRect(nsPresContext
* aRootPresContext
,
1404 nsIFrame
* aAnchorFrame
) const {
1405 // Get the root frame for a reference
1406 nsIFrame
* rootFrame
= aRootPresContext
->PresShell()->GetRootFrame();
1408 // The dimensions of the anchor
1409 nsRect anchorRect
= aAnchorFrame
->GetRectRelativeToSelf();
1411 // Relative to the root
1412 anchorRect
= nsLayoutUtils::TransformFrameRectToAncestor(
1413 aAnchorFrame
, anchorRect
, rootFrame
);
1414 // Relative to the screen
1415 anchorRect
.MoveBy(rootFrame
->GetScreenRectInAppUnits().TopLeft());
1417 // In its own app units
1418 return anchorRect
.ScaleToOtherAppUnitsRoundOut(
1419 aRootPresContext
->AppUnitsPerDevPixel(),
1420 PresContext()->AppUnitsPerDevPixel());
1423 static nsIFrame
* MaybeDelegatedAnchorFrame(nsIFrame
* aFrame
) {
1427 if (auto* element
= Element::FromNodeOrNull(aFrame
->GetContent())) {
1428 if (element
->HasAttr(nsGkAtoms::delegatesanchor
)) {
1429 for (nsIFrame
* f
: aFrame
->PrincipalChildList()) {
1430 if (!f
->IsPlaceholderFrame()) {
1439 auto nsMenuPopupFrame::GetRects(const nsSize
& aPrefSize
) const -> Rects
{
1440 if (NS_WARN_IF(aPrefSize
== nsSize(-1, -1))) {
1441 // Return early if the popup hasn't been laid out yet. On Windows, this can
1442 // happen when using a drag popup before it opens.
1446 nsPresContext
* pc
= PresContext();
1447 nsIFrame
* rootFrame
= pc
->PresShell()->GetRootFrame();
1448 NS_ASSERTION(rootFrame
->GetView() && GetView() &&
1449 rootFrame
->GetView() == GetView()->GetParent(),
1450 "rootFrame's view is not our view's parent???");
1452 // Indicators of whether the popup should be flipped or resized.
1453 FlipStyle hFlip
= FlipStyle_None
, vFlip
= FlipStyle_None
;
1455 const nsMargin margin
= GetMargin();
1457 // the screen rectangle of the root frame, in dev pixels.
1458 const nsRect rootScreenRect
= rootFrame
->GetScreenRectInAppUnits();
1460 const bool isNoAutoHide
= IsNoAutoHide();
1461 const PopupLevel popupLevel
= GetPopupLevel(isNoAutoHide
);
1465 // Set the popup's size to the preferred size. Below, this size will be
1466 // adjusted to fit on the screen or within the content area. If the anchor is
1467 // sized to the popup, use the anchor's width instead of the preferred width.
1468 result
.mUsedRect
= nsRect(nsPoint(), aPrefSize
);
1470 const bool anchored
= IsAnchored();
1472 // In order to deal with transforms, we need the root prescontext:
1473 nsPresContext
* rootPc
= pc
->GetRootPresContext();
1474 if (NS_WARN_IF(!rootPc
)) {
1475 // If we can't reach a root pres context, don't bother continuing.
1479 result
.mAnchorRect
= result
.mUntransformedAnchorRect
= [&] {
1480 // If anchored to a rectangle, use that rectangle. Otherwise, determine
1481 // the rectangle from the anchor.
1482 if (mAnchorType
== MenuPopupAnchorType_Rect
) {
1485 // if the frame is not specified, use the anchor node passed to OpenPopup.
1486 // If that wasn't specified either, use the root frame. Note that
1487 // mAnchorContent might be a different document so its presshell must be
1489 nsIFrame
* anchorFrame
= GetAnchorFrame();
1491 return rootScreenRect
;
1493 return ComputeAnchorRect(rootPc
, anchorFrame
);
1496 // if we are anchored, there are certain things we don't want to do when
1497 // repositioning the popup to fit on the screen, such as end up positioned
1498 // over the anchor, for instance a popup appearing over the menu label.
1499 // When doing this reposition, we want to move the popup to the side with
1500 // the most room. The combination of anchor and alignment dictate if we
1501 // readjust above/below or to the left/right.
1502 if (mAnchorContent
|| mAnchorType
== MenuPopupAnchorType_Rect
) {
1503 // move the popup according to the anchor and alignment. This will also
1504 // tell us which axis the popup is flush against in case we have to move
1505 // it around later. The AdjustPositionForAnchorAlign method accounts for
1506 // the popup's margin.
1507 result
.mUsedRect
.MoveTo(AdjustPositionForAnchorAlign(
1508 result
.mAnchorRect
, aPrefSize
, hFlip
, vFlip
));
1510 // With no anchor, the popup is positioned relative to the root frame.
1511 result
.mUsedRect
.MoveTo(result
.mAnchorRect
.TopLeft() +
1512 nsPoint(margin
.left
, margin
.top
));
1515 // mXPos and mYPos specify an additional offset passed to OpenPopup that
1516 // should be added to the position. We also add the offset to the anchor
1517 // pos so a later flip/resize takes the offset into account.
1518 // FIXME(emilio): Wayland doesn't seem to be accounting for this offset
1519 // anywhere, and it probably should.
1521 nsPoint
offset(CSSPixel::ToAppUnits(mXPos
), CSSPixel::ToAppUnits(mYPos
));
1522 if (IsDirectionRTL()) {
1523 offset
.x
= -offset
.x
;
1525 result
.mUsedRect
.MoveBy(offset
);
1526 result
.mAnchorRect
.MoveBy(offset
);
1529 // Not anchored, use mScreenRect
1530 result
.mUsedRect
.MoveTo(mScreenRect
.TopLeft());
1531 result
.mAnchorRect
= result
.mUntransformedAnchorRect
=
1532 nsRect(mScreenRect
.TopLeft(), nsSize());
1534 // Right-align RTL context menus, and apply margin and offsets as per the
1535 // platform conventions.
1536 if (mIsContextMenu
&& IsDirectionRTL()) {
1537 result
.mUsedRect
.x
-= aPrefSize
.Width();
1538 result
.mUsedRect
.MoveBy(-margin
.right
, margin
.top
);
1540 result
.mUsedRect
.MoveBy(margin
.left
, margin
.top
);
1543 // OSX tooltips follow standard flip rule but other popups flip horizontally
1545 if (mPopupType
== PopupType::Tooltip
) {
1546 vFlip
= FlipStyle_Outside
;
1548 hFlip
= FlipStyle_Outside
;
1551 // Other OS screen positioned popups can be flipped vertically but never
1553 vFlip
= FlipStyle_Outside
;
1554 #endif // #ifdef XP_MACOSX
1557 const int32_t a2d
= pc
->AppUnitsPerDevPixel();
1559 nsView
* view
= GetView();
1560 NS_ASSERTION(view
, "popup with no view");
1562 nsIWidget
* widget
= view
->GetWidget();
1564 // If a panel has flip="none", don't constrain or flip it.
1565 // Also, always do this for content shells, so that the popup doesn't extend
1566 // outside the containing frame.
1567 if (mInContentShell
|| mFlip
!= FlipType_None
) {
1568 const Maybe
<nsRect
> constraintRect
=
1569 GetConstraintRect(result
.mAnchorRect
, rootScreenRect
, popupLevel
);
1571 if (constraintRect
) {
1572 // Ensure that anchorRect is on the constraint rect.
1573 result
.mAnchorRect
= result
.mAnchorRect
.Intersect(*constraintRect
);
1574 // Shrink the popup down if it is larger than the constraint size
1575 if (result
.mUsedRect
.width
> constraintRect
->width
) {
1576 result
.mUsedRect
.width
= constraintRect
->width
;
1578 if (result
.mUsedRect
.height
> constraintRect
->height
) {
1579 result
.mUsedRect
.height
= constraintRect
->height
;
1581 result
.mConstrainedByLayout
= true;
1584 if (IS_WAYLAND_DISPLAY() && widget
) {
1585 // Shrink the popup down if it's larger than popup size received from
1586 // Wayland compositor. We don't know screen size on Wayland so this is the
1587 // only info we have there.
1588 const nsSize waylandSize
= LayoutDeviceIntRect::ToAppUnits(
1589 widget
->GetMoveToRectPopupSize(), a2d
);
1590 if (waylandSize
.width
> 0 && result
.mUsedRect
.width
> waylandSize
.width
) {
1591 LOG_WAYLAND("Wayland constraint width [%p]: %d to %d", widget
,
1592 result
.mUsedRect
.width
, waylandSize
.width
);
1593 result
.mUsedRect
.width
= waylandSize
.width
;
1595 if (waylandSize
.height
> 0 &&
1596 result
.mUsedRect
.height
> waylandSize
.height
) {
1597 LOG_WAYLAND("Wayland constraint height [%p]: %d to %d", widget
,
1598 result
.mUsedRect
.height
, waylandSize
.height
);
1599 result
.mUsedRect
.height
= waylandSize
.height
;
1601 if (RefPtr
<widget::Screen
> s
= widget
->GetWidgetScreen()) {
1602 const nsSize screenSize
=
1603 LayoutDeviceIntSize::ToAppUnits(s
->GetAvailRect().Size(), a2d
);
1604 if (result
.mUsedRect
.height
> screenSize
.height
) {
1605 LOG_WAYLAND("Wayland constraint height to screen [%p]: %d to %d",
1606 widget
, result
.mUsedRect
.height
, screenSize
.height
);
1607 result
.mUsedRect
.height
= screenSize
.height
;
1609 if (result
.mUsedRect
.width
> screenSize
.width
) {
1610 LOG_WAYLAND("Wayland constraint widthto screen [%p]: %d to %d",
1611 widget
, result
.mUsedRect
.width
, screenSize
.width
);
1612 result
.mUsedRect
.width
= screenSize
.width
;
1617 // At this point the anchor (anchorRect) is within the available screen
1618 // area (constraintRect) and the popup is known to be no larger than the
1620 if (constraintRect
) {
1621 // We might want to "slide" an arrow if the panel is of the correct type -
1622 // but we can only slide on one axis - the other axis must be "flipped or
1623 // resized" as normal.
1624 bool slideHorizontal
= false, slideVertical
= false;
1625 if (mFlip
== FlipType_Slide
) {
1626 int8_t position
= GetAlignmentPosition();
1627 slideHorizontal
= position
>= POPUPPOSITION_BEFORESTART
&&
1628 position
<= POPUPPOSITION_AFTEREND
;
1629 slideVertical
= position
>= POPUPPOSITION_STARTBEFORE
&&
1630 position
<= POPUPPOSITION_ENDAFTER
;
1633 // Next, check if there is enough space to show the popup at full size
1634 // when positioned at screenPoint. If not, flip the popups to the opposite
1635 // side of their anchor point, or resize them as necessary.
1636 if (slideHorizontal
) {
1637 result
.mUsedRect
.width
= SlideOrResize(
1638 result
.mUsedRect
.x
, result
.mUsedRect
.width
, constraintRect
->x
,
1639 constraintRect
->XMost(), &result
.mAlignmentOffset
);
1641 const bool endAligned
=
1643 ? mPopupAlignment
== POPUPALIGNMENT_TOPLEFT
||
1644 mPopupAlignment
== POPUPALIGNMENT_BOTTOMLEFT
||
1645 mPopupAlignment
== POPUPALIGNMENT_LEFTCENTER
1646 : mPopupAlignment
== POPUPALIGNMENT_TOPRIGHT
||
1647 mPopupAlignment
== POPUPALIGNMENT_BOTTOMRIGHT
||
1648 mPopupAlignment
== POPUPALIGNMENT_RIGHTCENTER
;
1649 result
.mUsedRect
.width
= FlipOrResize(
1650 result
.mUsedRect
.x
, result
.mUsedRect
.width
, constraintRect
->x
,
1651 constraintRect
->XMost(), result
.mAnchorRect
.x
,
1652 result
.mAnchorRect
.XMost(), margin
.left
, margin
.right
, hFlip
,
1653 endAligned
, &result
.mHFlip
);
1655 if (slideVertical
) {
1656 result
.mUsedRect
.height
= SlideOrResize(
1657 result
.mUsedRect
.y
, result
.mUsedRect
.height
, constraintRect
->y
,
1658 constraintRect
->YMost(), &result
.mAlignmentOffset
);
1660 bool endAligned
= mPopupAlignment
== POPUPALIGNMENT_BOTTOMLEFT
||
1661 mPopupAlignment
== POPUPALIGNMENT_BOTTOMRIGHT
||
1662 mPopupAlignment
== POPUPALIGNMENT_BOTTOMCENTER
;
1663 result
.mUsedRect
.height
= FlipOrResize(
1664 result
.mUsedRect
.y
, result
.mUsedRect
.height
, constraintRect
->y
,
1665 constraintRect
->YMost(), result
.mAnchorRect
.y
,
1666 result
.mAnchorRect
.YMost(), margin
.top
, margin
.bottom
, vFlip
,
1667 endAligned
, &result
.mVFlip
);
1671 NS_ASSERTION(constraintRect
->Contains(result
.mUsedRect
),
1672 "Popup is offscreen");
1673 if (!constraintRect
->Contains(result
.mUsedRect
)) {
1674 NS_WARNING(nsPrintfCString("Popup is offscreen (%s vs. %s)",
1675 ToString(constraintRect
).c_str(),
1676 ToString(result
.mUsedRect
).c_str())
1682 // snap the popup's position in screen coordinates to device pixels, see
1683 // bug 622507, bug 961431
1684 result
.mUsedRect
.x
= pc
->RoundAppUnitsToNearestDevPixels(result
.mUsedRect
.x
);
1685 result
.mUsedRect
.y
= pc
->RoundAppUnitsToNearestDevPixels(result
.mUsedRect
.y
);
1687 // determine the x and y position of the view by subtracting the desired
1688 // screen position from the screen position of the root frame.
1689 result
.mViewPoint
= result
.mUsedRect
.TopLeft() - rootScreenRect
.TopLeft();
1691 // Offset the position by the width and height of the borders and titlebar.
1692 // Even though GetClientOffset should return (0, 0) when there is no titlebar
1693 // or borders, we skip these calculations anyway for non-panels to save time
1694 // since they will never have a titlebar.
1695 if (mPopupType
== PopupType::Panel
&& widget
) {
1696 result
.mClientOffset
= widget
->GetClientOffset();
1697 result
.mViewPoint
+=
1698 LayoutDeviceIntPoint::ToAppUnits(result
.mClientOffset
, a2d
);
1704 void nsMenuPopupFrame::SetPopupPosition(bool aIsMove
) {
1705 if (aIsMove
&& (mPrefSize
.width
== -1 || mPrefSize
.height
== -1)) {
1709 auto rects
= GetRects(mPrefSize
);
1710 if (rects
.mUsedRect
.Size() != mRect
.Size()) {
1711 MOZ_ASSERT(!HasAnyStateBits(NS_FRAME_IN_REFLOW
));
1712 // We need to resize on top of moving, trigger an actual reflow.
1713 PresShell()->FrameNeedsReflow(this, IntrinsicDirty::FrameAndAncestors
,
1720 void nsMenuPopupFrame::PerformMove(const Rects
& aRects
) {
1721 auto* ps
= PresShell();
1723 // We're just moving, sync frame position and offset as needed.
1724 ps
->GetViewManager()->MoveViewTo(GetView(), aRects
.mViewPoint
.x
,
1725 aRects
.mViewPoint
.y
);
1727 // Now that we've positioned the view, sync up the frame's origin.
1728 nsBlockFrame::SetPosition(aRects
.mViewPoint
-
1729 GetParent()->GetOffsetTo(ps
->GetRootFrame()));
1731 // If the popup is in the positioned state or if it is shown and the position
1732 // or size changed, dispatch a popuppositioned event if the popup wants it.
1733 if (mPopupState
== ePopupPositioning
||
1734 (mPopupState
== ePopupShown
&&
1735 !aRects
.mUsedRect
.IsEqualEdges(mUsedScreenRect
)) ||
1736 (mPopupState
== ePopupShown
&&
1737 aRects
.mAlignmentOffset
!= mAlignmentOffset
)) {
1738 mUsedScreenRect
= aRects
.mUsedRect
;
1739 if (!HasAnyStateBits(NS_FRAME_FIRST_REFLOW
) && !mPendingPositionedEvent
) {
1740 mPendingPositionedEvent
=
1741 nsXULPopupPositionedEvent::DispatchIfNeeded(mContent
->AsElement());
1745 if (!mPositionedByMoveToRect
) {
1746 mUntransformedAnchorRect
= aRects
.mUntransformedAnchorRect
;
1749 mAlignmentOffset
= aRects
.mAlignmentOffset
;
1750 mLastClientOffset
= aRects
.mClientOffset
;
1751 mHFlip
= aRects
.mHFlip
;
1752 mVFlip
= aRects
.mVFlip
;
1753 mConstrainedByLayout
= aRects
.mConstrainedByLayout
;
1755 // If this is a noautohide popup, set the screen coordinates of the popup.
1756 // This way, the popup stays at the location where it was opened even when the
1757 // window is moved. Popups at the parent level follow the parent window as it
1758 // is moved and remained anchored, so we want to maintain the anchoring
1761 // FIXME: This suffers from issues like bug 1823552, where constraints imposed
1762 // by the anchor are lost, but this is super-old behavior.
1763 const bool fixPositionToPoint
=
1764 IsNoAutoHide() && (GetPopupLevel() != PopupLevel::Parent
||
1765 mAnchorType
== MenuPopupAnchorType_Rect
);
1766 if (fixPositionToPoint
) {
1767 // Account for the margin that will end up being added to the screen
1768 // coordinate the next time SetPopupPosition is called.
1769 const auto& margin
= GetMargin();
1770 mAnchorType
= MenuPopupAnchorType_Point
;
1771 mScreenRect
.x
= aRects
.mUsedRect
.x
- margin
.left
;
1772 mScreenRect
.y
= aRects
.mUsedRect
.y
- margin
.top
;
1775 // For anchored popups that shouldn't follow the anchor, fix the original
1777 if (IsAnchored() && !ShouldFollowAnchor() && !mUsedScreenRect
.IsEmpty() &&
1778 mAnchorType
!= MenuPopupAnchorType_Rect
) {
1779 mAnchorType
= MenuPopupAnchorType_Rect
;
1780 mScreenRect
= aRects
.mUntransformedAnchorRect
;
1783 // NOTE(emilio): This call below is kind of a workaround, but we need to do
1784 // this here because some position changes don't go through the
1785 // view system -> popup manager, like:
1787 // https://searchfox.org/mozilla-central/rev/477950cf9ca9c9bb5ff6f34e0d0f6ca4718ea798/widget/gtk/nsWindow.cpp#3847
1789 // So this might be the last chance we have to set the remote browser's
1792 // Ultimately this probably wants to get fixed in the widget size of things,
1793 // but given this is worst-case a redundant DOM traversal, and that popups
1794 // usually don't have all that much content, this is probably an ok
1796 WidgetPositionOrSizeDidChange();
1799 void nsMenuPopupFrame::WidgetPositionOrSizeDidChange() {
1800 // In the case this popup has remote contents having OOP iframes, it's
1801 // possible that OOP iframe's nsSubDocumentFrame has been already reflowed
1802 // thus, we will never have a chance to tell this parent browser's position
1803 // update to the OOP documents without notifying it explicitly.
1804 if (!HasRemoteContent()) {
1807 for (nsIContent
* content
= mContent
->GetFirstChild(); content
;
1808 content
= content
->GetNextNode(mContent
)) {
1809 if (content
->IsXULElement(nsGkAtoms::browser
) &&
1810 content
->AsElement()->AttrValueIs(kNameSpaceID_None
, nsGkAtoms::remote
,
1811 nsGkAtoms::_true
, eIgnoreCase
)) {
1812 if (auto* browserParent
= dom::BrowserParent::GetFrom(content
)) {
1813 browserParent
->NotifyPositionUpdatedForContentsInPopup();
1819 Maybe
<nsRect
> nsMenuPopupFrame::GetConstraintRect(
1820 const nsRect
& aAnchorRect
, const nsRect
& aRootScreenRect
,
1821 PopupLevel aPopupLevel
) const {
1822 const nsPresContext
* pc
= PresContext();
1823 const int32_t a2d
= PresContext()->AppUnitsPerDevPixel();
1824 Maybe
<nsRect
> result
;
1826 auto AddConstraint
= [&result
](const nsRect
& aConstraint
) {
1828 *result
= result
->Intersect(aConstraint
);
1830 result
.emplace(aConstraint
);
1834 // Determine the available screen space. It will be reduced by the OS chrome
1835 // such as menubars. It addition, for content shells, it will be the area of
1836 // the content rather than the screen.
1837 // In Wayland we can't use the screen rect because we can't know absolute
1839 if (!IS_WAYLAND_DISPLAY()) {
1840 const DesktopToLayoutDeviceScale scale
=
1841 pc
->DeviceContext()->GetDesktopToDeviceScale();
1842 // For content shells, get the screen where the root frame is located. This
1843 // is because we need to constrain the content to this content area, so we
1844 // should use the same screen. Otherwise, use the screen where the anchor is
1846 const nsRect
& rect
= mInContentShell
? aRootScreenRect
: aAnchorRect
;
1847 auto desktopRect
= DesktopIntRect::RoundOut(
1848 LayoutDeviceRect::FromAppUnits(rect
, a2d
) / scale
);
1849 desktopRect
.width
= std::max(1, desktopRect
.width
);
1850 desktopRect
.height
= std::max(1, desktopRect
.height
);
1852 RefPtr
<nsIScreen
> screen
=
1853 widget::ScreenManager::GetSingleton().ScreenForRect(desktopRect
);
1854 MOZ_ASSERT(screen
, "We always fall back to the primary screen");
1855 // Non-top-level popups (which will always be panels) should never overlap
1857 const bool canOverlapOSBar
=
1858 aPopupLevel
== PopupLevel::Top
&&
1859 LookAndFeel::GetInt(LookAndFeel::IntID::MenusCanOverlapOSBar
) &&
1861 // Get the total screen area if the popup is allowed to overlap it.
1862 const auto screenRect
=
1863 canOverlapOSBar
? screen
->GetRect() : screen
->GetAvailRect();
1864 AddConstraint(LayoutDeviceRect::ToAppUnits(screenRect
, a2d
));
1867 if (mInContentShell
) {
1868 // For content shells, clip to the client area rather than the screen area
1869 AddConstraint(aRootScreenRect
);
1870 } else if (!mOverrideConstraintRect
.IsEmpty()) {
1871 AddConstraint(mOverrideConstraintRect
);
1872 // This is currently only used for <select> elements where we want to
1873 // constrain vertically to the screen but not horizontally, so do the
1874 // intersection and then reset the horizontal values.
1876 // FIXME(emilio): This doesn't make any sense to me...
1877 result
->x
= mOverrideConstraintRect
.x
;
1878 result
->width
= mOverrideConstraintRect
.width
;
1881 // Expand the allowable screen rect by the input margin (which can't be
1882 // interacted with).
1884 const nscoord inputMargin
=
1885 StyleUIReset()->mMozWindowInputRegionMargin
.ToAppUnits();
1886 result
->Inflate(inputMargin
);
1891 ConsumeOutsideClicksResult
nsMenuPopupFrame::ConsumeOutsideClicks() {
1892 if (mContent
->AsElement()->AttrValueIs(kNameSpaceID_None
,
1893 nsGkAtoms::consumeoutsideclicks
,
1894 nsGkAtoms::_true
, eCaseMatters
)) {
1895 return ConsumeOutsideClicks_True
;
1897 if (mContent
->AsElement()->AttrValueIs(kNameSpaceID_None
,
1898 nsGkAtoms::consumeoutsideclicks
,
1899 nsGkAtoms::_false
, eCaseMatters
)) {
1900 return ConsumeOutsideClicks_ParentOnly
;
1902 if (mContent
->AsElement()->AttrValueIs(kNameSpaceID_None
,
1903 nsGkAtoms::consumeoutsideclicks
,
1904 nsGkAtoms::never
, eCaseMatters
)) {
1905 return ConsumeOutsideClicks_Never
;
1908 nsCOMPtr
<nsIContent
> parentContent
= mContent
->GetParent();
1909 if (parentContent
) {
1910 dom::NodeInfo
* ni
= parentContent
->NodeInfo();
1911 if (ni
->Equals(nsGkAtoms::menulist
, kNameSpaceID_XUL
)) {
1912 return ConsumeOutsideClicks_True
; // Consume outside clicks for combo
1913 // boxes on all platforms
1916 // Don't consume outside clicks for menus in Windows
1917 if (ni
->Equals(nsGkAtoms::menu
, kNameSpaceID_XUL
) ||
1918 ni
->Equals(nsGkAtoms::popupset
, kNameSpaceID_XUL
) ||
1919 ((ni
->Equals(nsGkAtoms::button
, kNameSpaceID_XUL
) ||
1920 ni
->Equals(nsGkAtoms::toolbarbutton
, kNameSpaceID_XUL
)) &&
1921 parentContent
->AsElement()->AttrValueIs(
1922 kNameSpaceID_None
, nsGkAtoms::type
, nsGkAtoms::menu
,
1924 return ConsumeOutsideClicks_Never
;
1929 return ConsumeOutsideClicks_True
;
1932 static nsIScrollableFrame
* DoGetScrollFrame(const nsIFrame
* aFrame
) {
1933 if (const nsIScrollableFrame
* sf
= do_QueryFrame(aFrame
)) {
1934 return const_cast<nsIScrollableFrame
*>(sf
);
1936 for (nsIFrame
* childFrame
: aFrame
->PrincipalChildList()) {
1937 if (auto* sf
= DoGetScrollFrame(childFrame
)) {
1944 // XXXroc this is megalame. Fossicking around for a frame of the right
1945 // type is a recipe for disaster in the long term.
1946 nsIScrollableFrame
* nsMenuPopupFrame::GetScrollFrame() const {
1947 return DoGetScrollFrame(this);
1950 void nsMenuPopupFrame::ChangeByPage(bool aIsUp
) {
1951 // Only scroll by page within menulists.
1952 if (!IsMenuList()) {
1956 nsIScrollableFrame
* scrollframe
= GetScrollFrame();
1958 RefPtr popup
= &PopupElement();
1959 XULButtonElement
* currentMenu
= popup
->GetActiveMenuChild();
1960 XULButtonElement
* newMenu
= nullptr;
1962 // If there is no current menu item, get the first item. When moving up,
1963 // just use this as the newMenu and leave currentMenu null so that no check
1964 // for a later element is performed. When moving down, set currentMenu so
1965 // that we look for one page down from the first item.
1966 newMenu
= popup
->GetFirstMenuItem();
1968 currentMenu
= newMenu
;
1972 if (currentMenu
&& currentMenu
->GetPrimaryFrame()) {
1973 const nscoord scrollHeight
=
1974 scrollframe
? scrollframe
->GetScrollPortRect().height
: mRect
.height
;
1975 const nsRect currentRect
= currentMenu
->GetPrimaryFrame()->GetRect();
1976 const XULButtonElement
* startMenu
= currentMenu
;
1978 // Get the position of the current item and add or subtract one popup's
1979 // height to or from it.
1980 const nscoord targetPos
= aIsUp
? currentRect
.YMost() - scrollHeight
1981 : currentRect
.y
+ scrollHeight
;
1982 // Look for the next child which is just past the target position. This
1983 // child will need to be selected.
1985 currentMenu
= aIsUp
? popup
->GetPrevMenuItemFrom(*currentMenu
)
1986 : popup
->GetNextMenuItemFrom(*currentMenu
)) {
1987 if (!currentMenu
->GetPrimaryFrame()) {
1990 const nsRect curRect
= currentMenu
->GetPrimaryFrame()->GetRect();
1991 const nscoord curPos
= aIsUp
? curRect
.y
: curRect
.YMost();
1992 // If the right position was found, break out. Otherwise, look for another
1994 if (aIsUp
? (curPos
< targetPos
) : (curPos
> targetPos
)) {
1995 if (!newMenu
|| newMenu
== startMenu
) {
1996 newMenu
= currentMenu
;
2001 // Assign this item to newMenu. This item will be selected in case we
2002 // don't find any more.
2003 newMenu
= currentMenu
;
2007 // Select the new menuitem.
2008 if (RefPtr newMenuRef
= newMenu
) {
2009 popup
->SetActiveMenuChild(newMenuRef
);
2013 dom::XULPopupElement
& nsMenuPopupFrame::PopupElement() const {
2014 auto* popup
= dom::XULPopupElement::FromNode(GetContent());
2015 MOZ_DIAGNOSTIC_ASSERT(popup
);
2019 XULButtonElement
* nsMenuPopupFrame::GetCurrentMenuItem() const {
2020 return PopupElement().GetActiveMenuChild();
2023 nsIFrame
* nsMenuPopupFrame::GetCurrentMenuItemFrame() const {
2024 auto* child
= GetCurrentMenuItem();
2025 return child
? child
->GetPrimaryFrame() : nullptr;
2028 void nsMenuPopupFrame::HandleEnterKeyPress(WidgetEvent
& aEvent
) {
2029 mIncrementalString
.Truncate();
2030 RefPtr popup
= &PopupElement();
2031 popup
->HandleEnterKeyPress(aEvent
);
2034 XULButtonElement
* nsMenuPopupFrame::FindMenuWithShortcut(
2035 mozilla::dom::KeyboardEvent
& aKeyEvent
, bool& aDoAction
) {
2036 uint32_t charCode
= aKeyEvent
.CharCode();
2037 uint32_t keyCode
= aKeyEvent
.KeyCode();
2041 // Enumerate over our list of frames.
2042 const bool isMenu
= !IsMenuList();
2043 TimeStamp keyTime
= aKeyEvent
.WidgetEventPtr()->mTimeStamp
;
2044 if (charCode
== 0) {
2045 if (keyCode
== dom::KeyboardEvent_Binding::DOM_VK_BACK_SPACE
) {
2046 if (!isMenu
&& !mIncrementalString
.IsEmpty()) {
2047 mIncrementalString
.SetLength(mIncrementalString
.Length() - 1);
2051 if (nsCOMPtr
<nsISound
> sound
= do_GetService("@mozilla.org/sound;1")) {
2054 #endif // #ifdef XP_WIN
2058 char16_t uniChar
= ToLowerCase(static_cast<char16_t
>(charCode
));
2060 // Menu supports only first-letter navigation
2061 mIncrementalString
= uniChar
;
2062 } else if (IsWithinIncrementalTime(keyTime
)) {
2063 mIncrementalString
.Append(uniChar
);
2065 // Interval too long, treat as new typing
2066 mIncrementalString
= uniChar
;
2069 // See bug 188199 & 192346, if all letters in incremental string are same,
2070 // just try to match the first one
2071 nsAutoString
incrementalString(mIncrementalString
);
2072 uint32_t charIndex
= 1, stringLength
= incrementalString
.Length();
2073 while (charIndex
< stringLength
&&
2074 incrementalString
[charIndex
] == incrementalString
[charIndex
- 1]) {
2077 if (charIndex
== stringLength
) {
2078 incrementalString
.Truncate(1);
2082 sLastKeyTime
= keyTime
;
2085 PopupElement().FindMenuWithShortcut(incrementalString
, aDoAction
);
2090 // If we don't match anything, rollback the last typing
2091 mIncrementalString
.SetLength(mIncrementalString
.Length() - 1);
2093 // didn't find a matching menu item
2095 // behavior on Windows - this item is in a menu popup off of the
2096 // menu bar, so beep and do nothing else
2098 if (nsCOMPtr
<nsISound
> sound
= do_GetService("@mozilla.org/sound;1")) {
2102 #endif // #ifdef XP_WIN
2107 nsIWidget
* nsMenuPopupFrame::GetWidget() const {
2108 return mView
? mView
->GetWidget() : nullptr;
2111 // helpers /////////////////////////////////////////////////////////////
2113 nsresult
nsMenuPopupFrame::AttributeChanged(int32_t aNameSpaceID
,
2119 nsBlockFrame::AttributeChanged(aNameSpaceID
, aAttribute
, aModType
);
2121 if (aAttribute
== nsGkAtoms::left
|| aAttribute
== nsGkAtoms::top
) {
2122 MoveToAttributePosition();
2125 if (aAttribute
== nsGkAtoms::remote
) {
2126 // When the remote attribute changes, we need to create a new widget to
2127 // ensure that it has the correct compositor and transparency settings to
2128 // match the new value.
2129 PrepareWidget(true);
2132 if (aAttribute
== nsGkAtoms::followanchor
) {
2133 if (nsXULPopupManager
* pm
= nsXULPopupManager::GetInstance()) {
2134 pm
->UpdateFollowAnchor(this);
2138 if (aAttribute
== nsGkAtoms::label
) {
2139 // set the label for the titlebar
2140 nsView
* view
= GetView();
2142 nsIWidget
* widget
= view
->GetWidget();
2145 mContent
->AsElement()->GetAttr(nsGkAtoms::label
, title
);
2146 if (!title
.IsEmpty()) {
2147 widget
->SetTitle(title
);
2151 } else if (aAttribute
== nsGkAtoms::ignorekeys
) {
2152 nsXULPopupManager
* pm
= nsXULPopupManager::GetInstance();
2154 nsAutoString ignorekeys
;
2155 mContent
->AsElement()->GetAttr(nsGkAtoms::ignorekeys
, ignorekeys
);
2156 pm
->UpdateIgnoreKeys(ignorekeys
.EqualsLiteral("true"));
2163 void nsMenuPopupFrame::MoveToAttributePosition() {
2164 // Move the widget around when the user sets the |left| and |top| attributes.
2165 // Note that this is not the best way to move the widget, as it results in
2166 // lots of FE notifications and is likely to be slow as molasses. Use |moveTo|
2167 // on the element if possible.
2168 nsAutoString left
, top
;
2169 mContent
->AsElement()->GetAttr(nsGkAtoms::left
, left
);
2170 mContent
->AsElement()->GetAttr(nsGkAtoms::top
, top
);
2171 nsresult err1
, err2
;
2172 mozilla::CSSIntPoint
pos(left
.ToInteger(&err1
), top
.ToInteger(&err2
));
2174 if (NS_SUCCEEDED(err1
) && NS_SUCCEEDED(err2
)) MoveTo(pos
, false);
2176 PresShell()->FrameNeedsReflow(
2177 this, IntrinsicDirty::FrameAncestorsAndDescendants
, NS_FRAME_IS_DIRTY
);
2180 void nsMenuPopupFrame::Destroy(DestroyContext
& aContext
) {
2181 // XXX: Currently we don't fire popuphidden for these popups, that seems wrong
2182 // but alas, also pre-existing.
2183 HidePopup(/* aDeselectMenu = */ false, ePopupClosed
,
2184 /* aFromFrameDestruction = */ true);
2186 if (RefPtr
<nsXULPopupManager
> pm
= nsXULPopupManager::GetInstance()) {
2187 pm
->PopupDestroyed(this);
2190 nsBlockFrame::Destroy(aContext
);
2193 nsMargin
nsMenuPopupFrame::GetMargin() const {
2195 StyleMargin()->GetMargin(margin
);
2196 if (mIsTopLevelContextMenu
) {
2197 const CSSIntPoint
offset(
2198 LookAndFeel::GetInt(LookAndFeel::IntID::ContextMenuOffsetHorizontal
),
2199 LookAndFeel::GetInt(LookAndFeel::IntID::ContextMenuOffsetVertical
));
2200 auto auOffset
= CSSIntPoint::ToAppUnits(offset
);
2201 margin
.top
+= auOffset
.y
;
2202 margin
.bottom
+= auOffset
.y
;
2203 margin
.left
+= auOffset
.x
;
2204 margin
.right
+= auOffset
.x
;
2209 void nsMenuPopupFrame::MoveTo(const CSSPoint
& aPos
, bool aUpdateAttrs
,
2210 bool aByMoveToRect
) {
2211 nsIWidget
* widget
= GetWidget();
2212 nsPoint appUnitsPos
= CSSPixel::ToAppUnits(aPos
);
2214 // reposition the popup at the specified coordinates. Don't clear the anchor
2215 // and position, because the popup can be reset to its anchor position by
2216 // using (-1, -1) as coordinates.
2218 // Subtract off the margin as it will be added to the position when
2219 // SetPopupPosition is called.
2221 nsMargin margin
= GetMargin();
2222 if (mIsContextMenu
&& IsDirectionRTL()) {
2223 appUnitsPos
.x
+= margin
.right
+ mRect
.Width();
2225 appUnitsPos
.x
-= margin
.left
;
2227 appUnitsPos
.y
-= margin
.top
;
2230 if (mScreenRect
.TopLeft() == appUnitsPos
&&
2231 (!widget
|| widget
->GetClientOffset() == mLastClientOffset
)) {
2235 mPositionedByMoveToRect
= aByMoveToRect
;
2236 mScreenRect
.MoveTo(appUnitsPos
);
2237 if (mAnchorType
== MenuPopupAnchorType_Rect
) {
2238 // This ensures that the anchor width is still honored, to prevent it from
2239 // changing spuriously.
2240 mScreenRect
.height
= 0;
2241 // But we still need to make sure that our top left position ends up in
2243 mPopupAlignment
= POPUPALIGNMENT_TOPLEFT
;
2244 mPopupAnchor
= POPUPALIGNMENT_BOTTOMLEFT
;
2247 mAnchorType
= MenuPopupAnchorType_Point
;
2250 SetPopupPosition(true);
2252 RefPtr
<Element
> popup
= mContent
->AsElement();
2254 (popup
->HasAttr(nsGkAtoms::left
) || popup
->HasAttr(nsGkAtoms::top
))) {
2255 nsAutoString left
, top
;
2256 left
.AppendInt(RoundedToInt(aPos
).x
);
2257 top
.AppendInt(RoundedToInt(aPos
).y
);
2258 popup
->SetAttr(kNameSpaceID_None
, nsGkAtoms::left
, left
, false);
2259 popup
->SetAttr(kNameSpaceID_None
, nsGkAtoms::top
, top
, false);
2263 void nsMenuPopupFrame::MoveToAnchor(nsIContent
* aAnchorContent
,
2264 const nsAString
& aPosition
, int32_t aXPos
,
2265 int32_t aYPos
, bool aAttributesOverride
) {
2266 NS_ASSERTION(IsVisible(), "popup must be visible to move it");
2268 nsPopupState oldstate
= mPopupState
;
2269 InitializePopup(aAnchorContent
, mTriggerContent
, aPosition
, aXPos
, aYPos
,
2270 MenuPopupAnchorType_Node
, aAttributesOverride
);
2271 // InitializePopup changed the state so reset it.
2272 mPopupState
= oldstate
;
2274 // Pass false here so that flipping and adjusting to fit on the screen happen.
2275 SetPopupPosition(false);
2278 int8_t nsMenuPopupFrame::GetAlignmentPosition() const {
2279 // The code below handles most cases of alignment, anchor and position values.
2280 // Those that are not handled just return POPUPPOSITION_UNKNOWN.
2282 if (mPosition
== POPUPPOSITION_OVERLAP
||
2283 mPosition
== POPUPPOSITION_AFTERPOINTER
||
2284 mPosition
== POPUPPOSITION_SELECTION
) {
2288 int8_t position
= mPosition
;
2290 if (position
== POPUPPOSITION_UNKNOWN
) {
2291 switch (mPopupAnchor
) {
2292 case POPUPALIGNMENT_BOTTOMRIGHT
:
2293 case POPUPALIGNMENT_BOTTOMLEFT
:
2294 case POPUPALIGNMENT_BOTTOMCENTER
:
2295 position
= mPopupAlignment
== POPUPALIGNMENT_TOPRIGHT
2296 ? POPUPPOSITION_AFTEREND
2297 : POPUPPOSITION_AFTERSTART
;
2299 case POPUPALIGNMENT_TOPRIGHT
:
2300 case POPUPALIGNMENT_TOPLEFT
:
2301 case POPUPALIGNMENT_TOPCENTER
:
2302 position
= mPopupAlignment
== POPUPALIGNMENT_BOTTOMRIGHT
2303 ? POPUPPOSITION_BEFOREEND
2304 : POPUPPOSITION_BEFORESTART
;
2306 case POPUPALIGNMENT_LEFTCENTER
:
2307 position
= mPopupAlignment
== POPUPALIGNMENT_BOTTOMRIGHT
2308 ? POPUPPOSITION_STARTAFTER
2309 : POPUPPOSITION_STARTBEFORE
;
2311 case POPUPALIGNMENT_RIGHTCENTER
:
2312 position
= mPopupAlignment
== POPUPALIGNMENT_BOTTOMLEFT
2313 ? POPUPPOSITION_ENDAFTER
2314 : POPUPPOSITION_ENDBEFORE
;
2322 position
= POPUPPOSITION_HFLIP(position
);
2326 position
= POPUPPOSITION_VFLIP(position
);
2333 * KEEP THIS IN SYNC WITH nsIFrame::CreateView
2334 * as much as possible. Until we get rid of views finally...
2336 void nsMenuPopupFrame::CreatePopupView() {
2341 nsViewManager
* viewManager
= PresContext()->GetPresShell()->GetViewManager();
2342 NS_ASSERTION(nullptr != viewManager
, "null view manager");
2345 nsView
* parentView
= viewManager
->GetRootView();
2346 auto visibility
= ViewVisibility::Hide
;
2348 NS_ASSERTION(parentView
, "no parent view");
2351 nsView
* view
= viewManager
->CreateView(GetRect(), parentView
, visibility
);
2352 auto zIndex
= ZIndex();
2353 viewManager
->SetViewZIndex(view
, zIndex
.isNothing(), zIndex
.valueOr(0));
2354 // XXX put view last in document order until we can do better
2355 viewManager
->InsertChild(parentView
, view
, nullptr, true);
2357 // Remember our view
2361 NS_FRAME_TRACE_CALLS
,
2362 ("nsMenuPopupFrame::CreatePopupView: frame=%p view=%p", this, view
));
2365 bool nsMenuPopupFrame::ShouldFollowAnchor() const {
2366 if (mAnchorType
!= MenuPopupAnchorType_Node
|| !mAnchorContent
) {
2370 // Follow anchor mode is used when followanchor="true" is set or for arrow
2372 if (mContent
->AsElement()->AttrValueIs(kNameSpaceID_None
,
2373 nsGkAtoms::followanchor
,
2374 nsGkAtoms::_true
, eCaseMatters
)) {
2378 if (mContent
->AsElement()->AttrValueIs(kNameSpaceID_None
,
2379 nsGkAtoms::followanchor
,
2380 nsGkAtoms::_false
, eCaseMatters
)) {
2384 return mPopupType
== PopupType::Panel
&&
2385 mContent
->AsElement()->AttrValueIs(kNameSpaceID_None
, nsGkAtoms::type
,
2386 nsGkAtoms::arrow
, eCaseMatters
);
2389 bool nsMenuPopupFrame::ShouldFollowAnchor(nsRect
& aRect
) {
2390 if (!ShouldFollowAnchor()) {
2394 if (nsIFrame
* anchorFrame
= GetAnchorFrame()) {
2395 if (nsPresContext
* rootPresContext
= PresContext()->GetRootPresContext()) {
2396 aRect
= ComputeAnchorRect(rootPresContext
, anchorFrame
);
2403 bool nsMenuPopupFrame::IsDirectionRTL() const {
2404 const nsIFrame
* anchor
= GetAnchorFrame();
2405 const nsIFrame
* f
= anchor
? anchor
: this;
2406 return f
->StyleVisibility()->mDirection
== StyleDirection::Rtl
;
2409 nsIFrame
* nsMenuPopupFrame::GetAnchorFrame() const {
2410 nsIContent
* anchor
= mAnchorContent
;
2414 return MaybeDelegatedAnchorFrame(anchor
->GetPrimaryFrame());
2417 void nsMenuPopupFrame::CheckForAnchorChange(nsRect
& aRect
) {
2418 // Don't update if the popup isn't visible or we shouldn't be following the
2420 if (!IsVisible() || !ShouldFollowAnchor()) {
2424 bool shouldHide
= false;
2426 nsPresContext
* rootPresContext
= PresContext()->GetRootPresContext();
2428 // If the frame for the anchor has gone away, hide the popup.
2429 nsIFrame
* anchor
= GetAnchorFrame();
2430 if (!anchor
|| !rootPresContext
) {
2432 } else if (!anchor
->IsVisibleConsideringAncestors(
2433 VISIBILITY_CROSS_CHROME_CONTENT_BOUNDARY
)) {
2434 // If the anchor is now inside something that is invisible, hide the popup.
2437 // If the anchor is now inside a hidden parent popup, hide the popup.
2438 nsIFrame
* frame
= anchor
;
2440 nsMenuPopupFrame
* popup
= do_QueryFrame(frame
);
2441 if (popup
&& popup
->PopupState() != ePopupShown
) {
2446 frame
= frame
->GetParent();
2451 nsXULPopupManager
* pm
= nsXULPopupManager::GetInstance();
2453 // As the caller will be iterating over the open popups, hide
2455 pm
->HidePopup(mContent
->AsElement(),
2456 {HidePopupOption::DeselectMenu
, HidePopupOption::Async
});
2462 nsRect anchorRect
= ComputeAnchorRect(rootPresContext
, anchor
);
2464 // If the rectangles are different, move the popup.
2465 if (!anchorRect
.IsEqualEdges(aRect
)) {
2467 SetPopupPosition(true);