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 "nsIDOMDocument.h"
15 #include "nsIDOMEvent.h"
16 #include "nsXULElement.h"
17 #include "nsIDOMXULMenuListElement.h"
18 #include "nsIDOMXULCommandDispatcher.h"
19 #include "nsCSSFrameConstructor.h"
20 #include "nsGlobalWindow.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 "nsIDOMMouseEvent.h"
31 #include "nsCaret.h"
32 #include "nsIDocument.h"
33 #include "nsPIWindowRoot.h"
34 #include "nsFrameManager.h"
35 #include "nsIObserverService.h"
36 #include "XULDocument.h"
37 #include "mozilla/dom/Element.h"
38 #include "mozilla/dom/Event.h" // for nsIDOMEvent::InternalDOMEvent()
39 #include "mozilla/dom/KeyboardEvent.h"
40 #include "mozilla/dom/KeyboardEventBinding.h"
41 #include "mozilla/dom/UIEvent.h"
42 #include "mozilla/EventDispatcher.h"
43 #include "mozilla/EventStateManager.h"
44 #include "mozilla/LookAndFeel.h"
45 #include "mozilla/MouseEvents.h"
46 #include "mozilla/Services.h"
47 #include "mozilla/widget/nsAutoRollup.h"
48
49 using namespace mozilla;
50 using namespace mozilla::dom;
51
52 static_assert(KeyboardEventBinding::DOM_VK_HOME ==
53 KeyboardEventBinding::DOM_VK_END + 1 &&
54 KeyboardEventBinding::DOM_VK_LEFT ==
55 KeyboardEventBinding::DOM_VK_END + 2 &&
56 KeyboardEventBinding::DOM_VK_UP ==
57 KeyboardEventBinding::DOM_VK_END + 3 &&
58 KeyboardEventBinding::DOM_VK_RIGHT ==
59 KeyboardEventBinding::DOM_VK_END + 4 &&
60 KeyboardEventBinding::DOM_VK_DOWN ==
61 KeyboardEventBinding::DOM_VK_END + 5,
62 "nsXULPopupManager assumes some keyCode values are consecutive");
63
64 const nsNavigationDirection DirectionFromKeyCodeTable[2][6] = {
65 {
66 eNavigationDirection_Last, // KeyboardEventBinding::DOM_VK_END
67 eNavigationDirection_First, // KeyboardEventBinding::DOM_VK_HOME
68 eNavigationDirection_Start, // KeyboardEventBinding::DOM_VK_LEFT
69 eNavigationDirection_Before, // KeyboardEventBinding::DOM_VK_UP
70 eNavigationDirection_End, // KeyboardEventBinding::DOM_VK_RIGHT
71 eNavigationDirection_After // KeyboardEventBinding::DOM_VK_DOWN
72 },
73 {
74 eNavigationDirection_Last, // KeyboardEventBinding::DOM_VK_END
75 eNavigationDirection_First, // KeyboardEventBinding::DOM_VK_HOME
76 eNavigationDirection_End, // KeyboardEventBinding::DOM_VK_LEFT
77 eNavigationDirection_Before, // KeyboardEventBinding::DOM_VK_UP
78 eNavigationDirection_Start, // KeyboardEventBinding::DOM_VK_RIGHT
79 eNavigationDirection_After // KeyboardEventBinding::DOM_VK_DOWN
80 }};
81
82 nsXULPopupManager* nsXULPopupManager::sInstance = nullptr;
83
Content()84 nsIContent* nsMenuChainItem::Content() { return mFrame->GetContent(); }
85
SetParent(nsMenuChainItem * aParent)86 void nsMenuChainItem::SetParent(nsMenuChainItem* aParent) {
87 if (mParent) {
88 NS_ASSERTION(mParent->mChild == this,
89 "Unexpected - parent's child not set to this");
90 mParent->mChild = nullptr;
91 }
92 mParent = aParent;
93 if (mParent) {
94 if (mParent->mChild) mParent->mChild->mParent = nullptr;
95 mParent->mChild = this;
96 }
97 }
98
Detach(nsMenuChainItem ** aRoot)99 void nsMenuChainItem::Detach(nsMenuChainItem** aRoot) {
100 // If the item has a child, set the child's parent to this item's parent,
101 // effectively removing the item from the chain. If the item has no child,
102 // just set the parent to null.
103 if (mChild) {
104 NS_ASSERTION(this != *aRoot,
105 "Unexpected - popup with child at end of chain");
106 mChild->SetParent(mParent);
107 } else {
108 // An item without a child should be the first item in the chain, so set
109 // the first item pointer, pointed to by aRoot, to the parent.
110 NS_ASSERTION(this == *aRoot,
111 "Unexpected - popup with no child not at end of chain");
112 *aRoot = mParent;
113 SetParent(nullptr);
114 }
115 }
116
UpdateFollowAnchor()117 void nsMenuChainItem::UpdateFollowAnchor() {
118 mFollowAnchor = mFrame->ShouldFollowAnchor(mCurrentRect);
119 }
120
CheckForAnchorChange()121 void nsMenuChainItem::CheckForAnchorChange() {
122 if (mFollowAnchor) {
123 mFrame->CheckForAnchorChange(mCurrentRect);
124 }
125 }
126
127 bool nsXULPopupManager::sDevtoolsDisableAutoHide = false;
128
129 const char* kPrefDevtoolsDisableAutoHide = "ui.popup.disable_autohide";
130
NS_IMPL_ISUPPORTS(nsXULPopupManager,nsIDOMEventListener,nsIObserver)131 NS_IMPL_ISUPPORTS(nsXULPopupManager, nsIDOMEventListener, nsIObserver)
132
133 nsXULPopupManager::nsXULPopupManager()
134 : mRangeOffset(0),
135 mCachedMousePoint(0, 0),
136 mCachedModifiers(0),
137 mActiveMenuBar(nullptr),
138 mPopups(nullptr),
139 mTimerMenu(nullptr) {
140 nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
141 if (obs) {
142 obs->AddObserver(this, "xpcom-shutdown", false);
143 }
144 Preferences::AddBoolVarCache(&sDevtoolsDisableAutoHide,
145 kPrefDevtoolsDisableAutoHide, false);
146 }
147
~nsXULPopupManager()148 nsXULPopupManager::~nsXULPopupManager() {
149 NS_ASSERTION(!mPopups, "XUL popups still open");
150 }
151
Init()152 nsresult nsXULPopupManager::Init() {
153 sInstance = new nsXULPopupManager();
154 NS_ENSURE_TRUE(sInstance, NS_ERROR_OUT_OF_MEMORY);
155 NS_ADDREF(sInstance);
156 return NS_OK;
157 }
158
Shutdown()159 void nsXULPopupManager::Shutdown() { NS_IF_RELEASE(sInstance); }
160
161 NS_IMETHODIMP
Observe(nsISupports * aSubject,const char * aTopic,const char16_t * aData)162 nsXULPopupManager::Observe(nsISupports* aSubject, const char* aTopic,
163 const char16_t* aData) {
164 if (!nsCRT::strcmp(aTopic, "xpcom-shutdown")) {
165 if (mKeyListener) {
166 mKeyListener->RemoveEventListener(NS_LITERAL_STRING("keypress"), this,
167 true);
168 mKeyListener->RemoveEventListener(NS_LITERAL_STRING("keydown"), this,
169 true);
170 mKeyListener->RemoveEventListener(NS_LITERAL_STRING("keyup"), this, true);
171 mKeyListener = nullptr;
172 }
173 mRangeParent = nullptr;
174 // mOpeningPopup is cleared explicitly soon after using it.
175 nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
176 if (obs) {
177 obs->RemoveObserver(this, "xpcom-shutdown");
178 }
179 }
180
181 return NS_OK;
182 }
183
GetInstance()184 nsXULPopupManager* nsXULPopupManager::GetInstance() {
185 MOZ_ASSERT(sInstance);
186 return sInstance;
187 }
188
Rollup(uint32_t aCount,bool aFlush,const nsIntPoint * pos,nsIContent ** aLastRolledUp)189 bool nsXULPopupManager::Rollup(uint32_t aCount, bool aFlush,
190 const nsIntPoint* pos,
191 nsIContent** aLastRolledUp) {
192 if (aLastRolledUp) {
193 *aLastRolledUp = nullptr;
194 }
195
196 // We can disable the autohide behavior via a pref to ease debugging.
197 if (nsXULPopupManager::sDevtoolsDisableAutoHide) {
198 // Required on linux to allow events to work on other targets.
199 if (mWidget) {
200 mWidget->CaptureRollupEvents(nullptr, false);
201 }
202 return false;
203 }
204
205 bool consume = false;
206
207 nsMenuChainItem* item = GetTopVisibleMenu();
208 if (item) {
209 if (aLastRolledUp) {
210 // We need to get the popup that will be closed last, so that widget can
211 // keep track of it so it doesn't reopen if a mousedown event is going to
212 // processed. Keep going up the menu chain to get the first level menu of
213 // the same type. If a different type is encountered it means we have,
214 // for example, a menulist or context menu inside a panel, and we want to
215 // treat these as distinct. It's possible that this menu doesn't end up
216 // closing because the popuphiding event was cancelled, but in that case
217 // we don't need to deal with the menu reopening as it will already still
218 // be open.
219 nsMenuChainItem* first = item;
220 while (first->GetParent()) {
221 nsMenuChainItem* parent = first->GetParent();
222 if (first->Frame()->PopupType() != parent->Frame()->PopupType() ||
223 first->IsContextMenu() != parent->IsContextMenu()) {
224 break;
225 }
226 first = parent;
227 }
228
229 *aLastRolledUp = first->Content();
230 }
231
232 ConsumeOutsideClicksResult consumeResult =
233 item->Frame()->ConsumeOutsideClicks();
234 consume = (consumeResult == ConsumeOutsideClicks_True);
235
236 bool rollup = true;
237
238 // If norolluponanchor is true, then don't rollup when clicking the anchor.
239 // This would be used to allow adjusting the caret position in an
240 // autocomplete field without hiding the popup for example.
241 bool noRollupOnAnchor =
242 (!consume && pos &&
243 item->Frame()->GetContent()->AsElement()->AttrValueIs(
244 kNameSpaceID_None, nsGkAtoms::norolluponanchor, nsGkAtoms::_true,
245 eCaseMatters));
246
247 // When ConsumeOutsideClicks_ParentOnly is used, always consume the click
248 // when the click was over the anchor. This way, clicking on a menu doesn't
249 // reopen the menu.
250 if ((consumeResult == ConsumeOutsideClicks_ParentOnly ||
251 noRollupOnAnchor) &&
252 pos) {
253 nsMenuPopupFrame* popupFrame = item->Frame();
254 CSSIntRect anchorRect;
255 if (popupFrame->IsAnchored()) {
256 // Check if the popup has a screen anchor rectangle. If not, get the
257 // rectangle from the anchor element.
258 anchorRect =
259 CSSIntRect::FromUnknownRect(popupFrame->GetScreenAnchorRect());
260 if (anchorRect.x == -1 || anchorRect.y == -1) {
261 nsCOMPtr<nsIContent> anchor = popupFrame->GetAnchor();
262
263 // Check if the anchor has indicated another node to use for checking
264 // for roll-up. That way, we can anchor a popup on anonymous content
265 // or an individual icon, while clicking elsewhere within a button or
266 // other container doesn't result in us re-opening the popup.
267 if (anchor && anchor->IsElement()) {
268 nsAutoString consumeAnchor;
269 anchor->AsElement()->GetAttr(
270 kNameSpaceID_None, nsGkAtoms::consumeanchor, consumeAnchor);
271 if (!consumeAnchor.IsEmpty()) {
272 nsIDocument* doc = anchor->GetOwnerDocument();
273 nsIContent* newAnchor = doc->GetElementById(consumeAnchor);
274 if (newAnchor) {
275 anchor = newAnchor;
276 }
277 }
278 }
279
280 if (anchor && anchor->GetPrimaryFrame()) {
281 anchorRect = anchor->GetPrimaryFrame()->GetScreenRect();
282 }
283 }
284 }
285
286 // It's possible that some other element is above the anchor at the same
287 // position, but the only thing that would happen is that the mouse
288 // event will get consumed, so here only a quick coordinates check is
289 // done rather than a slower complete check of what is at that location.
290 nsPresContext* presContext = item->Frame()->PresContext();
291 CSSIntPoint posCSSPixels(presContext->DevPixelsToIntCSSPixels(pos->x),
292 presContext->DevPixelsToIntCSSPixels(pos->y));
293 if (anchorRect.Contains(posCSSPixels)) {
294 if (consumeResult == ConsumeOutsideClicks_ParentOnly) {
295 consume = true;
296 }
297
298 if (noRollupOnAnchor) {
299 rollup = false;
300 }
301 }
302 }
303
304 if (rollup) {
305 // if a number of popups to close has been specified, determine the last
306 // popup to close
307 nsIContent* lastPopup = nullptr;
308 if (aCount != UINT32_MAX) {
309 nsMenuChainItem* last = item;
310 while (--aCount && last->GetParent()) {
311 last = last->GetParent();
312 }
313 if (last) {
314 lastPopup = last->Content();
315 }
316 }
317
318 nsPresContext* presContext = item->Frame()->PresContext();
319 RefPtr<nsViewManager> viewManager =
320 presContext->PresShell()->GetViewManager();
321
322 HidePopup(item->Content(), true, true, false, true, lastPopup);
323
324 if (aFlush) {
325 // The popup's visibility doesn't update until the minimize animation
326 // has finished, so call UpdateWidgetGeometry to update it right away.
327 viewManager->UpdateWidgetGeometry();
328 }
329 }
330 }
331
332 return consume;
333 }
334
335 ////////////////////////////////////////////////////////////////////////
ShouldRollupOnMouseWheelEvent()336 bool nsXULPopupManager::ShouldRollupOnMouseWheelEvent() {
337 // should rollup only for autocomplete widgets
338 // XXXndeakin this should really be something the popup has more control over
339
340 nsMenuChainItem* item = GetTopVisibleMenu();
341 if (!item) return false;
342
343 nsIContent* content = item->Frame()->GetContent();
344 if (!content || !content->IsElement()) return false;
345
346 Element* element = content->AsElement();
347 if (element->AttrValueIs(kNameSpaceID_None, nsGkAtoms::rolluponmousewheel,
348 nsGkAtoms::_true, eCaseMatters))
349 return true;
350
351 if (element->AttrValueIs(kNameSpaceID_None, nsGkAtoms::rolluponmousewheel,
352 nsGkAtoms::_false, eCaseMatters))
353 return false;
354
355 nsAutoString value;
356 element->GetAttr(kNameSpaceID_None, nsGkAtoms::type, value);
357 return StringBeginsWith(value, NS_LITERAL_STRING("autocomplete"));
358 }
359
ShouldConsumeOnMouseWheelEvent()360 bool nsXULPopupManager::ShouldConsumeOnMouseWheelEvent() {
361 nsMenuChainItem* item = GetTopVisibleMenu();
362 if (!item) return false;
363
364 nsMenuPopupFrame* frame = item->Frame();
365 if (frame->PopupType() != ePopupTypePanel) return true;
366
367 return !frame->GetContent()->AsElement()->AttrValueIs(
368 kNameSpaceID_None, nsGkAtoms::type, nsGkAtoms::arrow, eCaseMatters);
369 }
370
371 // a menu should not roll up if activated by a mouse activate message (eg.
372 // X-mouse)
ShouldRollupOnMouseActivate()373 bool nsXULPopupManager::ShouldRollupOnMouseActivate() { return false; }
374
GetSubmenuWidgetChain(nsTArray<nsIWidget * > * aWidgetChain)375 uint32_t nsXULPopupManager::GetSubmenuWidgetChain(
376 nsTArray<nsIWidget*>* aWidgetChain) {
377 // this method is used by the widget code to determine the list of popups
378 // that are open. If a mouse click occurs outside one of these popups, the
379 // panels will roll up. If the click is inside a popup, they will not roll up
380 uint32_t count = 0, sameTypeCount = 0;
381
382 NS_ASSERTION(aWidgetChain, "null parameter");
383 nsMenuChainItem* item = GetTopVisibleMenu();
384 while (item) {
385 nsMenuChainItem* parent = item->GetParent();
386 if (!item->IsNoAutoHide()) {
387 nsCOMPtr<nsIWidget> widget = item->Frame()->GetWidget();
388 NS_ASSERTION(widget, "open popup has no widget");
389 aWidgetChain->AppendElement(widget.get());
390 // In the case when a menulist inside a panel is open, clicking in the
391 // panel should still roll up the menu, so if a different type is found,
392 // stop scanning.
393 if (!sameTypeCount) {
394 count++;
395 if (!parent ||
396 item->Frame()->PopupType() != parent->Frame()->PopupType() ||
397 item->IsContextMenu() != parent->IsContextMenu()) {
398 sameTypeCount = count;
399 }
400 }
401 }
402
403 item = parent;
404 }
405
406 return sameTypeCount;
407 }
408
GetRollupWidget()409 nsIWidget* nsXULPopupManager::GetRollupWidget() {
410 nsMenuChainItem* item = GetTopVisibleMenu();
411 return item ? item->Frame()->GetWidget() : nullptr;
412 }
413
AdjustPopupsOnWindowChange(nsPIDOMWindowOuter * aWindow)414 void nsXULPopupManager::AdjustPopupsOnWindowChange(
415 nsPIDOMWindowOuter* aWindow) {
416 // When the parent window is moved, adjust any child popups. Dismissable
417 // menus and panels are expected to roll up when a window is moved, so there
418 // is no need to check these popups, only the noautohide popups.
419
420 // The items are added to a list so that they can be adjusted bottom to top.
421 nsTArray<nsMenuPopupFrame*> list;
422
423 nsMenuChainItem* item = mPopups;
424 while (item) {
425 // only move popups that are within the same window and where auto
426 // positioning has not been disabled
427 nsMenuPopupFrame* frame = item->Frame();
428 if (item->IsNoAutoHide() && frame->GetAutoPosition()) {
429 nsIContent* popup = frame->GetContent();
430 if (popup) {
431 nsIDocument* document = popup->GetUncomposedDoc();
432 if (document) {
433 if (nsPIDOMWindowOuter* window = document->GetWindow()) {
434 window = window->GetPrivateRoot();
435 if (window == aWindow) {
436 list.AppendElement(frame);
437 }
438 }
439 }
440 }
441 }
442
443 item = item->GetParent();
444 }
445
446 for (int32_t l = list.Length() - 1; l >= 0; l--) {
447 list[l]->SetPopupPosition(nullptr, true, false, true);
448 }
449 }
450
AdjustPopupsOnWindowChange(nsIPresShell * aPresShell)451 void nsXULPopupManager::AdjustPopupsOnWindowChange(nsIPresShell* aPresShell) {
452 if (aPresShell->GetDocument()) {
453 AdjustPopupsOnWindowChange(aPresShell->GetDocument()->GetWindow());
454 }
455 }
456
GetPopupToMoveOrResize(nsIFrame * aFrame)457 static nsMenuPopupFrame* GetPopupToMoveOrResize(nsIFrame* aFrame) {
458 nsMenuPopupFrame* menuPopupFrame = do_QueryFrame(aFrame);
459 if (!menuPopupFrame) return nullptr;
460
461 // no point moving or resizing hidden popups
462 if (!menuPopupFrame->IsVisible()) return nullptr;
463
464 nsIWidget* widget = menuPopupFrame->GetWidget();
465 if (widget && !widget->IsVisible()) return nullptr;
466
467 return menuPopupFrame;
468 }
469
PopupMoved(nsIFrame * aFrame,nsIntPoint aPnt)470 void nsXULPopupManager::PopupMoved(nsIFrame* aFrame, nsIntPoint aPnt) {
471 nsMenuPopupFrame* menuPopupFrame = GetPopupToMoveOrResize(aFrame);
472 if (!menuPopupFrame) return;
473
474 nsView* view = menuPopupFrame->GetView();
475 if (!view) return;
476
477 // Don't do anything if the popup is already at the specified location. This
478 // prevents recursive calls when a popup is positioned.
479 LayoutDeviceIntRect curDevSize = view->CalcWidgetBounds(eWindowType_popup);
480 nsIWidget* widget = menuPopupFrame->GetWidget();
481 if (curDevSize.x == aPnt.x && curDevSize.y == aPnt.y &&
482 (!widget ||
483 widget->GetClientOffset() == menuPopupFrame->GetLastClientOffset())) {
484 return;
485 }
486
487 // Update the popup's position using SetPopupPosition if the popup is
488 // anchored and at the parent level as these maintain their position
489 // relative to the parent window. Otherwise, just update the popup to
490 // the specified screen coordinates.
491 if (menuPopupFrame->IsAnchored() &&
492 menuPopupFrame->PopupLevel() == ePopupLevelParent) {
493 menuPopupFrame->SetPopupPosition(nullptr, true, false, true);
494 } else {
495 CSSPoint cssPos = LayoutDeviceIntPoint::FromUnknownPoint(aPnt) /
496 menuPopupFrame->PresContext()->CSSToDevPixelScale();
497 menuPopupFrame->MoveTo(RoundedToInt(cssPos), false);
498 }
499 }
500
PopupResized(nsIFrame * aFrame,LayoutDeviceIntSize aSize)501 void nsXULPopupManager::PopupResized(nsIFrame* aFrame,
502 LayoutDeviceIntSize aSize) {
503 nsMenuPopupFrame* menuPopupFrame = GetPopupToMoveOrResize(aFrame);
504 if (!menuPopupFrame) return;
505
506 nsView* view = menuPopupFrame->GetView();
507 if (!view) return;
508
509 LayoutDeviceIntRect curDevSize = view->CalcWidgetBounds(eWindowType_popup);
510 // If the size is what we think it is, we have nothing to do.
511 if (curDevSize.width == aSize.width && curDevSize.height == aSize.height)
512 return;
513
514 Element* popup = menuPopupFrame->GetContent()->AsElement();
515
516 // Only set the width and height if the popup already has these attributes.
517 if (!popup->HasAttr(kNameSpaceID_None, nsGkAtoms::width) ||
518 !popup->HasAttr(kNameSpaceID_None, nsGkAtoms::height)) {
519 return;
520 }
521
522 // The size is different. Convert the actual size to css pixels and store it
523 // as 'width' and 'height' attributes on the popup.
524 nsPresContext* presContext = menuPopupFrame->PresContext();
525
526 CSSIntSize newCSS(presContext->DevPixelsToIntCSSPixels(aSize.width),
527 presContext->DevPixelsToIntCSSPixels(aSize.height));
528
529 nsAutoString width, height;
530 width.AppendInt(newCSS.width);
531 height.AppendInt(newCSS.height);
532 popup->SetAttr(kNameSpaceID_None, nsGkAtoms::width, width, false);
533 popup->SetAttr(kNameSpaceID_None, nsGkAtoms::height, height, true);
534 }
535
GetPopupFrameForContent(nsIContent * aContent,bool aShouldFlush)536 nsMenuPopupFrame* nsXULPopupManager::GetPopupFrameForContent(
537 nsIContent* aContent, bool aShouldFlush) {
538 if (aShouldFlush) {
539 nsIDocument* document = aContent->GetUncomposedDoc();
540 if (document) {
541 nsCOMPtr<nsIPresShell> presShell = document->GetShell();
542 if (presShell) presShell->FlushPendingNotifications(FlushType::Layout);
543 }
544 }
545
546 return do_QueryFrame(aContent->GetPrimaryFrame());
547 }
548
GetTopVisibleMenu()549 nsMenuChainItem* nsXULPopupManager::GetTopVisibleMenu() {
550 nsMenuChainItem* item = mPopups;
551 while (item) {
552 if (!item->IsNoAutoHide() &&
553 item->Frame()->PopupState() != ePopupInvisible) {
554 return item;
555 }
556 item = item->GetParent();
557 }
558
559 return nullptr;
560 }
561
GetMouseLocationParent()562 nsINode* nsXULPopupManager::GetMouseLocationParent() { return mRangeParent; }
563
MouseLocationOffset()564 int32_t nsXULPopupManager::MouseLocationOffset() { return mRangeOffset; }
565
InitTriggerEvent(nsIDOMEvent * aEvent,nsIContent * aPopup,nsIContent ** aTriggerContent)566 void nsXULPopupManager::InitTriggerEvent(nsIDOMEvent* aEvent,
567 nsIContent* aPopup,
568 nsIContent** aTriggerContent) {
569 mCachedMousePoint = LayoutDeviceIntPoint(0, 0);
570
571 if (aTriggerContent) {
572 *aTriggerContent = nullptr;
573 if (aEvent) {
574 // get the trigger content from the event
575 nsCOMPtr<nsIContent> target =
576 do_QueryInterface(aEvent->InternalDOMEvent()->GetTarget());
577 target.forget(aTriggerContent);
578 }
579 }
580
581 mCachedModifiers = 0;
582
583 nsCOMPtr<nsIDOMUIEvent> domUiEvent = do_QueryInterface(aEvent);
584 if (domUiEvent) {
585 auto uiEvent = static_cast<UIEvent*>(domUiEvent.get());
586 mRangeParent = uiEvent->GetRangeParent();
587 mRangeOffset = uiEvent->RangeOffset();
588
589 // get the event coordinates relative to the root frame of the document
590 // containing the popup.
591 NS_ASSERTION(aPopup, "Expected a popup node");
592 WidgetEvent* event = aEvent->WidgetEventPtr();
593 if (event) {
594 WidgetInputEvent* inputEvent = event->AsInputEvent();
595 if (inputEvent) {
596 mCachedModifiers = inputEvent->mModifiers;
597 }
598 nsIDocument* doc = aPopup->GetUncomposedDoc();
599 if (doc) {
600 nsIPresShell* presShell = doc->GetShell();
601 nsPresContext* presContext;
602 if (presShell && (presContext = presShell->GetPresContext())) {
603 nsPresContext* rootDocPresContext = presContext->GetRootPresContext();
604 if (!rootDocPresContext) return;
605 nsIFrame* rootDocumentRootFrame =
606 rootDocPresContext->PresShell()->GetRootFrame();
607 if ((event->mClass == eMouseEventClass ||
608 event->mClass == eMouseScrollEventClass ||
609 event->mClass == eWheelEventClass) &&
610 !event->AsGUIEvent()->mWidget) {
611 // no widget, so just use the client point if available
612 nsCOMPtr<nsIDOMMouseEvent> mouseEvent = do_QueryInterface(aEvent);
613 nsIntPoint clientPt;
614 mouseEvent->GetClientX(&clientPt.x);
615 mouseEvent->GetClientY(&clientPt.y);
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
SetActiveMenuBar(nsMenuBarFrame * aMenuBar,bool aActivate)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
ShowMenu(nsIContent * aMenu,bool aSelectFirstItem,bool aAsynchronous)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 nsCOMPtr<nsIDOMXULMenuListElement> menulist = do_QueryInterface(aMenu);
677 bool isNonEditableMenulist = false;
678 if (menulist) {
679 bool editable;
680 menulist->GetEditable(&editable);
681 isNonEditableMenulist = !editable;
682 }
683
684 if (isNonEditableMenulist) {
685 position.AssignLiteral("selection");
686 } else
687 #endif
688
689 if (onMenuBar || !onmenu)
690 position.AssignLiteral("after_start");
691 else
692 position.AssignLiteral("end_before");
693
694 // there is no trigger event for menus
695 InitTriggerEvent(nullptr, nullptr, nullptr);
696 popupFrame->InitializePopup(menuFrame->GetAnchor(), nullptr, position, 0, 0,
697 MenuPopupAnchorType_Node, true);
698
699 if (aAsynchronous) {
700 nsCOMPtr<nsIRunnable> event = new nsXULPopupShowingEvent(
701 popupFrame->GetContent(), parentIsContextMenu, aSelectFirstItem);
702 aMenu->OwnerDoc()->Dispatch(TaskCategory::Other, event.forget());
703 } else {
704 nsCOMPtr<nsIContent> popupContent = popupFrame->GetContent();
705 FirePopupShowingEvent(popupContent, parentIsContextMenu, aSelectFirstItem,
706 nullptr);
707 }
708 }
709
ShowPopup(nsIContent * aPopup,nsIContent * aAnchorContent,const nsAString & aPosition,int32_t aXPos,int32_t aYPos,bool aIsContextMenu,bool aAttributesOverride,bool aSelectFirstItem,nsIDOMEvent * aTriggerEvent)710 void nsXULPopupManager::ShowPopup(
711 nsIContent* aPopup, nsIContent* aAnchorContent, const nsAString& aPosition,
712 int32_t aXPos, int32_t aYPos, bool aIsContextMenu, bool aAttributesOverride,
713 bool aSelectFirstItem, nsIDOMEvent* aTriggerEvent) {
714 nsMenuPopupFrame* popupFrame = GetPopupFrameForContent(aPopup, true);
715 if (!popupFrame || !MayShowPopup(popupFrame)) return;
716
717 nsCOMPtr<nsIContent> triggerContent;
718 InitTriggerEvent(aTriggerEvent, aPopup, getter_AddRefs(triggerContent));
719
720 popupFrame->InitializePopup(aAnchorContent, triggerContent, aPosition, aXPos,
721 aYPos, MenuPopupAnchorType_Node,
722 aAttributesOverride);
723
724 FirePopupShowingEvent(aPopup, aIsContextMenu, aSelectFirstItem,
725 aTriggerEvent);
726 }
727
ShowPopupAtScreen(nsIContent * aPopup,int32_t aXPos,int32_t aYPos,bool aIsContextMenu,nsIDOMEvent * aTriggerEvent)728 void nsXULPopupManager::ShowPopupAtScreen(nsIContent* aPopup, int32_t aXPos,
729 int32_t aYPos, bool aIsContextMenu,
730 nsIDOMEvent* aTriggerEvent) {
731 nsMenuPopupFrame* popupFrame = GetPopupFrameForContent(aPopup, true);
732 if (!popupFrame || !MayShowPopup(popupFrame)) return;
733
734 nsCOMPtr<nsIContent> triggerContent;
735 InitTriggerEvent(aTriggerEvent, aPopup, getter_AddRefs(triggerContent));
736
737 popupFrame->InitializePopupAtScreen(triggerContent, aXPos, aYPos,
738 aIsContextMenu);
739 FirePopupShowingEvent(aPopup, aIsContextMenu, false, aTriggerEvent);
740 }
741
ShowPopupAtScreenRect(nsIContent * aPopup,const nsAString & aPosition,const nsIntRect & aRect,bool aIsContextMenu,bool aAttributesOverride,nsIDOMEvent * aTriggerEvent)742 void nsXULPopupManager::ShowPopupAtScreenRect(
743 nsIContent* aPopup, const nsAString& aPosition, const nsIntRect& aRect,
744 bool aIsContextMenu, bool aAttributesOverride, nsIDOMEvent* aTriggerEvent) {
745 nsMenuPopupFrame* popupFrame = GetPopupFrameForContent(aPopup, true);
746 if (!popupFrame || !MayShowPopup(popupFrame)) return;
747
748 nsCOMPtr<nsIContent> triggerContent;
749 InitTriggerEvent(aTriggerEvent, aPopup, getter_AddRefs(triggerContent));
750
751 popupFrame->InitializePopupAtRect(triggerContent, aPosition, aRect,
752 aAttributesOverride);
753
754 FirePopupShowingEvent(aPopup, aIsContextMenu, false, aTriggerEvent);
755 }
756
ShowTooltipAtScreen(nsIContent * aPopup,nsIContent * aTriggerContent,int32_t aXPos,int32_t aYPos)757 void nsXULPopupManager::ShowTooltipAtScreen(nsIContent* aPopup,
758 nsIContent* aTriggerContent,
759 int32_t aXPos, int32_t aYPos) {
760 nsMenuPopupFrame* popupFrame = GetPopupFrameForContent(aPopup, true);
761 if (!popupFrame || !MayShowPopup(popupFrame)) return;
762
763 InitTriggerEvent(nullptr, nullptr, nullptr);
764
765 nsPresContext* pc = popupFrame->PresContext();
766 mCachedMousePoint = LayoutDeviceIntPoint(pc->CSSPixelsToDevPixels(aXPos),
767 pc->CSSPixelsToDevPixels(aYPos));
768
769 // coordinates are relative to the root widget
770 nsPresContext* rootPresContext = pc->GetRootPresContext();
771 if (rootPresContext) {
772 nsIWidget* rootWidget = rootPresContext->GetRootWidget();
773 if (rootWidget) {
774 mCachedMousePoint -= rootWidget->WidgetToScreenOffset();
775 }
776 }
777
778 popupFrame->InitializePopupAtScreen(aTriggerContent, aXPos, aYPos, false);
779
780 FirePopupShowingEvent(aPopup, false, false, nullptr);
781 }
782
ShowPopupWithAnchorAlign(nsIContent * aPopup,nsIContent * aAnchorContent,nsAString & aAnchor,nsAString & aAlign,int32_t aXPos,int32_t aYPos,bool aIsContextMenu)783 void nsXULPopupManager::ShowPopupWithAnchorAlign(
784 nsIContent* aPopup, nsIContent* aAnchorContent, nsAString& aAnchor,
785 nsAString& aAlign, int32_t aXPos, int32_t aYPos, bool aIsContextMenu) {
786 nsMenuPopupFrame* popupFrame = GetPopupFrameForContent(aPopup, true);
787 if (!popupFrame || !MayShowPopup(popupFrame)) return;
788
789 InitTriggerEvent(nullptr, nullptr, nullptr);
790
791 popupFrame->InitializePopupWithAnchorAlign(aAnchorContent, aAnchor, aAlign,
792 aXPos, aYPos);
793 FirePopupShowingEvent(aPopup, aIsContextMenu, false, nullptr);
794 }
795
CheckCaretDrawingState()796 static void CheckCaretDrawingState() {
797 // There is 1 caret per document, we need to find the focused
798 // document and erase its caret.
799 nsIFocusManager* fm = nsFocusManager::GetFocusManager();
800 if (fm) {
801 nsCOMPtr<mozIDOMWindowProxy> window;
802 fm->GetFocusedWindow(getter_AddRefs(window));
803 if (!window) return;
804
805 auto* piWindow = nsPIDOMWindowOuter::From(window);
806 MOZ_ASSERT(piWindow);
807
808 nsCOMPtr<nsIDocument> focusedDoc = piWindow->GetDoc();
809 if (!focusedDoc) return;
810
811 nsIPresShell* presShell = focusedDoc->GetShell();
812 if (!presShell) return;
813
814 RefPtr<nsCaret> caret = presShell->GetCaret();
815 if (!caret) return;
816 caret->SchedulePaint();
817 }
818 }
819
ShowPopupCallback(nsIContent * aPopup,nsMenuPopupFrame * aPopupFrame,bool aIsContextMenu,bool aSelectFirstItem)820 void nsXULPopupManager::ShowPopupCallback(nsIContent* aPopup,
821 nsMenuPopupFrame* aPopupFrame,
822 bool aIsContextMenu,
823 bool aSelectFirstItem) {
824 nsPopupType popupType = aPopupFrame->PopupType();
825 bool ismenu = (popupType == ePopupTypeMenu);
826
827 // Popups normally hide when an outside click occurs. Panels may use
828 // the noautohide attribute to disable this behaviour. It is expected
829 // that the application will hide these popups manually. The tooltip
830 // listener will handle closing the tooltip also.
831 bool isNoAutoHide =
832 aPopupFrame->IsNoAutoHide() || popupType == ePopupTypeTooltip;
833
834 nsMenuChainItem* item =
835 new nsMenuChainItem(aPopupFrame, isNoAutoHide, aIsContextMenu, popupType);
836 if (!item) return;
837
838 // install keyboard event listeners for navigating menus. For panels, the
839 // escape key may be used to close the panel. However, the ignorekeys
840 // attribute may be used to disable adding these event listeners for popups
841 // that want to handle their own keyboard events.
842 nsAutoString ignorekeys;
843 if (aPopup->IsElement()) {
844 aPopup->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::ignorekeys,
845 ignorekeys);
846 }
847 if (ignorekeys.EqualsLiteral("true")) {
848 item->SetIgnoreKeys(eIgnoreKeys_True);
849 } else if (ignorekeys.EqualsLiteral("shortcuts")) {
850 item->SetIgnoreKeys(eIgnoreKeys_Shortcuts);
851 }
852
853 if (ismenu) {
854 // if the menu is on a menubar, use the menubar's listener instead
855 nsMenuFrame* menuFrame = do_QueryFrame(aPopupFrame->GetParent());
856 if (menuFrame) {
857 item->SetOnMenuBar(menuFrame->IsOnMenuBar());
858 }
859 }
860
861 // use a weak frame as the popup will set an open attribute if it is a menu
862 AutoWeakFrame weakFrame(aPopupFrame);
863 aPopupFrame->ShowPopup(aIsContextMenu);
864 ENSURE_TRUE(weakFrame.IsAlive());
865
866 // popups normally hide when an outside click occurs. Panels may use
867 // the noautohide attribute to disable this behaviour. It is expected
868 // that the application will hide these popups manually. The tooltip
869 // listener will handle closing the tooltip also.
870 nsIContent* oldmenu = nullptr;
871 if (mPopups) {
872 oldmenu = mPopups->Content();
873 }
874 item->SetParent(mPopups);
875 mPopups = item;
876 SetCaptureState(oldmenu);
877 NS_ENSURE_TRUE_VOID(weakFrame.IsAlive());
878
879 item->UpdateFollowAnchor();
880
881 if (aSelectFirstItem) {
882 nsMenuFrame* next = GetNextMenuItem(aPopupFrame, nullptr, true, false);
883 aPopupFrame->SetCurrentMenuItem(next);
884 }
885
886 if (ismenu) UpdateMenuItems(aPopup);
887
888 // Caret visibility may have been affected, ensure that
889 // the caret isn't now drawn when it shouldn't be.
890 CheckCaretDrawingState();
891 }
892
HidePopup(nsIContent * aPopup,bool aHideChain,bool aDeselectMenu,bool aAsynchronous,bool aIsCancel,nsIContent * aLastPopup)893 void nsXULPopupManager::HidePopup(nsIContent* aPopup, bool aHideChain,
894 bool aDeselectMenu, bool aAsynchronous,
895 bool aIsCancel, nsIContent* aLastPopup) {
896 nsMenuPopupFrame* popupFrame = do_QueryFrame(aPopup->GetPrimaryFrame());
897 if (!popupFrame) {
898 return;
899 }
900
901 nsMenuChainItem* foundPopup = mPopups;
902 while (foundPopup) {
903 if (foundPopup->Content() == aPopup) {
904 break;
905 }
906 foundPopup = foundPopup->GetParent();
907 }
908
909 bool deselectMenu = false;
910 nsCOMPtr<nsIContent> popupToHide, nextPopup, lastPopup;
911
912 if (foundPopup) {
913 if (foundPopup->IsNoAutoHide()) {
914 // If this is a noautohide panel, remove it but don't close any other
915 // panels.
916 popupToHide = aPopup;
917 } else {
918 // At this point, foundPopup will be set to the found item in the list. If
919 // foundPopup is the topmost menu, the one to remove, then there are no
920 // other popups to hide. If foundPopup is not the topmost menu, then there
921 // may be open submenus below it. In this case, we need to make sure that
922 // those submenus are closed up first. To do this, we scan up the menu
923 // list to find the topmost popup with only menus between it and
924 // foundPopup and close that menu first. In synchronous mode, the
925 // FirePopupHidingEvent method will be called which in turn calls
926 // HidePopupCallback to close up the next popup in the chain. These two
927 // methods will be called in sequence recursively to close up all the
928 // necessary popups. In asynchronous mode, a similar process occurs except
929 // that the FirePopupHidingEvent method is called asynchronously. In
930 // either case, nextPopup is set to the content node of the next popup to
931 // close, and lastPopup is set to the last popup in the chain to close,
932 // which will be aPopup, or null to close up all menus.
933
934 nsMenuChainItem* topMenu = foundPopup;
935 // Use IsMenu to ensure that foundPopup is a menu and scan down the child
936 // list until a non-menu is found. If foundPopup isn't a menu at all,
937 // don't scan and just close up this menu.
938 if (foundPopup->IsMenu()) {
939 nsMenuChainItem* child = foundPopup->GetChild();
940 while (child && child->IsMenu()) {
941 topMenu = child;
942 child = child->GetChild();
943 }
944 }
945
946 deselectMenu = aDeselectMenu;
947 popupToHide = topMenu->Content();
948 popupFrame = topMenu->Frame();
949
950 // Close up another popup if there is one, and we are either hiding the
951 // entire chain or the item to hide isn't the topmost popup.
952 nsMenuChainItem* parent = topMenu->GetParent();
953 if (parent && (aHideChain || topMenu != foundPopup)) {
954 while (parent && parent->IsNoAutoHide()) {
955 parent = parent->GetParent();
956 }
957
958 if (parent) {
959 nextPopup = parent->Content();
960 }
961 }
962
963 lastPopup = aLastPopup ? aLastPopup : (aHideChain ? nullptr : aPopup);
964 }
965 } else if (popupFrame->PopupState() == ePopupPositioning) {
966 // When the popup is in the popuppositioning state, it will not be in the
967 // mPopups list. We need another way to find it and make sure it does not
968 // continue the popup showing process.
969 deselectMenu = aDeselectMenu;
970 popupToHide = aPopup;
971 }
972
973 if (popupToHide) {
974 nsPopupState state = popupFrame->PopupState();
975 // If the popup is already being hidden, don't attempt to hide it again
976 if (state == ePopupHiding) {
977 return;
978 }
979
980 // Change the popup state to hiding. Don't set the hiding state if the
981 // popup is invisible, otherwise nsMenuPopupFrame::HidePopup will
982 // run again. In the invisible state, we just want the events to fire.
983 if (state != ePopupInvisible) {
984 popupFrame->SetPopupState(ePopupHiding);
985 }
986
987 // For menus, popupToHide is always the frontmost item in the list to hide.
988 if (aAsynchronous) {
989 nsCOMPtr<nsIRunnable> event = new nsXULPopupHidingEvent(
990 popupToHide, nextPopup, lastPopup, popupFrame->PopupType(),
991 deselectMenu, aIsCancel);
992 aPopup->OwnerDoc()->Dispatch(TaskCategory::Other, event.forget());
993 } else {
994 FirePopupHidingEvent(popupToHide, nextPopup, lastPopup,
995 popupFrame->PresContext(), popupFrame->PopupType(),
996 deselectMenu, aIsCancel);
997 }
998 }
999 }
1000
1001 // This is used to hide the popup after a transition finishes.
1002 class TransitionEnder : public nsIDOMEventListener {
1003 protected:
~TransitionEnder()1004 virtual ~TransitionEnder() {}
1005
1006 public:
1007 nsCOMPtr<nsIContent> mContent;
1008 bool mDeselectMenu;
1009
1010 NS_DECL_CYCLE_COLLECTING_ISUPPORTS
NS_DECL_CYCLE_COLLECTION_CLASS(TransitionEnder)1011 NS_DECL_CYCLE_COLLECTION_CLASS(TransitionEnder)
1012
1013 TransitionEnder(nsIContent* aContent, bool aDeselectMenu)
1014 : mContent(aContent), mDeselectMenu(aDeselectMenu) {}
1015
HandleEvent(nsIDOMEvent * aEvent)1016 NS_IMETHOD HandleEvent(nsIDOMEvent* aEvent) override {
1017 mContent->RemoveSystemEventListener(NS_LITERAL_STRING("transitionend"),
1018 this, false);
1019
1020 nsMenuPopupFrame* popupFrame = do_QueryFrame(mContent->GetPrimaryFrame());
1021
1022 // Now hide the popup. There could be other properties transitioning, but
1023 // we'll assume they all end at the same time and just hide the popup upon
1024 // the first one ending.
1025 nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
1026 if (pm && popupFrame) {
1027 pm->HidePopupCallback(mContent, popupFrame, nullptr, nullptr,
1028 popupFrame->PopupType(), mDeselectMenu);
1029 }
1030
1031 return NS_OK;
1032 }
1033 };
1034
1035 NS_IMPL_CYCLE_COLLECTING_ADDREF(TransitionEnder)
1036 NS_IMPL_CYCLE_COLLECTING_RELEASE(TransitionEnder)
1037 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(TransitionEnder)
1038 NS_INTERFACE_MAP_ENTRY(nsIDOMEventListener)
1039 NS_INTERFACE_MAP_ENTRY(nsISupports)
1040 NS_INTERFACE_MAP_END
1041
1042 NS_IMPL_CYCLE_COLLECTION(TransitionEnder, mContent);
1043
HidePopupCallback(nsIContent * aPopup,nsMenuPopupFrame * aPopupFrame,nsIContent * aNextPopup,nsIContent * aLastPopup,nsPopupType aPopupType,bool aDeselectMenu)1044 void nsXULPopupManager::HidePopupCallback(
1045 nsIContent* aPopup, nsMenuPopupFrame* aPopupFrame, nsIContent* aNextPopup,
1046 nsIContent* aLastPopup, nsPopupType aPopupType, bool aDeselectMenu) {
1047 if (mCloseTimer && mTimerMenu == aPopupFrame) {
1048 mCloseTimer->Cancel();
1049 mCloseTimer = nullptr;
1050 mTimerMenu = nullptr;
1051 }
1052
1053 // The popup to hide is aPopup. Search the list again to find the item that
1054 // corresponds to the popup to hide aPopup. This is done because it's
1055 // possible someone added another item (attempted to open another popup)
1056 // or removed a popup frame during the event processing so the item isn't at
1057 // the front anymore.
1058 nsMenuChainItem* item = mPopups;
1059 while (item) {
1060 if (item->Content() == aPopup) {
1061 item->Detach(&mPopups);
1062 SetCaptureState(aPopup);
1063 break;
1064 }
1065 item = item->GetParent();
1066 }
1067
1068 delete item;
1069
1070 AutoWeakFrame weakFrame(aPopupFrame);
1071 aPopupFrame->HidePopup(aDeselectMenu, ePopupClosed);
1072 ENSURE_TRUE(weakFrame.IsAlive());
1073
1074 // send the popuphidden event synchronously. This event has no default
1075 // behaviour.
1076 nsEventStatus status = nsEventStatus_eIgnore;
1077 WidgetMouseEvent event(true, eXULPopupHidden, nullptr,
1078 WidgetMouseEvent::eReal);
1079 EventDispatcher::Dispatch(aPopup, aPopupFrame->PresContext(), &event, nullptr,
1080 &status);
1081 ENSURE_TRUE(weakFrame.IsAlive());
1082
1083 // Force any popups that might be anchored on elements within this popup to
1084 // update.
1085 UpdatePopupPositions(aPopupFrame->PresContext()->RefreshDriver());
1086
1087 // if there are more popups to close, look for the next one
1088 if (aNextPopup && aPopup != aLastPopup) {
1089 nsMenuChainItem* foundMenu = nullptr;
1090 nsMenuChainItem* item = mPopups;
1091 while (item) {
1092 if (item->Content() == aNextPopup) {
1093 foundMenu = item;
1094 break;
1095 }
1096 item = item->GetParent();
1097 }
1098
1099 // continue hiding the chain of popups until the last popup aLastPopup
1100 // is reached, or until a popup of a different type is reached. This
1101 // last check is needed so that a menulist inside a non-menu panel only
1102 // closes the menu and not the panel as well.
1103 if (foundMenu && (aLastPopup || aPopupType == foundMenu->PopupType())) {
1104 nsCOMPtr<nsIContent> popupToHide = item->Content();
1105 nsMenuChainItem* parent = item->GetParent();
1106
1107 nsCOMPtr<nsIContent> nextPopup;
1108 if (parent && popupToHide != aLastPopup) nextPopup = parent->Content();
1109
1110 nsMenuPopupFrame* popupFrame = item->Frame();
1111 nsPopupState state = popupFrame->PopupState();
1112 if (state == ePopupHiding) return;
1113 if (state != ePopupInvisible) popupFrame->SetPopupState(ePopupHiding);
1114
1115 FirePopupHidingEvent(popupToHide, nextPopup, aLastPopup,
1116 popupFrame->PresContext(), foundMenu->PopupType(),
1117 aDeselectMenu, false);
1118 }
1119 }
1120 }
1121
HidePopupAfterDelay(nsMenuPopupFrame * aPopup)1122 void nsXULPopupManager::HidePopupAfterDelay(nsMenuPopupFrame* aPopup) {
1123 // Don't close up immediately.
1124 // Kick off a close timer.
1125 KillMenuTimer();
1126
1127 int32_t menuDelay =
1128 LookAndFeel::GetInt(LookAndFeel::eIntID_SubmenuDelay, 300); // ms
1129
1130 // Kick off the timer.
1131 nsIEventTarget* target = nullptr;
1132 if (nsIContent* content = aPopup->GetContent()) {
1133 target = content->OwnerDoc()->EventTargetFor(TaskCategory::Other);
1134 }
1135 NS_NewTimerWithFuncCallback(
1136 getter_AddRefs(mCloseTimer),
1137 [](nsITimer* aTimer, void* aClosure) {
1138 nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
1139 if (pm) {
1140 pm->KillMenuTimer();
1141 }
1142 },
1143 nullptr, menuDelay, nsITimer::TYPE_ONE_SHOT, "KillMenuTimer", target);
1144
1145 // the popup will call PopupDestroyed if it is destroyed, which checks if it
1146 // is set to mTimerMenu, so it should be safe to keep a reference to it
1147 mTimerMenu = aPopup;
1148 }
1149
HidePopupsInList(const nsTArray<nsMenuPopupFrame * > & aFrames)1150 void nsXULPopupManager::HidePopupsInList(
1151 const nsTArray<nsMenuPopupFrame*>& aFrames) {
1152 // Create a weak frame list. This is done in a separate array with the
1153 // right capacity predetermined to avoid multiple allocations.
1154 nsTArray<WeakFrame> weakPopups(aFrames.Length());
1155 uint32_t f;
1156 for (f = 0; f < aFrames.Length(); f++) {
1157 WeakFrame* wframe = weakPopups.AppendElement();
1158 if (wframe) *wframe = aFrames[f];
1159 }
1160
1161 for (f = 0; f < weakPopups.Length(); f++) {
1162 // check to ensure that the frame is still alive before hiding it.
1163 if (weakPopups[f].IsAlive()) {
1164 nsMenuPopupFrame* frame =
1165 static_cast<nsMenuPopupFrame*>(weakPopups[f].GetFrame());
1166 frame->HidePopup(true, ePopupInvisible);
1167 }
1168 }
1169
1170 SetCaptureState(nullptr);
1171 }
1172
EnableRollup(nsIContent * aPopup,bool aShouldRollup)1173 void nsXULPopupManager::EnableRollup(nsIContent* aPopup, bool aShouldRollup) {
1174 #ifndef MOZ_GTK
1175 nsMenuChainItem* item = mPopups;
1176 while (item) {
1177 if (item->Content() == aPopup) {
1178 nsIContent* oldmenu = nullptr;
1179 if (mPopups) {
1180 oldmenu = mPopups->Content();
1181 }
1182
1183 item->SetNoAutoHide(!aShouldRollup);
1184 SetCaptureState(oldmenu);
1185 return;
1186 }
1187 item = item->GetParent();
1188 }
1189 #endif
1190 }
1191
IsChildOfDocShell(nsIDocument * aDoc,nsIDocShellTreeItem * aExpected)1192 bool nsXULPopupManager::IsChildOfDocShell(nsIDocument* aDoc,
1193 nsIDocShellTreeItem* aExpected) {
1194 nsCOMPtr<nsIDocShellTreeItem> docShellItem(aDoc->GetDocShell());
1195 while (docShellItem) {
1196 if (docShellItem == aExpected) return true;
1197
1198 nsCOMPtr<nsIDocShellTreeItem> parent;
1199 docShellItem->GetParent(getter_AddRefs(parent));
1200 docShellItem = parent;
1201 }
1202
1203 return false;
1204 }
1205
HidePopupsInDocShell(nsIDocShellTreeItem * aDocShellToHide)1206 void nsXULPopupManager::HidePopupsInDocShell(
1207 nsIDocShellTreeItem* aDocShellToHide) {
1208 nsTArray<nsMenuPopupFrame*> popupsToHide;
1209
1210 // iterate to get the set of popup frames to hide
1211 nsMenuChainItem* item = mPopups;
1212 while (item) {
1213 nsMenuChainItem* parent = item->GetParent();
1214 if (item->Frame()->PopupState() != ePopupInvisible &&
1215 IsChildOfDocShell(item->Content()->OwnerDoc(), aDocShellToHide)) {
1216 nsMenuPopupFrame* frame = item->Frame();
1217 item->Detach(&mPopups);
1218 delete item;
1219 popupsToHide.AppendElement(frame);
1220 }
1221 item = parent;
1222 }
1223
1224 HidePopupsInList(popupsToHide);
1225 }
1226
UpdatePopupPositions(nsRefreshDriver * aRefreshDriver)1227 void nsXULPopupManager::UpdatePopupPositions(nsRefreshDriver* aRefreshDriver) {
1228 nsMenuChainItem* item = mPopups;
1229 while (item) {
1230 if (item->Frame()->PresContext()->RefreshDriver() == aRefreshDriver) {
1231 item->CheckForAnchorChange();
1232 }
1233
1234 item = item->GetParent();
1235 }
1236 }
1237
UpdateFollowAnchor(nsMenuPopupFrame * aPopup)1238 void nsXULPopupManager::UpdateFollowAnchor(nsMenuPopupFrame* aPopup) {
1239 nsMenuChainItem* item = mPopups;
1240 while (item) {
1241 if (item->Frame() == aPopup) {
1242 item->UpdateFollowAnchor();
1243 break;
1244 }
1245
1246 item = item->GetParent();
1247 }
1248 }
1249
ExecuteMenu(nsIContent * aMenu,nsXULMenuCommandEvent * aEvent)1250 void nsXULPopupManager::ExecuteMenu(nsIContent* aMenu,
1251 nsXULMenuCommandEvent* aEvent) {
1252 CloseMenuMode cmm = CloseMenuMode_Auto;
1253
1254 static Element::AttrValuesArray strings[] = {&nsGkAtoms::none,
1255 &nsGkAtoms::single, nullptr};
1256
1257 if (aMenu->IsElement()) {
1258 switch (aMenu->AsElement()->FindAttrValueIn(
1259 kNameSpaceID_None, nsGkAtoms::closemenu, strings, eCaseMatters)) {
1260 case 0:
1261 cmm = CloseMenuMode_None;
1262 break;
1263 case 1:
1264 cmm = CloseMenuMode_Single;
1265 break;
1266 default:
1267 break;
1268 }
1269 }
1270
1271 // When a menuitem is selected to be executed, first hide all the open
1272 // popups, but don't remove them yet. This is needed when a menu command
1273 // opens a modal dialog. The views associated with the popups needed to be
1274 // hidden and the accesibility events fired before the command executes, but
1275 // the popuphiding/popuphidden events are fired afterwards.
1276 nsTArray<nsMenuPopupFrame*> popupsToHide;
1277 nsMenuChainItem* item = GetTopVisibleMenu();
1278 if (cmm != CloseMenuMode_None) {
1279 while (item) {
1280 // if it isn't a <menupopup>, don't close it automatically
1281 if (!item->IsMenu()) break;
1282 nsMenuChainItem* next = item->GetParent();
1283 popupsToHide.AppendElement(item->Frame());
1284 if (cmm == CloseMenuMode_Single) // only close one level of menu
1285 break;
1286 item = next;
1287 }
1288
1289 // Now hide the popups. If the closemenu mode is auto, deselect the menu,
1290 // otherwise only one popup is closing, so keep the parent menu selected.
1291 HidePopupsInList(popupsToHide);
1292 }
1293
1294 aEvent->SetCloseMenuMode(cmm);
1295 nsCOMPtr<nsIRunnable> event = aEvent;
1296 aMenu->OwnerDoc()->Dispatch(TaskCategory::Other, event.forget());
1297 }
1298
FirePopupShowingEvent(nsIContent * aPopup,bool aIsContextMenu,bool aSelectFirstItem,nsIDOMEvent * aTriggerEvent)1299 void nsXULPopupManager::FirePopupShowingEvent(nsIContent* aPopup,
1300 bool aIsContextMenu,
1301 bool aSelectFirstItem,
1302 nsIDOMEvent* aTriggerEvent) {
1303 nsCOMPtr<nsIContent> popup = aPopup; // keep a strong reference to the popup
1304
1305 nsMenuPopupFrame* popupFrame = do_QueryFrame(aPopup->GetPrimaryFrame());
1306 if (!popupFrame) return;
1307
1308 nsPresContext* presContext = popupFrame->PresContext();
1309 nsCOMPtr<nsIPresShell> presShell = presContext->PresShell();
1310 nsPopupType popupType = popupFrame->PopupType();
1311
1312 // generate the child frames if they have not already been generated
1313 if (!popupFrame->HasGeneratedChildren()) {
1314 popupFrame->SetGeneratedChildren();
1315 presShell->FrameConstructor()->GenerateChildFrames(popupFrame);
1316 }
1317
1318 // get the frame again
1319 nsIFrame* frame = aPopup->GetPrimaryFrame();
1320 if (!frame) return;
1321
1322 presShell->FrameNeedsReflow(frame, nsIPresShell::eTreeChange,
1323 NS_FRAME_HAS_DIRTY_CHILDREN);
1324
1325 // cache the popup so that document.popupNode can retrieve the trigger node
1326 // during the popupshowing event. It will be cleared below after the event
1327 // has fired.
1328 mOpeningPopup = aPopup;
1329
1330 nsEventStatus status = nsEventStatus_eIgnore;
1331 WidgetMouseEvent event(true, eXULPopupShowing, nullptr,
1332 WidgetMouseEvent::eReal);
1333
1334 // coordinates are relative to the root widget
1335 nsPresContext* rootPresContext =
1336 presShell->GetPresContext()->GetRootPresContext();
1337 if (rootPresContext) {
1338 rootPresContext->PresShell()->GetViewManager()->GetRootWidget(
1339 getter_AddRefs(event.mWidget));
1340 } else {
1341 event.mWidget = nullptr;
1342 }
1343
1344 if (aTriggerEvent) {
1345 WidgetMouseEventBase* mouseEvent =
1346 aTriggerEvent->WidgetEventPtr()->AsMouseEventBase();
1347 if (mouseEvent) {
1348 event.inputSource = mouseEvent->inputSource;
1349 }
1350 }
1351
1352 event.mRefPoint = mCachedMousePoint;
1353 event.mModifiers = mCachedModifiers;
1354 EventDispatcher::Dispatch(popup, presContext, &event, nullptr, &status);
1355
1356 mCachedMousePoint = LayoutDeviceIntPoint(0, 0);
1357 mOpeningPopup = nullptr;
1358
1359 mCachedModifiers = 0;
1360
1361 // if a panel, blur whatever has focus so that the panel can take the focus.
1362 // This is done after the popupshowing event in case that event is cancelled.
1363 // Using noautofocus="true" will disable this behaviour, which is needed for
1364 // the autocomplete widget as it manages focus itself.
1365 if (popupType == ePopupTypePanel &&
1366 !popup->AsElement()->AttrValueIs(kNameSpaceID_None,
1367 nsGkAtoms::noautofocus, nsGkAtoms::_true,
1368 eCaseMatters)) {
1369 nsIFocusManager* fm = nsFocusManager::GetFocusManager();
1370 if (fm) {
1371 nsIDocument* doc = popup->GetUncomposedDoc();
1372
1373 // Only remove the focus if the currently focused item is ouside the
1374 // popup. It isn't a big deal if the current focus is in a child popup
1375 // inside the popup as that shouldn't be visible. This check ensures that
1376 // a node inside the popup that is focused during a popupshowing event
1377 // remains focused.
1378 nsCOMPtr<nsIDOMElement> currentFocusElement;
1379 fm->GetFocusedElement(getter_AddRefs(currentFocusElement));
1380 nsCOMPtr<nsIContent> currentFocus =
1381 do_QueryInterface(currentFocusElement);
1382 if (doc && currentFocus &&
1383 !nsContentUtils::ContentIsCrossDocDescendantOf(currentFocus, popup)) {
1384 fm->ClearFocus(doc->GetWindow());
1385 }
1386 }
1387 }
1388
1389 // clear these as they are no longer valid
1390 mRangeParent = nullptr;
1391 mRangeOffset = 0;
1392
1393 // get the frame again in case it went away
1394 popupFrame = do_QueryFrame(aPopup->GetPrimaryFrame());
1395 if (popupFrame) {
1396 // if the event was cancelled, don't open the popup, reset its state back
1397 // to closed and clear its trigger content.
1398 if (status == nsEventStatus_eConsumeNoDefault) {
1399 popupFrame->SetPopupState(ePopupClosed);
1400 popupFrame->ClearTriggerContent();
1401 } else {
1402 // Now check if we need to fire the popuppositioned event. If not, call
1403 // ShowPopupCallback directly.
1404
1405 // The popuppositioned event only fires on arrow panels for now.
1406 if (popup->AsElement()->AttrValueIs(kNameSpaceID_None, nsGkAtoms::type,
1407 nsGkAtoms::arrow, eCaseMatters)) {
1408 popupFrame->ShowWithPositionedEvent();
1409 presShell->FrameNeedsReflow(popupFrame, nsIPresShell::eTreeChange,
1410 NS_FRAME_HAS_DIRTY_CHILDREN);
1411 } else {
1412 ShowPopupCallback(popup, popupFrame, aIsContextMenu, aSelectFirstItem);
1413 }
1414 }
1415 }
1416 }
1417
FirePopupHidingEvent(nsIContent * aPopup,nsIContent * aNextPopup,nsIContent * aLastPopup,nsPresContext * aPresContext,nsPopupType aPopupType,bool aDeselectMenu,bool aIsCancel)1418 void nsXULPopupManager::FirePopupHidingEvent(
1419 nsIContent* aPopup, nsIContent* aNextPopup, nsIContent* aLastPopup,
1420 nsPresContext* aPresContext, nsPopupType aPopupType, bool aDeselectMenu,
1421 bool aIsCancel) {
1422 nsCOMPtr<nsIPresShell> presShell = aPresContext->PresShell();
1423 mozilla::Unused << presShell; // This presShell may be keeping things alive
1424 // on non GTK platforms
1425
1426 nsEventStatus status = nsEventStatus_eIgnore;
1427 WidgetMouseEvent event(true, eXULPopupHiding, nullptr,
1428 WidgetMouseEvent::eReal);
1429 EventDispatcher::Dispatch(aPopup, aPresContext, &event, nullptr, &status);
1430
1431 // when a panel is closed, blur whatever has focus inside the popup
1432 if (aPopupType == ePopupTypePanel &&
1433 (!aPopup->IsElement() || !aPopup->AsElement()->AttrValueIs(
1434 kNameSpaceID_None, nsGkAtoms::noautofocus,
1435 nsGkAtoms::_true, eCaseMatters))) {
1436 nsIFocusManager* fm = nsFocusManager::GetFocusManager();
1437 if (fm) {
1438 nsIDocument* doc = aPopup->GetUncomposedDoc();
1439
1440 // Remove the focus from the focused node only if it is inside the popup.
1441 nsCOMPtr<nsIDOMElement> currentFocusElement;
1442 fm->GetFocusedElement(getter_AddRefs(currentFocusElement));
1443 nsCOMPtr<nsIContent> currentFocus =
1444 do_QueryInterface(currentFocusElement);
1445 if (doc && currentFocus &&
1446 nsContentUtils::ContentIsCrossDocDescendantOf(currentFocus, aPopup)) {
1447 fm->ClearFocus(doc->GetWindow());
1448 }
1449 }
1450 }
1451
1452 // get frame again in case it went away
1453 nsMenuPopupFrame* popupFrame = do_QueryFrame(aPopup->GetPrimaryFrame());
1454 if (popupFrame) {
1455 // if the event was cancelled, don't hide the popup, and reset its
1456 // state back to open. Only popups in chrome shells can prevent a popup
1457 // from hiding.
1458 if (status == nsEventStatus_eConsumeNoDefault &&
1459 !popupFrame->IsInContentShell()) {
1460 // XXXndeakin
1461 // If an attempt was made to hide this popup before the popupshown event
1462 // fired, then ePopupShown is set here even though it should be
1463 // ePopupVisible. This probably isn't worth the hassle of handling.
1464 popupFrame->SetPopupState(ePopupShown);
1465 } else {
1466 // If the popup has an animate attribute and it is not set to false, check
1467 // if it has a closing transition and wait for it to finish. The transition
1468 // may still occur either way, but the view will be hidden and you won't be
1469 // able to see it. If there is a next popup, indicating that mutliple popups
1470 // are rolling up, don't wait and hide the popup right away since the effect
1471 // would likely be undesirable. Transitions are currently disabled on Linux
1472 // due to rendering issues on certain configurations.
1473 #ifndef MOZ_WIDGET_GTK
1474 if (!aNextPopup && aPopup->IsElement() &&
1475 aPopup->AsElement()->HasAttr(kNameSpaceID_None, nsGkAtoms::animate)) {
1476 // If animate="false" then don't transition at all. If animate="cancel",
1477 // only show the transition if cancelling the popup or rolling up.
1478 // Otherwise, always show the transition.
1479 nsAutoString animate;
1480 aPopup->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::animate,
1481 animate);
1482
1483 if (!animate.EqualsLiteral("false") &&
1484 (!animate.EqualsLiteral("cancel") || aIsCancel)) {
1485 presShell->FlushPendingNotifications(FlushType::Layout);
1486
1487 // Get the frame again in case the flush caused it to go away
1488 popupFrame = do_QueryFrame(aPopup->GetPrimaryFrame());
1489 if (!popupFrame) return;
1490
1491 if (nsLayoutUtils::HasCurrentTransitions(popupFrame)) {
1492 RefPtr<TransitionEnder> ender =
1493 new TransitionEnder(aPopup, aDeselectMenu);
1494 aPopup->AddSystemEventListener(NS_LITERAL_STRING("transitionend"),
1495 ender, false, false);
1496 return;
1497 }
1498 }
1499 }
1500 #endif
1501
1502 HidePopupCallback(aPopup, popupFrame, aNextPopup, aLastPopup, aPopupType,
1503 aDeselectMenu);
1504 }
1505 }
1506 }
1507
IsPopupOpen(nsIContent * aPopup)1508 bool nsXULPopupManager::IsPopupOpen(nsIContent* aPopup) {
1509 // a popup is open if it is in the open list. The assertions ensure that the
1510 // frame is in the correct state. If the popup is in the hiding or invisible
1511 // state, it will still be in the open popup list until it is closed.
1512 nsMenuChainItem* item = mPopups;
1513 while (item) {
1514 if (item->Content() == aPopup) {
1515 NS_ASSERTION(item->Frame()->IsOpen() ||
1516 item->Frame()->PopupState() == ePopupHiding ||
1517 item->Frame()->PopupState() == ePopupInvisible,
1518 "popup in open list not actually open");
1519 return true;
1520 }
1521 item = item->GetParent();
1522 }
1523
1524 return false;
1525 }
1526
IsPopupOpenForMenuParent(nsMenuParent * aMenuParent)1527 bool nsXULPopupManager::IsPopupOpenForMenuParent(nsMenuParent* aMenuParent) {
1528 nsMenuChainItem* item = GetTopVisibleMenu();
1529 while (item) {
1530 nsMenuPopupFrame* popup = item->Frame();
1531 if (popup && popup->IsOpen()) {
1532 nsMenuFrame* menuFrame = do_QueryFrame(popup->GetParent());
1533 if (menuFrame && menuFrame->GetMenuParent() == aMenuParent) {
1534 return true;
1535 }
1536 }
1537 item = item->GetParent();
1538 }
1539
1540 return false;
1541 }
1542
GetTopPopup(nsPopupType aType)1543 nsIFrame* nsXULPopupManager::GetTopPopup(nsPopupType aType) {
1544 nsMenuChainItem* item = mPopups;
1545 while (item) {
1546 if (item->Frame()->IsVisible() &&
1547 (item->PopupType() == aType || aType == ePopupTypeAny)) {
1548 return item->Frame();
1549 }
1550
1551 item = item->GetParent();
1552 }
1553
1554 return nullptr;
1555 }
1556
GetVisiblePopups(nsTArray<nsIFrame * > & aPopups)1557 void nsXULPopupManager::GetVisiblePopups(nsTArray<nsIFrame*>& aPopups) {
1558 aPopups.Clear();
1559
1560 nsMenuChainItem* item = mPopups;
1561 while (item) {
1562 // Skip panels which are not visible as well as popups that
1563 // are transparent to mouse events.
1564 if (item->Frame()->IsVisible() && !item->Frame()->IsMouseTransparent()) {
1565 aPopups.AppendElement(item->Frame());
1566 }
1567
1568 item = item->GetParent();
1569 }
1570 }
1571
GetLastTriggerNode(nsIDocument * aDocument,bool aIsTooltip)1572 already_AddRefed<nsINode> nsXULPopupManager::GetLastTriggerNode(
1573 nsIDocument* aDocument, bool aIsTooltip) {
1574 if (!aDocument) return nullptr;
1575
1576 nsCOMPtr<nsINode> node;
1577
1578 // if mOpeningPopup is set, it means that a popupshowing event is being
1579 // fired. In this case, just use the cached node, as the popup is not yet in
1580 // the list of open popups.
1581 if (mOpeningPopup && mOpeningPopup->GetUncomposedDoc() == aDocument &&
1582 aIsTooltip == mOpeningPopup->IsXULElement(nsGkAtoms::tooltip)) {
1583 node = nsMenuPopupFrame::GetTriggerContent(
1584 GetPopupFrameForContent(mOpeningPopup, false));
1585 } else {
1586 nsMenuChainItem* item = mPopups;
1587 while (item) {
1588 // look for a popup of the same type and document.
1589 if ((item->PopupType() == ePopupTypeTooltip) == aIsTooltip &&
1590 item->Content()->GetUncomposedDoc() == aDocument) {
1591 node = nsMenuPopupFrame::GetTriggerContent(item->Frame());
1592 if (node) break;
1593 }
1594 item = item->GetParent();
1595 }
1596 }
1597
1598 return node.forget();
1599 }
1600
MayShowPopup(nsMenuPopupFrame * aPopup)1601 bool nsXULPopupManager::MayShowPopup(nsMenuPopupFrame* aPopup) {
1602 // if a popup's IsOpen method returns true, then the popup must always be in
1603 // the popup chain scanned in IsPopupOpen.
1604 NS_ASSERTION(!aPopup->IsOpen() || IsPopupOpen(aPopup->GetContent()),
1605 "popup frame state doesn't match XULPopupManager open state");
1606
1607 nsPopupState state = aPopup->PopupState();
1608
1609 // if the popup is not in the open popup chain, then it must have a state that
1610 // is either closed, in the process of being shown, or invisible.
1611 NS_ASSERTION(IsPopupOpen(aPopup->GetContent()) || state == ePopupClosed ||
1612 state == ePopupShowing || state == ePopupPositioning ||
1613 state == ePopupInvisible,
1614 "popup not in XULPopupManager open list is open");
1615
1616 // don't show popups unless they are closed or invisible
1617 if (state != ePopupClosed && state != ePopupInvisible) return false;
1618
1619 // Don't show popups that we already have in our popup chain
1620 if (IsPopupOpen(aPopup->GetContent())) {
1621 NS_WARNING("Refusing to show duplicate popup");
1622 return false;
1623 }
1624
1625 // if the popup was just rolled up, don't reopen it
1626 if (mozilla::widget::nsAutoRollup::GetLastRollup() == aPopup->GetContent())
1627 return false;
1628
1629 nsCOMPtr<nsIDocShellTreeItem> dsti = aPopup->PresContext()->GetDocShell();
1630 nsCOMPtr<nsIBaseWindow> baseWin = do_QueryInterface(dsti);
1631 if (!baseWin) return false;
1632
1633 nsCOMPtr<nsIDocShellTreeItem> root;
1634 dsti->GetRootTreeItem(getter_AddRefs(root));
1635 if (!root) {
1636 return false;
1637 }
1638
1639 nsCOMPtr<nsPIDOMWindowOuter> rootWin = root->GetWindow();
1640
1641 // chrome shells can always open popups, but other types of shells can only
1642 // open popups when they are focused and visible
1643 if (dsti->ItemType() != nsIDocShellTreeItem::typeChrome) {
1644 // only allow popups in active windows
1645 nsIFocusManager* fm = nsFocusManager::GetFocusManager();
1646 if (!fm || !rootWin) return false;
1647
1648 nsCOMPtr<mozIDOMWindowProxy> activeWindow;
1649 fm->GetActiveWindow(getter_AddRefs(activeWindow));
1650 if (activeWindow != rootWin) return false;
1651
1652 // only allow popups in visible frames
1653 bool visible;
1654 baseWin->GetVisibility(&visible);
1655 if (!visible) return false;
1656 }
1657
1658 // platforms respond differently when an popup is opened in a minimized
1659 // window, so this is always disabled.
1660 nsCOMPtr<nsIWidget> mainWidget;
1661 baseWin->GetMainWidget(getter_AddRefs(mainWidget));
1662 if (mainWidget && mainWidget->SizeMode() == nsSizeMode_Minimized) {
1663 return false;
1664 }
1665
1666 #ifdef XP_MACOSX
1667 if (rootWin) {
1668 auto globalWin = nsGlobalWindowOuter::Cast(rootWin.get());
1669 if (globalWin->IsInModalState()) {
1670 return false;
1671 }
1672 }
1673 #endif
1674
1675 // cannot open a popup that is a submenu of a menupopup that isn't open.
1676 nsMenuFrame* menuFrame = do_QueryFrame(aPopup->GetParent());
1677 if (menuFrame) {
1678 nsMenuParent* parentPopup = menuFrame->GetMenuParent();
1679 if (parentPopup && !parentPopup->IsOpen()) return false;
1680 }
1681
1682 return true;
1683 }
1684
PopupDestroyed(nsMenuPopupFrame * aPopup)1685 void nsXULPopupManager::PopupDestroyed(nsMenuPopupFrame* aPopup) {
1686 // when a popup frame is destroyed, just unhook it from the list of popups
1687 if (mTimerMenu == aPopup) {
1688 if (mCloseTimer) {
1689 mCloseTimer->Cancel();
1690 mCloseTimer = nullptr;
1691 }
1692 mTimerMenu = nullptr;
1693 }
1694
1695 nsTArray<nsMenuPopupFrame*> popupsToHide;
1696
1697 nsMenuChainItem* item = mPopups;
1698 while (item) {
1699 nsMenuPopupFrame* frame = item->Frame();
1700 if (frame == aPopup) {
1701 // XXXndeakin shouldn't this only happen for menus?
1702 if (!item->IsNoAutoHide() && frame->PopupState() != ePopupInvisible) {
1703 // Iterate through any child menus and hide them as well, since the
1704 // parent is going away. We won't remove them from the list yet, just
1705 // hide them, as they will be removed from the list when this function
1706 // gets called for that child frame.
1707 nsMenuChainItem* child = item->GetChild();
1708 while (child) {
1709 // if the popup is a child frame of the menu that was destroyed, add
1710 // it to the list of popups to hide. Don't bother with the events
1711 // since the frames are going away. If the child menu is not a child
1712 // frame, for example, a context menu, use HidePopup instead, but call
1713 // it asynchronously since we are in the middle of frame destruction.
1714 nsMenuPopupFrame* childframe = child->Frame();
1715 if (nsLayoutUtils::IsProperAncestorFrame(frame, childframe)) {
1716 popupsToHide.AppendElement(childframe);
1717 } else {
1718 // HidePopup will take care of hiding any of its children, so
1719 // break out afterwards
1720 HidePopup(child->Content(), false, false, true, false);
1721 break;
1722 }
1723
1724 child = child->GetChild();
1725 }
1726 }
1727
1728 item->Detach(&mPopups);
1729 delete item;
1730 break;
1731 }
1732
1733 item = item->GetParent();
1734 }
1735
1736 HidePopupsInList(popupsToHide);
1737 }
1738
HasContextMenu(nsMenuPopupFrame * aPopup)1739 bool nsXULPopupManager::HasContextMenu(nsMenuPopupFrame* aPopup) {
1740 nsMenuChainItem* item = GetTopVisibleMenu();
1741 while (item && item->Frame() != aPopup) {
1742 if (item->IsContextMenu()) return true;
1743 item = item->GetParent();
1744 }
1745
1746 return false;
1747 }
1748
SetCaptureState(nsIContent * aOldPopup)1749 void nsXULPopupManager::SetCaptureState(nsIContent* aOldPopup) {
1750 nsMenuChainItem* item = GetTopVisibleMenu();
1751 if (item && aOldPopup == item->Content()) return;
1752
1753 if (mWidget) {
1754 mWidget->CaptureRollupEvents(nullptr, false);
1755 mWidget = nullptr;
1756 }
1757
1758 if (item) {
1759 nsMenuPopupFrame* popup = item->Frame();
1760 mWidget = popup->GetWidget();
1761 if (mWidget) {
1762 mWidget->CaptureRollupEvents(nullptr, true);
1763 popup->AttachedDismissalListener();
1764 }
1765 }
1766
1767 UpdateKeyboardListeners();
1768 }
1769
UpdateKeyboardListeners()1770 void nsXULPopupManager::UpdateKeyboardListeners() {
1771 nsCOMPtr<EventTarget> newTarget;
1772 bool isForMenu = false;
1773 nsMenuChainItem* item = GetTopVisibleMenu();
1774 if (item) {
1775 if (item->IgnoreKeys() != eIgnoreKeys_True) {
1776 newTarget = item->Content()->GetComposedDoc();
1777 }
1778 isForMenu = item->PopupType() == ePopupTypeMenu;
1779 } else if (mActiveMenuBar) {
1780 newTarget = mActiveMenuBar->GetContent()->GetComposedDoc();
1781 isForMenu = true;
1782 }
1783
1784 if (mKeyListener != newTarget) {
1785 if (mKeyListener) {
1786 mKeyListener->RemoveEventListener(NS_LITERAL_STRING("keypress"), this,
1787 true);
1788 mKeyListener->RemoveEventListener(NS_LITERAL_STRING("keydown"), this,
1789 true);
1790 mKeyListener->RemoveEventListener(NS_LITERAL_STRING("keyup"), this, true);
1791 mKeyListener = nullptr;
1792 nsContentUtils::NotifyInstalledMenuKeyboardListener(false);
1793 }
1794
1795 if (newTarget) {
1796 newTarget->AddEventListener(NS_LITERAL_STRING("keypress"), this, true);
1797 newTarget->AddEventListener(NS_LITERAL_STRING("keydown"), this, true);
1798 newTarget->AddEventListener(NS_LITERAL_STRING("keyup"), this, true);
1799 nsContentUtils::NotifyInstalledMenuKeyboardListener(isForMenu);
1800 mKeyListener = newTarget;
1801 }
1802 }
1803 }
1804
UpdateMenuItems(nsIContent * aPopup)1805 void nsXULPopupManager::UpdateMenuItems(nsIContent* aPopup) {
1806 // Walk all of the menu's children, checking to see if any of them has a
1807 // command attribute. If so, then several attributes must potentially be
1808 // updated.
1809
1810 nsCOMPtr<nsIDocument> document = aPopup->GetUncomposedDoc();
1811 if (!document) {
1812 return;
1813 }
1814
1815 // When a menu is opened, make sure that command updating is unlocked first.
1816 XULDocument* xulDoc = document->AsXULDocument();
1817 if (xulDoc) {
1818 nsCOMPtr<nsIDOMXULCommandDispatcher> xulCommandDispatcher =
1819 xulDoc->GetCommandDispatcher();
1820 if (xulCommandDispatcher) {
1821 xulCommandDispatcher->Unlock();
1822 }
1823 }
1824
1825 for (nsCOMPtr<nsIContent> grandChild = aPopup->GetFirstChild(); grandChild;
1826 grandChild = grandChild->GetNextSibling()) {
1827 if (grandChild->IsXULElement(nsGkAtoms::menugroup)) {
1828 if (grandChild->GetChildCount() == 0) {
1829 continue;
1830 }
1831 grandChild = grandChild->GetFirstChild();
1832 }
1833 if (grandChild->IsXULElement(nsGkAtoms::menuitem)) {
1834 // See if we have a command attribute.
1835 Element* grandChildElement = grandChild->AsElement();
1836 nsAutoString command;
1837 grandChildElement->GetAttr(kNameSpaceID_None, nsGkAtoms::command,
1838 command);
1839 if (!command.IsEmpty()) {
1840 // We do! Look it up in our document
1841 RefPtr<dom::Element> commandElement = document->GetElementById(command);
1842 if (commandElement) {
1843 nsAutoString commandValue;
1844 // The menu's disabled state needs to be updated to match the command.
1845 if (commandElement->GetAttr(kNameSpaceID_None, nsGkAtoms::disabled,
1846 commandValue))
1847 grandChildElement->SetAttr(kNameSpaceID_None, nsGkAtoms::disabled,
1848 commandValue, true);
1849 else
1850 grandChildElement->UnsetAttr(kNameSpaceID_None, nsGkAtoms::disabled,
1851 true);
1852
1853 // The menu's label, accesskey checked and hidden states need to be
1854 // updated to match the command. Note that unlike the disabled state
1855 // if the command has *no* value, we assume the menu is supplying its
1856 // own.
1857 if (commandElement->GetAttr(kNameSpaceID_None, nsGkAtoms::label,
1858 commandValue))
1859 grandChildElement->SetAttr(kNameSpaceID_None, nsGkAtoms::label,
1860 commandValue, true);
1861
1862 if (commandElement->GetAttr(kNameSpaceID_None, nsGkAtoms::accesskey,
1863 commandValue))
1864 grandChildElement->SetAttr(kNameSpaceID_None, nsGkAtoms::accesskey,
1865 commandValue, true);
1866
1867 if (commandElement->GetAttr(kNameSpaceID_None, nsGkAtoms::checked,
1868 commandValue))
1869 grandChildElement->SetAttr(kNameSpaceID_None, nsGkAtoms::checked,
1870 commandValue, true);
1871
1872 if (commandElement->GetAttr(kNameSpaceID_None, nsGkAtoms::hidden,
1873 commandValue))
1874 grandChildElement->SetAttr(kNameSpaceID_None, nsGkAtoms::hidden,
1875 commandValue, true);
1876 }
1877 }
1878 }
1879 if (!grandChild->GetNextSibling() &&
1880 grandChild->GetParent()->IsXULElement(nsGkAtoms::menugroup)) {
1881 grandChild = grandChild->GetParent();
1882 }
1883 }
1884 }
1885
1886 // Notify
1887 //
1888 // The item selection timer has fired, we might have to readjust the
1889 // selected item. There are two cases here that we are trying to deal with:
1890 // (1) diagonal movement from a parent menu to a submenu passing briefly over
1891 // other items, and
1892 // (2) moving out from a submenu to a parent or grandparent menu.
1893 // In both cases, |mTimerMenu| is the menu item that might have an open submenu
1894 // and the first item in |mPopups| is the item the mouse is currently over,
1895 // which could be none of them.
1896 //
1897 // case (1):
1898 // As the mouse moves from the parent item of a submenu (we'll call 'A')
1899 // diagonally into the submenu, it probably passes through one or more
1900 // sibilings (B). As the mouse passes through B, it becomes the current menu
1901 // item and the timer is set and mTimerMenu is set to A. Before the timer
1902 // fires, the mouse leaves the menu containing A and B and enters the submenus.
1903 // Now when the timer fires, |mPopups| is null (!= |mTimerMenu|) so we have to
1904 // see if anything in A's children is selected (recall that even disabled items
1905 // are selected, the style just doesn't show it). If that is the case, we need
1906 // to set the selected item back to A.
1907 //
1908 // case (2);
1909 // Item A has an open submenu, and in it there is an item (B) which also has an
1910 // open submenu (so there are 3 menus displayed right now). The mouse then
1911 // leaves B's child submenu and selects an item that is a sibling of A, call it
1912 // C. When the mouse enters C, the timer is set and |mTimerMenu| is A and
1913 // |mPopups| is C. As the timer fires, the mouse is still within C. The correct
1914 // behavior is to set the current item to C and close up the chain parented at
1915 // A.
1916 //
1917 // This brings up the question of is the logic of case (1) enough? The answer
1918 // is no, and is discussed in bugzilla bug 29400. Case (1) asks if A's submenu
1919 // has a selected child, and if it does, set the selected item to A. Because B
1920 // has a submenu open, it is selected and as a result, A is set to be the
1921 // selected item even though the mouse rests in C -- very wrong.
1922 //
1923 // The solution is to use the same idea, but instead of only checking one
1924 // level, drill all the way down to the deepest open submenu and check if it
1925 // has something selected. Since the mouse is in a grandparent, it won't, and
1926 // we know that we can safely close up A and all its children.
1927 //
1928 // The code below melds the two cases together.
1929 //
KillMenuTimer()1930 void nsXULPopupManager::KillMenuTimer() {
1931 if (mCloseTimer && mTimerMenu) {
1932 mCloseTimer->Cancel();
1933 mCloseTimer = nullptr;
1934
1935 if (mTimerMenu->IsOpen())
1936 HidePopup(mTimerMenu->GetContent(), false, false, true, false);
1937 }
1938
1939 mTimerMenu = nullptr;
1940 }
1941
CancelMenuTimer(nsMenuParent * aMenuParent)1942 void nsXULPopupManager::CancelMenuTimer(nsMenuParent* aMenuParent) {
1943 if (mCloseTimer && mTimerMenu == aMenuParent) {
1944 mCloseTimer->Cancel();
1945 mCloseTimer = nullptr;
1946 mTimerMenu = nullptr;
1947 }
1948 }
1949
HandleShortcutNavigation(KeyboardEvent * aKeyEvent,nsMenuPopupFrame * aFrame)1950 bool nsXULPopupManager::HandleShortcutNavigation(KeyboardEvent* aKeyEvent,
1951 nsMenuPopupFrame* aFrame) {
1952 // On Windows, don't check shortcuts when the accelerator key is down.
1953 #ifdef XP_WIN
1954 WidgetInputEvent* evt = aKeyEvent->WidgetEventPtr()->AsInputEvent();
1955 if (evt && evt->IsAccel()) {
1956 return false;
1957 }
1958 #endif
1959
1960 nsMenuChainItem* item = GetTopVisibleMenu();
1961 if (!aFrame && item) aFrame = item->Frame();
1962
1963 if (aFrame) {
1964 bool action;
1965 nsMenuFrame* result = aFrame->FindMenuWithShortcut(aKeyEvent, action);
1966 if (result) {
1967 aFrame->ChangeMenuItem(result, false, true);
1968 if (action) {
1969 WidgetGUIEvent* evt = aKeyEvent->WidgetEventPtr()->AsGUIEvent();
1970 nsMenuFrame* menuToOpen = result->Enter(evt);
1971 if (menuToOpen) {
1972 nsCOMPtr<nsIContent> content = menuToOpen->GetContent();
1973 ShowMenu(content, true, false);
1974 }
1975 }
1976 return true;
1977 }
1978
1979 return false;
1980 }
1981
1982 if (mActiveMenuBar) {
1983 nsMenuFrame* result =
1984 mActiveMenuBar->FindMenuWithShortcut(aKeyEvent, false);
1985 if (result) {
1986 mActiveMenuBar->SetActive(true);
1987 result->OpenMenu(true);
1988 return true;
1989 }
1990 }
1991
1992 return false;
1993 }
1994
HandleKeyboardNavigation(uint32_t aKeyCode)1995 bool nsXULPopupManager::HandleKeyboardNavigation(uint32_t aKeyCode) {
1996 // navigate up through the open menus, looking for the topmost one
1997 // in the same hierarchy
1998 nsMenuChainItem* item = nullptr;
1999 nsMenuChainItem* nextitem = GetTopVisibleMenu();
2000
2001 while (nextitem) {
2002 item = nextitem;
2003 nextitem = item->GetParent();
2004
2005 if (nextitem) {
2006 // stop if the parent isn't a menu
2007 if (!nextitem->IsMenu()) break;
2008
2009 // check to make sure that the parent is actually the parent menu. It
2010 // won't be if the parent is in a different frame hierarchy, for example,
2011 // for a context menu opened on another menu.
2012 nsMenuParent* expectedParent =
2013 static_cast<nsMenuParent*>(nextitem->Frame());
2014 nsMenuFrame* menuFrame = do_QueryFrame(item->Frame()->GetParent());
2015 if (!menuFrame || menuFrame->GetMenuParent() != expectedParent) {
2016 break;
2017 }
2018 }
2019 }
2020
2021 nsIFrame* itemFrame;
2022 if (item)
2023 itemFrame = item->Frame();
2024 else if (mActiveMenuBar)
2025 itemFrame = mActiveMenuBar;
2026 else
2027 return false;
2028
2029 nsNavigationDirection theDirection;
2030 NS_ASSERTION(aKeyCode >= KeyboardEventBinding::DOM_VK_END &&
2031 aKeyCode <= KeyboardEventBinding::DOM_VK_DOWN,
2032 "Illegal key code");
2033 theDirection = NS_DIRECTION_FROM_KEY_CODE(itemFrame, aKeyCode);
2034
2035 bool selectFirstItem = true;
2036 #ifdef MOZ_WIDGET_GTK
2037 nsMenuFrame* currentItem = nullptr;
2038 if (item && mActiveMenuBar && NS_DIRECTION_IS_INLINE(theDirection)) {
2039 currentItem = item->Frame()->GetCurrentMenuItem();
2040 // If nothing is selected in the menu and we have a menubar, let it
2041 // handle the movement not to steal focus from it.
2042 if (!currentItem) {
2043 item = nullptr;
2044 }
2045 }
2046 // On menu change, only select first item if an item is already selected.
2047 selectFirstItem = currentItem != nullptr;
2048 #endif
2049
2050 // if a popup is open, first check for navigation within the popup
2051 if (item && HandleKeyboardNavigationInPopup(item, theDirection)) return true;
2052
2053 // no popup handled the key, so check the active menubar, if any
2054 if (mActiveMenuBar) {
2055 nsMenuFrame* currentMenu = mActiveMenuBar->GetCurrentMenuItem();
2056
2057 if (NS_DIRECTION_IS_INLINE(theDirection)) {
2058 nsMenuFrame* nextItem =
2059 (theDirection == eNavigationDirection_End)
2060 ? GetNextMenuItem(mActiveMenuBar, currentMenu, false, true)
2061 : GetPreviousMenuItem(mActiveMenuBar, currentMenu, false, true);
2062 mActiveMenuBar->ChangeMenuItem(nextItem, selectFirstItem, true);
2063 return true;
2064 } else if (NS_DIRECTION_IS_BLOCK(theDirection)) {
2065 // Open the menu and select its first item.
2066 if (currentMenu) {
2067 nsCOMPtr<nsIContent> content = currentMenu->GetContent();
2068 ShowMenu(content, true, false);
2069 }
2070 return true;
2071 }
2072 }
2073
2074 return false;
2075 }
2076
HandleKeyboardNavigationInPopup(nsMenuChainItem * item,nsMenuPopupFrame * aFrame,nsNavigationDirection aDir)2077 bool nsXULPopupManager::HandleKeyboardNavigationInPopup(
2078 nsMenuChainItem* item, nsMenuPopupFrame* aFrame,
2079 nsNavigationDirection aDir) {
2080 NS_ASSERTION(aFrame, "aFrame is null");
2081 NS_ASSERTION(!item || item->Frame() == aFrame,
2082 "aFrame is expected to be equal to item->Frame()");
2083
2084 nsMenuFrame* currentMenu = aFrame->GetCurrentMenuItem();
2085
2086 aFrame->ClearIncrementalString();
2087
2088 // This method only gets called if we're open.
2089 if (!currentMenu && NS_DIRECTION_IS_INLINE(aDir)) {
2090 // We've been opened, but we haven't had anything selected.
2091 // We can handle End, but our parent handles Start.
2092 if (aDir == eNavigationDirection_End) {
2093 nsMenuFrame* nextItem = GetNextMenuItem(aFrame, nullptr, true, false);
2094 if (nextItem) {
2095 aFrame->ChangeMenuItem(nextItem, false, true);
2096 return true;
2097 }
2098 }
2099 return false;
2100 }
2101
2102 bool isContainer = false;
2103 bool isOpen = false;
2104 if (currentMenu) {
2105 isOpen = currentMenu->IsOpen();
2106 isContainer = currentMenu->IsMenu();
2107 if (isOpen) {
2108 // for an open popup, have the child process the event
2109 nsMenuChainItem* child = item ? item->GetChild() : nullptr;
2110 if (child && HandleKeyboardNavigationInPopup(child, aDir)) return true;
2111 } else if (aDir == eNavigationDirection_End && isContainer &&
2112 !currentMenu->IsDisabled()) {
2113 // The menu is not yet open. Open it and select the first item.
2114 nsCOMPtr<nsIContent> content = currentMenu->GetContent();
2115 ShowMenu(content, true, false);
2116 return true;
2117 }
2118 }
2119
2120 // For block progression, we can move in either direction
2121 if (NS_DIRECTION_IS_BLOCK(aDir) || NS_DIRECTION_IS_BLOCK_TO_EDGE(aDir)) {
2122 nsMenuFrame* nextItem;
2123
2124 if (aDir == eNavigationDirection_Before ||
2125 aDir == eNavigationDirection_After) {
2126 // Cursor navigation does not wrap on Mac or for menulists on Windows.
2127 bool wrap =
2128 #ifdef XP_WIN
2129 aFrame->IsMenuList() ? false : true;
2130 #elif defined XP_MACOSX
2131 false;
2132 #else
2133 true;
2134 #endif
2135
2136 if (aDir == eNavigationDirection_Before) {
2137 nextItem = GetPreviousMenuItem(aFrame, currentMenu, true, wrap);
2138 } else {
2139 nextItem = GetNextMenuItem(aFrame, currentMenu, true, wrap);
2140 }
2141 } else if (aDir == eNavigationDirection_First) {
2142 nextItem = GetNextMenuItem(aFrame, nullptr, true, false);
2143 } else {
2144 nextItem = GetPreviousMenuItem(aFrame, nullptr, true, false);
2145 }
2146
2147 if (nextItem) {
2148 aFrame->ChangeMenuItem(nextItem, false, true);
2149 return true;
2150 }
2151 } else if (currentMenu && isContainer && isOpen) {
2152 if (aDir == eNavigationDirection_Start) {
2153 // close a submenu when Left is pressed
2154 nsMenuPopupFrame* popupFrame = currentMenu->GetPopup();
2155 if (popupFrame)
2156 HidePopup(popupFrame->GetContent(), false, false, false, false);
2157 return true;
2158 }
2159 }
2160
2161 return false;
2162 }
2163
HandleKeyboardEventWithKeyCode(KeyboardEvent * aKeyEvent,nsMenuChainItem * aTopVisibleMenuItem)2164 bool nsXULPopupManager::HandleKeyboardEventWithKeyCode(
2165 KeyboardEvent* aKeyEvent, nsMenuChainItem* aTopVisibleMenuItem) {
2166 uint32_t keyCode = aKeyEvent->KeyCode();
2167
2168 // Escape should close panels, but the other keys should have no effect.
2169 if (aTopVisibleMenuItem &&
2170 aTopVisibleMenuItem->PopupType() != ePopupTypeMenu) {
2171 if (keyCode == KeyboardEventBinding::DOM_VK_ESCAPE) {
2172 HidePopup(aTopVisibleMenuItem->Content(), false, false, false, true);
2173 aKeyEvent->StopPropagation();
2174 aKeyEvent->StopCrossProcessForwarding();
2175 aKeyEvent->PreventDefault();
2176 }
2177 return true;
2178 }
2179
2180 bool consume = (aTopVisibleMenuItem || mActiveMenuBar);
2181 switch (keyCode) {
2182 case KeyboardEventBinding::DOM_VK_UP:
2183 case KeyboardEventBinding::DOM_VK_DOWN:
2184 #ifndef XP_MACOSX
2185 // roll up the popup when alt+up/down are pressed within a menulist.
2186 if (aKeyEvent->AltKey() && aTopVisibleMenuItem &&
2187 aTopVisibleMenuItem->Frame()->IsMenuList()) {
2188 Rollup(0, false, nullptr, nullptr);
2189 break;
2190 }
2191 MOZ_FALLTHROUGH;
2192 #endif
2193
2194 case KeyboardEventBinding::DOM_VK_LEFT:
2195 case KeyboardEventBinding::DOM_VK_RIGHT:
2196 case KeyboardEventBinding::DOM_VK_HOME:
2197 case KeyboardEventBinding::DOM_VK_END:
2198 HandleKeyboardNavigation(keyCode);
2199 break;
2200
2201 case KeyboardEventBinding::DOM_VK_PAGE_DOWN:
2202 case KeyboardEventBinding::DOM_VK_PAGE_UP:
2203 if (aTopVisibleMenuItem) {
2204 aTopVisibleMenuItem->Frame()->ChangeByPage(
2205 keyCode == KeyboardEventBinding::DOM_VK_PAGE_UP);
2206 }
2207 break;
2208
2209 case KeyboardEventBinding::DOM_VK_ESCAPE:
2210 // Pressing Escape hides one level of menus only. If no menu is open,
2211 // check if a menubar is active and inform it that a menu closed. Even
2212 // though in this latter case, a menu didn't actually close, the effect
2213 // ends up being the same. Similar for the tab key below.
2214 if (aTopVisibleMenuItem) {
2215 HidePopup(aTopVisibleMenuItem->Content(), false, false, false, true);
2216 } else if (mActiveMenuBar) {
2217 mActiveMenuBar->MenuClosed();
2218 }
2219 break;
2220
2221 case KeyboardEventBinding::DOM_VK_TAB:
2222 #ifndef XP_MACOSX
2223 case KeyboardEventBinding::DOM_VK_F10:
2224 #endif
2225 if (aTopVisibleMenuItem &&
2226 !aTopVisibleMenuItem->Frame()->GetContent()->AsElement()->AttrValueIs(
2227 kNameSpaceID_None, nsGkAtoms::activateontab, nsGkAtoms::_true,
2228 eCaseMatters)) {
2229 // close popups or deactivate menubar when Tab or F10 are pressed
2230 Rollup(0, false, nullptr, nullptr);
2231 break;
2232 } else if (mActiveMenuBar) {
2233 mActiveMenuBar->MenuClosed();
2234 break;
2235 }
2236 // Intentional fall-through to RETURN case
2237 MOZ_FALLTHROUGH;
2238
2239 case KeyboardEventBinding::DOM_VK_RETURN: {
2240 // If there is a popup open, check if the current item needs to be opened.
2241 // Otherwise, tell the active menubar, if any, to activate the menu. The
2242 // Enter method will return a menu if one needs to be opened as a result.
2243 nsMenuFrame* menuToOpen = nullptr;
2244 WidgetGUIEvent* GUIEvent = aKeyEvent->WidgetEventPtr()->AsGUIEvent();
2245
2246 if (aTopVisibleMenuItem) {
2247 menuToOpen = aTopVisibleMenuItem->Frame()->Enter(GUIEvent);
2248 } else if (mActiveMenuBar) {
2249 menuToOpen = mActiveMenuBar->Enter(GUIEvent);
2250 }
2251 if (menuToOpen) {
2252 nsCOMPtr<nsIContent> content = menuToOpen->GetContent();
2253 ShowMenu(content, true, false);
2254 }
2255 break;
2256 }
2257
2258 default:
2259 return false;
2260 }
2261
2262 if (consume) {
2263 aKeyEvent->StopPropagation();
2264 aKeyEvent->StopCrossProcessForwarding();
2265 aKeyEvent->PreventDefault();
2266 }
2267 return true;
2268 }
2269
ImmediateParentFrame(nsContainerFrame * aFrame)2270 nsContainerFrame* nsXULPopupManager::ImmediateParentFrame(
2271 nsContainerFrame* aFrame) {
2272 MOZ_ASSERT(aFrame && aFrame->GetContent());
2273
2274 bool multiple = false; // Unused
2275 nsIContent* insertionPoint =
2276 aFrame->GetContent()
2277 ->OwnerDoc()
2278 ->BindingManager()
2279 ->FindNestedSingleInsertionPoint(aFrame->GetContent(), &multiple);
2280
2281 nsCSSFrameConstructor* fc = aFrame->PresContext()->FrameConstructor();
2282 nsContainerFrame* insertionFrame =
2283 insertionPoint ? fc->GetContentInsertionFrameFor(insertionPoint)
2284 : nullptr;
2285
2286 return insertionFrame ? insertionFrame : aFrame;
2287 }
2288
GetNextMenuItem(nsContainerFrame * aParent,nsMenuFrame * aStart,bool aIsPopup,bool aWrap)2289 nsMenuFrame* nsXULPopupManager::GetNextMenuItem(nsContainerFrame* aParent,
2290 nsMenuFrame* aStart,
2291 bool aIsPopup, bool aWrap) {
2292 nsContainerFrame* immediateParent = ImmediateParentFrame(aParent);
2293 nsIFrame* currFrame = nullptr;
2294 if (aStart) {
2295 if (aStart->GetNextSibling())
2296 currFrame = aStart->GetNextSibling();
2297 else if (aStart->GetParent()->GetContent()->IsXULElement(
2298 nsGkAtoms::menugroup))
2299 currFrame = aStart->GetParent()->GetNextSibling();
2300 } else
2301 currFrame = immediateParent->PrincipalChildList().FirstChild();
2302
2303 while (currFrame) {
2304 // See if it's a menu item.
2305 nsIContent* currFrameContent = currFrame->GetContent();
2306 if (IsValidMenuItem(currFrameContent, aIsPopup)) {
2307 return do_QueryFrame(currFrame);
2308 }
2309 if (currFrameContent->IsXULElement(nsGkAtoms::menugroup) &&
2310 currFrameContent->GetChildCount() > 0)
2311 currFrame = currFrame->PrincipalChildList().FirstChild();
2312 else if (!currFrame->GetNextSibling() &&
2313 currFrame->GetParent()->GetContent()->IsXULElement(
2314 nsGkAtoms::menugroup))
2315 currFrame = currFrame->GetParent()->GetNextSibling();
2316 else
2317 currFrame = currFrame->GetNextSibling();
2318 }
2319
2320 if (!aWrap) {
2321 return aStart;
2322 }
2323
2324 currFrame = immediateParent->PrincipalChildList().FirstChild();
2325
2326 // Still don't have anything. Try cycling from the beginning.
2327 while (currFrame && currFrame != aStart) {
2328 // See if it's a menu item.
2329 nsIContent* currFrameContent = currFrame->GetContent();
2330 if (IsValidMenuItem(currFrameContent, aIsPopup)) {
2331 return do_QueryFrame(currFrame);
2332 }
2333 if (currFrameContent->IsXULElement(nsGkAtoms::menugroup) &&
2334 currFrameContent->GetChildCount() > 0)
2335 currFrame = currFrame->PrincipalChildList().FirstChild();
2336 else if (!currFrame->GetNextSibling() &&
2337 currFrame->GetParent()->GetContent()->IsXULElement(
2338 nsGkAtoms::menugroup))
2339 currFrame = currFrame->GetParent()->GetNextSibling();
2340 else
2341 currFrame = currFrame->GetNextSibling();
2342 }
2343
2344 // No luck. Just return our start value.
2345 return aStart;
2346 }
2347
GetPreviousMenuItem(nsContainerFrame * aParent,nsMenuFrame * aStart,bool aIsPopup,bool aWrap)2348 nsMenuFrame* nsXULPopupManager::GetPreviousMenuItem(nsContainerFrame* aParent,
2349 nsMenuFrame* aStart,
2350 bool aIsPopup, bool aWrap) {
2351 nsContainerFrame* immediateParent = ImmediateParentFrame(aParent);
2352 const nsFrameList& frames(immediateParent->PrincipalChildList());
2353
2354 nsIFrame* currFrame = nullptr;
2355 if (aStart) {
2356 if (aStart->GetPrevSibling())
2357 currFrame = aStart->GetPrevSibling();
2358 else if (aStart->GetParent()->GetContent()->IsXULElement(
2359 nsGkAtoms::menugroup))
2360 currFrame = aStart->GetParent()->GetPrevSibling();
2361 } else
2362 currFrame = frames.LastChild();
2363
2364 while (currFrame) {
2365 // See if it's a menu item.
2366 nsIContent* currFrameContent = currFrame->GetContent();
2367 if (IsValidMenuItem(currFrameContent, aIsPopup)) {
2368 return do_QueryFrame(currFrame);
2369 }
2370 if (currFrameContent->IsXULElement(nsGkAtoms::menugroup) &&
2371 currFrameContent->GetChildCount() > 0) {
2372 const nsFrameList& menugroupFrames(currFrame->PrincipalChildList());
2373 currFrame = menugroupFrames.LastChild();
2374 } else if (!currFrame->GetPrevSibling() &&
2375 currFrame->GetParent()->GetContent()->IsXULElement(
2376 nsGkAtoms::menugroup))
2377 currFrame = currFrame->GetParent()->GetPrevSibling();
2378 else
2379 currFrame = currFrame->GetPrevSibling();
2380 }
2381
2382 if (!aWrap) {
2383 return aStart;
2384 }
2385
2386 currFrame = frames.LastChild();
2387
2388 // Still don't have anything. Try cycling from the end.
2389 while (currFrame && currFrame != aStart) {
2390 // See if it's a menu item.
2391 nsIContent* currFrameContent = currFrame->GetContent();
2392 if (IsValidMenuItem(currFrameContent, aIsPopup)) {
2393 return do_QueryFrame(currFrame);
2394 }
2395 if (currFrameContent->IsXULElement(nsGkAtoms::menugroup) &&
2396 currFrameContent->GetChildCount() > 0) {
2397 const nsFrameList& menugroupFrames(currFrame->PrincipalChildList());
2398 currFrame = menugroupFrames.LastChild();
2399 } else if (!currFrame->GetPrevSibling() &&
2400 currFrame->GetParent()->GetContent()->IsXULElement(
2401 nsGkAtoms::menugroup))
2402 currFrame = currFrame->GetParent()->GetPrevSibling();
2403 else
2404 currFrame = currFrame->GetPrevSibling();
2405 }
2406
2407 // No luck. Just return our start value.
2408 return aStart;
2409 }
2410
IsValidMenuItem(nsIContent * aContent,bool aOnPopup)2411 bool nsXULPopupManager::IsValidMenuItem(nsIContent* aContent, bool aOnPopup) {
2412 if (aContent->IsXULElement()) {
2413 if (!aContent->IsAnyOfXULElements(nsGkAtoms::menu, nsGkAtoms::menuitem)) {
2414 return false;
2415 }
2416 } else if (!aOnPopup || !aContent->IsHTMLElement(nsGkAtoms::option)) {
2417 return false;
2418 }
2419
2420 nsMenuFrame* menuFrame = do_QueryFrame(aContent->GetPrimaryFrame());
2421
2422 bool skipNavigatingDisabledMenuItem = true;
2423 if (aOnPopup &&
2424 (!menuFrame || menuFrame->GetParentMenuListType() == eNotMenuList)) {
2425 skipNavigatingDisabledMenuItem =
2426 LookAndFeel::GetInt(LookAndFeel::eIntID_SkipNavigatingDisabledMenuItem,
2427 0) != 0;
2428 }
2429
2430 return !(skipNavigatingDisabledMenuItem && aContent->IsElement() &&
2431 aContent->AsElement()->AttrValueIs(kNameSpaceID_None,
2432 nsGkAtoms::disabled,
2433 nsGkAtoms::_true, eCaseMatters));
2434 }
2435
HandleEvent(nsIDOMEvent * aEvent)2436 nsresult nsXULPopupManager::HandleEvent(nsIDOMEvent* aEvent) {
2437 RefPtr<KeyboardEvent> keyEvent =
2438 aEvent->InternalDOMEvent()->AsKeyboardEvent();
2439 NS_ENSURE_TRUE(keyEvent, NS_ERROR_UNEXPECTED);
2440
2441 // handlers shouldn't be triggered by non-trusted events.
2442 if (!keyEvent->IsTrusted()) {
2443 return NS_OK;
2444 }
2445
2446 nsAutoString eventType;
2447 keyEvent->GetType(eventType);
2448 if (eventType.EqualsLiteral("keyup")) {
2449 return KeyUp(keyEvent);
2450 }
2451 if (eventType.EqualsLiteral("keydown")) {
2452 return KeyDown(keyEvent);
2453 }
2454 if (eventType.EqualsLiteral("keypress")) {
2455 return KeyPress(keyEvent);
2456 }
2457
2458 MOZ_ASSERT_UNREACHABLE("Unexpected eventType");
2459 return NS_OK;
2460 }
2461
UpdateIgnoreKeys(bool aIgnoreKeys)2462 nsresult nsXULPopupManager::UpdateIgnoreKeys(bool aIgnoreKeys) {
2463 nsMenuChainItem* item = GetTopVisibleMenu();
2464 if (item) {
2465 item->SetIgnoreKeys(aIgnoreKeys ? eIgnoreKeys_True : eIgnoreKeys_Shortcuts);
2466 }
2467 UpdateKeyboardListeners();
2468 return NS_OK;
2469 }
2470
KeyUp(KeyboardEvent * aKeyEvent)2471 nsresult nsXULPopupManager::KeyUp(KeyboardEvent* aKeyEvent) {
2472 // don't do anything if a menu isn't open or a menubar isn't active
2473 if (!mActiveMenuBar) {
2474 nsMenuChainItem* item = GetTopVisibleMenu();
2475 if (!item || item->PopupType() != ePopupTypeMenu) return NS_OK;
2476
2477 if (item->IgnoreKeys() == eIgnoreKeys_Shortcuts) {
2478 aKeyEvent->StopCrossProcessForwarding();
2479 return NS_OK;
2480 }
2481 }
2482
2483 aKeyEvent->StopPropagation();
2484 aKeyEvent->StopCrossProcessForwarding();
2485 aKeyEvent->PreventDefault();
2486
2487 return NS_OK; // I am consuming event
2488 }
2489
KeyDown(KeyboardEvent * aKeyEvent)2490 nsresult nsXULPopupManager::KeyDown(KeyboardEvent* aKeyEvent) {
2491 nsMenuChainItem* item = GetTopVisibleMenu();
2492 if (item && item->Frame()->IsMenuLocked()) return NS_OK;
2493
2494 if (HandleKeyboardEventWithKeyCode(aKeyEvent, item)) {
2495 return NS_OK;
2496 }
2497
2498 // don't do anything if a menu isn't open or a menubar isn't active
2499 if (!mActiveMenuBar && (!item || item->PopupType() != ePopupTypeMenu))
2500 return NS_OK;
2501
2502 // Since a menu was open, stop propagation of the event to keep other event
2503 // listeners from becoming confused.
2504 if (!item || item->IgnoreKeys() != eIgnoreKeys_Shortcuts) {
2505 aKeyEvent->StopPropagation();
2506 }
2507
2508 int32_t menuAccessKey = -1;
2509
2510 // If the key just pressed is the access key (usually Alt),
2511 // dismiss and unfocus the menu.
2512
2513 nsMenuBarListener::GetMenuAccessKey(&menuAccessKey);
2514 if (menuAccessKey) {
2515 uint32_t theChar = aKeyEvent->KeyCode();
2516
2517 if (theChar == (uint32_t)menuAccessKey) {
2518 bool ctrl = (menuAccessKey != KeyboardEventBinding::DOM_VK_CONTROL &&
2519 aKeyEvent->CtrlKey());
2520 bool alt = (menuAccessKey != KeyboardEventBinding::DOM_VK_ALT &&
2521 aKeyEvent->AltKey());
2522 bool shift = (menuAccessKey != KeyboardEventBinding::DOM_VK_SHIFT &&
2523 aKeyEvent->ShiftKey());
2524 bool meta = (menuAccessKey != KeyboardEventBinding::DOM_VK_META &&
2525 aKeyEvent->MetaKey());
2526 if (!(ctrl || alt || shift || meta)) {
2527 // The access key just went down and no other
2528 // modifiers are already down.
2529 nsMenuChainItem* item = GetTopVisibleMenu();
2530 if (item && !item->Frame()->IsMenuList()) {
2531 Rollup(0, false, nullptr, nullptr);
2532 } else if (mActiveMenuBar) {
2533 mActiveMenuBar->MenuClosed();
2534 }
2535
2536 // Clear the item to avoid bugs as it may have been deleted during
2537 // rollup.
2538 item = nullptr;
2539 }
2540 aKeyEvent->StopPropagation();
2541 aKeyEvent->PreventDefault();
2542 }
2543 }
2544
2545 aKeyEvent->AsEvent()->StopCrossProcessForwarding();
2546 return NS_OK;
2547 }
2548
KeyPress(KeyboardEvent * aKeyEvent)2549 nsresult nsXULPopupManager::KeyPress(KeyboardEvent* aKeyEvent) {
2550 // Don't check prevent default flag -- menus always get first shot at key
2551 // events.
2552
2553 nsMenuChainItem* item = GetTopVisibleMenu();
2554 if (item &&
2555 (item->Frame()->IsMenuLocked() || item->PopupType() != ePopupTypeMenu)) {
2556 return NS_OK;
2557 }
2558
2559 // if a menu is open or a menubar is active, it consumes the key event
2560 bool consume = (item || mActiveMenuBar);
2561
2562 WidgetInputEvent* evt = aKeyEvent->WidgetEventPtr()->AsInputEvent();
2563 bool isAccel = evt && evt->IsAccel();
2564
2565 // When ignorekeys="shortcuts" is used, we don't call preventDefault on the
2566 // key event when the accelerator key is pressed. This allows another
2567 // listener to handle keys. For instance, this allows global shortcuts to
2568 // still apply while a menu is open.
2569 if (item && item->IgnoreKeys() == eIgnoreKeys_Shortcuts && isAccel) {
2570 consume = false;
2571 }
2572
2573 HandleShortcutNavigation(aKeyEvent, nullptr);
2574
2575 aKeyEvent->StopCrossProcessForwarding();
2576 if (consume) {
2577 aKeyEvent->StopPropagation();
2578 aKeyEvent->PreventDefault();
2579 }
2580
2581 return NS_OK; // I am consuming event
2582 }
2583
2584 NS_IMETHODIMP
Run()2585 nsXULPopupShowingEvent::Run() {
2586 nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
2587 if (pm) {
2588 pm->FirePopupShowingEvent(mPopup, mIsContextMenu, mSelectFirstItem,
2589 nullptr);
2590 }
2591
2592 return NS_OK;
2593 }
2594
2595 NS_IMETHODIMP
Run()2596 nsXULPopupHidingEvent::Run() {
2597 nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
2598
2599 nsIDocument* document = mPopup->GetUncomposedDoc();
2600 if (pm && document) {
2601 nsPresContext* context = document->GetPresContext();
2602 if (context) {
2603 pm->FirePopupHidingEvent(mPopup, mNextPopup, mLastPopup, context,
2604 mPopupType, mDeselectMenu, mIsRollup);
2605 }
2606 }
2607
2608 return NS_OK;
2609 }
2610
DispatchIfNeeded(nsIContent * aPopup,bool aIsContextMenu,bool aSelectFirstItem)2611 bool nsXULPopupPositionedEvent::DispatchIfNeeded(nsIContent* aPopup,
2612 bool aIsContextMenu,
2613 bool aSelectFirstItem) {
2614 // The popuppositioned event only fires on arrow panels for now.
2615 if (aPopup->IsElement() &&
2616 aPopup->AsElement()->AttrValueIs(kNameSpaceID_None, nsGkAtoms::type,
2617 nsGkAtoms::arrow, eCaseMatters)) {
2618 nsCOMPtr<nsIRunnable> event =
2619 new nsXULPopupPositionedEvent(aPopup, aIsContextMenu, aSelectFirstItem);
2620 aPopup->OwnerDoc()->Dispatch(TaskCategory::Other, event.forget());
2621
2622 return true;
2623 }
2624
2625 return false;
2626 }
2627
2628 NS_IMETHODIMP
Run()2629 nsXULPopupPositionedEvent::Run() {
2630 nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
2631 if (pm) {
2632 nsMenuPopupFrame* popupFrame = do_QueryFrame(mPopup->GetPrimaryFrame());
2633 if (popupFrame) {
2634 // At this point, hidePopup may have been called but it currently has no
2635 // way to stop this event. However, if hidePopup was called, the popup
2636 // will now be in the hiding or closed state. If we are in the shown or
2637 // positioning state instead, we can assume that we are still clear to
2638 // open/move the popup
2639 nsPopupState state = popupFrame->PopupState();
2640 if (state != ePopupPositioning && state != ePopupShown) {
2641 return NS_OK;
2642 }
2643 nsEventStatus status = nsEventStatus_eIgnore;
2644 WidgetMouseEvent event(true, eXULPopupPositioned, nullptr,
2645 WidgetMouseEvent::eReal);
2646 EventDispatcher::Dispatch(mPopup, popupFrame->PresContext(), &event,
2647 nullptr, &status);
2648
2649 // Get the popup frame and make sure it is still in the positioning
2650 // state. If it isn't, someone may have tried to reshow or hide it
2651 // during the popuppositioned event.
2652 // Alternately, this event may have been fired in reponse to moving the
2653 // popup rather than opening it. In that case, we are done.
2654 nsMenuPopupFrame* popupFrame = do_QueryFrame(mPopup->GetPrimaryFrame());
2655 if (popupFrame && popupFrame->PopupState() == ePopupPositioning) {
2656 pm->ShowPopupCallback(mPopup, popupFrame, mIsContextMenu,
2657 mSelectFirstItem);
2658 }
2659 }
2660 }
2661
2662 return NS_OK;
2663 }
2664
2665 NS_IMETHODIMP
Run()2666 nsXULMenuCommandEvent::Run() {
2667 nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
2668 if (!pm) return NS_OK;
2669
2670 // The order of the nsViewManager and nsIPresShell COM pointers is
2671 // important below. We want the pres shell to get released before the
2672 // associated view manager on exit from this function.
2673 // See bug 54233.
2674 // XXXndeakin is this still needed?
2675
2676 nsCOMPtr<nsIContent> popup;
2677 nsMenuFrame* menuFrame = do_QueryFrame(mMenu->GetPrimaryFrame());
2678 AutoWeakFrame weakFrame(menuFrame);
2679 if (menuFrame && mFlipChecked) {
2680 if (menuFrame->IsChecked()) {
2681 mMenu->UnsetAttr(kNameSpaceID_None, nsGkAtoms::checked, true);
2682 } else {
2683 mMenu->SetAttr(kNameSpaceID_None, nsGkAtoms::checked,
2684 NS_LITERAL_STRING("true"), true);
2685 }
2686 }
2687
2688 if (menuFrame && weakFrame.IsAlive()) {
2689 // Find the popup that the menu is inside. Below, this popup will
2690 // need to be hidden.
2691 nsIFrame* frame = menuFrame->GetParent();
2692 while (frame) {
2693 nsMenuPopupFrame* popupFrame = do_QueryFrame(frame);
2694 if (popupFrame) {
2695 popup = popupFrame->GetContent();
2696 break;
2697 }
2698 frame = frame->GetParent();
2699 }
2700
2701 nsPresContext* presContext = menuFrame->PresContext();
2702 nsCOMPtr<nsIPresShell> shell = presContext->PresShell();
2703 RefPtr<nsViewManager> kungFuDeathGrip = shell->GetViewManager();
2704 mozilla::Unused
2705 << kungFuDeathGrip; // Not referred to directly within this function
2706
2707 // Deselect ourselves.
2708 if (mCloseMenuMode != CloseMenuMode_None) menuFrame->SelectMenu(false);
2709
2710 AutoHandlingUserInputStatePusher userInpStatePusher(mUserInput, nullptr,
2711 shell->GetDocument());
2712 nsContentUtils::DispatchXULCommand(mMenu, mIsTrusted, nullptr, shell,
2713 mControl, mAlt, mShift, mMeta);
2714 }
2715
2716 if (popup && mCloseMenuMode != CloseMenuMode_None)
2717 pm->HidePopup(popup, mCloseMenuMode == CloseMenuMode_Auto, true, false,
2718 false);
2719
2720 return NS_OK;
2721 }
2722