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"
9 #include "nsIContent.h"
11 #include "nsPresContext.h"
12 #include "mozilla/ComputedStyle.h"
13 #include "nsCSSRendering.h"
14 #include "nsNameSpaceManager.h"
15 #include "nsViewManager.h"
16 #include "nsWidgetsCID.h"
17 #include "nsMenuFrame.h"
18 #include "nsMenuBarFrame.h"
19 #include "nsPopupSetFrame.h"
20 #include "nsPIDOMWindow.h"
21 #include "nsFrameManager.h"
22 #include "mozilla/dom/Document.h"
24 #include "nsBoxLayoutState.h"
25 #include "nsIScrollableFrame.h"
26 #include "nsIPopupContainer.h"
27 #include "nsIDocShell.h"
28 #include "nsReadableUtils.h"
29 #include "nsUnicharUtils.h"
30 #include "nsLayoutUtils.h"
31 #include "nsContentUtils.h"
32 #include "nsCSSFrameConstructor.h"
33 #include "nsPIWindowRoot.h"
34 #include "nsIReflowCallback.h"
35 #include "nsIDocShellTreeOwner.h"
36 #include "nsIBaseWindow.h"
38 #include "nsIScreenManager.h"
39 #include "nsServiceManagerUtils.h"
40 #include "nsStyleConsts.h"
41 #include "nsStyleStructInlines.h"
42 #include "nsTransitionManager.h"
43 #include "nsDisplayList.h"
44 #include "nsIDOMXULSelectCntrlEl.h"
45 #include "mozilla/AnimationUtils.h"
46 #include "mozilla/BasePrincipal.h"
47 #include "mozilla/EventDispatcher.h"
48 #include "mozilla/EventStateManager.h"
49 #include "mozilla/Preferences.h"
50 #include "mozilla/LookAndFeel.h"
51 #include "mozilla/MouseEvents.h"
52 #include "mozilla/PresShell.h"
53 #include "mozilla/Services.h"
54 #include "mozilla/StaticPrefs_xul.h"
55 #include "mozilla/dom/BrowserParent.h"
56 #include "mozilla/dom/Element.h"
57 #include "mozilla/dom/Event.h"
58 #include "mozilla/dom/KeyboardEvent.h"
59 #include "mozilla/dom/KeyboardEventBinding.h"
62 #include "X11UndefineNone.h"
64 using namespace mozilla
;
65 using mozilla::dom::Document
;
66 using mozilla::dom::Element
;
67 using mozilla::dom::Event
;
68 using mozilla::dom::KeyboardEvent
;
70 int8_t nsMenuPopupFrame::sDefaultLevelIsTop
= -1;
72 DOMTimeStamp
nsMenuPopupFrame::sLastKeyTime
= 0;
75 # include "mozilla/WidgetUtilsGtk.h"
76 # define IS_WAYLAND_DISPLAY() mozilla::widget::GdkIsWaylandDisplay()
77 extern mozilla::LazyLogModule gWidgetPopupLog
;
78 # define LOG_WAYLAND(...) \
79 MOZ_LOG(gWidgetPopupLog, mozilla::LogLevel::Debug, (__VA_ARGS__))
81 # define IS_WAYLAND_DISPLAY() false
82 # define LOG_WAYLAND (...)
85 // NS_NewMenuPopupFrame
87 // Wrapper for creating a new menu popup container
89 nsIFrame
* NS_NewMenuPopupFrame(PresShell
* aPresShell
, ComputedStyle
* aStyle
) {
90 return new (aPresShell
)
91 nsMenuPopupFrame(aStyle
, aPresShell
->GetPresContext());
94 NS_IMPL_FRAMEARENA_HELPERS(nsMenuPopupFrame
)
96 NS_QUERYFRAME_HEAD(nsMenuPopupFrame
)
97 NS_QUERYFRAME_ENTRY(nsMenuPopupFrame
)
98 NS_QUERYFRAME_TAIL_INHERITING(nsBoxFrame
)
101 // nsMenuPopupFrame ctor
103 nsMenuPopupFrame::nsMenuPopupFrame(ComputedStyle
* aStyle
,
104 nsPresContext
* aPresContext
)
105 : nsBoxFrame(aStyle
, aPresContext
, kClassID
),
106 mCurrentMenu(nullptr),
112 mLastClientOffset(0, 0),
113 mPopupType(ePopupTypePanel
),
114 mPopupState(ePopupClosed
),
115 mPopupAlignment(POPUPALIGNMENT_NONE
),
116 mPopupAnchor(POPUPALIGNMENT_NONE
),
117 mPosition(POPUPPOSITION_UNKNOWN
),
118 mFlip(FlipType_Default
),
119 mIsOpenChanged(false),
120 mMenuCanOverlapOSBar(false),
121 mShouldAutoPosition(true),
122 mInContentShell(true),
123 mIsMenuLocked(false),
127 mPositionedOffset(0),
128 mAnchorType(MenuPopupAnchorType_Node
) {
129 // the preference name is backwards here. True means that the 'top' level is
130 // the default, and false means that the 'parent' level is the default.
131 if (sDefaultLevelIsTop
>= 0) return;
133 Preferences::GetBool("ui.panel.default_level_parent", false);
136 nsMenuPopupFrame::~nsMenuPopupFrame() = default;
138 static bool IsMouseTransparent(const ComputedStyle
& aStyle
) {
139 // If pointer-events: none; is set on the popup, then the widget should
140 // ignore mouse events, passing them through to the content behind.
141 return aStyle
.PointerEvents() == StylePointerEvents::None
;
144 static nsIWidget::InputRegion
ComputeInputRegion(const ComputedStyle
& aStyle
,
145 const nsPresContext
& aPc
) {
146 return {IsMouseTransparent(aStyle
),
147 (aStyle
.StyleUIReset()->mMozWindowInputRegionMargin
.ToCSSPixels() *
148 aPc
.CSSToDevPixelScale())
152 bool nsMenuPopupFrame::ShouldCreateWidgetUpfront() const {
153 if (mPopupType
!= ePopupTypeMenu
) {
154 // Any panel with a type attribute, such as the autocomplete popup, is
155 // always generated right away.
156 return mContent
->AsElement()->HasAttr(nsGkAtoms::type
);
158 // Generate the widget up-front if the following is true:
160 // - If the parent menu is a <menulist> unless its sizetopopup is set to
162 // - For other elements, if the parent menu has a sizetopopup attribute.
164 // In these cases the size of the parent menu is dependent on the size of the
165 // popup, so the widget needs to exist in order to calculate this size.
166 nsIContent
* parentContent
= mContent
->GetParent();
167 if (!parentContent
) {
171 if (parentContent
->IsXULElement(nsGkAtoms::menulist
)) {
172 Element
* parent
= parentContent
->AsElement();
173 nsAutoString sizedToPopup
;
174 if (!parent
->GetAttr(nsGkAtoms::sizetopopup
, sizedToPopup
)) {
175 // No prop set, generate child frames normally for the default value
179 // Don't generate child frame only if the property is set to none.
180 return !sizedToPopup
.EqualsLiteral("none");
183 return parentContent
->IsElement() &&
184 parentContent
->AsElement()->HasAttr(nsGkAtoms::sizetopopup
);
187 void nsMenuPopupFrame::Init(nsIContent
* aContent
, nsContainerFrame
* aParent
,
188 nsIFrame
* aPrevInFlow
) {
189 nsBoxFrame::Init(aContent
, aParent
, aPrevInFlow
);
191 // lookup if we're allowed to overlap the OS bar (menubar/taskbar) from the
193 mMenuCanOverlapOSBar
=
194 LookAndFeel::GetInt(LookAndFeel::IntID::MenusCanOverlapOSBar
) != 0;
198 // XXX Hack. The popup's view should float above all other views,
199 // so we use the nsView::SetFloating() to tell the view manager
200 // about that constraint.
201 nsView
* ourView
= GetView();
202 nsViewManager
* viewManager
= ourView
->GetViewManager();
203 viewManager
->SetViewFloating(ourView
, true);
205 mPopupType
= ePopupTypePanel
;
206 if (aContent
->IsAnyOfXULElements(nsGkAtoms::menupopup
, nsGkAtoms::popup
)) {
207 mPopupType
= ePopupTypeMenu
;
208 } else if (aContent
->IsXULElement(nsGkAtoms::tooltip
)) {
209 mPopupType
= ePopupTypeTooltip
;
212 if (PresContext()->IsChrome()) {
213 mInContentShell
= false;
216 // Support incontentshell=false attribute to allow popups to be displayed
217 // outside of the content shell. Chrome only.
218 if (aContent
->NodePrincipal()->IsSystemPrincipal()) {
219 if (aContent
->AsElement()->AttrValueIs(kNameSpaceID_None
,
220 nsGkAtoms::incontentshell
,
221 nsGkAtoms::_true
, eCaseMatters
)) {
222 mInContentShell
= true;
223 } else if (aContent
->AsElement()->AttrValueIs(
224 kNameSpaceID_None
, nsGkAtoms::incontentshell
,
225 nsGkAtoms::_false
, eCaseMatters
)) {
226 mInContentShell
= false;
230 // To improve performance, create the widget for the popup if needed. Popups
231 // such as menus will create their widgets later when the popup opens.
233 // FIXME(emilio): Doing this up-front for all menupopups causes a bunch of
234 // assertions, while it's supposed to be just an optimization.
235 if (!ourView
->HasWidget() && ShouldCreateWidgetUpfront()) {
236 CreateWidgetForView(ourView
);
239 if (aContent
->NodeInfo()->Equals(nsGkAtoms::tooltip
, kNameSpaceID_XUL
) &&
240 aContent
->AsElement()->AttrValueIs(kNameSpaceID_None
, nsGkAtoms::_default
,
241 nsGkAtoms::_true
, eIgnoreCase
)) {
242 nsIPopupContainer
* popupContainer
=
243 nsIPopupContainer::GetPopupContainer(PresShell());
244 if (popupContainer
) {
245 popupContainer
->SetDefaultTooltip(aContent
->AsElement());
249 AddStateBits(NS_FRAME_IN_POPUP
);
252 bool nsMenuPopupFrame::HasRemoteContent() const {
253 return (!mInContentShell
&& mPopupType
== ePopupTypePanel
&&
254 mContent
->AsElement()->AttrValueIs(kNameSpaceID_None
,
256 nsGkAtoms::_true
, eIgnoreCase
));
259 bool nsMenuPopupFrame::IsNoAutoHide() const {
260 // Panels with noautohide="true" don't hide when the mouse is clicked
261 // outside of them, or when another application is made active. Non-autohide
262 // panels cannot be used in content windows.
263 return (!mInContentShell
&& mPopupType
== ePopupTypePanel
&&
264 mContent
->AsElement()->AttrValueIs(kNameSpaceID_None
,
265 nsGkAtoms::noautohide
,
266 nsGkAtoms::_true
, eIgnoreCase
));
269 nsPopupLevel
nsMenuPopupFrame::PopupLevel(bool aIsNoAutoHide
) const {
270 // The popup level is determined as follows, in this order:
271 // 1. non-panels (menus and tooltips) are always topmost
272 // 2. any specified level attribute
273 // 3. if a titlebar attribute is set, use the 'floating' level
274 // 4. if this is a noautohide panel, use the 'parent' level
275 // 5. use the platform-specific default level
277 // If this is not a panel, this is always a top-most popup.
278 if (mPopupType
!= ePopupTypePanel
) return ePopupLevelTop
;
280 // If the level attribute has been set, use that.
281 static Element::AttrValuesArray strings
[] = {
282 nsGkAtoms::top
, nsGkAtoms::parent
, nsGkAtoms::floating
, nullptr};
283 switch (mContent
->AsElement()->FindAttrValueIn(
284 kNameSpaceID_None
, nsGkAtoms::level
, strings
, eCaseMatters
)) {
286 return ePopupLevelTop
;
288 return ePopupLevelParent
;
290 return ePopupLevelFloating
;
293 // Panels with titlebars most likely want to be floating popups.
294 if (mContent
->AsElement()->HasAttr(kNameSpaceID_None
, nsGkAtoms::titlebar
))
295 return ePopupLevelFloating
;
297 // If this panel is a noautohide panel, the default is the parent level.
298 if (aIsNoAutoHide
) return ePopupLevelParent
;
300 // Otherwise, the result depends on the platform.
301 return sDefaultLevelIsTop
? ePopupLevelTop
: ePopupLevelParent
;
304 void nsMenuPopupFrame::PrepareWidget(bool aRecreate
) {
305 nsView
* ourView
= GetView();
307 if (auto* widget
= GetWidget()) {
308 // Widget's WebRender resources needs to be cleared before creating new
310 widget
->ClearCachedWebrenderResources();
312 ourView
->DestroyWidget();
314 if (!ourView
->HasWidget()) {
315 CreateWidgetForView(ourView
);
317 if (nsIWidget
* widget
= GetWidget()) {
318 // This won't dynamically update the color scheme changes while the widget
319 // is shown, but it's good enough.
320 widget
->SetColorScheme(Some(LookAndFeel::ColorSchemeForFrame(this)));
324 nsresult
nsMenuPopupFrame::CreateWidgetForView(nsView
* aView
) {
325 // Create a widget for ourselves.
326 nsWidgetInitData widgetData
;
327 widgetData
.mWindowType
= eWindowType_popup
;
328 widgetData
.mBorderStyle
= eBorderStyle_default
;
329 widgetData
.mForMenupopupFrame
= true;
330 widgetData
.mClipSiblings
= true;
331 widgetData
.mPopupHint
= mPopupType
;
332 widgetData
.mNoAutoHide
= IsNoAutoHide();
334 if (!mInContentShell
) {
335 // A drag popup may be used for non-static translucent drag feedback
336 if (mPopupType
== ePopupTypePanel
&&
337 mContent
->AsElement()->AttrValueIs(kNameSpaceID_None
, nsGkAtoms::type
,
338 nsGkAtoms::drag
, eIgnoreCase
)) {
339 widgetData
.mIsDragPopup
= true;
344 if (widgetData
.mNoAutoHide
) {
345 if (mContent
->AsElement()->AttrValueIs(kNameSpaceID_None
,
347 nsGkAtoms::normal
, eCaseMatters
)) {
348 widgetData
.mBorderStyle
= eBorderStyle_title
;
350 mContent
->AsElement()->GetAttr(kNameSpaceID_None
, nsGkAtoms::label
,
353 if (mContent
->AsElement()->AttrValueIs(kNameSpaceID_None
,
354 nsGkAtoms::close
, nsGkAtoms::_true
,
356 widgetData
.mBorderStyle
= static_cast<enum nsBorderStyle
>(
357 widgetData
.mBorderStyle
| eBorderStyle_close
);
362 bool remote
= HasRemoteContent();
364 nsTransparencyMode mode
= nsLayoutUtils::GetFrameTransparency(this, this);
365 widgetData
.mHasRemoteContent
= remote
;
366 widgetData
.mSupportTranslucency
= mode
== eTransparencyTransparent
;
367 widgetData
.mPopupLevel
= PopupLevel(widgetData
.mNoAutoHide
);
369 // Panels which have a parent level need a parent widget. This allows them to
370 // always appear in front of the parent window but behind other windows that
371 // should be in front of it.
372 nsCOMPtr
<nsIWidget
> parentWidget
;
373 if (widgetData
.mPopupLevel
!= ePopupLevelTop
) {
374 nsCOMPtr
<nsIDocShellTreeItem
> dsti
= PresContext()->GetDocShell();
375 if (!dsti
) return NS_ERROR_FAILURE
;
377 nsCOMPtr
<nsIDocShellTreeOwner
> treeOwner
;
378 dsti
->GetTreeOwner(getter_AddRefs(treeOwner
));
379 if (!treeOwner
) return NS_ERROR_FAILURE
;
381 nsCOMPtr
<nsIBaseWindow
> baseWindow(do_QueryInterface(treeOwner
));
382 if (baseWindow
) baseWindow
->GetMainWidget(getter_AddRefs(parentWidget
));
386 aView
->CreateWidgetForPopup(&widgetData
, parentWidget
, true, true);
391 nsIWidget
* widget
= aView
->GetWidget();
392 widget
->SetTransparencyMode(mode
);
393 widget
->SetInputRegion(ComputeInputRegion(*Style(), *PresContext()));
394 widget
->SetWindowShadowStyle(GetShadowStyle());
395 widget
->SetWindowOpacity(StyleUIReset()->mWindowOpacity
);
396 widget
->SetWindowTransform(ComputeWidgetTransform());
398 // most popups don't have a title so avoid setting the title if there isn't
400 if (!title
.IsEmpty()) {
401 widget
->SetTitle(title
);
407 bool nsMenuPopupFrame::IsMouseTransparent() const {
408 return ::IsMouseTransparent(*Style());
411 StyleWindowShadow
nsMenuPopupFrame::GetShadowStyle() {
412 StyleWindowShadow shadow
= StyleUIReset()->mWindowShadow
;
413 if (shadow
!= StyleWindowShadow::Default
) return shadow
;
415 switch (StyleDisplay()->EffectiveAppearance()) {
416 case StyleAppearance::Tooltip
:
417 return StyleWindowShadow::Tooltip
;
418 case StyleAppearance::Menupopup
:
419 return StyleWindowShadow::Menu
;
421 return StyleWindowShadow::Default
;
425 void nsMenuPopupFrame::SetPopupState(nsPopupState aState
) {
426 mPopupState
= aState
;
428 // Work around https://gitlab.gnome.org/GNOME/gtk/-/issues/4166
429 if (aState
== ePopupShown
&& IS_WAYLAND_DISPLAY()) {
430 if (nsIWidget
* widget
= GetWidget()) {
431 widget
->SetInputRegion(ComputeInputRegion(*Style(), *PresContext()));
436 // TODO: Convert this to MOZ_CAN_RUN_SCRIPT (bug 1415230, bug 1535398)
437 MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHODIMP
nsXULPopupShownEvent::Run() {
438 nsMenuPopupFrame
* popup
= do_QueryFrame(mPopup
->GetPrimaryFrame());
439 // Set the state to visible if the popup is still open.
440 if (popup
&& popup
->IsOpen()) {
441 popup
->SetPopupState(ePopupShown
);
444 if (!mPopup
->IsXULElement(nsGkAtoms::tooltip
)) {
445 nsCOMPtr
<nsIObserverService
> obsService
=
446 mozilla::services::GetObserverService();
448 obsService
->NotifyObservers(mPopup
, "popup-shown", nullptr);
451 WidgetMouseEvent
event(true, eXULPopupShown
, nullptr,
452 WidgetMouseEvent::eReal
);
453 return EventDispatcher::Dispatch(mPopup
, mPresContext
, &event
);
456 NS_IMETHODIMP
nsXULPopupShownEvent::HandleEvent(Event
* aEvent
) {
457 nsMenuPopupFrame
* popup
= do_QueryFrame(mPopup
->GetPrimaryFrame());
458 // Ignore events not targeted at the popup itself (ie targeted at
460 if (mPopup
!= aEvent
->GetTarget()) {
464 // ResetPopupShownDispatcher will delete the reference to this, so keep
465 // another one until Run is finished.
466 RefPtr
<nsXULPopupShownEvent
> event
= this;
467 // Only call Run if it the dispatcher was assigned. This avoids calling the
468 // Run method if the transitionend event fires multiple times.
469 if (popup
->ClearPopupShownDispatcher()) {
478 void nsXULPopupShownEvent::CancelListener() {
479 mPopup
->RemoveSystemEventListener(u
"transitionend"_ns
, this, false);
482 NS_IMPL_ISUPPORTS_INHERITED(nsXULPopupShownEvent
, Runnable
,
483 nsIDOMEventListener
);
485 void nsMenuPopupFrame::DidSetComputedStyle(ComputedStyle
* aOldStyle
) {
486 nsBoxFrame::DidSetComputedStyle(aOldStyle
);
492 auto& newUI
= *StyleUIReset();
493 auto& oldUI
= *aOldStyle
->StyleUIReset();
494 if (newUI
.mWindowOpacity
!= oldUI
.mWindowOpacity
) {
495 if (nsIWidget
* widget
= GetWidget()) {
496 widget
->SetWindowOpacity(newUI
.mWindowOpacity
);
500 if (newUI
.mMozWindowTransform
!= oldUI
.mMozWindowTransform
) {
501 if (nsIWidget
* widget
= GetWidget()) {
502 widget
->SetWindowTransform(ComputeWidgetTransform());
506 auto oldRegion
= ComputeInputRegion(*aOldStyle
, *PresContext());
507 auto newRegion
= ComputeInputRegion(*Style(), *PresContext());
508 if (oldRegion
.mFullyTransparent
!= newRegion
.mFullyTransparent
||
509 oldRegion
.mMargin
!= newRegion
.mMargin
) {
510 if (nsIWidget
* widget
= GetWidget()) {
511 widget
->SetInputRegion(newRegion
);
516 void nsMenuPopupFrame::ConstrainSizeForWayland(nsSize
& aSize
) const {
518 if (!IS_WAYLAND_DISPLAY()) {
522 // If the size is not a whole number in CSS pixels we need round it up to
523 // avoid reflow of the tooltips/popups and putting the text on two lines
524 // (usually happens with 200% scale factor and font scale factor <> 1) because
525 // GTK throws away the decimals.
526 int32_t appPerCSS
= AppUnitsPerCSSPixel();
527 if (aSize
.width
% appPerCSS
> 0) {
528 aSize
.width
+= appPerCSS
;
530 if (aSize
.height
% appPerCSS
> 0) {
531 aSize
.height
+= appPerCSS
;
534 nsIWidget
* widget
= GetWidget();
539 // Shrink the popup down if it's larger than popup size received from Wayland
540 // compositor. We don't know screen size on Wayland so this is the only info
542 const nsSize waylandSize
= LayoutDeviceIntRect::ToAppUnits(
543 widget
->GetMoveToRectPopupSize(), PresContext()->AppUnitsPerDevPixel());
544 if (waylandSize
.width
> 0 && aSize
.width
> waylandSize
.width
) {
545 LOG_WAYLAND("Wayland constraint width [%p]: %d to %d", widget
, aSize
.width
,
547 aSize
.width
= waylandSize
.width
;
549 if (waylandSize
.height
> 0 && aSize
.height
> waylandSize
.height
) {
550 LOG_WAYLAND("Wayland constraint height [%p]: %d to %d", widget
,
551 aSize
.height
, waylandSize
.height
);
552 aSize
.height
= waylandSize
.height
;
557 void nsMenuPopupFrame::LayoutPopup(nsBoxLayoutState
& aState
,
558 nsIFrame
* aParentMenu
, bool aSizedToPopup
) {
559 if (IsNativeMenu()) {
563 mSizedToPopup
= aSizedToPopup
;
567 bool shouldPosition
= [&] {
571 if (ShouldFollowAnchor()) {
574 // Don't reposition anchored popups that shouldn't follow the anchor and
575 // have already been positioned.
576 return mPopupState
!= ePopupShown
|| mUsedScreenRect
.IsEmpty();
579 bool isOpen
= IsOpen();
581 // if the popup is not open, only do layout while showing or if the menu
582 // is sized to the popup
584 (mPopupState
== ePopupShowing
|| mPopupState
== ePopupPositioning
);
585 if (!shouldPosition
&& !aSizedToPopup
) {
586 RemoveStateBits(NS_FRAME_FIRST_REFLOW
);
591 // if the popup has just been opened, make sure the scrolled window is at 0,0
592 // Don't scroll menulists as they will scroll to their selected item on their
594 if (mIsOpenChanged
&& !IsMenuList()) {
595 nsIScrollableFrame
* scrollframe
=
596 do_QueryFrame(nsIFrame::GetChildXULBox(this));
598 AutoWeakFrame
weakFrame(this);
599 scrollframe
->ScrollTo(nsPoint(0, 0), ScrollMode::Instant
);
600 if (!weakFrame
.IsAlive()) {
606 // get the preferred, minimum and maximum size. If the menu is sized to the
607 // popup, then the popup's width is the menu's width.
608 nsSize prefSize
= GetXULPrefSize(aState
);
609 nsSize minSize
= GetXULMinSize(aState
);
610 nsSize maxSize
= GetXULMaxSize(aState
);
613 prefSize
.width
= aParentMenu
->GetRect().width
;
616 prefSize
= XULBoundsCheck(minSize
, prefSize
, maxSize
);
618 ConstrainSizeForWayland(prefSize
);
620 const bool sizeChanged
= mPrefSize
!= prefSize
;
621 // if the size changed then set the bounds to be the preferred size, and make
622 // sure we re-position the popup too (as that can shrink or resize us again).
624 shouldPosition
= true;
625 SetXULBounds(aState
, nsRect(0, 0, prefSize
.width
, prefSize
.height
), false);
626 mPrefSize
= prefSize
;
629 bool needCallback
= false;
630 if (shouldPosition
) {
631 SetPopupPosition(aParentMenu
, false, aSizedToPopup
);
635 nsRect
bounds(GetRect());
638 // if the width or height changed, readjust the popup position. This is a
639 // special case for tooltips where the preferred height doesn't include the
640 // real height for its inline element, but does once it is laid out.
641 // This is bug 228673 which doesn't have a simple fix.
642 bool rePosition
= shouldPosition
&& (mPosition
== POPUPPOSITION_SELECTION
);
644 nsSize newsize
= GetSize();
645 if (newsize
.width
> bounds
.width
|| newsize
.height
> bounds
.height
) {
646 // the size after layout was larger than the preferred size,
647 // so set the preferred size accordingly
657 SetPopupPosition(aParentMenu
, false, aSizedToPopup
);
660 nsPresContext
* pc
= PresContext();
661 nsView
* view
= GetView();
664 // If the size of the popup changed, apply any size constraints.
665 nsIWidget
* widget
= view
->GetWidget();
667 SetSizeConstraints(pc
, widget
, minSize
, maxSize
);
672 nsViewManager
* viewManager
= view
->GetViewManager();
673 nsRect rect
= GetRect();
675 rect
.SizeTo(XULBoundsCheck(minSize
, rect
.Size(), maxSize
));
676 viewManager
->ResizeView(view
, rect
);
678 if (mPopupState
== ePopupOpening
) {
679 mPopupState
= ePopupVisible
;
682 viewManager
->SetViewVisibility(view
, nsViewVisibility_kShow
);
683 SyncFrameViewProperties(view
);
686 // finally, if the popup just opened, send a popupshown event
687 bool openChanged
= mIsOpenChanged
;
689 mIsOpenChanged
= false;
691 // Make sure the current selection in a menulist is visible.
692 if (IsMenuList() && mCurrentMenu
) {
693 EnsureMenuItemIsVisible(mCurrentMenu
);
696 // If the animate attribute is set to open, check for a transition and wait
697 // for it to finish before firing the popupshown event.
698 if (StaticPrefs::xul_panel_animations_enabled() &&
699 mContent
->AsElement()->AttrValueIs(kNameSpaceID_None
,
700 nsGkAtoms::animate
, nsGkAtoms::open
,
702 AnimationUtils::HasCurrentTransitions(mContent
->AsElement(),
703 PseudoStyleType::NotPseudo
)) {
704 mPopupShownDispatcher
= new nsXULPopupShownEvent(mContent
, pc
);
705 mContent
->AddSystemEventListener(u
"transitionend"_ns
,
706 mPopupShownDispatcher
, false, false);
710 // If there are no transitions, fire the popupshown event right away.
711 nsCOMPtr
<nsIRunnable
> event
= new nsXULPopupShownEvent(GetContent(), pc
);
712 mContent
->OwnerDoc()->Dispatch(TaskCategory::Other
, event
.forget());
715 if (needCallback
&& !mReflowCallbackData
.mPosted
) {
716 pc
->PresShell()->PostReflowCallback(this);
717 mReflowCallbackData
.MarkPosted(aParentMenu
, openChanged
);
721 bool nsMenuPopupFrame::ReflowFinished() {
722 SetPopupPosition(mReflowCallbackData
.mAnchor
, false, mSizedToPopup
);
723 mReflowCallbackData
.Clear();
727 void nsMenuPopupFrame::ReflowCallbackCanceled() { mReflowCallbackData
.Clear(); }
729 bool nsMenuPopupFrame::IsMenuList() {
730 nsIFrame
* parentMenu
= GetParent();
731 return (parentMenu
&& parentMenu
->GetContent() &&
732 parentMenu
->GetContent()->IsXULElement(nsGkAtoms::menulist
));
735 nsIContent
* nsMenuPopupFrame::GetTriggerContent(
736 nsMenuPopupFrame
* aMenuPopupFrame
) {
737 while (aMenuPopupFrame
) {
738 if (aMenuPopupFrame
->mTriggerContent
)
739 return aMenuPopupFrame
->mTriggerContent
;
741 // check up the menu hierarchy until a popup with a trigger node is found
742 nsMenuFrame
* menuFrame
= do_QueryFrame(aMenuPopupFrame
->GetParent());
743 if (!menuFrame
) break;
745 nsMenuParent
* parentPopup
= menuFrame
->GetMenuParent();
746 if (!parentPopup
|| !parentPopup
->IsMenu()) break;
748 aMenuPopupFrame
= static_cast<nsMenuPopupFrame
*>(parentPopup
);
754 void nsMenuPopupFrame::InitPositionFromAnchorAlign(const nsAString
& aAnchor
,
755 const nsAString
& aAlign
) {
756 mTriggerContent
= nullptr;
758 if (aAnchor
.EqualsLiteral("topleft"))
759 mPopupAnchor
= POPUPALIGNMENT_TOPLEFT
;
760 else if (aAnchor
.EqualsLiteral("topright"))
761 mPopupAnchor
= POPUPALIGNMENT_TOPRIGHT
;
762 else if (aAnchor
.EqualsLiteral("bottomleft"))
763 mPopupAnchor
= POPUPALIGNMENT_BOTTOMLEFT
;
764 else if (aAnchor
.EqualsLiteral("bottomright"))
765 mPopupAnchor
= POPUPALIGNMENT_BOTTOMRIGHT
;
766 else if (aAnchor
.EqualsLiteral("leftcenter"))
767 mPopupAnchor
= POPUPALIGNMENT_LEFTCENTER
;
768 else if (aAnchor
.EqualsLiteral("rightcenter"))
769 mPopupAnchor
= POPUPALIGNMENT_RIGHTCENTER
;
770 else if (aAnchor
.EqualsLiteral("topcenter"))
771 mPopupAnchor
= POPUPALIGNMENT_TOPCENTER
;
772 else if (aAnchor
.EqualsLiteral("bottomcenter"))
773 mPopupAnchor
= POPUPALIGNMENT_BOTTOMCENTER
;
775 mPopupAnchor
= POPUPALIGNMENT_NONE
;
777 if (aAlign
.EqualsLiteral("topleft"))
778 mPopupAlignment
= POPUPALIGNMENT_TOPLEFT
;
779 else if (aAlign
.EqualsLiteral("topright"))
780 mPopupAlignment
= POPUPALIGNMENT_TOPRIGHT
;
781 else if (aAlign
.EqualsLiteral("bottomleft"))
782 mPopupAlignment
= POPUPALIGNMENT_BOTTOMLEFT
;
783 else if (aAlign
.EqualsLiteral("bottomright"))
784 mPopupAlignment
= POPUPALIGNMENT_BOTTOMRIGHT
;
786 mPopupAlignment
= POPUPALIGNMENT_NONE
;
788 mPosition
= POPUPPOSITION_UNKNOWN
;
791 void nsMenuPopupFrame::InitializePopup(nsIContent
* aAnchorContent
,
792 nsIContent
* aTriggerContent
,
793 const nsAString
& aPosition
,
794 int32_t aXPos
, int32_t aYPos
,
795 MenuPopupAnchorType aAnchorType
,
796 bool aAttributesOverride
) {
797 auto* widget
= GetWidget();
798 bool recreateWidget
= widget
&& widget
->NeedsRecreateToReshow();
799 PrepareWidget(recreateWidget
);
801 mPopupState
= ePopupShowing
;
802 mAnchorContent
= aAnchorContent
;
803 mTriggerContent
= aTriggerContent
;
806 mIsNativeMenu
= false;
807 mIsTopLevelContextMenu
= false;
810 mAlignmentOffset
= 0;
811 mPositionedOffset
= 0;
812 mPositionedByMoveToRect
= false;
814 mAnchorType
= aAnchorType
;
816 // if aAttributesOverride is true, then the popupanchor, popupalign and
817 // position attributes on the <menupopup> override those values passed in.
818 // If false, those attributes are only used if the values passed in are empty
819 if (aAnchorContent
|| aAnchorType
== MenuPopupAnchorType_Rect
) {
820 nsAutoString anchor
, align
, position
, flip
;
821 mContent
->AsElement()->GetAttr(kNameSpaceID_None
, nsGkAtoms::popupanchor
,
823 mContent
->AsElement()->GetAttr(kNameSpaceID_None
, nsGkAtoms::popupalign
,
825 mContent
->AsElement()->GetAttr(kNameSpaceID_None
, nsGkAtoms::position
,
827 mContent
->AsElement()->GetAttr(kNameSpaceID_None
, nsGkAtoms::flip
, flip
);
829 if (aAttributesOverride
) {
830 // if the attributes are set, clear the offset position. Otherwise,
831 // the offset is used to adjust the position from the anchor point
832 if (anchor
.IsEmpty() && align
.IsEmpty() && position
.IsEmpty())
833 position
.Assign(aPosition
);
836 } else if (!aPosition
.IsEmpty()) {
837 position
.Assign(aPosition
);
840 if (flip
.EqualsLiteral("none")) {
841 mFlip
= FlipType_None
;
842 } else if (flip
.EqualsLiteral("both")) {
843 mFlip
= FlipType_Both
;
844 } else if (flip
.EqualsLiteral("slide")) {
845 mFlip
= FlipType_Slide
;
848 position
.CompressWhitespace();
849 int32_t spaceIdx
= position
.FindChar(' ');
850 // if there is a space in the position, assume it is the anchor and
851 // alignment as two separate tokens.
853 InitPositionFromAnchorAlign(Substring(position
, 0, spaceIdx
),
854 Substring(position
, spaceIdx
+ 1));
855 } else if (position
.EqualsLiteral("before_start")) {
856 mPopupAnchor
= POPUPALIGNMENT_TOPLEFT
;
857 mPopupAlignment
= POPUPALIGNMENT_BOTTOMLEFT
;
858 mPosition
= POPUPPOSITION_BEFORESTART
;
859 } else if (position
.EqualsLiteral("before_end")) {
860 mPopupAnchor
= POPUPALIGNMENT_TOPRIGHT
;
861 mPopupAlignment
= POPUPALIGNMENT_BOTTOMRIGHT
;
862 mPosition
= POPUPPOSITION_BEFOREEND
;
863 } else if (position
.EqualsLiteral("after_start")) {
864 mPopupAnchor
= POPUPALIGNMENT_BOTTOMLEFT
;
865 mPopupAlignment
= POPUPALIGNMENT_TOPLEFT
;
866 mPosition
= POPUPPOSITION_AFTERSTART
;
867 } else if (position
.EqualsLiteral("after_end")) {
868 mPopupAnchor
= POPUPALIGNMENT_BOTTOMRIGHT
;
869 mPopupAlignment
= POPUPALIGNMENT_TOPRIGHT
;
870 mPosition
= POPUPPOSITION_AFTEREND
;
871 } else if (position
.EqualsLiteral("start_before")) {
872 mPopupAnchor
= POPUPALIGNMENT_TOPLEFT
;
873 mPopupAlignment
= POPUPALIGNMENT_TOPRIGHT
;
874 mPosition
= POPUPPOSITION_STARTBEFORE
;
875 } else if (position
.EqualsLiteral("start_after")) {
876 mPopupAnchor
= POPUPALIGNMENT_BOTTOMLEFT
;
877 mPopupAlignment
= POPUPALIGNMENT_BOTTOMRIGHT
;
878 mPosition
= POPUPPOSITION_STARTAFTER
;
879 } else if (position
.EqualsLiteral("end_before")) {
880 mPopupAnchor
= POPUPALIGNMENT_TOPRIGHT
;
881 mPopupAlignment
= POPUPALIGNMENT_TOPLEFT
;
882 mPosition
= POPUPPOSITION_ENDBEFORE
;
883 } else if (position
.EqualsLiteral("end_after")) {
884 mPopupAnchor
= POPUPALIGNMENT_BOTTOMRIGHT
;
885 mPopupAlignment
= POPUPALIGNMENT_BOTTOMLEFT
;
886 mPosition
= POPUPPOSITION_ENDAFTER
;
887 } else if (position
.EqualsLiteral("overlap")) {
888 mPopupAnchor
= POPUPALIGNMENT_TOPLEFT
;
889 mPopupAlignment
= POPUPALIGNMENT_TOPLEFT
;
890 mPosition
= POPUPPOSITION_OVERLAP
;
891 } else if (position
.EqualsLiteral("after_pointer")) {
892 mPopupAnchor
= POPUPALIGNMENT_TOPLEFT
;
893 mPopupAlignment
= POPUPALIGNMENT_TOPLEFT
;
894 mPosition
= POPUPPOSITION_AFTERPOINTER
;
895 // XXXndeakin this is supposed to anchor vertically after, but with the
896 // horizontal position as the mouse pointer.
898 } else if (position
.EqualsLiteral("selection")) {
899 mPopupAnchor
= POPUPALIGNMENT_BOTTOMLEFT
;
900 mPopupAlignment
= POPUPALIGNMENT_TOPLEFT
;
901 mPosition
= POPUPPOSITION_SELECTION
;
903 InitPositionFromAnchorAlign(anchor
, align
);
906 // When converted back to CSSIntRect it is (-1, -1, 0, 0) - as expected in
907 // nsXULPopupManager::Rollup
908 mScreenRect
= nsRect(-AppUnitsPerCSSPixel(), -AppUnitsPerCSSPixel(), 0, 0);
910 if (aAttributesOverride
) {
911 // Use |left| and |top| dimension attributes to position the popup if
912 // present, as they may have been persisted.
913 nsAutoString left
, top
;
914 mContent
->AsElement()->GetAttr(kNameSpaceID_None
, nsGkAtoms::left
, left
);
915 mContent
->AsElement()->GetAttr(kNameSpaceID_None
, nsGkAtoms::top
, top
);
918 if (!left
.IsEmpty()) {
919 int32_t x
= left
.ToInteger(&err
);
920 if (NS_SUCCEEDED(err
)) {
921 mScreenRect
.x
= CSSPixel::ToAppUnits(x
);
924 if (!top
.IsEmpty()) {
925 int32_t y
= top
.ToInteger(&err
);
926 if (NS_SUCCEEDED(err
)) {
927 mScreenRect
.y
= CSSPixel::ToAppUnits(y
);
933 void nsMenuPopupFrame::InitializePopupAtScreen(nsIContent
* aTriggerContent
,
934 int32_t aXPos
, int32_t aYPos
,
935 bool aIsContextMenu
) {
936 auto* widget
= GetWidget();
937 bool recreateWidget
= widget
&& widget
->NeedsRecreateToReshow();
938 PrepareWidget(recreateWidget
);
940 mPopupState
= ePopupShowing
;
941 mAnchorContent
= nullptr;
942 mTriggerContent
= aTriggerContent
;
944 nsRect(CSSPixel::ToAppUnits(aXPos
), CSSPixel::ToAppUnits(aYPos
), 0, 0);
947 mFlip
= FlipType_Default
;
948 mPopupAnchor
= POPUPALIGNMENT_NONE
;
949 mPopupAlignment
= POPUPALIGNMENT_NONE
;
950 mPosition
= POPUPPOSITION_UNKNOWN
;
951 mIsContextMenu
= aIsContextMenu
;
952 mIsTopLevelContextMenu
= aIsContextMenu
;
953 mIsNativeMenu
= false;
954 mAnchorType
= MenuPopupAnchorType_Point
;
955 mPositionedOffset
= 0;
956 mPositionedByMoveToRect
= false;
959 void nsMenuPopupFrame::InitializePopupAsNativeContextMenu(
960 nsIContent
* aTriggerContent
, int32_t aXPos
, int32_t aYPos
) {
961 mTriggerContent
= aTriggerContent
;
962 mPopupState
= ePopupShowing
;
963 mAnchorContent
= nullptr;
965 nsRect(CSSPixel::ToAppUnits(aXPos
), CSSPixel::ToAppUnits(aYPos
), 0, 0);
968 mFlip
= FlipType_Default
;
969 mPopupAnchor
= POPUPALIGNMENT_NONE
;
970 mPopupAlignment
= POPUPALIGNMENT_NONE
;
971 mPosition
= POPUPPOSITION_UNKNOWN
;
972 mIsContextMenu
= true;
973 mIsTopLevelContextMenu
= true;
974 mIsNativeMenu
= true;
975 mAnchorType
= MenuPopupAnchorType_Point
;
976 mPositionedOffset
= 0;
977 mPositionedByMoveToRect
= false;
980 void nsMenuPopupFrame::InitializePopupAtRect(nsIContent
* aTriggerContent
,
981 const nsAString
& aPosition
,
982 const nsIntRect
& aRect
,
983 bool aAttributesOverride
) {
984 InitializePopup(nullptr, aTriggerContent
, aPosition
, 0, 0,
985 MenuPopupAnchorType_Rect
, aAttributesOverride
);
986 mScreenRect
= ToAppUnits(aRect
, AppUnitsPerCSSPixel());
989 void nsMenuPopupFrame::ShowPopup(bool aIsContextMenu
) {
990 mIsContextMenu
= aIsContextMenu
;
992 InvalidateFrameSubtree();
994 if (mPopupState
== ePopupShowing
|| mPopupState
== ePopupPositioning
) {
995 mPopupState
= ePopupOpening
;
996 mIsOpenChanged
= true;
998 // Clear mouse capture when a popup is opened.
999 if (mPopupType
== ePopupTypeMenu
) {
1000 EventStateManager
* activeESM
= static_cast<EventStateManager
*>(
1001 EventStateManager::GetActiveEventStateManager());
1003 EventStateManager::ClearGlobalActiveContent(activeESM
);
1006 PresShell::ReleaseCapturingContent();
1009 nsMenuFrame
* menuFrame
= do_QueryFrame(GetParent());
1011 AutoWeakFrame
weakFrame(this);
1012 menuFrame
->PopupOpened();
1013 if (!weakFrame
.IsAlive()) return;
1016 // do we need an actual reflow here?
1017 // is SetPopupPosition all that is needed?
1018 PresShell()->FrameNeedsReflow(this, IntrinsicDirty::TreeChange
,
1019 NS_FRAME_HAS_DIRTY_CHILDREN
);
1021 if (mPopupType
== ePopupTypeMenu
) {
1022 nsCOMPtr
<nsISound
> sound(do_GetService("@mozilla.org/sound;1"));
1023 if (sound
) sound
->PlayEventSound(nsISound::EVENT_MENU_POPUP
);
1027 mShouldAutoPosition
= true;
1030 void nsMenuPopupFrame::ClearTriggerContentIncludingDocument() {
1031 // clear the trigger content if the popup is being closed. But don't clear
1032 // it if the popup is just being made invisible as a popuphiding or command
1033 if (mTriggerContent
) {
1034 // if the popup had a trigger node set, clear the global window popup node
1036 Document
* doc
= mContent
->GetUncomposedDoc();
1038 if (nsPIDOMWindowOuter
* win
= doc
->GetWindow()) {
1039 nsCOMPtr
<nsPIWindowRoot
> root
= win
->GetTopWindowRoot();
1041 root
->SetPopupNode(nullptr);
1046 mTriggerContent
= nullptr;
1049 void nsMenuPopupFrame::HidePopup(bool aDeselectMenu
, nsPopupState aNewState
) {
1050 NS_ASSERTION(aNewState
== ePopupClosed
|| aNewState
== ePopupInvisible
,
1051 "popup being set to unexpected state");
1053 ClearPopupShownDispatcher();
1055 // don't hide the popup when it isn't open
1056 if (mPopupState
== ePopupClosed
|| mPopupState
== ePopupShowing
||
1057 mPopupState
== ePopupPositioning
)
1060 if (aNewState
== ePopupClosed
) {
1061 // clear the trigger content if the popup is being closed. But don't clear
1062 // it if the popup is just being made invisible as a popuphiding or command
1063 // event may want to retrieve it.
1064 ClearTriggerContentIncludingDocument();
1065 mAnchorContent
= nullptr;
1068 // when invisible and about to be closed, HidePopup has already been called,
1069 // so just set the new state to closed and return
1070 if (mPopupState
== ePopupInvisible
) {
1071 if (aNewState
== ePopupClosed
) mPopupState
= ePopupClosed
;
1075 mPopupState
= aNewState
;
1077 if (IsMenu()) SetCurrentMenuItem(nullptr);
1079 mIncrementalString
.Truncate();
1081 LockMenuUntilClosed(false);
1083 mIsOpenChanged
= false;
1084 mCurrentMenu
= nullptr; // make sure no current menu is set
1085 mHFlip
= mVFlip
= false;
1087 if (auto* widget
= GetWidget()) {
1088 // Ideally we should call ClearCachedWebrenderResources but there are
1089 // intermittent failures (see bug 1748788), so we currently call
1090 // ClearWebrenderAnimationResources instead.
1091 widget
->ClearWebrenderAnimationResources();
1094 nsView
* view
= GetView();
1095 nsViewManager
* viewManager
= view
->GetViewManager();
1096 viewManager
->SetViewVisibility(view
, nsViewVisibility_kHide
);
1098 FireDOMEvent(u
"DOMMenuInactive"_ns
, mContent
);
1100 // XXX, bug 137033, In Windows, if mouse is outside the window when the
1101 // menupopup closes, no mouse_enter/mouse_exit event will be fired to clear
1102 // current hover state, we should clear it manually. This code may not the
1103 // best solution, but we can leave it here until we find the better approach.
1104 NS_ASSERTION(mContent
->IsElement(), "How do we have a non-element?");
1105 if (mContent
->AsElement()->State().HasState(dom::ElementState::HOVER
)) {
1106 EventStateManager
* esm
= PresContext()->EventStateManager();
1107 esm
->SetContentState(nullptr, dom::ElementState::HOVER
);
1110 nsMenuFrame
* menuFrame
= do_QueryFrame(GetParent());
1112 menuFrame
->PopupClosed(aDeselectMenu
);
1116 nsIFrame::ReflowChildFlags
nsMenuPopupFrame::GetXULLayoutFlags() {
1117 return ReflowChildFlags::NoSizeView
| ReflowChildFlags::NoMoveView
;
1120 nsPoint
nsMenuPopupFrame::AdjustPositionForAnchorAlign(nsRect
& anchorRect
,
1122 FlipStyle
& aVFlip
) {
1123 // flip the anchor and alignment for right-to-left
1124 int8_t popupAnchor(mPopupAnchor
);
1125 int8_t popupAlign(mPopupAlignment
);
1126 if (IsDirectionRTL()) {
1127 // no need to flip the centered anchor types vertically
1128 if (popupAnchor
<= POPUPALIGNMENT_LEFTCENTER
) {
1129 popupAnchor
= -popupAnchor
;
1131 popupAlign
= -popupAlign
;
1134 nsRect
originalAnchorRect(anchorRect
);
1136 // first, determine at which corner of the anchor the popup should appear
1138 switch (popupAnchor
) {
1139 case POPUPALIGNMENT_LEFTCENTER
:
1140 pnt
= nsPoint(anchorRect
.x
, anchorRect
.y
+ anchorRect
.height
/ 2);
1141 anchorRect
.y
= pnt
.y
;
1142 anchorRect
.height
= 0;
1144 case POPUPALIGNMENT_RIGHTCENTER
:
1145 pnt
= nsPoint(anchorRect
.XMost(), anchorRect
.y
+ anchorRect
.height
/ 2);
1146 anchorRect
.y
= pnt
.y
;
1147 anchorRect
.height
= 0;
1149 case POPUPALIGNMENT_TOPCENTER
:
1150 pnt
= nsPoint(anchorRect
.x
+ anchorRect
.width
/ 2, anchorRect
.y
);
1151 anchorRect
.x
= pnt
.x
;
1152 anchorRect
.width
= 0;
1154 case POPUPALIGNMENT_BOTTOMCENTER
:
1155 pnt
= nsPoint(anchorRect
.x
+ anchorRect
.width
/ 2, anchorRect
.YMost());
1156 anchorRect
.x
= pnt
.x
;
1157 anchorRect
.width
= 0;
1159 case POPUPALIGNMENT_TOPRIGHT
:
1160 pnt
= anchorRect
.TopRight();
1162 case POPUPALIGNMENT_BOTTOMLEFT
:
1163 pnt
= anchorRect
.BottomLeft();
1165 case POPUPALIGNMENT_BOTTOMRIGHT
:
1166 pnt
= anchorRect
.BottomRight();
1168 case POPUPALIGNMENT_TOPLEFT
:
1170 pnt
= anchorRect
.TopLeft();
1174 // If the alignment is on the right edge of the popup, move the popup left
1175 // by the width. Similarly, if the alignment is on the bottom edge of the
1176 // popup, move the popup up by the height. In addition, account for the
1177 // margins of the popup on the edge on which it is aligned.
1178 nsMargin margin
= GetMargin();
1179 switch (popupAlign
) {
1180 case POPUPALIGNMENT_TOPRIGHT
:
1181 pnt
.MoveBy(-mRect
.width
- margin
.right
, margin
.top
);
1183 case POPUPALIGNMENT_BOTTOMLEFT
:
1184 pnt
.MoveBy(margin
.left
, -mRect
.height
- margin
.bottom
);
1186 case POPUPALIGNMENT_BOTTOMRIGHT
:
1187 pnt
.MoveBy(-mRect
.width
- margin
.right
, -mRect
.height
- margin
.bottom
);
1189 case POPUPALIGNMENT_TOPLEFT
:
1191 pnt
.MoveBy(margin
.left
, margin
.top
);
1195 // If we aligning to the selected item in the popup, adjust the vertical
1196 // position by the height of the menulist label and the selected item's
1198 if (mPosition
== POPUPPOSITION_SELECTION
) {
1199 MOZ_ASSERT(popupAnchor
== POPUPALIGNMENT_BOTTOMLEFT
||
1200 popupAnchor
== POPUPALIGNMENT_BOTTOMRIGHT
);
1201 MOZ_ASSERT(popupAlign
== POPUPALIGNMENT_TOPLEFT
||
1202 popupAlign
== POPUPALIGNMENT_TOPRIGHT
);
1204 // Only adjust the popup if it just opened, otherwise the popup will move
1205 // around if its gets resized or the selection changed. Cache the value in
1206 // mPositionedOffset and use that instead for any future calculations.
1207 if (mIsOpenChanged
|| mReflowCallbackData
.mIsOpenChanged
) {
1208 nsIFrame
* selectedItemFrame
= GetSelectedItemForAlignment();
1209 if (selectedItemFrame
) {
1210 int32_t scrolly
= 0;
1211 nsIScrollableFrame
* scrollframe
= GetScrollFrame(this);
1213 scrolly
= scrollframe
->GetScrollPosition().y
;
1216 mPositionedOffset
= originalAnchorRect
.height
+
1217 selectedItemFrame
->GetRect().y
- scrolly
;
1221 pnt
.y
-= mPositionedOffset
;
1224 // Flipping horizontally is allowed as long as the popup is above or below
1225 // the anchor. This will happen if both the anchor and alignment are top or
1226 // both are bottom, but different values. Similarly, flipping vertically is
1227 // allowed if the popup is to the left or right of the anchor. In this case,
1228 // the values of the constants are such that both must be positive or both
1229 // must be negative. A special case, used for overlap, allows flipping
1230 // vertically as well.
1231 // If we are flipping in both directions, we want to set a flip style both
1232 // horizontally and vertically. However, we want to flip on the inside edge
1233 // of the anchor. Consider the example of a typical dropdown menu.
1234 // Vertically, we flip the popup on the outside edges of the anchor menu,
1235 // however horizontally, we want to to use the inside edges so the popup
1236 // still appears underneath the anchor menu instead of floating off the
1237 // side of the menu.
1238 switch (popupAnchor
) {
1239 case POPUPALIGNMENT_LEFTCENTER
:
1240 case POPUPALIGNMENT_RIGHTCENTER
:
1241 aHFlip
= FlipStyle_Outside
;
1242 aVFlip
= FlipStyle_Inside
;
1244 case POPUPALIGNMENT_TOPCENTER
:
1245 case POPUPALIGNMENT_BOTTOMCENTER
:
1246 aHFlip
= FlipStyle_Inside
;
1247 aVFlip
= FlipStyle_Outside
;
1250 FlipStyle anchorEdge
=
1251 mFlip
== FlipType_Both
? FlipStyle_Inside
: FlipStyle_None
;
1252 aHFlip
= (popupAnchor
== -popupAlign
) ? FlipStyle_Outside
: anchorEdge
;
1253 if (((popupAnchor
> 0) == (popupAlign
> 0)) ||
1254 (popupAnchor
== POPUPALIGNMENT_TOPLEFT
&&
1255 popupAlign
== POPUPALIGNMENT_TOPLEFT
))
1256 aVFlip
= FlipStyle_Outside
;
1258 aVFlip
= anchorEdge
;
1266 nsIFrame
* nsMenuPopupFrame::GetSelectedItemForAlignment() {
1267 // This method adjusts a menulist's popup such that the selected item is under
1268 // the cursor, aligned with the menulist label.
1269 nsCOMPtr
<nsIDOMXULSelectControlElement
> select
;
1270 if (mAnchorContent
) {
1271 select
= mAnchorContent
->AsElement()->AsXULSelectControl();
1275 // If there isn't an anchor, then try just getting the parent of the popup.
1276 select
= mContent
->GetParent()->AsElement()->AsXULSelectControl();
1282 nsCOMPtr
<Element
> selectedElement
;
1283 select
->GetSelectedItem(getter_AddRefs(selectedElement
));
1284 return selectedElement
? selectedElement
->GetPrimaryFrame() : nullptr;
1287 nscoord
nsMenuPopupFrame::SlideOrResize(nscoord
& aScreenPoint
, nscoord aSize
,
1288 nscoord aScreenBegin
,
1289 nscoord aScreenEnd
, nscoord
* aOffset
) {
1290 // The popup may be positioned such that either the left/top or bottom/right
1291 // is outside the screen - but never both.
1293 std::max(aScreenBegin
, std::min(aScreenEnd
- aSize
, aScreenPoint
));
1294 *aOffset
= newPos
- aScreenPoint
;
1295 aScreenPoint
= newPos
;
1296 return std::min(aSize
, aScreenEnd
- aScreenPoint
);
1299 nscoord
nsMenuPopupFrame::FlipOrResize(nscoord
& aScreenPoint
, nscoord aSize
,
1300 nscoord aScreenBegin
, nscoord aScreenEnd
,
1301 nscoord aAnchorBegin
, nscoord aAnchorEnd
,
1302 nscoord aMarginBegin
, nscoord aMarginEnd
,
1303 FlipStyle aFlip
, bool aEndAligned
,
1305 // The flip side argument will be set to true if there wasn't room and we
1306 // flipped to the opposite side.
1309 // all of the coordinates used here are in app units relative to the screen
1310 nscoord popupSize
= aSize
;
1311 if (aScreenPoint
< aScreenBegin
) {
1312 // at its current position, the popup would extend past the left or top
1313 // edge of the screen, so it will have to be moved or resized.
1315 // for inside flips, we flip on the opposite side of the anchor
1316 nscoord startpos
= aFlip
== FlipStyle_Outside
? aAnchorBegin
: aAnchorEnd
;
1317 nscoord endpos
= aFlip
== FlipStyle_Outside
? aAnchorEnd
: aAnchorBegin
;
1319 // check whether there is more room to the left and right (or top and
1320 // bottom) of the anchor and put the popup on the side with more room.
1321 if (startpos
- aScreenBegin
>= aScreenEnd
- endpos
) {
1322 aScreenPoint
= aScreenBegin
;
1323 popupSize
= startpos
- aScreenPoint
- aMarginEnd
;
1324 *aFlipSide
= !aEndAligned
;
1326 // If the newly calculated position is different than the existing
1327 // position, flip such that the popup is to the right or bottom of the
1328 // anchor point instead . However, when flipping use the same margin
1330 nscoord newScreenPoint
= endpos
+ aMarginEnd
;
1331 if (newScreenPoint
!= aScreenPoint
) {
1332 *aFlipSide
= aEndAligned
;
1333 aScreenPoint
= newScreenPoint
;
1334 // check if the new position is still off the right or bottom edge of
1335 // the screen. If so, resize the popup.
1336 if (aScreenPoint
+ aSize
> aScreenEnd
) {
1337 popupSize
= aScreenEnd
- aScreenPoint
;
1342 aScreenPoint
= aScreenBegin
;
1344 } else if (aScreenPoint
+ aSize
> aScreenEnd
) {
1345 // at its current position, the popup would extend past the right or
1346 // bottom edge of the screen, so it will have to be moved or resized.
1348 // for inside flips, we flip on the opposite side of the anchor
1349 nscoord startpos
= aFlip
== FlipStyle_Outside
? aAnchorBegin
: aAnchorEnd
;
1350 nscoord endpos
= aFlip
== FlipStyle_Outside
? aAnchorEnd
: aAnchorBegin
;
1352 // check whether there is more room to the left and right (or top and
1353 // bottom) of the anchor and put the popup on the side with more room.
1354 if (aScreenEnd
- endpos
>= startpos
- aScreenBegin
) {
1355 *aFlipSide
= aEndAligned
;
1356 if (mIsContextMenu
) {
1357 aScreenPoint
= aScreenEnd
- aSize
;
1359 aScreenPoint
= endpos
+ aMarginBegin
;
1360 popupSize
= aScreenEnd
- aScreenPoint
;
1363 // if the newly calculated position is different than the existing
1364 // position, we flip such that the popup is to the left or top of the
1365 // anchor point instead.
1366 nscoord newScreenPoint
= startpos
- aSize
- aMarginBegin
;
1367 if (newScreenPoint
!= aScreenPoint
) {
1368 *aFlipSide
= !aEndAligned
;
1369 aScreenPoint
= newScreenPoint
;
1371 // check if the new position is still off the left or top edge of the
1372 // screen. If so, resize the popup.
1373 if (aScreenPoint
< aScreenBegin
) {
1374 aScreenPoint
= aScreenBegin
;
1375 if (!mIsContextMenu
) {
1376 popupSize
= startpos
- aScreenPoint
- aMarginBegin
;
1382 aScreenPoint
= aScreenEnd
- aSize
;
1386 // Make sure that the point is within the screen boundaries and that the
1387 // size isn't off the edge of the screen. This can happen when a large
1388 // positive or negative margin is used.
1389 if (aScreenPoint
< aScreenBegin
) {
1390 aScreenPoint
= aScreenBegin
;
1392 if (aScreenPoint
> aScreenEnd
) {
1393 aScreenPoint
= aScreenEnd
- aSize
;
1396 // If popupSize ended up being negative, or the original size was actually
1397 // smaller than the calculated popup size, just use the original size instead.
1398 if (popupSize
<= 0 || aSize
< popupSize
) {
1402 return std::min(popupSize
, aScreenEnd
- aScreenPoint
);
1405 nsRect
nsMenuPopupFrame::ComputeAnchorRect(nsPresContext
* aRootPresContext
,
1406 nsIFrame
* aAnchorFrame
) {
1407 // Get the root frame for a reference
1408 nsIFrame
* rootFrame
= aRootPresContext
->PresShell()->GetRootFrame();
1410 // The dimensions of the anchor
1411 nsRect anchorRect
= aAnchorFrame
->GetRectRelativeToSelf();
1413 // Relative to the root
1414 anchorRect
= nsLayoutUtils::TransformFrameRectToAncestor(
1415 aAnchorFrame
, anchorRect
, rootFrame
);
1416 // Relative to the screen
1417 anchorRect
.MoveBy(rootFrame
->GetScreenRectInAppUnits().TopLeft());
1419 // In its own app units
1420 return anchorRect
.ScaleToOtherAppUnitsRoundOut(
1421 aRootPresContext
->AppUnitsPerDevPixel(),
1422 PresContext()->AppUnitsPerDevPixel());
1425 nsresult
nsMenuPopupFrame::SetPopupPosition(nsIFrame
* aAnchorFrame
,
1426 bool aIsMove
, bool aSizedToPopup
) {
1427 if (!mShouldAutoPosition
) {
1431 // If this is due to a move, return early if the popup hasn't been laid out
1432 // yet. On Windows, this can happen when using a drag popup before it opens.
1433 if (aIsMove
&& (mPrefSize
.width
== -1 || mPrefSize
.height
== -1)) {
1437 nsPresContext
* presContext
= PresContext();
1438 nsIFrame
* rootFrame
= presContext
->PresShell()->GetRootFrame();
1439 NS_ASSERTION(rootFrame
->GetView() && GetView() &&
1440 rootFrame
->GetView() == GetView()->GetParent(),
1441 "rootFrame's view is not our view's parent???");
1443 // For anchored popups, the anchor rectangle. For non-anchored popups, the
1447 bool anchored
= IsAnchored();
1448 if (anchored
|| aSizedToPopup
) {
1449 // In order to deal with transforms, we need the root prescontext:
1450 nsPresContext
* rootPresContext
= presContext
->GetRootPresContext();
1452 // If we can't reach a root pres context, don't bother continuing:
1453 if (!rootPresContext
) {
1457 // If anchored to a rectangle, use that rectangle. Otherwise, determine the
1458 // rectangle from the anchor.
1459 if (mAnchorType
== MenuPopupAnchorType_Rect
) {
1460 anchorRect
= mScreenRect
;
1462 // if the frame is not specified, use the anchor node passed to OpenPopup.
1463 // If that wasn't specified either, use the root frame. Note that
1464 // mAnchorContent might be a different document so its presshell must be
1466 if (!aAnchorFrame
) {
1467 if (mAnchorContent
) {
1468 aAnchorFrame
= mAnchorContent
->GetPrimaryFrame();
1471 if (!aAnchorFrame
) {
1472 aAnchorFrame
= rootFrame
;
1473 if (!aAnchorFrame
) return NS_OK
;
1477 anchorRect
= ComputeAnchorRect(rootPresContext
, aAnchorFrame
);
1481 // Set the popup's size to the preferred size. Below, this size will be
1482 // adjusted to fit on the screen or within the content area. If the anchor
1483 // is sized to the popup, use the anchor's width instead of the preferred
1484 // width. The preferred size should already be set by the parent frame.
1486 NS_ASSERTION(mPrefSize
.width
>= 0 || mPrefSize
.height
>= 0,
1487 "preferred size of popup not set");
1488 nsSize newSize
= mPrefSize
;
1489 if (aSizedToPopup
) {
1490 // Input margin doesn't have contents, so account for it for popup sizing
1492 const nscoord inputMargin
=
1493 StyleUIReset()->mMozWindowInputRegionMargin
.ToAppUnits();
1494 newSize
.width
= anchorRect
.width
+ 2 * inputMargin
;
1495 // If we're anchoring to a rect, and the rect is smaller than the
1496 // preferred size of the popup, change its width accordingly.
1497 if (mAnchorType
== MenuPopupAnchorType_Rect
) {
1498 newSize
.width
= std::max(newSize
.width
, mPrefSize
.width
);
1501 // Pref size is already constrained by LayoutPopup().
1502 ConstrainSizeForWayland(newSize
);
1504 mRect
.SizeTo(newSize
);
1507 // the screen position in app units where the popup should appear
1508 nsPoint screenPoint
;
1510 // indicators of whether the popup should be flipped or resized.
1511 FlipStyle hFlip
= FlipStyle_None
, vFlip
= FlipStyle_None
;
1513 const nsMargin margin
= GetMargin();
1515 // the screen rectangle of the root frame, in dev pixels.
1516 nsRect rootScreenRect
= rootFrame
->GetScreenRectInAppUnits();
1518 bool isNoAutoHide
= IsNoAutoHide();
1519 nsPopupLevel popupLevel
= PopupLevel(isNoAutoHide
);
1522 // if we are anchored, there are certain things we don't want to do when
1523 // repositioning the popup to fit on the screen, such as end up positioned
1524 // over the anchor, for instance a popup appearing over the menu label.
1525 // When doing this reposition, we want to move the popup to the side with
1526 // the most room. The combination of anchor and alignment dictate if we
1527 // readjust above/below or to the left/right.
1528 if (mAnchorContent
|| mAnchorType
== MenuPopupAnchorType_Rect
) {
1529 // move the popup according to the anchor and alignment. This will also
1530 // tell us which axis the popup is flush against in case we have to move
1531 // it around later. The AdjustPositionForAnchorAlign method accounts for
1532 // the popup's margin.
1533 if (!mPositionedByMoveToRect
) {
1534 mUntransformedAnchorRect
= anchorRect
;
1536 screenPoint
= AdjustPositionForAnchorAlign(anchorRect
, hFlip
, vFlip
);
1538 // with no anchor, the popup is positioned relative to the root frame
1539 anchorRect
= rootScreenRect
;
1540 if (!mPositionedByMoveToRect
) {
1541 mUntransformedAnchorRect
= anchorRect
;
1543 screenPoint
= anchorRect
.TopLeft() + nsPoint(margin
.left
, margin
.top
);
1546 // mXPos and mYPos specify an additional offset passed to OpenPopup that
1547 // should be added to the position. We also add the offset to the anchor
1548 // pos so a later flip/resize takes the offset into account.
1549 // FIXME(emilio): Wayland doesn't seem to be accounting for this offset
1550 // anywhere, and it probably should.
1551 nscoord anchorXOffset
= CSSPixel::ToAppUnits(mXPos
);
1552 if (IsDirectionRTL()) {
1553 screenPoint
.x
-= anchorXOffset
;
1554 anchorRect
.x
-= anchorXOffset
;
1556 screenPoint
.x
+= anchorXOffset
;
1557 anchorRect
.x
+= anchorXOffset
;
1559 nscoord anchorYOffset
= CSSPixel::ToAppUnits(mYPos
);
1560 screenPoint
.y
+= anchorYOffset
;
1561 anchorRect
.y
+= anchorYOffset
;
1563 // If this is a noautohide popup, set the screen coordinates of the popup.
1564 // This way, the popup stays at the location where it was opened even when
1565 // the window is moved. Popups at the parent level follow the parent
1566 // window as it is moved and remained anchored, so we want to maintain the
1567 // anchoring instead.
1568 if (isNoAutoHide
&& (popupLevel
!= ePopupLevelParent
||
1569 mAnchorType
== MenuPopupAnchorType_Rect
)) {
1570 // Account for the margin that will end up being added to the screen
1571 // coordinate the next time SetPopupPosition is called.
1572 mAnchorType
= MenuPopupAnchorType_Point
;
1573 mScreenRect
.x
= screenPoint
.x
- margin
.left
;
1574 mScreenRect
.y
= screenPoint
.y
- margin
.top
;
1577 screenPoint
= mScreenRect
.TopLeft();
1578 anchorRect
= nsRect(screenPoint
, nsSize());
1579 if (!mPositionedByMoveToRect
) {
1580 mUntransformedAnchorRect
= anchorRect
;
1583 // Right-align RTL context menus, and apply margin and offsets as per the
1584 // platform conventions.
1585 if (mIsContextMenu
&& IsDirectionRTL()) {
1586 screenPoint
.x
-= mRect
.Width();
1587 screenPoint
.MoveBy(-margin
.right
, margin
.top
);
1589 screenPoint
.MoveBy(margin
.left
, margin
.top
);
1593 // OSX tooltips follow standard flip rule but other popups flip horizontally
1595 if (mPopupType
== ePopupTypeTooltip
) {
1596 vFlip
= FlipStyle_Outside
;
1598 hFlip
= FlipStyle_Outside
;
1601 // Other OS screen positioned popups can be flipped vertically but never
1603 vFlip
= FlipStyle_Outside
;
1604 #endif // #ifdef XP_MACOSX
1607 nscoord oldAlignmentOffset
= mAlignmentOffset
;
1609 // If a panel is being moved or has flip="none", don't constrain or flip it,
1610 // in order to avoid visual noise when moving windows between screens.
1611 // However, if a panel is already constrained or flipped (mIsOffset), then we
1612 // want to continue to calculate this. Also, always do this for content
1613 // shells, so that the popup doesn't extend outside the containing frame.
1614 if (!IS_WAYLAND_DISPLAY() &&
1616 (mFlip
!= FlipType_None
&&
1617 (!aIsMove
|| mIsOffset
|| mPopupType
!= ePopupTypePanel
)))) {
1618 const nsRect screenRect
= [&] {
1619 int32_t appPerDev
= presContext
->AppUnitsPerDevPixel();
1620 auto anchorRectDevPix
=
1621 LayoutDeviceIntRect::FromAppUnitsToNearest(anchorRect
, appPerDev
);
1622 auto rootScreenRectDevPix
=
1623 LayoutDeviceIntRect::FromAppUnitsToNearest(rootScreenRect
, appPerDev
);
1624 auto screenRectDevPix
=
1625 GetConstraintRect(anchorRectDevPix
, rootScreenRectDevPix
, popupLevel
);
1626 nsRect sr
= LayoutDeviceIntRect::ToAppUnits(screenRectDevPix
, appPerDev
);
1628 // Expand the allowable screen rect by the input margin (which can't be
1629 // interacted with).
1630 const nscoord inputMargin
=
1631 StyleUIReset()->mMozWindowInputRegionMargin
.ToAppUnits();
1632 sr
.Inflate(inputMargin
);
1636 // Ensure that anchorRect is on screen.
1637 anchorRect
= anchorRect
.Intersect(screenRect
);
1639 // Shrink the the popup down if it is larger than the screen size
1640 if (mRect
.width
> screenRect
.width
) {
1641 mRect
.width
= screenRect
.width
;
1643 if (mRect
.height
> screenRect
.height
) {
1644 mRect
.height
= screenRect
.height
;
1647 // At this point the anchor (anchorRect) is within the available screen
1648 // area (screenRect) and the popup is known to be no larger than the
1651 // We might want to "slide" an arrow if the panel is of the correct type -
1652 // but we can only slide on one axis - the other axis must be "flipped or
1653 // resized" as normal.
1654 bool slideHorizontal
= false, slideVertical
= false;
1655 if (mFlip
== FlipType_Slide
) {
1656 int8_t position
= GetAlignmentPosition();
1657 slideHorizontal
= position
>= POPUPPOSITION_BEFORESTART
&&
1658 position
<= POPUPPOSITION_AFTEREND
;
1659 slideVertical
= position
>= POPUPPOSITION_STARTBEFORE
&&
1660 position
<= POPUPPOSITION_ENDAFTER
;
1663 // Next, check if there is enough space to show the popup at full size
1664 // when positioned at screenPoint. If not, flip the popups to the opposite
1665 // side of their anchor point, or resize them as necessary.
1666 const nsPoint preOffsetScreenPoint
= screenPoint
;
1667 if (slideHorizontal
) {
1668 mRect
.width
= SlideOrResize(screenPoint
.x
, mRect
.width
, screenRect
.x
,
1669 screenRect
.XMost(), &mAlignmentOffset
);
1671 bool endAligned
= IsDirectionRTL()
1672 ? mPopupAlignment
== POPUPALIGNMENT_TOPLEFT
||
1673 mPopupAlignment
== POPUPALIGNMENT_BOTTOMLEFT
1674 : mPopupAlignment
== POPUPALIGNMENT_TOPRIGHT
||
1675 mPopupAlignment
== POPUPALIGNMENT_BOTTOMRIGHT
;
1677 FlipOrResize(screenPoint
.x
, mRect
.width
, screenRect
.x
,
1678 screenRect
.XMost(), anchorRect
.x
, anchorRect
.XMost(),
1679 margin
.left
, margin
.right
, hFlip
, endAligned
, &mHFlip
);
1681 if (slideVertical
) {
1682 mRect
.height
= SlideOrResize(screenPoint
.y
, mRect
.height
, screenRect
.y
,
1683 screenRect
.YMost(), &mAlignmentOffset
);
1685 bool endAligned
= mPopupAlignment
== POPUPALIGNMENT_BOTTOMLEFT
||
1686 mPopupAlignment
== POPUPALIGNMENT_BOTTOMRIGHT
;
1688 FlipOrResize(screenPoint
.y
, mRect
.height
, screenRect
.y
,
1689 screenRect
.YMost(), anchorRect
.y
, anchorRect
.YMost(),
1690 margin
.top
, margin
.bottom
, vFlip
, endAligned
, &mVFlip
);
1692 mIsOffset
= preOffsetScreenPoint
!= screenPoint
;
1694 NS_ASSERTION(screenPoint
.x
>= screenRect
.x
, "Popup is offscreen (x start)");
1695 NS_ASSERTION(screenPoint
.y
>= screenRect
.y
, "Popup is offscreen (y start)");
1696 NS_ASSERTION(screenPoint
.x
+ mRect
.width
<= screenRect
.XMost(),
1697 "Popup is offscreen (x end)");
1698 NS_ASSERTION(screenPoint
.y
+ mRect
.height
<= screenRect
.YMost(),
1699 "Popup is offscreen (y end)");
1702 // snap the popup's position in screen coordinates to device pixels,
1703 // see bug 622507, bug 961431
1704 screenPoint
.x
= presContext
->RoundAppUnitsToNearestDevPixels(screenPoint
.x
);
1705 screenPoint
.y
= presContext
->RoundAppUnitsToNearestDevPixels(screenPoint
.y
);
1707 // determine the x and y position of the view by subtracting the desired
1708 // screen position from the screen position of the root frame.
1709 nsPoint viewPoint
= screenPoint
- rootScreenRect
.TopLeft();
1711 nsView
* view
= GetView();
1712 NS_ASSERTION(view
, "popup with no view");
1714 // Offset the position by the width and height of the borders and titlebar.
1715 // Even though GetClientOffset should return (0, 0) when there is no
1716 // titlebar or borders, we skip these calculations anyway for non-panels
1717 // to save time since they will never have a titlebar.
1718 nsIWidget
* widget
= view
->GetWidget();
1719 if (mPopupType
== ePopupTypePanel
&& widget
) {
1720 mLastClientOffset
= widget
->GetClientOffset();
1721 viewPoint
.x
+= presContext
->DevPixelsToAppUnits(mLastClientOffset
.x
);
1722 viewPoint
.y
+= presContext
->DevPixelsToAppUnits(mLastClientOffset
.y
);
1725 presContext
->GetPresShell()->GetViewManager()->MoveViewTo(view
, viewPoint
.x
,
1728 // Now that we've positioned the view, sync up the frame's origin.
1729 nsBoxFrame::SetPosition(viewPoint
- GetParent()->GetOffsetTo(rootFrame
));
1731 if (aSizedToPopup
) {
1732 nsBoxLayoutState
state(PresContext());
1733 // XXXndeakin can parentSize.width still extend outside?
1734 SetXULBounds(state
, mRect
);
1737 // If the popup is in the positioned state or if it is shown and the position
1738 // or size changed, dispatch a popuppositioned event if the popup wants it.
1739 nsIntRect
newRect(screenPoint
.x
, screenPoint
.y
, mRect
.width
, mRect
.height
);
1740 if (mPopupState
== ePopupPositioning
||
1741 (mPopupState
== ePopupShown
&& !newRect
.IsEqualEdges(mUsedScreenRect
)) ||
1742 (mPopupState
== ePopupShown
&& oldAlignmentOffset
!= mAlignmentOffset
)) {
1743 mUsedScreenRect
= newRect
;
1744 if (!HasAnyStateBits(NS_FRAME_FIRST_REFLOW
) && !mPendingPositionedEvent
) {
1745 mPendingPositionedEvent
=
1746 nsXULPopupPositionedEvent::DispatchIfNeeded(mContent
);
1750 // NOTE(emilio): This call below is kind of a workaround, but we need to do
1751 // this here because some position changes don't go through the
1752 // view system -> popup manager, like:
1754 // https://searchfox.org/mozilla-central/rev/477950cf9ca9c9bb5ff6f34e0d0f6ca4718ea798/widget/gtk/nsWindow.cpp#3847
1756 // So this might be the last chance we have to set the remote browser's
1759 // Ultimately this probably wants to get fixed in the widget size of things,
1760 // but given this is worst-case a redundant DOM traversal, and that popups
1761 // usually don't have all that much content, this is probably an ok
1763 WidgetPositionOrSizeDidChange();
1768 void nsMenuPopupFrame::WidgetPositionOrSizeDidChange() {
1769 // In the case this popup has remote contents having OOP iframes, it's
1770 // possible that OOP iframe's nsSubDocumentFrame has been already reflowed
1771 // thus, we will never have a chance to tell this parent browser's position
1772 // update to the OOP documents without notifying it explicitly.
1773 if (!HasRemoteContent()) {
1776 for (nsIContent
* content
= mContent
->GetFirstChild(); content
;
1777 content
= content
->GetNextNode(mContent
)) {
1778 if (content
->IsXULElement(nsGkAtoms::browser
) &&
1779 content
->AsElement()->AttrValueIs(kNameSpaceID_None
, nsGkAtoms::remote
,
1780 nsGkAtoms::_true
, eIgnoreCase
)) {
1781 if (auto* browserParent
= dom::BrowserParent::GetFrom(content
)) {
1782 browserParent
->NotifyPositionUpdatedForContentsInPopup();
1789 nsMenuFrame
* nsMenuPopupFrame::GetCurrentMenuItem() { return mCurrentMenu
; }
1791 LayoutDeviceIntRect
nsMenuPopupFrame::GetConstraintRect(
1792 const LayoutDeviceIntRect
& aAnchorRect
,
1793 const LayoutDeviceIntRect
& aRootScreenRect
, nsPopupLevel aPopupLevel
) {
1794 LayoutDeviceIntRect screenRectPixels
;
1796 // GetConstraintRect() does not work on Wayland as we can't get absolute
1797 // window position there.
1798 MOZ_ASSERT(!IS_WAYLAND_DISPLAY(),
1799 "GetConstraintRect does not work on Wayland");
1801 // determine the available screen space. It will be reduced by the OS chrome
1802 // such as menubars. It addition, for content shells, it will be the area of
1803 // the content rather than the screen.
1804 nsCOMPtr
<nsIScreen
> screen
;
1805 nsCOMPtr
<nsIScreenManager
> sm(
1806 do_GetService("@mozilla.org/gfx/screenmanager;1"));
1808 // for content shells, get the screen where the root frame is located.
1809 // This is because we need to constrain the content to this content area,
1810 // so we should use the same screen. Otherwise, use the screen where the
1811 // anchor is located.
1812 DesktopToLayoutDeviceScale scale
=
1813 PresContext()->DeviceContext()->GetDesktopToDeviceScale();
1815 (mInContentShell
? aRootScreenRect
: aAnchorRect
) / scale
;
1816 int32_t width
= std::max(1, NSToIntRound(rect
.width
));
1817 int32_t height
= std::max(1, NSToIntRound(rect
.height
));
1818 sm
->ScreenForRect(rect
.x
, rect
.y
, width
, height
, getter_AddRefs(screen
));
1820 // Non-top-level popups (which will always be panels)
1821 // should never overlap the OS bar:
1822 bool dontOverlapOSBar
= aPopupLevel
!= ePopupLevelTop
;
1823 // get the total screen area if the popup is allowed to overlap it.
1824 if (!dontOverlapOSBar
&& mMenuCanOverlapOSBar
&& !mInContentShell
)
1825 screen
->GetRect(&screenRectPixels
.x
, &screenRectPixels
.y
,
1826 &screenRectPixels
.width
, &screenRectPixels
.height
);
1828 screen
->GetAvailRect(&screenRectPixels
.x
, &screenRectPixels
.y
,
1829 &screenRectPixels
.width
, &screenRectPixels
.height
);
1833 if (mInContentShell
) {
1834 // for content shells, clip to the client area rather than the screen area
1835 screenRectPixels
.IntersectRect(screenRectPixels
, aRootScreenRect
);
1836 } else if (!mOverrideConstraintRect
.IsEmpty()) {
1837 LayoutDeviceIntRect overrideConstrainRect
=
1838 LayoutDeviceIntRect::FromAppUnitsToNearest(
1839 mOverrideConstraintRect
, PresContext()->AppUnitsPerDevPixel());
1840 // This is currently only used for <select> elements where we want to
1841 // constrain vertically to the screen but not horizontally, so do the
1842 // intersection and then reset the horizontal values.
1843 screenRectPixels
.IntersectRect(screenRectPixels
, overrideConstrainRect
);
1844 screenRectPixels
.x
= overrideConstrainRect
.x
;
1845 screenRectPixels
.width
= overrideConstrainRect
.width
;
1848 return screenRectPixels
;
1851 void nsMenuPopupFrame::CanAdjustEdges(Side aHorizontalSide
, Side aVerticalSide
,
1852 LayoutDeviceIntPoint
& aChange
) {
1853 int8_t popupAlign(mPopupAlignment
);
1854 if (IsDirectionRTL()) {
1855 popupAlign
= -popupAlign
;
1858 if (aHorizontalSide
== (mHFlip
? eSideRight
: eSideLeft
)) {
1859 if (popupAlign
== POPUPALIGNMENT_TOPLEFT
||
1860 popupAlign
== POPUPALIGNMENT_BOTTOMLEFT
) {
1863 } else if (aHorizontalSide
== (mHFlip
? eSideLeft
: eSideRight
)) {
1864 if (popupAlign
== POPUPALIGNMENT_TOPRIGHT
||
1865 popupAlign
== POPUPALIGNMENT_BOTTOMRIGHT
) {
1870 if (aVerticalSide
== (mVFlip
? eSideBottom
: eSideTop
)) {
1871 if (popupAlign
== POPUPALIGNMENT_TOPLEFT
||
1872 popupAlign
== POPUPALIGNMENT_TOPRIGHT
) {
1875 } else if (aVerticalSide
== (mVFlip
? eSideTop
: eSideBottom
)) {
1876 if (popupAlign
== POPUPALIGNMENT_BOTTOMLEFT
||
1877 popupAlign
== POPUPALIGNMENT_BOTTOMRIGHT
) {
1883 ConsumeOutsideClicksResult
nsMenuPopupFrame::ConsumeOutsideClicks() {
1884 if (mContent
->AsElement()->AttrValueIs(kNameSpaceID_None
,
1885 nsGkAtoms::consumeoutsideclicks
,
1886 nsGkAtoms::_true
, eCaseMatters
)) {
1887 return ConsumeOutsideClicks_True
;
1889 if (mContent
->AsElement()->AttrValueIs(kNameSpaceID_None
,
1890 nsGkAtoms::consumeoutsideclicks
,
1891 nsGkAtoms::_false
, eCaseMatters
)) {
1892 return ConsumeOutsideClicks_ParentOnly
;
1894 if (mContent
->AsElement()->AttrValueIs(kNameSpaceID_None
,
1895 nsGkAtoms::consumeoutsideclicks
,
1896 nsGkAtoms::never
, eCaseMatters
)) {
1897 return ConsumeOutsideClicks_Never
;
1900 nsCOMPtr
<nsIContent
> parentContent
= mContent
->GetParent();
1901 if (parentContent
) {
1902 dom::NodeInfo
* ni
= parentContent
->NodeInfo();
1903 if (ni
->Equals(nsGkAtoms::menulist
, kNameSpaceID_XUL
)) {
1904 return ConsumeOutsideClicks_True
; // Consume outside clicks for combo
1905 // boxes on all platforms
1908 // Don't consume outside clicks for menus in Windows
1909 if (ni
->Equals(nsGkAtoms::menu
, kNameSpaceID_XUL
) ||
1910 ni
->Equals(nsGkAtoms::popupset
, kNameSpaceID_XUL
) ||
1911 ((ni
->Equals(nsGkAtoms::button
, kNameSpaceID_XUL
) ||
1912 ni
->Equals(nsGkAtoms::toolbarbutton
, kNameSpaceID_XUL
)) &&
1913 parentContent
->AsElement()->AttrValueIs(
1914 kNameSpaceID_None
, nsGkAtoms::type
, nsGkAtoms::menu
,
1916 return ConsumeOutsideClicks_Never
;
1921 return ConsumeOutsideClicks_True
;
1924 // XXXroc this is megalame. Fossicking around for a frame of the right
1925 // type is a recipe for disaster in the long term.
1926 nsIScrollableFrame
* nsMenuPopupFrame::GetScrollFrame(nsIFrame
* aStart
) {
1927 if (!aStart
) return nullptr;
1929 // try start frame and siblings
1930 nsIFrame
* currFrame
= aStart
;
1932 nsIScrollableFrame
* sf
= do_QueryFrame(currFrame
);
1934 currFrame
= currFrame
->GetNextSibling();
1935 } while (currFrame
);
1940 nsIFrame
* childFrame
= currFrame
->PrincipalChildList().FirstChild();
1941 nsIScrollableFrame
* sf
= GetScrollFrame(childFrame
);
1943 currFrame
= currFrame
->GetNextSibling();
1944 } while (currFrame
);
1949 void nsMenuPopupFrame::EnsureMenuItemIsVisible(nsMenuFrame
* aMenuItem
) {
1951 RefPtr
<mozilla::PresShell
> presShell
= aMenuItem
->PresShell();
1952 presShell
->ScrollFrameRectIntoView(
1953 aMenuItem
, nsRect(nsPoint(0, 0), aMenuItem
->GetRect().Size()),
1954 nsMargin(), ScrollAxis(), ScrollAxis(),
1955 ScrollFlags::ScrollOverflowHidden
|
1956 ScrollFlags::ScrollFirstAncestorOnly
);
1960 void nsMenuPopupFrame::ChangeByPage(bool aIsUp
) {
1961 // Only scroll by page within menulists.
1962 if (!IsMenuList()) {
1966 nsMenuFrame
* newMenu
= nullptr;
1967 nsIFrame
* currentMenu
= mCurrentMenu
;
1969 // If there is no current menu item, get the first item. When moving up,
1970 // just use this as the newMenu and leave currentMenu null so that no
1971 // check for a later element is performed. When moving down, set currentMenu
1972 // so that we look for one page down from the first item.
1973 newMenu
= nsXULPopupManager::GetNextMenuItem(this, nullptr, true, false);
1975 currentMenu
= newMenu
;
1980 nscoord scrollHeight
= mRect
.height
;
1981 nsIScrollableFrame
* scrollframe
= GetScrollFrame(this);
1983 scrollHeight
= scrollframe
->GetScrollPortRect().height
;
1986 // Get the position of the current item and add or subtract one popup's
1987 // height to or from it.
1988 nscoord targetPosition
= aIsUp
1989 ? currentMenu
->GetRect().YMost() - scrollHeight
1990 : currentMenu
->GetRect().y
+ scrollHeight
;
1992 // Indicates that the last visible child was a valid menuitem.
1993 bool lastWasValid
= false;
1995 // Look for the next child which is just past the target position. This
1996 // child will need to be selected.
1997 while (currentMenu
) {
1998 // Only consider menu frames.
1999 nsMenuFrame
* menuFrame
= do_QueryFrame(currentMenu
);
2001 nsXULPopupManager::IsValidMenuItem(menuFrame
->GetContent(), true)) {
2002 // If the right position was found, break out. Otherwise, look for
2004 if ((!aIsUp
&& currentMenu
->GetRect().YMost() > targetPosition
) ||
2005 (aIsUp
&& currentMenu
->GetRect().y
< targetPosition
)) {
2006 // If the last visible child was not a valid menuitem or was disabled,
2007 // use this as the menu to select, skipping over any non-valid items
2008 // at the edge of the page.
2009 if (!lastWasValid
) {
2010 newMenu
= menuFrame
;
2016 // Assign this item to newMenu. This item will be selected in case we
2017 // don't find any more.
2018 lastWasValid
= true;
2019 newMenu
= menuFrame
;
2021 lastWasValid
= false;
2025 aIsUp
? currentMenu
->GetPrevSibling() : currentMenu
->GetNextSibling();
2029 // Select the new menuitem.
2031 ChangeMenuItem(newMenu
, false, true);
2035 NS_IMETHODIMP
nsMenuPopupFrame::SetCurrentMenuItem(nsMenuFrame
* aMenuItem
) {
2036 if (mCurrentMenu
== aMenuItem
) return NS_OK
;
2039 mCurrentMenu
->SelectMenu(false);
2043 EnsureMenuItemIsVisible(aMenuItem
);
2044 aMenuItem
->SelectMenu(true);
2047 mCurrentMenu
= aMenuItem
;
2052 void nsMenuPopupFrame::CurrentMenuIsBeingDestroyed() { mCurrentMenu
= nullptr; }
2055 nsMenuPopupFrame::ChangeMenuItem(nsMenuFrame
* aMenuItem
, bool aSelectFirstItem
,
2057 if (mCurrentMenu
== aMenuItem
) return NS_OK
;
2059 // When a context menu is open, the current menu is locked, and no change
2060 // to the menu is allowed.
2061 nsXULPopupManager
* pm
= nsXULPopupManager::GetInstance();
2062 if (!mIsContextMenu
&& pm
&& pm
->HasContextMenu(this)) return NS_OK
;
2064 // Unset the current child.
2066 mCurrentMenu
->SelectMenu(false);
2067 nsMenuPopupFrame
* popup
= mCurrentMenu
->GetPopup();
2069 if (mCurrentMenu
->IsOpen()) {
2070 if (pm
) pm
->HidePopupAfterDelay(popup
);
2075 // Set the new child.
2077 EnsureMenuItemIsVisible(aMenuItem
);
2078 aMenuItem
->SelectMenu(true);
2080 // On Windows, a menulist should update its value whenever navigation was
2081 // done by the keyboard.
2083 if (aFromKey
&& IsOpen() && IsMenuList()) {
2084 // Fire a command event as the new item, but we don't want to close
2085 // the menu, blink it, or update any other state of the menuitem. The
2086 // command event will cause the item to be selected.
2087 nsCOMPtr
<nsIContent
> menuItemContent
= aMenuItem
->GetContent();
2088 RefPtr
<mozilla::PresShell
> presShell
= PresShell();
2089 nsContentUtils::DispatchXULCommand(menuItemContent
, /* aTrusted = */ true,
2090 nullptr, presShell
, false, false,
2096 mCurrentMenu
= aMenuItem
;
2101 nsMenuFrame
* nsMenuPopupFrame::Enter(WidgetGUIEvent
* aEvent
) {
2102 mIncrementalString
.Truncate();
2104 // Give it to the child.
2105 if (mCurrentMenu
) return mCurrentMenu
->Enter(aEvent
);
2110 nsMenuFrame
* nsMenuPopupFrame::FindMenuWithShortcut(KeyboardEvent
* aKeyEvent
,
2112 uint32_t charCode
= aKeyEvent
->CharCode();
2113 uint32_t keyCode
= aKeyEvent
->KeyCode();
2117 // Enumerate over our list of frames.
2118 nsContainerFrame
* immediateParent
=
2119 nsXULPopupManager::ImmediateParentFrame(this);
2120 uint32_t matchCount
= 0, matchShortcutCount
= 0;
2121 bool foundActive
= false;
2122 nsMenuFrame
* frameBefore
= nullptr;
2123 nsMenuFrame
* frameAfter
= nullptr;
2124 nsMenuFrame
* frameShortcut
= nullptr;
2126 nsIContent
* parentContent
= mContent
->GetParent();
2128 bool isMenu
= parentContent
&& !parentContent
->NodeInfo()->Equals(
2129 nsGkAtoms::menulist
, kNameSpaceID_XUL
);
2131 DOMTimeStamp keyTime
= aKeyEvent
->TimeStamp();
2133 if (charCode
== 0) {
2134 if (keyCode
== dom::KeyboardEvent_Binding::DOM_VK_BACK_SPACE
) {
2135 if (!isMenu
&& !mIncrementalString
.IsEmpty()) {
2136 mIncrementalString
.SetLength(mIncrementalString
.Length() - 1);
2140 nsCOMPtr
<nsISound
> soundInterface
= do_GetService("@mozilla.org/sound;1");
2141 if (soundInterface
) soundInterface
->Beep();
2142 #endif // #ifdef XP_WIN
2146 char16_t uniChar
= ToLowerCase(static_cast<char16_t
>(charCode
));
2148 // Menu supports only first-letter navigation
2149 mIncrementalString
= uniChar
;
2150 } else if (IsWithinIncrementalTime(keyTime
)) {
2151 mIncrementalString
.Append(uniChar
);
2153 // Interval too long, treat as new typing
2154 mIncrementalString
= uniChar
;
2157 // See bug 188199 & 192346, if all letters in incremental string are same,
2158 // just try to match the first one
2159 nsAutoString
incrementalString(mIncrementalString
);
2160 uint32_t charIndex
= 1, stringLength
= incrementalString
.Length();
2161 while (charIndex
< stringLength
&&
2162 incrementalString
[charIndex
] == incrementalString
[charIndex
- 1]) {
2165 if (charIndex
== stringLength
) {
2166 incrementalString
.Truncate(1);
2170 sLastKeyTime
= keyTime
;
2172 // NOTE: If you crashed here due to a bogus |immediateParent| it is
2173 // possible that the menu whose shortcut is being looked up has
2174 // been destroyed already. One strategy would be to
2175 // setTimeout(<func>,0) as detailed in:
2176 // <http://bugzilla.mozilla.org/show_bug.cgi?id=126675#c32>
2177 nsIFrame
* firstMenuItem
=
2178 nsXULPopupManager::GetNextMenuItem(immediateParent
, nullptr, true, false);
2179 nsIFrame
* currFrame
= firstMenuItem
;
2181 int32_t menuAccessKey
= -1;
2182 nsMenuBarListener::GetMenuAccessKey(&menuAccessKey
);
2184 // We start searching from first child. This process is divided into two parts
2185 // -- before current and after current -- by the current item
2187 nsIContent
* current
= currFrame
->GetContent();
2188 nsAutoString textKey
;
2189 bool isShortcut
= false;
2190 if (current
->IsElement()) {
2191 if (menuAccessKey
>= 0) {
2192 // Get the shortcut attribute.
2193 current
->AsElement()->GetAttr(kNameSpaceID_None
, nsGkAtoms::accesskey
,
2196 isShortcut
= !textKey
.IsEmpty();
2197 if (textKey
.IsEmpty()) { // No shortcut, try first letter
2198 current
->AsElement()->GetAttr(kNameSpaceID_None
, nsGkAtoms::label
,
2200 if (textKey
.IsEmpty()) // No label, try another attribute (value)
2201 current
->AsElement()->GetAttr(kNameSpaceID_None
, nsGkAtoms::value
,
2206 if (StringBeginsWith(
2207 nsContentUtils::TrimWhitespace
<
2208 nsContentUtils::IsHTMLWhitespaceOrNBSP
>(textKey
, false),
2209 incrementalString
, nsCaseInsensitiveStringComparator
)) {
2210 // mIncrementalString is a prefix of textKey
2211 nsMenuFrame
* menu
= do_QueryFrame(currFrame
);
2213 // There is one match
2216 // There is one shortcut-key match
2217 matchShortcutCount
++;
2218 // Record the matched item. If there is only one matched shortcut
2220 frameShortcut
= menu
;
2223 // It's a first candidate item located before/on the current item
2224 if (!frameBefore
) frameBefore
= menu
;
2226 // It's a first candidate item located after the current item
2227 if (!frameAfter
) frameAfter
= menu
;
2233 // Get the active status
2234 if (current
->IsElement() && current
->AsElement()->AttrValueIs(
2235 kNameSpaceID_None
, nsGkAtoms::menuactive
,
2236 nsGkAtoms::_true
, eCaseMatters
)) {
2238 if (stringLength
> 1) {
2239 // If there is more than one char typed, the current item has highest
2241 // otherwise the item next to current has highest priority
2242 if (currFrame
== frameBefore
) return frameBefore
;
2246 nsMenuFrame
* menu
= do_QueryFrame(currFrame
);
2248 nsXULPopupManager::GetNextMenuItem(immediateParent
, menu
, true, true);
2249 if (currFrame
== firstMenuItem
) break;
2252 doAction
= (isMenu
&& (matchCount
== 1 || matchShortcutCount
== 1));
2254 if (matchShortcutCount
== 1) // We have one matched shortcut item
2255 return frameShortcut
;
2256 if (frameAfter
) // If we have matched item after the current, use it
2258 else if (frameBefore
) // If we haven't, use the item before the current
2261 // If we don't match anything, rollback the last typing
2262 mIncrementalString
.SetLength(mIncrementalString
.Length() - 1);
2264 // didn't find a matching menu item
2266 // behavior on Windows - this item is in a menu popup off of the
2267 // menu bar, so beep and do nothing else
2269 nsCOMPtr
<nsISound
> soundInterface
= do_GetService("@mozilla.org/sound;1");
2270 if (soundInterface
) soundInterface
->Beep();
2272 #endif // #ifdef XP_WIN
2277 void nsMenuPopupFrame::LockMenuUntilClosed(bool aLock
) {
2278 mIsMenuLocked
= aLock
;
2280 // Lock / unlock the parent, too.
2281 nsMenuFrame
* menu
= do_QueryFrame(GetParent());
2283 nsMenuParent
* parentParent
= menu
->GetMenuParent();
2285 parentParent
->LockMenuUntilClosed(aLock
);
2290 nsIWidget
* nsMenuPopupFrame::GetWidget() const {
2291 return mView
? mView
->GetWidget() : nullptr;
2294 // helpers /////////////////////////////////////////////////////////////
2296 nsresult
nsMenuPopupFrame::AttributeChanged(int32_t aNameSpaceID
,
2302 nsBoxFrame::AttributeChanged(aNameSpaceID
, aAttribute
, aModType
);
2304 if (aAttribute
== nsGkAtoms::left
|| aAttribute
== nsGkAtoms::top
) {
2305 MoveToAttributePosition();
2308 if (aAttribute
== nsGkAtoms::remote
) {
2309 // When the remote attribute changes, we need to create a new widget to
2310 // ensure that it has the correct compositor and transparency settings to
2311 // match the new value.
2312 PrepareWidget(true);
2315 if (aAttribute
== nsGkAtoms::followanchor
) {
2316 nsXULPopupManager
* pm
= nsXULPopupManager::GetInstance();
2318 pm
->UpdateFollowAnchor(this);
2322 if (aAttribute
== nsGkAtoms::label
) {
2323 // set the label for the titlebar
2324 nsView
* view
= GetView();
2326 nsIWidget
* widget
= view
->GetWidget();
2329 mContent
->AsElement()->GetAttr(kNameSpaceID_None
, nsGkAtoms::label
,
2331 if (!title
.IsEmpty()) {
2332 widget
->SetTitle(title
);
2336 } else if (aAttribute
== nsGkAtoms::ignorekeys
) {
2337 nsXULPopupManager
* pm
= nsXULPopupManager::GetInstance();
2339 nsAutoString ignorekeys
;
2340 mContent
->AsElement()->GetAttr(kNameSpaceID_None
, nsGkAtoms::ignorekeys
,
2342 pm
->UpdateIgnoreKeys(ignorekeys
.EqualsLiteral("true"));
2349 void nsMenuPopupFrame::MoveToAttributePosition() {
2350 // Move the widget around when the user sets the |left| and |top| attributes.
2351 // Note that this is not the best way to move the widget, as it results in
2352 // lots of FE notifications and is likely to be slow as molasses. Use |moveTo|
2353 // on the element if possible.
2354 nsAutoString left
, top
;
2355 mContent
->AsElement()->GetAttr(kNameSpaceID_None
, nsGkAtoms::left
, left
);
2356 mContent
->AsElement()->GetAttr(kNameSpaceID_None
, nsGkAtoms::top
, top
);
2357 nsresult err1
, err2
;
2358 mozilla::CSSIntPoint
pos(left
.ToInteger(&err1
), top
.ToInteger(&err2
));
2360 if (NS_SUCCEEDED(err1
) && NS_SUCCEEDED(err2
)) MoveTo(pos
, false);
2362 PresShell()->FrameNeedsReflow(this, IntrinsicDirty::StyleChange
,
2366 void nsMenuPopupFrame::DestroyFrom(nsIFrame
* aDestructRoot
,
2367 PostDestroyData
& aPostDestroyData
) {
2368 if (mReflowCallbackData
.mPosted
) {
2369 PresShell()->CancelReflowCallback(this);
2370 mReflowCallbackData
.Clear();
2373 nsMenuFrame
* menu
= do_QueryFrame(GetParent());
2375 // clear the open attribute on the parent menu
2376 nsContentUtils::AddScriptRunner(new nsUnsetAttrRunnable(
2377 menu
->GetContent()->AsElement(), nsGkAtoms::open
));
2380 ClearPopupShownDispatcher();
2382 nsXULPopupManager
* pm
= nsXULPopupManager::GetInstance();
2383 if (pm
) pm
->PopupDestroyed(this);
2385 nsIPopupContainer
* popupContainer
=
2386 nsIPopupContainer::GetPopupContainer(PresShell());
2387 if (popupContainer
&& popupContainer
->GetDefaultTooltip() == mContent
) {
2388 popupContainer
->SetDefaultTooltip(nullptr);
2391 nsBoxFrame::DestroyFrom(aDestructRoot
, aPostDestroyData
);
2394 nsMargin
nsMenuPopupFrame::GetMargin() const {
2396 StyleMargin()->GetMargin(margin
);
2397 if (mIsTopLevelContextMenu
) {
2398 const CSSIntPoint
offset(
2399 LookAndFeel::GetInt(LookAndFeel::IntID::ContextMenuOffsetHorizontal
),
2400 LookAndFeel::GetInt(LookAndFeel::IntID::ContextMenuOffsetVertical
));
2401 auto auOffset
= CSSIntPoint::ToAppUnits(offset
);
2402 margin
.top
+= auOffset
.y
;
2403 margin
.bottom
+= auOffset
.y
;
2404 margin
.left
+= auOffset
.x
;
2405 margin
.right
+= auOffset
.x
;
2410 void nsMenuPopupFrame::MoveTo(const CSSPoint
& aPos
, bool aUpdateAttrs
,
2411 bool aByMoveToRect
) {
2412 nsIWidget
* widget
= GetWidget();
2413 nsPoint appUnitsPos
= CSSPixel::ToAppUnits(aPos
);
2415 // reposition the popup at the specified coordinates. Don't clear the anchor
2416 // and position, because the popup can be reset to its anchor position by
2417 // using (-1, -1) as coordinates.
2419 // Subtract off the margin as it will be added to the position when
2420 // SetPopupPosition is called.
2422 nsMargin margin
= GetMargin();
2423 if (mIsContextMenu
&& IsDirectionRTL()) {
2424 appUnitsPos
.x
+= margin
.right
+ mRect
.Width();
2426 appUnitsPos
.x
-= margin
.left
;
2428 appUnitsPos
.y
-= margin
.top
;
2431 if ((mScreenRect
.x
== appUnitsPos
.x
&& mScreenRect
.y
== appUnitsPos
.y
) &&
2432 (!widget
|| widget
->GetClientOffset() == mLastClientOffset
)) {
2436 mPositionedByMoveToRect
= aByMoveToRect
;
2437 mScreenRect
.MoveTo(appUnitsPos
);
2438 if (mAnchorType
== MenuPopupAnchorType_Rect
) {
2439 // This ensures that the anchor width is still honored, to prevent it from
2440 // changing spuriously.
2441 mScreenRect
.height
= 0;
2443 mAnchorType
= MenuPopupAnchorType_Point
;
2446 SetPopupPosition(nullptr, true, mSizedToPopup
);
2448 RefPtr
<Element
> popup
= mContent
->AsElement();
2449 if (aUpdateAttrs
&& (popup
->HasAttr(kNameSpaceID_None
, nsGkAtoms::left
) ||
2450 popup
->HasAttr(kNameSpaceID_None
, nsGkAtoms::top
))) {
2451 nsAutoString left
, top
;
2452 left
.AppendInt(RoundedToInt(aPos
).x
);
2453 top
.AppendInt(RoundedToInt(aPos
).y
);
2454 popup
->SetAttr(kNameSpaceID_None
, nsGkAtoms::left
, left
, false);
2455 popup
->SetAttr(kNameSpaceID_None
, nsGkAtoms::top
, top
, false);
2459 void nsMenuPopupFrame::MoveToAnchor(nsIContent
* aAnchorContent
,
2460 const nsAString
& aPosition
, int32_t aXPos
,
2461 int32_t aYPos
, bool aAttributesOverride
) {
2462 NS_ASSERTION(IsVisible(), "popup must be visible to move it");
2464 nsPopupState oldstate
= mPopupState
;
2465 InitializePopup(aAnchorContent
, mTriggerContent
, aPosition
, aXPos
, aYPos
,
2466 MenuPopupAnchorType_Node
, aAttributesOverride
);
2467 // InitializePopup changed the state so reset it.
2468 mPopupState
= oldstate
;
2470 // Pass false here so that flipping and adjusting to fit on the screen happen.
2471 SetPopupPosition(nullptr, false, false);
2474 bool nsMenuPopupFrame::GetAutoPosition() { return mShouldAutoPosition
; }
2476 void nsMenuPopupFrame::SetAutoPosition(bool aShouldAutoPosition
) {
2477 mShouldAutoPosition
= aShouldAutoPosition
;
2478 nsXULPopupManager
* pm
= nsXULPopupManager::GetInstance();
2480 pm
->UpdateFollowAnchor(this);
2484 int8_t nsMenuPopupFrame::GetAlignmentPosition() const {
2485 // The code below handles most cases of alignment, anchor and position values.
2486 // Those that are not handled just return POPUPPOSITION_UNKNOWN.
2488 if (mPosition
== POPUPPOSITION_OVERLAP
||
2489 mPosition
== POPUPPOSITION_AFTERPOINTER
||
2490 mPosition
== POPUPPOSITION_SELECTION
) {
2494 int8_t position
= mPosition
;
2496 if (position
== POPUPPOSITION_UNKNOWN
) {
2497 switch (mPopupAnchor
) {
2498 case POPUPALIGNMENT_BOTTOMRIGHT
:
2499 case POPUPALIGNMENT_BOTTOMLEFT
:
2500 case POPUPALIGNMENT_BOTTOMCENTER
:
2501 position
= mPopupAlignment
== POPUPALIGNMENT_TOPRIGHT
2502 ? POPUPPOSITION_AFTEREND
2503 : POPUPPOSITION_AFTERSTART
;
2505 case POPUPALIGNMENT_TOPRIGHT
:
2506 case POPUPALIGNMENT_TOPLEFT
:
2507 case POPUPALIGNMENT_TOPCENTER
:
2508 position
= mPopupAlignment
== POPUPALIGNMENT_BOTTOMRIGHT
2509 ? POPUPPOSITION_BEFOREEND
2510 : POPUPPOSITION_BEFORESTART
;
2512 case POPUPALIGNMENT_LEFTCENTER
:
2513 position
= mPopupAlignment
== POPUPALIGNMENT_BOTTOMRIGHT
2514 ? POPUPPOSITION_STARTAFTER
2515 : POPUPPOSITION_STARTBEFORE
;
2517 case POPUPALIGNMENT_RIGHTCENTER
:
2518 position
= mPopupAlignment
== POPUPALIGNMENT_BOTTOMLEFT
2519 ? POPUPPOSITION_ENDAFTER
2520 : POPUPPOSITION_ENDBEFORE
;
2528 position
= POPUPPOSITION_HFLIP(position
);
2532 position
= POPUPPOSITION_VFLIP(position
);
2539 * KEEP THIS IN SYNC WITH nsIFrame::CreateView
2540 * as much as possible. Until we get rid of views finally...
2542 void nsMenuPopupFrame::CreatePopupView() {
2547 nsViewManager
* viewManager
= PresContext()->GetPresShell()->GetViewManager();
2548 NS_ASSERTION(nullptr != viewManager
, "null view manager");
2551 nsView
* parentView
= viewManager
->GetRootView();
2552 nsViewVisibility visibility
= nsViewVisibility_kHide
;
2554 NS_ASSERTION(parentView
, "no parent view");
2557 nsView
* view
= viewManager
->CreateView(GetRect(), parentView
, visibility
);
2558 auto zIndex
= ZIndex();
2559 viewManager
->SetViewZIndex(view
, zIndex
.isNothing(), zIndex
.valueOr(0));
2560 // XXX put view last in document order until we can do better
2561 viewManager
->InsertChild(parentView
, view
, nullptr, true);
2563 // Remember our view
2567 NS_FRAME_TRACE_CALLS
,
2568 ("nsMenuPopupFrame::CreatePopupView: frame=%p view=%p", this, view
));
2571 bool nsMenuPopupFrame::ShouldFollowAnchor() {
2572 if (!mShouldAutoPosition
|| mAnchorType
!= MenuPopupAnchorType_Node
||
2577 // Follow anchor mode is used when followanchor="true" is set or for arrow
2579 if (mContent
->AsElement()->AttrValueIs(kNameSpaceID_None
,
2580 nsGkAtoms::followanchor
,
2581 nsGkAtoms::_true
, eCaseMatters
)) {
2585 if (mContent
->AsElement()->AttrValueIs(kNameSpaceID_None
,
2586 nsGkAtoms::followanchor
,
2587 nsGkAtoms::_false
, eCaseMatters
)) {
2591 return (mPopupType
== ePopupTypePanel
&&
2592 mContent
->AsElement()->AttrValueIs(kNameSpaceID_None
, nsGkAtoms::type
,
2593 nsGkAtoms::arrow
, eCaseMatters
));
2596 bool nsMenuPopupFrame::ShouldFollowAnchor(nsRect
& aRect
) {
2597 if (!ShouldFollowAnchor()) {
2601 nsIFrame
* anchorFrame
= mAnchorContent
->GetPrimaryFrame();
2603 nsPresContext
* rootPresContext
= PresContext()->GetRootPresContext();
2604 if (rootPresContext
) {
2605 aRect
= ComputeAnchorRect(rootPresContext
, anchorFrame
);
2612 void nsMenuPopupFrame::CheckForAnchorChange(nsRect
& aRect
) {
2613 // Don't update if the popup isn't visible or we shouldn't be following the
2615 if (!IsVisible() || !ShouldFollowAnchor()) {
2619 bool shouldHide
= false;
2621 nsPresContext
* rootPresContext
= PresContext()->GetRootPresContext();
2623 // If the frame for the anchor has gone away, hide the popup.
2624 nsIFrame
* anchor
= mAnchorContent
->GetPrimaryFrame();
2625 if (!anchor
|| !rootPresContext
) {
2627 } else if (!anchor
->IsVisibleConsideringAncestors(
2628 VISIBILITY_CROSS_CHROME_CONTENT_BOUNDARY
)) {
2629 // If the anchor is now inside something that is invisible, hide the popup.
2632 // If the anchor is now inside a hidden parent popup, hide the popup.
2633 nsIFrame
* frame
= anchor
;
2635 nsMenuPopupFrame
* popup
= do_QueryFrame(frame
);
2636 if (popup
&& popup
->PopupState() != ePopupShown
) {
2641 frame
= frame
->GetParent();
2646 nsXULPopupManager
* pm
= nsXULPopupManager::GetInstance();
2648 // As the caller will be iterating over the open popups, hide
2650 pm
->HidePopup(mContent
, false, true, true, false);
2656 nsRect anchorRect
= ComputeAnchorRect(rootPresContext
, anchor
);
2658 // If the rectangles are different, move the popup.
2659 if (!anchorRect
.IsEqualEdges(aRect
)) {
2661 SetPopupPosition(nullptr, true, false);
2665 nsIWidget
* nsMenuPopupFrame::GetParentMenuWidget() {
2666 nsMenuFrame
* menuFrame
= do_QueryFrame(GetParent());
2668 nsMenuParent
* parentPopup
= menuFrame
->GetMenuParent();
2669 if (parentPopup
&& (parentPopup
->IsMenu() || parentPopup
->IsMenuBar())) {
2670 return static_cast<nsMenuPopupFrame
*>(parentPopup
)->GetWidget();