Coverage Report

Created: 2018-09-25 14:53

/src/mozilla-central/layout/xul/nsMenuPopupFrame.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 "nsMenuPopupFrame.h"
8
#include "nsGkAtoms.h"
9
#include "nsIContent.h"
10
#include "nsAtom.h"
11
#include "nsPresContext.h"
12
#include "mozilla/ComputedStyle.h"
13
#include "nsCSSRendering.h"
14
#include "nsNameSpaceManager.h"
15
#include "nsViewManager.h"
16
#include "nsWidgetsCID.h"
17
#include "nsMenuFrame.h"
18
#include "nsMenuBarFrame.h"
19
#include "nsPopupSetFrame.h"
20
#include "nsPIDOMWindow.h"
21
#include "nsIDOMXULMenuListElement.h"
22
#include "nsIPresShell.h"
23
#include "nsFrameManager.h"
24
#include "nsIDocument.h"
25
#include "nsRect.h"
26
#include "nsIComponentManager.h"
27
#include "nsBoxLayoutState.h"
28
#include "nsIScrollableFrame.h"
29
#include "nsIPopupContainer.h"
30
#include "nsIDocShell.h"
31
#include "nsReadableUtils.h"
32
#include "nsUnicharUtils.h"
33
#include "nsLayoutUtils.h"
34
#include "nsContentUtils.h"
35
#include "nsCSSFrameConstructor.h"
36
#include "nsPIWindowRoot.h"
37
#include "nsIReflowCallback.h"
38
#include "nsBindingManager.h"
39
#include "nsIDocShellTreeOwner.h"
40
#include "nsIBaseWindow.h"
41
#include "nsISound.h"
42
#include "nsIScreenManager.h"
43
#include "nsIServiceManager.h"
44
#include "nsStyleConsts.h"
45
#include "nsTransitionManager.h"
46
#include "nsDisplayList.h"
47
#include "nsIDOMXULSelectCntrlItemEl.h"
48
#include "mozilla/EventDispatcher.h"
49
#include "mozilla/EventStateManager.h"
50
#include "mozilla/EventStates.h"
51
#include "mozilla/Preferences.h"
52
#include "mozilla/LookAndFeel.h"
53
#include "mozilla/MouseEvents.h"
54
#include "mozilla/dom/Element.h"
55
#include "mozilla/dom/Event.h"
56
#include "mozilla/dom/KeyboardEvent.h"
57
#include "mozilla/dom/KeyboardEventBinding.h"
58
#include <algorithm>
59
60
using namespace mozilla;
61
using mozilla::dom::KeyboardEvent;
62
using mozilla::dom::Event;
63
64
int8_t nsMenuPopupFrame::sDefaultLevelIsTop = -1;
65
66
DOMTimeStamp nsMenuPopupFrame::sLastKeyTime = 0;
67
68
// XXX, kyle.yuan@sun.com, there are 4 definitions for the same purpose:
69
//  nsMenuPopupFrame.h, nsListControlFrame.cpp, listbox.xml, tree.xml
70
//  need to find a good place to put them together.
71
//  if someone changes one, please also change the other.
72
uint32_t nsMenuPopupFrame::sTimeoutOfIncrementalSearch = 1000;
73
74
const char kPrefIncrementalSearchTimeout[] =
75
  "ui.menu.incremental_search.timeout";
76
77
// NS_NewMenuPopupFrame
78
//
79
// Wrapper for creating a new menu popup container
80
//
81
nsIFrame*
82
NS_NewMenuPopupFrame(nsIPresShell* aPresShell, ComputedStyle* aStyle)
83
0
{
84
0
  return new (aPresShell) nsMenuPopupFrame(aStyle);
85
0
}
86
87
NS_IMPL_FRAMEARENA_HELPERS(nsMenuPopupFrame)
88
89
0
NS_QUERYFRAME_HEAD(nsMenuPopupFrame)
90
0
  NS_QUERYFRAME_ENTRY(nsMenuPopupFrame)
91
0
NS_QUERYFRAME_TAIL_INHERITING(nsBoxFrame)
92
93
//
94
// nsMenuPopupFrame ctor
95
//
96
nsMenuPopupFrame::nsMenuPopupFrame(ComputedStyle* aStyle)
97
  : nsBoxFrame(aStyle, kClassID)
98
  , mCurrentMenu(nullptr)
99
  , mView(nullptr)
100
  , mPrefSize(-1, -1)
101
  , mXPos(0)
102
  , mYPos(0)
103
  , mAlignmentOffset(0)
104
  , mLastClientOffset(0, 0)
105
  , mPopupType(ePopupTypePanel)
106
  , mPopupState(ePopupClosed)
107
  , mPopupAlignment(POPUPALIGNMENT_NONE)
108
  , mPopupAnchor(POPUPALIGNMENT_NONE)
109
  , mPosition(POPUPPOSITION_UNKNOWN)
110
  , mFlip(FlipType_Default)
111
  , mIsOpenChanged(false)
112
  , mIsContextMenu(false)
113
  , mAdjustOffsetForContextMenu(false)
114
  , mGeneratedChildren(false)
115
  , mMenuCanOverlapOSBar(false)
116
  , mShouldAutoPosition(true)
117
  , mInContentShell(true)
118
  , mIsMenuLocked(false)
119
  , mMouseTransparent(false)
120
  , mIsOffset(false)
121
  , mHFlip(false)
122
  , mVFlip(false)
123
  , mPositionedOffset(0)
124
  , mAnchorType(MenuPopupAnchorType_Node)
125
0
{
126
0
  // the preference name is backwards here. True means that the 'top' level is
127
0
  // the default, and false means that the 'parent' level is the default.
128
0
  if (sDefaultLevelIsTop >= 0)
129
0
    return;
130
0
  sDefaultLevelIsTop =
131
0
    Preferences::GetBool("ui.panel.default_level_parent", false);
132
0
  Preferences::AddUintVarCache(&sTimeoutOfIncrementalSearch,
133
0
                               kPrefIncrementalSearchTimeout, 1000);
134
0
} // ctor
135
136
void
137
nsMenuPopupFrame::Init(nsIContent*       aContent,
138
                       nsContainerFrame* aParent,
139
                       nsIFrame*         aPrevInFlow)
140
0
{
141
0
  nsBoxFrame::Init(aContent, aParent, aPrevInFlow);
142
0
143
0
  // lookup if we're allowed to overlap the OS bar (menubar/taskbar) from the
144
0
  // look&feel object
145
0
  mMenuCanOverlapOSBar =
146
0
    LookAndFeel::GetInt(LookAndFeel::eIntID_MenusCanOverlapOSBar) != 0;
147
0
148
0
  CreatePopupView();
149
0
150
0
  // XXX Hack. The popup's view should float above all other views,
151
0
  // so we use the nsView::SetFloating() to tell the view manager
152
0
  // about that constraint.
153
0
  nsView* ourView = GetView();
154
0
  nsViewManager* viewManager = ourView->GetViewManager();
155
0
  viewManager->SetViewFloating(ourView, true);
156
0
157
0
  mPopupType = ePopupTypePanel;
158
0
  if (aContent->IsAnyOfXULElements(nsGkAtoms::menupopup, nsGkAtoms::popup)) {
159
0
    mPopupType = ePopupTypeMenu;
160
0
  } else if (aContent->IsXULElement(nsGkAtoms::tooltip)) {
161
0
    mPopupType = ePopupTypeTooltip;
162
0
  }
163
0
164
0
  nsCOMPtr<nsIDocShellTreeItem> dsti = PresContext()->GetDocShell();
165
0
  if (dsti && dsti->ItemType() == nsIDocShellTreeItem::typeChrome) {
166
0
    mInContentShell = false;
167
0
  }
168
0
169
0
  // To improve performance, create the widget for the popup only if it is not
170
0
  // a leaf. Leaf popups such as menus will create their widgets later when
171
0
  // the popup opens.
172
0
  if (!IsLeaf() && !ourView->HasWidget()) {
173
0
    CreateWidgetForView(ourView);
174
0
  }
175
0
176
0
  if (aContent->NodeInfo()->Equals(nsGkAtoms::tooltip, kNameSpaceID_XUL) &&
177
0
      aContent->AsElement()->AttrValueIs(kNameSpaceID_None, nsGkAtoms::_default,
178
0
                                         nsGkAtoms::_true, eIgnoreCase)) {
179
0
    nsIPopupContainer* popupContainer =
180
0
      nsIPopupContainer::GetPopupContainer(PresContext()->GetPresShell());
181
0
    if (popupContainer) {
182
0
      popupContainer->SetDefaultTooltip(aContent->AsElement());
183
0
    }
184
0
  }
185
0
186
0
  AddStateBits(NS_FRAME_IN_POPUP);
187
0
}
188
189
bool
190
nsMenuPopupFrame::HasRemoteContent() const
191
0
{
192
0
  return (!mInContentShell && mPopupType == ePopupTypePanel &&
193
0
          mContent->AsElement()->AttrValueIs(kNameSpaceID_None,
194
0
                                             nsGkAtoms::remote,
195
0
                                             nsGkAtoms::_true, eIgnoreCase));
196
0
}
197
198
bool
199
nsMenuPopupFrame::IsNoAutoHide() const
200
0
{
201
0
  // Panels with noautohide="true" don't hide when the mouse is clicked
202
0
  // outside of them, or when another application is made active. Non-autohide
203
0
  // panels cannot be used in content windows.
204
0
  return (!mInContentShell && mPopupType == ePopupTypePanel &&
205
0
           mContent->AsElement()->AttrValueIs(kNameSpaceID_None,
206
0
                                              nsGkAtoms::noautohide,
207
0
                                              nsGkAtoms::_true, eIgnoreCase));
208
0
}
209
210
nsPopupLevel
211
nsMenuPopupFrame::PopupLevel(bool aIsNoAutoHide) const
212
0
{
213
0
  // The popup level is determined as follows, in this order:
214
0
  //   1. non-panels (menus and tooltips) are always topmost
215
0
  //   2. any specified level attribute
216
0
  //   3. if a titlebar attribute is set, use the 'floating' level
217
0
  //   4. if this is a noautohide panel, use the 'parent' level
218
0
  //   5. use the platform-specific default level
219
0
220
0
  // If this is not a panel, this is always a top-most popup.
221
0
  if (mPopupType != ePopupTypePanel)
222
0
    return ePopupLevelTop;
223
0
224
0
  // If the level attribute has been set, use that.
225
0
  static Element::AttrValuesArray strings[] =
226
0
    {&nsGkAtoms::top, &nsGkAtoms::parent, &nsGkAtoms::floating, nullptr};
227
0
  switch (mContent->AsElement()->FindAttrValueIn(kNameSpaceID_None,
228
0
                                                 nsGkAtoms::level, strings,
229
0
                                                 eCaseMatters)) {
230
0
    case 0:
231
0
      return ePopupLevelTop;
232
0
    case 1:
233
0
      return ePopupLevelParent;
234
0
    case 2:
235
0
      return ePopupLevelFloating;
236
0
  }
237
0
238
0
  // Panels with titlebars most likely want to be floating popups.
239
0
  if (mContent->AsElement()->HasAttr(kNameSpaceID_None, nsGkAtoms::titlebar))
240
0
    return ePopupLevelFloating;
241
0
242
0
  // If this panel is a noautohide panel, the default is the parent level.
243
0
  if (aIsNoAutoHide)
244
0
    return ePopupLevelParent;
245
0
246
0
  // Otherwise, the result depends on the platform.
247
0
  return sDefaultLevelIsTop ? ePopupLevelTop : ePopupLevelParent;
248
0
}
249
250
void
251
nsMenuPopupFrame::EnsureWidget(bool aRecreate)
252
0
{
253
0
  nsView* ourView = GetView();
254
0
  if (aRecreate) {
255
0
    ourView->DestroyWidget();
256
0
  }
257
0
  if (!ourView->HasWidget()) {
258
0
    NS_ASSERTION(!mGeneratedChildren && !PrincipalChildList().FirstChild(),
259
0
                 "Creating widget for MenuPopupFrame with children");
260
0
    CreateWidgetForView(ourView);
261
0
  }
262
0
}
263
264
nsresult
265
nsMenuPopupFrame::CreateWidgetForView(nsView* aView)
266
0
{
267
0
  // Create a widget for ourselves.
268
0
  nsWidgetInitData widgetData;
269
0
  widgetData.mWindowType = eWindowType_popup;
270
0
  widgetData.mBorderStyle = eBorderStyle_default;
271
0
  widgetData.clipSiblings = true;
272
0
  widgetData.mPopupHint = mPopupType;
273
0
  widgetData.mNoAutoHide = IsNoAutoHide();
274
0
275
0
  if (!mInContentShell) {
276
0
    // A drag popup may be used for non-static translucent drag feedback
277
0
    if (mPopupType == ePopupTypePanel &&
278
0
        mContent->AsElement()->AttrValueIs(kNameSpaceID_None, nsGkAtoms::type,
279
0
                                           nsGkAtoms::drag, eIgnoreCase)) {
280
0
      widgetData.mIsDragPopup = true;
281
0
    }
282
0
283
0
    // If mousethrough="always" is set directly on the popup, then the widget
284
0
    // should ignore mouse events, passing them through to the content behind.
285
0
    mMouseTransparent = GetStateBits() & NS_FRAME_MOUSE_THROUGH_ALWAYS;
286
0
    widgetData.mMouseTransparent = mMouseTransparent;
287
0
  }
288
0
289
0
  nsAutoString title;
290
0
  if (widgetData.mNoAutoHide) {
291
0
    if (mContent->AsElement()->AttrValueIs(kNameSpaceID_None,
292
0
                                           nsGkAtoms::titlebar,
293
0
                                           nsGkAtoms::normal, eCaseMatters)) {
294
0
      widgetData.mBorderStyle = eBorderStyle_title;
295
0
296
0
      mContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::label, title);
297
0
298
0
      if (mContent->AsElement()->AttrValueIs(kNameSpaceID_None, nsGkAtoms::close,
299
0
                                             nsGkAtoms::_true, eCaseMatters)) {
300
0
        widgetData.mBorderStyle =
301
0
          static_cast<enum nsBorderStyle>(widgetData.mBorderStyle | eBorderStyle_close);
302
0
      }
303
0
    }
304
0
  }
305
0
306
0
  bool remote = HasRemoteContent();
307
0
308
0
  nsTransparencyMode mode = nsLayoutUtils::GetFrameTransparency(this, this);
309
0
  nsIContent* parentContent = GetContent()->GetParent();
310
0
  nsAtom *tag = nullptr;
311
0
  if (parentContent && parentContent->IsXULElement())
312
0
    tag = parentContent->NodeInfo()->NameAtom();
313
0
  widgetData.mHasRemoteContent = remote;
314
0
  widgetData.mSupportTranslucency = mode == eTransparencyTransparent;
315
0
  widgetData.mDropShadow = !(mode == eTransparencyTransparent || tag == nsGkAtoms::menulist);
316
0
  widgetData.mPopupLevel = PopupLevel(widgetData.mNoAutoHide);
317
0
318
0
  // panels which have a parent level need a parent widget. This allows them to
319
0
  // always appear in front of the parent window but behind other windows that
320
0
  // should be in front of it.
321
0
  nsCOMPtr<nsIWidget> parentWidget;
322
0
  if (widgetData.mPopupLevel != ePopupLevelTop) {
323
0
    nsCOMPtr<nsIDocShellTreeItem> dsti = PresContext()->GetDocShell();
324
0
    if (!dsti)
325
0
      return NS_ERROR_FAILURE;
326
0
327
0
    nsCOMPtr<nsIDocShellTreeOwner> treeOwner;
328
0
    dsti->GetTreeOwner(getter_AddRefs(treeOwner));
329
0
    if (!treeOwner) return NS_ERROR_FAILURE;
330
0
331
0
    nsCOMPtr<nsIBaseWindow> baseWindow(do_QueryInterface(treeOwner));
332
0
    if (baseWindow)
333
0
      baseWindow->GetMainWidget(getter_AddRefs(parentWidget));
334
0
  }
