Source code

Revision control

Other Tools

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/. */
5
6
/*
7
This file provides the implementation for xul popup listener which
8
tracks xul popups and context menus
9
*/
10
11
#include "nsXULPopupListener.h"
12
#include "nsCOMPtr.h"
13
#include "nsGkAtoms.h"
14
#include "nsContentCID.h"
15
#include "nsContentUtils.h"
16
#include "nsXULPopupManager.h"
17
#include "nsIScriptContext.h"
18
#include "mozilla/dom/Document.h"
19
#include "nsServiceManagerUtils.h"
20
#include "nsLayoutUtils.h"
21
#include "mozilla/ReflowInput.h"
22
#include "nsIObjectLoadingContent.h"
23
#include "mozilla/BasePrincipal.h"
24
#include "mozilla/EventStateManager.h"
25
#include "mozilla/EventStates.h"
26
#include "mozilla/Preferences.h"
27
#include "mozilla/dom/Element.h"
28
#include "mozilla/dom/Event.h" // for Event
29
#include "mozilla/dom/EventTarget.h"
30
#include "mozilla/dom/FragmentOrElement.h"
31
#include "mozilla/dom/MouseEvent.h"
32
#include "mozilla/dom/MouseEventBinding.h"
33
34
// for event firing in context menus
35
#include "nsPresContext.h"
36
#include "nsFocusManager.h"
37
#include "nsPIDOMWindow.h"
38
#include "nsViewManager.h"
39
#include "nsError.h"
40
#include "nsMenuFrame.h"
41
42
using namespace mozilla;
43
using namespace mozilla::dom;
44
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
50
51
nsXULPopupListener::nsXULPopupListener(mozilla::dom::Element* aElement,
52
bool aIsContext)
53
: mElement(aElement), mPopupContent(nullptr), mIsContext(aIsContext) {}
54
55
nsXULPopupListener::~nsXULPopupListener(void) { ClosePopup(); }
56
57
NS_IMPL_CYCLE_COLLECTION(nsXULPopupListener, mElement, mPopupContent)
58
NS_IMPL_CYCLE_COLLECTING_ADDREF(nsXULPopupListener)
59
NS_IMPL_CYCLE_COLLECTING_RELEASE(nsXULPopupListener)
60
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);
65
}
66
NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_END
67
68
NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_BEGIN(nsXULPopupListener)
69
if (tmp->mElement) {
70
return mozilla::dom::FragmentOrElement::CanSkipInCC(tmp->mElement);
71
}
72
NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_END
73
74
NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_BEGIN(nsXULPopupListener)
75
if (tmp->mElement) {
76
return mozilla::dom::FragmentOrElement::CanSkipThis(tmp->mElement);
77
}
78
NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_END
79
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
84
85
////////////////////////////////////////////////////////////////
86
// nsIDOMEventListener
87
88
nsresult nsXULPopupListener::HandleEvent(Event* aEvent) {
89
nsAutoString eventType;
90
aEvent->GetType(eventType);
91
92
if (!((eventType.EqualsLiteral("mousedown") && !mIsContext) ||
93
(eventType.EqualsLiteral("contextmenu") && mIsContext)))
94
return NS_OK;
95
96
MouseEvent* mouseEvent = aEvent->AsMouseEvent();
97
if (!mouseEvent) {
98
// non-ui event passed in. bad things.
99
return NS_OK;
100
}
101
102
// Get the node that was clicked on.
103
EventTarget* target = mouseEvent->GetTarget();
104
nsCOMPtr<nsIContent> targetContent = do_QueryInterface(target);
105
if (!targetContent) {
106
return NS_OK;
107
}
108
109
{
110
EventTarget* originalTarget = mouseEvent->GetOriginalTarget();
111
nsCOMPtr<nsIContent> content = do_QueryInterface(originalTarget);
112
if (content && EventStateManager::IsRemoteTarget(content)) {
113
return NS_OK;
114
}
115
}
116
117
bool preventDefault = mouseEvent->DefaultPrevented();
118
if (preventDefault && mIsContext) {
119
// Someone called preventDefault on a context menu.
120
// Let's make sure they are allowed to do so.
121
bool eventEnabled =
122
Preferences::GetBool("dom.event.contextmenu.enabled", true);
123
if (!eventEnabled) {
124
// If the target node is for plug-in, we should not open XUL context
125
// menu on windowless plug-ins.
126
nsCOMPtr<nsIObjectLoadingContent> olc = do_QueryInterface(targetContent);
127
uint32_t type;
128
if (olc && NS_SUCCEEDED(olc->GetDisplayedType(&type)) &&
129
type == nsIObjectLoadingContent::TYPE_PLUGIN) {
130
return NS_OK;
131
}
132
133
// The user wants his contextmenus. Let's make sure that this is a
134
// website and not chrome since there could be places in chrome which
135
// don't want contextmenus.
136
if (!targetContent->NodePrincipal()->IsSystemPrincipal()) {
137
// This isn't chrome. Cancel the preventDefault() and
138
// let the event go forth.
139
preventDefault = false;
140
}
141
}
142
}
143
144
if (preventDefault) {
145
// someone called preventDefault. bail.
146
return NS_OK;
147
}
148
149
// prevent popups on menu and menuitems as they handle their own popups
150
// This was added for bug 96920.
151
// If a menu item child was clicked on that leads to a popup needing
152
// to show, we know (guaranteed) that we're dealing with a menu or
153
// submenu of an already-showing popup. We don't need to do anything at all.
154
if (!mIsContext &&
155
targetContent->IsAnyOfXULElements(nsGkAtoms::menu, nsGkAtoms::menuitem)) {
156
return NS_OK;
157
}
158
159
if (mIsContext) {
160
#ifndef NS_CONTEXT_MENU_IS_MOUSEUP
161
uint16_t inputSource = mouseEvent->MozInputSource();
162
bool isTouch = inputSource == MouseEvent_Binding::MOZ_SOURCE_TOUCH;
163
// If the context menu launches on mousedown,
164
// we have to fire focus on the content we clicked on
165
FireFocusOnTargetContent(targetContent, isTouch);
166
#endif
167
} else {
168
// Only open popups when the left mouse button is down.
169
if (mouseEvent->Button() != 0) {
170
return NS_OK;
171
}
172
}
173
174
// Open the popup. LaunchPopup will call StopPropagation and PreventDefault
175
// in the right situations.
176
LaunchPopup(mouseEvent);
177
178
return NS_OK;
179
}
180
181
#ifndef NS_CONTEXT_MENU_IS_MOUSEUP
182
nsresult nsXULPopupListener::FireFocusOnTargetContent(
183
nsIContent* aTargetContent, bool aIsTouch) {
184
nsCOMPtr<Document> doc = aTargetContent->OwnerDoc();
185
186
// strong reference to keep this from going away between events
187
// XXXbz between what events? We don't use this local at all!
188
RefPtr<nsPresContext> context = doc->GetPresContext();
189
if (!context) {
190
return NS_ERROR_FAILURE;
191
}
192
193
nsIFrame* targetFrame = aTargetContent->GetPrimaryFrame();
194
if (!targetFrame) return NS_ERROR_FAILURE;
195
196
const nsStyleUI* ui = targetFrame->StyleUI();
197
bool suppressBlur = (ui->mUserFocus == StyleUserFocus::Ignore);
198
199
RefPtr<Element> newFocusElement;
200
201
nsIFrame* currFrame = targetFrame;
202
// Look for the nearest enclosing focusable frame.
203
while (currFrame) {
204
int32_t tabIndexUnused;
205
if (currFrame->IsFocusable(&tabIndexUnused, true) &&
206
currFrame->GetContent()->IsElement()) {
207
newFocusElement = currFrame->GetContent()->AsElement();
208
break;
209
}
210
currFrame = currFrame->GetParent();
211
}
212
213
nsIFocusManager* fm = nsFocusManager::GetFocusManager();
214
if (fm) {
215
if (newFocusElement) {
216
uint32_t focusFlags =
217
nsIFocusManager::FLAG_BYMOUSE | nsIFocusManager::FLAG_NOSCROLL;
218
if (aIsTouch) {
219
focusFlags |= nsIFocusManager::FLAG_BYTOUCH;
220
}
221
fm->SetFocus(newFocusElement, focusFlags);
222
} else if (!suppressBlur) {
223
nsPIDOMWindowOuter* window = doc->GetWindow();
224
fm->ClearFocus(window);
225
}
226
}
227
228
EventStateManager* esm = context->EventStateManager();
229
esm->SetContentState(newFocusElement, NS_EVENT_STATE_ACTIVE);
230
231
return NS_OK;
232
}
233
#endif
234
235
// ClosePopup
236
//
237
// Do everything needed to shut down the popup.
238
//
239
// NOTE: This routine is safe to call even if the popup is already closed.
240
//
241
void nsXULPopupListener::ClosePopup() {
242
if (mPopupContent) {
243
// this is called when the listener is going away, so make sure that the
244
// popup is hidden. Use asynchronous hiding just to be safe so we don't
245
// fire events during destruction.
246
nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
247
if (pm) pm->HidePopup(mPopupContent, false, true, true, false);
248
mPopupContent = nullptr; // release the popup
249
}
250
} // ClosePopup
251
252
static already_AddRefed<Element> GetImmediateChild(nsIContent* aContent,
253
nsAtom* aTag) {
254
for (nsIContent* child = aContent->GetFirstChild(); child;
255
child = child->GetNextSibling()) {
256
if (child->IsXULElement(aTag)) {
257
RefPtr<Element> ret = child->AsElement();
258
return ret.forget();
259
}
260
}
261
262
return nullptr;
263
}
264
265
//
266
// LaunchPopup
267
//
268
// Given the element on which the event was triggered and the mouse locations in
269
// Client and widget coordinates, popup a new window showing the appropriate
270
// content.
271
//
272
// aTargetContent is the target of the mouse event aEvent that triggered the
273
// popup. mElement is the element that the popup menu is attached to.
274
// aTargetContent may be equal to mElement or it may be a descendant.
275
//
276
// This looks for an attribute on |mElement| of the appropriate popup type
277
// (popup, context) and uses that attribute's value as an ID for
278
// the popup content in the document.
279
//
280
nsresult nsXULPopupListener::LaunchPopup(MouseEvent* aEvent) {
281
nsresult rv = NS_OK;
282
283
nsAutoString identifier;
284
nsAtom* type = mIsContext ? nsGkAtoms::context : nsGkAtoms::popup;
285
bool hasPopupAttr = mElement->GetAttr(kNameSpaceID_None, type, identifier);
286
287
if (identifier.IsEmpty()) {
288
hasPopupAttr =
289
mElement->GetAttr(kNameSpaceID_None,
290
mIsContext ? nsGkAtoms::contextmenu : nsGkAtoms::menu,
291
identifier) ||
292
hasPopupAttr;
293
}
294
295
if (hasPopupAttr) {
296
aEvent->StopPropagation();
297
aEvent->PreventDefault();
298
}
299
300
if (identifier.IsEmpty()) return rv;
301
302
// Try to find the popup content and the document.
303
nsCOMPtr<Document> document = mElement->GetComposedDoc();
304
if (!document) {
305
NS_WARNING("No document!");
306
return NS_ERROR_FAILURE;
307
}
308
309
// Handle the _child case for popups and context menus
310
RefPtr<Element> popup;
311
if (identifier.EqualsLiteral("_child")) {
312
popup = GetImmediateChild(mElement, nsGkAtoms::menupopup);
313
} else if (!mElement->IsInUncomposedDoc() ||
314
!(popup = document->GetElementById(identifier))) {
315
// XXXsmaug Should we try to use ShadowRoot::GetElementById in case
316
// mElement is in shadow DOM?
317
//
318
// Use getElementById to obtain the popup content and gracefully fail if
319
// we didn't find any popup content in the document.
320
NS_WARNING("GetElementById had some kind of spasm.");
321
return rv;
322
}
323
324
// return if no popup was found or the popup is the element itself.
325
if (!popup || popup == mElement) return NS_OK;
326
327
// Submenus can't be used as context menus or popups, bug 288763.
328
// Similar code also in nsXULTooltipListener::GetTooltipFor.
329
nsIContent* parent = popup->GetParent();
330
if (parent) {
331
nsMenuFrame* menu = do_QueryFrame(parent->GetPrimaryFrame());
332
if (menu) return NS_OK;
333
}
334
335
nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
336
if (!pm) return NS_OK;
337
338
// For left-clicks, if the popup has an position attribute, or both the
339
// popupanchor and popupalign attributes are used, anchor the popup to the
340
// element, otherwise just open it at the screen position where the mouse
341
// was clicked. Context menus always open at the mouse position.
342
mPopupContent = popup;
343
if (!mIsContext &&
344
(mPopupContent->HasAttr(kNameSpaceID_None, nsGkAtoms::position) ||
345
(mPopupContent->HasAttr(kNameSpaceID_None, nsGkAtoms::popupanchor) &&
346
mPopupContent->HasAttr(kNameSpaceID_None, nsGkAtoms::popupalign)))) {
347
pm->ShowPopup(mPopupContent, mElement, EmptyString(), 0, 0, false, true,
348
false, aEvent);
349
} else {
350
int32_t xPos = aEvent->ScreenX(CallerType::System);
351
int32_t yPos = aEvent->ScreenY(CallerType::System);
352
353
pm->ShowPopupAtScreen(mPopupContent, xPos, yPos, mIsContext, aEvent);
354
}
355
356
return NS_OK;
357
}