Coverage Report

Created: 2018-09-25 14:53

/src/mozilla-central/layout/xul/nsXULPopupManager.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 "nsXULPopupManager.h"
9
#include "nsMenuFrame.h"
10
#include "nsMenuPopupFrame.h"
11
#include "nsMenuBarFrame.h"
12
#include "nsMenuBarListener.h"
13
#include "nsContentUtils.h"
14
#include "nsXULElement.h"
15
#include "nsIDOMXULMenuListElement.h"
16
#include "nsIDOMXULCommandDispatcher.h"
17
#include "nsCSSFrameConstructor.h"
18
#include "nsGlobalWindow.h"
19
#include "nsLayoutUtils.h"
20
#include "nsViewManager.h"
21
#include "nsIComponentManager.h"
22
#include "nsITimer.h"
23
#include "nsFocusManager.h"
24
#include "nsIDocShell.h"
25
#include "nsPIDOMWindow.h"
26
#include "nsIInterfaceRequestorUtils.h"
27
#include "nsIBaseWindow.h"
28
#include "nsCaret.h"
29
#include "nsIDocument.h"
30
#include "nsPIWindowRoot.h"
31
#include "nsFrameManager.h"
32
#include "nsIObserverService.h"
33
#include "XULDocument.h"
34
#include "mozilla/dom/Element.h"
35
#include "mozilla/dom/Event.h" // for Event
36
#include "mozilla/dom/KeyboardEvent.h"
37
#include "mozilla/dom/KeyboardEventBinding.h"
38
#include "mozilla/dom/MouseEvent.h"
39
#include "mozilla/dom/UIEvent.h"
40
#include "mozilla/EventDispatcher.h"
41
#include "mozilla/EventStateManager.h"
42
#include "mozilla/LookAndFeel.h"
43
#include "mozilla/MouseEvents.h"
44
#include "mozilla/Services.h"
45
#include "mozilla/widget/nsAutoRollup.h"
46
47
using namespace mozilla;
48
using namespace mozilla::dom;
49
50
static_assert(KeyboardEvent_Binding::DOM_VK_HOME  ==
51
                KeyboardEvent_Binding::DOM_VK_END + 1 &&
52
              KeyboardEvent_Binding::DOM_VK_LEFT  ==
53
                KeyboardEvent_Binding::DOM_VK_END + 2 &&
54
              KeyboardEvent_Binding::DOM_VK_UP    ==
55
                KeyboardEvent_Binding::DOM_VK_END + 3 &&
56
              KeyboardEvent_Binding::DOM_VK_RIGHT ==
57
                KeyboardEvent_Binding::DOM_VK_END + 4 &&
58
              KeyboardEvent_Binding::DOM_VK_DOWN  ==
59
                KeyboardEvent_Binding::DOM_VK_END + 5,
60
              "nsXULPopupManager assumes some keyCode values are consecutive");
61
62
const nsNavigationDirection DirectionFromKeyCodeTable[2][6] = {
63
  {
64
    eNavigationDirection_Last,   // KeyboardEvent_Binding::DOM_VK_END
65
    eNavigationDirection_First,  // KeyboardEvent_Binding::DOM_VK_HOME
66
    eNavigationDirection_Start,  // KeyboardEvent_Binding::DOM_VK_LEFT
67
    eNavigationDirection_Before, // KeyboardEvent_Binding::DOM_VK_UP
68
    eNavigationDirection_End,    // KeyboardEvent_Binding::DOM_VK_RIGHT
69
    eNavigationDirection_After   // KeyboardEvent_Binding::DOM_VK_DOWN
70
  },
71
  {
72
    eNavigationDirection_Last,   // KeyboardEvent_Binding::DOM_VK_END
73
    eNavigationDirection_First,  // KeyboardEvent_Binding::DOM_VK_HOME
74
    eNavigationDirection_End,    // KeyboardEvent_Binding::DOM_VK_LEFT
75
    eNavigationDirection_Before, // KeyboardEvent_Binding::DOM_VK_UP
76
    eNavigationDirection_Start,  // KeyboardEvent_Binding::DOM_VK_RIGHT
77
    eNavigationDirection_After   // KeyboardEvent_Binding::DOM_VK_DOWN
78
  }
79
};
80
81
nsXULPopupManager* nsXULPopupManager::sInstance = nullptr;
82
83
nsIContent* nsMenuChainItem::Content()
84
0
{
85
0
  return mFrame->GetContent();
86
0
}
87
88
void nsMenuChainItem::SetParent(nsMenuChainItem* aParent)
89
0
{
90
0
  if (mParent) {
91
0
    NS_ASSERTION(mParent->mChild == this, "Unexpected - parent's child not set to this");
92
0
    mParent->mChild = nullptr;
93
0
  }
94
0
  mParent = aParent;
95
0
  if (mParent) {
96
0
    if (mParent->mChild)
97
0
      mParent->mChild->mParent = nullptr;
98
0
    mParent->mChild = this;
99
0
  }
100
0
}
101
102
void nsMenuChainItem::Detach(nsMenuChainItem** aRoot)
103
0
{
104
0
  // If the item has a child, set the child's parent to this item's parent,
105
0
  // effectively removing the item from the chain. If the item has no child,
106
0
  // just set the parent to null.
107
0
  if (mChild) {
108
0
    NS_ASSERTION(this != *aRoot, "Unexpected - popup with child at end of chain");
109
0
    mChild->SetParent(mParent);
110
0
  }
111
0
  else {
112
0
    // An item without a child should be the first item in the chain, so set
113
0
    // the first item pointer, pointed to by aRoot, to the parent.
114
0
    NS_ASSERTION(this == *aRoot, "Unexpected - popup with no child not at end of chain");
115
0
    *aRoot = mParent;
116
0
    SetParent(nullptr);
117
0
  }
118
0
}
119
120
void
121
nsMenuChainItem::UpdateFollowAnchor()
122
0
{
123
0
  mFollowAnchor = mFrame->ShouldFollowAnchor(mCurrentRect);
124
0
}
125
126
void
127
nsMenuChainItem::CheckForAnchorChange()
128
0
{
129
0
  if (mFollowAnchor) {
130
0
    mFrame->CheckForAnchorChange(mCurrentRect);
131
0
  }
132
0
}
133
134
bool nsXULPopupManager::sDevtoolsDisableAutoHide = false;
135
136
const char kPrefDevtoolsDisableAutoHide[] =
137
  "ui.popup.disable_autohide";
138
139
NS_IMPL_ISUPPORTS(nsXULPopupManager,
140
                  nsIDOMEventListener,
141
                  nsIObserver)
142
143
nsXULPopupManager::nsXULPopupManager() :
144
  mRangeOffset(0),
145
  mCachedMousePoint(0, 0),
146
  mCachedModifiers(0),
147
  mActiveMenuBar(nullptr),
148
  mPopups(nullptr),
149
  mTimerMenu(nullptr)
150
3
{
151
3
  nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
152
3
  if (obs) {
153
3
    obs->AddObserver(this, "xpcom-shutdown", false);
154
3
  }
155
3
  Preferences::AddBoolVarCache(&sDevtoolsDisableAutoHide,
156
3
                               kPrefDevtoolsDisableAutoHide, false);
157
3
}
158
159
nsXULPopupManager::~nsXULPopupManager()
160
0
{
161
0
  NS_ASSERTION(!mPopups, "XUL popups still open");
162
0
}
163
164
nsresult
165
nsXULPopupManager::Init()
166
3
{
167
3
  sInstance = new nsXULPopupManager();
168
3
  NS_ENSURE_TRUE(sInstance, NS_ERROR_OUT_OF_MEMORY);
169
3
  NS_ADDREF(sInstance);
170
3
  return NS_OK;
171
3
}
172
173
void
174
nsXULPopupManager::Shutdown()
175
0
{
176
0
  NS_IF_RELEASE(sInstance);
177
0
}
178
179
NS_IMETHODIMP
180
nsXULPopupManager::Observe(nsISupports *aSubject,
181
                           const char *aTopic,
182
                           const char16_t *aData)
183
0
{
184
0
  if (!nsCRT::strcmp(aTopic, "xpcom-shutdown")) {
185
0
    if (mKeyListener) {
186
0
      mKeyListener->RemoveEventListener(NS_LITERAL_STRING("keypress"), this, true);
187
0
      mKeyListener->RemoveEventListener(NS_LITERAL_STRING("keydown"), this, true);
188
0
      mKeyListener->RemoveEventListener(NS_LITERAL_STRING("keyup"), this, true);
189
0
      mKeyListener = nullptr;
190
0
    }
191
0
    mRangeParent = nullptr;
192
0
    // mOpeningPopup is cleared explicitly soon after using it.
193
0
    nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
194
0
    if (obs) {
195
0
      obs->RemoveObserver(this, "xpcom-shutdown");
196
0
    }
197
0
  }
198
0
199
0
  return NS_OK;
200
0
}
201
202
nsXULPopupManager*
203
nsXULPopupManager::GetInstance()
204
0
{
205
0
  MOZ_ASSERT(sInstance);
206
0
  return sInstance;
207
0
}
208
209
bool
210
nsXULPopupManager::Rollup(uint32_t aCount, bool aFlush,
211
                          const nsIntPoint* pos, nsIContent** aLastRolledUp)