335
0
336
0
  nsresult rv = aView->CreateWidgetForPopup(&widgetData, parentWidget,
337
0
                                            true, true);
338
0
  if (NS_FAILED(rv)) {
339
0
    return rv;
340
0
  }
341
0
342
0
  nsIWidget* widget = aView->GetWidget();
343
0
  widget->SetTransparencyMode(mode);
344
0
  widget->SetWindowShadowStyle(GetShadowStyle());
345
0
346
0
  // most popups don't have a title so avoid setting the title if there isn't one
347
0
  if (!title.IsEmpty()) {
348
0
    widget->SetTitle(title);
349
0
  }
350
0
351
0
  return NS_OK;
352
0
}
353
354
uint8_t
355
nsMenuPopupFrame::GetShadowStyle()
356
0
{
357
0
  uint8_t shadow = StyleUIReset()->mWindowShadow;
358
0
  if (shadow != NS_STYLE_WINDOW_SHADOW_DEFAULT)
359
0
    return shadow;
360
0
361
0
  switch (StyleDisplay()->mAppearance) {
362
0
    case StyleAppearance::Tooltip:
363
0
      return NS_STYLE_WINDOW_SHADOW_TOOLTIP;
364
0
    case StyleAppearance::Menupopup:
365
0
      return NS_STYLE_WINDOW_SHADOW_MENU;
366
0
    default:
367
0
      return NS_STYLE_WINDOW_SHADOW_DEFAULT;
368
0
  }
369
0
}
370
371
NS_IMETHODIMP nsXULPopupShownEvent::Run()
372
0
{
373
0
  nsMenuPopupFrame* popup = do_QueryFrame(mPopup->GetPrimaryFrame());
374
0
  // Set the state to visible if the popup is still open.
375
0
  if (popup && popup->IsOpen()) {
376
0
    popup->SetPopupState(ePopupShown);
377
0
  }
378
0
379
0
  WidgetMouseEvent event(true, eXULPopupShown, nullptr,
380
0
                         WidgetMouseEvent::eReal);
381
0
  return EventDispatcher::Dispatch(mPopup, mPresContext, &event);
382
0
}
383
384
NS_IMETHODIMP nsXULPopupShownEvent::HandleEvent(Event* aEvent)
385
0
{
386
0
  nsMenuPopupFrame* popup = do_QueryFrame(mPopup->GetPrimaryFrame());
387
0
  // Ignore events not targeted at the popup itself (ie targeted at
388
0
  // descendants):
389
0
  if (mPopup != aEvent->GetTarget()) {
390
0
    return NS_OK;
391
0
  }
392
0
  if (popup) {
393
0
    // ResetPopupShownDispatcher will delete the reference to this, so keep
394
0
    // another one until Run is finished.
395
0
    RefPtr<nsXULPopupShownEvent> event = this;
396
0
    // Only call Run if it the dispatcher was assigned. This avoids calling the
397
0
    // Run method if the transitionend event fires multiple times.
398
0
    if (popup->ClearPopupShownDispatcher()) {
399
0
      return Run();
400
0
    }
401
0
  }
402
0
403
0
  CancelListener();
404
0
  return NS_OK;
405
0
}
406
407
void nsXULPopupShownEvent::CancelListener()
408
0
{
409
0
  mPopup->RemoveSystemEventListener(NS_LITERAL_STRING("transitionend"), this, false);
410
0
}
411
412
NS_IMPL_ISUPPORTS_INHERITED(nsXULPopupShownEvent, Runnable, nsIDOMEventListener);
413
414
void
415
nsMenuPopupFrame::SetInitialChildList(ChildListID  aListID,
416
                                      nsFrameList& aChildList)
417
0
{
418
0
  // unless the list is empty, indicate that children have been generated.
419
0
  if (aListID == kPrincipalList && aChildList.NotEmpty()) {
420
0
    mGeneratedChildren = true;
421
0
  }
422
0
  nsBoxFrame::SetInitialChildList(aListID, aChildList);
423
0
}
424
425
bool
426
nsMenuPopupFrame::IsLeafDynamic() const
427
0
{
428
0
  if (mGeneratedChildren)
429
0
    return false;
430
0
431
0
  if (mPopupType != ePopupTypeMenu) {
432
0
    // any panel with a type attribute, such as the autocomplete popup,
433
0
    // is always generated right away.
434
0
    return !mContent->AsElement()->HasAttr(kNameSpaceID_None, nsGkAtoms::type);
435
0
  }
436
0
437
0
  // menu popups generate their child frames lazily only when opened, so
438
0
  // behave like a leaf frame. However, generate child frames normally if
439
0
  // the parent menu has a sizetopopup attribute. In this case the size of
440
0
  // the parent menu is dependent on the size of the popup, so the frames
441
0
  // need to exist in order to calculate this size.
442
0
  nsIContent* parentContent = mContent->GetParent();
443
0
  return parentContent &&
444
0
         (!parentContent->IsElement() ||
445
0
          !parentContent->AsElement()->HasAttr(kNameSpaceID_None,
446
0
                                               nsGkAtoms::sizetopopup));
447
0
}
448
449
void
450
nsMenuPopupFrame::UpdateWidgetProperties()
451
0
{
452
0
  if (nsIWidget* widget = GetWidget()) {
453
0
    widget->SetWindowOpacity(StyleUIReset()->mWindowOpacity);
454
0
    widget->SetWindowTransform(ComputeWidgetTransform());
455
0
  }
456
0
}
457
458
void
459
nsMenuPopupFrame::LayoutPopup(nsBoxLayoutState& aState, nsIFrame* aParentMenu,
460
                              nsIFrame* aAnchor, bool aSizedToPopup)
461
0
{
462
0
  if (!mGeneratedChildren)
463
0
    return;
464
0
465
0
  SchedulePaint();
466
0
467
0
  bool shouldPosition = true;
468
0
  bool isOpen = IsOpen();
469
0
  if (!isOpen) {
470
0
    // if the popup is not open, only do layout while showing or if the menu
471
0
    // is sized to the popup
472
0
    shouldPosition = (mPopupState == ePopupShowing || mPopupState == ePopupPositioning);
473
0
    if (!shouldPosition && !aSizedToPopup) {
474
0
      RemoveStateBits(NS_FRAME_FIRST_REFLOW);
475
0
      return;
476
0
    }
477
0
  }
478
0
479
0
  // if the popup has just been opened, make sure the scrolled window is at 0,0
480
0
  // Don't scroll menulists as they will scroll to their selected item on their own.
481
0
  if (mIsOpenChanged && !IsMenuList()) {
482
0
    nsIScrollableFrame *scrollframe = do_QueryFrame(nsBox::GetChildXULBox(this));
483
0
    if (scrollframe) {
484
0
      AutoWeakFrame weakFrame(this);
485
0
      scrollframe->ScrollTo(nsPoint(0,0), nsIScrollableFrame::INSTANT);
486
0
      if (!weakFrame.IsAlive()) {
487
0
        return;
488
0
      }
489
0
    }
490
0
  }
491
0
492
0
  // get the preferred, minimum and maximum size. If the menu is sized to the
493
0
  // popup, then the popup's width is the menu's width.
494
0
  nsSize prefSize = GetXULPrefSize(aState);
495
0
  nsSize minSize = GetXULMinSize(aState);
496
0
  nsSize maxSize = GetXULMaxSize(aState);
497
0
498
0
  if (aSizedToPopup) {
499
0
    prefSize.width = aParentMenu->GetRect().width;
500
0
  }
501
0
  prefSize = BoundsCheck(minSize, prefSize, maxSize);
502
0
503
0
  // if the size changed then set the bounds to be the preferred size
504
0
  bool sizeChanged = (mPrefSize != prefSize);
505
0
  if (sizeChanged) {
506
0
    SetXULBounds(aState, nsRect(0, 0, prefSize.width, prefSize.height), false);
507
0
    mPrefSize = prefSize;
508
0
  }
509
0
510
0
  bool needCallback = false;
511
0
  if (shouldPosition) {
512
0
    SetPopupPosition(aAnchor, false, aSizedToPopup, mPopupState == ePopupPositioning);
513
0
    needCallback = true;
514
0
  }
515
0
516
0
  nsRect bounds(GetRect());
517
0
  XULLayout(aState);
518
0
519
0
  // if the width or height changed, readjust the popup position. This is a
520
0
  // special case for tooltips where the preferred height doesn't include the
521
0
  // real height for its inline element, but does once it is laid out.
522
0
  // This is bug 228673 which doesn't have a simple fix.
523
0
  bool rePosition = shouldPosition && (mPosition == POPUPPOSITION_SELECTION);
524
0
  if (!aParentMenu) {
525
0
    nsSize newsize = GetSize();
526
0
    if (newsize.width > bounds.width || newsize.height > bounds.height) {
527
0
      // the size after layout was larger than the preferred size,
528
0
      // so set the preferred size accordingly
529
0
      mPrefSize = newsize;
530
0
      if (isOpen) {
531
0
        rePosition = true;
532
0
        needCallback = true;
533
0
      }
534
0
    }
535
0
  }
536
0
537
0
  if (rePosition) {
538
0
    SetPopupPosition(aAnchor, false, aSizedToPopup, false);
539
0
  }
540
0
541
0
  nsPresContext* pc = PresContext();
542
0
  nsView* view = GetView();
543
0
544
0
  if (sizeChanged) {
545
0
    // If the size of the popup changed, apply any size constraints.
546
0
    nsIWidget* widget = view->GetWidget();
547
0
    if (widget) {
548
0
      SetSizeConstraints(pc, widget, minSize, maxSize);
549
0
    }
550
0
  }
551
0
552
0
  if (isOpen) {
553
0
    nsViewManager* viewManager = view->GetViewManager();
554
0
    nsRect rect = GetRect();
555
0
    rect.x = rect.y = 0;
556
0
    rect.SizeTo(BoundsCheck(minSize, rect.Size(), maxSize));
557
0
    viewManager->ResizeView(view, rect);
558
0
559
0
    if (mPopupState == ePopupOpening) {
560
0
      mPopupState = ePopupVisible;
561
0
    }
562
0
563
0
    viewManager->SetViewVisibility(view, nsViewVisibility_kShow);
564
0
    SyncFrameViewProperties(view);
565
0
  }
566
0
567
0
  // finally, if the popup just opened, send a popupshown event
568
0
  bool openChanged = mIsOpenChanged;
569
0
  if (openChanged) {
570
0
    mIsOpenChanged = false;
571
0
572
0
    // Make sure the current selection in a menulist is visible.
573
0
    if (IsMenuList() && mCurrentMenu) {
574
0
      EnsureMenuItemIsVisible(mCurrentMenu);
575
0
    }
576
0
577
#ifndef MOZ_WIDGET_GTK
578
    // If the animate attribute is set to open, check for a transition and wait
579
    // for it to finish before firing the popupshown event.
580
    if (mContent->AsElement()->AttrValueIs(kNameSpaceID_None, nsGkAtoms::animate,
581
                                           nsGkAtoms::open, eCaseMatters) &&
582
        nsLayoutUtils::HasCurrentTransitions(this)) {
583
      mPopupShownDispatcher = new nsXULPopupShownEvent(mContent, pc);
584
      mContent->AddSystemEventListener(NS_LITERAL_STRING("transitionend"),
585
                                       mPopupShownDispatcher, false, false);
586
      return;
587
    }
588
#endif
589
590
0
    // If there are no transitions, fire the popupshown event right away.
591
0
    nsCOMPtr<nsIRunnable> event = new nsXULPopupShownEvent(GetContent(), pc);
592
0
    mContent->OwnerDoc()->Dispatch(TaskCategory::Other,
593
0
                                   event.forget());
594
0
  }
595
0
596
0
  if (needCallback && !mReflowCallbackData.mPosted) {
597
0
    pc->PresShell()->PostReflowCallback(this);
598
0
    mReflowCallbackData.MarkPosted(aAnchor, aSizedToPopup, openChanged);
599
0
  }
600
0
}
601
602
bool
603
nsMenuPopupFrame::ReflowFinished()
604
0
{
605
0
  SetPopupPosition(mReflowCallbackData.mAnchor, false, mReflowCallbackData.mSizedToPopup, true);
606
0
607
0
  mReflowCallbackData.Clear();
608
0
609
0
  return false;
610
0
}
611
612
void
613
nsMenuPopupFrame::ReflowCallbackCanceled()
614
0
{
615
0
  mReflowCallbackData.Clear();
616
0
}
617
618
bool
619
nsMenuPopupFrame::IsMenuList()
620
0
{
621
0
  nsIFrame* parentMenu = GetParent();
622
0
  if (!parentMenu) {
623
0
    return false;
624
0
  }
625
0
626
0
  nsCOMPtr<nsIDOMXULMenuListElement> menulist = do_QueryInterface(parentMenu->GetContent());
627
0
  return menulist != nullptr;
628
0
}
629
630
nsIContent*
631
nsMenuPopupFrame::GetTriggerContent(nsMenuPopupFrame* aMenuPopupFrame)
632
0
{
633
0
  while (aMenuPopupFrame) {
634
0
    if (aMenuPopupFrame->mTriggerContent)
635
0
      return aMenuPopupFrame->mTriggerContent;
636
0
637
0
    // check up the menu hierarchy until a popup with a trigger node is found
638
0
    nsMenuFrame* menuFrame = do_QueryFrame(aMenuPopupFrame->GetParent());
639
0
    if (!menuFrame)
640
0
      break;
641
0
642
0
    nsMenuParent* parentPopup = menuFrame->GetMenuParent();
643
0
    if (!parentPopup || !parentPopup->IsMenu())
644
0
      break;
645
0
646
0
    aMenuPopupFrame = static_cast<nsMenuPopupFrame *>(parentPopup);
647
0
  }
648
0
649
0
  return nullptr;
650
0
}
651
652
void
653
nsMenuPopupFrame::InitPositionFromAnchorAlign(const nsAString& aAnchor,
654
                                              const nsAString& aAlign)
655
0
{
656
0
  mTriggerContent = nullptr;
657
0
658
0
  if (aAnchor.EqualsLiteral("topleft"))
659
0
    mPopupAnchor = POPUPALIGNMENT_TOPLEFT;
660
0
  else if (aAnchor.EqualsLiteral("topright"))
661
0
    mPopupAnchor = POPUPALIGNMENT_TOPRIGHT;
662
0
  else if (aAnchor.EqualsLiteral("bottomleft"))
663
0
    mPopupAnchor = POPUPALIGNMENT_BOTTOMLEFT;
664
0
  else if (aAnchor.EqualsLiteral("bottomright"))
665
0
    mPopupAnchor = POPUPALIGNMENT_BOTTOMRIGHT;
666
0
  else if (aAnchor.EqualsLiteral("leftcenter"))
667
0
    mPopupAnchor = POPUPALIGNMENT_LEFTCENTER;
668
0
  else if (aAnchor.EqualsLiteral("rightcenter"))
669
0
    mPopupAnchor = POPUPALIGNMENT_RIGHTCENTER;
670
0
  else if (aAnchor.EqualsLiteral("topcenter"))
671
0
    mPopupAnchor = POPUPALIGNMENT_TOPCENTER;
672
0
  else if (aAnchor.EqualsLiteral("bottomcenter"))
673
0
    mPopupAnchor = POPUPALIGNMENT_BOTTOMCENTER;
674
0
  else
675
0
    mPopupAnchor = POPUPALIGNMENT_NONE;
676
0
677
0
  if (aAlign.EqualsLiteral("topleft"))
678
0
    mPopupAlignment = POPUPALIGNMENT_TOPLEFT;
679
0
  else if (aAlign.EqualsLiteral("topright"))
680
0
    mPopupAlignment = POPUPALIGNMENT_TOPRIGHT;
681
0
  else if (aAlign.EqualsLiteral("bottomleft"))
682
0
    mPopupAlignment = POPUPALIGNMENT_BOTTOMLEFT;
683
0
  else if (aAlign.EqualsLiteral("bottomright"))
684
0
    mPopupAlignment = POPUPALIGNMENT_BOTTOMRIGHT;
685
0
  else
686
0
    mPopupAlignment = POPUPALIGNMENT_NONE;
687
0
688
0
  mPosition = POPUPPOSITION_UNKNOWN;
689
0
}
690
691
void
692
nsMenuPopupFrame::InitializePopup(nsIContent* aAnchorContent,
693
                                  nsIContent* aTriggerContent,
694
                                  const nsAString& aPosition,
695
                                  int32_t aXPos, int32_t aYPos,
696
                                  MenuPopupAnchorType aAnchorType,
697
                                  bool aAttributesOverride)
