1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 This file provides the implementation for xul popup listener which
8 tracks xul popups and context menus
11 #include "nsXULPopupListener.h"
12 #include "XULButtonElement.h"
14 #include "nsGkAtoms.h"
15 #include "nsContentCID.h"
16 #include "nsContentUtils.h"
17 #include "nsXULPopupManager.h"
18 #include "nsIScriptContext.h"
19 #include "mozilla/dom/Document.h"
20 #include "mozilla/dom/DocumentInlines.h"
21 #include "nsServiceManagerUtils.h"
22 #include "nsLayoutUtils.h"
23 #include "mozilla/ReflowInput.h"
24 #include "nsIObjectLoadingContent.h"
25 #include "mozilla/BasePrincipal.h"
26 #include "mozilla/EventStateManager.h"
27 #include "mozilla/Preferences.h"
28 #include "mozilla/dom/Element.h"
29 #include "mozilla/dom/Event.h" // for Event
30 #include "mozilla/dom/EventTarget.h"
31 #include "mozilla/dom/FragmentOrElement.h"
32 #include "mozilla/dom/MouseEvent.h"
33 #include "mozilla/dom/MouseEventBinding.h"
35 // for event firing in context menus
36 #include "nsPresContext.h"
37 #include "nsFocusManager.h"
38 #include "nsPIDOMWindow.h"
39 #include "nsViewManager.h"
42 using namespace mozilla
;
43 using namespace mozilla::dom
;
45 // on win32 and os/2, context menus come up on mouse up. On other platforms,
46 // they appear on mouse down. Certain bits of code care about this difference.
48 # define NS_CONTEXT_MENU_IS_MOUSEUP 1
51 nsXULPopupListener::nsXULPopupListener(mozilla::dom::Element
* aElement
,
53 : mElement(aElement
), mPopupContent(nullptr), mIsContext(aIsContext
) {}
55 nsXULPopupListener::~nsXULPopupListener(void) { ClosePopup(); }
57 NS_IMPL_CYCLE_COLLECTION(nsXULPopupListener
, mElement
, mPopupContent
)
58 NS_IMPL_CYCLE_COLLECTING_ADDREF(nsXULPopupListener
)
59 NS_IMPL_CYCLE_COLLECTING_RELEASE(nsXULPopupListener
)
61 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_BEGIN(nsXULPopupListener
)
62 // If the owner, mElement, can be skipped, so can we.
64 return mozilla::dom::FragmentOrElement::CanSkip(tmp
->mElement
, true);
66 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_END
68 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_BEGIN(nsXULPopupListener
)
70 return mozilla::dom::FragmentOrElement::CanSkipInCC(tmp
->mElement
);
72 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_END
74 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_BEGIN(nsXULPopupListener
)
76 return mozilla::dom::FragmentOrElement::CanSkipThis(tmp
->mElement
);
78 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_END
80 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsXULPopupListener
)
81 NS_INTERFACE_MAP_ENTRY(nsIDOMEventListener
)
82 NS_INTERFACE_MAP_ENTRY(nsISupports
)
85 ////////////////////////////////////////////////////////////////
86 // nsIDOMEventListener
88 nsresult
nsXULPopupListener::HandleEvent(Event
* aEvent
) {
89 nsAutoString eventType
;
90 aEvent
->GetType(eventType
);
92 if (!((eventType
.EqualsLiteral("mousedown") && !mIsContext
) ||
93 (eventType
.EqualsLiteral("contextmenu") && mIsContext
)))
96 MouseEvent
* mouseEvent
= aEvent
->AsMouseEvent();
98 // non-ui event passed in. bad things.
102 // Get the node that was clicked on.
103 nsCOMPtr
<nsIContent
> targetContent
=
104 nsIContent::FromEventTargetOrNull(mouseEvent
->GetTarget());
105 if (!targetContent
) {
109 if (nsIContent
* content
=
110 nsIContent::FromEventTargetOrNull(mouseEvent
->GetOriginalTarget())) {
111 if (EventStateManager::IsTopLevelRemoteTarget(content
)) {
116 bool preventDefault
= mouseEvent
->DefaultPrevented();
117 if (preventDefault
&& mIsContext
) {
118 // Someone called preventDefault on a context menu.
119 // Let's make sure they are allowed to do so.
121 Preferences::GetBool("dom.event.contextmenu.enabled", true);
123 // The user wants his contextmenus. Let's make sure that this is a
124 // website and not chrome since there could be places in chrome which
125 // don't want contextmenus.
126 if (!targetContent
->NodePrincipal()->IsSystemPrincipal()) {
127 // This isn't chrome. Cancel the preventDefault() and
128 // let the event go forth.
129 preventDefault
= false;
134 if (preventDefault
) {
135 // someone called preventDefault. bail.
139 // prevent popups on menu and menuitems as they handle their own popups
140 // This was added for bug 96920.
141 // If a menu item child was clicked on that leads to a popup needing
142 // to show, we know (guaranteed) that we're dealing with a menu or
143 // submenu of an already-showing popup. We don't need to do anything at all.
145 targetContent
->IsAnyOfXULElements(nsGkAtoms::menu
, nsGkAtoms::menuitem
)) {
149 if (!mIsContext
&& mouseEvent
->Button() != 0) {
150 // Only open popups when the left mouse button is down.
154 // Open the popup. LaunchPopup will call StopPropagation and PreventDefault
155 // in the right situations.
156 LaunchPopup(mouseEvent
);
163 // Do everything needed to shut down the popup.
165 // NOTE: This routine is safe to call even if the popup is already closed.
167 void nsXULPopupListener::ClosePopup() {
169 // this is called when the listener is going away, so make sure that the
170 // popup is hidden. Use asynchronous hiding just to be safe so we don't
171 // fire events during destruction.
172 nsXULPopupManager
* pm
= nsXULPopupManager::GetInstance();
174 pm
->HidePopup(mPopupContent
,
175 {HidePopupOption::DeselectMenu
, HidePopupOption::Async
});
176 mPopupContent
= nullptr; // release the popup
180 static already_AddRefed
<Element
> GetImmediateChild(nsIContent
* aContent
,
182 for (nsIContent
* child
= aContent
->GetFirstChild(); child
;
183 child
= child
->GetNextSibling()) {
184 if (child
->IsXULElement(aTag
)) {
185 RefPtr
<Element
> ret
= child
->AsElement();
196 // Given the element on which the event was triggered and the mouse locations in
197 // Client and widget coordinates, popup a new window showing the appropriate
200 // aTargetContent is the target of the mouse event aEvent that triggered the
201 // popup. mElement is the element that the popup menu is attached to.
202 // aTargetContent may be equal to mElement or it may be a descendant.
204 // This looks for an attribute on |mElement| of the appropriate popup type
205 // (popup, context) and uses that attribute's value as an ID for
206 // the popup content in the document.
208 nsresult
nsXULPopupListener::LaunchPopup(MouseEvent
* aEvent
) {
211 nsAutoString identifier
;
212 nsAtom
* type
= mIsContext
? nsGkAtoms::context
: nsGkAtoms::popup
;
213 bool hasPopupAttr
= mElement
->GetAttr(kNameSpaceID_None
, type
, identifier
);
215 if (identifier
.IsEmpty()) {
217 mElement
->GetAttr(kNameSpaceID_None
,
218 mIsContext
? nsGkAtoms::contextmenu
: nsGkAtoms::menu
,
224 aEvent
->StopPropagation();
225 aEvent
->PreventDefault();
228 if (identifier
.IsEmpty()) return rv
;
230 // Try to find the popup content and the document.
231 nsCOMPtr
<Document
> document
= mElement
->GetComposedDoc();
233 NS_WARNING("No document!");
234 return NS_ERROR_FAILURE
;
237 // Handle the _child case for popups and context menus
238 RefPtr
<Element
> popup
;
239 if (identifier
.EqualsLiteral("_child")) {
240 popup
= GetImmediateChild(mElement
, nsGkAtoms::menupopup
);
241 } else if (!mElement
->IsInUncomposedDoc() ||
242 !(popup
= document
->GetElementById(identifier
))) {
243 // XXXsmaug Should we try to use ShadowRoot::GetElementById in case
244 // mElement is in shadow DOM?
246 // Use getElementById to obtain the popup content and gracefully fail if
247 // we didn't find any popup content in the document.
248 NS_WARNING("GetElementById had some kind of spasm.");
252 // return if no popup was found or the popup is the element itself.
253 if (!popup
|| popup
== mElement
) {
257 // Submenus can't be used as context menus or popups, bug 288763.
258 // Similar code also in nsXULTooltipListener::GetTooltipFor.
259 if (auto* button
= XULButtonElement::FromNodeOrNull(popup
->GetParent())) {
260 if (button
->IsMenu()) {
265 nsXULPopupManager
* pm
= nsXULPopupManager::GetInstance();
266 if (!pm
) return NS_OK
;
268 // For left-clicks, if the popup has an position attribute, or both the
269 // popupanchor and popupalign attributes are used, anchor the popup to the
270 // element, otherwise just open it at the screen position where the mouse
271 // was clicked. Context menus always open at the mouse position.
272 mPopupContent
= popup
;
274 (mPopupContent
->HasAttr(kNameSpaceID_None
, nsGkAtoms::position
) ||
275 (mPopupContent
->HasAttr(kNameSpaceID_None
, nsGkAtoms::popupanchor
) &&
276 mPopupContent
->HasAttr(kNameSpaceID_None
, nsGkAtoms::popupalign
)))) {
277 pm
->ShowPopup(mPopupContent
, mElement
, u
""_ns
, 0, 0, false, true, false,
280 CSSIntPoint pos
= aEvent
->ScreenPoint(CallerType::System
);
281 pm
->ShowPopupAtScreen(mPopupContent
, pos
.x
, pos
.y
, mIsContext
, aEvent
);