212
0
{
213
0
  if (aLastRolledUp) {
214
0
    *aLastRolledUp = nullptr;
215
0
  }
216
0
217
0
  // We can disable the autohide behavior via a pref to ease debugging.
218
0
  if (nsXULPopupManager::sDevtoolsDisableAutoHide) {
219
0
    // Required on linux to allow events to work on other targets.
220
0
    if (mWidget) {
221
0
      mWidget->CaptureRollupEvents(nullptr, false);
222
0
    }
223
0
    return false;
224
0
  }
225
0
226
0
  bool consume = false;
227
0
228
0
  nsMenuChainItem* item = GetTopVisibleMenu();
229
0
  if (item) {
230
0
    if (aLastRolledUp) {
231
0
      // We need to get the popup that will be closed last, so that widget can
232
0
      // keep track of it so it doesn't reopen if a mousedown event is going to
233
0
      // processed. Keep going up the menu chain to get the first level menu of
234
0
      // the same type. If a different type is encountered it means we have,
235
0
      // for example, a menulist or context menu inside a panel, and we want to
236
0
      // treat these as distinct. It's possible that this menu doesn't end up
237
0
      // closing because the popuphiding event was cancelled, but in that case
238
0
      // we don't need to deal with the menu reopening as it will already still
239
0
      // be open.
240
0
      nsMenuChainItem* first = item;
241
0
      while (first->GetParent()) {
242
0
        nsMenuChainItem* parent = first->GetParent();
243
0
        if (first->Frame()->PopupType() != parent->Frame()->PopupType() ||
244
0
            first->IsContextMenu() != parent->IsContextMenu()) {
245
0
          break;
246
0
        }
247
0
        first = parent;
248
0
      }
249
0
250
0
251
0
      *aLastRolledUp = first->Content();
252
0
    }
253
0
254
0
    ConsumeOutsideClicksResult consumeResult = item->Frame()->ConsumeOutsideClicks();
255
0
    consume = (consumeResult == ConsumeOutsideClicks_True);
256
0
257
0
    bool rollup = true;
258
0
259
0
    // If norolluponanchor is true, then don't rollup when clicking the anchor.
260
0
    // This would be used to allow adjusting the caret position in an
261
0
    // autocomplete field without hiding the popup for example.
262
0
    bool noRollupOnAnchor = (!consume && pos &&
263
0
      item->Frame()->GetContent()->AsElement()->AttrValueIs(kNameSpaceID_None,
264
0
        nsGkAtoms::norolluponanchor, nsGkAtoms::_true, eCaseMatters));
265
0
266
0
    // When ConsumeOutsideClicks_ParentOnly is used, always consume the click
267
0
    // when the click was over the anchor. This way, clicking on a menu doesn't
268
0
    // reopen the menu.
269
0
    if ((consumeResult == ConsumeOutsideClicks_ParentOnly || noRollupOnAnchor) && pos) {
270
0
      nsMenuPopupFrame* popupFrame = item->Frame();
271
0
      CSSIntRect anchorRect;
272
0
      if (popupFrame->IsAnchored()) {
273
0
        // Check if the popup has a screen anchor rectangle. If not, get the rectangle
274
0
        // from the anchor element.
275
0
        anchorRect = CSSIntRect::FromUnknownRect(popupFrame->GetScreenAnchorRect());
276
0
        if (anchorRect.x == -1 || anchorRect.y == -1) {
277
0
          nsCOMPtr<nsIContent> anchor = popupFrame->GetAnchor();
278
0
279
0
          // Check if the anchor has indicated another node to use for checking
280
0
          // for roll-up. That way, we can anchor a popup on anonymous content or
281
0
          // an individual icon, while clicking elsewhere within a button or other
282
0
          // container doesn't result in us re-opening the popup.
283
0
          if (anchor && anchor->IsElement()) {
284
0
            nsAutoString consumeAnchor;
285
0
            anchor->AsElement()->GetAttr(kNameSpaceID_None,
286
0
                                         nsGkAtoms::consumeanchor,
287
0
                                         consumeAnchor);
288
0
            if (!consumeAnchor.IsEmpty()) {
289
0
              nsIDocument* doc = anchor->GetOwnerDocument();
290
0
              nsIContent* newAnchor = doc->GetElementById(consumeAnchor);
291
0
              if (newAnchor) {
292
0
                anchor = newAnchor;
293
0
              }
294
0
            }
295
0
          }
296
0
297
0
          if (anchor && anchor->GetPrimaryFrame()) {
298
0
            anchorRect = anchor->GetPrimaryFrame()->GetScreenRect();
299
0
          }
300
0
        }
301
0
      }
302
0
303
0
      // It's possible that some other element is above the anchor at the same
304
0
      // position, but the only thing that would happen is that the mouse
305
0
      // event will get consumed, so here only a quick coordinates check is
306
0
      // done rather than a slower complete check of what is at that location.
307
0
      nsPresContext* presContext = item->Frame()->PresContext();
308
0
      CSSIntPoint posCSSPixels(presContext->DevPixelsToIntCSSPixels(pos->x),
309
0
                               presContext->DevPixelsToIntCSSPixels(pos->y));
310
0
      if (anchorRect.Contains(posCSSPixels)) {
311
0
        if (consumeResult == ConsumeOutsideClicks_ParentOnly) {
312
0
          consume = true;
313
0
        }
314
0
315
0
        if (noRollupOnAnchor) {
316
0
          rollup = false;
317
0
        }
318
0
      }
319
0
    }
320
0
321
0
    if (rollup) {
322
0
      // if a number of popups to close has been specified, determine the last
323
0
      // popup to close
324
0
      nsIContent* lastPopup = nullptr;
325
0
      if (aCount != UINT32_MAX) {
326
0
        nsMenuChainItem* last = item;
327
0
        while (--aCount && last->GetParent()) {
328
0
          last = last->GetParent();
329
0
        }
330
0
        if (last) {
331
0
          lastPopup = last->Content();
332
0
        }
333
0
      }
334
0
335
0
      nsPresContext* presContext = item->Frame()->PresContext();
336
0
      RefPtr<nsViewManager> viewManager = presContext->PresShell()->GetViewManager();
337
0
338
0
      HidePopup(item->Content(), true, true, false, true, lastPopup);
339
0
340
0
      if (aFlush) {
341
0
        // The popup's visibility doesn't update until the minimize animation has
342
0
        // finished, so call UpdateWidgetGeometry to update it right away.
343
0
        viewManager->UpdateWidgetGeometry();
344
0
      }
345
0
    }
346
0
  }
347
0
348
0
  return consume;
349
0
}
350
351
////////////////////////////////////////////////////////////////////////
352
bool nsXULPopupManager::ShouldRollupOnMouseWheelEvent()
353
0
{
354
0
  // should rollup only for autocomplete widgets
355
0
  // XXXndeakin this should really be something the popup has more control over
356
0
357
0
  nsMenuChainItem* item = GetTopVisibleMenu();
358
0
  if (!item)
359
0
    return false;
360
0
361
0
  nsIContent* content = item->Frame()->GetContent();
362
0
  if (!content || !content->IsElement())
363
0
    return false;
364
0
365
0
  Element* element = content->AsElement();
366
0
  if (element->AttrValueIs(kNameSpaceID_None, nsGkAtoms::rolluponmousewheel,
367
0
                           nsGkAtoms::_true, eCaseMatters))
368
0
    return true;
369
0
370
0
  if (element->AttrValueIs(kNameSpaceID_None, nsGkAtoms::rolluponmousewheel,
371
0
                           nsGkAtoms::_false, eCaseMatters))
372
0
    return false;
373
0
374
0
  nsAutoString value;
375
0
  element->GetAttr(kNameSpaceID_None, nsGkAtoms::type, value);
376
0
  return StringBeginsWith(value, NS_LITERAL_STRING("autocomplete"));
377
0
}
378
379
bool nsXULPopupManager::ShouldConsumeOnMouseWheelEvent()
380
0
{
381
0
  nsMenuChainItem* item = GetTopVisibleMenu();
382
0
  if (!item)
383
0
    return false;
384
0
385
0
  nsMenuPopupFrame* frame = item->Frame();
386
0
  if (frame->PopupType() != ePopupTypePanel)
387
0
    return true;
388
0
389
0
  return !frame->GetContent()->AsElement()->AttrValueIs(
390
0
      kNameSpaceID_None, nsGkAtoms::type, nsGkAtoms::arrow, eCaseMatters);
391
0
}
392
393
// a menu should not roll up if activated by a mouse activate message (eg. X-mouse)
394
bool nsXULPopupManager::ShouldRollupOnMouseActivate()
395
0
{
396
0
  return false;
397
0
}
398
399
uint32_t
400
nsXULPopupManager::GetSubmenuWidgetChain(nsTArray<nsIWidget*> *aWidgetChain)
401
0
{
402
0
  // this method is used by the widget code to determine the list of popups
403
0
  // that are open. If a mouse click occurs outside one of these popups, the
404
0
  // panels will roll up. If the click is inside a popup, they will not roll up
405
0
  uint32_t count = 0, sameTypeCount = 0;
406
0
407
0
  NS_ASSERTION(aWidgetChain, "null parameter");
408
0
  nsMenuChainItem* item = GetTopVisibleMenu();
409
0
  while (item) {
410
0
    nsMenuChainItem* parent = item->GetParent();
411
0
    if (!item->IsNoAutoHide()) {
412
0
      nsCOMPtr<nsIWidget> widget = item->Frame()->GetWidget();
413
0
      NS_ASSERTION(widget, "open popup has no widget");
414
0
      aWidgetChain->AppendElement(widget.get());
415
0
      // In the case when a menulist inside a panel is open, clicking in the
416
0
      // panel should still roll up the menu, so if a different type is found,
417
0
      // stop scanning.
418
0
      if (!sameTypeCount) {
419
0
        count++;
420
0
        if (!parent || item->Frame()->PopupType() != parent->Frame()->PopupType() ||
421
0
                       item->IsContextMenu() != parent->IsContextMenu()) {
422
0
          sameTypeCount = count;
423
0
        }
424
0
      }
425
0
    }
426
0
427
0
    item = parent;
428
0
  }
429
0
430
0
  return sameTypeCount;
431
0
}
432
433
nsIWidget*
434
nsXULPopupManager::GetRollupWidget()
435
0
{
436
0
  nsMenuChainItem* item = GetTopVisibleMenu();
437
0
  return item ? item->Frame()->GetWidget() : nullptr;
438
0
}
439
440
void
441
nsXULPopupManager::AdjustPopupsOnWindowChange(nsPIDOMWindowOuter* aWindow)
442
0
{
443
0
  // When the parent window is moved, adjust any child popups. Dismissable
444
0
  // menus and panels are expected to roll up when a window is moved, so there
445
0
  // is no need to check these popups, only the noautohide popups.
446
0
447
0
  // The items are added to a list so that they can be adjusted bottom to top.
448
0
  nsTArray<nsMenuPopupFrame *> list;
449
0
450
0
  nsMenuChainItem* item = mPopups;
451
0
  while (item) {
452
0
    // only move popups that are within the same window and where auto
453
0
    // positioning has not been disabled
454
0
    nsMenuPopupFrame* frame = item->Frame();
455
0
    if (item->IsNoAutoHide() && frame->GetAutoPosition()) {
456
0
      nsIContent* popup = frame->GetContent();
457
0
      if (popup) {
458
0
        nsIDocument* document = popup->GetUncomposedDoc();
459
0
        if (document) {
460
0
          if (nsPIDOMWindowOuter* window = document->GetWindow()) {
461
0
            window = window->GetPrivateRoot();
462
0
            if (window == aWindow) {
463
0
              list.AppendElement(frame);
464
0
            }
465
0
          }
466
0
        }
467
0
      }
468
0
    }
469
0
470
0
    item = item->GetParent();
471
0
  }
472
0
473
0
  for (int32_t l = list.Length() - 1; l >= 0; l--) {
474
0
    list[l]->SetPopupPosition(nullptr, true, false, true);
475
0
  }
476
0
}
477
478
void nsXULPopupManager::AdjustPopupsOnWindowChange(nsIPresShell* aPresShell)
479
0
{
480
0
  if (aPresShell->GetDocument()) {
481
0
    AdjustPopupsOnWindowChange(aPresShell->GetDocument()->GetWindow());
482
0
  }
483
0
}
484
485
static
486
nsMenuPopupFrame* GetPopupToMoveOrResize(nsIFrame* aFrame)
487
0
{
488
0
  nsMenuPopupFrame* menuPopupFrame = do_QueryFrame(aFrame);
489
0
  if (!menuPopupFrame)
490
0
    return nullptr;
491
0
492
0
  // no point moving or resizing hidden popups
493
0
  if (!menuPopupFrame->IsVisible())
494
0
    return nullptr;
495
0
496
0
  nsIWidget* widget = menuPopupFrame->GetWidget();
497
0
  if (widget && !widget->IsVisible())
498
0
    return nullptr;
499
0
500
0
  return menuPopupFrame;
501
0
}
502
503
void
504
nsXULPopupManager::PopupMoved(nsIFrame* aFrame, nsIntPoint aPnt)
505
0
{
506
0
  nsMenuPopupFrame* menuPopupFrame = GetPopupToMoveOrResize(aFrame);
507
0
  if (!menuPopupFrame)
508
0
    return;
509
0
510
0
  nsView* view = menuPopupFrame->GetView();
511
0
  if (!view)
512
0
    return;
513
0
514
0
  // Don't do anything if the popup is already at the specified location. This
515
0
  // prevents recursive calls when a popup is positioned.
516
0
  LayoutDeviceIntRect curDevSize = view->CalcWidgetBounds(eWindowType_popup);
517
0
  nsIWidget* widget = menuPopupFrame->GetWidget();
518
0
  if (curDevSize.x == aPnt.x && curDevSize.y == aPnt.y &&
519
0
      (!widget || widget->GetClientOffset() ==
520
0
                  menuPopupFrame->GetLastClientOffset())) {
521
0
    return;
522
0
  }
523
0
524
0
  // Update the popup's position using SetPopupPosition if the popup is
525
0
  // anchored and at the parent level as these maintain their position
526
0
  // relative to the parent window. Otherwise, just update the popup to
527
0
  // the specified screen coordinates.
528
0
  if (menuPopupFrame->IsAnchored() &&
529
0
      menuPopupFrame->PopupLevel() == ePopupLevelParent) {
530
0
    menuPopupFrame->SetPopupPosition(nullptr, true, false, true);
531
0
  }
532
0
  else {
533
0
    CSSPoint cssPos = LayoutDeviceIntPoint::FromUnknownPoint(aPnt)
534
0
                    / menuPopupFrame->PresContext()->CSSToDevPixelScale();
535
0
    menuPopupFrame->MoveTo(RoundedToInt(cssPos), false);
536
0
  }
537
0
}
538
539
void
540
nsXULPopupManager::PopupResized(nsIFrame* aFrame, LayoutDeviceIntSize aSize)
541
0
{
542
0
  nsMenuPopupFrame* menuPopupFrame = GetPopupToMoveOrResize(aFrame);
543
0
  if (!menuPopupFrame)
544
0
    return;
545
0
546
0
  nsView* view = menuPopupFrame->GetView();
547
0
  if (!view)
548
0
    return;
549
0
550
0
  LayoutDeviceIntRect curDevSize = view->CalcWidgetBounds(eWindowType_popup);
551
0
  // If the size is what we think it is, we have nothing to do.
552
0
  if (curDevSize.width == aSize.width && curDevSize.height == aSize.height)
553
0
    return;
554
0
555
0
  Element* popup = menuPopupFrame->GetContent()->AsElement();
556
0
557
0
  // Only set the width and height if the popup already has these attributes.
558
0
  if (!popup->HasAttr(kNameSpaceID_None, nsGkAtoms::width) ||
559
0
      !popup->HasAttr(kNameSpaceID_None, nsGkAtoms::height)) {
560
0
    return;
561
0
  }
562
0
563
0
  // The size is different. Convert the actual size to css pixels and store it
564
0
  // as 'width' and 'height' attributes on the popup.
565
0
  nsPresContext* presContext = menuPopupFrame->PresContext();
566
0
567
0
  CSSIntSize newCSS(presContext->DevPixelsToIntCSSPixels(aSize.width),
568
0
                    presContext->DevPixelsToIntCSSPixels(aSize.height));
569
0
570
0
  nsAutoString width, height;
571
0
  width.AppendInt(newCSS.width);
572
0
  height.AppendInt(newCSS.height);
573
0
  popup->SetAttr(kNameSpaceID_None, nsGkAtoms::width, width, false);
574
0
  popup->SetAttr(kNameSpaceID_None, nsGkAtoms::height, height, true);
575
0
}
576
577
nsMenuPopupFrame*
578
nsXULPopupManager::GetPopupFrameForContent(nsIContent* aContent, bool aShouldFlush)
579
0
{
580
0
  if (aShouldFlush) {
581
0
    nsIDocument *document = aContent->GetUncomposedDoc();
582
0
    if (document) {
583
0
      nsCOMPtr<nsIPresShell> presShell = document->GetShell();
584
0
      if (presShell)
585
0
        presShell->FlushPendingNotifications(FlushType::Layout);
586
0
    }
587
0
  }
588
0
589
0
  return do_QueryFrame(aContent->GetPrimaryFrame());
590
0
}
591
592
nsMenuChainItem*
593
nsXULPopupManager::GetTopVisibleMenu()
594
0
{
595
0
  nsMenuChainItem* item = mPopups;
596
0
  while (item) {
597
0
    if (!item->IsNoAutoHide() && item->Frame()->PopupState() != ePopupInvisible) {
598
0
      return item;
599
0
    }
600
0
    item = item->GetParent();
601
0
  }
602
0
603
0
  return nullptr;
604
0
}
605
606
nsINode*
607
nsXULPopupManager::GetMouseLocationParent()
608
0
{
609
0
  return mRangeParent;
610
0
}
611
612
int32_t
613
nsXULPopupManager::MouseLocationOffset()
614
0
{
615
0
  return mRangeOffset;
616
0
}
617
618
void
619
nsXULPopupManager::InitTriggerEvent(Event* aEvent, nsIContent* aPopup,
620
                                    nsIContent** aTriggerContent)
621
0
{
622
0
  mCachedMousePoint = LayoutDeviceIntPoint(0, 0);
623
0
624
0
  if (aTriggerContent) {
625
0
    *aTriggerContent = nullptr;
626
0
    if (aEvent) {
627
0
      // get the trigger content from the event
628
0
      nsCOMPtr<nsIContent> target = do_QueryInterface(aEvent->GetTarget());
629
0
      target.forget(aTriggerContent);
630
0
    }
631
0
  }
632
0
633
0
  mCachedModifiers = 0;
634
0
635
0
  UIEvent* uiEvent = aEvent ? aEvent->AsUIEvent() : nullptr;
636
0
  if (uiEvent) {
637
0
    mRangeParent = uiEvent->GetRangeParent();
638
0
    mRangeOffset = uiEvent->RangeOffset();
639
0
640
0
    // get the event coordinates relative to the root frame of the document
641
0
    // containing the popup.
642
0
    NS_ASSERTION(aPopup, "Expected a popup node");
643
0
    WidgetEvent* event = aEvent->WidgetEventPtr();
644
0
    if (event) {
645
0
      WidgetInputEvent* inputEvent = event->AsInputEvent();
646
0
      if (inputEvent) {
647
0
        mCachedModifiers = inputEvent->mModifiers;
648
0
      }
649
0
      nsIDocument* doc = aPopup->GetUncomposedDoc();
650
0
      if (doc) {
651
0
        nsIPresShell* presShell = doc->GetShell();
652
0
        nsPresContext* presContext;
653
0
        if (presShell && (presContext = presShell->GetPresContext())) {
654
0
          nsPresContext* rootDocPresContext =
655
0
            presContext->GetRootPresContext();
656
0
          if (!rootDocPresContext)
657
0
            return;
658
0
          nsIFrame* rootDocumentRootFrame = rootDocPresContext->
659
0
              PresShell()->GetRootFrame();
660
0
          if ((event->mClass == eMouseEventClass ||
661
0
               event->mClass == eMouseScrollEventClass ||
662
0
               event->mClass == eWheelEventClass) &&
663
0
               !event->AsGUIEvent()->mWidget) {
664
0
            // no widget, so just use the client point if available
665
0
            MouseEvent* mouseEvent = aEvent->AsMouseEvent();
666
0
            nsIntPoint clientPt(mouseEvent->ClientX(), mouseEvent->ClientY());
667
0
668
0
            // XXX this doesn't handle IFRAMEs in transforms
669
0
            nsPoint thisDocToRootDocOffset = presShell->
670
0
              GetRootFrame()->GetOffsetToCrossDoc(rootDocumentRootFrame);
671
0
            // convert to device pixels
672
0
            mCachedMousePoint.x = presContext->AppUnitsToDevPixels(
673
0
                nsPresContext::CSSPixelsToAppUnits(clientPt.x) + thisDocToRootDocOffset.x);
674
0
            mCachedMousePoint.y = presContext->AppUnitsToDevPixels(
675
0
                nsPresContext::CSSPixelsToAppUnits(clientPt.y) + thisDocToRootDocOffset.y);
676
0
          }
677
0
          else if (rootDocumentRootFrame) {
678
0
            nsPoint pnt =
679
0
              nsLayoutUtils::GetEventCoordinatesRelativeTo(event, rootDocumentRootFrame);
680
0
            mCachedMousePoint = LayoutDeviceIntPoint(rootDocPresContext->AppUnitsToDevPixels(pnt.x),
681
0
                                                     rootDocPresContext->AppUnitsToDevPixels(pnt.y));
682
0
          }
683
0
        }
684
0
      }
685
0
    }
686
0
  }
687
0
  else {
688
0
    mRangeParent = nullptr;
689
0
    mRangeOffset = 0;
690
0
  }
691
0
}
692
693
void
694
nsXULPopupManager::SetActiveMenuBar(nsMenuBarFrame* aMenuBar, bool aActivate)
695
0
{
696
0
  if (aActivate)
697
0
    mActiveMenuBar = aMenuBar;
698
0
  else if (mActiveMenuBar == aMenuBar)
699
0
    mActiveMenuBar = nullptr;
700
0
701
0
  UpdateKeyboardListeners();
702
0
}
703
704
void
705
nsXULPopupManager::ShowMenu(nsIContent *aMenu,
706
                            bool aSelectFirstItem,
707
                            bool aAsynchronous)
708
0
{
709
0
  nsMenuFrame* menuFrame = do_QueryFrame(aMenu->GetPrimaryFrame());
710
0
  if (!menuFrame || !menuFrame->IsMenu())
711
0
    return;
712
0
713
0
  nsMenuPopupFrame* popupFrame =  menuFrame->GetPopup();
714
0
  if (!popupFrame || !MayShowPopup(popupFrame))
715
0
    return;
716
0
717
0
  // inherit whether or not we're a context menu from the parent
718
0
  bool parentIsContextMenu = false;
719
0
  bool onMenuBar = false;
720
0
  bool onmenu = menuFrame->IsOnMenu();
721
0
722
0
  nsMenuParent* parent = menuFrame->GetMenuParent();
723
0
  if (parent && onmenu) {
724
0
    parentIsContextMenu = parent->IsContextMenu();
725
0
    onMenuBar = parent->IsMenuBar();
726
0
  }
727
0
728
0
  nsAutoString position;
729
0
730
#ifdef XP_MACOSX
731
  nsCOMPtr<nsIDOMXULMenuListElement> menulist = do_QueryInterface(aMenu);
732
  if (menulist) {
733
    position.AssignLiteral("selection");
734
  }
735
  else
736
#endif
737
738
0
  if (onMenuBar || !onmenu)
739
0
    position.AssignLiteral("after_start");
740
0
  else
741
0
    position.AssignLiteral("end_before");
742
0
743
0
  // there is no trigger event for menus
744
0
  InitTriggerEvent(nullptr, nullptr, nullptr);
745
0
  popupFrame->InitializePopup(menuFrame->GetAnchor(), nullptr, position, 0, 0,
746
0
                              MenuPopupAnchorType_Node, true);
747
0
748
0
  if (aAsynchronous) {
749
0
    nsCOMPtr<nsIRunnable> event =
750
0
      new nsXULPopupShowingEvent(popupFrame->GetContent(),
751
0
                                 parentIsContextMenu, aSelectFirstItem);
752
0
    aMenu->OwnerDoc()->Dispatch(TaskCategory::Other, event.forget());
753
0
  }
754
0
  else {
755
0
    nsCOMPtr<nsIContent> popupContent = popupFrame->GetContent();
756
0
    FirePopupShowingEvent(popupContent, parentIsContextMenu, aSelectFirstItem, nullptr);
757
0
  }
758
0
}
759
760
void
761
nsXULPopupManager::ShowPopup(nsIContent* aPopup,
762
                             nsIContent* aAnchorContent,
763
                             const nsAString& aPosition,
764
                             int32_t aXPos, int32_t aYPos,
765
                             bool aIsContextMenu,
766
                             bool aAttributesOverride,
767
                             bool aSelectFirstItem,
768
                             Event* aTriggerEvent)