698
0
{
699
0
  EnsureWidget();
700
0
701
0
  mPopupState = ePopupShowing;
702
0
  mAnchorContent = aAnchorContent;
703
0
  mTriggerContent = aTriggerContent;
704
0
  mXPos = aXPos;
705
0
  mYPos = aYPos;
706
0
  mAdjustOffsetForContextMenu = false;
707
0
  mVFlip = false;
708
0
  mHFlip = false;
709
0
  mAlignmentOffset = 0;
710
0
  mPositionedOffset = 0;
711
0
712
0
  mAnchorType = aAnchorType;
713
0
714
0
  // if aAttributesOverride is true, then the popupanchor, popupalign and
715
0
  // position attributes on the <popup> override those values passed in.
716
0
  // If false, those attributes are only used if the values passed in are empty
717
0
  if (aAnchorContent || aAnchorType == MenuPopupAnchorType_Rect) {
718
0
    nsAutoString anchor, align, position, flip;
719
0
    mContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::popupanchor, anchor);
720
0
    mContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::popupalign, align);
721
0
    mContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::position, position);
722
0
    mContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::flip, flip);
723
0
724
0
    if (aAttributesOverride) {
725
0
      // if the attributes are set, clear the offset position. Otherwise,
726
0
      // the offset is used to adjust the position from the anchor point
727
0
      if (anchor.IsEmpty() && align.IsEmpty() && position.IsEmpty())
728
0
        position.Assign(aPosition);
729
0
      else
730
0
        mXPos = mYPos = 0;
731
0
    }
732
0
    else if (!aPosition.IsEmpty()) {
733
0
      position.Assign(aPosition);
734
0
    }
735
0
736
0
    if (flip.EqualsLiteral("none")) {
737
0
      mFlip = FlipType_None;
738
0
    } else if (flip.EqualsLiteral("both")) {
739
0
      mFlip = FlipType_Both;
740
0
    } else if (flip.EqualsLiteral("slide")) {
741
0
      mFlip = FlipType_Slide;
742
0
    }
743
0
744
0
    position.CompressWhitespace();
745
0
    int32_t spaceIdx = position.FindChar(' ');
746
0
    // if there is a space in the position, assume it is the anchor and
747
0
    // alignment as two separate tokens.
748
0
    if (spaceIdx >= 0) {
749
0
      InitPositionFromAnchorAlign(Substring(position, 0, spaceIdx), Substring(position, spaceIdx + 1));
750
0
    }
751
0
    else if (position.EqualsLiteral("before_start")) {
752
0
      mPopupAnchor = POPUPALIGNMENT_TOPLEFT;
753
0
      mPopupAlignment = POPUPALIGNMENT_BOTTOMLEFT;
754
0
      mPosition = POPUPPOSITION_BEFORESTART;
755
0
    }
756
0
    else if (position.EqualsLiteral("before_end")) {
757
0
      mPopupAnchor = POPUPALIGNMENT_TOPRIGHT;
758
0
      mPopupAlignment = POPUPALIGNMENT_BOTTOMRIGHT;
759
0
      mPosition = POPUPPOSITION_BEFOREEND;
760
0
    }
761
0
    else if (position.EqualsLiteral("after_start")) {
762
0
      mPopupAnchor = POPUPALIGNMENT_BOTTOMLEFT;
763
0
      mPopupAlignment = POPUPALIGNMENT_TOPLEFT;
764
0
      mPosition = POPUPPOSITION_AFTERSTART;
765
0
    }
766
0
    else if (position.EqualsLiteral("after_end")) {
767
0
      mPopupAnchor = POPUPALIGNMENT_BOTTOMRIGHT;
768
0
      mPopupAlignment = POPUPALIGNMENT_TOPRIGHT;
769
0
      mPosition = POPUPPOSITION_AFTEREND;
770
0
    }
771
0
    else if (position.EqualsLiteral("start_before")) {
772
0
      mPopupAnchor = POPUPALIGNMENT_TOPLEFT;
773
0
      mPopupAlignment = POPUPALIGNMENT_TOPRIGHT;
774
0
      mPosition = POPUPPOSITION_STARTBEFORE;
775
0
    }
776
0
    else if (position.EqualsLiteral("start_after")) {
777
0
      mPopupAnchor = POPUPALIGNMENT_BOTTOMLEFT;
778
0
      mPopupAlignment = POPUPALIGNMENT_BOTTOMRIGHT;
779
0
      mPosition = POPUPPOSITION_STARTAFTER;
780
0
    }
781
0
    else if (position.EqualsLiteral("end_before")) {
782
0
      mPopupAnchor = POPUPALIGNMENT_TOPRIGHT;
783
0
      mPopupAlignment = POPUPALIGNMENT_TOPLEFT;
784
0
      mPosition = POPUPPOSITION_ENDBEFORE;
785
0
    }
786
0
    else if (position.EqualsLiteral("end_after")) {
787
0
      mPopupAnchor = POPUPALIGNMENT_BOTTOMRIGHT;
788
0
      mPopupAlignment = POPUPALIGNMENT_BOTTOMLEFT;
789
0
      mPosition = POPUPPOSITION_ENDAFTER;
790
0
    }
791
0
    else if (position.EqualsLiteral("overlap")) {
792
0
      mPopupAnchor = POPUPALIGNMENT_TOPLEFT;
793
0
      mPopupAlignment = POPUPALIGNMENT_TOPLEFT;
794
0
      mPosition = POPUPPOSITION_OVERLAP;
795
0
    }
796
0
    else if (position.EqualsLiteral("after_pointer")) {
797
0
      mPopupAnchor = POPUPALIGNMENT_TOPLEFT;
798
0
      mPopupAlignment = POPUPALIGNMENT_TOPLEFT;
799
0
      mPosition = POPUPPOSITION_AFTERPOINTER;
800
0
      // XXXndeakin this is supposed to anchor vertically after, but with the
801
0
      // horizontal position as the mouse pointer.
802
0
      mYPos += 21;
803
0
    }
804
0
    else if (position.EqualsLiteral("selection")) {
805
0
      mPopupAnchor = POPUPALIGNMENT_BOTTOMLEFT;
806
0
      mPopupAlignment = POPUPALIGNMENT_TOPLEFT;
807
0
      mPosition = POPUPPOSITION_SELECTION;
808
0
    }
809
0
    else {
810
0
      InitPositionFromAnchorAlign(anchor, align);
811
0
    }
812
0
  }
813
0
814
0
  mScreenRect = nsIntRect(-1, -1, 0, 0);
815
0
816
0
  if (aAttributesOverride) {
817
0
    // Use |left| and |top| dimension attributes to position the popup if
818
0
    // present, as they may have been persisted.
819
0
    nsAutoString left, top;
820
0
    mContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::left, left);
821
0
    mContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::top, top);
822
0
823
0
    nsresult err;
824
0
    if (!left.IsEmpty()) {
825
0
      int32_t x = left.ToInteger(&err);
826
0
      if (NS_SUCCEEDED(err))
827
0
        mScreenRect.x = x;
828
0
    }
829
0
    if (!top.IsEmpty()) {
830
0
      int32_t y = top.ToInteger(&err);
831
0
      if (NS_SUCCEEDED(err))
832
0
        mScreenRect.y = y;
833
0
    }
834
0
  }
835
0
}
836
837
void
838
nsMenuPopupFrame::InitializePopupAtScreen(nsIContent* aTriggerContent,
839
                                          int32_t aXPos, int32_t aYPos,
840
                                          bool aIsContextMenu)
841
0
{
842
0
  EnsureWidget();
843
0
844
0
  mPopupState = ePopupShowing;
845
0
  mAnchorContent = nullptr;
846
0
  mTriggerContent = aTriggerContent;
847
0
  mScreenRect = nsIntRect(aXPos, aYPos, 0, 0);
848
0
  mXPos = 0;
849
0
  mYPos = 0;
850
0
  mFlip = FlipType_Default;
851
0
  mPopupAnchor = POPUPALIGNMENT_NONE;
852
0
  mPopupAlignment = POPUPALIGNMENT_NONE;
853
0
  mPosition = POPUPPOSITION_UNKNOWN;
854
0
  mIsContextMenu = aIsContextMenu;
855
0
  mAdjustOffsetForContextMenu = aIsContextMenu;
856
0
  mAnchorType = MenuPopupAnchorType_Point;
857
0
  mPositionedOffset = 0;
858
0
}
859
860
void
861
nsMenuPopupFrame::InitializePopupAtRect(nsIContent* aTriggerContent,
862
                                        const nsAString& aPosition,
863
                                        const nsIntRect& aRect,
864
                                        bool aAttributesOverride)
865
0
{
866
0
  InitializePopup(nullptr, aTriggerContent, aPosition, 0, 0,
867
0
                  MenuPopupAnchorType_Rect, aAttributesOverride);
868
0
  mScreenRect = aRect;
869
0
}
870
871
void
872
nsMenuPopupFrame::ShowPopup(bool aIsContextMenu)
873
0
{
874
0
  mIsContextMenu = aIsContextMenu;
875
0
876
0
  InvalidateFrameSubtree();
877
0
878
0
  if (mPopupState == ePopupShowing || mPopupState == ePopupPositioning) {
879
0
    mPopupState = ePopupOpening;
880
0
    mIsOpenChanged = true;
881
0
882
0
    // Clear mouse capture when a popup is opened.
883
0
    if (mPopupType == ePopupTypeMenu) {
884
0
      EventStateManager* activeESM =
885
0
        static_cast<EventStateManager*>(
886
0
          EventStateManager::GetActiveEventStateManager());
887
0
      if (activeESM) {
888
0
        EventStateManager::ClearGlobalActiveContent(activeESM);
889
0
      }
890
0
891
0
      nsIPresShell::SetCapturingContent(nullptr, 0);
892
0
    }
893
0
894
0
    nsMenuFrame* menuFrame = do_QueryFrame(GetParent());
895
0
    if (menuFrame) {
896
0
      AutoWeakFrame weakFrame(this);
897
0
      menuFrame->PopupOpened();
898
0
      if (!weakFrame.IsAlive())
899
0
        return;
900
0
    }
901
0
902
0
    // do we need an actual reflow here?
903
0
    // is SetPopupPosition all that is needed?
904
0
    PresShell()->FrameNeedsReflow(this, nsIPresShell::eTreeChange,
905
0
                                  NS_FRAME_HAS_DIRTY_CHILDREN);
906
0
907
0
    if (mPopupType == ePopupTypeMenu) {
908
0
      nsCOMPtr<nsISound> sound(do_CreateInstance("@mozilla.org/sound;1"));
909
0
      if (sound)
910
0
        sound->PlayEventSound(nsISound::EVENT_MENU_POPUP);
911
0
    }
912
0
  }
913
0
914
0
  mShouldAutoPosition = true;
915
0
}
916
917
void
918
nsMenuPopupFrame::HidePopup(bool aDeselectMenu, nsPopupState aNewState)
919
0
{
920
0
  NS_ASSERTION(aNewState == ePopupClosed || aNewState == ePopupInvisible,
921
0
               "popup being set to unexpected state");
922
0
923
0
  ClearPopupShownDispatcher();
924
0
925
0
  // don't hide the popup when it isn't open
926
0
  if (mPopupState == ePopupClosed || mPopupState == ePopupShowing ||
927
0
      mPopupState == ePopupPositioning)
928
0
    return;
929
0
930
0
  // clear the trigger content if the popup is being closed. But don't clear
931
0
  // it if the popup is just being made invisible as a popuphiding or command
932
0
  // event may want to retrieve it.
933
0
  if (aNewState == ePopupClosed) {
934
0
    // if the popup had a trigger node set, clear the global window popup node
935
0
    // as well
936
0
    if (mTriggerContent) {
937
0
      nsIDocument* doc = mContent->GetUncomposedDoc();
938
0
      if (doc) {
939
0
        if (nsPIDOMWindowOuter* win = doc->GetWindow()) {
940
0
          nsCOMPtr<nsPIWindowRoot> root = win->GetTopWindowRoot();
941
0
          if (root) {
942
0
            root->SetPopupNode(nullptr);
943
0
          }
944
0
        }
945
0
      }
946
0
    }
947
0
    mTriggerContent = nullptr;
948
0
    mAnchorContent = nullptr;
949
0
  }
950
0
951
0
  // when invisible and about to be closed, HidePopup has already been called,
952
0
  // so just set the new state to closed and return
953
0
  if (mPopupState == ePopupInvisible) {
954
0
    if (aNewState == ePopupClosed)
955
0
      mPopupState = ePopupClosed;
956
0
    return;
957
0
  }
958
0
959
0
  mPopupState = aNewState;
960
0
961
0
  if (IsMenu())
962
0
    SetCurrentMenuItem(nullptr);
963
0
964
0
  mIncrementalString.Truncate();
965
0
966
0
  LockMenuUntilClosed(false);
967
0
968
0
  mIsOpenChanged = false;
969
0
  mCurrentMenu = nullptr; // make sure no current menu is set
970
0
  mHFlip = mVFlip = false;
971
0
972
0
  nsView* view = GetView();
973
0
  nsViewManager* viewManager = view->GetViewManager();
974
0
  viewManager->SetViewVisibility(view, nsViewVisibility_kHide);
975
0
976
0
  FireDOMEvent(NS_LITERAL_STRING("DOMMenuInactive"), mContent);
977
0
978
0
  // XXX, bug 137033, In Windows, if mouse is outside the window when the menupopup closes, no
979
0
  // mouse_enter/mouse_exit event will be fired to clear current hover state, we should clear it manually.
980
0
  // This code may not the best solution, but we can leave it here until we find the better approach.
981
0
  NS_ASSERTION(mContent->IsElement(), "How do we have a non-element?");
982
0
  EventStates state = mContent->AsElement()->State();
983
0
984
0
  if (state.HasState(NS_EVENT_STATE_HOVER)) {
985
0
    EventStateManager* esm = PresContext()->EventStateManager();
986
0
    esm->SetContentState(nullptr, NS_EVENT_STATE_HOVER);
987
0
  }
988
0
989
0
  nsMenuFrame* menuFrame = do_QueryFrame(GetParent());
990
0
  if (menuFrame) {
991
0
    menuFrame->PopupClosed(aDeselectMenu);
992
0
  }
993
0
}
994
995
uint32_t
996
nsMenuPopupFrame::GetXULLayoutFlags()
997
0
{
998
0
  return NS_FRAME_NO_SIZE_VIEW | NS_FRAME_NO_MOVE_VIEW | NS_FRAME_NO_VISIBILITY;
999
0
}
1000
1001
///////////////////////////////////////////////////////////////////////////////
1002
// GetRootViewForPopup
1003
//   Retrieves the view for the popup widget that contains the given frame.
1004
//   If the given frame is not contained by a popup widget, return the
1005
//   root view of the root viewmanager.
1006
nsView*
1007
nsMenuPopupFrame::GetRootViewForPopup(nsIFrame* aStartFrame)
1008
0
{
1009
0
  nsView* view = aStartFrame->GetClosestView();
1010
0
  NS_ASSERTION(view, "frame must have a closest view!");
1011
0
  while (view) {
1012
0
    // Walk up the view hierarchy looking for a view whose widget has a
1013
0
    // window type of eWindowType_popup - in other words a popup window
1014
0
    // widget. If we find one, this is the view we want.
1015
0
    nsIWidget* widget = view->GetWidget();
1016
0
    if (widget && widget->WindowType() == eWindowType_popup) {
1017
0
      return view;
1018
0
    }
1019
0
1020
0
    nsView* temp = view->GetParent();
1021
0
    if (!temp) {
1022
0
      // Otherwise, we've walked all the way up to the root view and not
1023
0
      // found a view for a popup window widget. Just return the root view.
1024
0
      return view;
1025
0
    }
1026
0
    view = temp;
1027
0
  }
1028
0
1029
0
  return nullptr;
1030
0
}
1031
1032
nsPoint
1033
nsMenuPopupFrame::AdjustPositionForAnchorAlign(nsRect& anchorRect,
1034
                                               FlipStyle& aHFlip, FlipStyle& aVFlip)
