Source code

Revision control

Other Tools

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/. */
6
7
#include "nsGkAtoms.h"
8
#include "nsXULPopupManager.h"
9
#include "nsMenuFrame.h"
10
#include "nsMenuPopupFrame.h"
11
#include "nsMenuBarFrame.h"
12
#include "nsMenuBarListener.h"
13
#include "nsContentUtils.h"
14
#include "nsXULElement.h"
15
#include "nsIDOMXULMenuListElement.h"
16
#include "nsIDOMXULCommandDispatcher.h"
17
#ifdef MOZ_XBL
18
# include "nsBindingManager.h"
19
#endif
20
#include "nsCSSFrameConstructor.h"
21
#include "nsGlobalWindow.h"
22
#include "nsIContentInlines.h"
23
#include "nsLayoutUtils.h"
24
#include "nsViewManager.h"
25
#include "nsIComponentManager.h"
26
#include "nsITimer.h"
27
#include "nsFocusManager.h"
28
#include "nsIDocShell.h"
29
#include "nsPIDOMWindow.h"
30
#include "nsIInterfaceRequestorUtils.h"
31
#include "nsIBaseWindow.h"
32
#include "nsCaret.h"
33
#include "mozilla/dom/Document.h"
34
#include "nsPIWindowRoot.h"
35
#include "nsFrameManager.h"
36
#include "nsIObserverService.h"
37
#include "mozilla/AnimationUtils.h"
38
#include "mozilla/dom/DocumentInlines.h"
39
#include "mozilla/dom/Element.h"
40
#include "mozilla/dom/Event.h" // for Event
41
#include "mozilla/dom/HTMLSlotElement.h"
42
#include "mozilla/dom/KeyboardEvent.h"
43
#include "mozilla/dom/KeyboardEventBinding.h"
44
#include "mozilla/dom/MouseEvent.h"
45
#include "mozilla/dom/UIEvent.h"
46
#include "mozilla/dom/UserActivation.h"
47
#include "mozilla/EventDispatcher.h"
48
#include "mozilla/LookAndFeel.h"
49
#include "mozilla/MouseEvents.h"
50
#include "mozilla/PresShell.h"
51
#include "mozilla/Services.h"
52
#include "mozilla/StaticPrefs_ui.h"
53
#include "mozilla/StaticPrefs_xul.h"
54
#include "mozilla/widget/nsAutoRollup.h"
55
56
using namespace mozilla;
57
using namespace mozilla::dom;
58
59
static_assert(KeyboardEvent_Binding::DOM_VK_HOME ==
60
KeyboardEvent_Binding::DOM_VK_END + 1 &&
61
KeyboardEvent_Binding::DOM_VK_LEFT ==
62
KeyboardEvent_Binding::DOM_VK_END + 2 &&
63
KeyboardEvent_Binding::DOM_VK_UP ==
64
KeyboardEvent_Binding::DOM_VK_END + 3 &&
65
KeyboardEvent_Binding::DOM_VK_RIGHT ==
66
KeyboardEvent_Binding::DOM_VK_END + 4 &&
67
KeyboardEvent_Binding::DOM_VK_DOWN ==
68
KeyboardEvent_Binding::DOM_VK_END + 5,
69
"nsXULPopupManager assumes some keyCode values are consecutive");
70
71
const nsNavigationDirection DirectionFromKeyCodeTable[2][6] = {
72
{
73
eNavigationDirection_Last, // KeyboardEvent_Binding::DOM_VK_END
74
eNavigationDirection_First, // KeyboardEvent_Binding::DOM_VK_HOME
75
eNavigationDirection_Start, // KeyboardEvent_Binding::DOM_VK_LEFT
76
eNavigationDirection_Before, // KeyboardEvent_Binding::DOM_VK_UP
77
eNavigationDirection_End, // KeyboardEvent_Binding::DOM_VK_RIGHT
78
eNavigationDirection_After // KeyboardEvent_Binding::DOM_VK_DOWN
79
},
80
{
81
eNavigationDirection_Last, // KeyboardEvent_Binding::DOM_VK_END
82
eNavigationDirection_First, // KeyboardEvent_Binding::DOM_VK_HOME
83
eNavigationDirection_End, // KeyboardEvent_Binding::DOM_VK_LEFT
84
eNavigationDirection_Before, // KeyboardEvent_Binding::DOM_VK_UP
85
eNavigationDirection_Start, // KeyboardEvent_Binding::DOM_VK_RIGHT
86
eNavigationDirection_After // KeyboardEvent_Binding::DOM_VK_DOWN
87
}};
88
89
nsXULPopupManager* nsXULPopupManager::sInstance = nullptr;
90
91
nsIContent* nsMenuChainItem::Content() { return mFrame->GetContent(); }
92
93
void nsMenuChainItem::SetParent(nsMenuChainItem* aParent) {
94
if (mParent) {
95
NS_ASSERTION(mParent->mChild == this,
96
"Unexpected - parent's child not set to this");
97
mParent->mChild = nullptr;
98
}
99
mParent = aParent;
100
if (mParent) {
101
if (mParent->mChild) mParent->mChild->mParent = nullptr;
102
mParent->mChild = this;
103
}
104
}
105
106
void nsMenuChainItem::Detach(nsMenuChainItem** aRoot) {
107
// If the item has a child, set the child's parent to this item's parent,
108
// effectively removing the item from the chain. If the item has no child,
109
// just set the parent to null.
110
if (mChild) {
111
NS_ASSERTION(this != *aRoot,
112
"Unexpected - popup with child at end of chain");
113
mChild->SetParent(mParent);
114
} else {
115
// An item without a child should be the first item in the chain, so set
116
// the first item pointer, pointed to by aRoot, to the parent.
117
NS_ASSERTION(this == *aRoot,
118
"Unexpected - popup with no child not at end of chain");
119
*aRoot = mParent;
120
SetParent(nullptr);
121
}
122
}
123
124
void nsMenuChainItem::UpdateFollowAnchor() {
125
mFollowAnchor = mFrame->ShouldFollowAnchor(mCurrentRect);
126
}
127
128
void nsMenuChainItem::CheckForAnchorChange() {
129
if (mFollowAnchor) {
130
mFrame->CheckForAnchorChange(mCurrentRect);
131
}
132
}
133
134
NS_IMPL_ISUPPORTS(nsXULPopupManager, nsIDOMEventListener, nsIObserver)
135
136
nsXULPopupManager::nsXULPopupManager()
137
: mRangeOffset(0),
138
mCachedMousePoint(0, 0),
139
mCachedModifiers(0),
140
mActiveMenuBar(nullptr),
141
mPopups(nullptr),
142
mTimerMenu(nullptr) {
143
nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
144
if (obs) {
145
obs->AddObserver(this, "xpcom-shutdown", false);
146
}
147
}
148
149
nsXULPopupManager::~nsXULPopupManager() {
150
NS_ASSERTION(!mPopups, "XUL popups still open");
151
}
152
153
nsresult nsXULPopupManager::Init() {
154
sInstance = new nsXULPopupManager();
155
NS_ENSURE_TRUE(sInstance, NS_ERROR_OUT_OF_MEMORY);
156
NS_ADDREF(sInstance);
157
return NS_OK;
158
}
159
160
void nsXULPopupManager::Shutdown() { NS_IF_RELEASE(sInstance); }
161
162
NS_IMETHODIMP
163
nsXULPopupManager::Observe(nsISupports* aSubject, const char* aTopic,
164
const char16_t* aData) {
165
if (!nsCRT::strcmp(aTopic, "xpcom-shutdown")) {
166
if (mKeyListener) {
167
mKeyListener->RemoveEventListener(NS_LITERAL_STRING("keypress"), this,
168
true);
169
mKeyListener->RemoveEventListener(NS_LITERAL_STRING("keydown"), this,
170
true);
171
mKeyListener->RemoveEventListener(NS_LITERAL_STRING("keyup"), this, true);
172
mKeyListener = nullptr;
173
}
174
mRangeParent = nullptr;
175
// mOpeningPopup is cleared explicitly soon after using it.
176
nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
177
if (obs) {
178
obs->RemoveObserver(this, "xpcom-shutdown");
179
}
180
}
181
182
return NS_OK;
183
}
184
185
nsXULPopupManager* nsXULPopupManager::GetInstance() {
186
MOZ_ASSERT(sInstance);
187
return sInstance;
188
}
189
190
bool nsXULPopupManager::Rollup(uint32_t aCount, bool aFlush,
191
const nsIntPoint* pos,
192
nsIContent** aLastRolledUp) {
193
if (aLastRolledUp) {
194
*aLastRolledUp = nullptr;
195
}
196
197
// We can disable the autohide behavior via a pref to ease debugging.
198
if (StaticPrefs::ui_popup_disable_autohide()) {
199
// Required on linux to allow events to work on other targets.
200
if (mWidget) {
201
mWidget->CaptureRollupEvents(nullptr, false);
202
}
203
return false;
204
}
205
206
bool consume = false;
207
208
nsMenuChainItem* item = GetTopVisibleMenu();
209
if (item) {
210
if (aLastRolledUp) {
211
// We need to get the popup that will be closed last, so that widget can
212
// keep track of it so it doesn't reopen if a mousedown event is going to
213
// processed. Keep going up the menu chain to get the first level menu of
214
// the same type. If a different type is encountered it means we have,
215
// for example, a menulist or context menu inside a panel, and we want to
216
// treat these as distinct. It's possible that this menu doesn't end up
217
// closing because the popuphiding event was cancelled, but in that case
218
// we don't need to deal with the menu reopening as it will already still
219
// be open.
220
nsMenuChainItem* first = item;
221
while (first->GetParent()) {
222
nsMenuChainItem* parent = first->GetParent();
223
if (first->Frame()->PopupType() != parent->Frame()->PopupType() ||
224
first->IsContextMenu() != parent->IsContextMenu()) {
225
break;
226
}
227
first = parent;
228
}
229
230
*aLastRolledUp = first->Content();
231
}
232
233
ConsumeOutsideClicksResult consumeResult =
234
item->Frame()->ConsumeOutsideClicks();
235
consume = (consumeResult == ConsumeOutsideClicks_True);
236
237
bool rollup = true;
238
239
// If norolluponanchor is true, then don't rollup when clicking the anchor.
240
// This would be used to allow adjusting the caret position in an
241
// autocomplete field without hiding the popup for example.
242
bool noRollupOnAnchor =
243
(!consume && pos &&
244
item->Frame()->GetContent()->AsElement()->AttrValueIs(
245
kNameSpaceID_None, nsGkAtoms::norolluponanchor, nsGkAtoms::_true,
246
eCaseMatters));
247
248
// When ConsumeOutsideClicks_ParentOnly is used, always consume the click
249
// when the click was over the anchor. This way, clicking on a menu doesn't
250
// reopen the menu.
251
if ((consumeResult == ConsumeOutsideClicks_ParentOnly ||
252
noRollupOnAnchor) &&
253
pos) {
254
nsMenuPopupFrame* popupFrame = item->Frame();
255
CSSIntRect anchorRect;
256
if (popupFrame->IsAnchored()) {
257
// Check if the popup has a screen anchor rectangle. If not, get the
258
// rectangle from the anchor element.
259
anchorRect =
260
CSSIntRect::FromUnknownRect(popupFrame->GetScreenAnchorRect());
261
if (anchorRect.x == -1 || anchorRect.y == -1) {
262
nsCOMPtr<nsIContent> anchor = popupFrame->GetAnchor();
263
264
// Check if the anchor has indicated another node to use for checking
265
// for roll-up. That way, we can anchor a popup on anonymous content
266
// or an individual icon, while clicking elsewhere within a button or
267
// other container doesn't result in us re-opening the popup.
268
if (anchor && anchor->IsElement()) {
269
nsAutoString consumeAnchor;
270
anchor->AsElement()->GetAttr(
271
kNameSpaceID_None, nsGkAtoms::consumeanchor, consumeAnchor);
272
if (!consumeAnchor.IsEmpty()) {
273
Document* doc = anchor->GetOwnerDocument();
274
nsIContent* newAnchor = doc->GetElementById(consumeAnchor);
275
if (newAnchor) {
276
anchor = newAnchor;
277
}
278
}
279
}
280
281
if (anchor && anchor->GetPrimaryFrame()) {
282
anchorRect = anchor->GetPrimaryFrame()->GetScreenRect();
283
}
284
}
285
}
286
287
// It's possible that some other element is above the anchor at the same
288
// position, but the only thing that would happen is that the mouse
289
// event will get consumed, so here only a quick coordinates check is
290
// done rather than a slower complete check of what is at that location.
291
nsPresContext* presContext = item->Frame()->PresContext();
292
CSSIntPoint posCSSPixels(presContext->DevPixelsToIntCSSPixels(pos->x),
293
presContext->DevPixelsToIntCSSPixels(pos->y));
294
if (anchorRect.Contains(posCSSPixels)) {
295
if (consumeResult == ConsumeOutsideClicks_ParentOnly) {
296
consume = true;
297
}
298
299
if (noRollupOnAnchor) {
300
rollup = false;
301
}
302
}
303
}
304
305
if (rollup) {
306
// if a number of popups to close has been specified, determine the last
307
// popup to close
308
nsIContent* lastPopup = nullptr;
309
if (aCount != UINT32_MAX) {
310
nsMenuChainItem* last = item;
311
while (--aCount && last->GetParent()) {
312
last = last->GetParent();
313
}
314
if (last) {
315
lastPopup = last->Content();
316
}
317
}
318
319
nsPresContext* presContext = item->Frame()->PresContext();
320
RefPtr<nsViewManager> viewManager =
321
presContext->PresShell()->GetViewManager();
322
323
HidePopup(item->Content(), true, true, false, true, lastPopup);
324
325
if (aFlush) {
326
// The popup's visibility doesn't update until the minimize animation
327
// has finished, so call UpdateWidgetGeometry to update it right away.
328
viewManager->UpdateWidgetGeometry();
329
}
330
}
331
}
332
333
return consume;
334
}
335
336
////////////////////////////////////////////////////////////////////////
337
bool nsXULPopupManager::ShouldRollupOnMouseWheelEvent() {
338
// should rollup only for autocomplete widgets
339
// XXXndeakin this should really be something the popup has more control over
340
341
nsMenuChainItem* item = GetTopVisibleMenu();
342
if (!item) return false;
343
344
nsIContent* content = item->Frame()->GetContent();
345
if (!content || !content->IsElement()) return false;
346
347
Element* element = content->AsElement();
348
if (element->AttrValueIs(kNameSpaceID_None, nsGkAtoms::rolluponmousewheel,
349
nsGkAtoms::_true, eCaseMatters))
350
return true;
351
352
if (element->AttrValueIs(kNameSpaceID_None, nsGkAtoms::rolluponmousewheel,
353
nsGkAtoms::_false, eCaseMatters))
354
return false;
355
356
nsAutoString value;
357
element->GetAttr(kNameSpaceID_None, nsGkAtoms::type, value);
358
return StringBeginsWith(value, NS_LITERAL_STRING("autocomplete"));
359
}
360
361
bool nsXULPopupManager::ShouldConsumeOnMouseWheelEvent() {
362
nsMenuChainItem* item = GetTopVisibleMenu();
363
if (!item) return false;
364
365
nsMenuPopupFrame* frame = item->Frame();
366
if (frame->PopupType() != ePopupTypePanel) return true;
367
368
return !frame->GetContent()->AsElement()->AttrValueIs(
369
kNameSpaceID_None, nsGkAtoms::type, nsGkAtoms::arrow, eCaseMatters);
370
}
371
372
// a menu should not roll up if activated by a mouse activate message (eg.
373
// X-mouse)
374
bool nsXULPopupManager::ShouldRollupOnMouseActivate() { return false; }
375
376
uint32_t nsXULPopupManager::GetSubmenuWidgetChain(
377
nsTArray<nsIWidget*>* aWidgetChain) {
378
// this method is used by the widget code to determine the list of popups
379
// that are open. If a mouse click occurs outside one of these popups, the
380
// panels will roll up. If the click is inside a popup, they will not roll up
381
uint32_t count = 0, sameTypeCount = 0;
382
383
NS_ASSERTION(aWidgetChain, "null parameter");
384
nsMenuChainItem* item = GetTopVisibleMenu();
385
while (item) {
386
nsMenuChainItem* parent = item->GetParent();
387
if (!item->IsNoAutoHide()) {
388
nsCOMPtr<nsIWidget> widget = item->Frame()->GetWidget();
389
NS_ASSERTION(widget, "open popup has no widget");
390
aWidgetChain->AppendElement(widget.get());
391
// In the case when a menulist inside a panel is open, clicking in the
392
// panel should still roll up the menu, so if a different type is found,
393
// stop scanning.
394
if (!sameTypeCount) {
395
count++;
396
if (!parent ||
397
item->Frame()->PopupType() != parent->Frame()->PopupType() ||
398
item->IsContextMenu() != parent->IsContextMenu()) {
399
sameTypeCount = count;
400
}
401
}
402
}
403
404
item = parent;
405
}
406
407
return sameTypeCount;
408
}
409
410
nsIWidget* nsXULPopupManager::GetRollupWidget() {
411
nsMenuChainItem* item = GetTopVisibleMenu();
412
return item ? item->Frame()->GetWidget() : nullptr;
413
}
414
415
void nsXULPopupManager::AdjustPopupsOnWindowChange(
416
nsPIDOMWindowOuter* aWindow) {
417
// When the parent window is moved, adjust any child popups. Dismissable
418
// menus and panels are expected to roll up when a window is moved, so there
419
// is no need to check these popups, only the noautohide popups.
420
421
// The items are added to a list so that they can be adjusted bottom to top.
422
nsTArray<nsMenuPopupFrame*> list;
423
424
nsMenuChainItem* item = mPopups;
425
while (item) {
426
// only move popups that are within the same window and where auto
427
// positioning has not been disabled
428
nsMenuPopupFrame* frame = item->Frame();
429
if (item->IsNoAutoHide() && frame->GetAutoPosition()) {
430
nsIContent* popup = frame->GetContent();
431
if (popup) {
432
Document* document = popup->GetUncomposedDoc();
433
if (document) {
434
if (nsPIDOMWindowOuter* window = document->GetWindow()) {
435
window = window->GetPrivateRoot();
436
if (window == aWindow) {
437
list.AppendElement(frame);
438
}
439
}
440
}
441
}
442
}
443
444
item = item->GetParent();
445
}
446
447
for (int32_t l = list.Length() - 1; l >= 0; l--) {
448
list[l]->SetPopupPosition(nullptr, true, false, true);
449
}
450
}
451
452
void nsXULPopupManager::AdjustPopupsOnWindowChange(PresShell* aPresShell) {
453
if (aPresShell->GetDocument()) {
454
AdjustPopupsOnWindowChange(aPresShell->GetDocument()->GetWindow());
455
}
456
}
457
458
static nsMenuPopupFrame* GetPopupToMoveOrResize(nsIFrame* aFrame) {
459
nsMenuPopupFrame* menuPopupFrame = do_QueryFrame(aFrame);
460
if (!menuPopupFrame) return nullptr;
461
462
// no point moving or resizing hidden popups
463
if (!menuPopupFrame->IsVisible()) return nullptr;
464
465
nsIWidget* widget = menuPopupFrame->GetWidget();
466
if (widget && !widget->IsVisible()) return nullptr;
467
468
return menuPopupFrame;
469
}
470
471
void nsXULPopupManager::PopupMoved(nsIFrame* aFrame, nsIntPoint aPnt) {
472
nsMenuPopupFrame* menuPopupFrame = GetPopupToMoveOrResize(aFrame);
473
if (!menuPopupFrame) return;
474
475
nsView* view = menuPopupFrame->GetView();
476
if (!view) return;
477
478
// Don't do anything if the popup is already at the specified location. This
479
// prevents recursive calls when a popup is positioned.
480
LayoutDeviceIntRect curDevSize = view->CalcWidgetBounds(eWindowType_popup);
481
nsIWidget* widget = menuPopupFrame->GetWidget();
482
if (curDevSize.x == aPnt.x && curDevSize.y == aPnt.y &&
483
(!widget ||
484
widget->GetClientOffset() == menuPopupFrame->GetLastClientOffset())) {
485
return;
486
}
487
488
// Update the popup's position using SetPopupPosition if the popup is
489
// anchored and at the parent level as these maintain their position
490
// relative to the parent window. Otherwise, just update the popup to
491
// the specified screen coordinates.
492
if (menuPopupFrame->IsAnchored() &&
493
menuPopupFrame->PopupLevel() == ePopupLevelParent) {
494
menuPopupFrame->SetPopupPosition(nullptr, true, false, true);
495
} else {
496
CSSPoint cssPos = LayoutDeviceIntPoint::FromUnknownPoint(aPnt) /
497
menuPopupFrame->PresContext()->CSSToDevPixelScale();
498
menuPopupFrame->MoveTo(RoundedToInt(cssPos), false);
499
}
500
}
501
502
void nsXULPopupManager::PopupResized(nsIFrame* aFrame,
503
LayoutDeviceIntSize aSize) {
504
nsMenuPopupFrame* menuPopupFrame = GetPopupToMoveOrResize(aFrame);
505
if (!menuPopupFrame) return;
506
507
nsView* view = menuPopupFrame->GetView();
508
if (!view) return;
509
510
LayoutDeviceIntRect curDevSize = view->CalcWidgetBounds(eWindowType_popup);
511
// If the size is what we think it is, we have nothing to do.
512
if (curDevSize.width == aSize.width && curDevSize.height == aSize.height)
513
return;
514
515
Element* popup = menuPopupFrame->GetContent()->AsElement();
516
517
// Only set the width and height if the popup already has these attributes.
518
if (!popup->HasAttr(kNameSpaceID_None, nsGkAtoms::width) ||
519
!popup->HasAttr(kNameSpaceID_None, nsGkAtoms::height)) {
520
return;
521
}
522
523
// The size is different. Convert the actual size to css pixels and store it
524
// as 'width' and 'height' attributes on the popup.
525
nsPresContext* presContext = menuPopupFrame->PresContext();
526
527
CSSIntSize newCSS(presContext->DevPixelsToIntCSSPixels(aSize.width),
528
presContext->DevPixelsToIntCSSPixels(aSize.height));
529
530
nsAutoString width, height;
531
width.AppendInt(newCSS.width);
532
height.AppendInt(newCSS.height);
533
popup->SetAttr(kNameSpaceID_None, nsGkAtoms::width, width, false);
534
popup->SetAttr(kNameSpaceID_None, nsGkAtoms::height, height, true);
535
}
536
537
nsMenuPopupFrame* nsXULPopupManager::GetPopupFrameForContent(
538
nsIContent* aContent, bool aShouldFlush) {
539
if (aShouldFlush) {
540
Document* document = aContent->GetUncomposedDoc();
541
if (document) {
542
if (RefPtr<PresShell> presShell = document->GetPresShell()) {
543
presShell->FlushPendingNotifications(FlushType::Layout);
544
}
545
}
546
}
547
548
return do_QueryFrame(aContent->GetPrimaryFrame());
549
}
550
551
nsMenuChainItem* nsXULPopupManager::GetTopVisibleMenu() {
552
nsMenuChainItem* item = mPopups;
553
while (item) {
554
if (!item->IsNoAutoHide() &&
555
item->Frame()->PopupState() != ePopupInvisible) {
556
return item;
557
}
558
item = item->GetParent();
559
}
560
561
return nullptr;
562
}
563
564
nsINode* nsXULPopupManager::GetMouseLocationParent() { return mRangeParent; }
565
566
int32_t nsXULPopupManager::MouseLocationOffset() { return mRangeOffset; }
567
568
void nsXULPopupManager::InitTriggerEvent(Event* aEvent, nsIContent* aPopup,
569
nsIContent** aTriggerContent) {
570
mCachedMousePoint = LayoutDeviceIntPoint(0, 0);
571
572
if (aTriggerContent) {
573
*aTriggerContent = nullptr;
574
if (aEvent) {
575
// get the trigger content from the event
576
nsCOMPtr<nsIContent> target = do_QueryInterface(aEvent->GetTarget());
577
target.forget(aTriggerContent);
578
}
579
}
580
581
mCachedModifiers = 0;
582
583
RefPtr<UIEvent> uiEvent = aEvent ? aEvent->AsUIEvent() : nullptr;
584
if (uiEvent) {
585
mRangeParent = uiEvent->GetRangeParent();
586
mRangeOffset = uiEvent->RangeOffset();
587
588
// get the event coordinates relative to the root frame of the document
589
// containing the popup.
590
NS_ASSERTION(aPopup, "Expected a popup node");
591
WidgetEvent* event = aEvent->WidgetEventPtr();
592
if (event) {
593
WidgetInputEvent* inputEvent = event->AsInputEvent();
594
if (inputEvent) {
595
mCachedModifiers = inputEvent->mModifiers;
596
}
597
Document* doc = aPopup->GetUncomposedDoc();
598
if (doc) {
599
PresShell* presShell = doc->GetPresShell();
600
nsPresContext* presContext;
601
if (presShell && (presContext = presShell->GetPresContext())) {
602
nsPresContext* rootDocPresContext = presContext->GetRootPresContext();
603
if (!rootDocPresContext) return;
604
nsIFrame* rootDocumentRootFrame =
605
rootDocPresContext->PresShell()->GetRootFrame();
606
if ((event->mClass == eMouseEventClass ||
607
event->mClass == eMouseScrollEventClass ||
608
event->mClass == eWheelEventClass) &&
609
!event->AsGUIEvent()->mWidget) {
610
// no widget, so just use the client point if available
611
MouseEvent* mouseEvent = aEvent->AsMouseEvent();
612
nsIntPoint clientPt(mouseEvent->ClientX(), mouseEvent->ClientY());
613
614
// XXX this doesn't handle IFRAMEs in transforms
615
nsPoint thisDocToRootDocOffset =
616
presShell->GetRootFrame()->GetOffsetToCrossDoc(
617
rootDocumentRootFrame);
618
// convert to device pixels
619
mCachedMousePoint.x = presContext->AppUnitsToDevPixels(
620
nsPresContext::CSSPixelsToAppUnits(clientPt.x) +
621
thisDocToRootDocOffset.x);
622
mCachedMousePoint.y = presContext->AppUnitsToDevPixels(
623
nsPresContext::CSSPixelsToAppUnits(clientPt.y) +
624
thisDocToRootDocOffset.y);
625
} else if (rootDocumentRootFrame) {
626
nsPoint pnt = nsLayoutUtils::GetEventCoordinatesRelativeTo(
627
event, rootDocumentRootFrame);
628
mCachedMousePoint = LayoutDeviceIntPoint(
629
rootDocPresContext->AppUnitsToDevPixels(pnt.x),
630
rootDocPresContext->AppUnitsToDevPixels(pnt.y));
631
}
632
}
633
}
634
}
635
} else {
636
mRangeParent = nullptr;
637
mRangeOffset = 0;
638
}
639
}
640
641
void nsXULPopupManager::SetActiveMenuBar(nsMenuBarFrame* aMenuBar,
642
bool aActivate) {
643
if (aActivate)
644
mActiveMenuBar = aMenuBar;
645
else if (mActiveMenuBar == aMenuBar)
646
mActiveMenuBar = nullptr;
647
648
UpdateKeyboardListeners();
649
}
650
651
void nsXULPopupManager::ShowMenu(nsIContent* aMenu, bool aSelectFirstItem,
652
bool aAsynchronous) {
653
nsMenuFrame* menuFrame = do_QueryFrame(aMenu->GetPrimaryFrame());
654
if (!menuFrame || !menuFrame->IsMenu()) return;
655
656
nsMenuPopupFrame* popupFrame = menuFrame->GetPopup();
657
if (!popupFrame || !MayShowPopup(popupFrame)) return;
658
659
// inherit whether or not we're a context menu from the parent
660
bool parentIsContextMenu = false;
661
bool onMenuBar = false;
662
bool onmenu = menuFrame->IsOnMenu();
663
664
nsMenuParent* parent = menuFrame->GetMenuParent();
665
if (parent && onmenu) {
666
parentIsContextMenu = parent->IsContextMenu();
667
onMenuBar = parent->IsMenuBar();
668
}
669
670
nsAutoString position;
671
672
#ifdef XP_MACOSX
673
if (aMenu->IsXULElement(nsGkAtoms::menulist)) {
674
position.AssignLiteral("selection");
675
} else
676
#endif
677
678
if (onMenuBar || !onmenu)
679
position.AssignLiteral("after_start");
680
else
681
position.AssignLiteral("end_before");
682
683
// there is no trigger event for menus
684
InitTriggerEvent(nullptr, nullptr, nullptr);
685
popupFrame->InitializePopup(aMenu, nullptr, position, 0, 0,
686
MenuPopupAnchorType_Node, true);
687
688
if (aAsynchronous) {
689
nsCOMPtr<nsIRunnable> event = new nsXULPopupShowingEvent(
690
popupFrame->GetContent(), parentIsContextMenu, aSelectFirstItem);
691
aMenu->OwnerDoc()->Dispatch(TaskCategory::Other, event.forget());
692
} else {
693
nsCOMPtr<nsIContent> popupContent = popupFrame->GetContent();
694
FirePopupShowingEvent(popupContent, parentIsContextMenu, aSelectFirstItem,
695
nullptr);
696
}
697
}
698
699
void nsXULPopupManager::ShowPopup(nsIContent* aPopup,
700
nsIContent* aAnchorContent,
701
const nsAString& aPosition, int32_t aXPos,
702
int32_t aYPos, bool aIsContextMenu,
703
bool aAttributesOverride,
704
bool aSelectFirstItem, Event* aTriggerEvent) {
705
nsMenuPopupFrame* popupFrame = GetPopupFrameForContent(aPopup, true);
706
if (!popupFrame || !MayShowPopup(popupFrame)) return;
707
708
nsCOMPtr<nsIContent> triggerContent;
709
InitTriggerEvent(aTriggerEvent, aPopup, getter_AddRefs(triggerContent));
710
711
popupFrame->InitializePopup(aAnchorContent, triggerContent, aPosition, aXPos,
712
aYPos, MenuPopupAnchorType_Node,
713
aAttributesOverride);
714
715
FirePopupShowingEvent(aPopup, aIsContextMenu, aSelectFirstItem,
716
aTriggerEvent);
717
}
718
719
void nsXULPopupManager::ShowPopupAtScreen(nsIContent* aPopup, int32_t aXPos,
720
int32_t aYPos, bool aIsContextMenu,
721
Event* aTriggerEvent) {
722
nsMenuPopupFrame* popupFrame = GetPopupFrameForContent(aPopup, true);
723
if (!popupFrame || !MayShowPopup(popupFrame)) return;
724
725
nsCOMPtr<nsIContent> triggerContent;
726
InitTriggerEvent(aTriggerEvent, aPopup, getter_AddRefs(triggerContent));
727
728
popupFrame->InitializePopupAtScreen(triggerContent, aXPos, aYPos,
729
aIsContextMenu);
730
FirePopupShowingEvent(aPopup, aIsContextMenu, false, aTriggerEvent);
731
}
732
733
void nsXULPopupManager::ShowPopupAtScreenRect(
734
nsIContent* aPopup, const nsAString& aPosition, const nsIntRect& aRect,
735
bool aIsContextMenu, bool aAttributesOverride, Event* aTriggerEvent) {
736
nsMenuPopupFrame* popupFrame = GetPopupFrameForContent(aPopup, true);
737
if (!popupFrame || !MayShowPopup(popupFrame)) return;
738
739
nsCOMPtr<nsIContent> triggerContent;
740
InitTriggerEvent(aTriggerEvent, aPopup, getter_AddRefs(triggerContent));
741
742
popupFrame->InitializePopupAtRect(triggerContent, aPosition, aRect,
743
aAttributesOverride);
744
745
FirePopupShowingEvent(aPopup, aIsContextMenu, false, aTriggerEvent);
746
}
747
748
void nsXULPopupManager::ShowTooltipAtScreen(nsIContent* aPopup,
749
nsIContent* aTriggerContent,
750
int32_t aXPos, int32_t aYPos) {
751
nsMenuPopupFrame* popupFrame = GetPopupFrameForContent(aPopup, true);
752
if (!popupFrame || !MayShowPopup(popupFrame)) return;
753
754
InitTriggerEvent(nullptr, nullptr, nullptr);
755
756
nsPresContext* pc = popupFrame->PresContext();
757
mCachedMousePoint = LayoutDeviceIntPoint(pc->CSSPixelsToDevPixels(aXPos),
758
pc->CSSPixelsToDevPixels(aYPos));
759
760
// coordinates are relative to the root widget
761
nsPresContext* rootPresContext = pc->GetRootPresContext();
762
if (rootPresContext) {
763
nsIWidget* rootWidget = rootPresContext->GetRootWidget();
764
if (rootWidget) {
765
mCachedMousePoint -= rootWidget->WidgetToScreenOffset();
766
}
767
}
768
769
popupFrame->InitializePopupAtScreen(aTriggerContent, aXPos, aYPos, false);
770
771
FirePopupShowingEvent(aPopup, false, false, nullptr);
772
}
773
774
static void CheckCaretDrawingState() {
775
// There is 1 caret per document, we need to find the focused
776
// document and erase its caret.
777
nsIFocusManager* fm = nsFocusManager::GetFocusManager();
778
if (fm) {
779
nsCOMPtr<mozIDOMWindowProxy> window;
780
fm->GetFocusedWindow(getter_AddRefs(window));
781
if (!window) return;
782
783
auto* piWindow = nsPIDOMWindowOuter::From(window);
784
MOZ_ASSERT(piWindow);
785
786
nsCOMPtr<Document> focusedDoc = piWindow->GetDoc();
787
if (!focusedDoc) return;
788
789
PresShell* presShell = focusedDoc->GetPresShell();
790
if (!presShell) {
791
return;
792
}
793
794
RefPtr<nsCaret> caret = presShell->GetCaret();
795
if (!caret) return;
796
caret->SchedulePaint();
797
}
798
}
799
800
void nsXULPopupManager::ShowPopupCallback(nsIContent* aPopup,
801
nsMenuPopupFrame* aPopupFrame,
802
bool aIsContextMenu,
803
bool aSelectFirstItem) {
804
nsPopupType popupType = aPopupFrame->PopupType();
805
bool ismenu = (popupType == ePopupTypeMenu);
806
807
// Popups normally hide when an outside click occurs. Panels may use
808
// the noautohide attribute to disable this behaviour. It is expected
809
// that the application will hide these popups manually. The tooltip
810
// listener will handle closing the tooltip also.
811
bool isNoAutoHide =
812
aPopupFrame->IsNoAutoHide() || popupType == ePopupTypeTooltip;
813
814
nsMenuChainItem* item =
815
new nsMenuChainItem(aPopupFrame, isNoAutoHide, aIsContextMenu, popupType);
816
if (!item) return;
817
818
// install keyboard event listeners for navigating menus. For panels, the
819
// escape key may be used to close the panel. However, the ignorekeys
820
// attribute may be used to disable adding these event listeners for popups
821
// that want to handle their own keyboard events.
822
nsAutoString ignorekeys;
823
if (aPopup->IsElement()) {
824
aPopup->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::ignorekeys,
825
ignorekeys);
826
}
827
if (ignorekeys.EqualsLiteral("true")) {
828
item->SetIgnoreKeys(eIgnoreKeys_True);
829
} else if (ignorekeys.EqualsLiteral("shortcuts")) {
830
item->SetIgnoreKeys(eIgnoreKeys_Shortcuts);
831
}
832
833
if (ismenu) {
834
// if the menu is on a menubar, use the menubar's listener instead
835
nsMenuFrame* menuFrame = do_QueryFrame(aPopupFrame->GetParent());
836
if (menuFrame) {
837
item->SetOnMenuBar(menuFrame->IsOnMenuBar());
838
}
839
}
840
841
// use a weak frame as the popup will set an open attribute if it is a menu
842
AutoWeakFrame weakFrame(aPopupFrame);
843
aPopupFrame->ShowPopup(aIsContextMenu);
844
NS_ENSURE_TRUE_VOID(weakFrame.IsAlive());
845
846
item->UpdateFollowAnchor();
847
848
// popups normally hide when an outside click occurs. Panels may use
849
// the noautohide attribute to disable this behaviour. It is expected
850
// that the application will hide these popups manually. The tooltip
851
// listener will handle closing the tooltip also.
852
nsIContent* oldmenu = nullptr;
853
if (mPopups) {
854
oldmenu = mPopups->Content();
855
}
856
item->SetParent(mPopups);
857
mPopups = item;
858
SetCaptureState(oldmenu);
859
NS_ENSURE_TRUE_VOID(weakFrame.IsAlive());
860
861
if (aSelectFirstItem) {
862
nsMenuFrame* next = GetNextMenuItem(aPopupFrame, nullptr, true, false);
863
aPopupFrame->SetCurrentMenuItem(next);
864
}
865
866
if (ismenu) UpdateMenuItems(aPopup);
867
868
// Caret visibility may have been affected, ensure that
869
// the caret isn't now drawn when it shouldn't be.
870
CheckCaretDrawingState();
871
}
872
873
void nsXULPopupManager::HidePopup(nsIContent* aPopup, bool aHideChain,
874
bool aDeselectMenu, bool aAsynchronous,
875
bool aIsCancel, nsIContent* aLastPopup) {
876
nsMenuPopupFrame* popupFrame = do_QueryFrame(aPopup->GetPrimaryFrame());
877
if (!popupFrame) {
878
return;
879
}
880
881
nsMenuChainItem* foundPopup = mPopups;
882
while (foundPopup) {
883
if (foundPopup->Content() == aPopup) {
884
break;
885
}
886
foundPopup = foundPopup->GetParent();
887
}
888
889
bool deselectMenu = false;
890
nsCOMPtr<nsIContent> popupToHide, nextPopup, lastPopup;
891
892
if (foundPopup) {
893
if (foundPopup->IsNoAutoHide()) {
894
// If this is a noautohide panel, remove it but don't close any other
895
// panels.
896
popupToHide = aPopup;
897
} else {
898
// At this point, foundPopup will be set to the found item in the list. If
899
// foundPopup is the topmost menu, the one to remove, then there are no
900
// other popups to hide. If foundPopup is not the topmost menu, then there
901
// may be open submenus below it. In this case, we need to make sure that
902
// those submenus are closed up first. To do this, we scan up the menu
903
// list to find the topmost popup with only menus between it and
904
// foundPopup and close that menu first. In synchronous mode, the
905
// FirePopupHidingEvent method will be called which in turn calls
906
// HidePopupCallback to close up the next popup in the chain. These two
907
// methods will be called in sequence recursively to close up all the
908
// necessary popups. In asynchronous mode, a similar process occurs except
909
// that the FirePopupHidingEvent method is called asynchronously. In
910
// either case, nextPopup is set to the content node of the next popup to
911
// close, and lastPopup is set to the last popup in the chain to close,
912
// which will be aPopup, or null to close up all menus.
913
914
nsMenuChainItem* topMenu = foundPopup;
915
// Use IsMenu to ensure that foundPopup is a menu and scan down the child
916
// list until a non-menu is found. If foundPopup isn't a menu at all,
917
// don't scan and just close up this menu.
918
if (foundPopup->IsMenu()) {
919
nsMenuChainItem* child = foundPopup->GetChild();
920
while (child && child->IsMenu()) {
921
topMenu = child;
922
child = child->GetChild();
923
}
924
}
925
926
deselectMenu = aDeselectMenu;
927
popupToHide = topMenu->Content();
928
popupFrame = topMenu->Frame();
929
930
// Close up another popup if there is one, and we are either hiding the
931
// entire chain or the item to hide isn't the topmost popup.
932
nsMenuChainItem* parent = topMenu->GetParent();
933
if (parent && (aHideChain || topMenu != foundPopup)) {
934
while (parent && parent->IsNoAutoHide()) {
935
parent = parent->GetParent();
936
}
937
938
if (parent) {
939
nextPopup = parent->Content();
940
}
941
}
942
943
lastPopup = aLastPopup ? aLastPopup : (aHideChain ? nullptr : aPopup);
944
}
945
} else if (popupFrame->PopupState() == ePopupPositioning) {
946
// When the popup is in the popuppositioning state, it will not be in the
947
// mPopups list. We need another way to find it and make sure it does not
948
// continue the popup showing process.
949
deselectMenu = aDeselectMenu;
950
popupToHide = aPopup;
951
}
952
953
if (popupToHide) {
954
nsPopupState state = popupFrame->PopupState();
955
// If the popup is already being hidden, don't attempt to hide it again
956
if (state == ePopupHiding) {
957
return;
958
}
959
960
// Change the popup state to hiding. Don't set the hiding state if the
961
// popup is invisible, otherwise nsMenuPopupFrame::HidePopup will
962
// run again. In the invisible state, we just want the events to fire.
963
if (state != ePopupInvisible) {
964
popupFrame->SetPopupState(ePopupHiding);
965
}
966
967
// For menus, popupToHide is always the frontmost item in the list to hide.
968
if (aAsynchronous) {
969
nsCOMPtr<nsIRunnable> event = new nsXULPopupHidingEvent(
970
popupToHide, nextPopup, lastPopup, popupFrame->PopupType(),
971
deselectMenu, aIsCancel);
972
aPopup->OwnerDoc()->Dispatch(TaskCategory::Other, event.forget());
973
} else {
974
RefPtr<nsPresContext> presContext = popupFrame->PresContext();
975
FirePopupHidingEvent(popupToHide, nextPopup, lastPopup, presContext,
976
popupFrame->PopupType(), deselectMenu, aIsCancel);
977
}
978
}
979
}
980
981
// This is used to hide the popup after a transition finishes.
982
class TransitionEnder final : public nsIDOMEventListener {
983
protected:
984
virtual ~TransitionEnder() {}
985
986
public:
987
nsCOMPtr<nsIContent> mContent;
988
bool mDeselectMenu;
989
990
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
991
NS_DECL_CYCLE_COLLECTION_CLASS(TransitionEnder)
992
993
TransitionEnder(nsIContent* aContent, bool aDeselectMenu)
994
: mContent(aContent), mDeselectMenu(aDeselectMenu) {}
995
996
NS_IMETHOD HandleEvent(Event* aEvent) override {
997
mContent->RemoveSystemEventListener(NS_LITERAL_STRING("transitionend"),
998
this, false);
999
1000
nsMenuPopupFrame* popupFrame = do_QueryFrame(mContent->GetPrimaryFrame());
1001
1002
// Now hide the popup. There could be other properties transitioning, but
1003
// we'll assume they all end at the same time and just hide the popup upon
1004
// the first one ending.
1005
nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
1006
if (pm && popupFrame) {
1007
pm->HidePopupCallback(mContent, popupFrame, nullptr, nullptr,
1008
popupFrame->PopupType(), mDeselectMenu);
1009
}
1010
1011
return NS_OK;
1012
}
1013
};
1014
1015
NS_IMPL_CYCLE_COLLECTING_ADDREF(TransitionEnder)
1016
NS_IMPL_CYCLE_COLLECTING_RELEASE(TransitionEnder)
1017
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(TransitionEnder)
1018
NS_INTERFACE_MAP_ENTRY(nsIDOMEventListener)
1019
NS_INTERFACE_MAP_ENTRY(nsISupports)
1020
NS_INTERFACE_MAP_END
1021
1022
NS_IMPL_CYCLE_COLLECTION(TransitionEnder, mContent);
1023
1024
void nsXULPopupManager::HidePopupCallback(
1025
nsIContent* aPopup, nsMenuPopupFrame* aPopupFrame, nsIContent* aNextPopup,
1026
nsIContent* aLastPopup, nsPopupType aPopupType, bool aDeselectMenu) {
1027
if (mCloseTimer && mTimerMenu == aPopupFrame) {
1028
mCloseTimer->Cancel();
1029
mCloseTimer = nullptr;
1030
mTimerMenu = nullptr;
1031
}
1032
1033
// The popup to hide is aPopup. Search the list again to find the item that
1034
// corresponds to the popup to hide aPopup. This is done because it's
1035
// possible someone added another item (attempted to open another popup)
1036
// or removed a popup frame during the event processing so the item isn't at
1037
// the front anymore.
1038
nsMenuChainItem* item = mPopups;
1039
while (item) {
1040
if (item->Content() == aPopup) {
1041
item->Detach(&mPopups);
1042
SetCaptureState(aPopup);
1043
break;
1044
}
1045
item = item->GetParent();
1046
}
1047
1048
delete item;
1049
1050
AutoWeakFrame weakFrame(aPopupFrame);
1051
aPopupFrame->HidePopup(aDeselectMenu, ePopupClosed);
1052
NS_ENSURE_TRUE_VOID(weakFrame.IsAlive());
1053
1054
// send the popuphidden event synchronously. This event has no default
1055
// behaviour.
1056
nsEventStatus status = nsEventStatus_eIgnore;
1057
WidgetMouseEvent event(true, eXULPopupHidden, nullptr,
1058
WidgetMouseEvent::eReal);
1059
EventDispatcher::Dispatch(aPopup, aPopupFrame->PresContext(), &event, nullptr,
1060
&status);
1061
NS_ENSURE_TRUE_VOID(weakFrame.IsAlive());
1062
1063
// Force any popups that might be anchored on elements within this popup to
1064
// update.
1065
UpdatePopupPositions(aPopupFrame->PresContext()->RefreshDriver());
1066
1067
// if there are more popups to close, look for the next one
1068
if (aNextPopup && aPopup != aLastPopup) {
1069
nsMenuChainItem* foundMenu = nullptr;
1070
nsMenuChainItem* item = mPopups;
1071
while (item) {
1072
if (item->Content() == aNextPopup) {
1073
foundMenu = item;
1074
break;
1075
}
1076
item = item->GetParent();
1077
}
1078
1079
// continue hiding the chain of popups until the last popup aLastPopup
1080
// is reached, or until a popup of a different type is reached. This
1081
// last check is needed so that a menulist inside a non-menu panel only
1082
// closes the menu and not the panel as well.
1083
if (foundMenu && (aLastPopup || aPopupType == foundMenu->PopupType())) {
1084
nsCOMPtr<nsIContent> popupToHide = item->Content();
1085
nsMenuChainItem* parent = item->GetParent();
1086
1087
nsCOMPtr<nsIContent> nextPopup;
1088
if (parent && popupToHide != aLastPopup) nextPopup = parent->Content();
1089
1090
nsMenuPopupFrame* popupFrame = item->Frame();
1091
nsPopupState state = popupFrame->PopupState();
1092
if (state == ePopupHiding) return;
1093
if (state != ePopupInvisible) popupFrame->SetPopupState(ePopupHiding);
1094
1095
RefPtr<nsPresContext> presContext = popupFrame->PresContext();
1096
FirePopupHidingEvent(popupToHide, nextPopup, aLastPopup, presContext,
1097
foundMenu->PopupType(), aDeselectMenu, false);
1098
}
1099
}
1100
}
1101
1102
void nsXULPopupManager::HidePopupAfterDelay(nsMenuPopupFrame* aPopup) {
1103
// Don't close up immediately.
1104
// Kick off a close timer.
1105
KillMenuTimer();
1106
1107
int32_t menuDelay =
1108
LookAndFeel::GetInt(LookAndFeel::eIntID_SubmenuDelay, 300); // ms
1109
1110
// Kick off the timer.
1111
nsIEventTarget* target = nullptr;
1112
if (nsIContent* content = aPopup->GetContent()) {
1113
target = content->OwnerDoc()->EventTargetFor(TaskCategory::Other);
1114
}
1115
NS_NewTimerWithFuncCallback(
1116
getter_AddRefs(mCloseTimer),
1117
[](nsITimer* aTimer, void* aClosure) {
1118
nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
1119
if (pm) {
1120
pm->KillMenuTimer();
1121
}
1122
},
1123
nullptr, menuDelay, nsITimer::TYPE_ONE_SHOT, "KillMenuTimer", target);
1124
1125
// the popup will call PopupDestroyed if it is destroyed, which checks if it
1126
// is set to mTimerMenu, so it should be safe to keep a reference to it
1127
mTimerMenu = aPopup;
1128
}
1129
1130
void nsXULPopupManager::HidePopupsInList(
1131
const nsTArray<nsMenuPopupFrame*>& aFrames) {
1132
// Create a weak frame list. This is done in a separate array with the
1133
// right capacity predetermined to avoid multiple allocations.
1134
nsTArray<WeakFrame> weakPopups(aFrames.Length());
1135
uint32_t f;
1136
for (f = 0; f < aFrames.Length(); f++) {
1137
WeakFrame* wframe = weakPopups.AppendElement();
1138
if (wframe) *wframe = aFrames[f];
1139
}
1140
1141
for (f = 0; f < weakPopups.Length(); f++) {
1142
// check to ensure that the frame is still alive before hiding it.
1143
if (weakPopups[f].IsAlive()) {
1144
nsMenuPopupFrame* frame =
1145
static_cast<nsMenuPopupFrame*>(weakPopups[f].GetFrame());
1146
frame->HidePopup(true, ePopupInvisible);
1147
}
1148
}
1149
1150
SetCaptureState(nullptr);
1151
}
1152
1153
void nsXULPopupManager::EnableRollup(nsIContent* aPopup, bool aShouldRollup) {
1154
#ifndef MOZ_GTK
1155
nsMenuChainItem* item = mPopups;
1156
while (item) {
1157
if (item->Content() == aPopup) {
1158
nsIContent* oldmenu = nullptr;
1159
if (mPopups) {
1160
oldmenu = mPopups->Content();
1161
}
1162
1163
item->SetNoAutoHide(!aShouldRollup);
1164
SetCaptureState(oldmenu);
1165
return;
1166
}
1167
item = item->GetParent();
1168
}
1169
#endif
1170
}
1171
1172
bool nsXULPopupManager::IsChildOfDocShell(Document* aDoc,
1173
nsIDocShellTreeItem* aExpected) {
1174
nsCOMPtr<nsIDocShellTreeItem> docShellItem(aDoc->GetDocShell());
1175
while (docShellItem) {
1176
if (docShellItem == aExpected) return true;
1177
1178
nsCOMPtr<nsIDocShellTreeItem> parent;
1179
docShellItem->GetInProcessParent(getter_AddRefs(parent));
1180
docShellItem = parent;
1181
}
1182
1183
return false;
1184
}
1185
1186
void nsXULPopupManager::HidePopupsInDocShell(
1187
nsIDocShellTreeItem* aDocShellToHide) {
1188
nsTArray<nsMenuPopupFrame*> popupsToHide;
1189
1190
// iterate to get the set of popup frames to hide
1191
nsMenuChainItem* item = mPopups;
1192
while (item) {
1193
nsMenuChainItem* parent = item->GetParent();
1194
if (item->Frame()->PopupState() != ePopupInvisible &&
1195
IsChildOfDocShell(item->Content()->OwnerDoc(), aDocShellToHide)) {
1196
nsMenuPopupFrame* frame = item->Frame();
1197
item->Detach(&mPopups);
1198
delete item;
1199
popupsToHide.AppendElement(frame);
1200
}
1201
item = parent;
1202
}
1203
1204
HidePopupsInList(popupsToHide);
1205
}
1206
1207
void nsXULPopupManager::UpdatePopupPositions(nsRefreshDriver* aRefreshDriver) {
1208
nsMenuChainItem* item = mPopups;
1209
while (item) {
1210
if (item->Frame()->PresContext()->RefreshDriver() == aRefreshDriver) {
1211
item->CheckForAnchorChange();
1212
}
1213
1214
item = item->GetParent();
1215
}
1216
}
1217
1218
void nsXULPopupManager::UpdateFollowAnchor(nsMenuPopupFrame* aPopup) {
1219
nsMenuChainItem* item = mPopups;
1220
while (item) {
1221
if (item->Frame() == aPopup) {
1222
item->UpdateFollowAnchor();
1223
break;
1224
}
1225
1226
item = item->GetParent();
1227
}
1228
}
1229
1230
void nsXULPopupManager::ExecuteMenu(nsIContent* aMenu,
1231
nsXULMenuCommandEvent* aEvent) {
1232
CloseMenuMode cmm = CloseMenuMode_Auto;
1233
1234
static Element::AttrValuesArray strings[] = {nsGkAtoms::none,
1235
nsGkAtoms::single, nullptr};
1236
1237
if (aMenu->IsElement()) {
1238
switch (aMenu->AsElement()->FindAttrValueIn(
1239
kNameSpaceID_None, nsGkAtoms::closemenu, strings, eCaseMatters)) {
1240
case 0:
1241
cmm = CloseMenuMode_None;
1242
break;
1243
case 1:
1244
cmm = CloseMenuMode_Single;
1245
break;
1246
default:
1247
break;
1248
}
1249
}
1250
1251
// When a menuitem is selected to be executed, first hide all the open
1252
// popups, but don't remove them yet. This is needed when a menu command
1253
// opens a modal dialog. The views associated with the popups needed to be
1254
// hidden and the accesibility events fired before the command executes, but
1255
// the popuphiding/popuphidden events are fired afterwards.
1256
nsTArray<nsMenuPopupFrame*> popupsToHide;
1257
nsMenuChainItem* item = GetTopVisibleMenu();
1258
if (cmm != CloseMenuMode_None) {
1259
while (item) {
1260
// if it isn't a <menupopup>, don't close it automatically
1261
if (!item->IsMenu()) break;
1262
nsMenuChainItem* next = item->GetParent();
1263
popupsToHide.AppendElement(item->Frame());
1264
if (cmm == CloseMenuMode_Single) // only close one level of menu
1265
break;
1266
item = next;
1267
}
1268
1269
// Now hide the popups. If the closemenu mode is auto, deselect the menu,
1270
// otherwise only one popup is closing, so keep the parent menu selected.
1271
HidePopupsInList(popupsToHide);
1272
}
1273
1274
aEvent->SetCloseMenuMode(cmm);
1275
nsCOMPtr<nsIRunnable> event = aEvent;
1276
aMenu->OwnerDoc()->Dispatch(TaskCategory::Other, event.forget());
1277
}
1278
1279
void nsXULPopupManager::FirePopupShowingEvent(nsIContent* aPopup,
1280
bool aIsContextMenu,
1281
bool aSelectFirstItem,
1282
Event* aTriggerEvent) {
1283
nsCOMPtr<nsIContent> popup = aPopup; // keep a strong reference to the popup
1284
1285
nsMenuPopupFrame* popupFrame = do_QueryFrame(aPopup->GetPrimaryFrame());
1286
if (!popupFrame) return;
1287
1288
popupFrame->GenerateFrames();
1289
1290
// get the frame again
1291
popupFrame = do_QueryFrame(aPopup->GetPrimaryFrame());
1292
if (!popupFrame) return;
1293
1294
nsPresContext* presContext = popupFrame->PresContext();
1295
RefPtr<PresShell> presShell = presContext->PresShell();
1296
presShell->FrameNeedsReflow(popupFrame, IntrinsicDirty::TreeChange,
1297
NS_FRAME_HAS_DIRTY_CHILDREN);
1298
1299
nsPopupType popupType = popupFrame->PopupType();
1300
1301
// cache the popup so that document.popupNode can retrieve the trigger node
1302
// during the popupshowing event. It will be cleared below after the event
1303
// has fired.
1304
mOpeningPopup = aPopup;
1305
1306
nsEventStatus status = nsEventStatus_eIgnore;
1307
WidgetMouseEvent event(true, eXULPopupShowing, nullptr,
1308
WidgetMouseEvent::eReal);
1309
1310
// coordinates are relative to the root widget
1311
nsPresContext* rootPresContext =
1312
presShell->GetPresContext()->GetRootPresContext();
1313
if (rootPresContext) {
1314
rootPresContext->PresShell()->GetViewManager()->GetRootWidget(
1315
getter_AddRefs(event.mWidget));
1316
} else {
1317
event.mWidget = nullptr;
1318
}
1319
1320
if (aTriggerEvent) {
1321
WidgetMouseEventBase* mouseEvent =
1322
aTriggerEvent->WidgetEventPtr()->AsMouseEventBase();
1323
if (mouseEvent) {
1324
event.mInputSource = mouseEvent->mInputSource;
1325
}
1326
}
1327
1328
event.mRefPoint = mCachedMousePoint;
1329
event.mModifiers = mCachedModifiers;
1330
EventDispatcher::Dispatch(popup, presContext, &event, nullptr, &status);
1331
1332
mCachedMousePoint = LayoutDeviceIntPoint(0, 0);
1333
mOpeningPopup = nullptr;
1334
1335
mCachedModifiers = 0;
1336
1337
// if a panel, blur whatever has focus so that the panel can take the focus.
1338
// This is done after the popupshowing event in case that event is cancelled.
1339
// Using noautofocus="true" will disable this behaviour, which is needed for
1340
// the autocomplete widget as it manages focus itself.
1341
if (popupType == ePopupTypePanel &&
1342
!popup->AsElement()->AttrValueIs(kNameSpaceID_None,
1343
nsGkAtoms::noautofocus, nsGkAtoms::_true,
1344
eCaseMatters)) {
1345
nsFocusManager* fm = nsFocusManager::GetFocusManager();
1346
if (fm) {
1347
Document* doc = popup->GetUncomposedDoc();
1348
1349
// Only remove the focus if the currently focused item is ouside the
1350
// popup. It isn't a big deal if the current focus is in a child popup
1351
// inside the popup as that shouldn't be visible. This check ensures that
1352
// a node inside the popup that is focused during a popupshowing event
1353
// remains focused.
1354
RefPtr<Element> currentFocus = fm->GetFocusedElement();
1355
if (doc && currentFocus &&
1356
!nsContentUtils::ContentIsCrossDocDescendantOf(currentFocus, popup)) {
1357
fm->ClearFocus(doc->GetWindow());
1358
}
1359
}
1360
}
1361
1362
// clear these as they are no longer valid
1363
mRangeParent = nullptr;
1364
mRangeOffset = 0;
1365
1366
// get the frame again in case it went away
1367
popupFrame = do_QueryFrame(aPopup->GetPrimaryFrame());
1368
if (popupFrame) {
1369
// if the event was cancelled, don't open the popup, reset its state back
1370
// to closed and clear its trigger content.
1371
if (status == nsEventStatus_eConsumeNoDefault) {
1372
popupFrame->SetPopupState(ePopupClosed);
1373
popupFrame->ClearTriggerContent();
1374
} else {
1375
// Now check if we need to fire the popuppositioned event. If not, call
1376
// ShowPopupCallback directly.
1377
1378
// The popuppositioned event only fires on arrow panels for now.
1379
if (popup->AsElement()->AttrValueIs(kNameSpaceID_None, nsGkAtoms::type,
1380
nsGkAtoms::arrow, eCaseMatters)) {
1381
popupFrame->ShowWithPositionedEvent();
1382
presShell->FrameNeedsReflow(popupFrame, IntrinsicDirty::TreeChange,
1383
NS_FRAME_HAS_DIRTY_CHILDREN);
1384
} else {
1385
ShowPopupCallback(popup, popupFrame, aIsContextMenu, aSelectFirstItem);
1386
}
1387
}
1388
}
1389
}
1390
1391
void nsXULPopupManager::FirePopupHidingEvent(
1392
nsIContent* aPopup, nsIContent* aNextPopup, nsIContent* aLastPopup,
1393
nsPresContext* aPresContext, nsPopupType aPopupType, bool aDeselectMenu,
1394
bool aIsCancel) {
1395
RefPtr<PresShell> presShell = aPresContext->PresShell();
1396
mozilla::Unused << presShell; // This presShell may be keeping things alive
1397
// on non GTK platforms
1398
1399
nsEventStatus status = nsEventStatus_eIgnore;
1400
WidgetMouseEvent event(true, eXULPopupHiding, nullptr,
1401
WidgetMouseEvent::eReal);
1402
EventDispatcher::Dispatch(aPopup, aPresContext, &event, nullptr, &status);
1403
1404
// when a panel is closed, blur whatever has focus inside the popup
1405
if (aPopupType == ePopupTypePanel &&
1406
(!aPopup->IsElement() || !aPopup->AsElement()->AttrValueIs(
1407
kNameSpaceID_None, nsGkAtoms::noautofocus,
1408
nsGkAtoms::_true, eCaseMatters))) {
1409
nsFocusManager* fm = nsFocusManager::GetFocusManager();
1410
if (fm) {
1411
Document* doc = aPopup->GetUncomposedDoc();
1412
1413
// Remove the focus from the focused node only if it is inside the popup.
1414
RefPtr<Element> currentFocus = fm->GetFocusedElement();
1415
if (doc && currentFocus &&
1416
nsContentUtils::ContentIsCrossDocDescendantOf(currentFocus, aPopup)) {
1417
fm->ClearFocus(doc->GetWindow());
1418
}
1419
}
1420
}
1421
1422
// get frame again in case it went away
1423
nsMenuPopupFrame* popupFrame = do_QueryFrame(aPopup->GetPrimaryFrame());
1424
if (popupFrame) {
1425
// if the event was cancelled, don't hide the popup, and reset its
1426
// state back to open. Only popups in chrome shells can prevent a popup
1427
// from hiding.
1428
if (status == nsEventStatus_eConsumeNoDefault &&
1429
!popupFrame->IsInContentShell()) {
1430
// XXXndeakin
1431
// If an attempt was made to hide this popup before the popupshown event
1432
// fired, then ePopupShown is set here even though it should be
1433
// ePopupVisible. This probably isn't worth the hassle of handling.
1434
popupFrame->SetPopupState(ePopupShown);
1435
} else {
1436
// If the popup has an animate attribute and it is not set to false, check
1437
// if it has a closing transition and wait for it to finish. The
1438
// transition may still occur either way, but the view will be hidden and
1439
// you won't be able to see it. If there is a next popup, indicating that
1440
// mutliple popups are rolling up, don't wait and hide the popup right
1441
// away since the effect would likely be undesirable.
1442
if (StaticPrefs::xul_panel_animations_enabled() && !aNextPopup &&
1443
aPopup->IsElement() &&
1444
aPopup->AsElement()->HasAttr(kNameSpaceID_None, nsGkAtoms::animate)) {
1445
// If animate="false" then don't transition at all. If animate="cancel",
1446
// only show the transition if cancelling the popup or rolling up.
1447
// Otherwise, always show the transition.
1448
nsAutoString animate;
1449
aPopup->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::animate,
1450
animate);
1451
1452
if (!animate.EqualsLiteral("false") &&
1453
(!animate.EqualsLiteral("cancel") || aIsCancel)) {
1454
presShell->FlushPendingNotifications(FlushType::Layout);
1455
1456
// Get the frame again in case the flush caused it to go away
1457
popupFrame = do_QueryFrame(aPopup->GetPrimaryFrame());
1458
if (!popupFrame) return;
1459
1460
if (AnimationUtils::HasCurrentTransitions(
1461
aPopup->AsElement(), PseudoStyleType::NotPseudo)) {
1462
RefPtr<TransitionEnder> ender =
1463
new TransitionEnder(aPopup, aDeselectMenu);
1464
aPopup->AddSystemEventListener(NS_LITERAL_STRING("transitionend"),
1465
ender, false, false);
1466
return;
1467
}
1468
}
1469
}
1470
1471
HidePopupCallback(aPopup, popupFrame, aNextPopup, aLastPopup, aPopupType,
1472
aDeselectMenu);
1473
}
1474
}
1475
}
1476
1477
bool nsXULPopupManager::IsPopupOpen(nsIContent* aPopup) {
1478
// a popup is open if it is in the open list. The assertions ensure that the
1479
// frame is in the correct state. If the popup is in the hiding or invisible
1480
// state, it will still be in the open popup list until it is closed.
1481
nsMenuChainItem* item = mPopups;
1482
while (item) {
1483
if (item->Content() == aPopup) {
1484
NS_ASSERTION(item->Frame()->IsOpen() ||
1485
item->Frame()->PopupState() == ePopupHiding ||
1486
item->Frame()->PopupState() == ePopupInvisible,
1487
"popup in open list not actually open");
1488
return true;
1489
}
1490
item = item->GetParent();
1491
}
1492
1493
return false;
1494
}
1495
1496
bool nsXULPopupManager::IsPopupOpenForMenuParent(nsMenuParent* aMenuParent) {
1497
nsMenuChainItem* item = GetTopVisibleMenu();
1498
while (item) {
1499
nsMenuPopupFrame* popup = item->Frame();
1500
if (popup && popup->IsOpen()) {
1501
nsMenuFrame* menuFrame = do_QueryFrame(popup->GetParent());
1502
if (menuFrame && menuFrame->GetMenuParent() == aMenuParent) {
1503
return true;
1504
}
1505
}
1506
item = item->GetParent();
1507
}
1508
1509
return false;
1510
}
1511
1512
nsIFrame* nsXULPopupManager::GetTopPopup(nsPopupType aType) {
1513
nsMenuChainItem* item = mPopups;
1514
while (item) {
1515
if (item->Frame()->IsVisible() &&
1516
(item->PopupType() == aType || aType == ePopupTypeAny)) {
1517
return item->Frame();
1518
}
1519
1520
item = item->GetParent();
1521
}
1522
1523
return nullptr;
1524
}
1525
1526
void nsXULPopupManager::GetVisiblePopups(nsTArray<nsIFrame*>& aPopups) {
1527
aPopups.Clear();
1528
1529
nsMenuChainItem* item = mPopups;
1530
while (item) {
1531
// Skip panels which are not visible as well as popups that
1532
// are transparent to mouse events.
1533
if (item->Frame()->IsVisible() && !item->Frame()->IsMouseTransparent()) {
1534
aPopups.AppendElement(item->Frame());
1535
}
1536
1537
item = item->GetParent();
1538
}
1539
}
1540
1541
already_AddRefed<nsINode> nsXULPopupManager::GetLastTriggerNode(
1542
Document* aDocument, bool aIsTooltip) {
1543
if (!aDocument) return nullptr;
1544
1545
nsCOMPtr<nsINode> node;
1546
1547
// if mOpeningPopup is set, it means that a popupshowing event is being
1548
// fired. In this case, just use the cached node, as the popup is not yet in
1549
// the list of open popups.
1550
if (mOpeningPopup && mOpeningPopup->GetUncomposedDoc() == aDocument &&
1551
aIsTooltip == mOpeningPopup->IsXULElement(nsGkAtoms::tooltip)) {
1552
nsCOMPtr<nsIContent> openingPopup = mOpeningPopup;
1553
node = nsMenuPopupFrame::GetTriggerContent(
1554
GetPopupFrameForContent(openingPopup, false));
1555
} else {
1556
nsMenuChainItem* item = mPopups;
1557
while (item) {
1558
// look for a popup of the same type and document.
1559
if ((item->PopupType() == ePopupTypeTooltip) == aIsTooltip &&
1560
item->Content()->GetUncomposedDoc() == aDocument) {
1561
node = nsMenuPopupFrame::GetTriggerContent(item->Frame());
1562
if (node) break;
1563
}
1564
item = item->GetParent();
1565
}
1566
}
1567
1568
return node.forget();
1569
}
1570
1571
bool nsXULPopupManager::MayShowPopup(nsMenuPopupFrame* aPopup) {
1572
// if a popup's IsOpen method returns true, then the popup must always be in
1573
// the popup chain scanned in IsPopupOpen.
1574
NS_ASSERTION(!aPopup->IsOpen() || IsPopupOpen(aPopup->GetContent()),
1575
"popup frame state doesn't match XULPopupManager open state");
1576
1577
nsPopupState state = aPopup->PopupState();
1578
1579
// if the popup is not in the open popup chain, then it must have a state that
1580
// is either closed, in the process of being shown, or invisible.
1581
NS_ASSERTION(IsPopupOpen(aPopup->GetContent()) || state == ePopupClosed ||
1582
state == ePopupShowing || state == ePopupPositioning ||
1583
state == ePopupInvisible,
1584
"popup not in XULPopupManager open list is open");
1585
1586
// don't show popups unless they are closed or invisible
1587
if (state != ePopupClosed && state != ePopupInvisible) return false;
1588
1589
// Don't show popups that we already have in our popup chain
1590
if (IsPopupOpen(aPopup->GetContent())) {
1591
NS_WARNING("Refusing to show duplicate popup");
1592
return false;
1593
}
1594
1595
// if the popup was just rolled up, don't reopen it
1596
if (mozilla::widget::nsAutoRollup::GetLastRollup() == aPopup->GetContent())
1597
return false;
1598
1599
nsCOMPtr<nsIDocShellTreeItem> dsti = aPopup->PresContext()->GetDocShell();
1600
nsCOMPtr<nsIBaseWindow> baseWin = do_QueryInterface(dsti);
1601
if (!baseWin) return false;
1602
1603
nsCOMPtr<nsIDocShellTreeItem> root;
1604
dsti->GetInProcessRootTreeItem(getter_AddRefs(root));
1605
if (!root) {
1606
return false;
1607
}
1608
1609
nsCOMPtr<nsPIDOMWindowOuter> rootWin = root->GetWindow();
1610
1611
// chrome shells can always open popups, but other types of shells can only
1612
// open popups when they are focused and visible
1613
if (dsti->ItemType() != nsIDocShellTreeItem::typeChrome) {
1614
// only allow popups in active windows
1615
nsIFocusManager* fm = nsFocusManager::GetFocusManager();
1616
if (!fm || !rootWin) return false;
1617
1618
nsCOMPtr<mozIDOMWindowProxy> activeWindow;
1619
fm->GetActiveWindow(getter_AddRefs(activeWindow));
1620
if (activeWindow != rootWin) return false;
1621
1622
// only allow popups in visible frames
1623
bool visible;
1624
baseWin->GetVisibility(&visible);
1625
if (!visible) return false;
1626
}
1627
1628
// platforms respond differently when an popup is opened in a minimized
1629
// window, so this is always disabled.
1630
nsCOMPtr<nsIWidget> mainWidget;
1631
baseWin->GetMainWidget(getter_AddRefs(mainWidget));
1632
if (mainWidget && mainWidget->SizeMode() == nsSizeMode_Minimized) {
1633
return false;
1634
}
1635
1636
#ifdef XP_MACOSX
1637
if (rootWin) {
1638
auto globalWin = nsGlobalWindowOuter::Cast(rootWin.get());
1639
if (globalWin->IsInModalState()) {
1640
return false;
1641
}
1642
}
1643
#endif
1644
1645
// cannot open a popup that is a submenu of a menupopup that isn't open.
1646
nsMenuFrame* menuFrame = do_QueryFrame(aPopup->GetParent());
1647
if (menuFrame) {
1648
nsMenuParent* parentPopup = menuFrame->GetMenuParent();
1649
if (parentPopup && !parentPopup->IsOpen()) return false;
1650
}
1651
1652
return true;
1653
}
1654
1655
void nsXULPopupManager::PopupDestroyed(nsMenuPopupFrame* aPopup) {
1656
// when a popup frame is destroyed, just unhook it from the list of popups
1657
if (mTimerMenu == aPopup) {
1658
if (mCloseTimer) {
1659
mCloseTimer->Cancel();
1660
mCloseTimer = nullptr;
1661
}
1662
mTimerMenu = nullptr;
1663
}
1664
1665
nsTArray<nsMenuPopupFrame*> popupsToHide;
1666
1667
nsMenuChainItem* item = mPopups;
1668
while (item) {
1669
nsMenuPopupFrame* frame = item->Frame();
1670
if (frame == aPopup) {
1671
// XXXndeakin shouldn't this only happen for menus?
1672
if (!item->IsNoAutoHide() && frame->PopupState() != ePopupInvisible) {
1673
// Iterate through any child menus and hide them as well, since the
1674
// parent is going away. We won't remove them from the list yet, just
1675
// hide them, as they will be removed from the list when this function
1676
// gets called for that child frame.
1677
nsMenuChainItem* child = item->GetChild();
1678
while (child) {
1679
// if the popup is a child frame of the menu that was destroyed, add
1680
// it to the list of popups to hide. Don't bother with the events
1681
// since the frames are going away. If the child menu is not a child
1682
// frame, for example, a context menu, use HidePopup instead, but call
1683
// it asynchronously since we are in the middle of frame destruction.
1684
nsMenuPopupFrame* childframe = child->Frame();
1685
if (nsLayoutUtils::IsProperAncestorFrame(frame, childframe)) {
1686
popupsToHide.AppendElement(childframe);
1687
} else {
1688
// HidePopup will take care of hiding any of its children, so
1689
// break out afterwards
1690
HidePopup(child->Content(), false, false, true, false);
1691
break;
1692
}
1693
1694
child = child->GetChild();
1695
}
1696
}
1697
1698
item->Detach(&mPopups);
1699
delete item;
1700
break;
1701
}
1702
1703
item = item->GetParent();
1704
}
1705
1706
HidePopupsInList(popupsToHide);
1707
}
1708
1709
bool nsXULPopupManager::HasContextMenu(nsMenuPopupFrame* aPopup) {
1710
nsMenuChainItem* item = GetTopVisibleMenu();
1711
while (item && item->Frame() != aPopup) {
1712
if (item->IsContextMenu()) return true;
1713
item = item->GetParent();
1714
}
1715
1716
return false;
1717
}
1718
1719
void nsXULPopupManager::SetCaptureState(nsIContent* aOldPopup) {
1720
nsMenuChainItem* item = GetTopVisibleMenu();
1721
if (item && aOldPopup == item->Content()) return;
1722
1723
if (mWidget) {
1724
mWidget->CaptureRollupEvents(nullptr, false);
1725
mWidget = nullptr;
1726
}
1727
1728
if (item) {
1729
nsMenuPopupFrame* popup = item->Frame();
1730
mWidget = popup->GetWidget();
1731
if (mWidget) {
1732
mWidget->CaptureRollupEvents(nullptr, true);
1733
}
1734
}
1735
1736
UpdateKeyboardListeners();
1737
}
1738
1739
void nsXULPopupManager::UpdateKeyboardListeners() {
1740
nsCOMPtr<EventTarget> newTarget;
1741
bool isForMenu = false;
1742
nsMenuChainItem* item = GetTopVisibleMenu();
1743
if (item) {
1744
if (item->IgnoreKeys() != eIgnoreKeys_True) {
1745
newTarget = item->Content()->GetComposedDoc();
1746
}
1747
isForMenu = item->PopupType() == ePopupTypeMenu;
1748
} else if (mActiveMenuBar) {
1749
newTarget = mActiveMenuBar->GetContent()->GetComposedDoc();
1750
isForMenu = true;
1751
}
1752
1753
if (mKeyListener != newTarget) {
1754
if (mKeyListener) {
1755
mKeyListener->RemoveEventListener(NS_LITERAL_STRING("keypress"), this,
1756