Bug 1825212 [wpt PR 39266] - [@scope] Propagate proximity from SubResult, a=testonly
[gecko.git] / dom / xul / nsXULPopupListener.cpp
blob6f6ac3158274fcd4bede74b5e163abed0d105f51
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/. */
6 /*
7 This file provides the implementation for xul popup listener which
8 tracks xul popups and context menus
9 */
11 #include "nsXULPopupListener.h"
12 #include "XULButtonElement.h"
13 #include "nsCOMPtr.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"
40 #include "nsError.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.
47 #if defined(XP_WIN)
48 # define NS_CONTEXT_MENU_IS_MOUSEUP 1
49 #endif
51 nsXULPopupListener::nsXULPopupListener(mozilla::dom::Element* aElement,
52 bool aIsContext)
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.
63 if (tmp->mElement) {
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)
69 if (tmp->mElement) {
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)
75 if (tmp->mElement) {
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)
83 NS_INTERFACE_MAP_END
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)))
94 return NS_OK;
96 MouseEvent* mouseEvent = aEvent->AsMouseEvent();
97 if (!mouseEvent) {
98 // non-ui event passed in. bad things.
99 return NS_OK;
102 // Get the node that was clicked on.
103 nsCOMPtr<nsIContent> targetContent =
104 nsIContent::FromEventTargetOrNull(mouseEvent->GetTarget());
105 if (!targetContent) {
106 return NS_OK;
109 if (nsIContent* content =
110 nsIContent::FromEventTargetOrNull(mouseEvent->GetOriginalTarget())) {
111 if (EventStateManager::IsTopLevelRemoteTarget(content)) {
112 return NS_OK;
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.
120 bool eventEnabled =
121 Preferences::GetBool("dom.event.contextmenu.enabled", true);
122 if (!eventEnabled) {
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.
136 return NS_OK;
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.
144 if (!mIsContext &&
145 targetContent->IsAnyOfXULElements(nsGkAtoms::menu, nsGkAtoms::menuitem)) {
146 return NS_OK;
149 if (!mIsContext && mouseEvent->Button() != 0) {
150 // Only open popups when the left mouse button is down.
151 return NS_OK;
154 // Open the popup. LaunchPopup will call StopPropagation and PreventDefault
155 // in the right situations.
156 LaunchPopup(mouseEvent);
158 return NS_OK;
161 // ClosePopup
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() {
168 if (mPopupContent) {
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();
173 if (pm)
174 pm->HidePopup(mPopupContent,
175 {HidePopupOption::DeselectMenu, HidePopupOption::Async});
176 mPopupContent = nullptr; // release the popup
178 } // ClosePopup
180 static already_AddRefed<Element> GetImmediateChild(nsIContent* aContent,
181 nsAtom* aTag) {
182 for (nsIContent* child = aContent->GetFirstChild(); child;
183 child = child->GetNextSibling()) {
184 if (child->IsXULElement(aTag)) {
185 RefPtr<Element> ret = child->AsElement();
186 return ret.forget();
190 return nullptr;
194 // LaunchPopup
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
198 // content.
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) {
209 nsresult rv = NS_OK;
211 nsAutoString identifier;
212 nsAtom* type = mIsContext ? nsGkAtoms::context : nsGkAtoms::popup;
213 bool hasPopupAttr = mElement->GetAttr(kNameSpaceID_None, type, identifier);
215 if (identifier.IsEmpty()) {
216 hasPopupAttr =
217 mElement->GetAttr(kNameSpaceID_None,
218 mIsContext ? nsGkAtoms::contextmenu : nsGkAtoms::menu,
219 identifier) ||
220 hasPopupAttr;
223 if (hasPopupAttr) {
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();
232 if (!document) {
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.");
249 return rv;
252 // return if no popup was found or the popup is the element itself.
253 if (!popup || popup == mElement) {
254 return NS_OK;
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()) {
261 return NS_OK;
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;
273 if (!mIsContext &&
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,
278 aEvent);
279 } else {
280 CSSIntPoint pos = aEvent->ScreenPoint(CallerType::System);
281 pm->ShowPopupAtScreen(mPopupContent, pos.x, pos.y, mIsContext, aEvent);
284 return NS_OK;