1035
0
{
1036
0
  // flip the anchor and alignment for right-to-left
1037
0
  int8_t popupAnchor(mPopupAnchor);
1038
0
  int8_t popupAlign(mPopupAlignment);
1039
0
  if (IsDirectionRTL()) {
1040
0
    // no need to flip the centered anchor types vertically
1041
0
    if (popupAnchor <= POPUPALIGNMENT_LEFTCENTER) {
1042
0
      popupAnchor = -popupAnchor;
1043
0
    }
1044
0
    popupAlign = -popupAlign;
1045
0
  }
1046
0
1047
0
  nsRect originalAnchorRect(anchorRect);
1048
0
1049
0
  // first, determine at which corner of the anchor the popup should appear
1050
0
  nsPoint pnt;
1051
0
  switch (popupAnchor) {
1052
0
    case POPUPALIGNMENT_LEFTCENTER:
1053
0
      pnt = nsPoint(anchorRect.x, anchorRect.y + anchorRect.height / 2);
1054
0
      anchorRect.y = pnt.y;
1055
0
      anchorRect.height = 0;
1056
0
      break;
1057
0
    case POPUPALIGNMENT_RIGHTCENTER:
1058
0
      pnt = nsPoint(anchorRect.XMost(), anchorRect.y + anchorRect.height / 2);
1059
0
      anchorRect.y = pnt.y;
1060
0
      anchorRect.height = 0;
1061
0
      break;
1062
0
    case POPUPALIGNMENT_TOPCENTER:
1063
0
      pnt = nsPoint(anchorRect.x + anchorRect.width / 2, anchorRect.y);
1064
0
      anchorRect.x = pnt.x;
1065
0
      anchorRect.width = 0;
1066
0
      break;
1067
0
    case POPUPALIGNMENT_BOTTOMCENTER:
1068
0
      pnt = nsPoint(anchorRect.x + anchorRect.width / 2, anchorRect.YMost());
1069
0
      anchorRect.x = pnt.x;
1070
0
      anchorRect.width = 0;
1071
0
      break;
1072
0
    case POPUPALIGNMENT_TOPRIGHT:
1073
0
      pnt = anchorRect.TopRight();
1074
0
      break;
1075
0
    case POPUPALIGNMENT_BOTTOMLEFT:
1076
0
      pnt = anchorRect.BottomLeft();
1077
0
      break;
1078
0
    case POPUPALIGNMENT_BOTTOMRIGHT:
1079
0
      pnt = anchorRect.BottomRight();
1080
0
      break;
1081
0
    case POPUPALIGNMENT_TOPLEFT:
1082
0
    default:
1083
0
      pnt = anchorRect.TopLeft();
1084
0
      break;
1085
0
  }
1086
0
1087
0
  // If the alignment is on the right edge of the popup, move the popup left
1088
0
  // by the width. Similarly, if the alignment is on the bottom edge of the
1089
0
  // popup, move the popup up by the height. In addition, account for the
1090
0
  // margins of the popup on the edge on which it is aligned.
1091
0
  nsMargin margin(0, 0, 0, 0);
1092
0
  StyleMargin()->GetMargin(margin);
1093
0
  switch (popupAlign) {
1094
0
    case POPUPALIGNMENT_TOPRIGHT:
1095
0
      pnt.MoveBy(-mRect.width - margin.right, margin.top);
1096
0
      break;
1097
0
    case POPUPALIGNMENT_BOTTOMLEFT:
1098
0
      pnt.MoveBy(margin.left, -mRect.height - margin.bottom);
1099
0
      break;
1100
0
    case POPUPALIGNMENT_BOTTOMRIGHT:
1101
0
      pnt.MoveBy(-mRect.width - margin.right, -mRect.height - margin.bottom);
1102
0
      break;
1103
0
    case POPUPALIGNMENT_TOPLEFT:
1104
0
    default:
1105
0
      pnt.MoveBy(margin.left, margin.top);
1106
0
      break;
1107
0
  }
1108
0
1109
0
  // If we aligning to the selected item in the popup, adjust the vertical
1110
0
  // position by the height of the menulist label and the selected item's
1111
0
  // position.
1112
0
  if (mPosition == POPUPPOSITION_SELECTION) {
1113
0
    MOZ_ASSERT(popupAnchor == POPUPALIGNMENT_BOTTOMLEFT ||
1114
0
              popupAnchor == POPUPALIGNMENT_BOTTOMRIGHT);
1115
0
    MOZ_ASSERT(popupAlign == POPUPALIGNMENT_TOPLEFT ||
1116
0
               popupAlign == POPUPALIGNMENT_TOPRIGHT);
1117
0
1118
0
    // Only adjust the popup if it just opened, otherwise the popup will move around if its gets
1119
0
    // resized or the selection changed. Cache the value in mPositionedOffset and use that instead
1120
0
    // for any future calculations.
1121
0
    if (mIsOpenChanged || mReflowCallbackData.mIsOpenChanged) {
1122
0
      nsIFrame* selectedItemFrame = GetSelectedItemForAlignment();
1123
0
      if (selectedItemFrame) {
1124
0
        int32_t scrolly = 0;
1125
0
        nsIScrollableFrame *scrollframe = do_QueryFrame(nsBox::GetChildXULBox(this));
1126
0
        if (scrollframe) {
1127
0
          scrolly = scrollframe->GetScrollPosition().y;
1128
0
        }
1129
0
1130
0
        mPositionedOffset = originalAnchorRect.height + selectedItemFrame->GetRect().y - scrolly;
1131
0
      }
1132
0
    }
1133
0
1134
0
    pnt.y -= mPositionedOffset;
1135
0
 }
1136
0
1137
0
  // Flipping horizontally is allowed as long as the popup is above or below
1138
0
  // the anchor. This will happen if both the anchor and alignment are top or
1139
0
  // both are bottom, but different values. Similarly, flipping vertically is
1140
0
  // allowed if the popup is to the left or right of the anchor. In this case,
1141
0
  // the values of the constants are such that both must be positive or both
1142
0
  // must be negative. A special case, used for overlap, allows flipping
1143
0
  // vertically as well.
1144
0
  // If we are flipping in both directions, we want to set a flip style both
1145
0
  // horizontally and vertically. However, we want to flip on the inside edge
1146
0
  // of the anchor. Consider the example of a typical dropdown menu.
1147
0
  // Vertically, we flip the popup on the outside edges of the anchor menu,
1148
0
  // however horizontally, we want to to use the inside edges so the popup
1149
0
  // still appears underneath the anchor menu instead of floating off the
1150
0
  // side of the menu.
1151
0
  switch (popupAnchor) {
1152
0
    case POPUPALIGNMENT_LEFTCENTER:
1153
0
    case POPUPALIGNMENT_RIGHTCENTER:
1154
0
      aHFlip = FlipStyle_Outside;
1155
0
      aVFlip = FlipStyle_Inside;
1156
0
      break;
1157
0
    case POPUPALIGNMENT_TOPCENTER:
1158
0
    case POPUPALIGNMENT_BOTTOMCENTER:
1159
0
      aHFlip = FlipStyle_Inside;
1160
0
      aVFlip = FlipStyle_Outside;
1161
0
      break;
1162
0
    default:
1163
0
    {
1164
0
      FlipStyle anchorEdge = mFlip == FlipType_Both ? FlipStyle_Inside : FlipStyle_None;
1165
0
      aHFlip = (popupAnchor == -popupAlign) ? FlipStyle_Outside : anchorEdge;
1166
0
      if (((popupAnchor > 0) == (popupAlign > 0)) ||
1167
0
          (popupAnchor == POPUPALIGNMENT_TOPLEFT && popupAlign == POPUPALIGNMENT_TOPLEFT))
1168
0
        aVFlip = FlipStyle_Outside;
1169
0
      else
1170
0
        aVFlip = anchorEdge;
1171
0
      break;
1172
0
    }
1173
0
  }
1174
0
1175
0
  return pnt;
1176
0
}
1177
1178
nsIFrame* nsMenuPopupFrame::GetSelectedItemForAlignment()
1179
0
{
1180
0
  // This method adjusts a menulist's popup such that the selected item is under the cursor, aligned
1181
0
  // with the menulist label.
1182
0
  nsCOMPtr<nsIDOMXULSelectControlElement> select = do_QueryInterface(mAnchorContent);
1183
0
  if (!select) {
1184
0
    // If there isn't an anchor, then try just getting the parent of the popup.
1185
0
    select = do_QueryInterface(mContent->GetParent());
1186
0
    if (!select) {
1187
0
      return nullptr;
1188
0
    }
1189
0
  }
1190
0
1191
0
  nsCOMPtr<nsIDOMXULSelectControlItemElement> item;
1192
0
  select->GetSelectedItem(getter_AddRefs(item));
1193
0
1194
0
  nsCOMPtr<nsIContent> selectedElement = do_QueryInterface(item);
1195
0
  return selectedElement ? selectedElement->GetPrimaryFrame() : nullptr;
1196
0
}
1197
1198
nscoord
1199
nsMenuPopupFrame::SlideOrResize(nscoord& aScreenPoint, nscoord aSize,
1200
                               nscoord aScreenBegin, nscoord aScreenEnd,
1201
                               nscoord *aOffset)
1202
0
{
1203
0
  // The popup may be positioned such that either the left/top or bottom/right
1204
0
  // is outside the screen - but never both.
1205
0
  nscoord newPos =
1206
0
    std::max(aScreenBegin, std::min(aScreenEnd - aSize, aScreenPoint));
1207
0
  *aOffset = newPos - aScreenPoint;
1208
0
  aScreenPoint = newPos;
1209
0
  return std::min(aSize, aScreenEnd - aScreenPoint);
1210
0
}
1211
1212
nscoord
1213
nsMenuPopupFrame::FlipOrResize(nscoord& aScreenPoint, nscoord aSize,
1214
                               nscoord aScreenBegin, nscoord aScreenEnd,
1215
                               nscoord aAnchorBegin, nscoord aAnchorEnd,
1216
                               nscoord aMarginBegin, nscoord aMarginEnd,
1217
                               nscoord aOffsetForContextMenu, FlipStyle aFlip,
1218
                               bool aEndAligned, bool* aFlipSide)
