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