tor-browser

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

nsXULPopupManager.cpp (107986B)


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