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 "XULMenuParentElement.h"
9 #include "nsICSSDeclaration.h"
10 #include "nsIContent.h"
11 #include "nsNameSpaceManager.h"
12 #include "nsGkAtoms.h"
13 #include "nsMenuPopupFrame.h"
14 #include "nsStringFwd.h"
16 #include "mozilla/AppUnits.h"
17 #include "mozilla/AsyncEventDispatcher.h"
18 #include "mozilla/dom/DOMRect.h"
19 #include "mozilla/dom/Document.h"
20 #include "mozilla/dom/Element.h"
21 #include "mozilla/dom/Event.h"
22 #include "mozilla/dom/XULPopupElement.h"
23 #include "mozilla/dom/XULButtonElement.h"
24 #include "mozilla/dom/XULMenuElement.h"
25 #include "mozilla/dom/XULPopupElementBinding.h"
27 # include "mozilla/WidgetUtilsGtk.h"
30 namespace mozilla::dom
{
32 nsXULElement
* NS_NewXULPopupElement(
33 already_AddRefed
<mozilla::dom::NodeInfo
>&& aNodeInfo
) {
34 RefPtr
<mozilla::dom::NodeInfo
> nodeInfo(aNodeInfo
);
35 auto* nim
= nodeInfo
->NodeInfoManager();
36 return new (nim
) XULPopupElement(nodeInfo
.forget());
39 JSObject
* XULPopupElement::WrapNode(JSContext
* aCx
,
40 JS::Handle
<JSObject
*> aGivenProto
) {
41 return XULPopupElement_Binding::Wrap(aCx
, this, aGivenProto
);
44 nsMenuPopupFrame
* XULPopupElement::GetFrame(FlushType aFlushType
) {
45 nsIFrame
* f
= GetPrimaryFrame(aFlushType
);
46 MOZ_ASSERT(!f
|| f
->IsMenuPopupFrame());
47 return static_cast<nsMenuPopupFrame
*>(f
);
50 void XULPopupElement::OpenPopup(Element
* aAnchorElement
,
51 const StringOrOpenPopupOptions
& aOptions
,
52 int32_t aXPos
, int32_t aYPos
,
53 bool aIsContextMenu
, bool aAttributesOverride
,
54 Event
* aTriggerEvent
) {
55 nsAutoString position
;
56 if (aOptions
.IsOpenPopupOptions()) {
57 const OpenPopupOptions
& options
= aOptions
.GetAsOpenPopupOptions();
58 position
= options
.mPosition
;
61 aIsContextMenu
= options
.mIsContextMenu
;
62 aAttributesOverride
= options
.mAttributesOverride
;
63 aTriggerEvent
= options
.mTriggerEvent
;
65 position
= aOptions
.GetAsString();
68 nsXULPopupManager
* pm
= nsXULPopupManager::GetInstance();
70 // As a special case for popups that are menus when no anchor or position
71 // are specified, open the popup with ShowMenu instead of ShowPopup so that
72 // the popup is aligned with the menu.
73 if (!aAnchorElement
&& position
.IsEmpty() && GetPrimaryFrame()) {
74 if (auto* menu
= GetContainingMenu()) {
75 pm
->ShowMenu(menu
, false);
80 pm
->ShowPopup(this, aAnchorElement
, position
, aXPos
, aYPos
, aIsContextMenu
,
81 aAttributesOverride
, false, aTriggerEvent
);
85 void XULPopupElement::OpenPopupAtScreen(int32_t aXPos
, int32_t aYPos
,
87 Event
* aTriggerEvent
) {
88 nsXULPopupManager
* pm
= nsXULPopupManager::GetInstance();
90 pm
->ShowPopupAtScreen(this, aXPos
, aYPos
, aIsContextMenu
, aTriggerEvent
);
94 void XULPopupElement::OpenPopupAtScreenRect(const nsAString
& aPosition
,
95 int32_t aXPos
, int32_t aYPos
,
96 int32_t aWidth
, int32_t aHeight
,
98 bool aAttributesOverride
,
99 Event
* aTriggerEvent
) {
100 nsXULPopupManager
* pm
= nsXULPopupManager::GetInstance();
102 pm
->ShowPopupAtScreenRect(
103 this, aPosition
, nsIntRect(aXPos
, aYPos
, aWidth
, aHeight
),
104 aIsContextMenu
, aAttributesOverride
, aTriggerEvent
);
108 void XULPopupElement::HidePopup(bool aCancel
) {
109 nsXULPopupManager
* pm
= nsXULPopupManager::GetInstance();
113 HidePopupOptions options
{HidePopupOption::DeselectMenu
};
115 options
+= HidePopupOption::IsRollup
;
117 pm
->HidePopup(this, options
);
120 static Modifiers
ConvertModifiers(const ActivateMenuItemOptions
& aModifiers
) {
121 Modifiers modifiers
= 0;
122 if (aModifiers
.mCtrlKey
) {
123 modifiers
|= MODIFIER_CONTROL
;
125 if (aModifiers
.mAltKey
) {
126 modifiers
|= MODIFIER_ALT
;
128 if (aModifiers
.mShiftKey
) {
129 modifiers
|= MODIFIER_SHIFT
;
131 if (aModifiers
.mMetaKey
) {
132 modifiers
|= MODIFIER_META
;
137 void XULPopupElement::PopupOpened(bool aSelectFirstItem
) {
138 if (aSelectFirstItem
) {
141 if (RefPtr button
= GetContainingMenu()) {
142 if (RefPtr parent
= button
->GetMenuParent()) {
143 parent
->SetActiveMenuChild(button
);
148 void XULPopupElement::PopupClosed(bool aDeselectMenu
) {
149 LockMenuUntilClosed(false);
150 SetActiveMenuChild(nullptr);
151 auto dispatcher
= MakeRefPtr
<AsyncEventDispatcher
>(
152 this, u
"DOMMenuInactive"_ns
, CanBubble::eYes
, ChromeOnlyDispatch::eNo
);
153 dispatcher
->PostDOMEvent();
154 if (RefPtr button
= GetContainingMenu()) {
155 button
->PopupClosed(aDeselectMenu
);
159 void XULPopupElement::ActivateItem(Element
& aItemElement
,
160 const ActivateMenuItemOptions
& aOptions
,
162 if (!Contains(&aItemElement
)) {
163 return aRv
.ThrowInvalidStateError("Menu item is not inside this menu.");
166 Modifiers modifiers
= ConvertModifiers(aOptions
);
168 // First, check if a native menu is open, and activate the item in it.
169 if (nsXULPopupManager
* pm
= nsXULPopupManager::GetInstance()) {
170 if (pm
->ActivateNativeMenuItem(&aItemElement
, modifiers
, aOptions
.mButton
,
176 auto* item
= XULButtonElement::FromNode(aItemElement
);
177 if (!item
|| !item
->IsMenu()) {
178 return aRv
.ThrowInvalidStateError("Not a menu item");
181 if (!item
->GetPrimaryFrame(FlushType::Frames
)) {
182 return aRv
.ThrowInvalidStateError("Menu item is hidden");
185 auto* popup
= item
->GetContainingPopupElement();
187 return aRv
.ThrowInvalidStateError("No popup");
190 nsMenuPopupFrame
* frame
= popup
->GetFrame(FlushType::None
);
191 if (!frame
|| !frame
->IsOpen()) {
192 return aRv
.ThrowInvalidStateError("Popup is not open");
195 // This is a chrome-only API, so we're trusted.
196 const bool trusted
= true;
197 // KnownLive because item is aItemElement.
198 MOZ_KnownLive(item
)->ExecuteMenu(modifiers
, aOptions
.mButton
, trusted
);
201 void XULPopupElement::MoveTo(int32_t aLeft
, int32_t aTop
) {
202 if (nsMenuPopupFrame
* menuPopupFrame
= do_QueryFrame(GetPrimaryFrame())) {
203 menuPopupFrame
->MoveTo(CSSIntPoint(aLeft
, aTop
), true);
207 void XULPopupElement::MoveToAnchor(Element
* aAnchorElement
,
208 const nsAString
& aPosition
, int32_t aXPos
,
209 int32_t aYPos
, bool aAttributesOverride
) {
210 nsMenuPopupFrame
* menuPopupFrame
= GetFrame(FlushType::None
);
211 if (menuPopupFrame
&& menuPopupFrame
->IsVisibleOrShowing()) {
212 menuPopupFrame
->MoveToAnchor(aAnchorElement
, aPosition
, aXPos
, aYPos
,
213 aAttributesOverride
);
217 void XULPopupElement::SizeTo(int32_t aWidth
, int32_t aHeight
) {
219 nsAutoCString height
;
220 width
.AppendInt(aWidth
);
221 width
.AppendLiteral("px");
222 height
.AppendInt(aHeight
);
223 height
.AppendLiteral("px");
225 nsCOMPtr
<nsICSSDeclaration
> style
= Style();
226 style
->SetProperty("width"_ns
, width
, ""_ns
, IgnoreErrors());
227 style
->SetProperty("height"_ns
, height
, ""_ns
, IgnoreErrors());
229 // If the popup is open, force a reposition of the popup after resizing it
230 // with notifications set to true so that the popuppositioned event is fired.
231 nsMenuPopupFrame
* menuPopupFrame
= do_QueryFrame(GetPrimaryFrame());
232 if (menuPopupFrame
&& menuPopupFrame
->PopupState() == ePopupShown
) {
233 menuPopupFrame
->SetPopupPosition(false);
237 void XULPopupElement::GetState(nsString
& aState
) {
238 // set this here in case there's no frame for the popup
239 aState
.AssignLiteral("closed");
241 if (nsXULPopupManager
* pm
= nsXULPopupManager::GetInstance()) {
242 switch (pm
->GetPopupState(this)) {
244 aState
.AssignLiteral("open");
247 case ePopupPositioning
:
250 aState
.AssignLiteral("showing");
253 case ePopupInvisible
:
254 aState
.AssignLiteral("hiding");
259 MOZ_ASSERT_UNREACHABLE("Bad popup state");
265 nsINode
* XULPopupElement::GetTriggerNode() const {
266 nsMenuPopupFrame
* menuPopupFrame
= do_QueryFrame(GetPrimaryFrame());
267 return nsMenuPopupFrame::GetTriggerContent(menuPopupFrame
);
270 // FIXME(emilio): should probably be renamed to GetAnchorElement?
271 Element
* XULPopupElement::GetAnchorNode() const {
272 nsMenuPopupFrame
* menuPopupFrame
= do_QueryFrame(GetPrimaryFrame());
273 if (!menuPopupFrame
) {
276 return Element::FromNodeOrNull(menuPopupFrame
->GetAnchor());
279 already_AddRefed
<DOMRect
> XULPopupElement::GetOuterScreenRect() {
280 RefPtr
<DOMRect
> rect
= new DOMRect(ToSupports(OwnerDoc()));
282 // Return an empty rectangle if the popup is not open.
283 nsMenuPopupFrame
* menuPopupFrame
=
284 do_QueryFrame(GetPrimaryFrame(FlushType::Frames
));
285 if (!menuPopupFrame
|| !menuPopupFrame
->IsOpen()) {
286 return rect
.forget();
289 Maybe
<CSSRect
> screenRect
;
291 if (menuPopupFrame
->IsNativeMenu()) {
292 // For native menus we can't query the true size. Use the anchor rect
293 // instead, which at least has the position at which we were intending to
295 screenRect
= Some(CSSRect(menuPopupFrame
->GetScreenAnchorRect()));
297 // For non-native menus, query the bounds from the widget.
298 if (nsView
* view
= menuPopupFrame
->GetView()) {
299 if (nsIWidget
* widget
= view
->GetWidget()) {
300 screenRect
= Some(widget
->GetScreenBounds() /
301 menuPopupFrame
->PresContext()->CSSToDevPixelScale());
307 rect
->SetRect(screenRect
->X(), screenRect
->Y(), screenRect
->Width(),
308 screenRect
->Height());
310 return rect
.forget();
313 void XULPopupElement::SetConstraintRect(dom::DOMRectReadOnly
& aRect
) {
314 nsMenuPopupFrame
* menuPopupFrame
=
315 do_QueryFrame(GetPrimaryFrame(FlushType::Frames
));
316 if (menuPopupFrame
) {
317 menuPopupFrame
->SetOverrideConstraintRect(CSSIntRect::Truncate(
318 aRect
.Left(), aRect
.Top(), aRect
.Width(), aRect
.Height()));
322 bool XULPopupElement::IsWaylandDragSource() const {
324 nsMenuPopupFrame
* f
= do_QueryFrame(GetPrimaryFrame());
325 return f
&& f
->IsDragSource();
331 bool XULPopupElement::IsWaylandPopup() const {
333 return widget::GdkIsWaylandDisplay() || widget::IsXWaylandProtocol();
339 } // namespace mozilla::dom