Coverage Report

Created: 2018-09-25 14:53

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