1219
0
{
1220
0
  // The flip side argument will be set to true if there wasn't room and we
1221
0
  // flipped to the opposite side.
1222
0
  *aFlipSide = false;
1223
0
1224
0
  // all of the coordinates used here are in app units relative to the screen
1225
0
  nscoord popupSize = aSize;
1226
0
  if (aScreenPoint < aScreenBegin) {
1227
0
    // at its current position, the popup would extend past the left or top
1228
0
    // edge of the screen, so it will have to be moved or resized.
1229
0
    if (aFlip) {
1230
0
      // for inside flips, we flip on the opposite side of the anchor
1231
0
      nscoord startpos = aFlip == FlipStyle_Outside ? aAnchorBegin : aAnchorEnd;
1232
0
      nscoord endpos = aFlip == FlipStyle_Outside ? aAnchorEnd : aAnchorBegin;
1233
0
1234
0
      // check whether there is more room to the left and right (or top and
1235
0
      // bottom) of the anchor and put the popup on the side with more room.
1236
0
      if (startpos - aScreenBegin >= aScreenEnd - endpos) {
1237
0
        aScreenPoint = aScreenBegin;
1238
0
        popupSize = startpos - aScreenPoint - aMarginEnd;
1239
0
        *aFlipSide = !aEndAligned;
1240
0
      }
1241
0
      else {
1242
0
        // If the newly calculated position is different than the existing
1243
0
        // position, flip such that the popup is to the right or bottom of the
1244
0
        // anchor point instead . However, when flipping use the same margin
1245
0
        // size.
1246
0
        nscoord newScreenPoint = endpos + aMarginEnd;
1247
0
        if (newScreenPoint != aScreenPoint) {
1248
0
          *aFlipSide = aEndAligned;
1249
0
          aScreenPoint = newScreenPoint;
1250
0
          // check if the new position is still off the right or bottom edge of
1251
0
          // the screen. If so, resize the popup.
1252
0
          if (aScreenPoint + aSize > aScreenEnd) {
1253
0
            popupSize = aScreenEnd - aScreenPoint;
1254
0
          }
1255
0
        }
1256
0
      }
1257
0
    }
1258
0
    else {
1259
0
      aScreenPoint = aScreenBegin;
1260
0
    }
1261
0
  }
1262
0
  else if (aScreenPoint + aSize > aScreenEnd) {
1263
0
    // at its current position, the popup would extend past the right or
1264
0
    // bottom edge of the screen, so it will have to be moved or resized.
1265
0
    if (aFlip) {
1266
0
      // for inside flips, we flip on the opposite side of the anchor
1267
0
      nscoord startpos = aFlip == FlipStyle_Outside ? aAnchorBegin : aAnchorEnd;
1268
0
      nscoord endpos = aFlip == FlipStyle_Outside ? aAnchorEnd : aAnchorBegin;
1269
0
1270
0
      // check whether there is more room to the left and right (or top and
1271
0
      // bottom) of the anchor and put the popup on the side with more room.
1272
0
      if (aScreenEnd - endpos >= startpos - aScreenBegin) {
1273
0
        *aFlipSide = aEndAligned;
1274
0
        if (mIsContextMenu) {
1275
0
          aScreenPoint = aScreenEnd - aSize;
1276
0
        }
1277
0
        else {
1278
0
          aScreenPoint = endpos + aMarginBegin;
1279
0
          popupSize = aScreenEnd - aScreenPoint;
1280
0
        }
1281
0
      }
1282
0
      else {
1283
0
        // if the newly calculated position is different than the existing
1284
0
        // position, we flip such that the popup is to the left or top of the
1285
0
        // anchor point instead.
1286
0
        nscoord newScreenPoint = startpos - aSize - aMarginBegin - std::max(aOffsetForContextMenu, 0);
1287
0
        if (newScreenPoint != aScreenPoint) {
1288
0
          *aFlipSide = !aEndAligned;
1289
0
          aScreenPoint = newScreenPoint;
1290
0
1291
0
          // check if the new position is still off the left or top edge of the
1292
0
          // screen. If so, resize the popup.
1293
0
          if (aScreenPoint < aScreenBegin) {
1294
0
            aScreenPoint = aScreenBegin;
1295
0
            if (!mIsContextMenu) {
1296
0
              popupSize = startpos - aScreenPoint - aMarginBegin;
1297
0
            }
1298
0
          }
1299
0
        }
1300
0
      }
1301
0
    }
1302
0
    else {
1303
0
      aScreenPoint = aScreenEnd - aSize;
1304
0
    }
1305
0
  }
1306
0
1307
0
  // Make sure that the point is within the screen boundaries and that the
1308
0
  // size isn't off the edge of the screen. This can happen when a large
1309
0
  // positive or negative margin is used.
1310
0
  if (aScreenPoint < aScreenBegin) {
1311
0
    aScreenPoint = aScreenBegin;
1312
0
  }
1313
0
  if (aScreenPoint > aScreenEnd) {
1314
0
    aScreenPoint = aScreenEnd - aSize;
1315
0
  }
1316
0
1317
0
  // If popupSize ended up being negative, or the original size was actually
1318
0
  // smaller than the calculated popup size, just use the original size instead.
1319
0
  if (popupSize <= 0 || aSize < popupSize) {
1320
0
    popupSize = aSize;
1321
0
  }
1322
0
1323
0
  return std::min(popupSize, aScreenEnd - aScreenPoint);
1324
0
}
1325
1326
nsRect
1327
nsMenuPopupFrame::ComputeAnchorRect(nsPresContext* aRootPresContext, nsIFrame* aAnchorFrame)
1328
0
{
1329
0
  // Get the root frame for a reference
1330
0
  nsIFrame* rootFrame = aRootPresContext->PresShell()->GetRootFrame();
1331
0
1332
0
  // The dimensions of the anchor
1333
0
  nsRect anchorRect = aAnchorFrame->GetRectRelativeToSelf();
1334
0
1335
0
  // Relative to the root
1336
0
  anchorRect = nsLayoutUtils::TransformFrameRectToAncestor(aAnchorFrame,
1337
0
                                                           anchorRect,
1338
0
                                                           rootFrame);
1339
0
  // Relative to the screen
1340
0
  anchorRect.MoveBy(rootFrame->GetScreenRectInAppUnits().TopLeft());
1341
0
1342
0
  // In its own app units
1343
0
  return anchorRect.ScaleToOtherAppUnitsRoundOut(aRootPresContext->AppUnitsPerDevPixel(),
1344
0
                                                 PresContext()->AppUnitsPerDevPixel());
1345
0
}
1346
1347
nsresult
1348
nsMenuPopupFrame::SetPopupPosition(nsIFrame* aAnchorFrame, bool aIsMove, bool aSizedToPopup, bool aNotify)
1349
0
{
1350
0
  if (!mShouldAutoPosition)
1351
0
    return NS_OK;
1352
0
1353
0
  // If this is due to a move, return early if the popup hasn't been laid out
1354
0
  // yet. On Windows, this can happen when using a drag popup before it opens.
1355
0
  if (aIsMove && (mPrefSize.width == -1 || mPrefSize.height == -1)) {
1356
0
    return NS_OK;
1357
0
  }
1358
0
1359
0
  nsPresContext* presContext = PresContext();
1360
0
  nsIFrame* rootFrame = presContext->PresShell()->GetRootFrame();
1361
0
  NS_ASSERTION(rootFrame->GetView() && GetView() &&
1362
0
               rootFrame->GetView() == GetView()->GetParent(),
1363
0
               "rootFrame's view is not our view's parent???");
1364
0
1365
0
  // For anchored popups, the anchor rectangle. For non-anchored popups, the
1366
0
  // size will be 0.
1367
0
  nsRect anchorRect;
1368
0
1369
0
  // Width of the parent, used when aSizedToPopup is true.
1370
0
  int32_t parentWidth = 0;
1371
0
1372
0
  bool anchored = IsAnchored();
1373
0
  if (anchored || aSizedToPopup) {
1374
0
    // In order to deal with transforms, we need the root prescontext:
1375
0
    nsPresContext* rootPresContext = presContext->GetRootPresContext();
1376
0
1377
0
    // If we can't reach a root pres context, don't bother continuing:
1378
0
    if (!rootPresContext) {
1379
0
      return NS_OK;
1380
0
    }
1381
0
1382
0
    // If anchored to a rectangle, use that rectangle. Otherwise, determine the
1383
0
    // rectangle from the anchor.
1384
0
    if (mAnchorType == MenuPopupAnchorType_Rect) {
1385
0
      anchorRect = ToAppUnits(mScreenRect, AppUnitsPerCSSPixel());
1386
0
    }
1387
0
    else {
1388
0
      // if the frame is not specified, use the anchor node passed to OpenPopup. If
1389
0
      // that wasn't specified either, use the root frame. Note that mAnchorContent
1390
0
      // might be a different document so its presshell must be used.
1391
0
      if (!aAnchorFrame) {
1392
0
        if (mAnchorContent) {
1393
0
          aAnchorFrame = mAnchorContent->GetPrimaryFrame();
1394
0
        }
1395
0
1396
0
        if (!aAnchorFrame) {
1397
0
          aAnchorFrame = rootFrame;
1398
0
          if (!aAnchorFrame)
1399
0
            return NS_OK;
1400
0
        }
1401
0
      }
1402
0
1403
0
      anchorRect = ComputeAnchorRect(rootPresContext, aAnchorFrame);
1404
0
    }
1405
0
1406
0
    // The width is needed when aSizedToPopup is true
1407
0
    parentWidth = anchorRect.width;
1408
0
  }
1409
0
1410
0
  // Set the popup's size to the preferred size. Below, this size will be
1411
0
  // adjusted to fit on the screen or within the content area. If the anchor
1412
0
  // is sized to the popup, use the anchor's width instead of the preferred
1413
0
  // width. The preferred size should already be set by the parent frame.
1414
0
  NS_ASSERTION(mPrefSize.width >= 0 || mPrefSize.height >= 0,
1415
0
               "preferred size of popup not set");
1416
0
  mRect.width = aSizedToPopup ? parentWidth : mPrefSize.width;
1417
0
  mRect.height = mPrefSize.height;
1418
0
1419
0
  // If we're anchoring to a rect, and the rect is smaller than the preferred size
1420
0
  // of the popup, change its width accordingly.
1421
0
  if (mAnchorType == MenuPopupAnchorType_Rect &&
1422
0
      parentWidth < mPrefSize.width) {
1423
0
    mRect.width = mPrefSize.width;
1424
0
  }
1425
0
1426
0
  // the screen position in app units where the popup should appear
1427
0
  nsPoint screenPoint;
1428
0
1429
0
  // indicators of whether the popup should be flipped or resized.
1430
0
  FlipStyle hFlip = FlipStyle_None, vFlip = FlipStyle_None;
1431
0
1432
0
  nsMargin margin(0, 0, 0, 0);
1433
0
  StyleMargin()->GetMargin(margin);
1434
0
1435
0
  // the screen rectangle of the root frame, in dev pixels.
1436
0
  nsRect rootScreenRect = rootFrame->GetScreenRectInAppUnits();
1437
0
1438
0
  nsDeviceContext* devContext = presContext->DeviceContext();
1439
0
  nsPoint offsetForContextMenu;
1440
0
1441
0
  bool isNoAutoHide = IsNoAutoHide();
1442
0
  nsPopupLevel popupLevel = PopupLevel(isNoAutoHide);
1443
0
1444
0
  if (anchored) {
1445
0
    // if we are anchored, there are certain things we don't want to do when
1446
0
    // repositioning the popup to fit on the screen, such as end up positioned
1447
0
    // over the anchor, for instance a popup appearing over the menu label.
1448
0
    // When doing this reposition, we want to move the popup to the side with
1449
0
    // the most room. The combination of anchor and alignment dictate if we
1450
0
    // readjust above/below or to the left/right.
1451
0
    if (mAnchorContent || mAnchorType == MenuPopupAnchorType_Rect) {
1452
0
      // move the popup according to the anchor and alignment. This will also
1453
0
      // tell us which axis the popup is flush against in case we have to move
1454
0
      // it around later. The AdjustPositionForAnchorAlign method accounts for
1455
0
      // the popup's margin.
1456
0
      screenPoint = AdjustPositionForAnchorAlign(anchorRect, hFlip, vFlip);
1457
0
    }
1458
0
    else {
1459
0
      // with no anchor, the popup is positioned relative to the root frame
1460
0
      anchorRect = rootScreenRect;
1461
0
      screenPoint = anchorRect.TopLeft() + nsPoint(margin.left, margin.top);
1462
0
    }
1463
0
1464
0
    // mXPos and mYPos specify an additonal offset passed to OpenPopup that
1465
0
    // should be added to the position.  We also add the offset to the anchor
1466
0
    // pos so a later flip/resize takes the offset into account.
1467
0
    nscoord anchorXOffset = nsPresContext::CSSPixelsToAppUnits(mXPos);
1468
0
    if (IsDirectionRTL()) {
1469
0
      screenPoint.x -= anchorXOffset;
1470
0
      anchorRect.x -= anchorXOffset;
1471
0
    } else {
1472
0
      screenPoint.x += anchorXOffset;
1473
0
      anchorRect.x += anchorXOffset;
1474
0
    }
1475
0
    nscoord anchorYOffset = nsPresContext::CSSPixelsToAppUnits(mYPos);
1476
0
    screenPoint.y += anchorYOffset;
1477
0
    anchorRect.y += anchorYOffset;
1478
0
1479
0
    // If this is a noautohide popup, set the screen coordinates of the popup.
1480
0
    // This way, the popup stays at the location where it was opened even when
1481
0
    // the window is moved. Popups at the parent level follow the parent
1482
0
    // window as it is moved and remained anchored, so we want to maintain the
1483
0
    // anchoring instead.
1484
0
    if (isNoAutoHide &&
1485
0
        (popupLevel != ePopupLevelParent || mAnchorType == MenuPopupAnchorType_Rect)) {
1486
0
      // Account for the margin that will end up being added to the screen coordinate
1487
0
      // the next time SetPopupPosition is called.
1488
0
      mAnchorType = MenuPopupAnchorType_Point;
1489
0
      mScreenRect.x = nsPresContext::AppUnitsToIntCSSPixels(screenPoint.x - margin.left);
1490
0
      mScreenRect.y = nsPresContext::AppUnitsToIntCSSPixels(screenPoint.y - margin.top);
1491
0
    }
1492
0
  }
1493
0
  else {
1494
0
    // The popup is positioned at a screen coordinate.
1495
0
    // First convert the screen position in mScreenRect from CSS pixels into
1496
0
    // device pixels, ignoring any zoom as mScreenRect holds unzoomed screen
1497
0
    // coordinates.
1498
0
    int32_t factor = devContext->AppUnitsPerDevPixelAtUnitFullZoom();
1499
0
1500
0
    // Depending on the platform, context menus should be offset by varying amounts
1501
0
    // to ensure that they don't appear directly where the cursor is. Otherwise,
1502
0
    // it is too easy to have the context menu close up again.
1503
0
    if (mAdjustOffsetForContextMenu) {
1504
0
      nsPoint offsetForContextMenuDev;
1505
0
      offsetForContextMenuDev.x = nsPresContext::CSSPixelsToAppUnits(LookAndFeel::GetInt(
1506
0
                                    LookAndFeel::eIntID_ContextMenuOffsetHorizontal)) / factor;
1507
0
      offsetForContextMenuDev.y = nsPresContext::CSSPixelsToAppUnits(LookAndFeel::GetInt(
1508
0
                                    LookAndFeel::eIntID_ContextMenuOffsetVertical)) / factor;
1509
0
      offsetForContextMenu.x = presContext->DevPixelsToAppUnits(offsetForContextMenuDev.x);
1510
0
      offsetForContextMenu.y = presContext->DevPixelsToAppUnits(offsetForContextMenuDev.y);
1511
0
    }
1512
0
1513
0
    // next, convert into app units accounting for the zoom
1514
0
    screenPoint.x = presContext->DevPixelsToAppUnits(
1515
0
                      nsPresContext::CSSPixelsToAppUnits(mScreenRect.x) / factor);
1516
0
    screenPoint.y = presContext->DevPixelsToAppUnits(
1517
0
                      nsPresContext::CSSPixelsToAppUnits(mScreenRect.y) / factor);
1518
0
    anchorRect = nsRect(screenPoint, nsSize(0, 0));
1519
0
1520
0
    // add the margins on the popup
1521
0
    screenPoint.MoveBy(margin.left + offsetForContextMenu.x,
1522
0
                       margin.top + offsetForContextMenu.y);
1523
0
1524
#ifdef XP_MACOSX
1525
    // OSX tooltips follow standard flip rule but other popups flip horizontally not vertically
1526
    if (mPopupType == ePopupTypeTooltip) {
1527
        vFlip = FlipStyle_Outside;
1528
    } else {
1529
        hFlip = FlipStyle_Outside;
1530
    }
1531
#else
1532
    // Other OS screen positioned popups can be flipped vertically but never horizontally
1533
0
    vFlip = FlipStyle_Outside;
1534
0
#endif // #ifdef XP_MACOSX
1535
0
  }
1536
0
1537
0
  nscoord oldAlignmentOffset = mAlignmentOffset;
1538
0
1539
0
  // If a panel is being moved or has flip="none", don't constrain or flip it, in order to avoid
1540
0
  // visual noise when moving windows between screens. However, if a panel is already constrained
1541
0
  // or flipped (mIsOffset), then we want to continue to calculate this. Also, always do this for
1542
0
  // content shells, so that the popup doesn't extend outside the containing frame.
1543
0
  if (mInContentShell || (mFlip != FlipType_None &&
1544
0
                          (!aIsMove || mIsOffset || mPopupType != ePopupTypePanel))) {
1545
0
    int32_t appPerDev = presContext->AppUnitsPerDevPixel();
1546
0
    LayoutDeviceIntRect anchorRectDevPix =
1547
0
      LayoutDeviceIntRect::FromAppUnitsToNearest(anchorRect, appPerDev);
1548
0
    LayoutDeviceIntRect rootScreenRectDevPix =
1549
0
      LayoutDeviceIntRect::FromAppUnitsToNearest(rootScreenRect, appPerDev);
1550
0
    LayoutDeviceIntRect screenRectDevPix =
1551
0
      GetConstraintRect(anchorRectDevPix, rootScreenRectDevPix, popupLevel);
1552
0
    nsRect screenRect =
1553
0
      LayoutDeviceIntRect::ToAppUnits(screenRectDevPix, appPerDev);
1554
0
1555
0
    // Ensure that anchorRect is on screen.
1556
0
    anchorRect = anchorRect.Intersect(screenRect);
1557
0
1558
0
    // shrink the the popup down if it is larger than the screen size
1559
0
    if (mRect.width > screenRect.width)
1560
0
      mRect.width = screenRect.width;
1561
0
    if (mRect.height > screenRect.height)
1562
0
      mRect.height = screenRect.height;
1563
0
1564
0
    // at this point the anchor (anchorRect) is within the available screen
1565
0
    // area (screenRect) and the popup is known to be no larger than the screen.
1566
0
1567
0
    // We might want to "slide" an arrow if the panel is of the correct type -
1568
0
    // but we can only slide on one axis - the other axis must be "flipped or
1569
0
    // resized" as normal.
1570
0
    bool slideHorizontal = false, slideVertical = false;
1571
0
    if (mFlip == FlipType_Slide) {
1572
0
      int8_t position = GetAlignmentPosition();
1573
0
      slideHorizontal = position >= POPUPPOSITION_BEFORESTART &&
1574
0
                        position <= POPUPPOSITION_AFTEREND;
1575
0
      slideVertical = position >= POPUPPOSITION_STARTBEFORE &&
1576
0
                      position <= POPUPPOSITION_ENDAFTER;
1577
0
    }
1578
0
1579
0
    // Next, check if there is enough space to show the popup at full size when
1580
0
    // positioned at screenPoint. If not, flip the popups to the opposite side
1581
0
    // of their anchor point, or resize them as necessary.
1582
0
    bool endAligned = IsDirectionRTL() ?
1583
0
      mPopupAlignment == POPUPALIGNMENT_TOPLEFT || mPopupAlignment == POPUPALIGNMENT_BOTTOMLEFT :
1584
0
      mPopupAlignment == POPUPALIGNMENT_TOPRIGHT || mPopupAlignment == POPUPALIGNMENT_BOTTOMRIGHT;
1585
0
    nscoord preOffsetScreenPoint = screenPoint.x;
1586
0
    if (slideHorizontal) {
1587
0
      mRect.width = SlideOrResize(screenPoint.x, mRect.width, screenRect.x,
1588
0
                                  screenRect.XMost(), &mAlignmentOffset);
1589
0
    } else {
1590
0
      mRect.width = FlipOrResize(screenPoint.x, mRect.width, screenRect.x,
1591
0
                                 screenRect.XMost(), anchorRect.x, anchorRect.XMost(),
1592
0
                                 margin.left, margin.right, offsetForContextMenu.x, hFlip,
1593
0
                                 endAligned, &mHFlip);
1594
0
    }
1595
0
    mIsOffset = preOffsetScreenPoint != screenPoint.x;
1596
0
1597
0
    endAligned = mPopupAlignment == POPUPALIGNMENT_BOTTOMLEFT ||
1598
0
                 mPopupAlignment == POPUPALIGNMENT_BOTTOMRIGHT;
1599
0
    preOffsetScreenPoint = screenPoint.y;
1600
0
    if (slideVertical) {
1601
0
      mRect.height = SlideOrResize(screenPoint.y, mRect.height, screenRect.y,
1602
0
                                  screenRect.YMost(), &mAlignmentOffset);
1603
0
    } else {
1604
0
      mRect.height = FlipOrResize(screenPoint.y, mRect.height, screenRect.y,
1605
0
                                  screenRect.YMost(), anchorRect.y, anchorRect.YMost(),
1606
0
                                  margin.top, margin.bottom, offsetForContextMenu.y, vFlip,
1607
0
                                  endAligned, &mVFlip);
1608
0
    }
1609
0
    mIsOffset = mIsOffset || (preOffsetScreenPoint != screenPoint.y);
1610
0
1611
0
    NS_ASSERTION(screenPoint.x >= screenRect.x && screenPoint.y >= screenRect.y &&
1612
0
                 screenPoint.x + mRect.width <= screenRect.XMost() &&
1613
0
                 screenPoint.y + mRect.height <= screenRect.YMost(),
1614
0
                 "Popup is offscreen");
1615
0
  }
1616
0
1617
0
  // snap the popup's position in screen coordinates to device pixels,
1618
0
  // see bug 622507, bug 961431
1619
0
  screenPoint.x = presContext->RoundAppUnitsToNearestDevPixels(screenPoint.x);
1620
0
  screenPoint.y = presContext->RoundAppUnitsToNearestDevPixels(screenPoint.y);
1621
0
1622
0
  // determine the x and y position of the view by subtracting the desired
1623
0
  // screen position from the screen position of the root frame.
1624
0
  nsPoint viewPoint = screenPoint - rootScreenRect.TopLeft();
1625
0
1626
0
  nsView* view = GetView();
1627
0
  NS_ASSERTION(view, "popup with no view");
1628
0
1629
0
  // Offset the position by the width and height of the borders and titlebar.
1630
0
  // Even though GetClientOffset should return (0, 0) when there is no
1631
0
  // titlebar or borders, we skip these calculations anyway for non-panels
1632
0
  // to save time since they will never have a titlebar.
1633
0
  nsIWidget* widget = view->GetWidget();
1634
0
  if (mPopupType == ePopupTypePanel && widget) {
1635
0
    mLastClientOffset = widget->GetClientOffset();
1636
0
    viewPoint.x += presContext->DevPixelsToAppUnits(mLastClientOffset.x);
1637
0
    viewPoint.y += presContext->DevPixelsToAppUnits(mLastClientOffset.y);
1638
0
  }
1639
0
1640
0
  presContext->GetPresShell()->GetViewManager()->
1641
0
    MoveViewTo(view, viewPoint.x, viewPoint.y);
1642
0
1643
0
  // Now that we've positioned the view, sync up the frame's origin.
1644
0
  nsBoxFrame::SetPosition(viewPoint - GetParent()->GetOffsetTo(rootFrame));
1645
0
1646
0
  if (aSizedToPopup) {
1647
0
    nsBoxLayoutState state(PresContext());
1648
0
    // XXXndeakin can parentSize.width still extend outside?
1649
0
    SetXULBounds(state, mRect);
1650
0
  }
1651
0
1652
0
  // If the popup is in the positioned state or if it is shown and the position
1653
0
  // or size changed, dispatch a popuppositioned event if the popup wants it.
1654
0
  nsIntRect newRect(screenPoint.x, screenPoint.y, mRect.width, mRect.height);
1655
0
  if (mPopupState == ePopupPositioning ||
1656
0
      (mPopupState == ePopupShown && !newRect.IsEqualEdges(mUsedScreenRect)) ||
1657
0
      (mPopupState == ePopupShown && oldAlignmentOffset != mAlignmentOffset)) {
1658
0
    mUsedScreenRect = newRect;
1659
0
    if (aNotify) {
1660
0
      nsXULPopupPositionedEvent::DispatchIfNeeded(mContent, false, false);
1661
0
    }
1662
0
  }
1663
0
1664
0
  return NS_OK;
1665
0
}
1666
1667
/* virtual */ nsMenuFrame*
1668
nsMenuPopupFrame::GetCurrentMenuItem()
1669
0
{
1670
0
  return mCurrentMenu;
1671
0
}
1672
1673
LayoutDeviceIntRect
1674
nsMenuPopupFrame::GetConstraintRect(const LayoutDeviceIntRect& aAnchorRect,
1675
                                    const LayoutDeviceIntRect& aRootScreenRect,
1676
                                    nsPopupLevel aPopupLevel)
