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