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