1677
0
{
1678
0
  LayoutDeviceIntRect screenRectPixels;
1679
0
1680
0
  // determine the available screen space. It will be reduced by the OS chrome
1681
0
  // such as menubars. It addition, for content shells, it will be the area of
1682
0
  // the content rather than the screen.
1683
0
  nsCOMPtr<nsIScreen> screen;
1684
0
  nsCOMPtr<nsIScreenManager> sm(do_GetService("@mozilla.org/gfx/screenmanager;1"));
1685
0
  if (sm) {
1686
0
    // for content shells, get the screen where the root frame is located.
1687
0
    // This is because we need to constrain the content to this content area,
1688
0
    // so we should use the same screen. Otherwise, use the screen where the
1689
0
    // anchor is located.
1690
0
    DesktopToLayoutDeviceScale scale =
1691
0
      PresContext()->DeviceContext()->GetDesktopToDeviceScale();
1692
0
    DesktopRect rect =
1693
0
      (mInContentShell ? aRootScreenRect : aAnchorRect) / scale;
1694
0
    int32_t width = std::max(1, NSToIntRound(rect.width));
1695
0
    int32_t height = std::max(1, NSToIntRound(rect.height));
1696
0
    sm->ScreenForRect(rect.x, rect.y, width, height, getter_AddRefs(screen));
1697
0
    if (screen) {
1698
0
      // Non-top-level popups (which will always be panels)
1699
0
      // should never overlap the OS bar:
1700
0
      bool dontOverlapOSBar = aPopupLevel != ePopupLevelTop;
1701
0
      // get the total screen area if the popup is allowed to overlap it.
1702
0
      if (!dontOverlapOSBar && mMenuCanOverlapOSBar && !mInContentShell)
1703
0
        screen->GetRect(&screenRectPixels.x, &screenRectPixels.y,
1704
0
          &screenRectPixels.width, &screenRectPixels.height);
1705
0
      else
1706
0
        screen->GetAvailRect(&screenRectPixels.x, &screenRectPixels.y,
1707
0
          &screenRectPixels.width, &screenRectPixels.height);
1708
0
    }
1709
0
  }
1710
0
1711
0
  if (mInContentShell) {
1712
0
    // for content shells, clip to the client area rather than the screen area
1713
0
    screenRectPixels.IntersectRect(screenRectPixels, aRootScreenRect);
1714
0
  }
1715
0
  else if (!mOverrideConstraintRect.IsEmpty()) {
1716
0
    LayoutDeviceIntRect overrideConstrainRect =
1717
0
      LayoutDeviceIntRect::FromAppUnitsToNearest(mOverrideConstraintRect,
1718
0
                                                 PresContext()->AppUnitsPerDevPixel());
1719
0
    // This is currently only used for <select> elements where we want to constrain
1720
0
    // vertically to the screen but not horizontally, so do the intersection and then
1721
0
    // reset the horizontal values.
1722
0
    screenRectPixels.IntersectRect(screenRectPixels, overrideConstrainRect);
1723
0
    screenRectPixels.x = overrideConstrainRect.x;
1724
0
    screenRectPixels.width = overrideConstrainRect.width;
1725
0
  }
1726
0
1727
0
  return screenRectPixels;
1728
0
}
1729
1730
void nsMenuPopupFrame::CanAdjustEdges(Side aHorizontalSide,
1731
                                      Side aVerticalSide,
1732
                                      LayoutDeviceIntPoint& aChange)
1733
0
{
1734
0
  int8_t popupAlign(mPopupAlignment);
1735
0
  if (IsDirectionRTL()) {
1736
0
    popupAlign = -popupAlign;
1737
0
  }
1738
0
1739
0
  if (aHorizontalSide == (mHFlip ? eSideRight : eSideLeft)) {
1740
0
    if (popupAlign == POPUPALIGNMENT_TOPLEFT || popupAlign == POPUPALIGNMENT_BOTTOMLEFT) {
1741
0
      aChange.x = 0;
1742
0
    }
1743
0
  }
1744
0
  else if (aHorizontalSide == (mHFlip ? eSideLeft : eSideRight)) {
1745
0
    if (popupAlign == POPUPALIGNMENT_TOPRIGHT || popupAlign == POPUPALIGNMENT_BOTTOMRIGHT) {
1746
0
      aChange.x = 0;
1747
0
    }
1748
0
  }
1749
0
1750
0
  if (aVerticalSide == (mVFlip ? eSideBottom : eSideTop)) {
1751
0
    if (popupAlign == POPUPALIGNMENT_TOPLEFT || popupAlign == POPUPALIGNMENT_TOPRIGHT) {
1752
0
      aChange.y = 0;
1753
0
    }
1754
0
  }
1755
0
  else if (aVerticalSide == (mVFlip ? eSideTop : eSideBottom)) {
1756
0
    if (popupAlign == POPUPALIGNMENT_BOTTOMLEFT || popupAlign == POPUPALIGNMENT_BOTTOMRIGHT) {
1757
0
      aChange.y = 0;
1758
0
    }
1759
0
  }
1760
0
}
1761
1762
ConsumeOutsideClicksResult nsMenuPopupFrame::ConsumeOutsideClicks()
1763
0
{
1764
0
  if (mContent->AsElement()->AttrValueIs(kNameSpaceID_None,
1765
0
                                         nsGkAtoms::consumeoutsideclicks,
1766
0
                                         nsGkAtoms::_true, eCaseMatters)) {
1767
0
    return ConsumeOutsideClicks_True;
1768
0
  }
1769
0
  if (mContent->AsElement()->AttrValueIs(kNameSpaceID_None,
1770
0
                                         nsGkAtoms::consumeoutsideclicks,
1771
0
                                         nsGkAtoms::_false, eCaseMatters)) {
1772
0
    return ConsumeOutsideClicks_ParentOnly;
1773
0
  }
1774
0
  if (mContent->AsElement()->AttrValueIs(kNameSpaceID_None,
1775
0
                                         nsGkAtoms::consumeoutsideclicks,
1776
0
                                         nsGkAtoms::never, eCaseMatters)) {
1777
0
    return ConsumeOutsideClicks_Never;
1778
0
  }
1779
0
1780
0
  nsCOMPtr<nsIContent> parentContent = mContent->GetParent();
1781
0
  if (parentContent) {
1782
0
    dom::NodeInfo *ni = parentContent->NodeInfo();
1783
0
    if (ni->Equals(nsGkAtoms::menulist, kNameSpaceID_XUL)) {
1784
0
      return ConsumeOutsideClicks_True;  // Consume outside clicks for combo boxes on all platforms
1785
0
    }
1786
#if defined(XP_WIN)
1787
    // Don't consume outside clicks for menus in Windows
1788
    if (ni->Equals(nsGkAtoms::menu, kNameSpaceID_XUL) ||
1789
        ni->Equals(nsGkAtoms::popupset, kNameSpaceID_XUL) ||
1790
        ((ni->Equals(nsGkAtoms::button, kNameSpaceID_XUL) ||
1791
          ni->Equals(nsGkAtoms::toolbarbutton, kNameSpaceID_XUL)) &&
1792
         parentContent->AsElement()->AttrValueIs(kNameSpaceID_None,
1793
                                                 nsGkAtoms::type,
1794
                                                 nsGkAtoms::menu,
1795
                                                 eCaseMatters))) {
1796
      return ConsumeOutsideClicks_Never;
1797
    }
1798
#endif
1799
0
    if (ni->Equals(nsGkAtoms::textbox, kNameSpaceID_XUL)) {
1800
0
      // Don't consume outside clicks for autocomplete widget
1801
0
      if (parentContent->AsElement()->AttrValueIs(kNameSpaceID_None,
1802
0
                                                  nsGkAtoms::type,
1803
0
                                                  nsGkAtoms::autocomplete,
1804
0
                                                  eCaseMatters)) {
1805
0
        return ConsumeOutsideClicks_Never;
1806
0
      }
1807
0
    }
1808
0
  }
1809
0
1810
0
  return ConsumeOutsideClicks_True;
1811
0
}
1812
1813
// XXXroc this is megalame. Fossicking around for a frame of the right
1814
// type is a recipe for disaster in the long term.
1815
nsIScrollableFrame* nsMenuPopupFrame::GetScrollFrame(nsIFrame* aStart)
1816
0
{
1817
0
  if (!aStart)
1818
0
    return nullptr;
1819
0
1820
0
  // try start frame and siblings
1821
0
  nsIFrame* currFrame = aStart;
1822
0
  do {
1823
0
    nsIScrollableFrame* sf = do_QueryFrame(currFrame);
1824
0
    if (sf)
1825
0
      return sf;
1826
0
    currFrame = currFrame->GetNextSibling();
1827
0
  } while (currFrame);
1828
0
1829
0
  // try children
1830
0
  currFrame = aStart;
1831
0
  do {
1832
0
    nsIFrame* childFrame = currFrame->PrincipalChildList().FirstChild();
1833
0
    nsIScrollableFrame* sf = GetScrollFrame(childFrame);
1834
0
    if (sf)
1835
0
      return sf;
1836
0
    currFrame = currFrame->GetNextSibling();
1837
0
  } while (currFrame);
1838
0
1839
0
  return nullptr;
1840
0
}
1841
1842
void nsMenuPopupFrame::EnsureMenuItemIsVisible(nsMenuFrame* aMenuItem)
1843
0
{
1844
0
  if (aMenuItem) {
1845
0
    aMenuItem->PresShell()->ScrollFrameRectIntoView(
1846
0
      aMenuItem,
1847
0
      nsRect(nsPoint(0,0), aMenuItem->GetRect().Size()),
1848
0
      nsIPresShell::ScrollAxis(),
1849
0
      nsIPresShell::ScrollAxis(),
1850
0
      nsIPresShell::SCROLL_OVERFLOW_HIDDEN |
1851
0
      nsIPresShell::SCROLL_FIRST_ANCESTOR_ONLY);
1852
0
  }
1853
0
}
1854
1855
void nsMenuPopupFrame::ChangeByPage(bool aIsUp)
1856
0
{
1857
0
  // Only scroll by page within menulists.
1858
0
  if (!IsMenuList()) {
1859
0
    return;
1860
0
  }
1861
0
1862
0
  nsMenuFrame* newMenu = nullptr;
1863
0
  nsIFrame* currentMenu = mCurrentMenu;
1864
0
  if (!currentMenu) {
1865
0
    // If there is no current menu item, get the first item. When moving up,
1866
0
    // just use this as the newMenu and leave currentMenu null so that no
1867
0
    // check for a later element is performed. When moving down, set currentMenu
1868
0
    // so that we look for one page down from the first item.
1869
0
    newMenu = nsXULPopupManager::GetNextMenuItem(this, nullptr, true, false);
1870
0
    if (!aIsUp) {
1871
0
      currentMenu = newMenu;
1872
0
    }
1873
0
  }
1874
0
1875
0
  if (currentMenu) {
1876
0
    nscoord scrollHeight = mRect.height;
1877
0
    nsIScrollableFrame *scrollframe = GetScrollFrame(this);
1878
0
    if (scrollframe) {
1879
0
      scrollHeight = scrollframe->GetScrollPortRect().height;
1880
0
    }
1881
0
1882
0
    // Get the position of the current item and add or subtract one popup's
1883
0
    // height to or from it.
1884
0
    nscoord targetPosition = aIsUp ? currentMenu->GetRect().YMost() - scrollHeight :
1885
0
                                     currentMenu->GetRect().y + scrollHeight;
1886
0
1887
0
    // Indicates that the last visible child was a valid menuitem.
1888
0
    bool lastWasValid = false;
1889
0
1890
0
    // Look for the next child which is just past the target position. This child
1891
0
    // will need to be selected.
1892
0
    while (currentMenu) {
1893
0
      // Only consider menu frames.
1894
0
      nsMenuFrame* menuFrame = do_QueryFrame(currentMenu);
1895
0
      if (menuFrame &&
1896
0
          nsXULPopupManager::IsValidMenuItem(menuFrame->GetContent(), true)) {
1897
0
1898
0
        // If the right position was found, break out. Otherwise, look for another item.
1899
0
        if ((!aIsUp && currentMenu->GetRect().YMost() > targetPosition) ||
1900
0
            (aIsUp && currentMenu->GetRect().y < targetPosition)) {
1901
0
1902
0
          // If the last visible child was not a valid menuitem or was disabled,
1903
0
          // use this as the menu to select, skipping over any non-valid items at
1904
0
          // the edge of the page.
1905
0
          if (!lastWasValid) {
1906
0
            newMenu = menuFrame;
1907
0
          }
1908
0
1909
0
          break;
1910
0
        }
1911
0
1912
0
        // Assign this item to newMenu. This item will be selected in case we
1913
0
        // don't find any more.
1914
0
        lastWasValid = true;
1915
0
        newMenu = menuFrame;
1916
0
      }
1917
0
      else {
1918
0
        lastWasValid = false;
1919
0
      }
1920
0
1921
0
      currentMenu = aIsUp ? currentMenu->GetPrevSibling() :
1922
0
                            currentMenu->GetNextSibling();
1923
0
    }
1924
0
  }
1925
0
1926
0
  // Select the new menuitem.
1927
0
  if (newMenu) {
1928
0
    ChangeMenuItem(newMenu, false, true);
1929
0
  }
1930
0
}
1931
1932
NS_IMETHODIMP nsMenuPopupFrame::SetCurrentMenuItem(nsMenuFrame* aMenuItem)
1933
0
{
1934
0
  if (mCurrentMenu == aMenuItem)
1935
0
    return NS_OK;
1936
0
1937
0
  if (mCurrentMenu) {
1938
0
    mCurrentMenu->SelectMenu(false);
1939
0
  }
1940
0
1941
0
  if (aMenuItem) {
1942
0
    EnsureMenuItemIsVisible(aMenuItem);
1943
0
    aMenuItem->SelectMenu(true);
1944
0
  }
1945
0
1946
0
  mCurrentMenu = aMenuItem;
1947
0
1948
0
  return NS_OK;
1949
0
}
1950
1951
void
1952
nsMenuPopupFrame::CurrentMenuIsBeingDestroyed()
1953
0
{
1954
0
  mCurrentMenu = nullptr;
1955
0
}
1956
1957
NS_IMETHODIMP
1958
nsMenuPopupFrame::ChangeMenuItem(nsMenuFrame* aMenuItem,
1959
                                 bool aSelectFirstItem,
1960
                                 bool aFromKey)