769
0
{
770
0
  nsMenuPopupFrame* popupFrame = GetPopupFrameForContent(aPopup, true);
771
0
  if (!popupFrame || !MayShowPopup(popupFrame))
772
0
    return;
773
0
774
0
  nsCOMPtr<nsIContent> triggerContent;
775
0
  InitTriggerEvent(aTriggerEvent, aPopup, getter_AddRefs(triggerContent));
776
0
777
0
  popupFrame->InitializePopup(aAnchorContent, triggerContent, aPosition,
778
0
                              aXPos, aYPos, MenuPopupAnchorType_Node, aAttributesOverride);
779
0
780
0
  FirePopupShowingEvent(aPopup, aIsContextMenu, aSelectFirstItem, aTriggerEvent);
781
0
}
782
783
void
784
nsXULPopupManager::ShowPopupAtScreen(nsIContent* aPopup,
785
                                     int32_t aXPos, int32_t aYPos,
786
                                     bool aIsContextMenu,
787
                                     Event* aTriggerEvent)
788
0
{
789
0
  nsMenuPopupFrame* popupFrame = GetPopupFrameForContent(aPopup, true);
790
0
  if (!popupFrame || !MayShowPopup(popupFrame))
791
0
    return;
792
0
793
0
  nsCOMPtr<nsIContent> triggerContent;
794
0
  InitTriggerEvent(aTriggerEvent, aPopup, getter_AddRefs(triggerContent));
795
0
796
0
  popupFrame->InitializePopupAtScreen(triggerContent, aXPos, aYPos, aIsContextMenu);
797
0
  FirePopupShowingEvent(aPopup, aIsContextMenu, false, aTriggerEvent);
798
0
}
799
800
void
801
nsXULPopupManager::ShowPopupAtScreenRect(nsIContent* aPopup,
802
                                         const nsAString& aPosition,
803
                                         const nsIntRect& aRect,
804
                                         bool aIsContextMenu,
805
                                         bool aAttributesOverride,
806
                                         Event* aTriggerEvent)
807
0
{
808
0
  nsMenuPopupFrame* popupFrame = GetPopupFrameForContent(aPopup, true);
809
0
  if (!popupFrame || !MayShowPopup(popupFrame))
810
0
    return;
811
0
812
0
  nsCOMPtr<nsIContent> triggerContent;
813
0
  InitTriggerEvent(aTriggerEvent, aPopup, getter_AddRefs(triggerContent));
814
0
815
0
  popupFrame->InitializePopupAtRect(triggerContent, aPosition,
816
0
                                    aRect, aAttributesOverride);
817
0
818
0
  FirePopupShowingEvent(aPopup, aIsContextMenu, false, aTriggerEvent);
819
0
}
820
821
void
822
nsXULPopupManager::ShowTooltipAtScreen(nsIContent* aPopup,
823
                                       nsIContent* aTriggerContent,
824
                                       int32_t aXPos, int32_t aYPos)
825
0
{
826
0
  nsMenuPopupFrame* popupFrame = GetPopupFrameForContent(aPopup, true);
827
0
  if (!popupFrame || !MayShowPopup(popupFrame))
828
0
    return;
829
0
830
0
  InitTriggerEvent(nullptr, nullptr, nullptr);
831
0
832
0
  nsPresContext* pc = popupFrame->PresContext();
833
0
  mCachedMousePoint = LayoutDeviceIntPoint(pc->CSSPixelsToDevPixels(aXPos),
834
0
                                           pc->CSSPixelsToDevPixels(aYPos));
835
0
836
0
  // coordinates are relative to the root widget
837
0
  nsPresContext* rootPresContext = pc->GetRootPresContext();
838
0
  if (rootPresContext) {
839
0
    nsIWidget *rootWidget = rootPresContext->GetRootWidget();
840
0
    if (rootWidget) {
841
0
      mCachedMousePoint -= rootWidget->WidgetToScreenOffset();
842
0
    }
843
0
  }
844
0
845
0
  popupFrame->InitializePopupAtScreen(aTriggerContent, aXPos, aYPos, false);
846
0
847
0
  FirePopupShowingEvent(aPopup, false, false, nullptr);
848
0
}
849
850
static void
851
CheckCaretDrawingState()
852
0
{
853
0
  // There is 1 caret per document, we need to find the focused
854
0
  // document and erase its caret.
855
0
  nsIFocusManager* fm = nsFocusManager::GetFocusManager();
856
0
  if (fm) {
857
0
    nsCOMPtr<mozIDOMWindowProxy> window;
858
0
    fm->GetFocusedWindow(getter_AddRefs(window));
859
0
    if (!window)
860
0
      return;
861
0
862
0
    auto* piWindow = nsPIDOMWindowOuter::From(window);
863
0
    MOZ_ASSERT(piWindow);
864
0
865
0
    nsCOMPtr<nsIDocument> focusedDoc = piWindow->GetDoc();
866
0
    if (!focusedDoc)
867
0
      return;
868
0
869
0
    nsIPresShell* presShell = focusedDoc->GetShell();
870
0
    if (!presShell)
871
0
      return;
872
0
873
0
    RefPtr<nsCaret> caret = presShell->GetCaret();
874
0
    if (!caret)
875
0
      return;
876
0
    caret->SchedulePaint();
877
0
  }
878
0
}
879
880
void
881
nsXULPopupManager::ShowPopupCallback(nsIContent* aPopup,
882
                                     nsMenuPopupFrame* aPopupFrame,
883
                                     bool aIsContextMenu,
884
                                     bool aSelectFirstItem)
885
0
{
886
0
  nsPopupType popupType = aPopupFrame->PopupType();
887
0
  bool ismenu = (popupType == ePopupTypeMenu);
888
0
889
0
  // Popups normally hide when an outside click occurs. Panels may use
890
0
  // the noautohide attribute to disable this behaviour. It is expected
891
0
  // that the application will hide these popups manually. The tooltip
892
0
  // listener will handle closing the tooltip also.
893
0
  bool isNoAutoHide = aPopupFrame->IsNoAutoHide() || popupType == ePopupTypeTooltip;
894
0
895
0
  nsMenuChainItem* item =
896
0
    new nsMenuChainItem(aPopupFrame, isNoAutoHide, aIsContextMenu, popupType);
897
0
  if (!item)
898
0
    return;
899
0
900
0
  // install keyboard event listeners for navigating menus. For panels, the
901
0
  // escape key may be used to close the panel. However, the ignorekeys
902
0
  // attribute may be used to disable adding these event listeners for popups
903
0
  // that want to handle their own keyboard events.
904
0
  nsAutoString ignorekeys;
905
0
  if (aPopup->IsElement()) {
906
0
    aPopup->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::ignorekeys, ignorekeys);
907
0
  }
908
0
  if (ignorekeys.EqualsLiteral("true")) {
909
0
    item->SetIgnoreKeys(eIgnoreKeys_True);
910
0
  } else if (ignorekeys.EqualsLiteral("shortcuts")) {
911
0
    item->SetIgnoreKeys(eIgnoreKeys_Shortcuts);
912
0
  }
913
0
914
0
  if (ismenu) {
915
0
    // if the menu is on a menubar, use the menubar's listener instead
916
0
    nsMenuFrame* menuFrame = do_QueryFrame(aPopupFrame->GetParent());
917
0
    if (menuFrame) {
918
0
      item->SetOnMenuBar(menuFrame->IsOnMenuBar());
919
0
    }
920
0
  }
921
0
922
0
  // use a weak frame as the popup will set an open attribute if it is a menu
923
0
  AutoWeakFrame weakFrame(aPopupFrame);
924
0
  aPopupFrame->ShowPopup(aIsContextMenu);
925
0
  NS_ENSURE_TRUE_VOID(weakFrame.IsAlive());
926
0
927
0
  // popups normally hide when an outside click occurs. Panels may use
928
0
  // the noautohide attribute to disable this behaviour. It is expected
929
0
  // that the application will hide these popups manually. The tooltip
930
0
  // listener will handle closing the tooltip also.
931
0
  nsIContent* oldmenu = nullptr;
932
0
  if (mPopups) {
933
0
    oldmenu = mPopups->Content();
934
0
  }
935
0
  item->SetParent(mPopups);
936
0
  mPopups = item;
937
0
  SetCaptureState(oldmenu);
938
0
  NS_ENSURE_TRUE_VOID(weakFrame.IsAlive());
939
0
940
0
  item->UpdateFollowAnchor();
941
0
942
0
  if (aSelectFirstItem) {
943
0
    nsMenuFrame* next = GetNextMenuItem(aPopupFrame, nullptr, true, false);
944
0
    aPopupFrame->SetCurrentMenuItem(next);
945
0
  }
946
0
947
0
  if (ismenu)
948
0
    UpdateMenuItems(aPopup);
949
0
950
0
  // Caret visibility may have been affected, ensure that
951
0
  // the caret isn't now drawn when it shouldn't be.
952
0
  CheckCaretDrawingState();
953
0
}
954
955
void
956
nsXULPopupManager::HidePopup(nsIContent* aPopup,
957
                             bool aHideChain,
958
                             bool aDeselectMenu,
959
                             bool aAsynchronous,
960
                             bool aIsCancel,
961
                             nsIContent* aLastPopup)
962
0
{
963
0
  nsMenuPopupFrame* popupFrame = do_QueryFrame(aPopup->GetPrimaryFrame());
964
0
  if (!popupFrame) {
965
0
    return;
966
0
  }
967
0
968
0
  nsMenuChainItem* foundPopup = mPopups;
969
0
  while (foundPopup) {
970
0
    if (foundPopup->Content() == aPopup) {
971
0
      break;
972
0
    }
973
0
    foundPopup = foundPopup->GetParent();
974
0
  }
975
0
976
0
  bool deselectMenu = false;
977
0
  nsCOMPtr<nsIContent> popupToHide, nextPopup, lastPopup;
978
0
979
0
  if (foundPopup) {
980
0
    if (foundPopup->IsNoAutoHide()) {
981
0
      // If this is a noautohide panel, remove it but don't close any other panels.
982
0
      popupToHide = aPopup;
983
0
    } else {
984
0
      // At this point, foundPopup will be set to the found item in the list. If
985
0
      // foundPopup is the topmost menu, the one to remove, then there are no other
986
0
      // popups to hide. If foundPopup is not the topmost menu, then there may be
987
0
      // open submenus below it. In this case, we need to make sure that those
988
0
      // submenus are closed up first. To do this, we scan up the menu list to
989
0
      // find the topmost popup with only menus between it and foundPopup and
990
0
      // close that menu first. In synchronous mode, the FirePopupHidingEvent
991
0
      // method will be called which in turn calls HidePopupCallback to close up
992
0
      // the next popup in the chain. These two methods will be called in
993
0
      // sequence recursively to close up all the necessary popups. In
994
0
      // asynchronous mode, a similar process occurs except that the
995
0
      // FirePopupHidingEvent method is called asynchronously. In either case,
996
0
      // nextPopup is set to the content node of the next popup to close, and
997
0
      // lastPopup is set to the last popup in the chain to close, which will be
998
0
      // aPopup, or null to close up all menus.
999
0
1000
0
      nsMenuChainItem* topMenu = foundPopup;
1001
0
      // Use IsMenu to ensure that foundPopup is a menu and scan down the child
1002
0
      // list until a non-menu is found. If foundPopup isn't a menu at all, don't
1003
0
      // scan and just close up this menu.
1004
0
      if (foundPopup->IsMenu()) {
1005
0
        nsMenuChainItem* child = foundPopup->GetChild();
1006
0
        while (child && child->IsMenu()) {
1007
0
          topMenu = child;
1008
0
          child = child->GetChild();
1009
0
        }
1010
0
      }
1011
0
1012
0
      deselectMenu = aDeselectMenu;
1013
0
      popupToHide = topMenu->Content();
1014
0
      popupFrame = topMenu->Frame();
1015
0
1016
0
      // Close up another popup if there is one, and we are either hiding the
1017
0
      // entire chain or the item to hide isn't the topmost popup.
1018
0
      nsMenuChainItem* parent = topMenu->GetParent();
1019
0
      if (parent && (aHideChain || topMenu != foundPopup)) {
1020
0
        while (parent && parent->IsNoAutoHide()) {
1021
0
          parent = parent->GetParent();
1022
0
        }
1023
0
1024
0
        if (parent) {
1025
0
          nextPopup = parent->Content();
1026
0
        }
1027
0
      }
1028
0
1029
0
      lastPopup = aLastPopup ? aLastPopup : (aHideChain ? nullptr : aPopup);
1030
0
    }
1031
0
  } else if (popupFrame->PopupState() == ePopupPositioning) {
1032
0
    // When the popup is in the popuppositioning state, it will not be in the
1033
0
    // mPopups list. We need another way to find it and make sure it does not
1034
0
    // continue the popup showing process.
1035
0
    deselectMenu = aDeselectMenu;
1036
0
    popupToHide = aPopup;
1037
0
  }
1038
0
1039
0
  if (popupToHide) {
1040
0
    nsPopupState state = popupFrame->PopupState();
1041
0
    // If the popup is already being hidden, don't attempt to hide it again
1042
0
    if (state == ePopupHiding) {
1043
0
      return;
1044
0
    }
1045
0
1046
0
    // Change the popup state to hiding. Don't set the hiding state if the
1047
0
    // popup is invisible, otherwise nsMenuPopupFrame::HidePopup will
1048
0
    // run again. In the invisible state, we just want the events to fire.
1049
0
    if (state != ePopupInvisible) {
1050
0
      popupFrame->SetPopupState(ePopupHiding);
1051
0
    }
1052
0
1053
0
    // For menus, popupToHide is always the frontmost item in the list to hide.
1054
0
    if (aAsynchronous) {
1055
0
      nsCOMPtr<nsIRunnable> event =
1056
0
        new nsXULPopupHidingEvent(popupToHide, nextPopup, lastPopup,
1057
0
                                  popupFrame->PopupType(), deselectMenu, aIsCancel);
1058
0
        aPopup->OwnerDoc()->Dispatch(TaskCategory::Other,
1059
0
                                     event.forget());
1060
0
    }
1061
0
    else {
1062
0
      FirePopupHidingEvent(popupToHide, nextPopup, lastPopup,
1063
0
                           popupFrame->PresContext(), popupFrame->PopupType(),
1064
0
                           deselectMenu, aIsCancel);
1065
0
    }
1066
0
  }
1067
0
}
1068
1069
// This is used to hide the popup after a transition finishes.
1070
class TransitionEnder final : public nsIDOMEventListener
1071
{
1072
protected:
1073
0
  virtual ~TransitionEnder() { }
1074
1075
public:
1076
1077
  nsCOMPtr<nsIContent> mContent;
1078
  bool mDeselectMenu;
1079
1080
  NS_DECL_CYCLE_COLLECTING_ISUPPORTS
1081
  NS_DECL_CYCLE_COLLECTION_CLASS(TransitionEnder)
1082
1083
  TransitionEnder(nsIContent* aContent, bool aDeselectMenu)
1084
    : mContent(aContent), mDeselectMenu(aDeselectMenu)
1085
0
  {
1086
0
  }
1087
1088
  NS_IMETHOD HandleEvent(Event* aEvent) override
1089
0
  {
1090
0
    mContent->RemoveSystemEventListener(NS_LITERAL_STRING("transitionend"), this, false);
1091
0
1092
0
    nsMenuPopupFrame* popupFrame = do_QueryFrame(mContent->GetPrimaryFrame());
1093
0
1094
0
    // Now hide the popup. There could be other properties transitioning, but
1095
0
    // we'll assume they all end at the same time and just hide the popup upon
1096
0
    // the first one ending.
1097
0
    nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
1098
0
    if (pm && popupFrame) {
1099
0
      pm->HidePopupCallback(mContent, popupFrame, nullptr, nullptr,
1100
0
                            popupFrame->PopupType(), mDeselectMenu);
1101
0
    }
1102
0
1103
0
    return NS_OK;
1104
0
  }
1105
};
1106
1107
NS_IMPL_CYCLE_COLLECTING_ADDREF(TransitionEnder)
1108
NS_IMPL_CYCLE_COLLECTING_RELEASE(TransitionEnder)
1109
0
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(TransitionEnder)
1110
0
  NS_INTERFACE_MAP_ENTRY(nsIDOMEventListener)
1111
0
  NS_INTERFACE_MAP_ENTRY(nsISupports)
