Wed, 31 Dec 2014 06:55:50 +0100
Added tag UPSTREAM_283F7C6 for changeset ca08bd8f51b2
1 /* -*- Mode: C++; tab-width: 8; 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/. */
6 #include "nsGkAtoms.h"
7 #include "nsHTMLParts.h"
8 #include "nsMenuFrame.h"
9 #include "nsBoxFrame.h"
10 #include "nsIContent.h"
11 #include "nsIAtom.h"
12 #include "nsPresContext.h"
13 #include "nsIPresShell.h"
14 #include "nsStyleContext.h"
15 #include "nsCSSRendering.h"
16 #include "nsNameSpaceManager.h"
17 #include "nsMenuPopupFrame.h"
18 #include "nsMenuBarFrame.h"
19 #include "nsIDocument.h"
20 #include "nsIDOMElement.h"
21 #include "nsIComponentManager.h"
22 #include "nsBoxLayoutState.h"
23 #include "nsIScrollableFrame.h"
24 #include "nsBindingManager.h"
25 #include "nsIServiceManager.h"
26 #include "nsCSSFrameConstructor.h"
27 #include "nsIDOMKeyEvent.h"
28 #include "nsXPIDLString.h"
29 #include "nsReadableUtils.h"
30 #include "nsUnicharUtils.h"
31 #include "nsIStringBundle.h"
32 #include "nsContentUtils.h"
33 #include "nsDisplayList.h"
34 #include "nsIReflowCallback.h"
35 #include "nsISound.h"
36 #include "nsIDOMXULMenuListElement.h"
37 #include "mozilla/Attributes.h"
38 #include "mozilla/EventDispatcher.h"
39 #include "mozilla/EventStateManager.h"
40 #include "mozilla/Likely.h"
41 #include "mozilla/LookAndFeel.h"
42 #include "mozilla/MouseEvents.h"
43 #include "mozilla/Preferences.h"
44 #include "mozilla/Services.h"
45 #include "mozilla/TextEvents.h"
46 #include "mozilla/dom/Element.h"
47 #include <algorithm>
49 using namespace mozilla;
51 #define NS_MENU_POPUP_LIST_INDEX 0
53 #if defined(XP_WIN)
54 #define NSCONTEXTMENUISMOUSEUP 1
55 #endif
57 static void
58 AssertNotCalled(void* aPropertyValue)
59 {
60 NS_ERROR("popup list should never be destroyed by the FramePropertyTable");
61 }
62 NS_DECLARE_FRAME_PROPERTY(PopupListProperty, AssertNotCalled)
64 static int32_t gEatMouseMove = false;
66 const int32_t kBlinkDelay = 67; // milliseconds
68 // this class is used for dispatching menu activation events asynchronously.
69 class nsMenuActivateEvent : public nsRunnable
70 {
71 public:
72 nsMenuActivateEvent(nsIContent *aMenu,
73 nsPresContext* aPresContext,
74 bool aIsActivate)
75 : mMenu(aMenu), mPresContext(aPresContext), mIsActivate(aIsActivate)
76 {
77 }
79 NS_IMETHOD Run() MOZ_OVERRIDE
80 {
81 nsAutoString domEventToFire;
83 if (mIsActivate) {
84 // Highlight the menu.
85 mMenu->SetAttr(kNameSpaceID_None, nsGkAtoms::menuactive,
86 NS_LITERAL_STRING("true"), true);
87 // The menuactivated event is used by accessibility to track the user's
88 // movements through menus
89 domEventToFire.AssignLiteral("DOMMenuItemActive");
90 }
91 else {
92 // Unhighlight the menu.
93 mMenu->UnsetAttr(kNameSpaceID_None, nsGkAtoms::menuactive, true);
94 domEventToFire.AssignLiteral("DOMMenuItemInactive");
95 }
97 nsCOMPtr<nsIDOMEvent> event;
98 if (NS_SUCCEEDED(EventDispatcher::CreateEvent(mMenu, mPresContext, nullptr,
99 NS_LITERAL_STRING("Events"),
100 getter_AddRefs(event)))) {
101 event->InitEvent(domEventToFire, true, true);
103 event->SetTrusted(true);
105 EventDispatcher::DispatchDOMEvent(mMenu, nullptr, event,
106 mPresContext, nullptr);
107 }
109 return NS_OK;
110 }
112 private:
113 nsCOMPtr<nsIContent> mMenu;
114 nsRefPtr<nsPresContext> mPresContext;
115 bool mIsActivate;
116 };
118 class nsMenuAttributeChangedEvent : public nsRunnable
119 {
120 public:
121 nsMenuAttributeChangedEvent(nsIFrame* aFrame, nsIAtom* aAttr)
122 : mFrame(aFrame), mAttr(aAttr)
123 {
124 }
126 NS_IMETHOD Run() MOZ_OVERRIDE
127 {
128 nsMenuFrame* frame = static_cast<nsMenuFrame*>(mFrame.GetFrame());
129 NS_ENSURE_STATE(frame);
130 if (mAttr == nsGkAtoms::checked) {
131 frame->UpdateMenuSpecialState(frame->PresContext());
132 } else if (mAttr == nsGkAtoms::acceltext) {
133 // someone reset the accelText attribute,
134 // so clear the bit that says *we* set it
135 frame->RemoveStateBits(NS_STATE_ACCELTEXT_IS_DERIVED);
136 frame->BuildAcceleratorText(true);
137 }
138 else if (mAttr == nsGkAtoms::key) {
139 frame->BuildAcceleratorText(true);
140 } else if (mAttr == nsGkAtoms::type || mAttr == nsGkAtoms::name) {
141 frame->UpdateMenuType(frame->PresContext());
142 }
143 return NS_OK;
144 }
145 protected:
146 nsWeakFrame mFrame;
147 nsCOMPtr<nsIAtom> mAttr;
148 };
150 //
151 // NS_NewMenuFrame and NS_NewMenuItemFrame
152 //
153 // Wrappers for creating a new menu popup container
154 //
155 nsIFrame*
156 NS_NewMenuFrame(nsIPresShell* aPresShell, nsStyleContext* aContext)
157 {
158 nsMenuFrame* it = new (aPresShell) nsMenuFrame (aPresShell, aContext);
159 it->SetIsMenu(true);
160 return it;
161 }
163 nsIFrame*
164 NS_NewMenuItemFrame(nsIPresShell* aPresShell, nsStyleContext* aContext)
165 {
166 nsMenuFrame* it = new (aPresShell) nsMenuFrame (aPresShell, aContext);
167 it->SetIsMenu(false);
168 return it;
169 }
171 NS_IMPL_FRAMEARENA_HELPERS(nsMenuFrame)
173 NS_QUERYFRAME_HEAD(nsMenuFrame)
174 NS_QUERYFRAME_ENTRY(nsMenuFrame)
175 NS_QUERYFRAME_TAIL_INHERITING(nsBoxFrame)
177 nsMenuFrame::nsMenuFrame(nsIPresShell* aShell, nsStyleContext* aContext):
178 nsBoxFrame(aShell, aContext),
179 mIsMenu(false),
180 mChecked(false),
181 mIgnoreAccelTextChange(false),
182 mType(eMenuType_Normal),
183 mMenuParent(nullptr),
184 mBlinkState(0)
185 {
186 }
188 void
189 nsMenuFrame::SetParent(nsIFrame* aParent)
190 {
191 nsBoxFrame::SetParent(aParent);
192 InitMenuParent(aParent);
193 }
195 void
196 nsMenuFrame::InitMenuParent(nsIFrame* aParent)
197 {
198 while (aParent) {
199 nsMenuPopupFrame* popup = do_QueryFrame(aParent);
200 if (popup) {
201 mMenuParent = popup;
202 break;
203 }
205 nsMenuBarFrame* menubar = do_QueryFrame(aParent);
206 if (menubar) {
207 mMenuParent = menubar;
208 break;
209 }
211 aParent = aParent->GetParent();
212 }
213 }
215 class nsASyncMenuInitialization MOZ_FINAL : public nsIReflowCallback
216 {
217 public:
218 nsASyncMenuInitialization(nsIFrame* aFrame)
219 : mWeakFrame(aFrame)
220 {
221 }
223 virtual bool ReflowFinished() MOZ_OVERRIDE
224 {
225 bool shouldFlush = false;
226 nsMenuFrame* menu = do_QueryFrame(mWeakFrame.GetFrame());
227 if (menu) {
228 menu->UpdateMenuType(menu->PresContext());
229 shouldFlush = true;
230 }
231 delete this;
232 return shouldFlush;
233 }
235 virtual void ReflowCallbackCanceled() MOZ_OVERRIDE
236 {
237 delete this;
238 }
240 nsWeakFrame mWeakFrame;
241 };
243 void
244 nsMenuFrame::Init(nsIContent* aContent,
245 nsIFrame* aParent,
246 nsIFrame* aPrevInFlow)
247 {
248 nsBoxFrame::Init(aContent, aParent, aPrevInFlow);
250 // Set up a mediator which can be used for callbacks on this frame.
251 mTimerMediator = new nsMenuTimerMediator(this);
253 InitMenuParent(aParent);
255 BuildAcceleratorText(false);
256 nsIReflowCallback* cb = new nsASyncMenuInitialization(this);
257 PresContext()->PresShell()->PostReflowCallback(cb);
258 }
260 const nsFrameList&
261 nsMenuFrame::GetChildList(ChildListID aListID) const
262 {
263 if (kPopupList == aListID) {
264 nsFrameList* list = GetPopupList();
265 return list ? *list : nsFrameList::EmptyList();
266 }
267 return nsBoxFrame::GetChildList(aListID);
268 }
270 void
271 nsMenuFrame::GetChildLists(nsTArray<ChildList>* aLists) const
272 {
273 nsBoxFrame::GetChildLists(aLists);
274 nsFrameList* list = GetPopupList();
275 if (list) {
276 list->AppendIfNonempty(aLists, kPopupList);
277 }
278 }
280 nsMenuPopupFrame*
281 nsMenuFrame::GetPopup()
282 {
283 nsFrameList* popupList = GetPopupList();
284 return popupList ? static_cast<nsMenuPopupFrame*>(popupList->FirstChild()) :
285 nullptr;
286 }
288 nsFrameList*
289 nsMenuFrame::GetPopupList() const
290 {
291 if (!HasPopup()) {
292 return nullptr;
293 }
294 nsFrameList* prop =
295 static_cast<nsFrameList*>(Properties().Get(PopupListProperty()));
296 NS_ASSERTION(prop && prop->GetLength() == 1 &&
297 prop->FirstChild()->GetType() == nsGkAtoms::menuPopupFrame,
298 "popup list should have exactly one nsMenuPopupFrame");
299 return prop;
300 }
302 void
303 nsMenuFrame::DestroyPopupList()
304 {
305 NS_ASSERTION(HasPopup(), "huh?");
306 nsFrameList* prop =
307 static_cast<nsFrameList*>(Properties().Remove(PopupListProperty()));
308 NS_ASSERTION(prop && prop->IsEmpty(),
309 "popup list must exist and be empty when destroying");
310 RemoveStateBits(NS_STATE_MENU_HAS_POPUP_LIST);
311 prop->Delete(PresContext()->PresShell());
312 }
314 void
315 nsMenuFrame::SetPopupFrame(nsFrameList& aFrameList)
316 {
317 for (nsFrameList::Enumerator e(aFrameList); !e.AtEnd(); e.Next()) {
318 nsMenuPopupFrame* popupFrame = do_QueryFrame(e.get());
319 if (popupFrame) {
320 // Remove the frame from the list and store it in a nsFrameList* property.
321 aFrameList.RemoveFrame(popupFrame);
322 nsFrameList* popupList = new (PresContext()->PresShell()) nsFrameList(popupFrame, popupFrame);
323 Properties().Set(PopupListProperty(), popupList);
324 AddStateBits(NS_STATE_MENU_HAS_POPUP_LIST);
325 break;
326 }
327 }
328 }
330 nsresult
331 nsMenuFrame::SetInitialChildList(ChildListID aListID,
332 nsFrameList& aChildList)
333 {
334 NS_ASSERTION(!HasPopup(), "SetInitialChildList called twice?");
335 if (aListID == kPrincipalList || aListID == kPopupList) {
336 SetPopupFrame(aChildList);
337 }
338 return nsBoxFrame::SetInitialChildList(aListID, aChildList);
339 }
341 void
342 nsMenuFrame::DestroyFrom(nsIFrame* aDestructRoot)
343 {
344 // Kill our timer if one is active. This is not strictly necessary as
345 // the pointer to this frame will be cleared from the mediator, but
346 // this is done for added safety.
347 if (mOpenTimer) {
348 mOpenTimer->Cancel();
349 }
351 StopBlinking();
353 // Null out the pointer to this frame in the mediator wrapper so that it
354 // doesn't try to interact with a deallocated frame.
355 mTimerMediator->ClearFrame();
357 // if the menu content is just being hidden, it may be made visible again
358 // later, so make sure to clear the highlighting.
359 mContent->UnsetAttr(kNameSpaceID_None, nsGkAtoms::menuactive, false);
361 // are we our menu parent's current menu item?
362 if (mMenuParent && mMenuParent->GetCurrentMenuItem() == this) {
363 // yes; tell it that we're going away
364 mMenuParent->CurrentMenuIsBeingDestroyed();
365 }
367 nsFrameList* popupList = GetPopupList();
368 if (popupList) {
369 popupList->DestroyFramesFrom(aDestructRoot);
370 DestroyPopupList();
371 }
373 nsBoxFrame::DestroyFrom(aDestructRoot);
374 }
376 void
377 nsMenuFrame::BuildDisplayListForChildren(nsDisplayListBuilder* aBuilder,
378 const nsRect& aDirtyRect,
379 const nsDisplayListSet& aLists)
380 {
381 if (!aBuilder->IsForEventDelivery()) {
382 nsBoxFrame::BuildDisplayListForChildren(aBuilder, aDirtyRect, aLists);
383 return;
384 }
386 nsDisplayListCollection set;
387 nsBoxFrame::BuildDisplayListForChildren(aBuilder, aDirtyRect, set);
389 WrapListsInRedirector(aBuilder, set, aLists);
390 }
392 nsresult
393 nsMenuFrame::HandleEvent(nsPresContext* aPresContext,
394 WidgetGUIEvent* aEvent,
395 nsEventStatus* aEventStatus)
396 {
397 NS_ENSURE_ARG_POINTER(aEventStatus);
398 if (nsEventStatus_eConsumeNoDefault == *aEventStatus ||
399 (mMenuParent && mMenuParent->IsMenuLocked())) {
400 return NS_OK;
401 }
403 nsWeakFrame weakFrame(this);
404 if (*aEventStatus == nsEventStatus_eIgnore)
405 *aEventStatus = nsEventStatus_eConsumeDoDefault;
407 bool onmenu = IsOnMenu();
409 if (aEvent->message == NS_KEY_PRESS && !IsDisabled()) {
410 WidgetKeyboardEvent* keyEvent = aEvent->AsKeyboardEvent();
411 uint32_t keyCode = keyEvent->keyCode;
412 #ifdef XP_MACOSX
413 // On mac, open menulist on either up/down arrow or space (w/o Cmd pressed)
414 if (!IsOpen() && ((keyEvent->charCode == NS_VK_SPACE && !keyEvent->IsMeta()) ||
415 (keyCode == NS_VK_UP || keyCode == NS_VK_DOWN))) {
416 *aEventStatus = nsEventStatus_eConsumeNoDefault;
417 OpenMenu(false);
418 }
419 #else
420 // On other platforms, toggle menulist on unmodified F4 or Alt arrow
421 if ((keyCode == NS_VK_F4 && !keyEvent->IsAlt()) ||
422 ((keyCode == NS_VK_UP || keyCode == NS_VK_DOWN) && keyEvent->IsAlt())) {
423 *aEventStatus = nsEventStatus_eConsumeNoDefault;
424 ToggleMenuState();
425 }
426 #endif
427 }
428 else if (aEvent->message == NS_MOUSE_BUTTON_DOWN &&
429 aEvent->AsMouseEvent()->button == WidgetMouseEvent::eLeftButton &&
430 !IsDisabled() && IsMenu()) {
431 // The menu item was selected. Bring up the menu.
432 // We have children.
433 // Don't prevent the default action here, since that will also cancel
434 // potential drag starts.
435 if (!mMenuParent || mMenuParent->IsMenuBar()) {
436 ToggleMenuState();
437 }
438 else {
439 if (!IsOpen()) {
440 OpenMenu(false);
441 }
442 }
443 }
444 else if (
445 #ifndef NSCONTEXTMENUISMOUSEUP
446 (aEvent->message == NS_MOUSE_BUTTON_UP &&
447 aEvent->AsMouseEvent()->button == WidgetMouseEvent::eRightButton) &&
448 #else
449 aEvent->message == NS_CONTEXTMENU &&
450 #endif
451 onmenu && !IsMenu() && !IsDisabled()) {
452 // if this menu is a context menu it accepts right-clicks...fire away!
453 // Make sure we cancel default processing of the context menu event so
454 // that it doesn't bubble and get seen again by the popuplistener and show
455 // another context menu.
456 //
457 // Furthermore (there's always more, isn't there?), on some platforms (win32
458 // being one of them) we get the context menu event on a mouse up while
459 // on others we get it on a mouse down. For the ones where we get it on a
460 // mouse down, we must continue listening for the right button up event to
461 // dismiss the menu.
462 if (mMenuParent->IsContextMenu()) {
463 *aEventStatus = nsEventStatus_eConsumeNoDefault;
464 Execute(aEvent);
465 }
466 }
467 else if (aEvent->message == NS_MOUSE_BUTTON_UP &&
468 aEvent->AsMouseEvent()->button == WidgetMouseEvent::eLeftButton &&
469 !IsMenu() && !IsDisabled()) {
470 // Execute the execute event handler.
471 *aEventStatus = nsEventStatus_eConsumeNoDefault;
472 Execute(aEvent);
473 }
474 else if (aEvent->message == NS_MOUSE_EXIT_SYNTH) {
475 // Kill our timer if one is active.
476 if (mOpenTimer) {
477 mOpenTimer->Cancel();
478 mOpenTimer = nullptr;
479 }
481 // Deactivate the menu.
482 if (mMenuParent) {
483 bool onmenubar = mMenuParent->IsMenuBar();
484 if (!(onmenubar && mMenuParent->IsActive())) {
485 if (IsMenu() && !onmenubar && IsOpen()) {
486 // Submenus don't get closed up immediately.
487 }
488 else if (this == mMenuParent->GetCurrentMenuItem()) {
489 mMenuParent->ChangeMenuItem(nullptr, false);
490 }
491 }
492 }
493 }
494 else if (aEvent->message == NS_MOUSE_MOVE &&
495 (onmenu || (mMenuParent && mMenuParent->IsMenuBar()))) {
496 if (gEatMouseMove) {
497 gEatMouseMove = false;
498 return NS_OK;
499 }
501 // Let the menu parent know we're the new item.
502 mMenuParent->ChangeMenuItem(this, false);
503 NS_ENSURE_TRUE(weakFrame.IsAlive(), NS_OK);
504 NS_ENSURE_TRUE(mMenuParent, NS_OK);
506 // we need to check if we really became the current menu
507 // item or not
508 nsMenuFrame *realCurrentItem = mMenuParent->GetCurrentMenuItem();
509 if (realCurrentItem != this) {
510 // we didn't (presumably because a context menu was active)
511 return NS_OK;
512 }
514 // Hovering over a menu in a popup should open it without a need for a click.
515 // A timer is used so that it doesn't open if the user moves the mouse quickly
516 // past the menu. This conditional check ensures that only menus have this
517 // behaviour
518 if (!IsDisabled() && IsMenu() && !IsOpen() && !mOpenTimer && !mMenuParent->IsMenuBar()) {
519 int32_t menuDelay =
520 LookAndFeel::GetInt(LookAndFeel::eIntID_SubmenuDelay, 300); // ms
522 // We're a menu, we're built, we're closed, and no timer has been kicked off.
523 mOpenTimer = do_CreateInstance("@mozilla.org/timer;1");
524 mOpenTimer->InitWithCallback(mTimerMediator, menuDelay, nsITimer::TYPE_ONE_SHOT);
525 }
526 }
528 return NS_OK;
529 }
531 void
532 nsMenuFrame::ToggleMenuState()
533 {
534 if (IsOpen())
535 CloseMenu(false);
536 else
537 OpenMenu(false);
538 }
540 void
541 nsMenuFrame::PopupOpened()
542 {
543 nsWeakFrame weakFrame(this);
544 mContent->SetAttr(kNameSpaceID_None, nsGkAtoms::open,
545 NS_LITERAL_STRING("true"), true);
546 if (!weakFrame.IsAlive())
547 return;
549 if (mMenuParent) {
550 mMenuParent->SetActive(true);
551 // Make sure the current menu which is being toggled on
552 // the menubar is highlighted
553 mMenuParent->SetCurrentMenuItem(this);
554 }
555 }
557 void
558 nsMenuFrame::PopupClosed(bool aDeselectMenu)
559 {
560 nsWeakFrame weakFrame(this);
561 nsContentUtils::AddScriptRunner(
562 new nsUnsetAttrRunnable(mContent, nsGkAtoms::open));
563 if (!weakFrame.IsAlive())
564 return;
566 // if the popup is for a menu on a menubar, inform menubar to deactivate
567 if (mMenuParent && mMenuParent->MenuClosed()) {
568 if (aDeselectMenu) {
569 SelectMenu(false);
570 } else {
571 // We are not deselecting the parent menu while closing the popup, so send
572 // a DOMMenuItemActive event to the menu to indicate that the menu is
573 // becoming active again.
574 nsMenuFrame *current = mMenuParent->GetCurrentMenuItem();
575 if (current) {
576 // However, if the menu is a descendant on a menubar, and the menubar
577 // has the 'stay active' flag set, it means that the menubar is switching
578 // to another toplevel menu entirely (for example from Edit to View), so
579 // don't fire the DOMMenuItemActive event or else we'll send extraneous
580 // events for submenus. nsMenuBarFrame::ChangeMenuItem has already deselected
581 // the old menu, so it doesn't need to happen again here, and the new
582 // menu can be selected right away.
583 nsIFrame* parent = current;
584 while (parent) {
585 nsMenuBarFrame* menubar = do_QueryFrame(parent);
586 if (menubar && menubar->GetStayActive())
587 return;
589 parent = parent->GetParent();
590 }
592 nsCOMPtr<nsIRunnable> event =
593 new nsMenuActivateEvent(current->GetContent(),
594 PresContext(), true);
595 NS_DispatchToCurrentThread(event);
596 }
597 }
598 }
599 }
601 NS_IMETHODIMP
602 nsMenuFrame::SelectMenu(bool aActivateFlag)
603 {
604 if (mContent) {
605 // When a menu opens a submenu, the mouse will often be moved onto a
606 // sibling before moving onto an item within the submenu, causing the
607 // parent to become deselected. We need to ensure that the parent menu
608 // is reselected when an item in the submenu is selected, so navigate up
609 // from the item to its popup, and then to the popup above that.
610 if (aActivateFlag) {
611 nsIFrame* parent = GetParent();
612 while (parent) {
613 nsMenuPopupFrame* menupopup = do_QueryFrame(parent);
614 if (menupopup) {
615 // a menu is always the direct parent of a menupopup
616 nsMenuFrame* menu = do_QueryFrame(menupopup->GetParent());
617 if (menu) {
618 // a popup however is not necessarily the direct parent of a menu
619 nsIFrame* popupParent = menu->GetParent();
620 while (popupParent) {
621 menupopup = do_QueryFrame(popupParent);
622 if (menupopup) {
623 menupopup->SetCurrentMenuItem(menu);
624 break;
625 }
626 popupParent = popupParent->GetParent();
627 }
628 }
629 break;
630 }
631 parent = parent->GetParent();
632 }
633 }
635 // cancel the close timer if selecting a menu within the popup to be closed
636 nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
637 if (pm)
638 pm->CancelMenuTimer(mMenuParent);
640 nsCOMPtr<nsIRunnable> event =
641 new nsMenuActivateEvent(mContent, PresContext(), aActivateFlag);
642 NS_DispatchToCurrentThread(event);
643 }
645 return NS_OK;
646 }
648 nsresult
649 nsMenuFrame::AttributeChanged(int32_t aNameSpaceID,
650 nsIAtom* aAttribute,
651 int32_t aModType)
652 {
653 if (aAttribute == nsGkAtoms::acceltext && mIgnoreAccelTextChange) {
654 // Reset the flag so that only one change is ignored.
655 mIgnoreAccelTextChange = false;
656 return NS_OK;
657 }
659 if (aAttribute == nsGkAtoms::checked ||
660 aAttribute == nsGkAtoms::acceltext ||
661 aAttribute == nsGkAtoms::key ||
662 aAttribute == nsGkAtoms::type ||
663 aAttribute == nsGkAtoms::name) {
664 nsCOMPtr<nsIRunnable> event =
665 new nsMenuAttributeChangedEvent(this, aAttribute);
666 nsContentUtils::AddScriptRunner(event);
667 }
668 return NS_OK;
669 }
671 nsIContent*
672 nsMenuFrame::GetAnchor()
673 {
674 mozilla::dom::Element* anchor = nullptr;
676 nsAutoString id;
677 mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::anchor, id);
678 if (!id.IsEmpty()) {
679 nsIDocument* doc = mContent->OwnerDoc();
681 anchor =
682 doc->GetAnonymousElementByAttribute(mContent, nsGkAtoms::anonid, id);
683 if (!anchor) {
684 anchor = doc->GetElementById(id);
685 }
686 }
688 // Always return the menu's content if the anchor wasn't set or wasn't found.
689 return anchor && anchor->GetPrimaryFrame() ? anchor : mContent;
690 }
692 void
693 nsMenuFrame::OpenMenu(bool aSelectFirstItem)
694 {
695 if (!mContent)
696 return;
698 gEatMouseMove = true;
700 nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
701 if (pm) {
702 pm->KillMenuTimer();
703 // This opens the menu asynchronously
704 pm->ShowMenu(mContent, aSelectFirstItem, true);
705 }
706 }
708 void
709 nsMenuFrame::CloseMenu(bool aDeselectMenu)
710 {
711 gEatMouseMove = true;
713 // Close the menu asynchronously
714 nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
715 if (pm && HasPopup())
716 pm->HidePopup(GetPopup()->GetContent(), false, aDeselectMenu, true, false);
717 }
719 bool
720 nsMenuFrame::IsSizedToPopup(nsIContent* aContent, bool aRequireAlways)
721 {
722 nsAutoString sizedToPopup;
723 aContent->GetAttr(kNameSpaceID_None, nsGkAtoms::sizetopopup, sizedToPopup);
724 return sizedToPopup.EqualsLiteral("always") ||
725 (!aRequireAlways && sizedToPopup.EqualsLiteral("pref"));
726 }
728 nsSize
729 nsMenuFrame::GetMinSize(nsBoxLayoutState& aBoxLayoutState)
730 {
731 nsSize size = nsBoxFrame::GetMinSize(aBoxLayoutState);
732 DISPLAY_MIN_SIZE(this, size);
734 if (IsSizedToPopup(mContent, true))
735 SizeToPopup(aBoxLayoutState, size);
737 return size;
738 }
740 NS_IMETHODIMP
741 nsMenuFrame::DoLayout(nsBoxLayoutState& aState)
742 {
743 // lay us out
744 nsresult rv = nsBoxFrame::DoLayout(aState);
746 nsMenuPopupFrame* popupFrame = GetPopup();
747 if (popupFrame) {
748 bool sizeToPopup = IsSizedToPopup(mContent, false);
749 popupFrame->LayoutPopup(aState, this, GetAnchor()->GetPrimaryFrame(), sizeToPopup);
750 }
752 return rv;
753 }
755 #ifdef DEBUG_LAYOUT
756 nsresult
757 nsMenuFrame::SetDebug(nsBoxLayoutState& aState, bool aDebug)
758 {
759 // see if our state matches the given debug state
760 bool debugSet = mState & NS_STATE_CURRENTLY_IN_DEBUG;
761 bool debugChanged = (!aDebug && debugSet) || (aDebug && !debugSet);
763 // if it doesn't then tell each child below us the new debug state
764 if (debugChanged)
765 {
766 nsBoxFrame::SetDebug(aState, aDebug);
767 nsMenuPopupFrame* popupFrame = GetPopup();
768 if (popupFrame)
769 SetDebug(aState, popupFrame, aDebug);
770 }
772 return NS_OK;
773 }
775 nsresult
776 nsMenuFrame::SetDebug(nsBoxLayoutState& aState, nsIFrame* aList, bool aDebug)
777 {
778 if (!aList)
779 return NS_OK;
781 while (aList) {
782 if (aList->IsBoxFrame())
783 aList->SetDebug(aState, aDebug);
785 aList = aList->GetNextSibling();
786 }
788 return NS_OK;
789 }
790 #endif
792 //
793 // Enter
794 //
795 // Called when the user hits the <Enter>/<Return> keys or presses the
796 // shortcut key. If this is a leaf item, the item's action will be executed.
797 // In either case, do nothing if the item is disabled.
798 //
799 nsMenuFrame*
800 nsMenuFrame::Enter(WidgetGUIEvent* aEvent)
801 {
802 if (IsDisabled()) {
803 #ifdef XP_WIN
804 // behavior on Windows - close the popup chain
805 if (mMenuParent) {
806 nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
807 if (pm) {
808 nsIFrame* popup = pm->GetTopPopup(ePopupTypeAny);
809 if (popup)
810 pm->HidePopup(popup->GetContent(), true, true, true, false);
811 }
812 }
813 #endif // #ifdef XP_WIN
814 // this menu item was disabled - exit
815 return nullptr;
816 }
818 if (!IsOpen()) {
819 // The enter key press applies to us.
820 if (!IsMenu() && mMenuParent)
821 Execute(aEvent); // Execute our event handler
822 else
823 return this;
824 }
826 return nullptr;
827 }
829 bool
830 nsMenuFrame::IsOpen()
831 {
832 nsMenuPopupFrame* popupFrame = GetPopup();
833 return popupFrame && popupFrame->IsOpen();
834 }
836 bool
837 nsMenuFrame::IsMenu()
838 {
839 return mIsMenu;
840 }
842 nsMenuListType
843 nsMenuFrame::GetParentMenuListType()
844 {
845 if (mMenuParent && mMenuParent->IsMenu()) {
846 nsMenuPopupFrame* popupFrame = static_cast<nsMenuPopupFrame*>(mMenuParent);
847 nsIFrame* parentMenu = popupFrame->GetParent();
848 if (parentMenu) {
849 nsCOMPtr<nsIDOMXULMenuListElement> menulist = do_QueryInterface(parentMenu->GetContent());
850 if (menulist) {
851 bool isEditable = false;
852 menulist->GetEditable(&isEditable);
853 return isEditable ? eEditableMenuList : eReadonlyMenuList;
854 }
855 }
856 }
857 return eNotMenuList;
858 }
860 nsresult
861 nsMenuFrame::Notify(nsITimer* aTimer)
862 {
863 // Our timer has fired.
864 if (aTimer == mOpenTimer.get()) {
865 mOpenTimer = nullptr;
867 if (!IsOpen() && mMenuParent) {
868 // make sure we didn't open a context menu in the meantime
869 // (i.e. the user right-clicked while hovering over a submenu).
870 nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
871 if (pm) {
872 if ((!pm->HasContextMenu(nullptr) || mMenuParent->IsContextMenu()) &&
873 mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::menuactive,
874 nsGkAtoms::_true, eCaseMatters)) {
875 OpenMenu(false);
876 }
877 }
878 }
879 } else if (aTimer == mBlinkTimer) {
880 switch (mBlinkState++) {
881 case 0:
882 NS_ASSERTION(false, "Blink timer fired while not blinking");
883 StopBlinking();
884 break;
885 case 1:
886 {
887 // Turn the highlight back on and wait for a while before closing the menu.
888 nsWeakFrame weakFrame(this);
889 mContent->SetAttr(kNameSpaceID_None, nsGkAtoms::menuactive,
890 NS_LITERAL_STRING("true"), true);
891 if (weakFrame.IsAlive()) {
892 aTimer->InitWithCallback(mTimerMediator, kBlinkDelay, nsITimer::TYPE_ONE_SHOT);
893 }
894 }
895 break;
896 default:
897 if (mMenuParent) {
898 mMenuParent->LockMenuUntilClosed(false);
899 }
900 PassMenuCommandEventToPopupManager();
901 StopBlinking();
902 break;
903 }
904 }
906 return NS_OK;
907 }
909 bool
910 nsMenuFrame::IsDisabled()
911 {
912 return mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::disabled,
913 nsGkAtoms::_true, eCaseMatters);
914 }
916 void
917 nsMenuFrame::UpdateMenuType(nsPresContext* aPresContext)
918 {
919 static nsIContent::AttrValuesArray strings[] =
920 {&nsGkAtoms::checkbox, &nsGkAtoms::radio, nullptr};
921 switch (mContent->FindAttrValueIn(kNameSpaceID_None, nsGkAtoms::type,
922 strings, eCaseMatters)) {
923 case 0: mType = eMenuType_Checkbox; break;
924 case 1:
925 mType = eMenuType_Radio;
926 mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::name, mGroupName);
927 break;
929 default:
930 if (mType != eMenuType_Normal) {
931 nsWeakFrame weakFrame(this);
932 mContent->UnsetAttr(kNameSpaceID_None, nsGkAtoms::checked,
933 true);
934 ENSURE_TRUE(weakFrame.IsAlive());
935 }
936 mType = eMenuType_Normal;
937 break;
938 }
939 UpdateMenuSpecialState(aPresContext);
940 }
942 /* update checked-ness for type="checkbox" and type="radio" */
943 void
944 nsMenuFrame::UpdateMenuSpecialState(nsPresContext* aPresContext)
945 {
946 bool newChecked =
947 mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::checked,
948 nsGkAtoms::_true, eCaseMatters);
949 if (newChecked == mChecked) {
950 /* checked state didn't change */
952 if (mType != eMenuType_Radio)
953 return; // only Radio possibly cares about other kinds of change
955 if (!mChecked || mGroupName.IsEmpty())
956 return; // no interesting change
957 } else {
958 mChecked = newChecked;
959 if (mType != eMenuType_Radio || !mChecked)
960 /*
961 * Unchecking something requires no further changes, and only
962 * menuRadio has to do additional work when checked.
963 */
964 return;
965 }
967 /*
968 * If we get this far, we're type=radio, and:
969 * - our name= changed, or
970 * - we went from checked="false" to checked="true"
971 */
973 /*
974 * Behavioural note:
975 * If we're checked and renamed _into_ an existing radio group, we are
976 * made the new checked item, and we unselect the previous one.
977 *
978 * The only other reasonable behaviour would be to check for another selected
979 * item in that group. If found, unselect ourselves, otherwise we're the
980 * selected item. That, however, would be a lot more work, and I don't think
981 * it's better at all.
982 */
984 /* walk siblings, looking for the other checked item with the same name */
985 // get the first sibling in this menu popup. This frame may be it, and if we're
986 // being called at creation time, this frame isn't yet in the parent's child list.
987 // All I'm saying is that this may fail, but it's most likely alright.
988 nsIFrame* sib = GetParent()->GetFirstPrincipalChild();
990 while (sib) {
991 if (sib != this) {
992 nsMenuFrame* menu = do_QueryFrame(sib);
993 if (menu && menu->GetMenuType() == eMenuType_Radio &&
994 menu->IsChecked() && menu->GetRadioGroupName() == mGroupName) {
995 /* uncheck the old item */
996 sib->GetContent()->UnsetAttr(kNameSpaceID_None, nsGkAtoms::checked,
997 true);
998 /* XXX in DEBUG, check to make sure that there aren't two checked items */
999 return;
1000 }
1001 }
1003 sib = sib->GetNextSibling();
1004 }
1005 }
1007 void
1008 nsMenuFrame::BuildAcceleratorText(bool aNotify)
1009 {
1010 nsAutoString accelText;
1012 if ((GetStateBits() & NS_STATE_ACCELTEXT_IS_DERIVED) == 0) {
1013 mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::acceltext, accelText);
1014 if (!accelText.IsEmpty())
1015 return;
1016 }
1017 // accelText is definitely empty here.
1019 // Now we're going to compute the accelerator text, so remember that we did.
1020 AddStateBits(NS_STATE_ACCELTEXT_IS_DERIVED);
1022 // If anything below fails, just leave the accelerator text blank.
1023 nsWeakFrame weakFrame(this);
1024 mContent->UnsetAttr(kNameSpaceID_None, nsGkAtoms::acceltext, aNotify);
1025 ENSURE_TRUE(weakFrame.IsAlive());
1027 // See if we have a key node and use that instead.
1028 nsAutoString keyValue;
1029 mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::key, keyValue);
1030 if (keyValue.IsEmpty())
1031 return;
1033 // Turn the document into a DOM document so we can use getElementById
1034 nsIDocument *document = mContent->GetDocument();
1035 if (!document)
1036 return;
1038 nsIContent *keyElement = document->GetElementById(keyValue);
1039 if (!keyElement) {
1040 #ifdef DEBUG
1041 nsAutoString label;
1042 mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::label, label);
1043 nsAutoString msg = NS_LITERAL_STRING("Key '") +
1044 keyValue +
1045 NS_LITERAL_STRING("' of menu item '") +
1046 label +
1047 NS_LITERAL_STRING("' could not be found");
1048 NS_WARNING(NS_ConvertUTF16toUTF8(msg).get());
1049 #endif
1050 return;
1051 }
1053 // get the string to display as accelerator text
1054 // check the key element's attributes in this order:
1055 // |keytext|, |key|, |keycode|
1056 nsAutoString accelString;
1057 keyElement->GetAttr(kNameSpaceID_None, nsGkAtoms::keytext, accelString);
1059 if (accelString.IsEmpty()) {
1060 keyElement->GetAttr(kNameSpaceID_None, nsGkAtoms::key, accelString);
1062 if (!accelString.IsEmpty()) {
1063 ToUpperCase(accelString);
1064 } else {
1065 nsAutoString keyCode;
1066 keyElement->GetAttr(kNameSpaceID_None, nsGkAtoms::keycode, keyCode);
1067 ToUpperCase(keyCode);
1069 nsresult rv;
1070 nsCOMPtr<nsIStringBundleService> bundleService =
1071 mozilla::services::GetStringBundleService();
1072 if (bundleService) {
1073 nsCOMPtr<nsIStringBundle> bundle;
1074 rv = bundleService->CreateBundle("chrome://global/locale/keys.properties",
1075 getter_AddRefs(bundle));
1077 if (NS_SUCCEEDED(rv) && bundle) {
1078 nsXPIDLString keyName;
1079 rv = bundle->GetStringFromName(keyCode.get(), getter_Copies(keyName));
1080 if (keyName)
1081 accelString = keyName;
1082 }
1083 }
1085 // nothing usable found, bail
1086 if (accelString.IsEmpty())
1087 return;
1088 }
1089 }
1091 static int32_t accelKey = 0;
1093 if (!accelKey)
1094 {
1095 // Compiled-in defaults, in case we can't get LookAndFeel --
1096 // command for mac, control for all other platforms.
1097 #ifdef XP_MACOSX
1098 accelKey = nsIDOMKeyEvent::DOM_VK_META;
1099 #else
1100 accelKey = nsIDOMKeyEvent::DOM_VK_CONTROL;
1101 #endif
1103 // Get the accelerator key value from prefs, overriding the default:
1104 accelKey = Preferences::GetInt("ui.key.accelKey", accelKey);
1105 }
1107 nsAutoString modifiers;
1108 keyElement->GetAttr(kNameSpaceID_None, nsGkAtoms::modifiers, modifiers);
1110 char* str = ToNewCString(modifiers);
1111 char* newStr;
1112 char* token = nsCRT::strtok(str, ", \t", &newStr);
1114 nsAutoString shiftText;
1115 nsAutoString altText;
1116 nsAutoString metaText;
1117 nsAutoString controlText;
1118 nsAutoString osText;
1119 nsAutoString modifierSeparator;
1121 nsContentUtils::GetShiftText(shiftText);
1122 nsContentUtils::GetAltText(altText);
1123 nsContentUtils::GetMetaText(metaText);
1124 nsContentUtils::GetControlText(controlText);
1125 nsContentUtils::GetOSText(osText);
1126 nsContentUtils::GetModifierSeparatorText(modifierSeparator);
1128 while (token) {
1130 if (PL_strcmp(token, "shift") == 0)
1131 accelText += shiftText;
1132 else if (PL_strcmp(token, "alt") == 0)
1133 accelText += altText;
1134 else if (PL_strcmp(token, "meta") == 0)
1135 accelText += metaText;
1136 else if (PL_strcmp(token, "os") == 0)
1137 accelText += osText;
1138 else if (PL_strcmp(token, "control") == 0)
1139 accelText += controlText;
1140 else if (PL_strcmp(token, "accel") == 0) {
1141 switch (accelKey)
1142 {
1143 case nsIDOMKeyEvent::DOM_VK_META:
1144 accelText += metaText;
1145 break;
1147 case nsIDOMKeyEvent::DOM_VK_WIN:
1148 accelText += osText;
1149 break;
1151 case nsIDOMKeyEvent::DOM_VK_ALT:
1152 accelText += altText;
1153 break;
1155 case nsIDOMKeyEvent::DOM_VK_CONTROL:
1156 default:
1157 accelText += controlText;
1158 break;
1159 }
1160 }
1162 accelText += modifierSeparator;
1164 token = nsCRT::strtok(newStr, ", \t", &newStr);
1165 }
1167 nsMemory::Free(str);
1169 accelText += accelString;
1171 mIgnoreAccelTextChange = true;
1172 mContent->SetAttr(kNameSpaceID_None, nsGkAtoms::acceltext, accelText, aNotify);
1173 ENSURE_TRUE(weakFrame.IsAlive());
1175 mIgnoreAccelTextChange = false;
1176 }
1178 void
1179 nsMenuFrame::Execute(WidgetGUIEvent* aEvent)
1180 {
1181 // flip "checked" state if we're a checkbox menu, or an un-checked radio menu
1182 bool needToFlipChecked = false;
1183 if (mType == eMenuType_Checkbox || (mType == eMenuType_Radio && !mChecked)) {
1184 needToFlipChecked = !mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::autocheck,
1185 nsGkAtoms::_false, eCaseMatters);
1186 }
1188 nsCOMPtr<nsISound> sound(do_CreateInstance("@mozilla.org/sound;1"));
1189 if (sound)
1190 sound->PlayEventSound(nsISound::EVENT_MENU_EXECUTE);
1192 StartBlinking(aEvent, needToFlipChecked);
1193 }
1195 bool
1196 nsMenuFrame::ShouldBlink()
1197 {
1198 int32_t shouldBlink =
1199 LookAndFeel::GetInt(LookAndFeel::eIntID_ChosenMenuItemsShouldBlink, 0);
1200 if (!shouldBlink)
1201 return false;
1203 // Don't blink in editable menulists.
1204 if (GetParentMenuListType() == eEditableMenuList)
1205 return false;
1207 return true;
1208 }
1210 void
1211 nsMenuFrame::StartBlinking(WidgetGUIEvent* aEvent, bool aFlipChecked)
1212 {
1213 StopBlinking();
1214 CreateMenuCommandEvent(aEvent, aFlipChecked);
1216 if (!ShouldBlink()) {
1217 PassMenuCommandEventToPopupManager();
1218 return;
1219 }
1221 // Blink off.
1222 nsWeakFrame weakFrame(this);
1223 mContent->UnsetAttr(kNameSpaceID_None, nsGkAtoms::menuactive, true);
1224 if (!weakFrame.IsAlive())
1225 return;
1227 if (mMenuParent) {
1228 // Make this menu ignore events from now on.
1229 mMenuParent->LockMenuUntilClosed(true);
1230 }
1232 // Set up a timer to blink back on.
1233 mBlinkTimer = do_CreateInstance("@mozilla.org/timer;1");
1234 mBlinkTimer->InitWithCallback(mTimerMediator, kBlinkDelay, nsITimer::TYPE_ONE_SHOT);
1235 mBlinkState = 1;
1236 }
1238 void
1239 nsMenuFrame::StopBlinking()
1240 {
1241 mBlinkState = 0;
1242 if (mBlinkTimer) {
1243 mBlinkTimer->Cancel();
1244 mBlinkTimer = nullptr;
1245 }
1246 mDelayedMenuCommandEvent = nullptr;
1247 }
1249 void
1250 nsMenuFrame::CreateMenuCommandEvent(WidgetGUIEvent* aEvent, bool aFlipChecked)
1251 {
1252 // Create a trusted event if the triggering event was trusted, or if
1253 // we're called from chrome code (since at least one of our caller
1254 // passes in a null event).
1255 bool isTrusted = aEvent ? aEvent->mFlags.mIsTrusted :
1256 nsContentUtils::IsCallerChrome();
1258 bool shift = false, control = false, alt = false, meta = false;
1259 WidgetInputEvent* inputEvent = aEvent ? aEvent->AsInputEvent() : nullptr;
1260 if (inputEvent) {
1261 shift = inputEvent->IsShift();
1262 control = inputEvent->IsControl();
1263 alt = inputEvent->IsAlt();
1264 meta = inputEvent->IsMeta();
1265 }
1267 // Because the command event is firing asynchronously, a flag is needed to
1268 // indicate whether user input is being handled. This ensures that a popup
1269 // window won't get blocked.
1270 bool userinput = EventStateManager::IsHandlingUserInput();
1272 mDelayedMenuCommandEvent =
1273 new nsXULMenuCommandEvent(mContent, isTrusted, shift, control, alt, meta,
1274 userinput, aFlipChecked);
1275 }
1277 void
1278 nsMenuFrame::PassMenuCommandEventToPopupManager()
1279 {
1280 nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
1281 if (pm && mMenuParent && mDelayedMenuCommandEvent) {
1282 pm->ExecuteMenu(mContent, mDelayedMenuCommandEvent);
1283 }
1284 mDelayedMenuCommandEvent = nullptr;
1285 }
1287 nsresult
1288 nsMenuFrame::RemoveFrame(ChildListID aListID,
1289 nsIFrame* aOldFrame)
1290 {
1291 nsFrameList* popupList = GetPopupList();
1292 if (popupList && popupList->FirstChild() == aOldFrame) {
1293 popupList->RemoveFirstChild();
1294 aOldFrame->Destroy();
1295 DestroyPopupList();
1296 PresContext()->PresShell()->
1297 FrameNeedsReflow(this, nsIPresShell::eTreeChange,
1298 NS_FRAME_HAS_DIRTY_CHILDREN);
1299 return NS_OK;
1300 }
1301 return nsBoxFrame::RemoveFrame(aListID, aOldFrame);
1302 }
1304 nsresult
1305 nsMenuFrame::InsertFrames(ChildListID aListID,
1306 nsIFrame* aPrevFrame,
1307 nsFrameList& aFrameList)
1308 {
1309 if (!HasPopup() && (aListID == kPrincipalList || aListID == kPopupList)) {
1310 SetPopupFrame(aFrameList);
1311 if (HasPopup()) {
1312 #ifdef DEBUG_LAYOUT
1313 nsBoxLayoutState state(PresContext());
1314 SetDebug(state, aFrameList, mState & NS_STATE_CURRENTLY_IN_DEBUG);
1315 #endif
1317 PresContext()->PresShell()->
1318 FrameNeedsReflow(this, nsIPresShell::eTreeChange,
1319 NS_FRAME_HAS_DIRTY_CHILDREN);
1320 }
1321 }
1323 if (aFrameList.IsEmpty())
1324 return NS_OK;
1326 if (MOZ_UNLIKELY(aPrevFrame && aPrevFrame == GetPopup())) {
1327 aPrevFrame = nullptr;
1328 }
1330 return nsBoxFrame::InsertFrames(aListID, aPrevFrame, aFrameList);
1331 }
1333 nsresult
1334 nsMenuFrame::AppendFrames(ChildListID aListID,
1335 nsFrameList& aFrameList)
1336 {
1337 if (!HasPopup() && (aListID == kPrincipalList || aListID == kPopupList)) {
1338 SetPopupFrame(aFrameList);
1339 if (HasPopup()) {
1341 #ifdef DEBUG_LAYOUT
1342 nsBoxLayoutState state(PresContext());
1343 SetDebug(state, aFrameList, mState & NS_STATE_CURRENTLY_IN_DEBUG);
1344 #endif
1345 PresContext()->PresShell()->
1346 FrameNeedsReflow(this, nsIPresShell::eTreeChange,
1347 NS_FRAME_HAS_DIRTY_CHILDREN);
1348 }
1349 }
1351 if (aFrameList.IsEmpty())
1352 return NS_OK;
1354 return nsBoxFrame::AppendFrames(aListID, aFrameList);
1355 }
1357 bool
1358 nsMenuFrame::SizeToPopup(nsBoxLayoutState& aState, nsSize& aSize)
1359 {
1360 if (!IsCollapsed()) {
1361 bool widthSet, heightSet;
1362 nsSize tmpSize(-1, 0);
1363 nsIFrame::AddCSSPrefSize(this, tmpSize, widthSet, heightSet);
1364 if (!widthSet && GetFlex(aState) == 0) {
1365 nsMenuPopupFrame* popupFrame = GetPopup();
1366 if (!popupFrame)
1367 return false;
1368 tmpSize = popupFrame->GetPrefSize(aState);
1370 // Produce a size such that:
1371 // (1) the menu and its popup can be the same width
1372 // (2) there's enough room in the menu for the content and its
1373 // border-padding
1374 // (3) there's enough room in the popup for the content and its
1375 // scrollbar
1376 nsMargin borderPadding;
1377 GetBorderAndPadding(borderPadding);
1379 // if there is a scroll frame, add the desired width of the scrollbar as well
1380 nsIScrollableFrame* scrollFrame = do_QueryFrame(popupFrame->GetFirstPrincipalChild());
1381 nscoord scrollbarWidth = 0;
1382 if (scrollFrame) {
1383 scrollbarWidth =
1384 scrollFrame->GetDesiredScrollbarSizes(&aState).LeftRight();
1385 }
1387 aSize.width =
1388 tmpSize.width + std::max(borderPadding.LeftRight(), scrollbarWidth);
1390 return true;
1391 }
1392 }
1394 return false;
1395 }
1397 nsSize
1398 nsMenuFrame::GetPrefSize(nsBoxLayoutState& aState)
1399 {
1400 nsSize size = nsBoxFrame::GetPrefSize(aState);
1401 DISPLAY_PREF_SIZE(this, size);
1403 // If we are using sizetopopup="always" then
1404 // nsBoxFrame will already have enforced the minimum size
1405 if (!IsSizedToPopup(mContent, true) &&
1406 IsSizedToPopup(mContent, false) &&
1407 SizeToPopup(aState, size)) {
1408 // We now need to ensure that size is within the min - max range.
1409 nsSize minSize = nsBoxFrame::GetMinSize(aState);
1410 nsSize maxSize = GetMaxSize(aState);
1411 size = BoundsCheck(minSize, size, maxSize);
1412 }
1414 return size;
1415 }
1417 NS_IMETHODIMP
1418 nsMenuFrame::GetActiveChild(nsIDOMElement** aResult)
1419 {
1420 nsMenuPopupFrame* popupFrame = GetPopup();
1421 if (!popupFrame)
1422 return NS_ERROR_FAILURE;
1424 nsMenuFrame* menuFrame = popupFrame->GetCurrentMenuItem();
1425 if (!menuFrame) {
1426 *aResult = nullptr;
1427 }
1428 else {
1429 nsCOMPtr<nsIDOMElement> elt(do_QueryInterface(menuFrame->GetContent()));
1430 *aResult = elt;
1431 NS_IF_ADDREF(*aResult);
1432 }
1434 return NS_OK;
1435 }
1437 NS_IMETHODIMP
1438 nsMenuFrame::SetActiveChild(nsIDOMElement* aChild)
1439 {
1440 nsMenuPopupFrame* popupFrame = GetPopup();
1441 if (!popupFrame)
1442 return NS_ERROR_FAILURE;
1444 if (!aChild) {
1445 // Remove the current selection
1446 popupFrame->ChangeMenuItem(nullptr, false);
1447 return NS_OK;
1448 }
1450 nsCOMPtr<nsIContent> child(do_QueryInterface(aChild));
1452 nsMenuFrame* menu = do_QueryFrame(child->GetPrimaryFrame());
1453 if (menu)
1454 popupFrame->ChangeMenuItem(menu, false);
1455 return NS_OK;
1456 }
1458 nsIScrollableFrame* nsMenuFrame::GetScrollTargetFrame()
1459 {
1460 nsMenuPopupFrame* popupFrame = GetPopup();
1461 if (!popupFrame)
1462 return nullptr;
1463 nsIFrame* childFrame = popupFrame->GetFirstPrincipalChild();
1464 if (childFrame)
1465 return popupFrame->GetScrollFrame(childFrame);
1466 return nullptr;
1467 }
1469 // nsMenuTimerMediator implementation.
1470 NS_IMPL_ISUPPORTS(nsMenuTimerMediator, nsITimerCallback)
1472 /**
1473 * Constructs a wrapper around an nsMenuFrame.
1474 * @param aFrame nsMenuFrame to create a wrapper around.
1475 */
1476 nsMenuTimerMediator::nsMenuTimerMediator(nsMenuFrame *aFrame) :
1477 mFrame(aFrame)
1478 {
1479 NS_ASSERTION(mFrame, "Must have frame");
1480 }
1482 nsMenuTimerMediator::~nsMenuTimerMediator()
1483 {
1484 }
1486 /**
1487 * Delegates the notification to the contained frame if it has not been destroyed.
1488 * @param aTimer Timer which initiated the callback.
1489 * @return NS_ERROR_FAILURE if the frame has been destroyed.
1490 */
1491 NS_IMETHODIMP nsMenuTimerMediator::Notify(nsITimer* aTimer)
1492 {
1493 if (!mFrame)
1494 return NS_ERROR_FAILURE;
1496 return mFrame->Notify(aTimer);
1497 }
1499 /**
1500 * Clear the pointer to the contained nsMenuFrame. This should be called
1501 * when the contained nsMenuFrame is destroyed.
1502 */
1503 void nsMenuTimerMediator::ClearFrame()
1504 {
1505 mFrame = nullptr;
1506 }