1961
0
{
1962
0
  if (mCurrentMenu == aMenuItem)
1963
0
    return NS_OK;
1964
0
1965
0
  // When a context menu is open, the current menu is locked, and no change
1966
0
  // to the menu is allowed.
1967
0
  nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
1968
0
  if (!mIsContextMenu && pm && pm->HasContextMenu(this))
1969
0
    return NS_OK;
1970
0
1971
0
  // Unset the current child.
1972
0
  if (mCurrentMenu) {
1973
0
    mCurrentMenu->SelectMenu(false);
1974
0
    nsMenuPopupFrame* popup = mCurrentMenu->GetPopup();
1975
0
    if (popup) {
1976
0
      if (mCurrentMenu->IsOpen()) {
1977
0
        if (pm)
1978
0
          pm->HidePopupAfterDelay(popup);
1979
0
      }
1980
0
    }
1981
0
  }
1982
0
1983
0
  // Set the new child.
1984
0
  if (aMenuItem) {
1985
0
    EnsureMenuItemIsVisible(aMenuItem);
1986
0
    aMenuItem->SelectMenu(true);
1987
0
1988
0
    // On Windows, a menulist should update its value whenever navigation was
1989
0
    // done by the keyboard.
1990
#ifdef XP_WIN
1991
    if (aFromKey && IsOpen() && IsMenuList()) {
1992
      // Fire a command event as the new item, but we don't want to close
1993
      // the menu, blink it, or update any other state of the menuitem. The
1994
      // command event will cause the item to be selected.
1995
      nsContentUtils::DispatchXULCommand(aMenuItem->GetContent(), /* aTrusted = */ true,
1996
                                         nullptr, PresShell(),
1997
                                         false, false, false, false);
1998
    }
1999
#endif
2000
  }
2001
0
2002
0
  mCurrentMenu = aMenuItem;
2003
0
2004
0
  return NS_OK;
2005
0
}
2006
2007
nsMenuFrame*
2008
nsMenuPopupFrame::Enter(WidgetGUIEvent* aEvent)
2009
0
{
2010
0
  mIncrementalString.Truncate();
2011
0
2012
0
  // Give it to the child.
2013
0
  if (mCurrentMenu)
2014
0
    return mCurrentMenu->Enter(aEvent);
2015
0
2016
0
  return nullptr;
2017
0
}
2018
2019
nsMenuFrame*
2020
nsMenuPopupFrame::FindMenuWithShortcut(KeyboardEvent* aKeyEvent, bool& doAction)
2021
0
{
2022
0
  uint32_t charCode = aKeyEvent->CharCode();
2023
0
  uint32_t keyCode = aKeyEvent->KeyCode();
2024
0
2025
0
  doAction = false;
2026
0
2027
0
  // Enumerate over our list of frames.
2028
0
  nsContainerFrame* immediateParent =
2029
0
    nsXULPopupManager::ImmediateParentFrame(this);
2030
0
  uint32_t matchCount = 0, matchShortcutCount = 0;
2031
0
  bool foundActive = false;
2032
0
  nsMenuFrame* frameBefore = nullptr;
2033
0
  nsMenuFrame* frameAfter = nullptr;
2034
0
  nsMenuFrame* frameShortcut = nullptr;
2035
0
2036
0
  nsIContent* parentContent = mContent->GetParent();
2037
0
2038
0
  bool isMenu = parentContent &&
2039
0
                  !parentContent->NodeInfo()->Equals(nsGkAtoms::menulist, kNameSpaceID_XUL);
2040
0
2041
0
  DOMTimeStamp keyTime = aKeyEvent->TimeStamp();
2042
0
2043
0
  if (charCode == 0) {
2044
0
    if (keyCode == dom::KeyboardEvent_Binding::DOM_VK_BACK_SPACE) {
2045
0
      if (!isMenu && !mIncrementalString.IsEmpty()) {
2046
0
        mIncrementalString.SetLength(mIncrementalString.Length() - 1);
2047
0
        return nullptr;
2048
0
      }
2049
0
      else {
2050
#ifdef XP_WIN
2051
        nsCOMPtr<nsISound> soundInterface = do_CreateInstance("@mozilla.org/sound;1");
2052
        if (soundInterface)
2053
          soundInterface->Beep();
2054
#endif  // #ifdef XP_WIN
2055
      }
2056
0
    }
2057
0
    return nullptr;
2058
0
  }
2059
0
  else {
2060
0
    char16_t uniChar = ToLowerCase(static_cast<char16_t>(charCode));
2061
0
    if (isMenu) {
2062
0
      // Menu supports only first-letter navigation
2063
0
      mIncrementalString = uniChar;
2064
0
    } else if (IsWithinIncrementalTime(keyTime)) {
2065
0
      mIncrementalString.Append(uniChar);
2066
0
    } else {
2067
0
      // Interval too long, treat as new typing
2068
0
      mIncrementalString = uniChar;
2069
0
    }
2070
0
  }
2071
0
2072
0
  // See bug 188199 & 192346, if all letters in incremental string are same, just try to match the first one
2073
0
  nsAutoString incrementalString(mIncrementalString);
2074
0
  uint32_t charIndex = 1, stringLength = incrementalString.Length();
2075
0
  while (charIndex < stringLength && incrementalString[charIndex] == incrementalString[charIndex - 1]) {
2076
0
    charIndex++;
2077
0
  }
2078
0
  if (charIndex == stringLength) {
2079
0
    incrementalString.Truncate(1);
2080
0
    stringLength = 1;
2081
0
  }
2082
0
2083
0
  sLastKeyTime = keyTime;
2084
0
2085
0
  // NOTE: If you crashed here due to a bogus |immediateParent| it is
2086
0
  //       possible that the menu whose shortcut is being looked up has
2087
0
  //       been destroyed already.  One strategy would be to
2088
0
  //       setTimeout(<func>,0) as detailed in:
2089
0
  //       <http://bugzilla.mozilla.org/show_bug.cgi?id=126675#c32>
2090
0
  nsIFrame* firstMenuItem = nsXULPopupManager::GetNextMenuItem(immediateParent, nullptr, true, false);
2091
0
  nsIFrame* currFrame = firstMenuItem;
2092
0
2093
0
  int32_t menuAccessKey = -1;
2094
0
  nsMenuBarListener::GetMenuAccessKey(&menuAccessKey);
2095
0
2096
0
  // We start searching from first child. This process is divided into two parts
2097
0
  //   -- before current and after current -- by the current item
2098
0
  while (currFrame) {
2099
0
    nsIContent* current = currFrame->GetContent();
2100
0
    nsAutoString textKey;
2101
0
    bool isShortcut = false;
2102
0
    if (current->IsElement()) {
2103
0
      if (menuAccessKey >= 0) {
2104
0
        // Get the shortcut attribute.
2105
0
        current->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::accesskey,
2106
0
                                      textKey);
2107
0
      }
2108
0
      isShortcut = !textKey.IsEmpty();
2109
0
      if (textKey.IsEmpty()) { // No shortcut, try first letter
2110
0
        current->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::label,
2111
0
                                      textKey);
2112
0
        if (textKey.IsEmpty()) // No label, try another attribute (value)
2113
0
          current->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::value,
2114
0
                                        textKey);
2115
0
      }
2116
0
    }
2117
0
2118
0
    if (StringBeginsWith(textKey, incrementalString,
2119
0
                         nsCaseInsensitiveStringComparator())) {
2120
0
      // mIncrementalString is a prefix of textKey
2121
0
      nsMenuFrame* menu = do_QueryFrame(currFrame);
2122
0
      if (menu) {
2123
0
        // There is one match
2124
0
        matchCount++;
2125
0
        if (isShortcut) {
2126
0
          // There is one shortcut-key match
2127
0
          matchShortcutCount++;
2128
0
          // Record the matched item. If there is only one matched shortcut item, do it
2129
0
          frameShortcut = menu;
2130
0
        }
2131
0
        if (!foundActive) {
2132
0
          // It's a first candidate item located before/on the current item
2133
0
          if (!frameBefore)
2134
0
            frameBefore = menu;
2135
0
        }
2136
0
        else {
2137
0
          // It's a first candidate item located after the current item
2138
0
          if (!frameAfter)
2139
0
            frameAfter = menu;
2140
0
        }
2141
0
      }
2142
0
      else
2143
0
        return nullptr;
2144
0
    }
2145
0
2146
0
    // Get the active status
2147
0
    if (current->IsElement() &&
2148
0
        current->AsElement()->AttrValueIs(kNameSpaceID_None,
2149
0
                                          nsGkAtoms::menuactive,
2150
0
                                          nsGkAtoms::_true, eCaseMatters)) {
2151
0
      foundActive = true;
2152
0
      if (stringLength > 1) {
2153
0
        // If there is more than one char typed, the current item has highest priority,
2154
0
        //   otherwise the item next to current has highest priority
2155
0
        if (currFrame == frameBefore)
2156
0
          return frameBefore;
2157
0
      }
2158
0
    }
2159
0
2160
0
    nsMenuFrame* menu = do_QueryFrame(currFrame);
2161
0
    currFrame = nsXULPopupManager::GetNextMenuItem(immediateParent, menu, true, true);
2162
0
    if (currFrame == firstMenuItem)
2163
0
      break;
2164
0
  }
2165
0
2166
0
  doAction = (isMenu && (matchCount == 1 || matchShortcutCount == 1));
2167
0
2168
0
  if (matchShortcutCount == 1) // We have one matched shortcut item
2169
0
    return frameShortcut;
2170
0
  if (frameAfter) // If we have matched item after the current, use it
2171
0
    return frameAfter;
2172
0
  else if (frameBefore) // If we haven't, use the item before the current
2173
0
    return frameBefore;
2174
0
2175
0
  // If we don't match anything, rollback the last typing
2176
0
  mIncrementalString.SetLength(mIncrementalString.Length() - 1);
2177
0
2178
0
  // didn't find a matching menu item
2179
#ifdef XP_WIN
2180
  // behavior on Windows - this item is in a menu popup off of the
2181
  // menu bar, so beep and do nothing else
2182
  if (isMenu) {
2183
    nsCOMPtr<nsISound> soundInterface = do_CreateInstance("@mozilla.org/sound;1");
2184
    if (soundInterface)
2185
      soundInterface->Beep();
2186
  }
2187
#endif  // #ifdef XP_WIN
2188
2189
0
  return nullptr;
2190
0
}
2191
2192
void
2193
nsMenuPopupFrame::LockMenuUntilClosed(bool aLock)
2194
0
{
2195
0
  mIsMenuLocked = aLock;
2196
0
2197
0
  // Lock / unlock the parent, too.
2198
0
  nsMenuFrame* menu = do_QueryFrame(GetParent());
2199
0
  if (menu) {
2200
0
    nsMenuParent* parentParent = menu->GetMenuParent();
2201
0
    if (parentParent) {
2202
0
      parentParent->LockMenuUntilClosed(aLock);
2203
0
    }
2204
0
  }
2205
0
}
2206
2207
nsIWidget*
2208
nsMenuPopupFrame::GetWidget()
2209
0
{
2210
0
  nsView * view = GetRootViewForPopup(this);
2211
0
  if (!view)
2212
0
    return nullptr;
2213
0
2214
0
  return view->GetWidget();
2215
0
}
2216
2217
// helpers /////////////////////////////////////////////////////////////
2218
2219
nsresult
2220
nsMenuPopupFrame::AttributeChanged(int32_t aNameSpaceID,
2221
                                   nsAtom* aAttribute,
2222
                                   int32_t aModType)