1112
0
NS_INTERFACE_MAP_END
1113
1114
NS_IMPL_CYCLE_COLLECTION(TransitionEnder, mContent);
1115
1116
void
1117
nsXULPopupManager::HidePopupCallback(nsIContent* aPopup,
1118
                                     nsMenuPopupFrame* aPopupFrame,
1119
                                     nsIContent* aNextPopup,
1120
                                     nsIContent* aLastPopup,
1121
                                     nsPopupType aPopupType,
1122
                                     bool aDeselectMenu)
1123
0
{
1124
0
  if (mCloseTimer && mTimerMenu == aPopupFrame) {
1125
0
    mCloseTimer->Cancel();
1126
0
    mCloseTimer = nullptr;
1127
0
    mTimerMenu = nullptr;
1128
0
  }
1129
0
1130
0
  // The popup to hide is aPopup. Search the list again to find the item that
1131
0
  // corresponds to the popup to hide aPopup. This is done because it's
1132
0
  // possible someone added another item (attempted to open another popup)
1133
0
  // or removed a popup frame during the event processing so the item isn't at
1134
0
  // the front anymore.
1135
0
  nsMenuChainItem* item = mPopups;
1136
0
  while (item) {
1137
0
    if (item->Content() == aPopup) {
1138
0
      item->Detach(&mPopups);
1139
0
      SetCaptureState(aPopup);
1140
0
      break;
1141
0
    }
1142
0
    item = item->GetParent();
1143
0
  }
1144
0
1145
0
  delete item;
1146
0
1147
0
  AutoWeakFrame weakFrame(aPopupFrame);
1148
0
  aPopupFrame->HidePopup(aDeselectMenu, ePopupClosed);
1149
0
  NS_ENSURE_TRUE_VOID(weakFrame.IsAlive());
1150
0
1151
0
  // send the popuphidden event synchronously. This event has no default
1152
0
  // behaviour.
1153
0
  nsEventStatus status = nsEventStatus_eIgnore;
1154
0
  WidgetMouseEvent event(true, eXULPopupHidden, nullptr,
1155
0
                         WidgetMouseEvent::eReal);
1156
0
  EventDispatcher::Dispatch(aPopup, aPopupFrame->PresContext(),
1157
0
                            &event, nullptr, &status);
1158
0
  NS_ENSURE_TRUE_VOID(weakFrame.IsAlive());
1159
0
1160
0
  // Force any popups that might be anchored on elements within this popup to update.
1161
0
  UpdatePopupPositions(aPopupFrame->PresContext()->RefreshDriver());
1162
0
1163
0
  // if there are more popups to close, look for the next one
1164
0
  if (aNextPopup && aPopup != aLastPopup) {
1165
0
    nsMenuChainItem* foundMenu = nullptr;
1166
0
    nsMenuChainItem* item = mPopups;
1167
0
    while (item) {
1168
0
      if (item->Content() == aNextPopup) {
1169
0
        foundMenu = item;
1170
0
        break;
1171
0
      }
1172
0
      item = item->GetParent();
1173
0
    }
1174
0
1175
0
    // continue hiding the chain of popups until the last popup aLastPopup
1176
0
    // is reached, or until a popup of a different type is reached. This
1177
0
    // last check is needed so that a menulist inside a non-menu panel only
1178
0
    // closes the menu and not the panel as well.
1179
0
    if (foundMenu &&
1180
0
        (aLastPopup || aPopupType == foundMenu->PopupType())) {
1181
0
1182
0
      nsCOMPtr<nsIContent> popupToHide = item->Content();
1183
0
      nsMenuChainItem* parent = item->GetParent();
1184
0
1185
0
      nsCOMPtr<nsIContent> nextPopup;
1186
0
      if (parent && popupToHide != aLastPopup)
1187
0
        nextPopup = parent->Content();
1188
0
1189
0
      nsMenuPopupFrame* popupFrame = item->Frame();
1190
0
      nsPopupState state = popupFrame->PopupState();
1191
0
      if (state == ePopupHiding)
1192
0
        return;
1193
0
      if (state != ePopupInvisible)
1194
0
        popupFrame->SetPopupState(ePopupHiding);
1195
0
1196
0
      FirePopupHidingEvent(popupToHide, nextPopup, aLastPopup,
1197
0
                           popupFrame->PresContext(),
1198
0
                           foundMenu->PopupType(), aDeselectMenu, false);
1199
0
    }
1200
0
  }
1201
0
}
1202
1203
void
1204
nsXULPopupManager::HidePopupAfterDelay(nsMenuPopupFrame* aPopup)
1205
0
{
1206
0
  // Don't close up immediately.
1207
0
  // Kick off a close timer.
1208
0
  KillMenuTimer();
1209
0
1210
0
  int32_t menuDelay =
1211
0
    LookAndFeel::GetInt(LookAndFeel::eIntID_SubmenuDelay, 300); // ms
1212
0
1213
0
  // Kick off the timer.
1214
0
  nsIEventTarget* target = nullptr;
1215
0
  if (nsIContent* content = aPopup->GetContent()) {
1216
0
    target = content->OwnerDoc()->EventTargetFor(TaskCategory::Other);
1217
0
  }
1218
0
  NS_NewTimerWithFuncCallback(
1219
0
    getter_AddRefs(mCloseTimer),
1220
0
    [](nsITimer* aTimer, void* aClosure) {
1221
0
      nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
1222
0
      if (pm) {
1223
0
        pm->KillMenuTimer();
1224
0
      }
1225
0
    }, nullptr, menuDelay, nsITimer::TYPE_ONE_SHOT, "KillMenuTimer",
1226
0
    target);
1227
0
1228
0
  // the popup will call PopupDestroyed if it is destroyed, which checks if it
1229
0
  // is set to mTimerMenu, so it should be safe to keep a reference to it
1230
0
  mTimerMenu = aPopup;
1231
0
}
1232
1233
void
1234
nsXULPopupManager::HidePopupsInList(const nsTArray<nsMenuPopupFrame *> &aFrames)
1235
0
{
1236
0
  // Create a weak frame list. This is done in a separate array with the
1237
0
  // right capacity predetermined to avoid multiple allocations.
1238
0
  nsTArray<WeakFrame> weakPopups(aFrames.Length());
1239
0
  uint32_t f;
1240
0
  for (f = 0; f < aFrames.Length(); f++) {
1241
0
    WeakFrame* wframe = weakPopups.AppendElement();
1242
0
    if (wframe)
1243
0
      *wframe = aFrames[f];
1244
0
  }
1245
0
1246
0
  for (f = 0; f < weakPopups.Length(); f++) {
1247
0
    // check to ensure that the frame is still alive before hiding it.
1248
0
    if (weakPopups[f].IsAlive()) {
1249
0
      nsMenuPopupFrame* frame =
1250
0
        static_cast<nsMenuPopupFrame *>(weakPopups[f].GetFrame());
1251
0
      frame->HidePopup(true, ePopupInvisible);
1252
0
    }
1253
0
  }
1254
0
1255
0
  SetCaptureState(nullptr);
1256
0
}
1257
1258
void
1259
nsXULPopupManager::EnableRollup(nsIContent* aPopup, bool aShouldRollup)
1260
0
{
1261
0
#ifndef MOZ_GTK
1262
0
  nsMenuChainItem* item = mPopups;
1263
0
  while (item) {
1264
0
    if (item->Content() == aPopup) {
1265
0
      nsIContent* oldmenu = nullptr;
1266
0
      if (mPopups) {
1267
0
        oldmenu = mPopups->Content();
1268
0
      }
1269
0
1270
0
      item->SetNoAutoHide(!aShouldRollup);
1271
0
      SetCaptureState(oldmenu);
1272
0
      return;
1273
0
    }
1274
0
    item = item->GetParent();
1275
0
  }
1276
0
#endif
1277
0
}
1278
1279
bool
1280
nsXULPopupManager::IsChildOfDocShell(nsIDocument* aDoc, nsIDocShellTreeItem* aExpected)
1281
0
{
1282
0
  nsCOMPtr<nsIDocShellTreeItem> docShellItem(aDoc->GetDocShell());
1283
0
  while(docShellItem) {
1284
0
    if (docShellItem == aExpected)
1285
0
      return true;
1286
0
1287
0
    nsCOMPtr<nsIDocShellTreeItem> parent;
1288
0
    docShellItem->GetParent(getter_AddRefs(parent));
1289
0
    docShellItem = parent;
1290
0
  }
1291
0
1292
0
  return false;
1293
0
}
1294
1295
void
1296
nsXULPopupManager::HidePopupsInDocShell(nsIDocShellTreeItem* aDocShellToHide)
1297
0
{
1298
0
  nsTArray<nsMenuPopupFrame *> popupsToHide;
1299
0
1300
0
  // iterate to get the set of popup frames to hide
1301
0
  nsMenuChainItem* item = mPopups;
1302
0
  while (item) {
1303
0
    nsMenuChainItem* parent = item->GetParent();
1304
0
    if (item->Frame()->PopupState() != ePopupInvisible &&
1305
0
        IsChildOfDocShell(item->Content()->OwnerDoc(), aDocShellToHide)) {
1306
0
      nsMenuPopupFrame* frame = item->Frame();
1307
0
      item->Detach(&mPopups);
1308
0
      delete item;
1309
0
      popupsToHide.AppendElement(frame);
1310
0
    }
1311
0
    item = parent;
1312
0
  }
1313
0
1314
0
  HidePopupsInList(popupsToHide);
1315
0
}
1316
1317
void
1318
nsXULPopupManager::UpdatePopupPositions(nsRefreshDriver* aRefreshDriver)
1319
0
{
1320
0
  nsMenuChainItem* item = mPopups;
1321
0
  while (item) {
1322
0
    if (item->Frame()->PresContext()->RefreshDriver() == aRefreshDriver) {
1323
0
      item->CheckForAnchorChange();
1324
0
    }
1325
0
1326
0
    item = item->GetParent();
1327
0
  }
1328
0
}
1329
1330
void
1331
nsXULPopupManager::UpdateFollowAnchor(nsMenuPopupFrame* aPopup)
1332
0
{
1333
0
  nsMenuChainItem* item = mPopups;
1334
0
  while (item) {
1335
0
    if (item->Frame() == aPopup) {
1336
0
      item->UpdateFollowAnchor();
1337
0
      break;
1338
0
    }
1339
0
1340
0
    item = item->GetParent();
1341
0
  }
1342
0
}
1343
1344
void
1345
nsXULPopupManager::ExecuteMenu(nsIContent* aMenu, nsXULMenuCommandEvent* aEvent)
1346
0
{
1347
0
  CloseMenuMode cmm = CloseMenuMode_Auto;
1348
0
1349
0
  static Element::AttrValuesArray strings[] =
1350
0
    {&nsGkAtoms::none, &nsGkAtoms::single, nullptr};
1351
0
1352
0
  if (aMenu->IsElement()) {
1353
0
    switch (aMenu->AsElement()->FindAttrValueIn(kNameSpaceID_None,
1354
0
                                                nsGkAtoms::closemenu,
1355
0
                                                strings,
1356
0
                                                eCaseMatters)) {
1357
0
      case 0:
1358
0
        cmm = CloseMenuMode_None;
1359
0
        break;
1360
0
      case 1:
1361
0
        cmm = CloseMenuMode_Single;
1362
0
        break;
1363
0
      default:
1364
0
        break;
1365
0
    }
1366
0
  }
1367
0
1368
0
  // When a menuitem is selected to be executed, first hide all the open
1369
0
  // popups, but don't remove them yet. This is needed when a menu command
1370
0
  // opens a modal dialog. The views associated with the popups needed to be
1371
0
  // hidden and the accesibility events fired before the command executes, but
1372
0
  // the popuphiding/popuphidden events are fired afterwards.
1373
0
  nsTArray<nsMenuPopupFrame *> popupsToHide;
1374
0
  nsMenuChainItem* item = GetTopVisibleMenu();
1375
0
  if (cmm != CloseMenuMode_None) {
1376
0
    while (item) {
1377
0
      // if it isn't a <menupopup>, don't close it automatically
1378
0
      if (!item->IsMenu())
1379
0
        break;
1380
0
      nsMenuChainItem* next = item->GetParent();
1381
0
      popupsToHide.AppendElement(item->Frame());
1382
0
      if (cmm == CloseMenuMode_Single) // only close one level of menu
1383
0
        break;
1384
0
      item = next;
1385
0
    }
1386
0
1387
0
    // Now hide the popups. If the closemenu mode is auto, deselect the menu,
1388
0
    // otherwise only one popup is closing, so keep the parent menu selected.
1389
0
    HidePopupsInList(popupsToHide);
1390
0
  }
1391
0
1392
0
  aEvent->SetCloseMenuMode(cmm);
1393
0
  nsCOMPtr<nsIRunnable> event = aEvent;
1394
0
  aMenu->OwnerDoc()->Dispatch(TaskCategory::Other,
1395
0
                              event.forget());
1396
0
}
1397
1398
void
1399
nsXULPopupManager::FirePopupShowingEvent(nsIContent* aPopup,
1400
                                         bool aIsContextMenu,
1401
                                         bool aSelectFirstItem,
1402
                                         Event* aTriggerEvent)
1403
0
{
1404
0
  nsCOMPtr<nsIContent> popup = aPopup; // keep a strong reference to the popup
1405
0
1406
0
  nsMenuPopupFrame* popupFrame = do_QueryFrame(aPopup->GetPrimaryFrame());
1407
0
  if (!popupFrame)
1408
0
    return;
1409
0
1410
0
  nsPresContext *presContext = popupFrame->PresContext();
1411
0
  nsCOMPtr<nsIPresShell> presShell = presContext->PresShell();
1412
0
  nsPopupType popupType = popupFrame->PopupType();
1413
0
1414
0
  // generate the child frames if they have not already been generated
1415
0
  if (!popupFrame->HasGeneratedChildren()) {
1416
0
    popupFrame->SetGeneratedChildren();
1417
0
    presShell->FrameConstructor()->GenerateChildFrames(popupFrame);
1418
0
  }
1419
0
1420
0
  // get the frame again
1421
0
  nsIFrame* frame = aPopup->GetPrimaryFrame();
1422
0
  if (!frame)
1423
0
    return;
1424
0
1425
0
  presShell->FrameNeedsReflow(frame, nsIPresShell::eTreeChange,
1426
0
                              NS_FRAME_HAS_DIRTY_CHILDREN);
1427
0
1428
0
  // cache the popup so that document.popupNode can retrieve the trigger node
1429
0
  // during the popupshowing event. It will be cleared below after the event
1430
0
  // has fired.
1431
0
  mOpeningPopup = aPopup;
1432
0
1433
0
  nsEventStatus status = nsEventStatus_eIgnore;
1434
0
  WidgetMouseEvent event(true, eXULPopupShowing, nullptr,
1435
0
                         WidgetMouseEvent::eReal);
1436
0
1437
0
  // coordinates are relative to the root widget
1438
0
  nsPresContext* rootPresContext =
1439
0
    presShell->GetPresContext()->GetRootPresContext();
1440
0
  if (rootPresContext) {
1441
0
    rootPresContext->PresShell()->GetViewManager()->
1442
0
      GetRootWidget(getter_AddRefs(event.mWidget));
1443
0
  }
1444
0
  else {
1445
0
    event.mWidget = nullptr;
1446
0
  }
1447
0
1448
0
  if (aTriggerEvent) {
1449
0
    WidgetMouseEventBase* mouseEvent =
1450
0
      aTriggerEvent->WidgetEventPtr()->AsMouseEventBase();
1451
0
    if (mouseEvent) {
1452
0
      event.inputSource = mouseEvent->inputSource;
1453
0
    }
1454
0
  }
1455
0
1456
0
  event.mRefPoint = mCachedMousePoint;
1457
0
  event.mModifiers = mCachedModifiers;
1458
0
  EventDispatcher::Dispatch(popup, presContext, &event, nullptr, &status);
1459
0
1460
0
  mCachedMousePoint = LayoutDeviceIntPoint(0, 0);
1461
0
  mOpeningPopup = nullptr;
1462
0
1463
0
  mCachedModifiers = 0;
1464
0
1465
0
  // if a panel, blur whatever has focus so that the panel can take the focus.
1466
0
  // This is done after the popupshowing event in case that event is cancelled.
1467
0
  // Using noautofocus="true" will disable this behaviour, which is needed for
1468
0
  // the autocomplete widget as it manages focus itself.
1469
0
  if (popupType == ePopupTypePanel &&
1470
0
      !popup->AsElement()->AttrValueIs(kNameSpaceID_None,
1471
0
                                       nsGkAtoms::noautofocus,
1472
0
                                       nsGkAtoms::_true, eCaseMatters)) {
1473
0
    nsFocusManager* fm = nsFocusManager::GetFocusManager();
1474
0
    if (fm) {
1475
0
      nsIDocument* doc = popup->GetUncomposedDoc();
1476
0
1477
0
      // Only remove the focus if the currently focused item is ouside the
1478
0
      // popup. It isn't a big deal if the current focus is in a child popup
1479
0
      // inside the popup as that shouldn't be visible. This check ensures that
1480
0
      // a node inside the popup that is focused during a popupshowing event
1481
0
      // remains focused.
1482
0
      RefPtr<Element> currentFocus = fm->GetFocusedElement();
1483
0
      if (doc && currentFocus &&
1484
0
          !nsContentUtils::ContentIsCrossDocDescendantOf(currentFocus, popup)) {
1485
0
        fm->ClearFocus(doc->GetWindow());
1486
0
      }
1487
0
    }
1488
0
  }
1489
0
1490
0
  // clear these as they are no longer valid
1491
0
  mRangeParent = nullptr;
1492
0
  mRangeOffset = 0;
1493
0
1494
0
  // get the frame again in case it went away
1495
0
  popupFrame = do_QueryFrame(aPopup->GetPrimaryFrame());
1496
0
  if (popupFrame) {
1497
0
    // if the event was cancelled, don't open the popup, reset its state back
1498
0
    // to closed and clear its trigger content.
1499
0
    if (status == nsEventStatus_eConsumeNoDefault) {
1500
0
      popupFrame->SetPopupState(ePopupClosed);
1501
0
      popupFrame->ClearTriggerContent();
1502
0
    } else {
1503
0
      // Now check if we need to fire the popuppositioned event. If not, call
1504
0
      // ShowPopupCallback directly.
1505
0
1506
0
      // The popuppositioned event only fires on arrow panels for now.
1507
0
      if (popup->AsElement()->AttrValueIs(kNameSpaceID_None, nsGkAtoms::type,
1508
0
                                          nsGkAtoms::arrow, eCaseMatters)) {
1509
0
        popupFrame->ShowWithPositionedEvent();
1510
0
        presShell->FrameNeedsReflow(popupFrame, nsIPresShell::eTreeChange,
1511
0
                                    NS_FRAME_HAS_DIRTY_CHILDREN);
1512
0
      }
1513
0
      else {
1514
0
        ShowPopupCallback(popup, popupFrame, aIsContextMenu, aSelectFirstItem);
1515
0
      }
1516
0
    }
1517
0
  }
1518
0
}
1519
1520
void
1521
nsXULPopupManager::FirePopupHidingEvent(nsIContent* aPopup,
1522
                                        nsIContent* aNextPopup,
1523
                                        nsIContent* aLastPopup,
1524
                                        nsPresContext *aPresContext,
1525
                                        nsPopupType aPopupType,
1526
                                        bool aDeselectMenu,
1527
                                        bool aIsCancel)
