Source code

Revision control

Other Tools

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