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/. */
8 #include "nsIContent.h"
9 #include "nsNameSpaceManager.h"
10 #include "nsGkAtoms.h"
11 #include "nsMenuPopupFrame.h"
13 #include "mozilla/AppUnits.h"
14 #include "mozilla/dom/DOMRect.h"
15 #include "mozilla/dom/Document.h"
16 #include "mozilla/dom/Element.h"
17 #include "mozilla/dom/Event.h"
18 #include "mozilla/dom/XULPopupElement.h"
19 #include "mozilla/dom/XULPopupElementBinding.h"
21 namespace mozilla::dom
{
23 nsXULElement
* NS_NewXULPopupElement(
24 already_AddRefed
<mozilla::dom::NodeInfo
>&& aNodeInfo
) {
25 RefPtr
<mozilla::dom::NodeInfo
> nodeInfo(aNodeInfo
);
26 auto* nim
= nodeInfo
->NodeInfoManager();
27 return new (nim
) XULPopupElement(nodeInfo
.forget());
30 JSObject
* XULPopupElement::WrapNode(JSContext
* aCx
,
31 JS::Handle
<JSObject
*> aGivenProto
) {
32 return XULPopupElement_Binding::Wrap(aCx
, this, aGivenProto
);
35 void XULPopupElement::OpenPopup(Element
* aAnchorElement
,
36 const StringOrOpenPopupOptions
& aOptions
,
37 int32_t aXPos
, int32_t aYPos
,
38 bool aIsContextMenu
, bool aAttributesOverride
,
39 Event
* aTriggerEvent
) {
40 nsAutoString position
;
41 if (aOptions
.IsOpenPopupOptions()) {
42 const OpenPopupOptions
& options
= aOptions
.GetAsOpenPopupOptions();
43 position
= options
.mPosition
;
46 aIsContextMenu
= options
.mIsContextMenu
;
47 aAttributesOverride
= options
.mAttributesOverride
;
48 aTriggerEvent
= options
.mTriggerEvent
;
50 position
= aOptions
.GetAsString();
53 nsXULPopupManager
* pm
= nsXULPopupManager::GetInstance();
55 // As a special case for popups that are menus when no anchor or position
56 // are specified, open the popup with ShowMenu instead of ShowPopup so that
57 // the popup is aligned with the menu.
58 if (!aAnchorElement
&& position
.IsEmpty() && GetPrimaryFrame()) {
59 nsMenuFrame
* menu
= do_QueryFrame(GetPrimaryFrame()->GetParent());
61 pm
->ShowMenu(menu
->GetContent(), false);
66 pm
->ShowPopup(this, aAnchorElement
, position
, aXPos
, aYPos
, aIsContextMenu
,
67 aAttributesOverride
, false, aTriggerEvent
);
71 void XULPopupElement::OpenPopupAtScreen(int32_t aXPos
, int32_t aYPos
,
73 Event
* aTriggerEvent
) {
74 nsXULPopupManager
* pm
= nsXULPopupManager::GetInstance();
76 pm
->ShowPopupAtScreen(this, aXPos
, aYPos
, aIsContextMenu
, aTriggerEvent
);
80 void XULPopupElement::OpenPopupAtScreenRect(const nsAString
& aPosition
,
81 int32_t aXPos
, int32_t aYPos
,
82 int32_t aWidth
, int32_t aHeight
,
84 bool aAttributesOverride
,
85 Event
* aTriggerEvent
) {
86 nsXULPopupManager
* pm
= nsXULPopupManager::GetInstance();
88 pm
->ShowPopupAtScreenRect(
89 this, aPosition
, nsIntRect(aXPos
, aYPos
, aWidth
, aHeight
),
90 aIsContextMenu
, aAttributesOverride
, aTriggerEvent
);
94 void XULPopupElement::HidePopup(bool aCancel
) {
95 nsXULPopupManager
* pm
= nsXULPopupManager::GetInstance();
97 pm
->HidePopup(this, false, true, false, aCancel
);
101 static Modifiers
ConvertModifiers(const ActivateMenuItemOptions
& aModifiers
) {
102 Modifiers modifiers
= 0;
103 if (aModifiers
.mCtrlKey
) {
104 modifiers
|= MODIFIER_CONTROL
;
106 if (aModifiers
.mAltKey
) {
107 modifiers
|= MODIFIER_ALT
;
109 if (aModifiers
.mShiftKey
) {
110 modifiers
|= MODIFIER_SHIFT
;
112 if (aModifiers
.mMetaKey
) {
113 modifiers
|= MODIFIER_META
;
118 void XULPopupElement::ActivateItem(Element
& aItemElement
,
119 const ActivateMenuItemOptions
& aOptions
,
121 if (!Contains(&aItemElement
)) {
122 return aRv
.ThrowInvalidStateError("Menu item is not inside this menu.");
125 Modifiers modifiers
= ConvertModifiers(aOptions
);
127 // First, check if a native menu is open, and activate the item in it.
128 if (nsXULPopupManager
* pm
= nsXULPopupManager::GetInstance()) {
129 if (pm
->ActivateNativeMenuItem(&aItemElement
, modifiers
, aOptions
.mButton
,
135 // Used only to flush frames.
136 GetPrimaryFrame(FlushType::Frames
);
138 nsMenuFrame
* itemFrame
= do_QueryFrame(aItemElement
.GetPrimaryFrame());
140 return aRv
.ThrowInvalidStateError("Menu item is not visible");
143 if (!itemFrame
->GetMenuParent() || !itemFrame
->GetMenuParent()->IsOpen()) {
144 return aRv
.ThrowInvalidStateError("Menu is closed");
147 itemFrame
->ActivateItem(modifiers
, aOptions
.mButton
);
150 void XULPopupElement::MoveTo(int32_t aLeft
, int32_t aTop
) {
151 nsMenuPopupFrame
* menuPopupFrame
= do_QueryFrame(GetPrimaryFrame());
152 if (menuPopupFrame
) {
153 menuPopupFrame
->MoveTo(CSSIntPoint(aLeft
, aTop
), true);
157 void XULPopupElement::MoveToAnchor(Element
* aAnchorElement
,
158 const nsAString
& aPosition
, int32_t aXPos
,
159 int32_t aYPos
, bool aAttributesOverride
) {
160 nsMenuPopupFrame
* menuPopupFrame
= do_QueryFrame(GetPrimaryFrame());
161 if (menuPopupFrame
&& menuPopupFrame
->IsVisibleOrShowing()) {
162 menuPopupFrame
->MoveToAnchor(aAnchorElement
, aPosition
, aXPos
, aYPos
,
163 aAttributesOverride
);
167 void XULPopupElement::SizeTo(int32_t aWidth
, int32_t aHeight
) {
168 nsAutoString width
, height
;
169 width
.AppendInt(aWidth
);
170 height
.AppendInt(aHeight
);
172 nsCOMPtr
<nsIContent
> kungFuDeathGrip
= this; // keep a reference
174 // We only want to pass aNotify=true to SetAttr once, but must make sure
175 // we pass it when a value is being changed. Thus, we check if the height
176 // is the same and if so, pass true when setting the width.
178 AttrValueIs(kNameSpaceID_None
, nsGkAtoms::height
, height
, eCaseMatters
);
180 SetAttr(kNameSpaceID_None
, nsGkAtoms::width
, width
, heightSame
);
181 SetAttr(kNameSpaceID_None
, nsGkAtoms::height
, height
, true);
183 // If the popup is open, force a reposition of the popup after resizing it
184 // with notifications set to true so that the popuppositioned event is fired.
185 nsMenuPopupFrame
* menuPopupFrame
= do_QueryFrame(GetPrimaryFrame());
186 if (menuPopupFrame
&& menuPopupFrame
->PopupState() == ePopupShown
) {
187 menuPopupFrame
->SetPopupPosition(nullptr, false, false);
191 bool XULPopupElement::AutoPosition() {
192 nsMenuPopupFrame
* menuPopupFrame
= do_QueryFrame(GetPrimaryFrame());
193 if (menuPopupFrame
) {
194 return menuPopupFrame
->GetAutoPosition();
199 void XULPopupElement::SetAutoPosition(bool aShouldAutoPosition
) {
200 nsMenuPopupFrame
* menuPopupFrame
= do_QueryFrame(GetPrimaryFrame());
201 if (menuPopupFrame
) {
202 menuPopupFrame
->SetAutoPosition(aShouldAutoPosition
);
206 void XULPopupElement::GetState(nsString
& aState
) {
207 // set this here in case there's no frame for the popup
208 aState
.AssignLiteral("closed");
210 if (nsXULPopupManager
* pm
= nsXULPopupManager::GetInstance()) {
211 switch (pm
->GetPopupState(this)) {
213 aState
.AssignLiteral("open");
216 case ePopupPositioning
:
219 aState
.AssignLiteral("showing");
222 case ePopupInvisible
:
223 aState
.AssignLiteral("hiding");
228 MOZ_ASSERT_UNREACHABLE("Bad popup state");
234 nsINode
* XULPopupElement::GetTriggerNode() const {
235 nsMenuPopupFrame
* menuPopupFrame
= do_QueryFrame(GetPrimaryFrame());
236 return nsMenuPopupFrame::GetTriggerContent(menuPopupFrame
);
239 // FIXME(emilio): should probably be renamed to GetAnchorElement?
240 Element
* XULPopupElement::GetAnchorNode() const {
241 nsMenuPopupFrame
* menuPopupFrame
= do_QueryFrame(GetPrimaryFrame());
242 if (!menuPopupFrame
) {
245 return Element::FromNodeOrNull(menuPopupFrame
->GetAnchor());
248 already_AddRefed
<DOMRect
> XULPopupElement::GetOuterScreenRect() {
249 RefPtr
<DOMRect
> rect
= new DOMRect(ToSupports(OwnerDoc()));
251 // Return an empty rectangle if the popup is not open.
252 nsMenuPopupFrame
* menuPopupFrame
=
253 do_QueryFrame(GetPrimaryFrame(FlushType::Frames
));
254 if (!menuPopupFrame
|| !menuPopupFrame
->IsOpen()) {
255 return rect
.forget();
258 Maybe
<CSSRect
> screenRect
;
260 if (menuPopupFrame
->IsNativeMenu()) {
261 // For native menus we can't query the true size. Use the anchor rect
262 // instead, which at least has the position at which we were intending to
264 screenRect
= Some(CSSRect(menuPopupFrame
->GetScreenAnchorRect()));
266 // For non-native menus, query the bounds from the widget.
267 if (nsView
* view
= menuPopupFrame
->GetView()) {
268 if (nsIWidget
* widget
= view
->GetWidget()) {
269 screenRect
= Some(widget
->GetScreenBounds() /
270 menuPopupFrame
->PresContext()->CSSToDevPixelScale());
276 rect
->SetRect(screenRect
->X(), screenRect
->Y(), screenRect
->Width(),
277 screenRect
->Height());
279 return rect
.forget();
282 void XULPopupElement::SetConstraintRect(dom::DOMRectReadOnly
& aRect
) {
283 nsMenuPopupFrame
* menuPopupFrame
=
284 do_QueryFrame(GetPrimaryFrame(FlushType::Frames
));
285 if (menuPopupFrame
) {
286 menuPopupFrame
->SetOverrideConstraintRect(LayoutDeviceIntRect::Truncate(
287 aRect
.Left(), aRect
.Top(), aRect
.Width(), aRect
.Height()));
291 } // namespace mozilla::dom