1528
0
{
1529
0
  nsCOMPtr<nsIPresShell> presShell = aPresContext->PresShell();
1530
0
  mozilla::Unused << presShell; // This presShell may be keeping things alive on non GTK platforms
1531
0
1532
0
  nsEventStatus status = nsEventStatus_eIgnore;
1533
0
  WidgetMouseEvent event(true, eXULPopupHiding, nullptr,
1534
0
                         WidgetMouseEvent::eReal);
1535
0
  EventDispatcher::Dispatch(aPopup, aPresContext, &event, nullptr, &status);
1536
0
1537
0
  // when a panel is closed, blur whatever has focus inside the popup
1538
0
  if (aPopupType == ePopupTypePanel &&
1539
0
      (!aPopup->IsElement() ||
1540
0
       !aPopup->AsElement()->AttrValueIs(kNameSpaceID_None,
1541
0
                                         nsGkAtoms::noautofocus,
1542
0
                                         nsGkAtoms::_true, eCaseMatters))) {
1543
0
    nsFocusManager* fm = nsFocusManager::GetFocusManager();
1544
0
    if (fm) {
1545
0
      nsIDocument* doc = aPopup->GetUncomposedDoc();
1546
0
1547
0
      // Remove the focus from the focused node only if it is inside the popup.
1548
0
      RefPtr<Element> currentFocus = fm->GetFocusedElement();
1549
0
      if (doc && currentFocus &&
1550
0
          nsContentUtils::ContentIsCrossDocDescendantOf(currentFocus, aPopup)) {
1551
0
        fm->ClearFocus(doc->GetWindow());
1552
0
      }
1553
0
    }
1554
0
  }
1555
0
1556
0
  // get frame again in case it went away
1557
0
  nsMenuPopupFrame* popupFrame = do_QueryFrame(aPopup->GetPrimaryFrame());
1558
0
  if (popupFrame) {
1559
0
    // if the event was cancelled, don't hide the popup, and reset its
1560
0
    // state back to open. Only popups in chrome shells can prevent a popup
1561
0
    // from hiding.
1562
0
    if (status == nsEventStatus_eConsumeNoDefault &&
1563
0
        !popupFrame->IsInContentShell()) {
1564
0
      // XXXndeakin
1565
0
      // If an attempt was made to hide this popup before the popupshown event
1566
0
      // fired, then ePopupShown is set here even though it should be
1567
0
      // ePopupVisible. This probably isn't worth the hassle of handling.
1568
0
      popupFrame->SetPopupState(ePopupShown);
1569
0
    }
1570
0
    else {
1571
0
      // If the popup has an animate attribute and it is not set to false, check
1572
0
      // if it has a closing transition and wait for it to finish. The transition
1573
0
      // may still occur either way, but the view will be hidden and you won't be
1574
0
      // able to see it. If there is a next popup, indicating that mutliple popups
1575
0
      // are rolling up, don't wait and hide the popup right away since the effect
1576
0
      // would likely be undesirable. Transitions are currently disabled on Linux
1577
0
      // due to rendering issues on certain configurations.
1578
#ifndef MOZ_WIDGET_GTK
1579
      if (!aNextPopup &&
1580
          aPopup->IsElement() &&
1581
          aPopup->AsElement()->HasAttr(kNameSpaceID_None, nsGkAtoms::animate)) {
1582
        // If animate="false" then don't transition at all. If animate="cancel",
1583
        // only show the transition if cancelling the popup or rolling up.
1584
        // Otherwise, always show the transition.
1585
        nsAutoString animate;
1586
        aPopup->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::animate, animate);
1587
1588
        if (!animate.EqualsLiteral("false") &&
1589
            (!animate.EqualsLiteral("cancel") || aIsCancel)) {
1590
          presShell->FlushPendingNotifications(FlushType::Layout);
1591
1592
          // Get the frame again in case the flush caused it to go away
1593
          popupFrame = do_QueryFrame(aPopup->GetPrimaryFrame());
1594
          if (!popupFrame)
1595
            return;
1596
1597
          if (nsLayoutUtils::HasCurrentTransitions(popupFrame)) {
1598
            RefPtr<TransitionEnder> ender = new TransitionEnder(aPopup, aDeselectMenu);
1599
            aPopup->AddSystemEventListener(NS_LITERAL_STRING("transitionend"),
1600
                                           ender, false, false);
1601
            return;
1602
          }
1603
        }
1604
      }
1605
#endif
1606
1607
0
      HidePopupCallback(aPopup, popupFrame, aNextPopup, aLastPopup,
1608
0
                        aPopupType, aDeselectMenu);
1609
0
    }
1610
0
  }
