tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

nsMenuPopupFrame.cpp (96394B)


      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 
      9 #include <algorithm>
     10 
     11 #include "LayoutConstants.h"
     12 #include "WindowRenderer.h"
     13 #include "X11UndefineNone.h"
     14 #include "XULButtonElement.h"
     15 #include "XULPopupElement.h"
     16 #include "mozilla/AnimationUtils.h"
     17 #include "mozilla/BasePrincipal.h"
     18 #include "mozilla/ClearOnShutdown.h"
     19 #include "mozilla/ComputedStyle.h"
     20 #include "mozilla/EventDispatcher.h"
     21 #include "mozilla/EventStateManager.h"
     22 #include "mozilla/LookAndFeel.h"
     23 #include "mozilla/MouseEvents.h"
     24 #include "mozilla/Preferences.h"
     25 #include "mozilla/PresShell.h"
     26 #include "mozilla/ScrollContainerFrame.h"
     27 #include "mozilla/Services.h"
     28 #include "mozilla/dom/BrowserParent.h"
     29 #include "mozilla/dom/Document.h"
     30 #include "mozilla/dom/Element.h"
     31 #include "mozilla/dom/Event.h"
     32 #include "mozilla/dom/KeyboardEvent.h"
     33 #include "mozilla/dom/KeyboardEventBinding.h"
     34 #include "mozilla/dom/XULPopupElement.h"
     35 #include "mozilla/widget/ScreenManager.h"
     36 #include "nsAtom.h"
     37 #include "nsCSSFrameConstructor.h"
     38 #include "nsCSSRendering.h"
     39 #include "nsContentUtils.h"
     40 #include "nsDisplayList.h"
     41 #include "nsExpirationTracker.h"
     42 #include "nsFrameManager.h"
     43 #include "nsGkAtoms.h"
     44 #include "nsIBaseWindow.h"
     45 #include "nsIContent.h"
     46 #include "nsIDOMXULSelectCntrlEl.h"
     47 #include "nsIDocShell.h"
     48 #include "nsIDocShellTreeOwner.h"
     49 #include "nsIFrameInlines.h"
     50 #include "nsIPopupContainer.h"
     51 #include "nsIReflowCallback.h"
     52 #include "nsIScreenManager.h"
     53 #include "nsISound.h"
     54 #include "nsLayoutUtils.h"
     55 #include "nsNameSpaceManager.h"
     56 #include "nsPIDOMWindow.h"
     57 #include "nsPIWindowRoot.h"
     58 #include "nsPresContext.h"
     59 #include "nsReadableUtils.h"
     60 #include "nsRect.h"
     61 #include "nsServiceManagerUtils.h"
     62 #include "nsStyleConsts.h"
     63 #include "nsStyleStructInlines.h"
     64 #include "nsTransitionManager.h"
     65 #include "nsUnicharUtils.h"
     66 #include "nsWidgetsCID.h"
     67 #include "nsXULPopupManager.h"
     68 
     69 using namespace mozilla;
     70 using namespace mozilla::widget;
     71 using mozilla::dom::Document;
     72 using mozilla::dom::Element;
     73 using mozilla::dom::Event;
     74 using mozilla::dom::XULButtonElement;
     75 
     76 TimeStamp nsMenuPopupFrame::sLastKeyTime;
     77 
     78 #ifdef MOZ_WAYLAND
     79 #  include "mozilla/WidgetUtilsGtk.h"
     80 #  define IS_WAYLAND_DISPLAY() mozilla::widget::GdkIsWaylandDisplay()
     81 extern mozilla::LazyLogModule gWidgetPopupLog;
     82 #  define LOG_WAYLAND(...) \
     83    MOZ_LOG(gWidgetPopupLog, mozilla::LogLevel::Debug, (__VA_ARGS__))
     84 #  define LOG_WAYLAND_VERBOSE(...) \
     85    MOZ_LOG(gWidgetPopupLog, mozilla::LogLevel::Verbose, (__VA_ARGS__))
     86 #else
     87 #  define IS_WAYLAND_DISPLAY() false
     88 #  define LOG_WAYLAND(...)
     89 #  define LOG_WAYLAND_VERBOSE(...)
     90 #endif
     91 
     92 nsIFrame* NS_NewMenuPopupFrame(PresShell* aPresShell, ComputedStyle* aStyle) {
     93  return new (aPresShell)
     94      nsMenuPopupFrame(aStyle, aPresShell->GetPresContext());
     95 }
     96 
     97 NS_IMPL_FRAMEARENA_HELPERS(nsMenuPopupFrame)
     98 
     99 NS_QUERYFRAME_HEAD(nsMenuPopupFrame)
    100  NS_QUERYFRAME_ENTRY(nsMenuPopupFrame)
    101 NS_QUERYFRAME_TAIL_INHERITING(nsBlockFrame)
    102 
    103 // Three generations of 5000ms (so 15s to get rid of all the closed popups).
    104 class PopupExpirationTracker final
    105    : public nsExpirationTracker<nsMenuPopupFrame, 3> {
    106  static StaticAutoPtr<PopupExpirationTracker> sInstance;
    107 
    108  void NotifyExpired(nsMenuPopupFrame* aPopup) override {
    109    // printf_stderr("PopupExpirationTracker::NotifyExpired(%s)\n",
    110    //               aPopup->ListTag().get());
    111    RemoveObject(aPopup);
    112    aPopup->DestroyWidgetIfNeeded();
    113  }
    114 
    115 public:
    116  PopupExpirationTracker()
    117      : nsExpirationTracker(5000 /* ms */, "PopupExpirationTracker"_ns) {}
    118  static PopupExpirationTracker* Get() { return sInstance.get(); }
    119  static PopupExpirationTracker& GetOrCreate() {
    120    if (!sInstance) {
    121      sInstance = new PopupExpirationTracker();
    122      ClearOnShutdown(&sInstance);
    123    }
    124    return *sInstance;
    125  }
    126 };
    127 StaticAutoPtr<PopupExpirationTracker> PopupExpirationTracker::sInstance;
    128 
    129 nsMenuPopupFrame::nsMenuPopupFrame(ComputedStyle* aStyle,
    130                                   nsPresContext* aPresContext)
    131    : nsBlockFrame(aStyle, aPresContext, kClassID) {}
    132 
    133 nsMenuPopupFrame::~nsMenuPopupFrame() = default;
    134 
    135 static bool IsMouseTransparent(const ComputedStyle& aStyle) {
    136  // If pointer-events: none; is set on the popup, then the widget should
    137  // ignore mouse events, passing them through to the content behind.
    138  return aStyle.PointerEvents() == StylePointerEvents::None;
    139 }
    140 
    141 static nsIWidget::InputRegion ComputeInputRegion(const ComputedStyle& aStyle,
    142                                                 const nsPresContext& aPc) {
    143  return {IsMouseTransparent(aStyle),
    144          (aStyle.StyleUIReset()->mMozWindowInputRegionMargin.ToCSSPixels() *
    145           aPc.CSSToDevPixelScale())
    146              .Truncated()};
    147 }
    148 
    149 bool nsMenuPopupFrame::IsDragPopup() const {
    150  return !mInContentShell && mPopupType == PopupType::Panel &&
    151         mContent->AsElement()->AttrValueIs(kNameSpaceID_None, nsGkAtoms::type,
    152                                            nsGkAtoms::drag, eIgnoreCase);
    153 }
    154 
    155 bool nsMenuPopupFrame::ShouldHaveWidgetWhenHidden() const {
    156  if (mContent->AsElement()->HasAttr(nsGkAtoms::neverhidden)) {
    157    // Create a widget upfront for panels that never hide frames for their
    158    // contents (like web extension popups). These, for now, need to create the
    159    // widgets upfront, so that the frames inside the popup don't get
    160    // "reparented" in the widget tree.
    161    //
    162    // TODO(emilio, bug 1976324): Try to somehow remove this special-case,
    163    // web-ext panel needs it to compute the "natural" bounds of their contents
    164    // before showing the popup, but that seems like it could be tweaked.
    165    return true;
    166  }
    167  if (IsDragPopup()) {
    168    // Create widgets upfront for the drag popup for now, see bug 1976623.
    169    return true;
    170  }
    171  return false;
    172 }
    173 
    174 void nsMenuPopupFrame::Init(nsIContent* aContent, nsContainerFrame* aParent,
    175                            nsIFrame* aPrevInFlow) {
    176  nsBlockFrame::Init(aContent, aParent, aPrevInFlow);
    177 
    178  const auto& el = PopupElement();
    179  mPopupType = PopupType::Panel;
    180  if (el.IsMenu()) {
    181    mPopupType = PopupType::Menu;
    182  } else if (el.IsXULElement(nsGkAtoms::tooltip)) {
    183    mPopupType = PopupType::Tooltip;
    184  }
    185 
    186  if (PresContext()->IsChrome()) {
    187    mInContentShell = false;
    188  }
    189 
    190  // Support incontentshell=false attribute to allow popups to be displayed
    191  // outside of the content shell. Chrome only.
    192  if (el.NodePrincipal()->IsSystemPrincipal()) {
    193    if (el.GetXULBoolAttr(nsGkAtoms::incontentshell)) {
    194      mInContentShell = true;
    195    } else if (el.AttrValueIs(kNameSpaceID_None, nsGkAtoms::incontentshell,
    196                              nsGkAtoms::_false, eCaseMatters)) {
    197      mInContentShell = false;
    198    }
    199  }
    200 
    201  // To improve performance, create the widget for the popup if needed. Popups
    202  // such as menus will create their widgets later when the popup opens.
    203  if (!mWidget && ShouldHaveWidgetWhenHidden()) {
    204    CreateWidget();
    205  }
    206 
    207  AddStateBits(NS_FRAME_IN_POPUP);
    208 }
    209 
    210 bool nsMenuPopupFrame::HasRemoteContent() const {
    211  return !mInContentShell && mPopupType == PopupType::Panel &&
    212         mContent->AsElement()->AttrValueIs(kNameSpaceID_None,
    213                                            nsGkAtoms::remote, nsGkAtoms::_true,
    214                                            eIgnoreCase);
    215 }
    216 
    217 bool nsMenuPopupFrame::IsNoAutoHide() const {
    218  // Panels with noautohide="true" don't hide when the mouse is clicked
    219  // outside of them, or when another application is made active. Non-autohide
    220  // panels cannot be used in content windows.
    221  return !mInContentShell && mPopupType == PopupType::Panel &&
    222         mContent->AsElement()->AttrValueIs(kNameSpaceID_None,
    223                                            nsGkAtoms::noautohide,
    224                                            nsGkAtoms::_true, eIgnoreCase);
    225 }
    226 
    227 widget::PopupLevel nsMenuPopupFrame::GetPopupLevel(bool aIsNoAutoHide) const {
    228  // The popup level is determined as follows, in this order:
    229  //   1. non-panels (menus and tooltips) are always topmost
    230  //   2. any specified level attribute
    231  //   3. if a titlebar attribute is set, use the 'floating' level
    232  //   4. if this is a noautohide panel, use the 'parent' level
    233  //   5. use the platform-specific default level
    234 
    235  // If this is not a panel, this is always a top-most popup.
    236  if (mPopupType != PopupType::Panel) {
    237    return PopupLevel::Top;
    238  }
    239 
    240  // If the level attribute has been set, use that.
    241  static Element::AttrValuesArray strings[] = {nsGkAtoms::top,
    242                                               nsGkAtoms::parent, nullptr};
    243  switch (mContent->AsElement()->FindAttrValueIn(
    244      kNameSpaceID_None, nsGkAtoms::level, strings, eCaseMatters)) {
    245    case 0:
    246      return PopupLevel::Top;
    247    case 1:
    248      return PopupLevel::Parent;
    249    default:
    250      break;
    251  }
    252 
    253  // If this panel is a noautohide panel, the default is the parent level.
    254  if (aIsNoAutoHide) {
    255    return PopupLevel::Parent;
    256  }
    257 
    258  // Otherwise, the result depends on the platform.
    259  return StaticPrefs::ui_panel_default_level_parent() ? PopupLevel::Parent
    260                                                      : PopupLevel::Top;
    261 }
    262 
    263 void nsMenuPopupFrame::PrepareWidget(bool aForceRecreate) {
    264  if (mExpirationState.IsTracked()) {
    265    PopupExpirationTracker::Get()->RemoveObject(this);
    266  }
    267  if (auto* widget = GetWidget()) {
    268    nsCOMPtr<nsIWidget> parent = ComputeParentWidget();
    269    if (aForceRecreate || widget->GetParent() != parent ||
    270        widget->NeedsRecreateToReshow()) {
    271      DestroyWidget();
    272    }
    273  }
    274  if (!mWidget) {
    275    CreateWidget();
    276  } else {
    277    PropagateStyleToWidget();
    278  }
    279 }
    280 
    281 already_AddRefed<nsIWidget> nsMenuPopupFrame::ComputeParentWidget() const {
    282  auto popupLevel = GetPopupLevel(IsNoAutoHide());
    283  // Panels which have a parent level need a parent widget. This allows them to
    284  // always appear in front of the parent window but behind other windows that
    285  // should be in front of it.
    286  nsCOMPtr<nsIWidget> parentWidget;
    287  if (popupLevel != PopupLevel::Top) {
    288    nsCOMPtr<nsIDocShellTreeItem> dsti = PresContext()->GetDocShell();
    289    if (!dsti) {
    290      return nullptr;
    291    }
    292 
    293    nsCOMPtr<nsIDocShellTreeOwner> treeOwner;
    294    dsti->GetTreeOwner(getter_AddRefs(treeOwner));
    295    if (!treeOwner) {
    296      return nullptr;
    297    }
    298 
    299    if (nsCOMPtr<nsIBaseWindow> baseWindow = do_QueryInterface(treeOwner)) {
    300      parentWidget = baseWindow->GetMainWidget();
    301    }
    302  }
    303  if (!parentWidget) {
    304    parentWidget = GetParent()->GetNearestWidget();
    305  }
    306  return parentWidget.forget();
    307 }
    308 
    309 void nsMenuPopupFrame::CreateWidget() {
    310  // Create a widget for ourselves.
    311  widget::InitData widgetData;
    312  widgetData.mWindowType = widget::WindowType::Popup;
    313  widgetData.mBorderStyle = widget::BorderStyle::Default;
    314  widgetData.mClipSiblings = true;
    315  widgetData.mPopupHint = mPopupType;
    316  widgetData.mIsDragPopup = IsDragPopup();
    317 
    318  const bool remote = HasRemoteContent();
    319 
    320  const auto mode = nsLayoutUtils::GetFrameTransparency(this, this);
    321  widgetData.mHasRemoteContent = remote;
    322  widgetData.mTransparencyMode = mode;
    323  widgetData.mPopupLevel = GetPopupLevel(IsNoAutoHide());
    324 
    325  nsCOMPtr<nsIWidget> parentWidget = ComputeParentWidget();
    326  if (NS_WARN_IF(!parentWidget)) {
    327    return;
    328  }
    329 
    330  mWidget = parentWidget->CreateChild(CalcWidgetBounds(), widgetData);
    331  if (NS_WARN_IF(!mWidget)) {
    332    return;
    333  }
    334  mWidget->SetWidgetListener(this);
    335  mWidget->EnableDragDrop(true);
    336  // TODO(emilio): Make all widgets look at widgetData.mTransparencyMode
    337  // (maybe in BaseCreate?) then remove this call.
    338  mWidget->SetTransparencyMode(mode);
    339  PropagateStyleToWidget();
    340 }
    341 
    342 LayoutDeviceIntRect nsMenuPopupFrame::CalcWidgetBounds() const {
    343  auto a2d = PresContext()->AppUnitsPerDevPixel();
    344  nsPoint offset;
    345  nsIWidget* parentWidget =
    346      PresShell()->GetRootFrame()->GetNearestWidget(offset);
    347  // We want the bounds be relative to the parent widget, in appunits
    348  if (parentWidget) {
    349    // put offset into screen coordinates. (based on client area origin)
    350    offset += LayoutDeviceIntPoint::ToAppUnits(
    351        parentWidget->WidgetToScreenOffset(), a2d);
    352  }
    353  int32_t roundTo =
    354      parentWidget ? parentWidget->RoundsWidgetCoordinatesTo() : 1;
    355  auto bounds = GetRect() + offset;
    356  // We use outside pixels for transparent windows if possible, so that we
    357  // don't truncate the contents. For opaque popups, we use nearest pixels
    358  // which prevents having pixels not drawn by the frame.
    359  const auto transparency = nsLayoutUtils::GetFrameTransparency(this, this);
    360  const bool opaque = transparency == TransparencyMode::Opaque;
    361  const auto idealBounds = LayoutDeviceIntRect::FromUnknownRect(
    362      opaque ? bounds.ToNearestPixels(a2d) : bounds.ToOutsidePixels(a2d));
    363  return nsIWidget::MaybeRoundToDisplayPixels(idealBounds, transparency,
    364                                              roundTo);
    365 }
    366 
    367 void nsMenuPopupFrame::DestroyWidget() {
    368  RefPtr widget = mWidget.forget();
    369  if (!widget) {
    370    return;
    371  }
    372  // Widget's WebRender resources needs to be cleared before creating new
    373  // widget.
    374  widget->ClearCachedWebrenderResources();
    375  widget->SetWidgetListener(nullptr);
    376  NS_DispatchToMainThread(
    377      NewRunnableMethod("DestroyWidget", widget, &nsIWidget::Destroy));
    378 }
    379 
    380 void nsMenuPopupFrame::PropagateStyleToWidget(WidgetStyleFlags aFlags) const {
    381  if (aFlags.isEmpty()) {
    382    return;
    383  }
    384 
    385  nsIWidget* widget = GetWidget();
    386  if (!widget) {
    387    return;
    388  }
    389 
    390  if (aFlags.contains(WidgetStyle::ColorScheme)) {
    391    widget->SetColorScheme(Some(LookAndFeel::ColorSchemeForFrame(this)));
    392  }
    393  if (aFlags.contains(WidgetStyle::InputRegion)) {
    394    widget->SetInputRegion(ComputeInputRegion(*Style(), *PresContext()));
    395  }
    396  if (aFlags.contains(WidgetStyle::Opacity)) {
    397    widget->SetWindowOpacity(StyleUIReset()->mWindowOpacity);
    398  }
    399  if (aFlags.contains(WidgetStyle::Shadow)) {
    400    widget->SetWindowShadowStyle(GetShadowStyle());
    401  }
    402  if (aFlags.contains(WidgetStyle::Transform)) {
    403    widget->SetWindowTransform(ComputeWidgetTransform());
    404  }
    405  if (aFlags.contains(WidgetStyle::MicaBackdrop)) {
    406    widget->SetMicaBackdrop(StyleDisplay()->EffectiveAppearance() ==
    407                            StyleAppearance::Menupopup);
    408  }
    409 }
    410 
    411 bool nsMenuPopupFrame::IsMouseTransparent() const {
    412  return ::IsMouseTransparent(*Style());
    413 }
    414 
    415 WindowShadow nsMenuPopupFrame::GetShadowStyle() const {
    416  StyleWindowShadow shadow = StyleUIReset()->mWindowShadow;
    417  if (shadow != StyleWindowShadow::Auto) {
    418    MOZ_ASSERT(shadow == StyleWindowShadow::None);
    419    return WindowShadow::None;
    420  }
    421 
    422  switch (StyleDisplay()->EffectiveAppearance()) {
    423    case StyleAppearance::Tooltip:
    424      return WindowShadow::Tooltip;
    425    case StyleAppearance::Menupopup:
    426      return WindowShadow::Menu;
    427    default:
    428      return WindowShadow::Panel;
    429  }
    430 }
    431 
    432 void nsMenuPopupFrame::SetPopupState(nsPopupState aState) {
    433  mPopupState = aState;
    434 
    435  // Work around https://gitlab.gnome.org/GNOME/gtk/-/issues/4166
    436  if (aState == ePopupShown && IS_WAYLAND_DISPLAY()) {
    437    if (nsIWidget* widget = GetWidget()) {
    438      widget->SetInputRegion(ComputeInputRegion(*Style(), *PresContext()));
    439    }
    440  }
    441 }
    442 
    443 // TODO: Convert this to MOZ_CAN_RUN_SCRIPT (bug 1415230, bug 1535398)
    444 MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHODIMP nsXULPopupShownEvent::Run() {
    445  nsMenuPopupFrame* popup = do_QueryFrame(mPopup->GetPrimaryFrame());
    446  // Set the state to visible if the popup is still open.
    447  if (popup && popup->IsOpen()) {
    448    popup->SetPopupState(ePopupShown);
    449  }
    450 
    451  if (!mPopup->IsXULElement(nsGkAtoms::tooltip)) {
    452    nsCOMPtr<nsIObserverService> obsService =
    453        mozilla::services::GetObserverService();
    454    if (obsService) {
    455      obsService->NotifyObservers(mPopup, "popup-shown", nullptr);
    456    }
    457  }
    458  WidgetMouseEvent event(true, eXULPopupShown, nullptr,
    459                         WidgetMouseEvent::eReal);
    460  return EventDispatcher::Dispatch(mPopup, mPresContext, &event);
    461 }
    462 
    463 NS_IMETHODIMP nsXULPopupShownEvent::HandleEvent(Event* aEvent) {
    464  nsMenuPopupFrame* popup = do_QueryFrame(mPopup->GetPrimaryFrame());
    465  // Ignore events not targeted at the popup itself (ie targeted at
    466  // descendants):
    467  if (mPopup != aEvent->GetTarget()) {
    468    return NS_OK;
    469  }
    470  if (popup) {
    471    // ResetPopupShownDispatcher will delete the reference to this, so keep
    472    // another one until Run is finished.
    473    RefPtr<nsXULPopupShownEvent> event = this;
    474    // Only call Run if it the dispatcher was assigned. This avoids calling the
    475    // Run method if the transitionend event fires multiple times.
    476    if (popup->ClearPopupShownDispatcher()) {
    477      return Run();
    478    }
    479  }
    480 
    481  CancelListener();
    482  return NS_OK;
    483 }
    484 
    485 void nsXULPopupShownEvent::CancelListener() {
    486  mPopup->RemoveSystemEventListener(u"transitionend"_ns, this, false);
    487 }
    488 
    489 NS_IMPL_ISUPPORTS_INHERITED(nsXULPopupShownEvent, Runnable,
    490                            nsIDOMEventListener);
    491 
    492 void nsMenuPopupFrame::DidSetComputedStyle(ComputedStyle* aOldStyle) {
    493  nsBlockFrame::DidSetComputedStyle(aOldStyle);
    494 
    495  if (!aOldStyle) {
    496    return;
    497  }
    498 
    499  WidgetStyleFlags flags;
    500 
    501  if (aOldStyle->StyleUI()->mColorScheme != StyleUI()->mColorScheme) {
    502    flags += WidgetStyle::ColorScheme;
    503  }
    504 
    505  auto& newUI = *StyleUIReset();
    506  auto& oldUI = *aOldStyle->StyleUIReset();
    507  if (newUI.mWindowOpacity != oldUI.mWindowOpacity) {
    508    flags += WidgetStyle::Opacity;
    509  }
    510 
    511  if (newUI.mMozWindowTransform != oldUI.mMozWindowTransform) {
    512    flags += WidgetStyle::Transform;
    513  }
    514 
    515  if (newUI.mWindowShadow != oldUI.mWindowShadow) {
    516    flags += WidgetStyle::Shadow;
    517  }
    518 
    519  if (aOldStyle->StyleDisplay()->EffectiveAppearance() !=
    520      StyleDisplay()->EffectiveAppearance()) {
    521    flags += WidgetStyle::MicaBackdrop;
    522  }
    523 
    524  const auto& pc = *PresContext();
    525  auto oldRegion = ComputeInputRegion(*aOldStyle, pc);
    526  auto newRegion = ComputeInputRegion(*Style(), pc);
    527  if (oldRegion.mFullyTransparent != newRegion.mFullyTransparent ||
    528      oldRegion.mMargin != newRegion.mMargin) {
    529    flags += WidgetStyle::InputRegion;
    530  }
    531 
    532  PropagateStyleToWidget(flags);
    533 }
    534 
    535 nscoord nsMenuPopupFrame::IntrinsicISize(const IntrinsicSizeInput& aInput,
    536                                         IntrinsicISizeType aType) {
    537  if (CanSkipLayout()) {
    538    return 0;
    539  }
    540  nscoord iSize = nsBlockFrame::IntrinsicISize(aInput, aType);
    541  if (!ShouldExpandToInflowParentOrAnchor()) {
    542    return iSize;
    543  }
    544  // Make sure to accommodate for our scrollbar if needed. Do it only for
    545  // menulists to match previous behavior.
    546  //
    547  // NOTE(emilio): This is somewhat hacky. The "right" fix (which would be
    548  // using scrollbar-gutter: stable on the scroller) isn't great, because even
    549  // though we want a stable gutter, we want to draw on top of the gutter when
    550  // there's no scrollbar, otherwise it looks rather weird.
    551  //
    552  // Automatically accommodating for the scrollbar otherwise would be bug
    553  // 764076, but that has its own set of problems.
    554  if (ScrollContainerFrame* sf = GetScrollContainerFrame()) {
    555    iSize += sf->GetDesiredScrollbarSizes().LeftRight();
    556  }
    557 
    558  nscoord menuListOrAnchorWidth = 0;
    559  if (nsIFrame* menuList = GetInFlowParent()) {
    560    menuListOrAnchorWidth = menuList->GetRect().width;
    561  }
    562  if (mAnchorType == MenuPopupAnchorType::Rect) {
    563    menuListOrAnchorWidth = std::max(menuListOrAnchorWidth, mScreenRect.width);
    564  }
    565  // Input margin doesn't have contents, so account for it for popup sizing
    566  // purposes.
    567  menuListOrAnchorWidth +=
    568      2 * StyleUIReset()->mMozWindowInputRegionMargin.ToAppUnits();
    569 
    570  return std::max(iSize, menuListOrAnchorWidth);
    571 }
    572 
    573 void nsMenuPopupFrame::Reflow(nsPresContext* aPresContext,
    574                              ReflowOutput& aDesiredSize,
    575                              const ReflowInput& aReflowInput,
    576                              nsReflowStatus& aStatus) {
    577  MarkInReflow();
    578  DO_GLOBAL_REFLOW_COUNT("nsMenuPopupFrame");
    579  MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!");
    580 
    581  const auto wm = GetWritingMode();
    582  // Default to preserving our bounds.
    583  aDesiredSize.SetSize(wm, GetLogicalSize(wm));
    584 
    585  LayoutPopup(aPresContext, aDesiredSize, aReflowInput, aStatus);
    586 
    587  aDesiredSize.SetBlockStartAscent(aDesiredSize.BSize(wm));
    588  aDesiredSize.SetOverflowAreasToDesiredBounds();
    589  FinishAndStoreOverflow(&aDesiredSize, aReflowInput.mStyleDisplay);
    590 }
    591 
    592 void nsMenuPopupFrame::EnsureActiveMenuListItemIsVisible() {
    593  if (!IsMenuList() || !IsOpen()) {
    594    return;
    595  }
    596  nsIFrame* frame = GetCurrentMenuItemFrame();
    597  if (!frame) {
    598    return;
    599  }
    600  RefPtr<mozilla::PresShell> presShell = PresShell();
    601  presShell->ScrollFrameIntoView(
    602      frame, Nothing(), ScrollAxis(), ScrollAxis(),
    603      ScrollFlags::ScrollOverflowHidden | ScrollFlags::ScrollFirstAncestorOnly);
    604 }
    605 
    606 bool nsMenuPopupFrame::CanSkipLayout() const {
    607  // If the popup is not open, only do layout while showing or if we're a
    608  // menulist.
    609  //
    610  // The later is needed because the SelectParent code wants to limit the height
    611  // of the popup before opening it.
    612  //
    613  // TODO(emilio): We should consider adding a way to do that more reliably
    614  // instead, but this preserves existing behavior.
    615  return !IsVisibleOrShowing() && !IsMenuList();
    616 }
    617 
    618 void nsMenuPopupFrame::LayoutPopup(nsPresContext* aPresContext,
    619                                   ReflowOutput& aDesiredSize,
    620                                   const ReflowInput& aReflowInput,
    621                                   nsReflowStatus& aStatus) {
    622  if (IsNativeMenu()) {
    623    return;
    624  }
    625 
    626  SchedulePaint();
    627 
    628  const bool isOpen = IsOpen();
    629  if (CanSkipLayout()) {
    630    RemoveStateBits(NS_FRAME_FIRST_REFLOW);
    631    return;
    632  }
    633 
    634  // Do a first reflow, with all our content, in order to find our preferred
    635  // size. Then, we do a second reflow with the updated dimensions.
    636  const bool needsPrefSize = mPrefSize == nsSize(-1, -1) || IsSubtreeDirty();
    637  if (needsPrefSize) {
    638    // Get the preferred, minimum and maximum size. If the menu is sized to the
    639    // popup, then the popup's width is the menu's width.
    640    ReflowOutput preferredSize(aReflowInput);
    641    nsBlockFrame::Reflow(aPresContext, preferredSize, aReflowInput, aStatus);
    642    mPrefSize = preferredSize.PhysicalSize();
    643  }
    644 
    645  // Get our desired position and final size, now that we have a preferred size.
    646  auto constraints = GetRects(mPrefSize);
    647  const auto finalSize = constraints.mUsedRect.Size();
    648 
    649  // We need to do an extra reflow if we haven't reflowed, our size doesn't
    650  // match with our final intended size, or our bsize is unconstrained (in which
    651  // case we need to specify the final size so that percentages work).
    652  const bool needDefiniteReflow =
    653      aReflowInput.ComputedBSize() == NS_UNCONSTRAINEDSIZE || !needsPrefSize ||
    654      finalSize != mPrefSize;
    655 
    656  if (needDefiniteReflow) {
    657    ReflowInput constrainedReflowInput(aReflowInput);
    658    const auto& bp = aReflowInput.ComputedPhysicalBorderPadding();
    659    // TODO: writing-mode handling not terribly correct, but it doesn't matter.
    660    const nsSize finalContentSize(finalSize.width - bp.LeftRight(),
    661                                  finalSize.height - bp.TopBottom());
    662    constrainedReflowInput.SetComputedISize(finalContentSize.width);
    663    constrainedReflowInput.SetComputedBSize(finalContentSize.height);
    664    constrainedReflowInput.SetIResize(finalSize.width != mPrefSize.width);
    665    constrainedReflowInput.SetBResize([&] {
    666      if (finalSize.height != mPrefSize.height) {
    667        return true;
    668      }
    669      if (needsPrefSize &&
    670          aReflowInput.ComputedBSize() == NS_UNCONSTRAINEDSIZE &&
    671          aReflowInput.ComputedMaxBSize() == finalContentSize.height) {
    672        // If we have measured, and maybe clamped our children via max-height,
    673        // they might need to get percentages in the block axis re-resolved.
    674        return true;
    675      }
    676      return false;
    677    }());
    678 
    679    aStatus.Reset();
    680    nsBlockFrame::Reflow(aPresContext, aDesiredSize, constrainedReflowInput,
    681                         aStatus);
    682  }
    683 
    684  if (mIsOpenChanged || !mRect.IsEqualEdges(constraints.mUsedRect)) {
    685    SchedulePendingWidgetMoveResize();
    686  }
    687 
    688  // Set our size, since AbsoluteContainingBlock won't.
    689  SetRect(constraints.mUsedRect);
    690 
    691  if (isOpen) {
    692    if (mPopupState == ePopupOpening) {
    693      mPopupState = ePopupVisible;
    694    }
    695  }
    696 
    697  // Perform our move now. That will position the view and so on.
    698  PerformMove(constraints);
    699 
    700  // finally, if the popup just opened, send a popupshown event
    701  bool openChanged = mIsOpenChanged;
    702  if (openChanged) {
    703    mIsOpenChanged = false;
    704 
    705    // Make sure the current selection in a menulist is visible.
    706    EnsureActiveMenuListItemIsVisible();
    707 
    708    // If the animate attribute is set to open, check for a transition and wait
    709    // for it to finish before firing the popupshown event.
    710    if (LookAndFeel::GetInt(LookAndFeel::IntID::PanelAnimations) &&
    711        mContent->AsElement()->AttrValueIs(kNameSpaceID_None,
    712                                           nsGkAtoms::animate, nsGkAtoms::open,
    713                                           eCaseMatters) &&
    714        AnimationUtils::HasCurrentTransitions(mContent->AsElement())) {
    715      mPopupShownDispatcher = new nsXULPopupShownEvent(mContent, aPresContext);
    716      mContent->AddSystemEventListener(u"transitionend"_ns,
    717                                       mPopupShownDispatcher, false, false);
    718      return;
    719    }
    720 
    721    // If there are no transitions, fire the popupshown event right away.
    722    nsCOMPtr<nsIRunnable> event =
    723        new nsXULPopupShownEvent(GetContent(), aPresContext);
    724    mContent->OwnerDoc()->Dispatch(event.forget());
    725  }
    726 }
    727 
    728 bool nsMenuPopupFrame::IsMenuList() const {
    729  return PopupElement().IsInMenuList();
    730 }
    731 
    732 bool nsMenuPopupFrame::ShouldExpandToInflowParentOrAnchor() const {
    733  return IsMenuList() && !mContent->GetParent()->AsElement()->AttrValueIs(
    734                             kNameSpaceID_None, nsGkAtoms::sizetopopup,
    735                             nsGkAtoms::none, eCaseMatters);
    736 }
    737 
    738 nsIContent* nsMenuPopupFrame::GetTriggerContent(
    739    nsMenuPopupFrame* aMenuPopupFrame) {
    740  while (aMenuPopupFrame) {
    741    if (aMenuPopupFrame->mTriggerContent) {
    742      return aMenuPopupFrame->mTriggerContent;
    743    }
    744 
    745    auto* button = XULButtonElement::FromNodeOrNull(
    746        aMenuPopupFrame->GetContent()->GetParent());
    747    if (!button || !button->IsMenu()) {
    748      break;
    749    }
    750 
    751    auto* popup = button->GetContainingPopupElement();
    752    if (!popup) {
    753      break;
    754    }
    755 
    756    // check up the menu hierarchy until a popup with a trigger node is found
    757    aMenuPopupFrame = do_QueryFrame(popup->GetPrimaryFrame());
    758  }
    759 
    760  return nullptr;
    761 }
    762 
    763 void nsMenuPopupFrame::InitPositionFromAnchorAlign(const nsAString& aAnchor,
    764                                                   const nsAString& aAlign) {
    765  mTriggerContent = nullptr;
    766 
    767  if (aAnchor.EqualsLiteral("topleft")) {
    768    mPopupAnchor = POPUPALIGNMENT_TOPLEFT;
    769  } else if (aAnchor.EqualsLiteral("topright")) {
    770    mPopupAnchor = POPUPALIGNMENT_TOPRIGHT;
    771  } else if (aAnchor.EqualsLiteral("bottomleft")) {
    772    mPopupAnchor = POPUPALIGNMENT_BOTTOMLEFT;
    773  } else if (aAnchor.EqualsLiteral("bottomright")) {
    774    mPopupAnchor = POPUPALIGNMENT_BOTTOMRIGHT;
    775  } else if (aAnchor.EqualsLiteral("leftcenter")) {
    776    mPopupAnchor = POPUPALIGNMENT_LEFTCENTER;
    777  } else if (aAnchor.EqualsLiteral("rightcenter")) {
    778    mPopupAnchor = POPUPALIGNMENT_RIGHTCENTER;
    779  } else if (aAnchor.EqualsLiteral("topcenter")) {
    780    mPopupAnchor = POPUPALIGNMENT_TOPCENTER;
    781  } else if (aAnchor.EqualsLiteral("bottomcenter")) {
    782    mPopupAnchor = POPUPALIGNMENT_BOTTOMCENTER;
    783  } else {
    784    mPopupAnchor = POPUPALIGNMENT_NONE;
    785  }
    786 
    787  if (aAlign.EqualsLiteral("topleft")) {
    788    mPopupAlignment = POPUPALIGNMENT_TOPLEFT;
    789  } else if (aAlign.EqualsLiteral("topright")) {
    790    mPopupAlignment = POPUPALIGNMENT_TOPRIGHT;
    791  } else if (aAlign.EqualsLiteral("bottomleft")) {
    792    mPopupAlignment = POPUPALIGNMENT_BOTTOMLEFT;
    793  } else if (aAlign.EqualsLiteral("bottomright")) {
    794    mPopupAlignment = POPUPALIGNMENT_BOTTOMRIGHT;
    795  } else if (aAlign.EqualsLiteral("leftcenter")) {
    796    mPopupAlignment = POPUPALIGNMENT_LEFTCENTER;
    797  } else if (aAlign.EqualsLiteral("rightcenter")) {
    798    mPopupAlignment = POPUPALIGNMENT_RIGHTCENTER;
    799  } else if (aAlign.EqualsLiteral("topcenter")) {
    800    mPopupAlignment = POPUPALIGNMENT_TOPCENTER;
    801  } else if (aAlign.EqualsLiteral("bottomcenter")) {
    802    mPopupAlignment = POPUPALIGNMENT_BOTTOMCENTER;
    803  } else {
    804    mPopupAlignment = POPUPALIGNMENT_NONE;
    805  }
    806 
    807  mPosition = POPUPPOSITION_UNKNOWN;
    808 }
    809 
    810 static FlipType FlipFromAttribute(nsMenuPopupFrame* aFrame) {
    811  nsAutoString flip;
    812  aFrame->PopupElement().GetAttr(nsGkAtoms::flip, flip);
    813  if (flip.EqualsLiteral("none")) {
    814    return FlipType::None;
    815  }
    816  if (flip.EqualsLiteral("both")) {
    817    return FlipType::Both;
    818  }
    819  if (flip.EqualsLiteral("slide")) {
    820    return FlipType::Slide;
    821  }
    822  return FlipType::Default;
    823 }
    824 
    825 void nsMenuPopupFrame::InitializePopup(nsIContent* aAnchorContent,
    826                                       nsIContent* aTriggerContent,
    827                                       const nsAString& aPosition,
    828                                       int32_t aXPos, int32_t aYPos,
    829                                       MenuPopupAnchorType aAnchorType,
    830                                       bool aAttributesOverride) {
    831  PrepareWidget();
    832 
    833  mPopupState = ePopupShowing;
    834  mAnchorContent = aAnchorContent;
    835  mAnchorType = aAnchorType;
    836  const nscoord auPerCssPx = AppUnitsPerCSSPixel();
    837  const nsPoint pos = CSSPixel::ToAppUnits(CSSIntPoint(aXPos, aYPos));
    838  // When converted back to CSSIntRect it is (-1, -1, 0, 0) - as expected in
    839  // nsXULPopupManager::Rollup
    840  mScreenRect = nsRect(-auPerCssPx, -auPerCssPx, 0, 0);
    841  mExtraMargin = pos;
    842  // If we have no anchor node, anchor to the given position instead.
    843  if (mAnchorType == MenuPopupAnchorType::Node && !aAnchorContent) {
    844    mAnchorType = MenuPopupAnchorType::Point;
    845    mScreenRect = nsRect(
    846        pos + PresShell()->GetRootFrame()->GetScreenRectInAppUnits().TopLeft(),
    847        nsSize());
    848    mExtraMargin = {};
    849  }
    850  mTriggerContent = aTriggerContent;
    851  mIsNativeMenu = false;
    852  mIsTopLevelContextMenu = false;
    853  mVFlip = false;
    854  mHFlip = false;
    855  mConstrainedByLayout = false;
    856  mAlignmentOffset = 0;
    857  mPositionedOffset = 0;
    858  mPositionedByMoveToRect = false;
    859 
    860  // if aAttributesOverride is true, then the popupanchor, popupalign and
    861  // position attributes on the <menupopup> override those values passed in.
    862  // If false, those attributes are only used if the values passed in are empty
    863  if (aAnchorContent || aAnchorType == MenuPopupAnchorType::Rect) {
    864    nsAutoString anchor, align, position;
    865    mContent->AsElement()->GetAttr(nsGkAtoms::popupanchor, anchor);
    866    mContent->AsElement()->GetAttr(nsGkAtoms::popupalign, align);
    867    mContent->AsElement()->GetAttr(nsGkAtoms::position, position);
    868 
    869    if (aAttributesOverride) {
    870      // if the attributes are set, clear the offset position. Otherwise,
    871      // the offset is used to adjust the position from the anchor point
    872      if (anchor.IsEmpty() && align.IsEmpty() && position.IsEmpty()) {
    873        position.Assign(aPosition);
    874      }
    875    } else if (!aPosition.IsEmpty()) {
    876      position.Assign(aPosition);
    877    }
    878 
    879    mFlip = FlipFromAttribute(this);
    880 
    881    position.CompressWhitespace();
    882    int32_t spaceIdx = position.FindChar(' ');
    883    // if there is a space in the position, assume it is the anchor and
    884    // alignment as two separate tokens.
    885    if (spaceIdx >= 0) {
    886      InitPositionFromAnchorAlign(Substring(position, 0, spaceIdx),
    887                                  Substring(position, spaceIdx + 1));
    888    } else if (position.EqualsLiteral("before_start")) {
    889      mPopupAnchor = POPUPALIGNMENT_TOPLEFT;
    890      mPopupAlignment = POPUPALIGNMENT_BOTTOMLEFT;
    891      mPosition = POPUPPOSITION_BEFORESTART;
    892    } else if (position.EqualsLiteral("before_end")) {
    893      mPopupAnchor = POPUPALIGNMENT_TOPRIGHT;
    894      mPopupAlignment = POPUPALIGNMENT_BOTTOMRIGHT;
    895      mPosition = POPUPPOSITION_BEFOREEND;
    896    } else if (position.EqualsLiteral("after_start")) {
    897      mPopupAnchor = POPUPALIGNMENT_BOTTOMLEFT;
    898      mPopupAlignment = POPUPALIGNMENT_TOPLEFT;
    899      mPosition = POPUPPOSITION_AFTERSTART;
    900    } else if (position.EqualsLiteral("after_end")) {
    901      mPopupAnchor = POPUPALIGNMENT_BOTTOMRIGHT;
    902      mPopupAlignment = POPUPALIGNMENT_TOPRIGHT;
    903      mPosition = POPUPPOSITION_AFTEREND;
    904    } else if (position.EqualsLiteral("start_before")) {
    905      mPopupAnchor = POPUPALIGNMENT_TOPLEFT;
    906      mPopupAlignment = POPUPALIGNMENT_TOPRIGHT;
    907      mPosition = POPUPPOSITION_STARTBEFORE;
    908    } else if (position.EqualsLiteral("start_after")) {
    909      mPopupAnchor = POPUPALIGNMENT_BOTTOMLEFT;
    910      mPopupAlignment = POPUPALIGNMENT_BOTTOMRIGHT;
    911      mPosition = POPUPPOSITION_STARTAFTER;
    912    } else if (position.EqualsLiteral("end_before")) {
    913      mPopupAnchor = POPUPALIGNMENT_TOPRIGHT;
    914      mPopupAlignment = POPUPALIGNMENT_TOPLEFT;
    915      mPosition = POPUPPOSITION_ENDBEFORE;
    916    } else if (position.EqualsLiteral("end_after")) {
    917      mPopupAnchor = POPUPALIGNMENT_BOTTOMRIGHT;
    918      mPopupAlignment = POPUPALIGNMENT_BOTTOMLEFT;
    919      mPosition = POPUPPOSITION_ENDAFTER;
    920    } else if (position.EqualsLiteral("overlap")) {
    921      mPopupAnchor = POPUPALIGNMENT_TOPLEFT;
    922      mPopupAlignment = POPUPALIGNMENT_TOPLEFT;
    923      mPosition = POPUPPOSITION_OVERLAP;
    924    } else if (position.EqualsLiteral("selection")) {
    925      mPopupAnchor = POPUPALIGNMENT_BOTTOMLEFT;
    926      mPopupAlignment = POPUPALIGNMENT_TOPLEFT;
    927      mPosition = POPUPPOSITION_SELECTION;
    928    } else {
    929      InitPositionFromAnchorAlign(anchor, align);
    930    }
    931  }
    932  mUntransformedPopupAnchor = mPopupAnchor;
    933  mUntransformedPopupAlignment = mPopupAlignment;
    934 
    935  if (aAttributesOverride) {
    936    // Use |left| and |top| dimension attributes to position the popup if
    937    // present, as they may have been persisted.
    938    nsAutoString left, top;
    939    mContent->AsElement()->GetAttr(nsGkAtoms::left, left);
    940    mContent->AsElement()->GetAttr(nsGkAtoms::top, top);
    941 
    942    nsresult err;
    943    if (!left.IsEmpty()) {
    944      int32_t x = left.ToInteger(&err);
    945      if (NS_SUCCEEDED(err)) {
    946        mScreenRect.x = CSSPixel::ToAppUnits(x);
    947      }
    948    }
    949    if (!top.IsEmpty()) {
    950      int32_t y = top.ToInteger(&err);
    951      if (NS_SUCCEEDED(err)) {
    952        mScreenRect.y = CSSPixel::ToAppUnits(y);
    953      }
    954    }
    955  }
    956 }
    957 
    958 void nsMenuPopupFrame::InitializePopupAtScreen(nsIContent* aTriggerContent,
    959                                               int32_t aXPos, int32_t aYPos,
    960                                               bool aIsContextMenu) {
    961  PrepareWidget();
    962 
    963  mPopupState = ePopupShowing;
    964  mAnchorContent = nullptr;
    965  mTriggerContent = aTriggerContent;
    966  mScreenRect =
    967      nsRect(CSSPixel::ToAppUnits(CSSIntPoint(aXPos, aYPos)), nsSize());
    968  mExtraMargin = {};
    969  mFlip = FlipFromAttribute(this);
    970  mPopupAnchor = POPUPALIGNMENT_NONE;
    971  mPopupAlignment = POPUPALIGNMENT_NONE;
    972  mPosition = POPUPPOSITION_UNKNOWN;
    973  mIsContextMenu = aIsContextMenu;
    974  mIsTopLevelContextMenu = aIsContextMenu;
    975  mIsNativeMenu = false;
    976  mAnchorType = MenuPopupAnchorType::Point;
    977  mPositionedOffset = 0;
    978  mPositionedByMoveToRect = false;
    979 }
    980 
    981 void nsMenuPopupFrame::InitializePopupAsNativeContextMenu(
    982    nsIContent* aTriggerContent, int32_t aXPos, int32_t aYPos) {
    983  mTriggerContent = aTriggerContent;
    984  mPopupState = ePopupShowing;
    985  mAnchorContent = nullptr;
    986  mScreenRect =
    987      nsRect(CSSPixel::ToAppUnits(CSSIntPoint(aXPos, aYPos)), nsSize());
    988  mExtraMargin = {};
    989  mFlip = FlipType::Default;
    990  mPopupAnchor = POPUPALIGNMENT_NONE;
    991  mPopupAlignment = POPUPALIGNMENT_NONE;
    992  mPosition = POPUPPOSITION_UNKNOWN;
    993  mIsContextMenu = true;
    994  mIsTopLevelContextMenu = true;
    995  mIsNativeMenu = true;
    996  mAnchorType = MenuPopupAnchorType::Point;
    997  mPositionedOffset = 0;
    998  mPositionedByMoveToRect = false;
    999  // Native context menus don't call PrepareWidget(), so if we have a widget
   1000  // already (which generally should only be possible on tests, since
   1001  // otherwise we shouldn't ever mix native / non-native for the same popup) we
   1002  // should destroy it now.
   1003  if (mExpirationState.IsTracked()) {
   1004    PopupExpirationTracker::Get()->RemoveObject(this);
   1005  }
   1006  DestroyWidget();
   1007 }
   1008 
   1009 void nsMenuPopupFrame::InitializePopupAtRect(nsIContent* aTriggerContent,
   1010                                             const nsAString& aPosition,
   1011                                             const nsIntRect& aRect,
   1012                                             bool aAttributesOverride) {
   1013  InitializePopup(nullptr, aTriggerContent, aPosition, 0, 0,
   1014                  MenuPopupAnchorType::Rect, aAttributesOverride);
   1015  mScreenRect = ToAppUnits(aRect, AppUnitsPerCSSPixel());
   1016 }
   1017 
   1018 void nsMenuPopupFrame::ShowPopup(bool aIsContextMenu) {
   1019  mIsContextMenu = aIsContextMenu;
   1020 
   1021  InvalidateFrameSubtree();
   1022  SchedulePendingWidgetMoveResize();
   1023 
   1024  if (mPopupState == ePopupShowing || mPopupState == ePopupPositioning) {
   1025    mPopupState = ePopupOpening;
   1026    mIsOpenChanged = true;
   1027 
   1028    // Clear mouse capture when a popup is opened.
   1029    if (mPopupType == PopupType::Menu) {
   1030      if (auto* activeESM = EventStateManager::GetActiveEventStateManager()) {
   1031        EventStateManager::ClearGlobalActiveContent(activeESM);
   1032      }
   1033 
   1034      PresShell::ReleaseCapturingContent();
   1035    }
   1036 
   1037    if (RefPtr menu = PopupElement().GetContainingMenu()) {
   1038      menu->PopupOpened();
   1039    }
   1040 
   1041    // We skip laying out children if we're closed, so make sure that we do a
   1042    // full dirty reflow when opening to pick up any potential change.
   1043    PresShell()->FrameNeedsReflow(
   1044        this, IntrinsicDirty::FrameAncestorsAndDescendants, NS_FRAME_IS_DIRTY);
   1045 
   1046    if (mPopupType == PopupType::Menu) {
   1047      nsCOMPtr<nsISound> sound(do_GetService("@mozilla.org/sound;1"));
   1048      if (sound) {
   1049        sound->PlayEventSound(nsISound::EVENT_MENU_POPUP);
   1050      }
   1051    }
   1052  }
   1053 }
   1054 
   1055 void nsMenuPopupFrame::ClearTriggerContentIncludingDocument() {
   1056  // clear the trigger content if the popup is being closed. But don't clear
   1057  // it if the popup is just being made invisible as a popuphiding or command
   1058  if (mTriggerContent) {
   1059    // if the popup had a trigger node set, clear the global window popup node
   1060    // as well
   1061    Document* doc = mContent->GetUncomposedDoc();
   1062    if (doc) {
   1063      if (nsPIDOMWindowOuter* win = doc->GetWindow()) {
   1064        nsCOMPtr<nsPIWindowRoot> root = win->GetTopWindowRoot();
   1065        if (root) {
   1066          root->SetPopupNode(nullptr);
   1067        }
   1068      }
   1069    }
   1070  }
   1071  mTriggerContent = nullptr;
   1072 }
   1073 
   1074 void nsMenuPopupFrame::HidePopup(bool aDeselectMenu, nsPopupState aNewState,
   1075                                 bool aFromFrameDestruction) {
   1076  NS_ASSERTION(aNewState == ePopupClosed || aNewState == ePopupInvisible,
   1077               "popup being set to unexpected state");
   1078 
   1079  ClearPopupShownDispatcher();
   1080 
   1081  // don't hide the popup when it isn't open
   1082  if (mPopupState == ePopupClosed || mPopupState == ePopupShowing ||
   1083      mPopupState == ePopupPositioning) {
   1084    return;
   1085  }
   1086 
   1087  if (aNewState == ePopupClosed) {
   1088    // clear the trigger content if the popup is being closed. But don't clear
   1089    // it if the popup is just being made invisible as a popuphiding or command
   1090    // event may want to retrieve it.
   1091    ClearTriggerContentIncludingDocument();
   1092    mAnchorContent = nullptr;
   1093  }
   1094 
   1095  // when invisible and about to be closed, HidePopup has already been called,
   1096  // so just set the new state to closed and return
   1097  if (mPopupState == ePopupInvisible) {
   1098    if (aNewState == ePopupClosed) {
   1099      mPopupState = ePopupClosed;
   1100    }
   1101    return;
   1102  }
   1103 
   1104  mPopupState = aNewState;
   1105 
   1106  mIncrementalString.Truncate();
   1107 
   1108  mIsOpenChanged = false;
   1109  mHFlip = mVFlip = false;
   1110  mConstrainedByLayout = false;
   1111 
   1112  RefPtr widget = GetWidget();
   1113  if (widget) {
   1114    widget->ClearCachedWebrenderResources();
   1115    if (!aFromFrameDestruction && !ShouldHaveWidgetWhenHidden()) {
   1116      PopupExpirationTracker::GetOrCreate().AddObject(this);
   1117    }
   1118  }
   1119 
   1120  ClearPendingWidgetMoveResize();
   1121  RefPtr popup = &PopupElement();
   1122  // XXX, bug 137033, In Windows, if mouse is outside the window when the
   1123  // menupopup closes, no mouse_enter/mouse_exit event will be fired to clear
   1124  // current hover state, we should clear it manually. This code may not the
   1125  // best solution, but we can leave it here until we find the better approach.
   1126  if (!aFromFrameDestruction &&
   1127      popup->State().HasState(dom::ElementState::HOVER)) {
   1128    EventStateManager* esm = PresContext()->EventStateManager();
   1129    esm->SetContentState(nullptr, dom::ElementState::HOVER);
   1130  }
   1131  popup->PopupClosed(aDeselectMenu);
   1132 
   1133  if (widget) {
   1134    nsContentUtils::AddScriptRunner(
   1135        NS_NewRunnableFunction("HideWidget", [widget = std::move(widget)] {
   1136          auto* frame = widget->GetPopupFrame();
   1137          if (!frame || !frame->IsVisibleOrShowing()) {
   1138            widget->Show(false);
   1139          }
   1140        }));
   1141  }
   1142 }
   1143 
   1144 void nsMenuPopupFrame::SchedulePendingWidgetMoveResize() {
   1145  if (mPendingWidgetMoveResize) {
   1146    return;
   1147  }
   1148  mPendingWidgetMoveResize = true;
   1149  SchedulePaint();
   1150 }
   1151 
   1152 nsPoint nsMenuPopupFrame::AdjustPositionForAnchorAlign(
   1153    nsRect& anchorRect, const nsSize& aPrefSize, FlipStyle& aHFlip,
   1154    FlipStyle& aVFlip) const {
   1155  // flip the anchor and alignment for right-to-left
   1156  int8_t popupAnchor(mPopupAnchor);
   1157  int8_t popupAlign(mPopupAlignment);
   1158  if (IsDirectionRTL()) {
   1159    // no need to flip the centered anchor types vertically
   1160    if (popupAnchor <= POPUPALIGNMENT_LEFTCENTER) {
   1161      popupAnchor = -popupAnchor;
   1162    }
   1163    popupAlign = -popupAlign;
   1164  }
   1165 
   1166  nsRect originalAnchorRect(anchorRect);
   1167 
   1168  // first, determine at which corner of the anchor the popup should appear
   1169  nsPoint pnt;
   1170  switch (popupAnchor) {
   1171    case POPUPALIGNMENT_LEFTCENTER:
   1172      pnt = nsPoint(anchorRect.x, anchorRect.y + anchorRect.height / 2);
   1173      anchorRect.y = pnt.y;
   1174      anchorRect.height = 0;
   1175      break;
   1176    case POPUPALIGNMENT_RIGHTCENTER:
   1177      pnt = nsPoint(anchorRect.XMost(), anchorRect.y + anchorRect.height / 2);
   1178      anchorRect.y = pnt.y;
   1179      anchorRect.height = 0;
   1180      break;
   1181    case POPUPALIGNMENT_TOPCENTER:
   1182      pnt = nsPoint(anchorRect.x + anchorRect.width / 2, anchorRect.y);
   1183      anchorRect.x = pnt.x;
   1184      anchorRect.width = 0;
   1185      break;
   1186    case POPUPALIGNMENT_BOTTOMCENTER:
   1187      pnt = nsPoint(anchorRect.x + anchorRect.width / 2, anchorRect.YMost());
   1188      anchorRect.x = pnt.x;
   1189      anchorRect.width = 0;
   1190      break;
   1191    case POPUPALIGNMENT_TOPRIGHT:
   1192      pnt = anchorRect.TopRight();
   1193      break;
   1194    case POPUPALIGNMENT_BOTTOMLEFT:
   1195      pnt = anchorRect.BottomLeft();
   1196      break;
   1197    case POPUPALIGNMENT_BOTTOMRIGHT:
   1198      pnt = anchorRect.BottomRight();
   1199      break;
   1200    case POPUPALIGNMENT_TOPLEFT:
   1201    default:
   1202      pnt = anchorRect.TopLeft();
   1203      break;
   1204  }
   1205 
   1206  // If the alignment is on the right edge of the popup, move the popup left
   1207  // by the width. Similarly, if the alignment is on the bottom edge of the
   1208  // popup, move the popup up by the height. In addition, account for the
   1209  // margins of the popup on the edge on which it is aligned.
   1210  nsMargin margin = GetMargin();
   1211  switch (popupAlign) {
   1212    case POPUPALIGNMENT_LEFTCENTER:
   1213      pnt.MoveBy(margin.left, -aPrefSize.height / 2);
   1214      break;
   1215    case POPUPALIGNMENT_RIGHTCENTER:
   1216      pnt.MoveBy(-aPrefSize.width - margin.right, -aPrefSize.height / 2);
   1217      break;
   1218    case POPUPALIGNMENT_TOPCENTER:
   1219      pnt.MoveBy(-aPrefSize.width / 2, margin.top);
   1220      break;
   1221    case POPUPALIGNMENT_BOTTOMCENTER:
   1222      pnt.MoveBy(-aPrefSize.width / 2, -aPrefSize.height - margin.bottom);
   1223      break;
   1224    case POPUPALIGNMENT_TOPRIGHT:
   1225      pnt.MoveBy(-aPrefSize.width - margin.right, margin.top);
   1226      break;
   1227    case POPUPALIGNMENT_BOTTOMLEFT:
   1228      pnt.MoveBy(margin.left, -aPrefSize.height - margin.bottom);
   1229      break;
   1230    case POPUPALIGNMENT_BOTTOMRIGHT:
   1231      pnt.MoveBy(-aPrefSize.width - margin.right,
   1232                 -aPrefSize.height - margin.bottom);
   1233      break;
   1234    case POPUPALIGNMENT_TOPLEFT:
   1235    default:
   1236      pnt.MoveBy(margin.left, margin.top);
   1237      break;
   1238  }
   1239 
   1240  // If we aligning to the selected item in the popup, adjust the vertical
   1241  // position by the height of the menulist label and the selected item's
   1242  // position.
   1243  if (mPosition == POPUPPOSITION_SELECTION) {
   1244    MOZ_ASSERT(popupAnchor == POPUPALIGNMENT_BOTTOMLEFT ||
   1245               popupAnchor == POPUPALIGNMENT_BOTTOMRIGHT);
   1246    MOZ_ASSERT(popupAlign == POPUPALIGNMENT_TOPLEFT ||
   1247               popupAlign == POPUPALIGNMENT_TOPRIGHT);
   1248 
   1249    // Only adjust the popup if it just opened, otherwise the popup will move
   1250    // around if its gets resized or the selection changed. Cache the value in
   1251    // mPositionedOffset and use that instead for any future calculations.
   1252    if (mIsOpenChanged) {
   1253      if (nsIFrame* selectedItemFrame = GetSelectedItemForAlignment()) {
   1254        const nscoord itemHeight = selectedItemFrame->GetRect().height;
   1255        const nscoord itemOffset =
   1256            selectedItemFrame->GetOffsetToIgnoringScrolling(this).y;
   1257        // We want to line-up the anchor rect with the selected item, but if the
   1258        // selected item is outside of our bounds, we don't want to shift the
   1259        // popup up in a way that our box would no longer intersect with the
   1260        // anchor.
   1261        nscoord maxOffset = aPrefSize.height - itemHeight;
   1262        if (const ScrollContainerFrame* sf = GetScrollContainerFrame()) {
   1263          // HACK: We ideally would want to use the offset from the bottom
   1264          // bottom of our scroll-frame to the bottom of our frame (so as to
   1265          // ensure that the bottom of the scrollport is inside the anchor
   1266          // rect).
   1267          //
   1268          // But at this point of the code, the scroll frame may not be laid out
   1269          // with a definite size (might be overflowing us).
   1270          //
   1271          // So, we assume the offset from the bottom is symmetric to the offset
   1272          // from the top. This holds for all the popups where this matters
   1273          // (menulists on macOS, effectively), and seems better than somehow
   1274          // moving the popup after the fact as we used to do.
   1275          maxOffset -= sf->GetOffsetTo(this).y;
   1276        }
   1277        mPositionedOffset =
   1278            originalAnchorRect.height + std::min(itemOffset, maxOffset);
   1279      }
   1280    }
   1281 
   1282    pnt.y -= mPositionedOffset;
   1283  }
   1284 
   1285  // Flipping horizontally is allowed as long as the popup is above or below
   1286  // the anchor. This will happen if both the anchor and alignment are top or
   1287  // both are bottom, but different values. Similarly, flipping vertically is
   1288  // allowed if the popup is to the left or right of the anchor. In this case,
   1289  // the values of the constants are such that both must be positive or both
   1290  // must be negative. A special case, used for overlap, allows flipping
   1291  // vertically as well.
   1292  // If we are flipping in both directions, we want to set a flip style both
   1293  // horizontally and vertically. However, we want to flip on the inside edge
   1294  // of the anchor. Consider the example of a typical dropdown menu.
   1295  // Vertically, we flip the popup on the outside edges of the anchor menu,
   1296  // however horizontally, we want to to use the inside edges so the popup
   1297  // still appears underneath the anchor menu instead of floating off the
   1298  // side of the menu.
   1299  switch (popupAnchor) {
   1300    case POPUPALIGNMENT_LEFTCENTER:
   1301    case POPUPALIGNMENT_RIGHTCENTER:
   1302      aHFlip = FlipStyle::Outside;
   1303      aVFlip = FlipStyle::Inside;
   1304      break;
   1305    case POPUPALIGNMENT_TOPCENTER:
   1306    case POPUPALIGNMENT_BOTTOMCENTER:
   1307      aHFlip = FlipStyle::Inside;
   1308      aVFlip = FlipStyle::Outside;
   1309      break;
   1310    default: {
   1311      FlipStyle anchorEdge =
   1312          mFlip == FlipType::Both ? FlipStyle::Inside : FlipStyle::None;
   1313      aHFlip = (popupAnchor == -popupAlign) ? FlipStyle::Outside : anchorEdge;
   1314      if (((popupAnchor > 0) == (popupAlign > 0)) ||
   1315          (popupAnchor == POPUPALIGNMENT_TOPLEFT &&
   1316           popupAlign == POPUPALIGNMENT_TOPLEFT)) {
   1317        aVFlip = FlipStyle::Outside;
   1318      } else {
   1319        aVFlip = anchorEdge;
   1320      }
   1321      break;
   1322    }
   1323  }
   1324 
   1325  return pnt;
   1326 }
   1327 
   1328 nsIFrame* nsMenuPopupFrame::GetSelectedItemForAlignment() const {
   1329  // This method adjusts a menulist's popup such that the selected item is under
   1330  // the cursor, aligned with the menulist label.
   1331  nsCOMPtr<nsIDOMXULSelectControlElement> select;
   1332  if (mAnchorContent) {
   1333    select = mAnchorContent->AsElement()->AsXULSelectControl();
   1334  }
   1335 
   1336  if (!select) {
   1337    // If there isn't an anchor, then try just getting the parent of the popup.
   1338    select = mContent->GetParent()->AsElement()->AsXULSelectControl();
   1339    if (!select) {
   1340      return nullptr;
   1341    }
   1342  }
   1343 
   1344  nsCOMPtr<Element> selectedElement;
   1345  select->GetSelectedItem(getter_AddRefs(selectedElement));
   1346  return selectedElement ? selectedElement->GetPrimaryFrame() : nullptr;
   1347 }
   1348 
   1349 nscoord nsMenuPopupFrame::SlideOrResize(nscoord& aScreenPoint, nscoord aSize,
   1350                                        nscoord aScreenBegin,
   1351                                        nscoord aScreenEnd,
   1352                                        nscoord* aOffset) const {
   1353  // The popup may be positioned such that either the left/top or bottom/right
   1354  // is outside the screen - but never both.
   1355  nscoord newPos =
   1356      std::max(aScreenBegin, std::min(aScreenEnd - aSize, aScreenPoint));
   1357  *aOffset = newPos - aScreenPoint;
   1358  aScreenPoint = newPos;
   1359  return std::min(aSize, aScreenEnd - aScreenPoint);
   1360 }
   1361 
   1362 nscoord nsMenuPopupFrame::FlipOrResize(nscoord& aScreenPoint, nscoord aSize,
   1363                                       nscoord aScreenBegin, nscoord aScreenEnd,
   1364                                       nscoord aAnchorBegin, nscoord aAnchorEnd,
   1365                                       nscoord aMarginBegin, nscoord aMarginEnd,
   1366                                       FlipStyle aFlip, bool aEndAligned,
   1367                                       bool* aFlipSide) const {
   1368  // The flip side argument will be set to true if there wasn't room and we
   1369  // flipped to the opposite side.
   1370  *aFlipSide = false;
   1371 
   1372  // all of the coordinates used here are in app units relative to the screen
   1373  nscoord popupSize = aSize;
   1374  if (aScreenPoint < aScreenBegin) {
   1375    // at its current position, the popup would extend past the left or top
   1376    // edge of the screen, so it will have to be moved or resized.
   1377    if (aFlip != FlipStyle::None) {
   1378      // for inside flips, we flip on the opposite side of the anchor
   1379      nscoord startpos =
   1380          aFlip == FlipStyle::Outside ? aAnchorBegin : aAnchorEnd;
   1381      nscoord endpos = aFlip == FlipStyle::Outside ? aAnchorEnd : aAnchorBegin;
   1382 
   1383      // check whether there is more room to the left and right (or top and
   1384      // bottom) of the anchor and put the popup on the side with more room.
   1385      if (startpos - aScreenBegin >= aScreenEnd - endpos) {
   1386        aScreenPoint = aScreenBegin;
   1387        popupSize = startpos - aScreenPoint - aMarginEnd;
   1388        *aFlipSide = !aEndAligned;
   1389      } else {
   1390        // If the newly calculated position is different than the existing
   1391        // position, flip such that the popup is to the right or bottom of the
   1392        // anchor point instead . However, when flipping use the same margin
   1393        // size.
   1394        nscoord newScreenPoint = endpos + aMarginEnd;
   1395        if (newScreenPoint != aScreenPoint) {
   1396          *aFlipSide = aEndAligned;
   1397          aScreenPoint = newScreenPoint;
   1398          // check if the new position is still off the right or bottom edge of
   1399          // the screen. If so, resize the popup.
   1400          if (aScreenPoint + aSize > aScreenEnd) {
   1401            popupSize = aScreenEnd - aScreenPoint;
   1402          }
   1403        }
   1404      }
   1405    } else {
   1406      aScreenPoint = aScreenBegin;
   1407    }
   1408  } else if (aScreenPoint + aSize > aScreenEnd) {
   1409    // at its current position, the popup would extend past the right or
   1410    // bottom edge of the screen, so it will have to be moved or resized.
   1411    if (aFlip != FlipStyle::None) {
   1412      // for inside flips, we flip on the opposite side of the anchor
   1413      nscoord startpos =
   1414          aFlip == FlipStyle::Outside ? aAnchorBegin : aAnchorEnd;
   1415      nscoord endpos = aFlip == FlipStyle::Outside ? aAnchorEnd : aAnchorBegin;
   1416 
   1417      // check whether there is more room to the left and right (or top and
   1418      // bottom) of the anchor and put the popup on the side with more room.
   1419      if (aScreenEnd - endpos >= startpos - aScreenBegin) {
   1420        *aFlipSide = aEndAligned;
   1421        if (mIsContextMenu) {
   1422          aScreenPoint = aScreenEnd - aSize;
   1423        } else {
   1424          aScreenPoint = endpos + aMarginBegin;
   1425          popupSize = aScreenEnd - aScreenPoint;
   1426        }
   1427      } else {
   1428        // if the newly calculated position is different than the existing
   1429        // position, we flip such that the popup is to the left or top of the
   1430        // anchor point instead.
   1431        nscoord newScreenPoint = startpos - aSize - aMarginBegin;
   1432        if (newScreenPoint != aScreenPoint) {
   1433          *aFlipSide = !aEndAligned;
   1434          aScreenPoint = newScreenPoint;
   1435 
   1436          // check if the new position is still off the left or top edge of the
   1437          // screen. If so, resize the popup.
   1438          if (aScreenPoint < aScreenBegin) {
   1439            aScreenPoint = aScreenBegin;
   1440            if (!mIsContextMenu) {
   1441              popupSize = startpos - aScreenPoint - aMarginBegin;
   1442            }
   1443          }
   1444        }
   1445      }
   1446    } else {
   1447      aScreenPoint = aScreenEnd - aSize;
   1448    }
   1449  }
   1450 
   1451  // Make sure that the point is within the screen boundaries and that the
   1452  // size isn't off the edge of the screen. This can happen when a large
   1453  // positive or negative margin is used.
   1454  if (aScreenPoint < aScreenBegin) {
   1455    aScreenPoint = aScreenBegin;
   1456  }
   1457  if (aScreenPoint > aScreenEnd) {
   1458    aScreenPoint = aScreenEnd - aSize;
   1459  }
   1460 
   1461  // If popupSize ended up being negative, or the original size was actually
   1462  // smaller than the calculated popup size, just use the original size instead.
   1463  if (popupSize <= 0 || aSize < popupSize) {
   1464    popupSize = aSize;
   1465  }
   1466 
   1467  return std::min(popupSize, aScreenEnd - aScreenPoint);
   1468 }
   1469 
   1470 nsRect nsMenuPopupFrame::ComputeAnchorRect(nsPresContext* aRootPresContext,
   1471                                           nsIFrame* aAnchorFrame) const {
   1472  // Get the root frame for a reference
   1473  nsIFrame* rootFrame = aRootPresContext->PresShell()->GetRootFrame();
   1474 
   1475  // The dimensions of the anchor
   1476  nsRect anchorRect = aAnchorFrame->GetRectRelativeToSelf();
   1477 
   1478  // Relative to the root
   1479  anchorRect = nsLayoutUtils::TransformFrameRectToAncestor(
   1480      aAnchorFrame, anchorRect, rootFrame);
   1481  // Relative to the screen
   1482  anchorRect.MoveBy(rootFrame->GetScreenRectInAppUnits().TopLeft());
   1483 
   1484  // In its own app units
   1485  return anchorRect.ScaleToOtherAppUnitsRoundOut(
   1486      aRootPresContext->AppUnitsPerDevPixel(),
   1487      PresContext()->AppUnitsPerDevPixel());
   1488 }
   1489 
   1490 static nsIFrame* MaybeDelegatedAnchorFrame(nsIFrame* aFrame) {
   1491  if (!aFrame) {
   1492    return nullptr;
   1493  }
   1494  if (auto* element = Element::FromNodeOrNull(aFrame->GetContent())) {
   1495    if (element->HasAttr(nsGkAtoms::delegatesanchor)) {
   1496      for (nsIFrame* f : aFrame->PrincipalChildList()) {
   1497        if (!f->IsPlaceholderFrame()) {
   1498          return f;
   1499        }
   1500      }
   1501    }
   1502  }
   1503  return aFrame;
   1504 }
   1505 
   1506 auto nsMenuPopupFrame::GetRects(const nsSize& aPrefSize) const -> Rects {
   1507  if (NS_WARN_IF(aPrefSize == nsSize(-1, -1))) {
   1508    // Return early if the popup hasn't been laid out yet. On Windows, this can
   1509    // happen when using a drag popup before it opens.
   1510    return {};
   1511  }
   1512 
   1513  nsPresContext* pc = PresContext();
   1514  nsIFrame* rootFrame = pc->PresShell()->GetRootFrame();
   1515 
   1516  // Indicators of whether the popup should be flipped or resized.
   1517  FlipStyle hFlip = FlipStyle::None;
   1518  FlipStyle vFlip = FlipStyle::None;
   1519 
   1520  const nsMargin margin = GetMargin();
   1521 
   1522  // the screen rectangle of the root frame, in dev pixels.
   1523  const nsRect rootScreenRect = rootFrame->GetScreenRectInAppUnits();
   1524 
   1525  const bool isNoAutoHide = IsNoAutoHide();
   1526  const PopupLevel popupLevel = GetPopupLevel(isNoAutoHide);
   1527 
   1528  Rects result;
   1529 
   1530  // Set the popup's size to the preferred size. Below, this size will be
   1531  // adjusted to fit on the screen or within the content area. If the anchor is
   1532  // sized to the popup, use the anchor's width instead of the preferred width.
   1533  result.mUsedRect = nsRect(nsPoint(), aPrefSize);
   1534 
   1535  const bool anchored = IsAnchored();
   1536  if (anchored) {
   1537    // In order to deal with transforms, we need the root prescontext:
   1538    nsPresContext* rootPc = pc->GetRootPresContext();
   1539    if (NS_WARN_IF(!rootPc)) {
   1540      // If we can't reach a root pres context, don't bother continuing.
   1541      return result;
   1542    }
   1543 
   1544    result.mAnchorRect = result.mUntransformedAnchorRect = [&] {
   1545      // If anchored to a rectangle, use that rectangle. Otherwise, determine
   1546      // the rectangle from the anchor.
   1547      if (mAnchorType == MenuPopupAnchorType::Rect) {
   1548        return mScreenRect;
   1549      }
   1550      // if the frame is not specified, use the anchor node passed to OpenPopup.
   1551      // If that wasn't specified either, use the root frame. Note that
   1552      // mAnchorContent might be a different document so its presshell must be
   1553      // used.
   1554      nsIFrame* anchorFrame = GetAnchorFrame();
   1555      if (!anchorFrame) {
   1556        return rootScreenRect;
   1557      }
   1558      return ComputeAnchorRect(rootPc, anchorFrame);
   1559    }();
   1560 
   1561    // if we are anchored, there are certain things we don't want to do when
   1562    // repositioning the popup to fit on the screen, such as end up positioned
   1563    // over the anchor, for instance a popup appearing over the menu label.
   1564    // When doing this reposition, we want to move the popup to the side with
   1565    // the most room. The combination of anchor and alignment dictate if we
   1566    // readjust above/below or to the left/right.
   1567    if (mAnchorContent || mAnchorType == MenuPopupAnchorType::Rect) {
   1568      // move the popup according to the anchor and alignment. This will also
   1569      // tell us which axis the popup is flush against in case we have to move
   1570      // it around later. The AdjustPositionForAnchorAlign method accounts for
   1571      // the popup's margin.
   1572      result.mUsedRect.MoveTo(AdjustPositionForAnchorAlign(
   1573          result.mAnchorRect, aPrefSize, hFlip, vFlip));
   1574    } else {
   1575      // With no anchor, the popup is positioned relative to the root frame.
   1576      result.mUsedRect.MoveTo(result.mAnchorRect.TopLeft() +
   1577                              nsPoint(margin.left, margin.top));
   1578    }
   1579  } else {
   1580    // Not anchored, use mScreenRect
   1581    result.mUsedRect.MoveTo(mScreenRect.TopLeft());
   1582    result.mAnchorRect = result.mUntransformedAnchorRect =
   1583        nsRect(mScreenRect.TopLeft(), nsSize());
   1584 
   1585    // Right-align RTL context menus, and apply margin and offsets as per the
   1586    // platform conventions.
   1587    if (mIsContextMenu && IsDirectionRTL()) {
   1588      result.mUsedRect.x -= aPrefSize.Width();
   1589      result.mUsedRect.MoveBy(-margin.right, margin.top);
   1590    } else {
   1591      result.mUsedRect.MoveBy(margin.left, margin.top);
   1592    }
   1593 #ifdef XP_MACOSX
   1594    // On macOS, tooltips follow standard flip rule but other popups like
   1595    // context menus flip horizontally, not vertically.
   1596    if (mPopupType == PopupType::Tooltip) {
   1597      vFlip = FlipStyle::Outside;
   1598    } else {
   1599      hFlip = FlipStyle::Outside;
   1600    }
   1601 #else
   1602    // On Windows and Linux, other OS screen positioned popups can be flipped
   1603    // vertically. Only the context menu can be flipped horizontally as well, in
   1604    // order to avoid showing the context menu accidentally under the mouse.
   1605    vFlip = FlipStyle::Outside;
   1606    if (mIsContextMenu) {
   1607      hFlip = FlipStyle::Outside;
   1608    }
   1609 #endif  // #ifdef XP_MACOSX
   1610  }
   1611 
   1612  const int32_t a2d = pc->AppUnitsPerDevPixel();
   1613 
   1614  nsIWidget* widget = mWidget;
   1615 
   1616  // If a panel has flip="none", don't constrain or flip it.
   1617  // Also, always do this for content shells, so that the popup doesn't extend
   1618  // outside the containing frame.
   1619  if (mInContentShell || mFlip != FlipType::None) {
   1620    const Maybe<nsRect> constraintRect =
   1621        GetConstraintRect(result.mAnchorRect, rootScreenRect, popupLevel);
   1622 
   1623    if (constraintRect) {
   1624      // Ensure that anchorRect is on the constraint rect.
   1625      result.mAnchorRect = result.mAnchorRect.Intersect(*constraintRect);
   1626      // Shrink the popup down if it is larger than the constraint size
   1627      if (result.mUsedRect.width > constraintRect->width) {
   1628        result.mUsedRect.width = constraintRect->width;
   1629      }
   1630      if (result.mUsedRect.height > constraintRect->height) {
   1631        result.mUsedRect.height = constraintRect->height;
   1632      }
   1633      result.mConstrainedByLayout = true;
   1634    }
   1635 
   1636    if (IS_WAYLAND_DISPLAY() && widget) {
   1637      // Shrink the popup down if it's larger than popup size received from
   1638      // Wayland compositor. We don't know screen size on Wayland so this is the
   1639      // only info we have there.
   1640      const nsSize waylandSize = LayoutDeviceIntRect::ToAppUnits(
   1641          widget->GetMoveToRectPopupSize(), a2d);
   1642 
   1643      LOG_WAYLAND_VERBOSE(
   1644          "[%p] Wayland popup size from layout [%d x %d] a2d %d", widget,
   1645          result.mUsedRect.width / a2d, result.mUsedRect.height / a2d, a2d);
   1646      LOG_WAYLAND_VERBOSE(
   1647          "[%p] Wayland popup size from last move-to-rect [%d x %d] a2d %d",
   1648          widget, widget->GetMoveToRectPopupSize().width,
   1649          widget->GetMoveToRectPopupSize().height, a2d);
   1650 
   1651      if (waylandSize.width > 0 && result.mUsedRect.width > waylandSize.width) {
   1652        LOG_WAYLAND("[%p] Wayland constraint width %d to %d", widget,
   1653                    result.mUsedRect.width, waylandSize.width);
   1654        result.mUsedRect.width = waylandSize.width;
   1655      }
   1656      if (waylandSize.height > 0 &&
   1657          result.mUsedRect.height > waylandSize.height) {
   1658        LOG_WAYLAND("[%p] Wayland constraint height %d to %d", widget,
   1659                    result.mUsedRect.height, waylandSize.height);
   1660        result.mUsedRect.height = waylandSize.height;
   1661      }
   1662      if (RefPtr<widget::Screen> s = widget->GetWidgetScreen()) {
   1663        const nsSize screenSize =
   1664            LayoutDeviceIntSize::ToAppUnits(s->GetAvailRect().Size(), a2d);
   1665        LOG_WAYLAND_VERBOSE("[%p] Wayland screen size [%d x %d] a2d %d", widget,
   1666                            s->GetAvailRect().Size().width,
   1667                            s->GetAvailRect().Size().height, a2d);
   1668 
   1669        if (result.mUsedRect.height > screenSize.height) {
   1670          LOG_WAYLAND("[%p] Wayland constraint height to screen %d to %d",
   1671                      widget, result.mUsedRect.height / a2d,
   1672                      screenSize.height / a2d);
   1673          result.mUsedRect.height = screenSize.height;
   1674        }
   1675        if (result.mUsedRect.width > screenSize.width) {
   1676          LOG_WAYLAND("[%p] Wayland constraint widthto screen %d to %d", widget,
   1677                      result.mUsedRect.width / a2d, screenSize.width / a2d);
   1678          result.mUsedRect.width = screenSize.width;
   1679        }
   1680      }
   1681    }
   1682 
   1683    // At this point the anchor (anchorRect) is within the available screen
   1684    // area (constraintRect) and the popup is known to be no larger than the
   1685    // screen.
   1686    if (constraintRect) {
   1687      // We might want to "slide" an arrow if the panel is of the correct type -
   1688      // but we can only slide on one axis - the other axis must be "flipped or
   1689      // resized" as normal.
   1690      bool slideHorizontal = false, slideVertical = false;
   1691      if (mFlip == FlipType::Slide) {
   1692        int8_t position = GetAlignmentPosition();
   1693        slideHorizontal = position >= POPUPPOSITION_BEFORESTART &&
   1694                          position <= POPUPPOSITION_AFTEREND;
   1695        slideVertical = position >= POPUPPOSITION_STARTBEFORE &&
   1696                        position <= POPUPPOSITION_ENDAFTER;
   1697      }
   1698 
   1699      // Next, check if there is enough space to show the popup at full size
   1700      // when positioned at screenPoint. If not, flip the popups to the opposite
   1701      // side of their anchor point, or resize them as necessary.
   1702      if (slideHorizontal) {
   1703        result.mUsedRect.width = SlideOrResize(
   1704            result.mUsedRect.x, result.mUsedRect.width, constraintRect->x,
   1705            constraintRect->XMost(), &result.mAlignmentOffset);
   1706      } else {
   1707        const bool endAligned =
   1708            IsDirectionRTL()
   1709                ? mPopupAlignment == POPUPALIGNMENT_TOPLEFT ||
   1710                      mPopupAlignment == POPUPALIGNMENT_BOTTOMLEFT ||
   1711                      mPopupAlignment == POPUPALIGNMENT_LEFTCENTER
   1712                : mPopupAlignment == POPUPALIGNMENT_TOPRIGHT ||
   1713                      mPopupAlignment == POPUPALIGNMENT_BOTTOMRIGHT ||
   1714                      mPopupAlignment == POPUPALIGNMENT_RIGHTCENTER;
   1715        result.mUsedRect.width = FlipOrResize(
   1716            result.mUsedRect.x, result.mUsedRect.width, constraintRect->x,
   1717            constraintRect->XMost(), result.mAnchorRect.x,
   1718            result.mAnchorRect.XMost(), margin.left, margin.right, hFlip,
   1719            endAligned, &result.mHFlip);
   1720      }
   1721      if (slideVertical) {
   1722        result.mUsedRect.height = SlideOrResize(
   1723            result.mUsedRect.y, result.mUsedRect.height, constraintRect->y,
   1724            constraintRect->YMost(), &result.mAlignmentOffset);
   1725      } else {
   1726        bool endAligned = mPopupAlignment == POPUPALIGNMENT_BOTTOMLEFT ||
   1727                          mPopupAlignment == POPUPALIGNMENT_BOTTOMRIGHT ||
   1728                          mPopupAlignment == POPUPALIGNMENT_BOTTOMCENTER;
   1729        result.mUsedRect.height = FlipOrResize(
   1730            result.mUsedRect.y, result.mUsedRect.height, constraintRect->y,
   1731            constraintRect->YMost(), result.mAnchorRect.y,
   1732            result.mAnchorRect.YMost(), margin.top, margin.bottom, vFlip,
   1733            endAligned, &result.mVFlip);
   1734      }
   1735 
   1736 #ifdef DEBUG
   1737      NS_ASSERTION(constraintRect->Contains(result.mUsedRect),
   1738                   "Popup is offscreen");
   1739      if (!constraintRect->Contains(result.mUsedRect)) {
   1740        NS_WARNING(nsPrintfCString("Popup is offscreen (%s vs. %s)",
   1741                                   ToString(constraintRect).c_str(),
   1742                                   ToString(result.mUsedRect).c_str())
   1743                       .get());
   1744      }
   1745 #endif
   1746    }
   1747  }
   1748  // snap the popup's position in screen coordinates to device pixels, see
   1749  // bug 622507, bug 961431
   1750  result.mUsedRect.x = pc->RoundAppUnitsToNearestDevPixels(result.mUsedRect.x);
   1751  result.mUsedRect.y = pc->RoundAppUnitsToNearestDevPixels(result.mUsedRect.y);
   1752 
   1753  // determine the x and y position of the view by subtracting the desired
   1754  // screen position from the screen position of the root frame.
   1755  result.mViewPoint = result.mUsedRect.TopLeft() - rootScreenRect.TopLeft();
   1756  return result;
   1757 }
   1758 
   1759 void nsMenuPopupFrame::SetPopupPosition(bool aIsMove) {
   1760  if (aIsMove && (mPrefSize.width == -1 || mPrefSize.height == -1)) {
   1761    return;
   1762  }
   1763 
   1764  auto rects = GetRects(mPrefSize);
   1765  if (rects.mUsedRect.Size() != mRect.Size()) {
   1766    MOZ_ASSERT(!HasAnyStateBits(NS_FRAME_IN_REFLOW));
   1767    // We need to resize on top of moving, trigger an actual reflow.
   1768    PresShell()->FrameNeedsReflow(this, IntrinsicDirty::FrameAndAncestors,
   1769                                  NS_FRAME_IS_DIRTY);
   1770    return;
   1771  }
   1772  PerformMove(rects);
   1773 }
   1774 
   1775 void nsMenuPopupFrame::PerformMove(const Rects& aRects) {
   1776  auto* ps = PresShell();
   1777 
   1778  // Now that we've positioned the view, sync up the frame's origin.
   1779  const nsPoint oldPos = mRect.TopLeft();
   1780  const nsPoint newPos =
   1781      aRects.mViewPoint - GetParent()->GetOffsetTo(ps->GetRootFrame());
   1782  nsBlockFrame::SetPosition(newPos);
   1783  if (oldPos != newPos) {
   1784    SchedulePendingWidgetMoveResize();
   1785  }
   1786 
   1787  // If the popup is in the positioned state or if it is shown and the position
   1788  // or size changed, dispatch a popuppositioned event if the popup wants it.
   1789  if (mPopupState == ePopupPositioning ||
   1790      (mPopupState == ePopupShown &&
   1791       !aRects.mUsedRect.IsEqualEdges(mUsedScreenRect)) ||
   1792      (mPopupState == ePopupShown &&
   1793       aRects.mAlignmentOffset != mAlignmentOffset)) {
   1794    mUsedScreenRect = aRects.mUsedRect;
   1795    if (!HasAnyStateBits(NS_FRAME_FIRST_REFLOW) && !mPendingPositionedEvent) {
   1796      mPendingPositionedEvent =
   1797          nsXULPopupPositionedEvent::DispatchIfNeeded(mContent->AsElement());
   1798    }
   1799  }
   1800 
   1801  if (!mPositionedByMoveToRect) {
   1802    mUntransformedAnchorRect = aRects.mUntransformedAnchorRect;
   1803  }
   1804 
   1805  mAlignmentOffset = aRects.mAlignmentOffset;
   1806  mHFlip = aRects.mHFlip;
   1807  mVFlip = aRects.mVFlip;
   1808  mConstrainedByLayout = aRects.mConstrainedByLayout;
   1809 
   1810  // If this is a noautohide popup, set the screen coordinates of the popup.
   1811  // This way, the popup stays at the location where it was opened even when the
   1812  // window is moved. Popups at the parent level follow the parent window as it
   1813  // is moved and remained anchored, so we want to maintain the anchoring
   1814  // instead.
   1815  //
   1816  // FIXME: This suffers from issues like bug 1823552, where constraints imposed
   1817  // by the anchor are lost, but this is super-old behavior.
   1818  const bool fixPositionToPoint =
   1819      IsNoAutoHide() && (GetPopupLevel() != PopupLevel::Parent ||
   1820                         mAnchorType == MenuPopupAnchorType::Rect);
   1821  if (fixPositionToPoint) {
   1822    // Account for the margin that will end up being added to the screen
   1823    // coordinate the next time SetPopupPosition is called.
   1824    const auto& margin = GetMargin();
   1825    mAnchorType = MenuPopupAnchorType::Point;
   1826    mScreenRect.x = aRects.mUsedRect.x - margin.left;
   1827    mScreenRect.y = aRects.mUsedRect.y - margin.top;
   1828  }
   1829 
   1830  // For anchored popups that shouldn't follow the anchor, fix the original
   1831  // anchor rect.
   1832  if (IsAnchored() && !ShouldFollowAnchor() && !mUsedScreenRect.IsEmpty() &&
   1833      mAnchorType != MenuPopupAnchorType::Rect) {
   1834    mAnchorType = MenuPopupAnchorType::Rect;
   1835    mScreenRect = aRects.mUntransformedAnchorRect;
   1836  }
   1837 }
   1838 
   1839 Maybe<nsRect> nsMenuPopupFrame::GetConstraintRect(
   1840    const nsRect& aAnchorRect, const nsRect& aRootScreenRect,
   1841    PopupLevel aPopupLevel) const {
   1842  const nsPresContext* pc = PresContext();
   1843  const int32_t a2d = PresContext()->AppUnitsPerDevPixel();
   1844  Maybe<nsRect> result;
   1845 
   1846  auto AddConstraint = [&result](const nsRect& aConstraint) {
   1847    if (result) {
   1848      *result = result->Intersect(aConstraint);
   1849    } else {
   1850      result.emplace(aConstraint);
   1851    }
   1852  };
   1853 
   1854  // Determine the available screen space. It will be reduced by the OS chrome
   1855  // such as menubars. It addition, for content shells, it will be the area of
   1856  // the content rather than the screen.
   1857  if (IS_WAYLAND_DISPLAY()) {
   1858    // In Wayland we can't use the screen rect, because we can't know the
   1859    // absolute window position. MoveToRect usually deals with this, but we
   1860    // can't use it unconditionally. Tooltips are presumed small enough, and
   1861    // they can open basically at any time with any other open menu, so we
   1862    // constrain them to the window area, see bug 1941237.
   1863    // TODO(emilio, stransky): Do we want to constrain other popups to the
   1864    // window area, if the window is big enough or maximized?
   1865    if (mPopupType == PopupType::Tooltip) {
   1866      AddConstraint(aRootScreenRect);
   1867    }
   1868  } else {
   1869    const DesktopToLayoutDeviceScale scale =
   1870        pc->DeviceContext()->GetDesktopToDeviceScale();
   1871    // For content shells, get the screen where the root frame is located. This
   1872    // is because we need to constrain the content to this content area, so we
   1873    // should use the same screen. Otherwise, use the screen where the anchor is
   1874    // located.
   1875    const nsRect& rect = mInContentShell ? aRootScreenRect : aAnchorRect;
   1876    auto desktopRect = DesktopIntRect::RoundOut(
   1877        LayoutDeviceRect::FromAppUnits(rect, a2d) / scale);
   1878    desktopRect.width = std::max(1, desktopRect.width);
   1879    desktopRect.height = std::max(1, desktopRect.height);
   1880 
   1881    RefPtr<nsIScreen> screen =
   1882        widget::ScreenManager::GetSingleton().ScreenForRect(desktopRect);
   1883    MOZ_ASSERT(screen, "We always fall back to the primary screen");
   1884    // Non-top-level popups (which will always be panels) should never overlap
   1885    // the OS bar.
   1886    const bool canOverlapOSBar =
   1887        aPopupLevel == PopupLevel::Top &&
   1888        LookAndFeel::GetInt(LookAndFeel::IntID::MenusCanOverlapOSBar) &&
   1889        !mInContentShell;
   1890    // Get the total screen area if the popup is allowed to overlap it.
   1891    const auto screenRect =
   1892        canOverlapOSBar ? screen->GetRect() : screen->GetAvailRect();
   1893    AddConstraint(LayoutDeviceRect::ToAppUnits(screenRect, a2d));
   1894  }
   1895 
   1896  if (mInContentShell) {
   1897    // For content shells, clip to the client area rather than the screen area
   1898    AddConstraint(aRootScreenRect);
   1899  } else if (!mOverrideConstraintRect.IsEmpty()) {
   1900    AddConstraint(mOverrideConstraintRect);
   1901    // This is currently only used for <select> elements where we want to
   1902    // constrain vertically to the screen but not horizontally, so do the
   1903    // intersection and then reset the horizontal values.
   1904    //
   1905    // FIXME(emilio): This doesn't make any sense to me...
   1906    result->x = mOverrideConstraintRect.x;
   1907    result->width = mOverrideConstraintRect.width;
   1908  }
   1909 
   1910  // Expand the allowable screen rect by the input margin (which can't be
   1911  // interacted with).
   1912  if (result) {
   1913    const nscoord inputMargin =
   1914        StyleUIReset()->mMozWindowInputRegionMargin.ToAppUnits();
   1915    result->Inflate(inputMargin);
   1916  }
   1917  return result;
   1918 }
   1919 
   1920 ConsumeOutsideClicksResult nsMenuPopupFrame::ConsumeOutsideClicks() {
   1921  if (mContent->AsElement()->AttrValueIs(kNameSpaceID_None,
   1922                                         nsGkAtoms::consumeoutsideclicks,
   1923                                         nsGkAtoms::_true, eCaseMatters)) {
   1924    return ConsumeOutsideClicks_True;
   1925  }
   1926  if (mContent->AsElement()->AttrValueIs(kNameSpaceID_None,
   1927                                         nsGkAtoms::consumeoutsideclicks,
   1928                                         nsGkAtoms::_false, eCaseMatters)) {
   1929    return ConsumeOutsideClicks_ParentOnly;
   1930  }
   1931  if (mContent->AsElement()->AttrValueIs(kNameSpaceID_None,
   1932                                         nsGkAtoms::consumeoutsideclicks,
   1933                                         nsGkAtoms::never, eCaseMatters)) {
   1934    return ConsumeOutsideClicks_Never;
   1935  }
   1936 
   1937  nsCOMPtr<nsIContent> parentContent = mContent->GetParent();
   1938  if (parentContent) {
   1939    dom::NodeInfo* ni = parentContent->NodeInfo();
   1940    if (ni->Equals(nsGkAtoms::menulist, kNameSpaceID_XUL)) {
   1941      return ConsumeOutsideClicks_True;  // Consume outside clicks for combo
   1942                                         // boxes on all platforms
   1943    }
   1944 #if defined(XP_WIN)
   1945    // Don't consume outside clicks for menus in Windows
   1946    if (ni->Equals(nsGkAtoms::menu, kNameSpaceID_XUL) ||
   1947        ni->Equals(nsGkAtoms::popupset, kNameSpaceID_XUL) ||
   1948        ((ni->Equals(nsGkAtoms::button, kNameSpaceID_XUL) ||
   1949          ni->Equals(nsGkAtoms::toolbarbutton, kNameSpaceID_XUL)) &&
   1950         parentContent->AsElement()->AttrValueIs(
   1951             kNameSpaceID_None, nsGkAtoms::type, nsGkAtoms::menu,
   1952             eCaseMatters))) {
   1953      return ConsumeOutsideClicks_Never;
   1954    }
   1955 #endif
   1956  }
   1957 
   1958  return ConsumeOutsideClicks_True;
   1959 }
   1960 
   1961 static ScrollContainerFrame* DoGetScrollContainerFrame(const nsIFrame* aFrame) {
   1962  if (const ScrollContainerFrame* sf = do_QueryFrame(aFrame)) {
   1963    return const_cast<ScrollContainerFrame*>(sf);
   1964  }
   1965  for (nsIFrame* childFrame : aFrame->PrincipalChildList()) {
   1966    if (auto* sf = DoGetScrollContainerFrame(childFrame)) {
   1967      return sf;
   1968    }
   1969  }
   1970  return nullptr;
   1971 }
   1972 
   1973 // XXXroc this is megalame. Fossicking around for a frame of the right
   1974 // type is a recipe for disaster in the long term.
   1975 ScrollContainerFrame* nsMenuPopupFrame::GetScrollContainerFrame() const {
   1976  return DoGetScrollContainerFrame(this);
   1977 }
   1978 
   1979 void nsMenuPopupFrame::ChangeByPage(bool aIsUp) {
   1980  // Only scroll by page within menulists.
   1981  if (!IsMenuList()) {
   1982    return;
   1983  }
   1984 
   1985  ScrollContainerFrame* scrollContainerFrame = GetScrollContainerFrame();
   1986 
   1987  RefPtr popup = &PopupElement();
   1988  XULButtonElement* currentMenu = popup->GetActiveMenuChild();
   1989  XULButtonElement* newMenu = nullptr;
   1990  if (!currentMenu) {
   1991    // If there is no current menu item, get the first item. When moving up,
   1992    // just use this as the newMenu and leave currentMenu null so that no check
   1993    // for a later element is performed. When moving down, set currentMenu so
   1994    // that we look for one page down from the first item.
   1995    newMenu = popup->GetFirstMenuItem();
   1996    if (!aIsUp) {
   1997      currentMenu = newMenu;
   1998    }
   1999  }
   2000 
   2001  if (currentMenu && currentMenu->GetPrimaryFrame()) {
   2002    const nscoord scrollHeight =
   2003        scrollContainerFrame ? scrollContainerFrame->GetScrollPortRect().height
   2004                             : mRect.height;
   2005    const nsRect currentRect = currentMenu->GetPrimaryFrame()->GetRect();
   2006    const XULButtonElement* startMenu = currentMenu;
   2007 
   2008    // Get the position of the current item and add or subtract one popup's
   2009    // height to or from it.
   2010    const nscoord targetPos = aIsUp ? currentRect.YMost() - scrollHeight
   2011                                    : currentRect.y + scrollHeight;
   2012    // Look for the next child which is just past the target position. This
   2013    // child will need to be selected.
   2014    for (; currentMenu;
   2015         currentMenu = aIsUp ? popup->GetPrevMenuItemFrom(*currentMenu)
   2016                             : popup->GetNextMenuItemFrom(*currentMenu)) {
   2017      if (!currentMenu->GetPrimaryFrame()) {
   2018        continue;
   2019      }
   2020      const nsRect curRect = currentMenu->GetPrimaryFrame()->GetRect();
   2021      const nscoord curPos = aIsUp ? curRect.y : curRect.YMost();
   2022      // If the right position was found, break out. Otherwise, look for another
   2023      // item.
   2024      if (aIsUp ? (curPos < targetPos) : (curPos > targetPos)) {
   2025        if (!newMenu || newMenu == startMenu) {
   2026          newMenu = currentMenu;
   2027        }
   2028        break;
   2029      }
   2030 
   2031      // Assign this item to newMenu. This item will be selected in case we
   2032      // don't find any more.
   2033      newMenu = currentMenu;
   2034    }
   2035  }
   2036 
   2037  // Select the new menuitem.
   2038  if (RefPtr newMenuRef = newMenu) {
   2039    popup->SetActiveMenuChild(newMenuRef);
   2040  }
   2041 }
   2042 
   2043 dom::XULPopupElement& nsMenuPopupFrame::PopupElement() const {
   2044  auto* popup = dom::XULPopupElement::FromNode(GetContent());
   2045  MOZ_DIAGNOSTIC_ASSERT(popup);
   2046  return *popup;
   2047 }
   2048 
   2049 XULButtonElement* nsMenuPopupFrame::GetCurrentMenuItem() const {
   2050  return PopupElement().GetActiveMenuChild();
   2051 }
   2052 
   2053 nsIFrame* nsMenuPopupFrame::GetCurrentMenuItemFrame() const {
   2054  auto* child = GetCurrentMenuItem();
   2055  return child ? child->GetPrimaryFrame() : nullptr;
   2056 }
   2057 
   2058 void nsMenuPopupFrame::HandleEnterKeyPress(WidgetEvent& aEvent) {
   2059  mIncrementalString.Truncate();
   2060  RefPtr popup = &PopupElement();
   2061  popup->HandleEnterKeyPress(aEvent);
   2062 }
   2063 
   2064 XULButtonElement* nsMenuPopupFrame::FindMenuWithShortcut(
   2065    mozilla::dom::KeyboardEvent& aKeyEvent, bool& aDoAction) {
   2066  uint32_t charCode = aKeyEvent.CharCode();
   2067  uint32_t keyCode = aKeyEvent.KeyCode();
   2068 
   2069  aDoAction = false;
   2070 
   2071  // Enumerate over our list of frames.
   2072  const bool isMenu = !IsMenuList();
   2073  TimeStamp keyTime = aKeyEvent.WidgetEventPtr()->mTimeStamp;
   2074  if (charCode == 0) {
   2075    if (keyCode == dom::KeyboardEvent_Binding::DOM_VK_BACK_SPACE) {
   2076      if (!isMenu && !mIncrementalString.IsEmpty()) {
   2077        mIncrementalString.SetLength(mIncrementalString.Length() - 1);
   2078        return nullptr;
   2079      }
   2080 #ifdef XP_WIN
   2081      if (nsCOMPtr<nsISound> sound = do_GetService("@mozilla.org/sound;1")) {
   2082        sound->Beep();
   2083      }
   2084 #endif  // #ifdef XP_WIN
   2085    }
   2086    return nullptr;
   2087  }
   2088  char16_t uniChar = ToLowerCase(static_cast<char16_t>(charCode));
   2089  if (isMenu) {
   2090    // Menu supports only first-letter navigation
   2091    mIncrementalString = uniChar;
   2092  } else if (IsWithinIncrementalTime(keyTime)) {
   2093    mIncrementalString.Append(uniChar);
   2094  } else {
   2095    // Interval too long, treat as new typing
   2096    mIncrementalString = uniChar;
   2097  }
   2098 
   2099  // See bug 188199 & 192346, if all letters in incremental string are same,
   2100  // just try to match the first one
   2101  nsAutoString incrementalString(mIncrementalString);
   2102  uint32_t charIndex = 1, stringLength = incrementalString.Length();
   2103  while (charIndex < stringLength &&
   2104         incrementalString[charIndex] == incrementalString[charIndex - 1]) {
   2105    charIndex++;
   2106  }
   2107  if (charIndex == stringLength) {
   2108    incrementalString.Truncate(1);
   2109    stringLength = 1;
   2110  }
   2111 
   2112  sLastKeyTime = keyTime;
   2113 
   2114  auto* item =
   2115      PopupElement().FindMenuWithShortcut(incrementalString, aDoAction);
   2116  if (item) {
   2117    return item;
   2118  }
   2119 
   2120  // If we don't match anything, rollback the last typing
   2121  mIncrementalString.SetLength(mIncrementalString.Length() - 1);
   2122 
   2123  // didn't find a matching menu item
   2124 #ifdef XP_WIN
   2125  // behavior on Windows - this item is in a menu popup off of the
   2126  // menu bar, so beep and do nothing else
   2127  if (isMenu) {
   2128    if (nsCOMPtr<nsISound> sound = do_GetService("@mozilla.org/sound;1")) {
   2129      sound->Beep();
   2130    }
   2131  }
   2132 #endif  // #ifdef XP_WIN
   2133 
   2134  return nullptr;
   2135 }
   2136 
   2137 nsIWidget* nsMenuPopupFrame::GetWidget() const { return mWidget.get(); }
   2138 
   2139 // helpers /////////////////////////////////////////////////////////////
   2140 
   2141 nsresult nsMenuPopupFrame::AttributeChanged(int32_t aNameSpaceID,
   2142                                            nsAtom* aAttribute,
   2143                                            AttrModType aModType)
   2144 
   2145 {
   2146  nsresult rv =
   2147      nsBlockFrame::AttributeChanged(aNameSpaceID, aAttribute, aModType);
   2148 
   2149  if (aAttribute == nsGkAtoms::left || aAttribute == nsGkAtoms::top) {
   2150    MoveToAttributePosition();
   2151  }
   2152 
   2153  if (aAttribute == nsGkAtoms::remote && GetWidget()) {
   2154    // When the remote attribute changes, we need to create a new widget to
   2155    // ensure that it has the correct compositor and transparency settings to
   2156    // match the new value. Do that only if we already have a widget.
   2157    // TODO(emilio): We should consider doing it only when we get re-shown or
   2158    // so.
   2159    PrepareWidget(true);
   2160  }
   2161 
   2162  if (aAttribute == nsGkAtoms::followanchor) {
   2163    if (nsXULPopupManager* pm = nsXULPopupManager::GetInstance()) {
   2164      pm->UpdateFollowAnchor(this);
   2165    }
   2166  }
   2167 
   2168  if (aAttribute == nsGkAtoms::label) {
   2169    // set the label for the titlebar
   2170    if (nsIWidget* widget = GetWidget()) {
   2171      nsAutoString title;
   2172      mContent->AsElement()->GetAttr(nsGkAtoms::label, title);
   2173      if (!title.IsEmpty()) {
   2174        widget->SetTitle(title);
   2175      }
   2176    }
   2177  } else if (aAttribute == nsGkAtoms::ignorekeys) {
   2178    nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
   2179    if (pm) {
   2180      nsAutoString ignorekeys;
   2181      mContent->AsElement()->GetAttr(nsGkAtoms::ignorekeys, ignorekeys);
   2182      pm->UpdateIgnoreKeys(ignorekeys.EqualsLiteral("true"));
   2183    }
   2184  }
   2185 
   2186  return rv;
   2187 }
   2188 
   2189 void nsMenuPopupFrame::MoveToAttributePosition() {
   2190  // Move the widget around when the user sets the |left| and |top| attributes.
   2191  // Note that this is not the best way to move the widget, as it results in
   2192  // lots of FE notifications and is likely to be slow as molasses. Use |moveTo|
   2193  // on the element if possible.
   2194  nsAutoString left, top;
   2195  mContent->AsElement()->GetAttr(nsGkAtoms::left, left);
   2196  mContent->AsElement()->GetAttr(nsGkAtoms::top, top);
   2197  nsresult err1, err2;
   2198  const CSSIntPoint pos(left.ToInteger(&err1), top.ToInteger(&err2));
   2199  if (NS_SUCCEEDED(err1) && NS_SUCCEEDED(err2)) {
   2200    MoveTo(pos, false);
   2201  }
   2202 
   2203  PresShell()->FrameNeedsReflow(
   2204      this, IntrinsicDirty::FrameAncestorsAndDescendants, NS_FRAME_IS_DIRTY);
   2205 }
   2206 
   2207 void nsMenuPopupFrame::Destroy(DestroyContext& aContext) {
   2208  // XXX: Currently we don't fire popuphidden for these popups, that seems wrong
   2209  // but alas, also pre-existing.
   2210  HidePopup(/* aDeselectMenu = */ false, ePopupClosed,
   2211            /* aFromFrameDestruction = */ true);
   2212  if (mExpirationState.IsTracked()) {
   2213    PopupExpirationTracker::Get()->RemoveObject(this);
   2214  }
   2215 
   2216  if (RefPtr<nsXULPopupManager> pm = nsXULPopupManager::GetInstance()) {
   2217    pm->PopupDestroyed(this);
   2218  }
   2219 
   2220  DestroyWidget();
   2221  nsBlockFrame::Destroy(aContext);
   2222 }
   2223 
   2224 nsMargin nsMenuPopupFrame::GetMargin() const {
   2225  nsMargin margin;
   2226  StyleMargin()->GetMargin(margin);
   2227  if (mIsTopLevelContextMenu) {
   2228    const CSSIntPoint offset(
   2229        LookAndFeel::GetInt(LookAndFeel::IntID::ContextMenuOffsetHorizontal),
   2230        LookAndFeel::GetInt(LookAndFeel::IntID::ContextMenuOffsetVertical));
   2231    auto auOffset = CSSIntPoint::ToAppUnits(offset);
   2232    margin.top += auOffset.y;
   2233    margin.bottom += auOffset.y;
   2234    margin.left += auOffset.x;
   2235    margin.right += auOffset.x;
   2236  }
   2237  if (mPopupType == PopupType::Tooltip && !IsAnchored()) {
   2238    const auto auOffset =
   2239        CSSPixel::ToAppUnits(LookAndFeel::TooltipOffsetVertical());
   2240    margin.top += auOffset;
   2241    margin.bottom += auOffset;
   2242  }
   2243  // TODO(emilio): We should consider make these properly mirrored (that is,
   2244  // changing -= to += here, and removing the rtl special case), but some tests
   2245  // rely on the old behavior of the anchor moving physically regardless of
   2246  // alignment...
   2247  margin.top += mExtraMargin.y;
   2248  margin.bottom -= mExtraMargin.y;
   2249  if (IsDirectionRTL()) {
   2250    margin.left -= mExtraMargin.x;
   2251    margin.right += mExtraMargin.x;
   2252  } else {
   2253    margin.left += mExtraMargin.x;
   2254    margin.right -= mExtraMargin.x;
   2255  }
   2256  return margin;
   2257 }
   2258 
   2259 void nsMenuPopupFrame::DestroyWidgetIfNeeded() {
   2260  if (IsVisibleOrShowing()) {
   2261    MOZ_ASSERT_UNREACHABLE("Shouldn't be tracked while visible");
   2262    return;
   2263  }
   2264  DestroyWidget();
   2265 }
   2266 
   2267 void nsMenuPopupFrame::MoveTo(const CSSPoint& aPos, bool aUpdateAttrs,
   2268                              bool aByMoveToRect) {
   2269  nsPoint appUnitsPos = CSSPixel::ToAppUnits(aPos);
   2270 
   2271  const bool rtl = IsDirectionRTL();
   2272 
   2273  // reposition the popup at the specified coordinates. Don't clear the anchor
   2274  // and position, because the popup can be reset to its anchor position by
   2275  // using (-1, -1) as coordinates.
   2276  //
   2277  // Subtract off the margin as it will be added to the position when
   2278  // SetPopupPosition is called.
   2279  {
   2280    nsMargin margin = GetMargin();
   2281    if (rtl && mIsContextMenu) {
   2282      appUnitsPos.x += margin.right + mRect.Width();
   2283    } else {
   2284      appUnitsPos.x -= margin.left;
   2285    }
   2286    appUnitsPos.y -= margin.top;
   2287  }
   2288 
   2289  if (mScreenRect.TopLeft() == appUnitsPos) {
   2290    return;
   2291  }
   2292 
   2293  mPositionedByMoveToRect = aByMoveToRect;
   2294  mScreenRect.MoveTo(appUnitsPos);
   2295  if (mAnchorType == MenuPopupAnchorType::Rect) {
   2296    // This ensures that the anchor width is still honored, to prevent it from
   2297    // changing spuriously.
   2298    mScreenRect.height = 0;
   2299    // But we still need to make sure that our top left position ends up in
   2300    // appUnitsPos.
   2301    mPopupAlignment = rtl ? POPUPALIGNMENT_TOPRIGHT : POPUPALIGNMENT_TOPLEFT;
   2302    mPopupAnchor = rtl ? POPUPALIGNMENT_BOTTOMRIGHT : POPUPALIGNMENT_BOTTOMLEFT;
   2303  } else {
   2304    mAnchorType = MenuPopupAnchorType::Point;
   2305  }
   2306 
   2307  SetPopupPosition(true);
   2308 
   2309  RefPtr<Element> popup = mContent->AsElement();
   2310  if (aUpdateAttrs &&
   2311      (popup->HasAttr(nsGkAtoms::left) || popup->HasAttr(nsGkAtoms::top))) {
   2312    nsAutoString left, top;
   2313    left.AppendInt(RoundedToInt(aPos).x);
   2314    top.AppendInt(RoundedToInt(aPos).y);
   2315    popup->SetAttr(kNameSpaceID_None, nsGkAtoms::left, left, false);
   2316    popup->SetAttr(kNameSpaceID_None, nsGkAtoms::top, top, false);
   2317  }
   2318 }
   2319 
   2320 void nsMenuPopupFrame::MoveToAnchor(nsIContent* aAnchorContent,
   2321                                    const nsAString& aPosition, int32_t aXPos,
   2322                                    int32_t aYPos, bool aAttributesOverride) {
   2323  NS_ASSERTION(IsVisibleOrShowing(),
   2324               "popup must be visible or showing to move it");
   2325 
   2326  nsPopupState oldstate = mPopupState;
   2327  InitializePopup(aAnchorContent, mTriggerContent, aPosition, aXPos, aYPos,
   2328                  MenuPopupAnchorType::Node, aAttributesOverride);
   2329  // InitializePopup changed the state so reset it.
   2330  mPopupState = oldstate;
   2331 
   2332  // Pass false here so that flipping and adjusting to fit on the screen happen.
   2333  SetPopupPosition(false);
   2334 }
   2335 
   2336 int8_t nsMenuPopupFrame::GetAlignmentPosition() const {
   2337  // The code below handles most cases of alignment, anchor and position values.
   2338  // Those that are not handled just return POPUPPOSITION_UNKNOWN.
   2339 
   2340  if (mPosition == POPUPPOSITION_OVERLAP ||
   2341      mPosition == POPUPPOSITION_AFTERPOINTER ||
   2342      mPosition == POPUPPOSITION_SELECTION) {
   2343    return mPosition;
   2344  }
   2345 
   2346  int8_t position = mPosition;
   2347 
   2348  if (position == POPUPPOSITION_UNKNOWN) {
   2349    switch (mPopupAnchor) {
   2350      case POPUPALIGNMENT_BOTTOMRIGHT:
   2351      case POPUPALIGNMENT_BOTTOMLEFT:
   2352      case POPUPALIGNMENT_BOTTOMCENTER:
   2353        position = mPopupAlignment == POPUPALIGNMENT_TOPRIGHT
   2354                       ? POPUPPOSITION_AFTEREND
   2355                       : POPUPPOSITION_AFTERSTART;
   2356        break;
   2357      case POPUPALIGNMENT_TOPRIGHT:
   2358      case POPUPALIGNMENT_TOPLEFT:
   2359      case POPUPALIGNMENT_TOPCENTER:
   2360        position = mPopupAlignment == POPUPALIGNMENT_BOTTOMRIGHT
   2361                       ? POPUPPOSITION_BEFOREEND
   2362                       : POPUPPOSITION_BEFORESTART;
   2363        break;
   2364      case POPUPALIGNMENT_LEFTCENTER:
   2365        position = mPopupAlignment == POPUPALIGNMENT_BOTTOMRIGHT
   2366                       ? POPUPPOSITION_STARTAFTER
   2367                       : POPUPPOSITION_STARTBEFORE;
   2368        break;
   2369      case POPUPALIGNMENT_RIGHTCENTER:
   2370        position = mPopupAlignment == POPUPALIGNMENT_BOTTOMLEFT
   2371                       ? POPUPPOSITION_ENDAFTER
   2372                       : POPUPPOSITION_ENDBEFORE;
   2373        break;
   2374      default:
   2375        break;
   2376    }
   2377  }
   2378 
   2379  if (mHFlip) {
   2380    position = POPUPPOSITION_HFLIP(position);
   2381  }
   2382 
   2383  if (mVFlip) {
   2384    position = POPUPPOSITION_VFLIP(position);
   2385  }
   2386 
   2387  return position;
   2388 }
   2389 
   2390 bool nsMenuPopupFrame::ShouldFollowAnchor() const {
   2391  if (mAnchorType != MenuPopupAnchorType::Node || !mAnchorContent) {
   2392    return false;
   2393  }
   2394 
   2395  // Follow anchor mode is used when followanchor="true" is set or for arrow
   2396  // panels.
   2397  if (mContent->AsElement()->AttrValueIs(kNameSpaceID_None,
   2398                                         nsGkAtoms::followanchor,
   2399                                         nsGkAtoms::_true, eCaseMatters)) {
   2400    return true;
   2401  }
   2402 
   2403  if (mContent->AsElement()->AttrValueIs(kNameSpaceID_None,
   2404                                         nsGkAtoms::followanchor,
   2405                                         nsGkAtoms::_false, eCaseMatters)) {
   2406    return false;
   2407  }
   2408 
   2409  return mPopupType == PopupType::Panel &&
   2410         mContent->AsElement()->AttrValueIs(kNameSpaceID_None, nsGkAtoms::type,
   2411                                            nsGkAtoms::arrow, eCaseMatters);
   2412 }
   2413 
   2414 bool nsMenuPopupFrame::ShouldFollowAnchor(nsRect& aRect) {
   2415  if (!ShouldFollowAnchor()) {
   2416    return false;
   2417  }
   2418 
   2419  if (nsIFrame* anchorFrame = GetAnchorFrame()) {
   2420    if (nsPresContext* rootPresContext = PresContext()->GetRootPresContext()) {
   2421      aRect = ComputeAnchorRect(rootPresContext, anchorFrame);
   2422    }
   2423  }
   2424 
   2425  return true;
   2426 }
   2427 
   2428 bool nsMenuPopupFrame::IsDirectionRTL() const {
   2429  const nsIFrame* anchor = GetAnchorFrame();
   2430  const nsIFrame* f = anchor ? anchor : this;
   2431  return f->StyleVisibility()->mDirection == StyleDirection::Rtl;
   2432 }
   2433 
   2434 nsIFrame* nsMenuPopupFrame::GetAnchorFrame() const {
   2435  nsIContent* anchor = mAnchorContent;
   2436  if (!anchor) {
   2437    return nullptr;
   2438  }
   2439  return MaybeDelegatedAnchorFrame(anchor->GetPrimaryFrame());
   2440 }
   2441 
   2442 void nsMenuPopupFrame::CheckForAnchorChange(nsRect& aRect) {
   2443  // Don't update if the popup isn't visible or we shouldn't be following the
   2444  // anchor.
   2445  if (!IsVisible() || !ShouldFollowAnchor()) {
   2446    return;
   2447  }
   2448 
   2449  bool shouldHide = false;
   2450 
   2451  nsPresContext* rootPresContext = PresContext()->GetRootPresContext();
   2452 
   2453  // If the frame for the anchor has gone away, hide the popup.
   2454  nsIFrame* anchor = GetAnchorFrame();
   2455  if (!anchor || !rootPresContext) {
   2456    shouldHide = true;
   2457  } else if (!anchor->IsVisibleConsideringAncestors(
   2458                 VISIBILITY_CROSS_CHROME_CONTENT_BOUNDARY)) {
   2459    // If the anchor is now inside something that is invisible, hide the popup.
   2460    shouldHide = true;
   2461  } else {
   2462    // If the anchor is now inside a hidden parent popup, hide the popup.
   2463    nsIFrame* frame = anchor;
   2464    while (frame) {
   2465      nsMenuPopupFrame* popup = do_QueryFrame(frame);
   2466      if (popup && popup->PopupState() != ePopupShown) {
   2467        shouldHide = true;
   2468        break;
   2469      }
   2470 
   2471      frame = frame->GetParent();
   2472    }
   2473  }
   2474 
   2475  if (shouldHide) {
   2476    if (nsXULPopupManager* pm = nsXULPopupManager::GetInstance()) {
   2477      // As the caller will be iterating over the open popups, hide
   2478      // asyncronously.
   2479      pm->HidePopup(mContent->AsElement(),
   2480                    {HidePopupOption::DeselectMenu, HidePopupOption::Async});
   2481    }
   2482 
   2483    return;
   2484  }
   2485 
   2486  nsRect anchorRect = ComputeAnchorRect(rootPresContext, anchor);
   2487 
   2488  // If the rectangles are different, move the popup.
   2489  if (!anchorRect.IsEqualEdges(aRect)) {
   2490    aRect = anchorRect;
   2491    SetPopupPosition(true);
   2492  }
   2493 }
   2494 
   2495 void nsMenuPopupFrame::WindowMoved(nsIWidget* aWidget,
   2496                                   const LayoutDeviceIntPoint& aPoint,
   2497                                   ByMoveToRect aByMoveToRect) {
   2498  MOZ_ASSERT(aWidget == mWidget);
   2499 
   2500  if (!IsVisibleOrShowing()) {
   2501    return;
   2502  }
   2503 
   2504  // Don't do anything if the popup is already at the specified location. This
   2505  // prevents recursive calls when a popup is positioned.
   2506  LayoutDeviceIntRect curDevBounds = CalcWidgetBounds();
   2507  if (curDevBounds.TopLeft() == aPoint) {
   2508    return;
   2509  }
   2510 
   2511  // Update the popup's position using SetPopupPosition if the popup is
   2512  // anchored and at the parent level as these maintain their position
   2513  // relative to the parent window (except if positioned by move to rect, in
   2514  // which case we better make sure that layout matches that). Otherwise, just
   2515  // update the popup to the specified screen coordinates.
   2516  if (IsAnchored() && GetPopupLevel() == widget::PopupLevel::Parent &&
   2517      aByMoveToRect == ByMoveToRect::No) {
   2518    SetPopupPosition(true);
   2519  } else {
   2520    CSSPoint cssPos = aPoint / PresContext()->CSSToDevPixelScale();
   2521    MoveTo(cssPos, false, aByMoveToRect == ByMoveToRect::Yes);
   2522  }
   2523 }
   2524 
   2525 void nsMenuPopupFrame::WindowResized(nsIWidget* aWidget,
   2526                                     const LayoutDeviceIntSize& aSize) {
   2527  MOZ_ASSERT(aWidget == mWidget);
   2528  if (!IsVisibleOrShowing()) {
   2529    return;
   2530  }
   2531 
   2532  const LayoutDeviceIntRect curDevBounds = CalcWidgetBounds();
   2533  // If the size is what we think it is, we have nothing to do.
   2534  if (curDevBounds.Size() == aSize) {
   2535    return;
   2536  }
   2537 
   2538  RefPtr<Element> popup = &PopupElement();
   2539 
   2540  // Only set the width and height if the popup already has these attributes.
   2541  if (!popup->HasAttr(nsGkAtoms::width) || !popup->HasAttr(nsGkAtoms::height)) {
   2542    return;
   2543  }
   2544 
   2545  // The size is different. Convert the actual size to css pixels and store it
   2546  // as 'width' and 'height' attributes on the popup.
   2547  nsPresContext* presContext = PresContext();
   2548 
   2549  CSSIntSize newCSS(presContext->DevPixelsToIntCSSPixels(aSize.width),
   2550                    presContext->DevPixelsToIntCSSPixels(aSize.height));
   2551 
   2552  nsAutoString width, height;
   2553  width.AppendInt(newCSS.width);
   2554  height.AppendInt(newCSS.height);
   2555  popup->SetAttr(kNameSpaceID_None, nsGkAtoms::width, width, true);
   2556  popup->SetAttr(kNameSpaceID_None, nsGkAtoms::height, height, true);
   2557 }
   2558 
   2559 bool nsMenuPopupFrame::RequestWindowClose(nsIWidget* aWidget) {
   2560  MOZ_ASSERT(aWidget == mWidget);
   2561  if (nsXULPopupManager* pm = nsXULPopupManager::GetInstance()) {
   2562    pm->HidePopup(&PopupElement(), {HidePopupOption::DeselectMenu});
   2563    return true;
   2564  }
   2565  return false;
   2566 }
   2567 
   2568 nsEventStatus nsMenuPopupFrame::HandleEvent(mozilla::WidgetGUIEvent* aEvent) {
   2569  MOZ_ASSERT(aEvent->mWidget);
   2570  MOZ_ASSERT(aEvent->mWidget == mWidget);
   2571  nsEventStatus status = nsEventStatus_eIgnore;
   2572  RefPtr ps = PresShell();
   2573  ps->HandleEvent(this, aEvent, false, &status);
   2574  return status;
   2575 }
   2576 
   2577 void nsMenuPopupFrame::PaintWindow(nsIWidget* aWidget) {
   2578  MOZ_ASSERT(aWidget == mWidget);
   2579  nsAutoScriptBlocker scriptBlocker;
   2580  RefPtr ps = PresShell();
   2581  RefPtr<WindowRenderer> renderer = aWidget->GetWindowRenderer();
   2582  if (!renderer->NeedsWidgetInvalidation()) {
   2583    renderer->FlushRendering(wr::RenderReasons::WIDGET);
   2584  } else {
   2585    ps->SyncPaintFallback(this, renderer);
   2586  }
   2587 }
   2588 
   2589 void nsMenuPopupFrame::DidCompositeWindow(
   2590    mozilla::layers::TransactionId aTransactionId,
   2591    const TimeStamp& aCompositeStart, const TimeStamp& aCompositeEnd) {
   2592  RefPtr rootPc = PresContext()->GetRootPresContext();
   2593  if (!rootPc) {
   2594    return;
   2595  }
   2596  nsAutoScriptBlocker scriptBlocker;
   2597  rootPc->NotifyDidPaintForSubtree(aTransactionId, aCompositeEnd);
   2598 }