1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4  * License, v. 2.0. If a copy of the MPL was not distributed with this
5  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 
7 #include "nsGkAtoms.h"
8 #include "nsHTMLParts.h"
9 #include "nsMenuFrame.h"
10 #include "nsBoxFrame.h"
11 #include "nsIContent.h"
12 #include "nsAtom.h"
13 #include "nsPresContext.h"
14 #include "mozilla/ComputedStyle.h"
15 #include "nsCSSRendering.h"
16 #include "nsNameSpaceManager.h"
17 #include "nsMenuPopupFrame.h"
18 #include "nsMenuBarFrame.h"
19 #include "mozilla/dom/Document.h"
20 #include "nsBoxLayoutState.h"
21 #include "nsIScrollableFrame.h"
22 #include "nsCSSFrameConstructor.h"
23 #include "nsString.h"
24 #include "nsReadableUtils.h"
25 #include "nsUnicharUtils.h"
26 #include "nsIStringBundle.h"
27 #include "nsContentUtils.h"
28 #include "nsDisplayList.h"
29 #include "nsIReflowCallback.h"
30 #include "nsISound.h"
31 #include "mozilla/Attributes.h"
32 #include "mozilla/EventDispatcher.h"
33 #include "mozilla/Likely.h"
34 #include "mozilla/LookAndFeel.h"
35 #include "mozilla/MouseEvents.h"
36 #include "mozilla/Preferences.h"
37 #include "mozilla/PresShell.h"
38 #include "mozilla/Services.h"
39 #include "mozilla/TextEvents.h"
40 #include "mozilla/dom/Element.h"
41 #include "mozilla/dom/Event.h"
42 #include "mozilla/dom/UserActivation.h"
43 #include <algorithm>
44 
45 using namespace mozilla;
46 using dom::Element;
47 
48 #define NS_MENU_POPUP_LIST_INDEX 0
49 
50 #if defined(XP_WIN)
51 #  define NSCONTEXTMENUISMOUSEUP 1
52 #endif
53 
54 NS_DECLARE_FRAME_PROPERTY_FRAMELIST(PopupListProperty)
55 
56 // This global flag indicates that a menu just opened or closed and is used
57 // to ignore the mousemove and mouseup events that would fire on the menu after
58 // the mousedown occurred.
59 static int32_t gMenuJustOpenedOrClosed = false;
60 
61 const int32_t kBlinkDelay = 67;  // milliseconds
62 
63 // this class is used for dispatching menu activation events asynchronously.
64 class nsMenuActivateEvent : public Runnable {
65  public:
nsMenuActivateEvent(Element * aMenu,nsPresContext * aPresContext,bool aIsActivate)66   nsMenuActivateEvent(Element* aMenu, nsPresContext* aPresContext,
67                       bool aIsActivate)
68       : mozilla::Runnable("nsMenuActivateEvent"),
69         mMenu(aMenu),
70         mPresContext(aPresContext),
71         mIsActivate(aIsActivate) {}
72 
Run()73   NS_IMETHOD Run() override {
74     nsAutoString domEventToFire;
75 
76     if (mIsActivate) {
77       // Highlight the menu.
78       mMenu->SetAttr(kNameSpaceID_None, nsGkAtoms::menuactive, u"true"_ns,
79                      true);
80       // The menuactivated event is used by accessibility to track the user's
81       // movements through menus
82       domEventToFire.AssignLiteral("DOMMenuItemActive");
83     } else {
84       // Unhighlight the menu.
85       mMenu->UnsetAttr(kNameSpaceID_None, nsGkAtoms::menuactive, true);
86       domEventToFire.AssignLiteral("DOMMenuItemInactive");
87     }
88 
89     RefPtr<dom::Event> event = NS_NewDOMEvent(mMenu, mPresContext, nullptr);
90     event->InitEvent(domEventToFire, true, true);
91 
92     event->SetTrusted(true);
93 
94     EventDispatcher::DispatchDOMEvent(mMenu, nullptr, event, mPresContext,
95                                       nullptr);
96 
97     return NS_OK;
98   }
99 
100  private:
101   RefPtr<Element> mMenu;
102   RefPtr<nsPresContext> mPresContext;
103   bool mIsActivate;
104 };
105 
106 class nsMenuAttributeChangedEvent : public Runnable {
107  public:
nsMenuAttributeChangedEvent(nsIFrame * aFrame,nsAtom * aAttr)108   nsMenuAttributeChangedEvent(nsIFrame* aFrame, nsAtom* aAttr)
109       : mozilla::Runnable("nsMenuAttributeChangedEvent"),
110         mFrame(aFrame),
111         mAttr(aAttr) {}
112 
Run()113   NS_IMETHOD Run() override {
114     nsMenuFrame* frame = static_cast<nsMenuFrame*>(mFrame.GetFrame());
115     NS_ENSURE_STATE(frame);
116     if (mAttr == nsGkAtoms::checked) {
117       frame->UpdateMenuSpecialState();
118     } else if (mAttr == nsGkAtoms::type || mAttr == nsGkAtoms::name) {
119       frame->UpdateMenuType();
120     }
121     return NS_OK;
122   }
123 
124  protected:
125   WeakFrame mFrame;
126   RefPtr<nsAtom> mAttr;
127 };
128 
129 //
130 // NS_NewMenuFrame and NS_NewMenuItemFrame
131 //
132 // Wrappers for creating a new menu popup container
133 //
NS_NewMenuFrame(PresShell * aPresShell,ComputedStyle * aStyle)134 nsIFrame* NS_NewMenuFrame(PresShell* aPresShell, ComputedStyle* aStyle) {
135   nsMenuFrame* it =
136       new (aPresShell) nsMenuFrame(aStyle, aPresShell->GetPresContext());
137   it->SetIsMenu(true);
138   return it;
139 }
140 
NS_NewMenuItemFrame(PresShell * aPresShell,ComputedStyle * aStyle)141 nsIFrame* NS_NewMenuItemFrame(PresShell* aPresShell, ComputedStyle* aStyle) {
142   nsMenuFrame* it =
143       new (aPresShell) nsMenuFrame(aStyle, aPresShell->GetPresContext());
144   it->SetIsMenu(false);
145   return it;
146 }
147 
148 NS_IMPL_FRAMEARENA_HELPERS(nsMenuFrame)
149 
NS_QUERYFRAME_HEAD(nsMenuFrame)150 NS_QUERYFRAME_HEAD(nsMenuFrame)
151   NS_QUERYFRAME_ENTRY(nsMenuFrame)
152 NS_QUERYFRAME_TAIL_INHERITING(nsBoxFrame)
153 
154 nsMenuFrame::nsMenuFrame(ComputedStyle* aStyle, nsPresContext* aPresContext)
155     : nsBoxFrame(aStyle, aPresContext, kClassID),
156       mIsMenu(false),
157       mChecked(false),
158       mReflowCallbackPosted(false),
159       mType(eMenuType_Normal),
160       mBlinkState(0) {}
161 
GetMenuParent() const162 nsMenuParent* nsMenuFrame::GetMenuParent() const {
163   nsContainerFrame* parent = GetParent();
164   for (; parent; parent = parent->GetParent()) {
165     nsMenuPopupFrame* popup = do_QueryFrame(parent);
166     if (popup) {
167       return popup;
168     }
169     nsMenuBarFrame* menubar = do_QueryFrame(parent);
170     if (menubar) {
171       return menubar;
172     }
173   }
174   return nullptr;
175 }
176 
ReflowFinished()177 bool nsMenuFrame::ReflowFinished() {
178   mReflowCallbackPosted = false;
179 
180   UpdateMenuType();
181   return true;
182 }
183 
ReflowCallbackCanceled()184 void nsMenuFrame::ReflowCallbackCanceled() { mReflowCallbackPosted = false; }
185 
Init(nsIContent * aContent,nsContainerFrame * aParent,nsIFrame * aPrevInFlow)186 void nsMenuFrame::Init(nsIContent* aContent, nsContainerFrame* aParent,
187                        nsIFrame* aPrevInFlow) {
188   nsBoxFrame::Init(aContent, aParent, aPrevInFlow);
189 
190   // Set up a mediator which can be used for callbacks on this frame.
191   mTimerMediator = new nsMenuTimerMediator(this);
192 
193   if (!mReflowCallbackPosted) {
194     mReflowCallbackPosted = true;
195     PresShell()->PostReflowCallback(this);
196   }
197 }
198 
GetChildList(ChildListID aListID) const199 const nsFrameList& nsMenuFrame::GetChildList(ChildListID aListID) const {
200   if (kPopupList == aListID) {
201     nsFrameList* list = GetPopupList();
202     return list ? *list : nsFrameList::EmptyList();
203   }
204   return nsBoxFrame::GetChildList(aListID);
205 }
206 
GetChildLists(nsTArray<ChildList> * aLists) const207 void nsMenuFrame::GetChildLists(nsTArray<ChildList>* aLists) const {
208   nsBoxFrame::GetChildLists(aLists);
209   nsFrameList* list = GetPopupList();
210   if (list) {
211     list->AppendIfNonempty(aLists, kPopupList);
212   }
213 }
214 
GetPopup() const215 nsMenuPopupFrame* nsMenuFrame::GetPopup() const {
216   nsFrameList* popupList = GetPopupList();
217   return popupList ? static_cast<nsMenuPopupFrame*>(popupList->FirstChild())
218                    : nullptr;
219 }
220 
GetPopupList() const221 nsFrameList* nsMenuFrame::GetPopupList() const {
222   if (!HasPopup()) {
223     return nullptr;
224   }
225   nsFrameList* prop = GetProperty(PopupListProperty());
226   NS_ASSERTION(
227       prop && prop->GetLength() == 1 && prop->FirstChild()->IsMenuPopupFrame(),
228       "popup list should have exactly one nsMenuPopupFrame");
229   return prop;
230 }
231 
DestroyPopupList()232 void nsMenuFrame::DestroyPopupList() {
233   NS_ASSERTION(HasPopup(), "huh?");
234   nsFrameList* prop = TakeProperty(PopupListProperty());
235   NS_ASSERTION(prop && prop->IsEmpty(),
236                "popup list must exist and be empty when destroying");
237   RemoveStateBits(NS_STATE_MENU_HAS_POPUP_LIST);
238   prop->Delete(PresShell());
239 }
240 
SetPopupFrame(nsFrameList & aFrameList)241 void nsMenuFrame::SetPopupFrame(nsFrameList& aFrameList) {
242   for (nsFrameList::Enumerator e(aFrameList); !e.AtEnd(); e.Next()) {
243     nsMenuPopupFrame* popupFrame = do_QueryFrame(e.get());
244     if (popupFrame) {
245       // Remove the frame from the list and store it in a nsFrameList* property.
246       aFrameList.RemoveFrame(popupFrame);
247       nsFrameList* popupList =
248           new (PresShell()) nsFrameList(popupFrame, popupFrame);
249       SetProperty(PopupListProperty(), popupList);
250       AddStateBits(NS_STATE_MENU_HAS_POPUP_LIST);
251       break;
252     }
253   }
254 }
255 
SetInitialChildList(ChildListID aListID,nsFrameList & aChildList)256 void nsMenuFrame::SetInitialChildList(ChildListID aListID,
257                                       nsFrameList& aChildList) {
258   if (aListID == kPrincipalList || aListID == kPopupList) {
259     NS_ASSERTION(!HasPopup(), "SetInitialChildList called twice?");
260 #ifdef DEBUG
261     for (nsIFrame* f : aChildList) {
262       MOZ_ASSERT(f->GetParent() == this, "Unexpected parent");
263     }
264 #endif
265     SetPopupFrame(aChildList);
266   }
267   nsBoxFrame::SetInitialChildList(aListID, aChildList);
268 }
269 
DestroyFrom(nsIFrame * aDestructRoot,PostDestroyData & aPostDestroyData)270 void nsMenuFrame::DestroyFrom(nsIFrame* aDestructRoot,
271                               PostDestroyData& aPostDestroyData) {
272   if (mReflowCallbackPosted) {
273     PresShell()->CancelReflowCallback(this);
274     mReflowCallbackPosted = false;
275   }
276 
277   // Kill our timer if one is active. This is not strictly necessary as
278   // the pointer to this frame will be cleared from the mediator, but
279   // this is done for added safety.
280   if (mOpenTimer) {
281     mOpenTimer->Cancel();
282   }
283 
284   StopBlinking();
285 
286   // Null out the pointer to this frame in the mediator wrapper so that it
287   // doesn't try to interact with a deallocated frame.
288   mTimerMediator->ClearFrame();
289 
290   // if the menu content is just being hidden, it may be made visible again
291   // later, so make sure to clear the highlighting.
292   mContent->AsElement()->UnsetAttr(kNameSpaceID_None, nsGkAtoms::menuactive,
293                                    false);
294 
295   // are we our menu parent's current menu item?
296   nsMenuParent* menuParent = GetMenuParent();
297   if (menuParent && menuParent->GetCurrentMenuItem() == this) {
298     // yes; tell it that we're going away
299     menuParent->CurrentMenuIsBeingDestroyed();
300   }
301 
302   nsFrameList* popupList = GetPopupList();
303   if (popupList) {
304     popupList->DestroyFramesFrom(aDestructRoot, aPostDestroyData);
305     DestroyPopupList();
306   }
307 
308   nsBoxFrame::DestroyFrom(aDestructRoot, aPostDestroyData);
309 }
310 
BuildDisplayListForChildren(nsDisplayListBuilder * aBuilder,const nsDisplayListSet & aLists)311 void nsMenuFrame::BuildDisplayListForChildren(nsDisplayListBuilder* aBuilder,
312                                               const nsDisplayListSet& aLists) {
313   if (!aBuilder->IsForEventDelivery()) {
314     nsBoxFrame::BuildDisplayListForChildren(aBuilder, aLists);
315     return;
316   }
317 
318   nsDisplayListCollection set(aBuilder);
319   nsBoxFrame::BuildDisplayListForChildren(aBuilder, set);
320 
321   WrapListsInRedirector(aBuilder, set, aLists);
322 }
323 
HandleEvent(nsPresContext * aPresContext,WidgetGUIEvent * aEvent,nsEventStatus * aEventStatus)324 nsresult nsMenuFrame::HandleEvent(nsPresContext* aPresContext,
325                                   WidgetGUIEvent* aEvent,
326                                   nsEventStatus* aEventStatus) {
327   NS_ENSURE_ARG_POINTER(aEventStatus);
328   if (nsEventStatus_eConsumeNoDefault == *aEventStatus) {
329     return NS_OK;
330   }
331   nsMenuParent* menuParent = GetMenuParent();
332   if (menuParent && menuParent->IsMenuLocked()) {
333     return NS_OK;
334   }
335 
336   AutoWeakFrame weakFrame(this);
337   if (*aEventStatus == nsEventStatus_eIgnore)
338     *aEventStatus = nsEventStatus_eConsumeDoDefault;
339 
340   // If a menu just opened, ignore the mouseup event that might occur after a
341   // the mousedown event that opened it. However, if a different mousedown
342   // event occurs, just clear this flag.
343   if (gMenuJustOpenedOrClosed) {
344     if (aEvent->mMessage == eMouseDown) {
345       gMenuJustOpenedOrClosed = false;
346     } else if (aEvent->mMessage == eMouseUp) {
347       return NS_OK;
348     }
349   }
350 
351   bool onmenu = IsOnMenu();
352 
353   if (aEvent->mMessage == eKeyPress && !IsDisabled()) {
354     WidgetKeyboardEvent* keyEvent = aEvent->AsKeyboardEvent();
355     uint32_t keyCode = keyEvent->mKeyCode;
356 #ifdef XP_MACOSX
357     // On mac, open menulist on either up/down arrow or space (w/o Cmd pressed)
358     if (!IsOpen() && ((keyEvent->mCharCode == ' ' && !keyEvent->IsMeta()) ||
359                       (keyCode == NS_VK_UP || keyCode == NS_VK_DOWN))) {
360       // When pressing space, don't open the menu if performing an incremental
361       // search.
362       if (keyEvent->mCharCode != ' ' ||
363           !nsMenuPopupFrame::IsWithinIncrementalTime(keyEvent->mTime)) {
364         *aEventStatus = nsEventStatus_eConsumeNoDefault;
365         OpenMenu(false);
366       }
367     }
368 #else
369     // On other platforms, toggle menulist on unmodified F4 or Alt arrow
370     if ((keyCode == NS_VK_F4 && !keyEvent->IsAlt()) ||
371         ((keyCode == NS_VK_UP || keyCode == NS_VK_DOWN) && keyEvent->IsAlt())) {
372       *aEventStatus = nsEventStatus_eConsumeNoDefault;
373       ToggleMenuState();
374     }
375 #endif
376   } else if (aEvent->mMessage == eMouseDown &&
377              aEvent->AsMouseEvent()->mButton == MouseButton::ePrimary &&
378 #ifdef XP_MACOSX
379              // On mac, ctrl-click will send a context menu event from the
380              // widget, so we don't want to bring up the menu.
381              !aEvent->AsMouseEvent()->IsControl() &&
382 #endif
383              !IsDisabled() && IsMenu()) {
384     // The menu item was selected. Bring up the menu.
385     // We have children.
386     // Don't prevent the default action here, since that will also cancel
387     // potential drag starts.
388     if (!menuParent || menuParent->IsMenuBar()) {
389       ToggleMenuState();
390     } else {
391       if (!IsOpen()) {
392         menuParent->ChangeMenuItem(this, false, false);
393         OpenMenu(false);
394       }
395     }
396   } else if (aEvent->mMessage == eMouseUp && !IsMenu() && !IsDisabled()) {
397     // We accept left and middle clicks on all menu items to activate the item.
398     // On context menus we also accept right click to activate the item, because
399     // right-clicking on an item in a context menu cannot open another context
400     // menu.
401     bool isMacCtrlClick = false;
402 #ifdef XP_MACOSX
403     isMacCtrlClick = aEvent->AsMouseEvent()->mButton == MouseButton::ePrimary &&
404                      aEvent->AsMouseEvent()->IsControl();
405 #endif
406     bool clickMightOpenContextMenu =
407         aEvent->AsMouseEvent()->mButton == MouseButton::eSecondary ||
408         isMacCtrlClick;
409     if (!clickMightOpenContextMenu || (onmenu && menuParent->IsContextMenu())) {
410       *aEventStatus = nsEventStatus_eConsumeNoDefault;
411       Execute(aEvent);
412     }
413   } else if (aEvent->mMessage == eContextMenu && onmenu && !IsMenu() &&
414              !IsDisabled() && menuParent->IsContextMenu()) {
415     // Make sure we cancel default processing of the context menu event so
416     // that it doesn't bubble and get seen again by the popuplistener and show
417     // another context menu.
418     *aEventStatus = nsEventStatus_eConsumeNoDefault;
419   } else if (aEvent->mMessage == eMouseOut) {
420     // Kill our timer if one is active.
421     if (mOpenTimer) {
422       mOpenTimer->Cancel();
423       mOpenTimer = nullptr;
424     }
425 
426     // Deactivate the menu.
427     if (menuParent) {
428       bool onmenubar = menuParent->IsMenuBar();
429       if (!(onmenubar && menuParent->IsActive())) {
430         if (IsMenu() && !onmenubar && IsOpen()) {
431           // Submenus don't get closed up immediately.
432         } else if (this == menuParent->GetCurrentMenuItem()
433 #ifdef XP_WIN
434                    && !IsParentMenuList()
435 #endif
436         ) {
437           menuParent->ChangeMenuItem(nullptr, false, false);
438         }
439       }
440     }
441   } else if (aEvent->mMessage == eMouseMove &&
442              (onmenu || (menuParent && menuParent->IsMenuBar()))) {
443     if (gMenuJustOpenedOrClosed) {
444       gMenuJustOpenedOrClosed = false;
445       return NS_OK;
446     }
447 
448     if (IsDisabled() && IsParentMenuList()) {
449       return NS_OK;
450     }
451 
452     // Let the menu parent know we're the new item.
453     menuParent->ChangeMenuItem(this, false, false);
454     NS_ENSURE_TRUE(weakFrame.IsAlive(), NS_OK);
455     NS_ENSURE_TRUE(menuParent, NS_OK);
456 
457     // we need to check if we really became the current menu
458     // item or not
459     nsMenuFrame* realCurrentItem = menuParent->GetCurrentMenuItem();
460     if (realCurrentItem != this) {
461       // we didn't (presumably because a context menu was active)
462       return NS_OK;
463     }
464 
465     // Hovering over a menu in a popup should open it without a need for a
466     // click. A timer is used so that it doesn't open if the user moves the
467     // mouse quickly past the menu. This conditional check ensures that only
468     // menus have this behaviour
469     if (!IsDisabled() && IsMenu() && !IsOpen() && !mOpenTimer &&
470         !menuParent->IsMenuBar()) {
471       int32_t menuDelay =
472           LookAndFeel::GetInt(LookAndFeel::IntID::SubmenuDelay, 300);  // ms
473 
474       // We're a menu, we're built, we're closed, and no timer has been kicked
475       // off.
476       NS_NewTimerWithCallback(
477           getter_AddRefs(mOpenTimer), mTimerMediator, menuDelay,
478           nsITimer::TYPE_ONE_SHOT,
479           mContent->OwnerDoc()->EventTargetFor(TaskCategory::Other));
480     }
481   }
482 
483   return NS_OK;
484 }
485 
ToggleMenuState()486 void nsMenuFrame::ToggleMenuState() {
487   if (IsOpen())
488     CloseMenu(false);
489   else
490     OpenMenu(false);
491 }
492 
PopupOpened()493 void nsMenuFrame::PopupOpened() {
494   gMenuJustOpenedOrClosed = true;
495 
496   AutoWeakFrame weakFrame(this);
497   mContent->AsElement()->SetAttr(kNameSpaceID_None, nsGkAtoms::open, u"true"_ns,
498                                  true);
499   if (!weakFrame.IsAlive()) return;
500 
501   nsMenuParent* menuParent = GetMenuParent();
502   if (menuParent) {
503     menuParent->SetActive(true);
504     // Make sure the current menu which is being toggled on
505     // the menubar is highlighted
506     menuParent->SetCurrentMenuItem(this);
507   }
508 }
509 
PopupClosed(bool aDeselectMenu)510 void nsMenuFrame::PopupClosed(bool aDeselectMenu) {
511   AutoWeakFrame weakFrame(this);
512   nsContentUtils::AddScriptRunner(
513       new nsUnsetAttrRunnable(mContent->AsElement(), nsGkAtoms::open));
514   if (!weakFrame.IsAlive()) return;
515 
516   // if the popup is for a menu on a menubar, inform menubar to deactivate
517   nsMenuParent* menuParent = GetMenuParent();
518   if (menuParent && menuParent->MenuClosed()) {
519     if (aDeselectMenu) {
520       SelectMenu(false);
521     } else {
522       // We are not deselecting the parent menu while closing the popup, so send
523       // a DOMMenuItemActive event to the menu to indicate that the menu is
524       // becoming active again.
525       nsMenuFrame* current = menuParent->GetCurrentMenuItem();
526       if (current) {
527         // However, if the menu is a descendant on a menubar, and the menubar
528         // has the 'stay active' flag set, it means that the menubar is
529         // switching to another toplevel menu entirely (for example from Edit to
530         // View), so don't fire the DOMMenuItemActive event or else we'll send
531         // extraneous events for submenus. nsMenuBarFrame::ChangeMenuItem has
532         // already deselected the old menu, so it doesn't need to happen again
533         // here, and the new menu can be selected right away.
534         nsIFrame* parent = current;
535         while (parent) {
536           nsMenuBarFrame* menubar = do_QueryFrame(parent);
537           if (menubar && menubar->GetStayActive()) return;
538 
539           parent = parent->GetParent();
540         }
541 
542         nsCOMPtr<nsIRunnable> event = new nsMenuActivateEvent(
543             current->GetContent()->AsElement(), PresContext(), true);
544         mContent->OwnerDoc()->Dispatch(TaskCategory::Other, event.forget());
545       }
546     }
547   }
548 }
549 
550 NS_IMETHODIMP
SelectMenu(bool aActivateFlag)551 nsMenuFrame::SelectMenu(bool aActivateFlag) {
552   if (mContent) {
553     // When a menu opens a submenu, the mouse will often be moved onto a
554     // sibling before moving onto an item within the submenu, causing the
555     // parent to become deselected. We need to ensure that the parent menu
556     // is reselected when an item in the submenu is selected, so navigate up
557     // from the item to its popup, and then to the popup above that.
558     if (aActivateFlag) {
559       nsIFrame* parent = GetParent();
560       while (parent) {
561         nsMenuPopupFrame* menupopup = do_QueryFrame(parent);
562         if (menupopup) {
563           // a menu is always the direct parent of a menupopup
564           nsMenuFrame* menu = do_QueryFrame(menupopup->GetParent());
565           if (menu) {
566             // a popup however is not necessarily the direct parent of a menu
567             nsIFrame* popupParent = menu->GetParent();
568             while (popupParent) {
569               menupopup = do_QueryFrame(popupParent);
570               if (menupopup) {
571                 menupopup->SetCurrentMenuItem(menu);
572                 break;
573               }
574               popupParent = popupParent->GetParent();
575             }
576           }
577           break;
578         }
579         parent = parent->GetParent();
580       }
581     }
582 
583     // cancel the close timer if selecting a menu within the popup to be closed
584     nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
585     if (pm) {
586       nsMenuParent* menuParent = GetMenuParent();
587       pm->CancelMenuTimer(menuParent);
588     }
589 
590     nsCOMPtr<nsIRunnable> event = new nsMenuActivateEvent(
591         mContent->AsElement(), PresContext(), aActivateFlag);
592     mContent->OwnerDoc()->Dispatch(TaskCategory::Other, event.forget());
593   }
594 
595   return NS_OK;
596 }
597 
AttributeChanged(int32_t aNameSpaceID,nsAtom * aAttribute,int32_t aModType)598 nsresult nsMenuFrame::AttributeChanged(int32_t aNameSpaceID, nsAtom* aAttribute,
599                                        int32_t aModType) {
600   if (aAttribute == nsGkAtoms::checked || aAttribute == nsGkAtoms::acceltext ||
601       aAttribute == nsGkAtoms::key || aAttribute == nsGkAtoms::type ||
602       aAttribute == nsGkAtoms::name) {
603     nsCOMPtr<nsIRunnable> event =
604         new nsMenuAttributeChangedEvent(this, aAttribute);
605     nsContentUtils::AddScriptRunner(event);
606   }
607   return NS_OK;
608 }
609 
OpenMenu(bool aSelectFirstItem)610 void nsMenuFrame::OpenMenu(bool aSelectFirstItem) {
611   if (!mContent) return;
612 
613   nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
614   if (pm) {
615     pm->KillMenuTimer();
616     // This opens the menu asynchronously
617     pm->ShowMenu(mContent, aSelectFirstItem, true);
618   }
619 }
620 
CloseMenu(bool aDeselectMenu)621 void nsMenuFrame::CloseMenu(bool aDeselectMenu) {
622   gMenuJustOpenedOrClosed = true;
623 
624   // Close the menu asynchronously
625   nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
626   if (pm && HasPopup())
627     pm->HidePopup(GetPopup()->GetContent(), false, aDeselectMenu, true, false);
628 }
629 
IsSizedToPopup(nsIContent * aContent,bool aRequireAlways)630 bool nsMenuFrame::IsSizedToPopup(nsIContent* aContent, bool aRequireAlways) {
631   MOZ_ASSERT(aContent->IsElement());
632   nsAutoString sizedToPopup;
633   aContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::sizetopopup,
634                                  sizedToPopup);
635   bool sizedToPopupSetToPref =
636       sizedToPopup.EqualsLiteral("pref") ||
637       (sizedToPopup.IsEmpty() && aContent->IsXULElement(nsGkAtoms::menulist));
638   return sizedToPopup.EqualsLiteral("always") ||
639          (!aRequireAlways && sizedToPopupSetToPref);
640 }
641 
GetXULMinSize(nsBoxLayoutState & aBoxLayoutState)642 nsSize nsMenuFrame::GetXULMinSize(nsBoxLayoutState& aBoxLayoutState) {
643   nsSize size = nsBoxFrame::GetXULMinSize(aBoxLayoutState);
644   DISPLAY_MIN_SIZE(this, size);
645 
646   if (IsSizedToPopup(mContent, true)) SizeToPopup(aBoxLayoutState, size);
647 
648   return size;
649 }
650 
651 NS_IMETHODIMP
DoXULLayout(nsBoxLayoutState & aState)652 nsMenuFrame::DoXULLayout(nsBoxLayoutState& aState) {
653   // lay us out
654   nsresult rv = nsBoxFrame::DoXULLayout(aState);
655 
656   nsMenuPopupFrame* popupFrame = GetPopup();
657   if (popupFrame) {
658     bool sizeToPopup = IsSizedToPopup(mContent, false);
659     popupFrame->LayoutPopup(aState, this, sizeToPopup);
660   }
661 
662   return rv;
663 }
664 
665 //
666 // Enter
667 //
668 // Called when the user hits the <Enter>/<Return> keys or presses the
669 // shortcut key. If this is a leaf item, the item's action will be executed.
670 // In either case, do nothing if the item is disabled.
671 //
Enter(WidgetGUIEvent * aEvent)672 nsMenuFrame* nsMenuFrame::Enter(WidgetGUIEvent* aEvent) {
673   if (IsDisabled()) {
674 #ifdef XP_WIN
675     // behavior on Windows - close the popup chain
676     nsMenuParent* menuParent = GetMenuParent();
677     if (menuParent) {
678       nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
679       if (pm) {
680         nsIFrame* popup = pm->GetTopPopup(ePopupTypeAny);
681         if (popup) pm->HidePopup(popup->GetContent(), true, true, true, false);
682       }
683     }
684 #endif  // #ifdef XP_WIN
685     // this menu item was disabled - exit
686     return nullptr;
687   }
688 
689   if (!IsOpen()) {
690     // The enter key press applies to us.
691     nsMenuParent* menuParent = GetMenuParent();
692     if (!IsMenu() && menuParent)
693       Execute(aEvent);  // Execute our event handler
694     else
695       return this;
696   }
697 
698   return nullptr;
699 }
700 
IsOpen()701 bool nsMenuFrame::IsOpen() {
702   nsMenuPopupFrame* popupFrame = GetPopup();
703   return popupFrame && popupFrame->IsOpen();
704 }
705 
IsMenu()706 bool nsMenuFrame::IsMenu() { return mIsMenu; }
707 
IsParentMenuList()708 bool nsMenuFrame::IsParentMenuList() {
709   nsMenuParent* menuParent = GetMenuParent();
710   if (menuParent && menuParent->IsMenu()) {
711     nsMenuPopupFrame* popupFrame = static_cast<nsMenuPopupFrame*>(menuParent);
712     return popupFrame->IsMenuList();
713   }
714   return false;
715 }
716 
Notify(nsITimer * aTimer)717 nsresult nsMenuFrame::Notify(nsITimer* aTimer) {
718   // Our timer has fired.
719   if (aTimer == mOpenTimer.get()) {
720     mOpenTimer = nullptr;
721 
722     nsMenuParent* menuParent = GetMenuParent();
723     if (!IsOpen() && menuParent) {
724       // make sure we didn't open a context menu in the meantime
725       // (i.e. the user right-clicked while hovering over a submenu).
726       nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
727       if (pm) {
728         if ((!pm->HasContextMenu(nullptr) || menuParent->IsContextMenu()) &&
729             mContent->AsElement()->AttrValueIs(
730                 kNameSpaceID_None, nsGkAtoms::menuactive, nsGkAtoms::_true,
731                 eCaseMatters)) {
732           OpenMenu(false);
733         }
734       }
735     }
736   } else if (aTimer == mBlinkTimer) {
737     switch (mBlinkState++) {
738       case 0:
739         NS_ASSERTION(false, "Blink timer fired while not blinking");
740         StopBlinking();
741         break;
742       case 1: {
743         // Turn the highlight back on and wait for a while before closing the
744         // menu.
745         AutoWeakFrame weakFrame(this);
746         mContent->AsElement()->SetAttr(kNameSpaceID_None, nsGkAtoms::menuactive,
747                                        u"true"_ns, true);
748         if (weakFrame.IsAlive()) {
749           aTimer->InitWithCallback(mTimerMediator, kBlinkDelay,
750                                    nsITimer::TYPE_ONE_SHOT);
751         }
752       } break;
753       default: {
754         nsMenuParent* menuParent = GetMenuParent();
755         if (menuParent) {
756           menuParent->LockMenuUntilClosed(false);
757         }
758         PassMenuCommandEventToPopupManager();
759         StopBlinking();
760         break;
761       }
762     }
763   }
764 
765   return NS_OK;
766 }
767 
IsDisabled()768 bool nsMenuFrame::IsDisabled() {
769   return mContent->AsElement()->AttrValueIs(
770       kNameSpaceID_None, nsGkAtoms::disabled, nsGkAtoms::_true, eCaseMatters);
771 }
772 
UpdateMenuType()773 void nsMenuFrame::UpdateMenuType() {
774   static Element::AttrValuesArray strings[] = {nsGkAtoms::checkbox,
775                                                nsGkAtoms::radio, nullptr};
776   switch (mContent->AsElement()->FindAttrValueIn(
777       kNameSpaceID_None, nsGkAtoms::type, strings, eCaseMatters)) {
778     case 0:
779       mType = eMenuType_Checkbox;
780       break;
781     case 1:
782       mType = eMenuType_Radio;
783       mContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::name,
784                                      mGroupName);
785       break;
786 
787     default:
788       if (mType != eMenuType_Normal) {
789         AutoWeakFrame weakFrame(this);
790         mContent->AsElement()->UnsetAttr(kNameSpaceID_None, nsGkAtoms::checked,
791                                          true);
792         NS_ENSURE_TRUE_VOID(weakFrame.IsAlive());
793       }
794       mType = eMenuType_Normal;
795       break;
796   }
797   UpdateMenuSpecialState();
798 }
799 
800 /* update checked-ness for type="checkbox" and type="radio" */
UpdateMenuSpecialState()801 void nsMenuFrame::UpdateMenuSpecialState() {
802   bool newChecked = mContent->AsElement()->AttrValueIs(
803       kNameSpaceID_None, nsGkAtoms::checked, nsGkAtoms::_true, eCaseMatters);
804   if (newChecked == mChecked) {
805     /* checked state didn't change */
806 
807     if (mType != eMenuType_Radio)
808       return;  // only Radio possibly cares about other kinds of change
809 
810     if (!mChecked || mGroupName.IsEmpty()) return;  // no interesting change
811   } else {
812     mChecked = newChecked;
813     if (mType != eMenuType_Radio || !mChecked)
814       /*
815        * Unchecking something requires no further changes, and only
816        * menuRadio has to do additional work when checked.
817        */
818       return;
819   }
820 
821   /*
822    * If we get this far, we're type=radio, and:
823    * - our name= changed, or
824    * - we went from checked="false" to checked="true"
825    */
826 
827   /*
828    * Behavioural note:
829    * If we're checked and renamed _into_ an existing radio group, we are
830    * made the new checked item, and we unselect the previous one.
831    *
832    * The only other reasonable behaviour would be to check for another selected
833    * item in that group.  If found, unselect ourselves, otherwise we're the
834    * selected item.  That, however, would be a lot more work, and I don't think
835    * it's better at all.
836    */
837 
838   /* walk siblings, looking for the other checked item with the same name */
839   // get the first sibling in this menu popup. This frame may be it, and if
840   // we're being called at creation time, this frame isn't yet in the parent's
841   // child list. All I'm saying is that this may fail, but it's most likely
842   // alright.
843   nsIFrame* firstMenuItem =
844       nsXULPopupManager::GetNextMenuItem(GetParent(), nullptr, true, false);
845   nsIFrame* sib = firstMenuItem;
846   while (sib) {
847     nsMenuFrame* menu = do_QueryFrame(sib);
848     if (sib != this) {
849       if (menu && menu->GetMenuType() == eMenuType_Radio && menu->IsChecked() &&
850           menu->GetRadioGroupName() == mGroupName) {
851         /* uncheck the old item */
852         sib->GetContent()->AsElement()->UnsetAttr(kNameSpaceID_None,
853                                                   nsGkAtoms::checked, true);
854         // XXX in DEBUG, check to make sure that there aren't two checked items
855         return;
856       }
857     }
858     sib = nsXULPopupManager::GetNextMenuItem(GetParent(), menu, true, true);
859     if (sib == firstMenuItem) {
860       break;
861     }
862   }
863 }
864 
Execute(WidgetGUIEvent * aEvent)865 void nsMenuFrame::Execute(WidgetGUIEvent* aEvent) {
866   nsCOMPtr<nsISound> sound(do_CreateInstance("@mozilla.org/sound;1"));
867   if (sound) sound->PlayEventSound(nsISound::EVENT_MENU_EXECUTE);
868 
869   // Create a trusted event if the triggering event was trusted, or if
870   // we're called from chrome code (since at least one of our caller
871   // passes in a null event).
872   bool isTrusted =
873       aEvent ? aEvent->IsTrusted() : nsContentUtils::IsCallerChrome();
874 
875   mozilla::Modifiers modifiers = 0;
876   WidgetInputEvent* inputEvent = aEvent ? aEvent->AsInputEvent() : nullptr;
877   if (inputEvent) {
878     modifiers = inputEvent->mModifiers;
879   }
880 
881   int16_t button = 0;
882   WidgetMouseEventBase* mouseEvent =
883       aEvent ? aEvent->AsMouseEventBase() : nullptr;
884   if (mouseEvent) {
885     button = mouseEvent->mButton;
886   }
887 
888   StopBlinking();
889   CreateMenuCommandEvent(isTrusted, modifiers, button);
890   StartBlinking();
891 }
892 
ActivateItem(Modifiers aModifiers,int16_t aButton)893 void nsMenuFrame::ActivateItem(Modifiers aModifiers, int16_t aButton) {
894   StopBlinking();
895   CreateMenuCommandEvent(nsContentUtils::IsCallerChrome(), aModifiers, aButton);
896   StartBlinking();
897 }
898 
ShouldBlink()899 bool nsMenuFrame::ShouldBlink() {
900   int32_t shouldBlink =
901       LookAndFeel::GetInt(LookAndFeel::IntID::ChosenMenuItemsShouldBlink, 0);
902   if (!shouldBlink) return false;
903 
904   return true;
905 }
906 
StartBlinking()907 void nsMenuFrame::StartBlinking() {
908   if (!ShouldBlink()) {
909     PassMenuCommandEventToPopupManager();
910     return;
911   }
912 
913   // Blink off.
914   AutoWeakFrame weakFrame(this);
915   mContent->AsElement()->UnsetAttr(kNameSpaceID_None, nsGkAtoms::menuactive,
916                                    true);
917   if (!weakFrame.IsAlive()) return;
918 
919   nsMenuParent* menuParent = GetMenuParent();
920   if (menuParent) {
921     // Make this menu ignore events from now on.
922     menuParent->LockMenuUntilClosed(true);
923   }
924 
925   // Set up a timer to blink back on.
926   NS_NewTimerWithCallback(
927       getter_AddRefs(mBlinkTimer), mTimerMediator, kBlinkDelay,
928       nsITimer::TYPE_ONE_SHOT,
929       mContent->OwnerDoc()->EventTargetFor(TaskCategory::Other));
930   mBlinkState = 1;
931 }
932 
StopBlinking()933 void nsMenuFrame::StopBlinking() {
934   mBlinkState = 0;
935   if (mBlinkTimer) {
936     mBlinkTimer->Cancel();
937     mBlinkTimer = nullptr;
938   }
939   mDelayedMenuCommandEvent = nullptr;
940 }
941 
CreateMenuCommandEvent(bool aIsTrusted,mozilla::Modifiers aModifiers,int16_t aButton)942 void nsMenuFrame::CreateMenuCommandEvent(bool aIsTrusted,
943                                          mozilla::Modifiers aModifiers,
944                                          int16_t aButton) {
945   // Because the command event is firing asynchronously, a flag is needed to
946   // indicate whether user input is being handled. This ensures that a popup
947   // window won't get blocked.
948   bool userinput = dom::UserActivation::IsHandlingUserInput();
949 
950   // Flip "checked" state if we're a checkbox menu, or an un-checked radio menu.
951   bool needToFlipChecked = false;
952   if (mType == eMenuType_Checkbox || (mType == eMenuType_Radio && !mChecked)) {
953     needToFlipChecked = !mContent->AsElement()->AttrValueIs(
954         kNameSpaceID_None, nsGkAtoms::autocheck, nsGkAtoms::_false,
955         eCaseMatters);
956   }
957 
958   mDelayedMenuCommandEvent =
959       new nsXULMenuCommandEvent(mContent->AsElement(), aIsTrusted, aModifiers,
960                                 userinput, needToFlipChecked, aButton);
961 }
962 
PassMenuCommandEventToPopupManager()963 void nsMenuFrame::PassMenuCommandEventToPopupManager() {
964   nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
965   nsMenuParent* menuParent = GetMenuParent();
966   if (pm && menuParent && mDelayedMenuCommandEvent) {
967     nsCOMPtr<nsIContent> content = mContent;
968     RefPtr<nsXULMenuCommandEvent> event = mDelayedMenuCommandEvent;
969     pm->ExecuteMenu(content, event);
970   }
971   mDelayedMenuCommandEvent = nullptr;
972 }
973 
RemoveFrame(ChildListID aListID,nsIFrame * aOldFrame)974 void nsMenuFrame::RemoveFrame(ChildListID aListID, nsIFrame* aOldFrame) {
975   nsFrameList* popupList = GetPopupList();
976   if (popupList && popupList->FirstChild() == aOldFrame) {
977     popupList->RemoveFirstChild();
978     aOldFrame->Destroy();
979     DestroyPopupList();
980     PresShell()->FrameNeedsReflow(this, IntrinsicDirty::TreeChange,
981                                   NS_FRAME_HAS_DIRTY_CHILDREN);
982     return;
983   }
984   nsBoxFrame::RemoveFrame(aListID, aOldFrame);
985 }
986 
InsertFrames(ChildListID aListID,nsIFrame * aPrevFrame,const nsLineList::iterator * aPrevFrameLine,nsFrameList & aFrameList)987 void nsMenuFrame::InsertFrames(ChildListID aListID, nsIFrame* aPrevFrame,
988                                const nsLineList::iterator* aPrevFrameLine,
989                                nsFrameList& aFrameList) {
990   if (!HasPopup() && (aListID == kPrincipalList || aListID == kPopupList)) {
991     SetPopupFrame(aFrameList);
992     if (HasPopup()) {
993       PresShell()->FrameNeedsReflow(this, IntrinsicDirty::TreeChange,
994                                     NS_FRAME_HAS_DIRTY_CHILDREN);
995     }
996   }
997 
998   if (aFrameList.IsEmpty()) return;
999 
1000   if (MOZ_UNLIKELY(aPrevFrame && aPrevFrame == GetPopup())) {
1001     aPrevFrame = nullptr;
1002   }
1003 
1004   nsBoxFrame::InsertFrames(aListID, aPrevFrame, aPrevFrameLine, aFrameList);
1005 }
1006 
AppendFrames(ChildListID aListID,nsFrameList & aFrameList)1007 void nsMenuFrame::AppendFrames(ChildListID aListID, nsFrameList& aFrameList) {
1008   if (!HasPopup() && (aListID == kPrincipalList || aListID == kPopupList)) {
1009     SetPopupFrame(aFrameList);
1010     if (HasPopup()) {
1011       PresShell()->FrameNeedsReflow(this, IntrinsicDirty::TreeChange,
1012                                     NS_FRAME_HAS_DIRTY_CHILDREN);
1013     }
1014   }
1015 
1016   if (aFrameList.IsEmpty()) return;
1017 
1018   nsBoxFrame::AppendFrames(aListID, aFrameList);
1019 }
1020 
SizeToPopup(nsBoxLayoutState & aState,nsSize & aSize)1021 bool nsMenuFrame::SizeToPopup(nsBoxLayoutState& aState, nsSize& aSize) {
1022   if (!IsXULCollapsed()) {
1023     bool widthSet, heightSet;
1024     nsSize tmpSize(-1, 0);
1025     nsIFrame::AddXULPrefSize(this, tmpSize, widthSet, heightSet);
1026     if (!widthSet && GetXULFlex() == 0) {
1027       nsMenuPopupFrame* popupFrame = GetPopup();
1028       if (!popupFrame) return false;
1029       tmpSize = popupFrame->GetXULPrefSize(aState);
1030 
1031       // Produce a size such that:
1032       //  (1) the menu and its popup can be the same width
1033       //  (2) there's enough room in the menu for the content and its
1034       //      border-padding
1035       //  (3) there's enough room in the popup for the content and its
1036       //      scrollbar
1037       nsMargin borderPadding;
1038       GetXULBorderAndPadding(borderPadding);
1039 
1040       // if there is a scroll frame, add the desired width of the scrollbar as
1041       // well
1042       nsIScrollableFrame* scrollFrame = popupFrame->GetScrollFrame(popupFrame);
1043       nscoord scrollbarWidth = 0;
1044       if (scrollFrame) {
1045         scrollbarWidth =
1046             scrollFrame->GetDesiredScrollbarSizes(&aState).LeftRight();
1047       }
1048 
1049       aSize.width =
1050           tmpSize.width + std::max(borderPadding.LeftRight(), scrollbarWidth);
1051 
1052       return true;
1053     }
1054   }
1055 
1056   return false;
1057 }
1058 
GetXULPrefSize(nsBoxLayoutState & aState)1059 nsSize nsMenuFrame::GetXULPrefSize(nsBoxLayoutState& aState) {
1060   nsSize size = nsBoxFrame::GetXULPrefSize(aState);
1061   DISPLAY_PREF_SIZE(this, size);
1062 
1063   // If we are using sizetopopup="always" then
1064   // nsBoxFrame will already have enforced the minimum size
1065   if (!IsSizedToPopup(mContent, true) && IsSizedToPopup(mContent, false) &&
1066       SizeToPopup(aState, size)) {
1067     // We now need to ensure that size is within the min - max range.
1068     nsSize minSize = nsBoxFrame::GetXULMinSize(aState);
1069     nsSize maxSize = GetXULMaxSize(aState);
1070     size = XULBoundsCheck(minSize, size, maxSize);
1071   }
1072 
1073   return size;
1074 }
1075 
1076 NS_IMETHODIMP
GetActiveChild(dom::Element ** aResult)1077 nsMenuFrame::GetActiveChild(dom::Element** aResult) {
1078   nsMenuPopupFrame* popupFrame = GetPopup();
1079   if (!popupFrame) return NS_ERROR_FAILURE;
1080 
1081   nsMenuFrame* menuFrame = popupFrame->GetCurrentMenuItem();
1082   if (!menuFrame) {
1083     *aResult = nullptr;
1084   } else {
1085     RefPtr<dom::Element> elt = menuFrame->GetContent()->AsElement();
1086     elt.forget(aResult);
1087   }
1088 
1089   return NS_OK;
1090 }
1091 
1092 NS_IMETHODIMP
SetActiveChild(dom::Element * aChild)1093 nsMenuFrame::SetActiveChild(dom::Element* aChild) {
1094   nsMenuPopupFrame* popupFrame = GetPopup();
1095   if (!popupFrame) return NS_ERROR_FAILURE;
1096 
1097   // Force the child frames within the popup to be generated.
1098   AutoWeakFrame weakFrame(popupFrame);
1099   popupFrame->GenerateFrames();
1100   if (!weakFrame.IsAlive()) {
1101     return NS_OK;
1102   }
1103 
1104   if (!aChild) {
1105     // Remove the current selection
1106     popupFrame->ChangeMenuItem(nullptr, false, false);
1107     return NS_OK;
1108   }
1109 
1110   nsMenuFrame* menu = do_QueryFrame(aChild->GetPrimaryFrame());
1111   if (menu) popupFrame->ChangeMenuItem(menu, false, false);
1112   return NS_OK;
1113 }
1114 
GetScrollTargetFrame() const1115 nsIScrollableFrame* nsMenuFrame::GetScrollTargetFrame() const {
1116   nsMenuPopupFrame* popupFrame = GetPopup();
1117   if (!popupFrame) return nullptr;
1118   nsIFrame* childFrame = popupFrame->PrincipalChildList().FirstChild();
1119   if (childFrame) return popupFrame->GetScrollFrame(childFrame);
1120   return nullptr;
1121 }
1122 
1123 // nsMenuTimerMediator implementation.
NS_IMPL_ISUPPORTS(nsMenuTimerMediator,nsITimerCallback)1124 NS_IMPL_ISUPPORTS(nsMenuTimerMediator, nsITimerCallback)
1125 
1126 /**
1127  * Constructs a wrapper around an nsMenuFrame.
1128  * @param aFrame nsMenuFrame to create a wrapper around.
1129  */
1130 nsMenuTimerMediator::nsMenuTimerMediator(nsMenuFrame* aFrame) : mFrame(aFrame) {
1131   NS_ASSERTION(mFrame, "Must have frame");
1132 }
1133 
1134 nsMenuTimerMediator::~nsMenuTimerMediator() = default;
1135 
1136 /**
1137  * Delegates the notification to the contained frame if it has not been
1138  * destroyed.
1139  * @param aTimer Timer which initiated the callback.
1140  * @return NS_ERROR_FAILURE if the frame has been destroyed.
1141  */
Notify(nsITimer * aTimer)1142 NS_IMETHODIMP nsMenuTimerMediator::Notify(nsITimer* aTimer) {
1143   if (!mFrame) return NS_ERROR_FAILURE;
1144 
1145   return mFrame->Notify(aTimer);
1146 }
1147 
1148 /**
1149  * Clear the pointer to the contained nsMenuFrame. This should be called
1150  * when the contained nsMenuFrame is destroyed.
1151  */
ClearFrame()1152 void nsMenuTimerMediator::ClearFrame() { mFrame = nullptr; }
1153 
1154 /**
1155  * Get the name of this timer callback.
1156  * @param aName the name to return
1157  */
1158 NS_IMETHODIMP
GetName(nsACString & aName)1159 nsMenuTimerMediator::GetName(nsACString& aName) {
1160   aName.AssignLiteral("nsMenuTimerMediator");
1161   return NS_OK;
1162 }
1163