1611
0
}
1612
1613
bool
1614
nsXULPopupManager::IsPopupOpen(nsIContent* aPopup)
1615
0
{
1616
0
  // a popup is open if it is in the open list. The assertions ensure that the
1617
0
  // frame is in the correct state. If the popup is in the hiding or invisible
1618
0
  // state, it will still be in the open popup list until it is closed.
1619
0
  nsMenuChainItem* item = mPopups;
1620
0
  while (item) {
1621
0
    if (item->Content() == aPopup) {
1622
0
      NS_ASSERTION(item->Frame()->IsOpen() ||
1623
0
                   item->Frame()->PopupState() == ePopupHiding ||
1624
0
                   item->Frame()->PopupState() == ePopupInvisible,
1625
0
                   "popup in open list not actually open");
1626
0
      return true;
1627
0
    }
1628
0
    item = item->GetParent();
1629
0
  }
1630
0
1631
0
  return false;
1632
0
}
1633
1634
bool
1635
nsXULPopupManager::IsPopupOpenForMenuParent(nsMenuParent* aMenuParent)
1636
0
{
1637
0
  nsMenuChainItem* item = GetTopVisibleMenu();
1638
0
  while (item) {
1639
0
    nsMenuPopupFrame* popup = item->Frame();
1640
0
    if (popup && popup->IsOpen()) {
1641
0
      nsMenuFrame* menuFrame = do_QueryFrame(popup->GetParent());
1642
0
      if (menuFrame && menuFrame->GetMenuParent() == aMenuParent) {
1643
0
        return true;
1644
0
      }
1645
0
    }
1646
0
    item = item->GetParent();
1647
0
  }
1648
0
1649
0
  return false;
1650
0
}
1651
1652
nsIFrame*
1653
nsXULPopupManager::GetTopPopup(nsPopupType aType)
1654
0
{
1655
0
  nsMenuChainItem* item = mPopups;
1656
0
  while (item) {
1657
0
    if (item->Frame()->IsVisible() &&
1658
0
        (item->PopupType() == aType || aType == ePopupTypeAny)) {
1659
0
      return item->Frame();
1660
0
    }
1661
0
1662
0
    item = item->GetParent();
1663
0
  }
1664
0
1665
0
  return nullptr;
1666
0
}
1667
1668
void
1669
nsXULPopupManager::GetVisiblePopups(nsTArray<nsIFrame *>& aPopups)
1670
0
{
1671
0
  aPopups.Clear();
1672
0
1673
0
  nsMenuChainItem* item = mPopups;
1674
0
  while (item) {
1675
0
    // Skip panels which are not visible as well as popups that
1676
0
    // are transparent to mouse events.
1677
0
    if (item->Frame()->IsVisible() && !item->Frame()->IsMouseTransparent()) {
1678
0
      aPopups.AppendElement(item->Frame());
1679
0
    }
1680
0
1681
0
    item = item->GetParent();
1682
0
  }
1683
0
}
1684
1685
already_AddRefed<nsINode>
1686
nsXULPopupManager::GetLastTriggerNode(nsIDocument* aDocument, bool aIsTooltip)
1687
0
{
1688
0
  if (!aDocument)
1689
0
    return nullptr;
1690
0
1691
0
  nsCOMPtr<nsINode> node;
1692
0
1693
0
  // if mOpeningPopup is set, it means that a popupshowing event is being
1694
0
  // fired. In this case, just use the cached node, as the popup is not yet in
1695
0
  // the list of open popups.
1696
0
  if (mOpeningPopup && mOpeningPopup->GetUncomposedDoc() == aDocument &&
1697
0
      aIsTooltip == mOpeningPopup->IsXULElement(nsGkAtoms::tooltip)) {
1698
0
    node = nsMenuPopupFrame::GetTriggerContent(GetPopupFrameForContent(mOpeningPopup, false));
1699
0
  }
1700
0
  else {
1701
0
    nsMenuChainItem* item = mPopups;
1702
0
    while (item) {
1703
0
      // look for a popup of the same type and document.
1704
0
      if ((item->PopupType() == ePopupTypeTooltip) == aIsTooltip &&
1705
0
          item->Content()->GetUncomposedDoc() == aDocument) {
1706
0
        node = nsMenuPopupFrame::GetTriggerContent(item->Frame());
1707
0
        if (node)
1708
0
          break;
1709
0
      }
1710
0
      item = item->GetParent();
1711
0
    }
1712
0
  }
1713
0
1714
0
  return node.forget();
1715
0
}
1716
1717
bool
1718
nsXULPopupManager::MayShowPopup(nsMenuPopupFrame* aPopup)
1719
0
{
1720
0
  // if a popup's IsOpen method returns true, then the popup must always be in
1721
0
  // the popup chain scanned in IsPopupOpen.
1722
0
  NS_ASSERTION(!aPopup->IsOpen() || IsPopupOpen(aPopup->GetContent()),
1723
0
               "popup frame state doesn't match XULPopupManager open state");
1724
0
1725
0
  nsPopupState state = aPopup->PopupState();
1726
0
1727
0
  // if the popup is not in the open popup chain, then it must have a state that
1728
0
  // is either closed, in the process of being shown, or invisible.
1729
0
  NS_ASSERTION(IsPopupOpen(aPopup->GetContent()) || state == ePopupClosed ||
1730
0
               state == ePopupShowing || state == ePopupPositioning ||
1731
0
               state == ePopupInvisible,
1732
0
               "popup not in XULPopupManager open list is open");
1733
0
1734
0
  // don't show popups unless they are closed or invisible
1735
0
  if (state != ePopupClosed && state != ePopupInvisible)
1736
0
    return false;
1737
0
1738
0
  // Don't show popups that we already have in our popup chain
1739
0
  if (IsPopupOpen(aPopup->GetContent())) {
1740
0
    NS_WARNING("Refusing to show duplicate popup");
1741
0
    return false;
1742
0
  }
1743
0
1744
0
  // if the popup was just rolled up, don't reopen it
1745
0
  if (mozilla::widget::nsAutoRollup::GetLastRollup() == aPopup->GetContent())
1746
0
      return false;
1747
0
1748
0
  nsCOMPtr<nsIDocShellTreeItem> dsti = aPopup->PresContext()->GetDocShell();
1749
0
  nsCOMPtr<nsIBaseWindow> baseWin = do_QueryInterface(dsti);
1750
0
  if (!baseWin)
1751
0
    return false;
1752
0
1753
0
  nsCOMPtr<nsIDocShellTreeItem> root;
1754
0
  dsti->GetRootTreeItem(getter_AddRefs(root));
1755
0
  if (!root) {
1756
0
    return false;
1757
0
  }
1758
0
1759
0
  nsCOMPtr<nsPIDOMWindowOuter> rootWin = root->GetWindow();
1760
0
1761
0
  // chrome shells can always open popups, but other types of shells can only
1762
0
  // open popups when they are focused and visible
1763
0
  if (dsti->ItemType() != nsIDocShellTreeItem::typeChrome) {
1764
0
    // only allow popups in active windows
1765
0
    nsIFocusManager* fm = nsFocusManager::GetFocusManager();
1766
0
    if (!fm || !rootWin)
1767
0
      return false;
1768
0
1769
0
    nsCOMPtr<mozIDOMWindowProxy> activeWindow;
1770
0
    fm->GetActiveWindow(getter_AddRefs(activeWindow));
1771
0
    if (activeWindow != rootWin)
1772
0
      return false;
1773
0
1774
0
    // only allow popups in visible frames
1775
0
    bool visible;
1776
0
    baseWin->GetVisibility(&visible);
1777
0
    if (!visible)
1778
0
      return false;
1779
0
  }
1780
0
1781
0
  // platforms respond differently when an popup is opened in a minimized
1782
0
  // window, so this is always disabled.
1783
0
  nsCOMPtr<nsIWidget> mainWidget;
1784
0
  baseWin->GetMainWidget(getter_AddRefs(mainWidget));
1785
0
  if (mainWidget && mainWidget->SizeMode() == nsSizeMode_Minimized) {
1786
0
    return false;
1787
0
  }
1788
0
1789
#ifdef XP_MACOSX
1790
  if (rootWin) {
1791
    auto globalWin = nsGlobalWindowOuter::Cast(rootWin.get());
1792
    if (globalWin->IsInModalState()) {
1793
      return false;
1794
    }
1795
  }
1796
#endif
1797
1798
0
  // cannot open a popup that is a submenu of a menupopup that isn't open.
1799
0
  nsMenuFrame* menuFrame = do_QueryFrame(aPopup->GetParent());
1800
0
  if (menuFrame) {
1801
0
    nsMenuParent* parentPopup = menuFrame->GetMenuParent();
1802
0
    if (parentPopup && !parentPopup->IsOpen())
1803
0
      return false;
1804
0
  }
1805
0
1806
0
  return true;
1807
0
}
1808
1809
void
1810
nsXULPopupManager::PopupDestroyed(nsMenuPopupFrame* aPopup)
1811
0
{
1812
0
  // when a popup frame is destroyed, just unhook it from the list of popups
1813
0
  if (mTimerMenu == aPopup) {
1814
0
    if (mCloseTimer) {
1815
0
      mCloseTimer->Cancel();
1816
0
      mCloseTimer = nullptr;
1817
0
    }
1818
0
    mTimerMenu = nullptr;
1819
0
  }
1820
0
1821
0
  nsTArray<nsMenuPopupFrame *> popupsToHide;
1822
0
1823
0
  nsMenuChainItem* item = mPopups;
1824
0
  while (item) {
1825
0
    nsMenuPopupFrame* frame = item->Frame();
1826
0
    if (frame == aPopup) {
1827
0
      // XXXndeakin shouldn't this only happen for menus?
1828
0
      if (!item->IsNoAutoHide() && frame->PopupState() != ePopupInvisible) {
1829
0
        // Iterate through any child menus and hide them as well, since the
1830
0
        // parent is going away. We won't remove them from the list yet, just
1831
0
        // hide them, as they will be removed from the list when this function
1832
0
        // gets called for that child frame.
1833
0
        nsMenuChainItem* child = item->GetChild();
1834
0
        while (child) {
1835
0
          // if the popup is a child frame of the menu that was destroyed, add
1836
0
          // it to the list of popups to hide. Don't bother with the events
1837
0
          // since the frames are going away. If the child menu is not a child
1838
0
          // frame, for example, a context menu, use HidePopup instead, but call
1839
0
          // it asynchronously since we are in the middle of frame destruction.
1840
0
          nsMenuPopupFrame* childframe = child->Frame();
1841
0
          if (nsLayoutUtils::IsProperAncestorFrame(frame, childframe)) {
1842
0
            popupsToHide.AppendElement(childframe);
1843
0
          }
1844
0
          else {
1845
0
            // HidePopup will take care of hiding any of its children, so
1846
0
            // break out afterwards
1847
0
            HidePopup(child->Content(), false, false, true, false);
1848
0
            break;
1849
0
          }
1850
0
1851
0
          child = child->GetChild();
1852
0
        }
1853
0
      }
1854
0
1855
0
      item->Detach(&mPopups);
1856
0
      delete item;
1857
0
      break;
1858
0
    }
1859
0
1860
0
    item = item->GetParent();
1861
0
  }
1862
0
1863
0
  HidePopupsInList(popupsToHide);
1864
0
}
1865
1866
bool
1867
nsXULPopupManager::HasContextMenu(nsMenuPopupFrame* aPopup)
1868
0
{
1869
0
  nsMenuChainItem* item = GetTopVisibleMenu();
1870
0
  while (item && item->Frame() != aPopup) {
1871
0
    if (item->IsContextMenu())
1872
0
      return true;
1873
0
    item = item->GetParent();
1874
0
  }
1875
0
1876
0
  return false;
1877
0
}
1878
1879
void
1880
nsXULPopupManager::SetCaptureState(nsIContent* aOldPopup)
1881
0
{
1882
0
  nsMenuChainItem* item = GetTopVisibleMenu();
1883
0
  if (item && aOldPopup == item->Content())
1884
0
    return;
1885
0
1886
0
  if (mWidget) {
1887
0
    mWidget->CaptureRollupEvents(nullptr, false);
1888
0
    mWidget = nullptr;
1889
0
  }
1890
0
1891
0
  if (item) {
1892
0
    nsMenuPopupFrame* popup = item->Frame();
1893
0
    mWidget = popup->GetWidget();
1894
0
    if (mWidget) {
1895
0
      mWidget->CaptureRollupEvents(nullptr, true);
1896
0
    }
1897
0
  }
1898
0
1899
0
  UpdateKeyboardListeners();
1900
0
}
1901
1902
void
1903
nsXULPopupManager::UpdateKeyboardListeners()
1904
0
{
1905
0
  nsCOMPtr<EventTarget> newTarget;
1906
0
  bool isForMenu = false;
1907
0
  nsMenuChainItem* item = GetTopVisibleMenu();
1908
0
  if (item) {
1909
0
    if (item->IgnoreKeys() != eIgnoreKeys_True) {
1910
0
      newTarget = item->Content()->GetComposedDoc();
1911
0
    }
1912
0
    isForMenu = item->PopupType() == ePopupTypeMenu;
1913
0
  }
1914
0
  else if (mActiveMenuBar) {
1915
0
    newTarget = mActiveMenuBar->GetContent()->GetComposedDoc();
1916
0
    isForMenu = true;
1917
0
  }
1918
0
1919
0
  if (mKeyListener != newTarget) {
1920
0
    if (mKeyListener) {
1921
0
      mKeyListener->RemoveEventListener(NS_LITERAL_STRING("keypress"), this, true);
1922
0
      mKeyListener->RemoveEventListener(NS_LITERAL_STRING("keydown"), this, true);
1923
0
      mKeyListener->RemoveEventListener(NS_LITERAL_STRING("keyup"), this, true);
1924
0
      mKeyListener = nullptr;
1925
0
      nsContentUtils::NotifyInstalledMenuKeyboardListener(false);
1926
0
    }
1927
0
1928
0
    if (newTarget) {
1929
0
      newTarget->AddEventListener(NS_LITERAL_STRING("keypress"), this, true);
1930
0
      newTarget->AddEventListener(NS_LITERAL_STRING("keydown"), this, true);
1931
0
      newTarget->AddEventListener(NS_LITERAL_STRING("keyup"), this, true);
1932
0
      nsContentUtils::NotifyInstalledMenuKeyboardListener(isForMenu);
1933
0
      mKeyListener = newTarget;
1934
0
    }
1935
0
  }
1936
0
}
1937
1938
void
1939
nsXULPopupManager::UpdateMenuItems(nsIContent* aPopup)
1940
0
{
1941
0
  // Walk all of the menu's children, checking to see if any of them has a
1942
0
  // command attribute. If so, then several attributes must potentially be updated.
1943
0
1944
0
  nsCOMPtr<nsIDocument> document = aPopup->GetUncomposedDoc();
1945
0
  if (!document) {
1946
0
    return;
1947
0
  }
1948
0
1949
0
  // When a menu is opened, make sure that command updating is unlocked first.
1950
0
  nsCOMPtr<nsIDOMXULCommandDispatcher> commandDispatcher =
1951
0
    document->GetCommandDispatcher();
1952
0
  if (commandDispatcher) {
1953
0
    commandDispatcher->Unlock();
1954
0
  }
1955
0
1956
0
  for (nsCOMPtr<nsIContent> grandChild = aPopup->GetFirstChild();
1957
0
       grandChild;
1958
0
       grandChild = grandChild->GetNextSibling()) {
1959
0
    if (grandChild->IsXULElement(nsGkAtoms::menugroup)) {
1960
0
      if (grandChild->GetChildCount() == 0) {
1961
0
        continue;
1962
0
      }
1963
0
      grandChild = grandChild->GetFirstChild();
1964
0
    }
1965
0
    if (grandChild->IsXULElement(nsGkAtoms::menuitem)) {
1966
0
      // See if we have a command attribute.
1967
0
      Element* grandChildElement = grandChild->AsElement();
1968
0
      nsAutoString command;
1969
0
      grandChildElement->GetAttr(kNameSpaceID_None, nsGkAtoms::command, command);
1970
0
      if (!command.IsEmpty()) {
1971
0
        // We do! Look it up in our document
1972
0
        RefPtr<dom::Element> commandElement =
1973
0
          document->GetElementById(command);
1974
0
        if (commandElement) {
1975
0
          nsAutoString commandValue;
1976
0
          // The menu's disabled state needs to be updated to match the command.
1977
0
          if (commandElement->GetAttr(kNameSpaceID_None, nsGkAtoms::disabled, commandValue))
1978
0
            grandChildElement->SetAttr(kNameSpaceID_None, nsGkAtoms::disabled, commandValue, true);
1979
0
          else
1980
0
            grandChildElement->UnsetAttr(kNameSpaceID_None, nsGkAtoms::disabled, true);
1981
0
1982
0
          // The menu's label, accesskey checked and hidden states need to be updated
1983
0
          // to match the command. Note that unlike the disabled state if the
1984
0
          // command has *no* value, we assume the menu is supplying its own.
1985
0
          if (commandElement->GetAttr(kNameSpaceID_None, nsGkAtoms::label, commandValue))
1986
0
            grandChildElement->SetAttr(kNameSpaceID_None, nsGkAtoms::label, commandValue, true);
1987
0
1988
0
          if (commandElement->GetAttr(kNameSpaceID_None, nsGkAtoms::accesskey, commandValue))
1989
0
            grandChildElement->SetAttr(kNameSpaceID_None, nsGkAtoms::accesskey, commandValue, true);
1990
0
1991
0
          if (commandElement->GetAttr(kNameSpaceID_None, nsGkAtoms::checked, commandValue))
1992
0
            grandChildElement->SetAttr(kNameSpaceID_None, nsGkAtoms::checked, commandValue, true);
1993
0
1994
0
          if (commandElement->GetAttr(kNameSpaceID_None, nsGkAtoms::hidden, commandValue))
1995
0
            grandChildElement->SetAttr(kNameSpaceID_None, nsGkAtoms::hidden, commandValue, true);
1996
0
        }
1997
0
      }
1998
0
    }
1999
0
    if (!grandChild->GetNextSibling() &&
2000
0
        grandChild->GetParent()->IsXULElement(nsGkAtoms::menugroup)) {
2001
0
      grandChild = grandChild->GetParent();
2002
0
    }
2003
0
  }
2004
0
}
2005
2006
// Notify
2007
//
2008
// The item selection timer has fired, we might have to readjust the
2009
// selected item. There are two cases here that we are trying to deal with:
2010
//   (1) diagonal movement from a parent menu to a submenu passing briefly over
2011
//       other items, and
2012
//   (2) moving out from a submenu to a parent or grandparent menu.
2013
// In both cases, |mTimerMenu| is the menu item that might have an open submenu and
2014
// the first item in |mPopups| is the item the mouse is currently over, which could be
2015
// none of them.
2016
//
2017
// case (1):
2018
//  As the mouse moves from the parent item of a submenu (we'll call 'A') diagonally into the
2019
//  submenu, it probably passes through one or more sibilings (B). As the mouse passes
2020
//  through B, it becomes the current menu item and the timer is set and mTimerMenu is
2021
//  set to A. Before the timer fires, the mouse leaves the menu containing A and B and
2022
//  enters the submenus. Now when the timer fires, |mPopups| is null (!= |mTimerMenu|)
2023
//  so we have to see if anything in A's children is selected (recall that even disabled
2024
//  items are selected, the style just doesn't show it). If that is the case, we need to
2025
//  set the selected item back to A.
2026
//
2027
// case (2);
2028
//  Item A has an open submenu, and in it there is an item (B) which also has an open
2029
//  submenu (so there are 3 menus displayed right now). The mouse then leaves B's child
2030
//  submenu and selects an item that is a sibling of A, call it C. When the mouse enters C,
2031
//  the timer is set and |mTimerMenu| is A and |mPopups| is C. As the timer fires,
2032
//  the mouse is still within C. The correct behavior is to set the current item to C
2033
//  and close up the chain parented at A.
2034
//
2035
//  This brings up the question of is the logic of case (1) enough? The answer is no,
2036
//  and is discussed in bugzilla bug 29400. Case (1) asks if A's submenu has a selected
2037
//  child, and if it does, set the selected item to A. Because B has a submenu open, it
2038
//  is selected and as a result, A is set to be the selected item even though the mouse
2039
//  rests in C -- very wrong.
2040
//
2041
//  The solution is to use the same idea, but instead of only checking one level,
2042
//  drill all the way down to the deepest open submenu and check if it has something
2043
//  selected. Since the mouse is in a grandparent, it won't, and we know that we can
2044
//  safely close up A and all its children.
2045
//
2046
// The code below melds the two cases together.
2047
//
2048
void
2049
nsXULPopupManager::KillMenuTimer()
2050
0
{
2051
0
  if (mCloseTimer && mTimerMenu) {
2052
0
    mCloseTimer->Cancel();
2053
0
    mCloseTimer = nullptr;
2054
0
2055
0
    if (mTimerMenu->IsOpen())
2056
0
      HidePopup(mTimerMenu->GetContent(), false, false, true, false);
2057
0
  }
2058
0
2059
0
  mTimerMenu = nullptr;
2060
0
}
2061
2062
void
2063
nsXULPopupManager::CancelMenuTimer(nsMenuParent* aMenuParent)
2064
0
{
2065
0
  if (mCloseTimer && mTimerMenu == aMenuParent) {
2066
0
    mCloseTimer->Cancel();
2067
0
    mCloseTimer = nullptr;
2068
0
    mTimerMenu = nullptr;
2069
0
  }
2070
0
}
2071
2072
bool
2073
nsXULPopupManager::HandleShortcutNavigation(KeyboardEvent* aKeyEvent,
2074
                                            nsMenuPopupFrame* aFrame)
2075
0
{
2076
0
  // On Windows, don't check shortcuts when the accelerator key is down.
2077
#ifdef XP_WIN
2078
  WidgetInputEvent* evt = aKeyEvent->WidgetEventPtr()->AsInputEvent();
2079
  if (evt && evt->IsAccel()) {
2080
    return false;
2081
  }
2082
#endif
2083
2084
0
  nsMenuChainItem* item = GetTopVisibleMenu();
2085
0
  if (!aFrame && item)
2086
0
    aFrame = item->Frame();
2087
0
2088
0
  if (aFrame) {
2089
0
    bool action;
2090
0
    nsMenuFrame* result = aFrame->FindMenuWithShortcut(aKeyEvent, action);
2091
0
    if (result) {
2092
0
      aFrame->ChangeMenuItem(result, false, true);
2093
0
      if (action) {
2094
0
        WidgetGUIEvent* evt = aKeyEvent->WidgetEventPtr()->AsGUIEvent();
2095
0
        nsMenuFrame* menuToOpen = result->Enter(evt);
2096
0
        if (menuToOpen) {
2097
0
          nsCOMPtr<nsIContent> content = menuToOpen->GetContent();
2098
0
          ShowMenu(content, true, false);
2099
0
        }
2100
0
      }
2101
0
      return true;
2102
0
    }
2103
0
2104
0
    return false;
2105
0
  }
2106
0
2107
0
  if (mActiveMenuBar) {
2108
0
    nsMenuFrame* result = mActiveMenuBar->FindMenuWithShortcut(aKeyEvent, false);
2109
0
    if (result) {
2110
0
      mActiveMenuBar->SetActive(true);
2111
0
      result->OpenMenu(true);
2112
0
      return true;
2113
0
    }
2114
0
  }
2115
0
2116
0
  return false;
2117
0
}
2118
2119
2120
bool
2121
nsXULPopupManager::HandleKeyboardNavigation(uint32_t aKeyCode)
2122
0
{
2123
0
  // navigate up through the open menus, looking for the topmost one
2124
0
  // in the same hierarchy
2125
0
  nsMenuChainItem* item = nullptr;
2126
0
  nsMenuChainItem* nextitem = GetTopVisibleMenu();
2127
0
2128
0
  while (nextitem) {
2129
0
    item = nextitem;
2130
0
    nextitem = item->GetParent();
2131
0
2132
0
    if (nextitem) {
2133
0
      // stop if the parent isn't a menu
2134
0
      if (!nextitem->IsMenu())
2135
0
        break;
2136
0
2137
0
      // check to make sure that the parent is actually the parent menu. It won't
2138
0
      // be if the parent is in a different frame hierarchy, for example, for a
2139
0
      // context menu opened on another menu.
2140
0
      nsMenuParent* expectedParent = static_cast<nsMenuParent *>(nextitem->Frame());
2141
0
      nsMenuFrame* menuFrame = do_QueryFrame(item->Frame()->GetParent());
2142
0
      if (!menuFrame || menuFrame->GetMenuParent() != expectedParent) {
2143
0
        break;
2144
0
      }
2145
0
    }
2146
0
  }
2147
0
2148
0
  nsIFrame* itemFrame;
2149
0
  if (item)
2150
0
    itemFrame = item->Frame();
2151
0
  else if (mActiveMenuBar)
2152
0
    itemFrame = mActiveMenuBar;
2153
0
  else
2154
0
    return false;
2155
0
2156
0
  nsNavigationDirection theDirection;
2157
0
  NS_ASSERTION(aKeyCode >= KeyboardEvent_Binding::DOM_VK_END &&
2158
0
                 aKeyCode <= KeyboardEvent_Binding::DOM_VK_DOWN, "Illegal key code");
2159
0
  theDirection = NS_DIRECTION_FROM_KEY_CODE(itemFrame, aKeyCode);
2160
0
2161
0
  bool selectFirstItem = true;
2162
0
#ifdef MOZ_WIDGET_GTK
2163
0
  nsMenuFrame* currentItem = nullptr;
2164
0
  if (item && mActiveMenuBar && NS_DIRECTION_IS_INLINE(theDirection)) {
2165
0
    currentItem = item->Frame()->GetCurrentMenuItem();
2166
0
    // If nothing is selected in the menu and we have a menubar, let it
2167
0
    // handle the movement not to steal focus from it.
2168
0
    if (!currentItem) {
2169
0
      item = nullptr;
2170
0
    }
2171
0
  }
2172
0
  // On menu change, only select first item if an item is already selected.
2173
0
  selectFirstItem = currentItem != nullptr;
2174
0
#endif
2175
0
2176
0
  // if a popup is open, first check for navigation within the popup
2177
0
  if (item && HandleKeyboardNavigationInPopup(item, theDirection))
2178
0
    return true;
2179
0
2180
0
  // no popup handled the key, so check the active menubar, if any
2181
0
  if (mActiveMenuBar) {
2182
0
    nsMenuFrame* currentMenu = mActiveMenuBar->GetCurrentMenuItem();
2183
0
2184
0
    if (NS_DIRECTION_IS_INLINE(theDirection)) {
2185
0
      nsMenuFrame* nextItem = (theDirection == eNavigationDirection_End) ?
2186
0
                              GetNextMenuItem(mActiveMenuBar, currentMenu, false, true) :
2187
0
                              GetPreviousMenuItem(mActiveMenuBar, currentMenu, false, true);
2188
0
      mActiveMenuBar->ChangeMenuItem(nextItem, selectFirstItem, true);
2189
0
      return true;
2190
0
    }
2191
0
    else if (NS_DIRECTION_IS_BLOCK(theDirection)) {
2192
0
      // Open the menu and select its first item.
2193
0
      if (currentMenu) {
2194
0
        nsCOMPtr<nsIContent> content = currentMenu->GetContent();
2195
0
        ShowMenu(content, true, false);
2196
0
      }
2197
0
      return true;
2198
0
    }
2199
0
  }
2200
0
2201
0
  return false;
2202
0
}
2203
2204
bool
2205
nsXULPopupManager::HandleKeyboardNavigationInPopup(nsMenuChainItem* item,
2206
                                                   nsMenuPopupFrame* aFrame,
2207
                                                   nsNavigationDirection aDir)