2223
2224
0
{
2225
0
  nsresult rv = nsBoxFrame::AttributeChanged(aNameSpaceID, aAttribute,
2226
0
                                             aModType);
2227
0
2228
0
  if (aAttribute == nsGkAtoms::left || aAttribute == nsGkAtoms::top)
2229
0
    MoveToAttributePosition();
2230
0
2231
0
  if (aAttribute == nsGkAtoms::remote) {
2232
0
    // When the remote attribute changes, we need to create a new widget to
2233
0
    // ensure that it has the correct compositor and transparency settings to
2234
0
    // match the new value.
2235
0
    EnsureWidget(true);
2236
0
  }
2237
0
2238
0
  if (aAttribute == nsGkAtoms::followanchor) {
2239
0
    nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
2240
0
    if (pm) {
2241
0
      pm->UpdateFollowAnchor(this);
2242
0
    }
2243
0
  }
2244
0
2245
0
  if (aAttribute == nsGkAtoms::label) {
2246
0
    // set the label for the titlebar
2247
0
    nsView* view = GetView();
2248
0
    if (view) {
2249
0
      nsIWidget* widget = view->GetWidget();
2250
0
      if (widget) {
2251
0
        nsAutoString title;
2252
0
        mContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::label, title);
2253
0
        if (!title.IsEmpty()) {
2254
0
          widget->SetTitle(title);
2255
0
        }
2256
0
      }
2257
0
    }
2258
0
  } else if (aAttribute == nsGkAtoms::ignorekeys) {
2259
0
    nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
2260
0
    if (pm) {
2261
0
      nsAutoString ignorekeys;
2262
0
      mContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::ignorekeys, ignorekeys);
2263
0
      pm->UpdateIgnoreKeys(ignorekeys.EqualsLiteral("true"));
2264
0
    }
2265
0
  }
2266
0
2267
0
  return rv;
2268
0
}
2269
2270
void
2271
nsMenuPopupFrame::MoveToAttributePosition()
2272
0
{
2273
0
  // Move the widget around when the user sets the |left| and |top| attributes.
2274
0
  // Note that this is not the best way to move the widget, as it results in lots
2275
0
  // of FE notifications and is likely to be slow as molasses. Use |moveTo| on
2276
0
  // the element if possible.
2277
0
  nsAutoString left, top;
2278
0
  mContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::left, left);
2279
0
  mContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::top, top);
2280
0
  nsresult err1, err2;
2281
0
  mozilla::CSSIntPoint pos(left.ToInteger(&err1), top.ToInteger(&err2));
2282
0
2283
0
  if (NS_SUCCEEDED(err1) && NS_SUCCEEDED(err2))
2284
0
    MoveTo(pos, false);
2285
0
}
2286
2287
void
2288
nsMenuPopupFrame::DestroyFrom(nsIFrame* aDestructRoot, PostDestroyData& aPostDestroyData)
2289
0
{
2290
0
  if (mReflowCallbackData.mPosted) {
2291
0
    PresShell()->CancelReflowCallback(this);
2292
0
    mReflowCallbackData.Clear();
2293
0
  }
2294
0
2295
0
  nsMenuFrame* menu = do_QueryFrame(GetParent());
2296
0
  if (menu) {
2297
0
    // clear the open attribute on the parent menu
2298
0
    nsContentUtils::AddScriptRunner(
2299
0
      new nsUnsetAttrRunnable(menu->GetContent()->AsElement(), nsGkAtoms::open));
2300
0
  }
2301
0
2302
0
  ClearPopupShownDispatcher();
2303
0
2304
0
  nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
2305
0
  if (pm)
2306
0
    pm->PopupDestroyed(this);
2307
0
2308
0
  nsIPopupContainer* popupContainer =
2309
0
    nsIPopupContainer::GetPopupContainer(PresContext()->GetPresShell());
2310
0
  if (popupContainer && popupContainer->GetDefaultTooltip() == mContent) {
2311
0
    popupContainer->SetDefaultTooltip(nullptr);
2312
0
  }
2313
0
2314
0
  nsBoxFrame::DestroyFrom(aDestructRoot, aPostDestroyData);
2315
0
}
2316
2317
2318
void
2319
nsMenuPopupFrame::MoveTo(const CSSIntPoint& aPos, bool aUpdateAttrs)
2320
0
{
2321
0
  nsIWidget* widget = GetWidget();
2322
0
  if ((mScreenRect.x == aPos.x && mScreenRect.y == aPos.y) &&
2323
0
      (!widget || widget->GetClientOffset() == mLastClientOffset)) {
2324
0
    return;
2325
0
  }
2326
0
2327
0
  // reposition the popup at the specified coordinates. Don't clear the anchor
2328
0
  // and position, because the popup can be reset to its anchor position by
2329
0
  // using (-1, -1) as coordinates. Subtract off the margin as it will be
2330
0
  // added to the position when SetPopupPosition is called.
2331
0
  nsMargin margin(0, 0, 0, 0);
2332
0
  StyleMargin()->GetMargin(margin);
2333
0
2334
0
  // Workaround for bug 788189.  See also bug 708278 comment #25 and following.
2335
0
  if (mAdjustOffsetForContextMenu) {
2336
0
    margin.left += nsPresContext::CSSPixelsToAppUnits(LookAndFeel::GetInt(
2337
0
                     LookAndFeel::eIntID_ContextMenuOffsetHorizontal));
2338
0
    margin.top += nsPresContext::CSSPixelsToAppUnits(LookAndFeel::GetInt(
2339
0
                     LookAndFeel::eIntID_ContextMenuOffsetVertical));
2340
0
  }
2341
0
2342
0
  mAnchorType = MenuPopupAnchorType_Point;
2343
0
  mScreenRect.x = aPos.x - nsPresContext::AppUnitsToIntCSSPixels(margin.left);
2344
0
  mScreenRect.y = aPos.y - nsPresContext::AppUnitsToIntCSSPixels(margin.top);
2345
0
2346
0
  SetPopupPosition(nullptr, true, false, true);
2347
0
2348
0
  RefPtr<Element> popup = mContent->AsElement();
2349
0
  if (aUpdateAttrs && (popup->HasAttr(kNameSpaceID_None, nsGkAtoms::left) ||
2350
0
                       popup->HasAttr(kNameSpaceID_None, nsGkAtoms::top)))
2351
0
  {
2352
0
    nsAutoString left, top;
2353
0
    left.AppendInt(aPos.x);
2354
0
    top.AppendInt(aPos.y);
2355
0
    popup->SetAttr(kNameSpaceID_None, nsGkAtoms::left, left, false);
2356
0
    popup->SetAttr(kNameSpaceID_None, nsGkAtoms::top, top, false);
2357
0
  }
2358
0
}
2359
2360
void
2361
nsMenuPopupFrame::MoveToAnchor(nsIContent* aAnchorContent,
2362
                               const nsAString& aPosition,
2363
                               int32_t aXPos, int32_t aYPos,
2364
                               bool aAttributesOverride)
2365
0
{
2366
0
  NS_ASSERTION(IsVisible(), "popup must be visible to move it");
2367
0
2368
0
  nsPopupState oldstate = mPopupState;
2369
0
  InitializePopup(aAnchorContent, mTriggerContent, aPosition,
2370
0
                  aXPos, aYPos, MenuPopupAnchorType_Node, aAttributesOverride);
2371
0
  // InitializePopup changed the state so reset it.
2372
0
  mPopupState = oldstate;
2373
0
2374
0
  // Pass false here so that flipping and adjusting to fit on the screen happen.
2375
0
  SetPopupPosition(nullptr, false, false, true);
2376
0
}
2377
2378
bool
2379
nsMenuPopupFrame::GetAutoPosition()
2380
0
{
2381
0
  return mShouldAutoPosition;
2382
0
}
2383
2384
void
2385
nsMenuPopupFrame::SetAutoPosition(bool aShouldAutoPosition)
2386
0
{
2387
0
  mShouldAutoPosition = aShouldAutoPosition;
2388
0
  nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
2389
0
  if (pm) {
2390
0
    pm->UpdateFollowAnchor(this);
2391
0
  }
2392
0
}
2393
2394
int8_t
2395
nsMenuPopupFrame::GetAlignmentPosition() const
2396
0
{
2397
0
  // The code below handles most cases of alignment, anchor and position values. Those that are
2398
0
  // not handled just return POPUPPOSITION_UNKNOWN.
2399
0
2400
0
  if (mPosition == POPUPPOSITION_OVERLAP || mPosition == POPUPPOSITION_AFTERPOINTER ||
2401
0
      mPosition == POPUPPOSITION_SELECTION)
2402
0
    return mPosition;
2403
0
2404
0
  int8_t position = mPosition;
2405
0
2406
0
  if (position == POPUPPOSITION_UNKNOWN) {
2407
0
    switch (mPopupAnchor) {
2408
0
      case POPUPALIGNMENT_BOTTOMCENTER:
2409
0
        position = mPopupAlignment == POPUPALIGNMENT_TOPRIGHT ?
2410
0
                     POPUPPOSITION_AFTEREND : POPUPPOSITION_AFTERSTART;
2411
0
        break;
2412
0
      case POPUPALIGNMENT_TOPCENTER:
2413
0
        position = mPopupAlignment == POPUPALIGNMENT_BOTTOMRIGHT ?
2414
0
                     POPUPPOSITION_BEFOREEND : POPUPPOSITION_BEFORESTART;
2415
0
        break;
2416
0
      case POPUPALIGNMENT_LEFTCENTER:
2417
0
        position = mPopupAlignment == POPUPALIGNMENT_BOTTOMRIGHT ?
2418
0
                     POPUPPOSITION_STARTAFTER : POPUPPOSITION_STARTBEFORE;
2419
0
        break;
2420
0
      case POPUPALIGNMENT_RIGHTCENTER:
2421
0
        position = mPopupAlignment == POPUPALIGNMENT_BOTTOMLEFT ?
2422
0
                     POPUPPOSITION_ENDAFTER : POPUPPOSITION_ENDBEFORE;
2423
0
        break;
2424
0
      default:
2425
0
        break;
2426
0
    }
2427
0
  }
2428
0
2429
0
  if (mHFlip) {
2430
0
    position = POPUPPOSITION_HFLIP(position);
2431
0
  }
2432
0
2433
0
  if (mVFlip) {
2434
0
    position = POPUPPOSITION_VFLIP(position);
2435
0
  }
2436
0
2437
0
  return position;
2438
0
}
2439
2440
/**
2441
 * KEEP THIS IN SYNC WITH nsFrame::CreateView
2442
 * as much as possible. Until we get rid of views finally...
2443
 */
2444
void
2445
nsMenuPopupFrame::CreatePopupView()
2446
0
{
2447
0
  if (HasView()) {
2448
0
    return;
2449
0
  }
2450
0
2451
0
  nsViewManager* viewManager = PresContext()->GetPresShell()->GetViewManager();
2452
0
  NS_ASSERTION(nullptr != viewManager, "null view manager");
2453
0
2454
0
  // Create a view
2455
0
  nsView* parentView = viewManager->GetRootView();
2456
0
  nsViewVisibility visibility = nsViewVisibility_kHide;
2457
0
  int32_t zIndex = INT32_MAX;
2458
0
  bool    autoZIndex = false;
2459
0
2460
0
  NS_ASSERTION(parentView, "no parent view");
2461
0
2462
0
  // Create a view
2463
0
  nsView *view = viewManager->CreateView(GetRect(), parentView, visibility);
2464
0
  viewManager->SetViewZIndex(view, autoZIndex, zIndex);
2465
0
  // XXX put view last in document order until we can do better
2466
0
  viewManager->InsertChild(parentView, view, nullptr, true);
2467
0
2468
0
  // Remember our view
2469
0
  SetView(view);
2470
0
2471
0
  NS_FRAME_LOG(NS_FRAME_TRACE_CALLS,
2472
0
    ("nsMenuPopupFrame::CreatePopupView: frame=%p view=%p", this, view));
2473
0
}
2474
2475
bool
2476
nsMenuPopupFrame::ShouldFollowAnchor()
2477
0
{
2478
0
  if (!mShouldAutoPosition ||
2479
0
      mAnchorType != MenuPopupAnchorType_Node || !mAnchorContent) {
2480
0
    return false;
2481
0
  }
2482
0
2483
0
  // Follow anchor mode is used when followanchor="true" is set or for arrow panels.
2484
0
  if (mContent->AsElement()->AttrValueIs(kNameSpaceID_None,
2485
0
                                         nsGkAtoms::followanchor,
2486
0
                                         nsGkAtoms::_true, eCaseMatters)) {
2487
0
    return true;
2488
0
  }
2489
0
2490
0
  if (mContent->AsElement()->AttrValueIs(kNameSpaceID_None,
2491
0
                                         nsGkAtoms::followanchor,
2492
0
                                         nsGkAtoms::_false, eCaseMatters)) {
2493
0
    return false;
2494
0
  }
2495
0
2496
0
  return (mPopupType == ePopupTypePanel &&
2497
0
          mContent->AsElement()->AttrValueIs(kNameSpaceID_None, nsGkAtoms::type,
2498
0
                                             nsGkAtoms::arrow, eCaseMatters));
2499
0
}
2500
2501
bool
2502
nsMenuPopupFrame::ShouldFollowAnchor(nsRect& aRect)
2503
0
{
2504
0
  if (!ShouldFollowAnchor()) {
2505
0
    return false;
2506
0
  }
2507
0
2508
0
  nsIFrame* anchorFrame = mAnchorContent->GetPrimaryFrame();
2509
0
  if (anchorFrame) {
2510
0
    nsPresContext* rootPresContext = PresContext()->GetRootPresContext();
2511
0
    if (rootPresContext) {
2512
0
      aRect = ComputeAnchorRect(rootPresContext, anchorFrame);
2513
0
    }
2514
0
  }
2515
0
2516
0
  return true;
2517
0
}
2518
2519
void
2520
nsMenuPopupFrame::CheckForAnchorChange(nsRect& aRect)
2521
0
{
2522
0
  // Don't update if the popup isn't visible or we shouldn't be following the anchor.
2523
0
  if (!IsVisible() || !ShouldFollowAnchor()) {
2524
0
    return;
2525
0
  }
2526
0
2527
0
  bool shouldHide = false;
2528
0
2529
0
  nsPresContext* rootPresContext = PresContext()->GetRootPresContext();
2530
0
2531
0
  // If the frame for the anchor has gone away, hide the popup.
2532
0
  nsIFrame* anchor = mAnchorContent->GetPrimaryFrame();
2533
0
  if (!anchor || !rootPresContext) {
2534
0
    shouldHide = true;
2535
0
  } else if (!anchor->IsVisibleConsideringAncestors(VISIBILITY_CROSS_CHROME_CONTENT_BOUNDARY)) {
2536
0
    // If the anchor is now inside something that is invisible, hide the popup.
2537
0
    shouldHide = true;
2538
0
  } else {
2539
0
    // If the anchor is now inside a hidden parent popup, hide the popup.
2540
0
    nsIFrame* frame = anchor;
2541
0
    while (frame) {
2542
0
      nsMenuPopupFrame* popup = do_QueryFrame(frame);
2543
0
      if (popup && popup->PopupState() != ePopupShown) {
2544
0
        shouldHide = true;
2545
0
        break;
2546
0
      }
2547
0
2548
0
      frame = frame->GetParent();
2549
0
    }
2550
0
  }
2551
0
2552
0
  if (shouldHide) {
2553
0
    nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
2554
0
    if (pm) {
2555
0
      // As the caller will be iterating over the open popups, hide asyncronously.
2556
0
      pm->HidePopup(mContent, false, true, true, false);
2557
0
    }
2558
0
2559
0
    return;
2560
0
  }
2561
0
2562
0
  nsRect anchorRect = ComputeAnchorRect(rootPresContext, anchor);
2563
0
2564
0
  // If the rectangles are different, move the popup.
2565
0
  if (!anchorRect.IsEqualEdges(aRect)) {
2566
0
    aRect = anchorRect;
2567
0
    SetPopupPosition(nullptr, true, false, true);
2568
0
  }
2569
0
}