2208
0
{
2209
0
  NS_ASSERTION(aFrame, "aFrame is null");
2210
0
  NS_ASSERTION(!item || item->Frame() == aFrame,
2211
0
               "aFrame is expected to be equal to item->Frame()");
2212
0
2213
0
  nsMenuFrame* currentMenu = aFrame->GetCurrentMenuItem();
2214
0
2215
0
  aFrame->ClearIncrementalString();
2216
0
2217
0
  // This method only gets called if we're open.
2218
0
  if (!currentMenu && NS_DIRECTION_IS_INLINE(aDir)) {
2219
0
    // We've been opened, but we haven't had anything selected.
2220
0
    // We can handle End, but our parent handles Start.
2221
0
    if (aDir == eNavigationDirection_End) {
2222
0
      nsMenuFrame* nextItem = GetNextMenuItem(aFrame, nullptr, true, false);
2223
0
      if (nextItem) {
2224
0
        aFrame->ChangeMenuItem(nextItem, false, true);
2225
0
        return true;
2226
0
      }
2227
0
    }
2228
0
    return false;
2229
0
  }
2230
0
2231
0
  bool isContainer = false;
2232
0
  bool isOpen = false;
2233
0
  if (currentMenu) {
2234
0
    isOpen = currentMenu->IsOpen();
2235
0
    isContainer = currentMenu->IsMenu();
2236
0
    if (isOpen) {
2237
0
      // for an open popup, have the child process the event
2238
0
      nsMenuChainItem* child = item ? item->GetChild() : nullptr;
2239
0
      if (child && HandleKeyboardNavigationInPopup(child, aDir))
2240
0
        return true;
2241
0
    }
2242
0
    else if (aDir == eNavigationDirection_End &&
2243
0
             isContainer && !currentMenu->IsDisabled()) {
2244
0
      // The menu is not yet open. Open it and select the first item.
2245
0
      nsCOMPtr<nsIContent> content = currentMenu->GetContent();
2246
0
      ShowMenu(content, true, false);
2247
0
      return true;
2248
0
    }
2249
0
  }
2250
0
2251
0
  // For block progression, we can move in either direction
2252
0
  if (NS_DIRECTION_IS_BLOCK(aDir) ||
2253
0
      NS_DIRECTION_IS_BLOCK_TO_EDGE(aDir)) {
2254
0
    nsMenuFrame* nextItem;
2255
0
2256
0
    if (aDir == eNavigationDirection_Before ||
2257
0
        aDir == eNavigationDirection_After) {
2258
0
      // Cursor navigation does not wrap on Mac or for menulists on Windows.
2259
0
      bool wrap =
2260
#ifdef XP_WIN
2261
        aFrame->IsMenuList() ? false : true;
2262
#elif defined XP_MACOSX
2263
        false;
2264
#else
2265
        true;
2266
0
#endif
2267
0
2268
0
      if (aDir == eNavigationDirection_Before) {
2269
0
        nextItem = GetPreviousMenuItem(aFrame, currentMenu, true, wrap);
2270
0
      } else {
2271
0
        nextItem = GetNextMenuItem(aFrame, currentMenu, true, wrap);
2272
0
      }
2273
0
    } else if (aDir == eNavigationDirection_First) {
2274
0
      nextItem = GetNextMenuItem(aFrame, nullptr, true, false);
2275
0
    } else {
2276
0
      nextItem = GetPreviousMenuItem(aFrame, nullptr, true, false);
2277
0
    }
2278
0
2279
0
    if (nextItem) {
2280
0
      aFrame->ChangeMenuItem(nextItem, false, true);
2281
0
      return true;
2282
0
    }
2283
0
  }
2284
0
  else if (currentMenu && isContainer && isOpen) {
2285
0
    if (aDir == eNavigationDirection_Start) {
2286
0
      // close a submenu when Left is pressed
2287
0
      nsMenuPopupFrame* popupFrame = currentMenu->GetPopup();
2288
0
      if (popupFrame)
2289
0
        HidePopup(popupFrame->GetContent(), false, false, false, false);
2290
0
      return true;
2291
0
    }
2292
0
  }
2293
0
2294
0
  return false;
2295
0
}
2296
2297
bool
2298
nsXULPopupManager::HandleKeyboardEventWithKeyCode(
2299
                        KeyboardEvent* aKeyEvent,
2300
                        nsMenuChainItem* aTopVisibleMenuItem)
2301
0
{
2302
0
  uint32_t keyCode = aKeyEvent->KeyCode();
2303
0
2304
0
  // Escape should close panels, but the other keys should have no effect.
2305
0
  if (aTopVisibleMenuItem &&
2306
0
      aTopVisibleMenuItem->PopupType() != ePopupTypeMenu) {
2307
0
    if (keyCode == KeyboardEvent_Binding::DOM_VK_ESCAPE) {
2308
0
      HidePopup(aTopVisibleMenuItem->Content(), false, false, false, true);
2309
0
      aKeyEvent->StopPropagation();
2310
0
      aKeyEvent->StopCrossProcessForwarding();
2311
0
      aKeyEvent->PreventDefault();
2312
0
    }
2313
0
    return true;
2314
0
  }
2315
0
2316
0
  bool consume = (aTopVisibleMenuItem || mActiveMenuBar);
2317
0
  switch (keyCode) {
2318
0
    case KeyboardEvent_Binding::DOM_VK_UP:
2319
0
    case KeyboardEvent_Binding::DOM_VK_DOWN:
2320
0
#ifndef XP_MACOSX
2321
0
      // roll up the popup when alt+up/down are pressed within a menulist.
2322
0
      if (aKeyEvent->AltKey() &&
2323
0
          aTopVisibleMenuItem &&
2324
0
          aTopVisibleMenuItem->Frame()->IsMenuList()) {
2325
0
        Rollup(0, false, nullptr, nullptr);
2326
0
        break;
2327
0
      }
2328
0
      MOZ_FALLTHROUGH;
2329
0
#endif
2330
0
2331
0
    case KeyboardEvent_Binding::DOM_VK_LEFT:
2332
0
    case KeyboardEvent_Binding::DOM_VK_RIGHT:
2333
0
    case KeyboardEvent_Binding::DOM_VK_HOME:
2334
0
    case KeyboardEvent_Binding::DOM_VK_END:
2335
0
      HandleKeyboardNavigation(keyCode);
2336
0
      break;
2337
0
2338
0
    case KeyboardEvent_Binding::DOM_VK_PAGE_DOWN:
2339
0
    case KeyboardEvent_Binding::DOM_VK_PAGE_UP:
2340
0
      if (aTopVisibleMenuItem) {
2341
0
        aTopVisibleMenuItem->Frame()->ChangeByPage(
2342
0
          keyCode == KeyboardEvent_Binding::DOM_VK_PAGE_UP);
2343
0
      }
2344
0
      break;
2345
0
2346
0
    case KeyboardEvent_Binding::DOM_VK_ESCAPE:
2347
0
      // Pressing Escape hides one level of menus only. If no menu is open,
2348
0
      // check if a menubar is active and inform it that a menu closed. Even
2349
0
      // though in this latter case, a menu didn't actually close, the effect
2350
0
      // ends up being the same. Similar for the tab key below.
2351
0
      if (aTopVisibleMenuItem) {
2352
0
        HidePopup(aTopVisibleMenuItem->Content(), false, false, false, true);
2353
0
      } else if (mActiveMenuBar) {
2354
0
        mActiveMenuBar->MenuClosed();
2355
0
      }
2356
0
      break;
2357
0
2358
0
    case KeyboardEvent_Binding::DOM_VK_TAB:
2359
0
#ifndef XP_MACOSX
2360
0
    case KeyboardEvent_Binding::DOM_VK_F10:
2361
0
#endif
2362
0
      if (aTopVisibleMenuItem &&
2363
0
          !aTopVisibleMenuItem->Frame()->GetContent()->AsElement()->AttrValueIs(
2364
0
            kNameSpaceID_None, nsGkAtoms::activateontab, nsGkAtoms::_true,
2365
0
            eCaseMatters)) {
2366
0
        // close popups or deactivate menubar when Tab or F10 are pressed
2367
0
        Rollup(0, false, nullptr, nullptr);
2368
0
        break;
2369
0
      } else if (mActiveMenuBar) {
2370
0
        mActiveMenuBar->MenuClosed();
2371
0
        break;
2372
0
      }
2373
0
      // Intentional fall-through to RETURN case
2374
0
      MOZ_FALLTHROUGH;
2375
0
2376
0
    case KeyboardEvent_Binding::DOM_VK_RETURN: {
2377
0
      // If there is a popup open, check if the current item needs to be opened.
2378
0
      // Otherwise, tell the active menubar, if any, to activate the menu. The
2379
0
      // Enter method will return a menu if one needs to be opened as a result.
2380
0
      nsMenuFrame* menuToOpen = nullptr;
2381
0
      WidgetGUIEvent* GUIEvent = aKeyEvent->WidgetEventPtr()->AsGUIEvent();
2382
0
2383
0
      if (aTopVisibleMenuItem) {
2384
0
        menuToOpen = aTopVisibleMenuItem->Frame()->Enter(GUIEvent);
2385
0
      } else if (mActiveMenuBar) {
2386
0
        menuToOpen = mActiveMenuBar->Enter(GUIEvent);
2387
0
      }
2388
0
      if (menuToOpen) {
2389
0
        nsCOMPtr<nsIContent> content = menuToOpen->GetContent();
2390
0
        ShowMenu(content, true, false);
2391
0
      }
2392
0
      break;
2393
0
    }
2394
0
2395
0
    default:
2396
0
      return false;
2397
0
  }
2398
0
2399
0
  if (consume) {
2400
0
    aKeyEvent->StopPropagation();
2401
0
    aKeyEvent->StopCrossProcessForwarding();
2402
0
    aKeyEvent->PreventDefault();
2403
0
  }
2404
0
  return true;
2405
0
}
2406
2407
nsContainerFrame*
2408
nsXULPopupManager::ImmediateParentFrame(nsContainerFrame* aFrame)
2409
0
{
2410
0
  MOZ_ASSERT(aFrame && aFrame->GetContent());
2411
0
2412
0
  bool multiple = false; // Unused
2413
0
  nsIContent* insertionPoint =
2414
0
    aFrame->GetContent()->OwnerDoc()->BindingManager()->
2415
0
      FindNestedSingleInsertionPoint(aFrame->GetContent(), &multiple);
2416
0
2417
0
  nsCSSFrameConstructor* fc = aFrame->PresContext()->FrameConstructor();
2418
0
  nsContainerFrame* insertionFrame =
2419
0
    insertionPoint
2420
0
      ? fc->GetContentInsertionFrameFor(insertionPoint)
2421
0
      : nullptr;
2422
0
2423
0
  return insertionFrame ? insertionFrame : aFrame;
2424
0
}
2425
2426
nsMenuFrame*
2427
nsXULPopupManager::GetNextMenuItem(nsContainerFrame* aParent,
2428
                                   nsMenuFrame* aStart,
2429
                                   bool aIsPopup,
2430
                                   bool aWrap)
2431
0
{
2432
0
  nsContainerFrame* immediateParent = ImmediateParentFrame(aParent);
2433
0
  nsIFrame* currFrame = nullptr;
2434
0
  if (aStart) {
2435
0
    if (aStart->GetNextSibling())
2436
0
      currFrame = aStart->GetNextSibling();
2437
0
    else if (aStart->GetParent()->GetContent()->IsXULElement(nsGkAtoms::menugroup))
2438
0
      currFrame = aStart->GetParent()->GetNextSibling();
2439
0
  }
2440
0
  else
2441
0
    currFrame = immediateParent->PrincipalChildList().FirstChild();
2442
0
2443
0
  while (currFrame) {
2444
0
    // See if it's a menu item.
2445
0
    nsIContent* currFrameContent = currFrame->GetContent();
2446
0
    if (IsValidMenuItem(currFrameContent, aIsPopup)) {
2447
0
      return do_QueryFrame(currFrame);
2448
0
    }
2449
0
    if (currFrameContent->IsXULElement(nsGkAtoms::menugroup) &&
2450
0
        currFrameContent->GetChildCount() > 0)
2451
0
      currFrame = currFrame->PrincipalChildList().FirstChild();
2452
0
    else if (!currFrame->GetNextSibling() &&
2453
0
             currFrame->GetParent()->GetContent()->IsXULElement(nsGkAtoms::menugroup))
2454
0
      currFrame = currFrame->GetParent()->GetNextSibling();
2455
0
    else
2456
0
      currFrame = currFrame->GetNextSibling();
2457
0
  }
2458
0
2459
0
  if (!aWrap) {
2460
0
    return aStart;
2461
0
  }
2462
0
2463
0
  currFrame = immediateParent->PrincipalChildList().FirstChild();
2464
0
2465
0
  // Still don't have anything. Try cycling from the beginning.
2466
0
  while (currFrame && currFrame != aStart) {
2467
0
    // See if it's a menu item.
2468
0
    nsIContent* currFrameContent = currFrame->GetContent();
2469
0
    if (IsValidMenuItem(currFrameContent, aIsPopup)) {
2470
0
      return do_QueryFrame(currFrame);
2471
0
    }
2472
0
    if (currFrameContent->IsXULElement(nsGkAtoms::menugroup) &&
2473
0
        currFrameContent->GetChildCount() > 0)
2474
0
      currFrame = currFrame->PrincipalChildList().FirstChild();
2475
0
    else if (!currFrame->GetNextSibling() &&
2476
0
             currFrame->GetParent()->GetContent()->IsXULElement(nsGkAtoms::menugroup))
2477
0
      currFrame = currFrame->GetParent()->GetNextSibling();
2478
0
    else
2479
0
      currFrame = currFrame->GetNextSibling();
2480
0
  }
2481
0
2482
0
  // No luck. Just return our start value.
2483
0
  return aStart;
2484
0
}
2485
2486
nsMenuFrame*
2487
nsXULPopupManager::GetPreviousMenuItem(nsContainerFrame* aParent,
2488
                                       nsMenuFrame* aStart,
2489
                                       bool aIsPopup,
2490
                                       bool aWrap)
2491
0
{
2492
0
  nsContainerFrame* immediateParent = ImmediateParentFrame(aParent);
2493
0
  const nsFrameList& frames(immediateParent->PrincipalChildList());
2494
0
2495
0
  nsIFrame* currFrame = nullptr;
2496
0
  if (aStart) {
2497
0
    if (aStart->GetPrevSibling())
2498
0
      currFrame = aStart->GetPrevSibling();
2499
0
    else if (aStart->GetParent()->GetContent()->IsXULElement(nsGkAtoms::menugroup))
2500
0
      currFrame = aStart->GetParent()->GetPrevSibling();
2501
0
  }
2502
0
  else
2503
0
    currFrame = frames.LastChild();
2504
0
2505
0
  while (currFrame) {
2506
0
    // See if it's a menu item.
2507
0
    nsIContent* currFrameContent = currFrame->GetContent();
2508
0
    if (IsValidMenuItem(currFrameContent, aIsPopup)) {
2509
0
      return do_QueryFrame(currFrame);
2510
0
    }
2511
0
    if (currFrameContent->IsXULElement(nsGkAtoms::menugroup) &&
2512
0
        currFrameContent->GetChildCount() > 0) {
2513
0
      const nsFrameList& menugroupFrames(currFrame->PrincipalChildList());
2514
0
      currFrame = menugroupFrames.LastChild();
2515
0
    }
2516
0
    else if (!currFrame->GetPrevSibling() &&
2517
0
             currFrame->GetParent()->GetContent()->IsXULElement(nsGkAtoms::menugroup))
2518
0
      currFrame = currFrame->GetParent()->GetPrevSibling();
2519
0
    else
2520
0
      currFrame = currFrame->GetPrevSibling();
2521
0
  }
2522
0
2523
0
  if (!aWrap) {
2524
0
    return aStart;
2525
0
  }
2526
0
2527
0
  currFrame = frames.LastChild();
2528
0
2529
0
  // Still don't have anything. Try cycling from the end.
2530
0
  while (currFrame && currFrame != aStart) {
2531
0
    // See if it's a menu item.
2532
0
    nsIContent* currFrameContent = currFrame->GetContent();
2533
0
    if (IsValidMenuItem(currFrameContent, aIsPopup)) {
2534
0
      return do_QueryFrame(currFrame);
2535
0
    }
2536
0
    if (currFrameContent->IsXULElement(nsGkAtoms::menugroup) &&
2537
0
        currFrameContent->GetChildCount() > 0) {
2538
0
      const nsFrameList& menugroupFrames(currFrame->PrincipalChildList());
2539
0
      currFrame = menugroupFrames.LastChild();
2540
0
    }
2541
0
    else if (!currFrame->GetPrevSibling() &&
2542
0
             currFrame->GetParent()->GetContent()->IsXULElement(nsGkAtoms::menugroup))
2543
0
      currFrame = currFrame->GetParent()->GetPrevSibling();
2544
0
    else
2545
0
      currFrame = currFrame->GetPrevSibling();
2546
0
  }
2547
0
2548
0
  // No luck. Just return our start value.
2549
0
  return aStart;
2550
0
}
2551
2552
bool
2553
nsXULPopupManager::IsValidMenuItem(nsIContent* aContent, bool aOnPopup)
2554
0
{
2555
0
  if (!aContent->IsAnyOfXULElements(nsGkAtoms::menu, nsGkAtoms::menuitem)) {
2556
0
    return false;
2557
0
  }
2558
0
2559
0
  nsMenuFrame* menuFrame = do_QueryFrame(aContent->GetPrimaryFrame());
2560
0
2561
0
  bool skipNavigatingDisabledMenuItem = true;
2562
0
  if (aOnPopup && (!menuFrame || menuFrame->GetParentMenuListType() == eNotMenuList)) {
2563
0
    skipNavigatingDisabledMenuItem =
2564
0
      LookAndFeel::GetInt(LookAndFeel::eIntID_SkipNavigatingDisabledMenuItem,
2565
0
                          0) != 0;
2566
0
  }
2567
0
2568
0
  return !(skipNavigatingDisabledMenuItem &&
2569
0
    aContent->IsElement() &&
2570
0
    aContent->AsElement()->AttrValueIs(kNameSpaceID_None, nsGkAtoms::disabled,
2571
0
                                       nsGkAtoms::_true, eCaseMatters));
2572
0
}
2573
2574
nsresult
2575
nsXULPopupManager::HandleEvent(Event* aEvent)
2576
0
{
2577
0
  RefPtr<KeyboardEvent> keyEvent = aEvent->AsKeyboardEvent();
2578
0
  NS_ENSURE_TRUE(keyEvent, NS_ERROR_UNEXPECTED);
2579
0
2580
0
  //handlers shouldn't be triggered by non-trusted events.
2581
0
  if (!keyEvent->IsTrusted()) {
2582
0
    return NS_OK;
2583
0
  }
2584
0
2585
0
  nsAutoString eventType;
2586
0
  keyEvent->GetType(eventType);
2587
0
  if (eventType.EqualsLiteral("keyup")) {
2588
0
    return KeyUp(keyEvent);
2589
0
  }
2590
0
  if (eventType.EqualsLiteral("keydown")) {
2591
0
    return KeyDown(keyEvent);
2592
0
  }
2593
0
  if (eventType.EqualsLiteral("keypress")) {
2594
0
    return KeyPress(keyEvent);
2595
0
  }
2596
0
2597
0
  MOZ_ASSERT_UNREACHABLE("Unexpected eventType");
2598
0
  return NS_OK;
2599
0
}
2600
2601
nsresult
2602
nsXULPopupManager::UpdateIgnoreKeys(bool aIgnoreKeys)
2603
0
{
2604
0
  nsMenuChainItem* item = GetTopVisibleMenu();
2605
0
  if (item) {
2606
0
    item->SetIgnoreKeys(aIgnoreKeys ? eIgnoreKeys_True : eIgnoreKeys_Shortcuts);
2607
0
  }
2608
0
  UpdateKeyboardListeners();
2609
0
  return NS_OK;
2610
0
}
2611
2612
nsresult
2613
nsXULPopupManager::KeyUp(KeyboardEvent* aKeyEvent)
2614
0
{
2615
0
  // don't do anything if a menu isn't open or a menubar isn't active
2616
0
  if (!mActiveMenuBar) {
2617
0
    nsMenuChainItem* item = GetTopVisibleMenu();
2618
0
    if (!item || item->PopupType() != ePopupTypeMenu)
2619
0
      return NS_OK;
2620
0
2621
0
    if (item->IgnoreKeys() == eIgnoreKeys_Shortcuts) {
2622
0
      aKeyEvent->StopCrossProcessForwarding();
2623
0
      return NS_OK;
2624
0
    }
2625
0
  }
2626
0
2627
0
  aKeyEvent->StopPropagation();
2628
0
  aKeyEvent->StopCrossProcessForwarding();
2629
0
  aKeyEvent->PreventDefault();
2630
0
2631
0
  return NS_OK; // I am consuming event
2632
0
}
2633
2634
nsresult
2635
nsXULPopupManager::KeyDown(KeyboardEvent* aKeyEvent)
2636
0
{
2637
0
  nsMenuChainItem* item = GetTopVisibleMenu();
2638
0
  if (item && item->Frame()->IsMenuLocked())
2639
0
    return NS_OK;
2640
0
2641
0
  if (HandleKeyboardEventWithKeyCode(aKeyEvent, item)) {
2642
0
    return NS_OK;
2643
0
  }
2644
0
2645
0
  // don't do anything if a menu isn't open or a menubar isn't active
2646
0
  if (!mActiveMenuBar && (!item || item->PopupType() != ePopupTypeMenu))
2647
0
    return NS_OK;
2648
0
2649
0
  // Since a menu was open, stop propagation of the event to keep other event
2650
0
  // listeners from becoming confused.
2651
0
  if (!item || item->IgnoreKeys() != eIgnoreKeys_Shortcuts) {
2652
0
    aKeyEvent->StopPropagation();
2653
0
  }
2654
0
2655
0
  int32_t menuAccessKey = -1;
2656
0
2657
0
  // If the key just pressed is the access key (usually Alt),
2658
0
  // dismiss and unfocus the menu.
2659
0
2660
0
  nsMenuBarListener::GetMenuAccessKey(&menuAccessKey);
2661
0
  if (menuAccessKey) {
2662
0
    uint32_t theChar = aKeyEvent->KeyCode();
2663
0
2664
0
    if (theChar == (uint32_t)menuAccessKey) {
2665
0
      bool ctrl = (menuAccessKey != KeyboardEvent_Binding::DOM_VK_CONTROL &&
2666
0
                   aKeyEvent->CtrlKey());
2667
0
      bool alt = (menuAccessKey != KeyboardEvent_Binding::DOM_VK_ALT &&
2668
0
                  aKeyEvent->AltKey());
2669
0
      bool shift = (menuAccessKey != KeyboardEvent_Binding::DOM_VK_SHIFT &&
2670
0
                    aKeyEvent->ShiftKey());
2671
0
      bool meta = (menuAccessKey != KeyboardEvent_Binding::DOM_VK_META &&
2672
0
                   aKeyEvent->MetaKey());
2673
0
      if (!(ctrl || alt || shift || meta)) {
2674
0
        // The access key just went down and no other
2675
0
        // modifiers are already down.
2676
0
        nsMenuChainItem* item = GetTopVisibleMenu();
2677
0
        if (item && !item->Frame()->IsMenuList()) {
2678
0
          Rollup(0, false, nullptr, nullptr);
2679
0
        } else if (mActiveMenuBar) {
2680
0
          mActiveMenuBar->MenuClosed();
2681
0
        }
2682
0
2683
0
        // Clear the item to avoid bugs as it may have been deleted during rollup.
2684
0
        item = nullptr;
2685
0
      }
2686
0
      aKeyEvent->StopPropagation();
2687
0
      aKeyEvent->PreventDefault();
2688
0
    }
2689
0
  }
2690
0
2691
0
  aKeyEvent->StopCrossProcessForwarding();
2692
0
  return NS_OK;
2693
0
}
2694
2695
nsresult
2696
nsXULPopupManager::KeyPress(KeyboardEvent* aKeyEvent)
2697
0
{
2698
0
  // Don't check prevent default flag -- menus always get first shot at key events.
2699
0
2700
0
  nsMenuChainItem* item = GetTopVisibleMenu();
2701
0
  if (item &&
2702
0
      (item->Frame()->IsMenuLocked() || item->PopupType() != ePopupTypeMenu)) {
2703
0
    return NS_OK;
2704
0
  }
2705
0
2706
0
  // if a menu is open or a menubar is active, it consumes the key event
2707
0
  bool consume = (item || mActiveMenuBar);
2708
0
2709
0
  WidgetInputEvent* evt = aKeyEvent->WidgetEventPtr()->AsInputEvent();
2710
0
  bool isAccel = evt && evt->IsAccel();
2711
0
2712
0
  // When ignorekeys="shortcuts" is used, we don't call preventDefault on the
2713
0
  // key event when the accelerator key is pressed. This allows another
2714
0
  // listener to handle keys. For instance, this allows global shortcuts to
2715
0
  // still apply while a menu is open.
2716
0
  if (item && item->IgnoreKeys() == eIgnoreKeys_Shortcuts && isAccel) {
2717
0
    consume = false;
2718
0
  }
2719
0
2720
0
  HandleShortcutNavigation(aKeyEvent, nullptr);
2721
0
2722
0
  aKeyEvent->StopCrossProcessForwarding();
2723
0
  if (consume) {
2724
0
    aKeyEvent->StopPropagation();
2725
0
    aKeyEvent->PreventDefault();
2726
0
  }
2727
0
2728
0
  return NS_OK; // I am consuming event
2729
0
}
2730
2731
NS_IMETHODIMP
2732
nsXULPopupShowingEvent::Run()
2733
0
{
2734
0
  nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
2735
0
  if (pm) {
2736
0
    pm->FirePopupShowingEvent(mPopup, mIsContextMenu, mSelectFirstItem, nullptr);
2737
0
  }
2738
0
2739
0
  return NS_OK;
2740
0
}
2741
2742
NS_IMETHODIMP
2743
nsXULPopupHidingEvent::Run()
2744
0
{
2745
0
  nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
2746
0
2747
0
  nsIDocument *document = mPopup->GetUncomposedDoc();
2748
0
  if (pm && document) {
2749
0
    nsPresContext* context = document->GetPresContext();
2750
0
    if (context) {
2751
0
      pm->FirePopupHidingEvent(mPopup, mNextPopup, mLastPopup,
2752
0
                               context, mPopupType, mDeselectMenu, mIsRollup);
2753
0
    }
2754
0
  }
2755
0
2756
0
  return NS_OK;
2757
0
}
2758
2759
bool
2760
nsXULPopupPositionedEvent::DispatchIfNeeded(nsIContent *aPopup,
2761
                                            bool aIsContextMenu,
2762
                                            bool aSelectFirstItem)
2763
0
{
2764
0
  // The popuppositioned event only fires on arrow panels for now.
2765
0
  if (aPopup->IsElement() &&
2766
0
      aPopup->AsElement()->AttrValueIs(kNameSpaceID_None, nsGkAtoms::type,
2767
0
                                       nsGkAtoms::arrow, eCaseMatters)) {
2768
0
    nsCOMPtr<nsIRunnable> event =
2769
0
      new nsXULPopupPositionedEvent(aPopup, aIsContextMenu, aSelectFirstItem);
2770
0
    aPopup->OwnerDoc()->Dispatch(TaskCategory::Other, event.forget());
2771
0
2772
0
    return true;
2773
0
  }
2774
0
2775
0
  return false;
2776
0
}
2777
2778
NS_IMETHODIMP
2779
nsXULPopupPositionedEvent::Run()
2780
0
{
2781
0
  nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
2782
0
  if (pm) {
2783
0
    nsMenuPopupFrame* popupFrame = do_QueryFrame(mPopup->GetPrimaryFrame());
2784
0
    if (popupFrame) {
2785
0
      // At this point, hidePopup may have been called but it currently has no
2786
0
      // way to stop this event. However, if hidePopup was called, the popup
2787
0
      // will now be in the hiding or closed state. If we are in the shown or
2788
0
      // positioning state instead, we can assume that we are still clear to
2789
0
      // open/move the popup
2790
0
      nsPopupState state = popupFrame->PopupState();
2791
0
      if (state != ePopupPositioning && state != ePopupShown) {
2792
0
        return NS_OK;
2793
0
      }
2794
0
      nsEventStatus status = nsEventStatus_eIgnore;
2795
0
      WidgetMouseEvent event(true, eXULPopupPositioned, nullptr,
2796
0
                             WidgetMouseEvent::eReal);
2797
0
      EventDispatcher::Dispatch(mPopup, popupFrame->PresContext(),
2798
0
                                &event, nullptr, &status);
2799
0
2800
0
      // Get the popup frame and make sure it is still in the positioning
2801
0
      // state. If it isn't, someone may have tried to reshow or hide it
2802
0
      // during the popuppositioned event.
2803
0
      // Alternately, this event may have been fired in reponse to moving the
2804
0
      // popup rather than opening it. In that case, we are done.
2805
0
      nsMenuPopupFrame* popupFrame = do_QueryFrame(mPopup->GetPrimaryFrame());
2806
0
      if (popupFrame && popupFrame->PopupState() == ePopupPositioning) {
2807
0
        pm->ShowPopupCallback(mPopup, popupFrame, mIsContextMenu, mSelectFirstItem);
2808
0
      }
2809
0
    }
2810
0
  }
2811
0
2812
0
  return NS_OK;
2813
0
}
2814
2815
NS_IMETHODIMP
2816
nsXULMenuCommandEvent::Run()
2817
0
{
2818
0
  nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
2819
0
  if (!pm)
2820
0
    return NS_OK;
2821
0
2822
0
  // The order of the nsViewManager and nsIPresShell COM pointers is
2823
0
  // important below.  We want the pres shell to get released before the
2824
0
  // associated view manager on exit from this function.
2825
0
  // See bug 54233.
2826
0
  // XXXndeakin is this still needed?
2827
0
2828
0
  nsCOMPtr<nsIContent> popup;
2829
0
  nsMenuFrame* menuFrame = do_QueryFrame(mMenu->GetPrimaryFrame());
2830
0
  AutoWeakFrame weakFrame(menuFrame);
2831
0
  if (menuFrame && mFlipChecked) {
2832
0
    if (menuFrame->IsChecked()) {
2833
0
      mMenu->UnsetAttr(kNameSpaceID_None, nsGkAtoms::checked, true);
2834
0
    } else {
2835
0
      mMenu->SetAttr(kNameSpaceID_None, nsGkAtoms::checked,
2836
0
                     NS_LITERAL_STRING("true"), true);
2837
0
    }
2838
0
  }
2839
0
2840
0
  if (menuFrame && weakFrame.IsAlive()) {
2841
0
    // Find the popup that the menu is inside. Below, this popup will
2842
0
    // need to be hidden.
2843
0
    nsIFrame* frame = menuFrame->GetParent();
2844
0
    while (frame) {
2845
0
      nsMenuPopupFrame* popupFrame = do_QueryFrame(frame);
2846
0
      if (popupFrame) {
2847
0
        popup = popupFrame->GetContent();
2848
0
        break;
2849
0
      }
2850
0
      frame = frame->GetParent();
2851
0
    }
2852
0
2853
0
    nsPresContext* presContext = menuFrame->PresContext();
2854
0
    nsCOMPtr<nsIPresShell> shell = presContext->PresShell();
2855
0
    RefPtr<nsViewManager> kungFuDeathGrip = shell->GetViewManager();
2856
0
    mozilla::Unused << kungFuDeathGrip; // Not referred to directly within this function
2857
0
2858
0
    // Deselect ourselves.
2859
0
    if (mCloseMenuMode != CloseMenuMode_None)
2860
0
      menuFrame->SelectMenu(false);
2861
0
2862
0
    AutoHandlingUserInputStatePusher userInpStatePusher(mUserInput, nullptr,
2863
0
                                                        shell->GetDocument());
2864
0
    nsContentUtils::DispatchXULCommand(mMenu, mIsTrusted, nullptr, shell,
2865
0
                                       mControl, mAlt, mShift, mMeta);
2866
0
  }
2867
0
2868
0
  if (popup && mCloseMenuMode != CloseMenuMode_None)
2869
0
    pm->HidePopup(popup, mCloseMenuMode == CloseMenuMode_Auto, true, false, false);
2870
0
2871
0
  return NS_OK;
2872
0
}