Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/Firefox/layout/xul/   (Browser von der Mozilla Stiftung Version 136.0.1©)  Datei vom 10.2.2025 mit Größe 104 kB image not shown  

Quelle  nsXULPopupManager.cpp   Sprache: C

 
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */


#include "XULButtonElement.h"
#include "mozilla/Assertions.h"
#include "mozilla/Attributes.h"
#include "mozilla/FlushType.h"
#include "mozilla/UniquePtr.h"
#include "nsGkAtoms.h"
#include "nsISound.h"
#include "nsXULPopupManager.h"
#include "nsMenuPopupFrame.h"
#include "nsContentUtils.h"
#include "nsXULElement.h"
#include "nsIDOMXULCommandDispatcher.h"
#include "nsCSSFrameConstructor.h"
#include "nsGlobalWindowOuter.h"
#include "nsIContentInlines.h"
#include "nsLayoutUtils.h"
#include "nsViewManager.h"
#include "nsITimer.h"
#include "nsFocusManager.h"
#include "nsIDocShell.h"
#include "nsPIDOMWindow.h"
#include "nsIInterfaceRequestorUtils.h"
#include "nsIBaseWindow.h"
#include "nsCaret.h"
#include "mozilla/dom/Document.h"
#include "nsPIWindowRoot.h"
#include "nsFrameManager.h"
#include "nsPresContextInlines.h"
#include "nsIObserverService.h"
#include "mozilla/AnimationUtils.h"
#include "mozilla/dom/DocumentInlines.h"
#include "mozilla/dom/Element.h"
#include "mozilla/dom/Event.h"  // for Event
#include "mozilla/dom/HTMLSlotElement.h"
#include "mozilla/dom/KeyboardEvent.h"
#include "mozilla/dom/KeyboardEventBinding.h"
#include "mozilla/dom/MouseEvent.h"
#include "mozilla/dom/UIEvent.h"
#include "mozilla/dom/UserActivation.h"
#include "mozilla/dom/PopupPositionedEvent.h"
#include "mozilla/dom/PopupPositionedEventBinding.h"
#include "mozilla/dom/XULCommandEvent.h"
#include "mozilla/dom/XULMenuElement.h"
#include "mozilla/dom/XULMenuBarElement.h"
#include "mozilla/dom/XULPopupElement.h"
#include "mozilla/AutoRestore.h"
#include "mozilla/EventDispatcher.h"
#include "mozilla/EventStateManager.h"
#include "mozilla/LookAndFeel.h"
#include "mozilla/MouseEvents.h"
#include "mozilla/PointerLockManager.h"
#include "mozilla/PresShell.h"
#include "mozilla/Services.h"
#include "mozilla/StaticPrefs_ui.h"
#include "mozilla/widget/nsAutoRollup.h"
#include "mozilla/widget/NativeMenuSupport.h"

using namespace mozilla;
using namespace mozilla::dom;
using mozilla::widget::NativeMenu;

static_assert(KeyboardEvent_Binding::DOM_VK_HOME ==
                      KeyboardEvent_Binding::DOM_VK_END + 1 &&
                  KeyboardEvent_Binding::DOM_VK_LEFT ==
                      KeyboardEvent_Binding::DOM_VK_END + 2 &&
                  KeyboardEvent_Binding::DOM_VK_UP ==
                      KeyboardEvent_Binding::DOM_VK_END + 3 &&
                  KeyboardEvent_Binding::DOM_VK_RIGHT ==
                      KeyboardEvent_Binding::DOM_VK_END + 4 &&
                  KeyboardEvent_Binding::DOM_VK_DOWN ==
                      KeyboardEvent_Binding::DOM_VK_END + 5,
              "nsXULPopupManager assumes some keyCode values are consecutive");

#define NS_DIRECTION_IS_INLINE(dir) \
  (dir == eNavigationDirection_Start || dir == eNavigationDirection_End)
#define NS_DIRECTION_IS_BLOCK(dir) \
  (dir == eNavigationDirection_Before || dir == eNavigationDirection_After)
#define NS_DIRECTION_IS_BLOCK_TO_EDGE(dir) \
  (dir == eNavigationDirection_First || dir == eNavigationDirection_Last)

static_assert(static_cast<uint8_t>(mozilla::StyleDirection::Ltr) == 0 &&
                  static_cast<uint8_t>(mozilla::StyleDirection::Rtl) == 1,
              "Left to Right should be 0 and Right to Left should be 1");

const nsNavigationDirection DirectionFromKeyCodeTable[2][6] = {
    {
        eNavigationDirection_Last,    // KeyboardEvent_Binding::DOM_VK_END
        eNavigationDirection_First,   // KeyboardEvent_Binding::DOM_VK_HOME
        eNavigationDirection_Start,   // KeyboardEvent_Binding::DOM_VK_LEFT
        eNavigationDirection_Before,  // KeyboardEvent_Binding::DOM_VK_UP
        eNavigationDirection_End,     // KeyboardEvent_Binding::DOM_VK_RIGHT
        eNavigationDirection_After    // KeyboardEvent_Binding::DOM_VK_DOWN
    },
    {
        eNavigationDirection_Last,    // KeyboardEvent_Binding::DOM_VK_END
        eNavigationDirection_First,   // KeyboardEvent_Binding::DOM_VK_HOME
        eNavigationDirection_End,     // KeyboardEvent_Binding::DOM_VK_LEFT
        eNavigationDirection_Before,  // KeyboardEvent_Binding::DOM_VK_UP
        eNavigationDirection_Start,   // KeyboardEvent_Binding::DOM_VK_RIGHT
        eNavigationDirection_After    // KeyboardEvent_Binding::DOM_VK_DOWN
    }};

nsXULPopupManager* nsXULPopupManager::sInstance = nullptr;

PendingPopup::PendingPopup(Element* aPopup, mozilla::dom::Event* aEvent)
    : mPopup(aPopup), mEvent(aEvent), mModifiers(0) {
  InitMousePoint();
}

void PendingPopup::InitMousePoint() {
  // get the event coordinates relative to the root frame of the document
  // containing the popup.
  if (!mEvent) {
    return;
  }

  WidgetEvent* event = mEvent->WidgetEventPtr();
  WidgetInputEvent* inputEvent = event->AsInputEvent();
  if (inputEvent) {
    mModifiers = inputEvent->mModifiers;
  }
  Document* doc = mPopup->GetUncomposedDoc();
  if (!doc) {
    return;
  }

  PresShell* presShell = doc->GetPresShell();
  nsPresContext* presContext;
  if (presShell && (presContext = presShell->GetPresContext())) {
    nsPresContext* rootDocPresContext = presContext->GetRootPresContext();
    if (!rootDocPresContext) {
      return;
    }

    nsIFrame* rootDocumentRootFrame =
        rootDocPresContext->PresShell()->GetRootFrame();
    if ((event->IsMouseEventClassOrHasClickRelatedPointerEvent() ||
         event->mClass == eMouseScrollEventClass ||
         event->mClass == eWheelEventClass) &&
        !event->AsGUIEvent()->mWidget) {
      // no widget, so just use the client point if available
      MouseEvent* mouseEvent = mEvent->AsMouseEvent();
      const CSSIntPoint clientPt(RoundedToInt(mouseEvent->ClientPoint()));

      // XXX this doesn't handle IFRAMEs in transforms
      nsPoint thisDocToRootDocOffset =
          presShell->GetRootFrame()->GetOffsetToCrossDoc(rootDocumentRootFrame);
      // convert to device pixels
      mMousePoint.x = presContext->AppUnitsToDevPixels(
          nsPresContext::CSSPixelsToAppUnits(clientPt.x) +
          thisDocToRootDocOffset.x);
      mMousePoint.y = presContext->AppUnitsToDevPixels(
          nsPresContext::CSSPixelsToAppUnits(clientPt.y) +
          thisDocToRootDocOffset.y);
    } else if (rootDocumentRootFrame) {
      nsPoint pnt = nsLayoutUtils::GetEventCoordinatesRelativeTo(
          event, RelativeTo{rootDocumentRootFrame});
      mMousePoint =
          LayoutDeviceIntPoint(rootDocPresContext->AppUnitsToDevPixels(pnt.x),
                               rootDocPresContext->AppUnitsToDevPixels(pnt.y));
    }
  }
}

already_AddRefed<nsIContent> PendingPopup::GetTriggerContent() const {
  nsCOMPtr<nsIContent> target =
      do_QueryInterface(mEvent ? mEvent->GetTarget() : nullptr);
  return target.forget();
}

uint16_t PendingPopup::MouseInputSource() const {
  if (mEvent) {
    mozilla::WidgetMouseEventBase* mouseEvent =
        mEvent->WidgetEventPtr()->AsMouseEventBase();
    if (mouseEvent) {
      return mouseEvent->mInputSource;
    }

    RefPtr<XULCommandEvent> commandEvent = mEvent->AsXULCommandEvent();
    if (commandEvent) {
      return commandEvent->InputSource();
    }
  }

  return MouseEvent_Binding::MOZ_SOURCE_UNKNOWN;
}

XULPopupElement* nsMenuChainItem::Element() { return &mFrame->PopupElement(); }

void nsMenuChainItem::SetParent(UniquePtr<nsMenuChainItem> aParent) {
  MOZ_ASSERT_IF(aParent, !aParent->mChild);
  auto oldParent = Detach();
  mParent = std::move(aParent);
  if (mParent) {
    mParent->mChild = this;
  }
}

UniquePtr<nsMenuChainItem> nsMenuChainItem::Detach() {
  if (mParent) {
    MOZ_ASSERT(mParent->mChild == this,
               "Unexpected - parent's child not set to this");
    mParent->mChild = nullptr;
  }
  return std::move(mParent);
}

void nsXULPopupManager::AddMenuChainItem(UniquePtr<nsMenuChainItem> aItem) {
  PopupType popupType = aItem->Frame()->GetPopupType();
  if (StaticPrefs::layout_cursor_disable_for_popups() &&
      popupType != PopupType::Tooltip) {
    if (nsPresContext* rootPC =
            aItem->Frame()->PresContext()->GetRootPresContext()) {
      if (nsCOMPtr<nsIWidget> rootWidget = rootPC->GetRootWidget()) {
        rootWidget->SetCustomCursorAllowed(false);
      }
    }
  }

  // popups normally hide when an outside click occurs. Panels may use
  // the noautohide attribute to disable this behaviour. It is expected
  // that the application will hide these popups manually. The tooltip
  // listener will handle closing the tooltip also.
  nsIContent* oldmenu = nullptr;
  if (mPopups) {
    oldmenu = mPopups->Element();
  }
  aItem->SetParent(std::move(mPopups));
  mPopups = std::move(aItem);
  SetCaptureState(oldmenu);
}

void nsXULPopupManager::RemoveMenuChainItem(nsMenuChainItem* aItem) {
  nsPresContext* rootPC = aItem->Frame()->PresContext()->GetRootPresContext();
  auto matcher = [&](nsMenuChainItem* aChainItem) -> bool {
    return aChainItem != aItem &&
           rootPC == aChainItem->Frame()->PresContext()->GetRootPresContext();
  };
  if (rootPC && !FirstMatchingPopup(matcher)) {
    if (nsCOMPtr<nsIWidget> rootWidget = rootPC->GetRootWidget()) {
      rootWidget->SetCustomCursorAllowed(true);
    }
  }

  auto parent = aItem->Detach();
  if (auto* child = aItem->GetChild()) {
    MOZ_ASSERT(aItem != mPopups,
               "Unexpected - popup with child at end of chain");
    // This will kill aItem by changing child's mParent pointer.
    child->SetParent(std::move(parent));
  } else {
    // An item without a child should be the first item in the chain, so set
    // the first item pointer, pointed to by aRoot, to the parent.
    MOZ_ASSERT(aItem == mPopups,
               "Unexpected - popup with no child not at end of chain");
    mPopups = std::move(parent);
  }
}

nsMenuChainItem* nsXULPopupManager::FirstMatchingPopup(
    mozilla::FunctionRef<bool(nsMenuChainItem*)> aMatcher) const {
  for (nsMenuChainItem* popup = mPopups.get(); popup;
       popup = popup->GetParent()) {
    if (aMatcher(popup)) {
      return popup;
    }
  }
  return nullptr;
}

void nsMenuChainItem::UpdateFollowAnchor() {
  mFollowAnchor = mFrame->ShouldFollowAnchor(mCurrentRect);
}

void nsMenuChainItem::CheckForAnchorChange() {
  if (mFollowAnchor) {
    mFrame->CheckForAnchorChange(mCurrentRect);
  }
}

NS_IMPL_ISUPPORTS(nsXULPopupManager, nsIDOMEventListener, nsIObserver)

nsXULPopupManager::nsXULPopupManager()
    : mActiveMenuBar(nullptr), mPopups(nullptr), mPendingPopup(nullptr) {
  nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
  if (obs) {
    obs->AddObserver(this"xpcom-shutdown"false);
  }
}

nsXULPopupManager::~nsXULPopupManager() {
  NS_ASSERTION(!mPopups, "XUL popups still open");

  if (mNativeMenu) {
    mNativeMenu->RemoveObserver(this);
  }
}

nsresult nsXULPopupManager::Init() {
  sInstance = new nsXULPopupManager();
  NS_ENSURE_TRUE(sInstance, NS_ERROR_OUT_OF_MEMORY);
  NS_ADDREF(sInstance);
  return NS_OK;
}

void nsXULPopupManager::Shutdown() { NS_IF_RELEASE(sInstance); }

NS_IMETHODIMP
nsXULPopupManager::Observe(nsISupports* aSubject, const char* aTopic,
                           const char16_t* aData) {
  if (!nsCRT::strcmp(aTopic, "xpcom-shutdown")) {
    if (mKeyListener) {
      mKeyListener->RemoveEventListener(u"keypress"_ns, thistrue);
      mKeyListener->RemoveEventListener(u"keydown"_ns, thistrue);
      mKeyListener->RemoveEventListener(u"keyup"_ns, thistrue);
      mKeyListener = nullptr;
    }
    nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
    if (obs) {
      obs->RemoveObserver(this"xpcom-shutdown");
    }
  }

  return NS_OK;
}

nsXULPopupManager* nsXULPopupManager::GetInstance() {
  MOZ_ASSERT(sInstance);
  return sInstance;
}

bool nsXULPopupManager::RollupTooltips() {
  const RollupOptions options{0, FlushViews::Yes, nullptr, AllowAnimations::No};
  return RollupInternal(RollupKind::Tooltip, options, nullptr);
}

bool nsXULPopupManager::Rollup(const RollupOptions& aOptions,
                               nsIContent** aLastRolledUp) {
  return RollupInternal(RollupKind::Menu, aOptions, aLastRolledUp);
}

bool nsXULPopupManager::RollupNativeMenu() {
  if (mNativeMenu) {
    RefPtr<NativeMenu> menu = mNativeMenu;
    return menu->Close();
  }
  return false;
}

bool nsXULPopupManager::RollupInternal(RollupKind aKind,
                                       const RollupOptions& aOptions,
                                       nsIContent** aLastRolledUp) {
  if (aLastRolledUp) {
    *aLastRolledUp = nullptr;
  }

  // We can disable the autohide behavior via a pref to ease debugging.
  if (StaticPrefs::ui_popup_disable_autohide()) {
    // Required on linux to allow events to work on other targets.
    if (mWidget) {
      mWidget->CaptureRollupEvents(false);
    }
    return false;
  }

  nsMenuChainItem* item = GetRollupItem(aKind);
  if (!item) {
    return false;
  }
  if (aLastRolledUp) {
    // We need to get the popup that will be closed last, so that widget can
    // keep track of it so it doesn't reopen if a mousedown event is going to
    // processed. Keep going up the menu chain to get the first level menu of
    // the same type. If a different type is encountered it means we have,
    // for example, a menulist or context menu inside a panel, and we want to
    // treat these as distinct. It's possible that this menu doesn't end up
    // closing because the popuphiding event was cancelled, but in that case
    // we don't need to deal with the menu reopening as it will already still
    // be open.
    nsMenuChainItem* first = item;
    while (first->GetParent()) {
      nsMenuChainItem* parent = first->GetParent();
      if (first->Frame()->GetPopupType() != parent->Frame()->GetPopupType() ||
          first->IsContextMenu() != parent->IsContextMenu()) {
        break;
      }
      first = parent;
    }

    *aLastRolledUp = first->Element();
  }

  ConsumeOutsideClicksResult consumeResult =
      item->Frame()->ConsumeOutsideClicks();
  bool consume = consumeResult == ConsumeOutsideClicks_True;
  bool rollup = true;

  // If norolluponanchor is true, then don't rollup when clicking the anchor.
  // This would be used to allow adjusting the caret position in an
  // autocomplete field without hiding the popup for example.
  bool noRollupOnAnchor =
      (!consume && aOptions.mPoint &&
       item->Frame()->GetContent()->AsElement()->AttrValueIs(
           kNameSpaceID_None, nsGkAtoms::norolluponanchor, nsGkAtoms::_true,
           eCaseMatters));

  // When ConsumeOutsideClicks_ParentOnly is used, always consume the click
  // when the click was over the anchor. This way, clicking on a menu doesn't
  // reopen the menu.
  if ((consumeResult == ConsumeOutsideClicks_ParentOnly || noRollupOnAnchor) &&
      aOptions.mPoint) {
    nsMenuPopupFrame* popupFrame = item->Frame();
    CSSIntRect anchorRect = [&] {
      if (popupFrame->IsAnchored()) {
        // Check if the popup has an anchor rectangle set. If not, get the
        // rectangle from the anchor element.
        auto r = popupFrame->GetScreenAnchorRect();
        if (r.x != -1 && r.y != -1) {
          // Prefer the untransformed anchor rect, so as to account for Wayland
          // properly. Note we still need to check GetScreenAnchorRect() tho, so
          // as to detect whether the anchor came from the popup opening call,
          // or from an element (in which case we want to take the code-path
          // below)..
          auto untransformed = popupFrame->GetUntransformedAnchorRect();
          if (!untransformed.IsEmpty()) {
            return CSSIntRect::FromAppUnitsRounded(untransformed);
          }
          return r;
        }
      }

      auto* anchor = Element::FromNodeOrNull(popupFrame->GetAnchor());
      if (!anchor) {
        return CSSIntRect();
      }

      // Check if the anchor has indicated another node to use for checking
      // for roll-up. That way, we can anchor a popup on anonymous content
      // or an individual icon, while clicking elsewhere within a button or
      // other container doesn't result in us re-opening the popup.
      nsAutoString consumeAnchor;
      anchor->GetAttr(nsGkAtoms::consumeanchor, consumeAnchor);
      if (!consumeAnchor.IsEmpty()) {
        if (Element* newAnchor =
                anchor->OwnerDoc()->GetElementById(consumeAnchor)) {
          anchor = newAnchor;
        }
      }

      nsIFrame* f = anchor->GetPrimaryFrame();
      if (!f) {
        return CSSIntRect();
      }
      return f->GetScreenRect();
    }();

    // It's possible that some other element is above the anchor at the same
    // position, but the only thing that would happen is that the mouse
    // event will get consumed, so here only a quick coordinates check is
    // done rather than a slower complete check of what is at that location.
    nsPresContext* presContext = item->Frame()->PresContext();
    CSSIntPoint posCSSPixels =
        presContext->DevPixelsToIntCSSPixels(*aOptions.mPoint);
    if (anchorRect.Contains(posCSSPixels)) {
      if (consumeResult == ConsumeOutsideClicks_ParentOnly) {
        consume = true;
      }

      if (noRollupOnAnchor) {
        rollup = false;
      }
    }
  }

  if (!rollup) {
    return false;
  }

  // If a number of popups to close has been specified, determine the last
  // popup to close.
  Element* lastPopup = nullptr;
  uint32_t count = aOptions.mCount;
  if (count && count != UINT32_MAX) {
    nsMenuChainItem* last = item;
    while (--count && last->GetParent()) {
      last = last->GetParent();
    }
    if (last) {
      lastPopup = last->Element();
    }
  }

  nsPresContext* presContext = item->Frame()->PresContext();
  RefPtr<nsViewManager> viewManager =
      presContext->PresShell()->GetViewManager();

  HidePopupOptions options{HidePopupOption::HideChain,
                           HidePopupOption::DeselectMenu,
                           HidePopupOption::IsRollup};
  if (aOptions.mAllowAnimations == AllowAnimations::No) {
    options += HidePopupOption::DisableAnimations;
  }

  HidePopup(item->Element(), options, lastPopup);

  if (aOptions.mFlush == FlushViews::Yes) {
    // The popup's visibility doesn't update until the minimize animation
    // has finished, so call UpdateWidgetGeometry to update it right away.
    viewManager->UpdateWidgetGeometry();
  }

  return consume;
}

////////////////////////////////////////////////////////////////////////
bool nsXULPopupManager::ShouldRollupOnMouseWheelEvent() {
  // should rollup only for autocomplete widgets
  // XXXndeakin this should really be something the popup has more control over

  nsMenuChainItem* item = GetTopVisibleMenu();
  if (!item) {
    return false;
  }

  nsIContent* content = item->Frame()->GetContent();
  if (!content || !content->IsElement()) {
    return false;
  }

  Element* element = content->AsElement();
  if (element->AttrValueIs(kNameSpaceID_None, nsGkAtoms::rolluponmousewheel,
                           nsGkAtoms::_true, eCaseMatters)) {
    return true;
  }

  if (element->AttrValueIs(kNameSpaceID_None, nsGkAtoms::rolluponmousewheel,
                           nsGkAtoms::_false, eCaseMatters)) {
    return false;
  }

  nsAutoString value;
  element->GetAttr(nsGkAtoms::type, value);
  return StringBeginsWith(value, u"autocomplete"_ns);
}

bool nsXULPopupManager::ShouldConsumeOnMouseWheelEvent() {
  nsMenuChainItem* item = GetTopVisibleMenu();
  if (!item) {
    return false;
  }

  nsMenuPopupFrame* frame = item->Frame();
  if (frame->GetPopupType() != PopupType::Panel) {
    return true;
  }

  return !frame->GetContent()->AsElement()->AttrValueIs(
      kNameSpaceID_None, nsGkAtoms::type, nsGkAtoms::arrow, eCaseMatters);
}

// a menu should not roll up if activated by a mouse activate message (eg.
// X-mouse)
bool nsXULPopupManager::ShouldRollupOnMouseActivate() { return false; }

uint32_t nsXULPopupManager::GetSubmenuWidgetChain(
    nsTArray<nsIWidget*>* aWidgetChain) {
  // this method is used by the widget code to determine the list of popups
  // that are open. If a mouse click occurs outside one of these popups, the
  // panels will roll up. If the click is inside a popup, they will not roll up
  uint32_t count = 0, sameTypeCount = 0;

  NS_ASSERTION(aWidgetChain, "null parameter");
  nsMenuChainItem* item = GetTopVisibleMenu();
  while (item) {
    nsMenuChainItem* parent = item->GetParent();
    if (!item->IsNoAutoHide()) {
      nsCOMPtr<nsIWidget> widget = item->Frame()->GetWidget();
      NS_ASSERTION(widget, "open popup has no widget");
      if (widget) {
        aWidgetChain->AppendElement(widget.get());
        // In the case when a menulist inside a panel is open, clicking in the
        // panel should still roll up the menu, so if a different type is found,
        // stop scanning.
        if (!sameTypeCount) {
          count++;
          if (!parent ||
              item->Frame()->GetPopupType() !=
                  parent->Frame()->GetPopupType() ||
              item->IsContextMenu() != parent->IsContextMenu()) {
            sameTypeCount = count;
          }
        }
      }
    }
    item = parent;
  }

  return sameTypeCount;
}

nsIWidget* nsXULPopupManager::GetRollupWidget() {
  nsMenuChainItem* item = GetTopVisibleMenu();
  return item ? item->Frame()->GetWidget() : nullptr;
}

void nsXULPopupManager::AdjustPopupsOnWindowChange(
    nsPIDOMWindowOuter* aWindow) {
  // When the parent window is moved, adjust any child popups. Dismissable
  // menus and panels are expected to roll up when a window is moved, so there
  // is no need to check these popups, only the noautohide popups.

  // The items are added to a list so that they can be adjusted bottom to top.
  nsTArray<nsMenuPopupFrame*> list;

  for (nsMenuChainItem* item = mPopups.get(); item; item = item->GetParent()) {
    // only move popups that are within the same window and where auto
    // positioning has not been disabled
    if (!item->IsNoAutoHide()) {
      continue;
    }
    nsMenuPopupFrame* frame = item->Frame();
    nsIContent* popup = frame->GetContent();
    if (!popup) {
      continue;
    }
    Document* document = popup->GetUncomposedDoc();
    if (!document) {
      continue;
    }
    nsPIDOMWindowOuter* window = document->GetWindow();
    if (!window) {
      continue;
    }
    window = window->GetPrivateRoot();
    if (window == aWindow) {
      list.AppendElement(frame);
    }
  }

  for (int32_t l = list.Length() - 1; l >= 0; l--) {
    list[l]->SetPopupPosition(true);
  }
}

void nsXULPopupManager::AdjustPopupsOnWindowChange(PresShell* aPresShell) {
  if (aPresShell->GetDocument()) {
    AdjustPopupsOnWindowChange(aPresShell->GetDocument()->GetWindow());
  }
}

static nsMenuPopupFrame* GetPopupToMoveOrResize(nsIFrame* aFrame) {
  nsMenuPopupFrame* menuPopupFrame = do_QueryFrame(aFrame);
  if (!menuPopupFrame) {
    return nullptr;
  }

  // no point moving or resizing hidden popups
  if (!menuPopupFrame->IsVisible()) {
    return nullptr;
  }

  nsIWidget* widget = menuPopupFrame->GetWidget();
  if (widget && !widget->IsVisible()) {
    return nullptr;
  }

  return menuPopupFrame;
}

void nsXULPopupManager::PopupMoved(nsIFrame* aFrame,
                                   const LayoutDeviceIntPoint& aPoint,
                                   bool aByMoveToRect) {
  nsMenuPopupFrame* menuPopupFrame = GetPopupToMoveOrResize(aFrame);
  if (!menuPopupFrame) {
    return;
  }

  nsView* view = menuPopupFrame->GetView();
  if (!view) {
    return;
  }

  menuPopupFrame->WidgetPositionOrSizeDidChange();

  // Don't do anything if the popup is already at the specified location. This
  // prevents recursive calls when a popup is positioned.
  LayoutDeviceIntRect curDevBounds = view->RecalcWidgetBounds();
  nsIWidget* widget = menuPopupFrame->GetWidget();
  if (curDevBounds.TopLeft() == aPoint &&
      (!widget ||
       widget->GetClientOffset() == menuPopupFrame->GetLastClientOffset())) {
    return;
  }

  // Update the popup's position using SetPopupPosition if the popup is
  // anchored and at the parent level as these maintain their position
  // relative to the parent window (except if positioned by move to rect, in
  // which case we better make sure that layout matches that). Otherwise, just
  // update the popup to the specified screen coordinates.
  if (menuPopupFrame->IsAnchored() &&
      menuPopupFrame->GetPopupLevel() == widget::PopupLevel::Parent &&
      !aByMoveToRect) {
    menuPopupFrame->SetPopupPosition(true);
  } else {
    CSSPoint cssPos =
        aPoint / menuPopupFrame->PresContext()->CSSToDevPixelScale();
    menuPopupFrame->MoveTo(cssPos, false, aByMoveToRect);
  }
}

void nsXULPopupManager::PopupResized(nsIFrame* aFrame,
                                     const LayoutDeviceIntSize& aSize) {
  nsMenuPopupFrame* menuPopupFrame = GetPopupToMoveOrResize(aFrame);
  if (!menuPopupFrame) {
    return;
  }

  menuPopupFrame->WidgetPositionOrSizeDidChange();

  nsView* view = menuPopupFrame->GetView();
  if (!view) {
    return;
  }

  const LayoutDeviceIntRect curDevBounds = view->RecalcWidgetBounds();
  // If the size is what we think it is, we have nothing to do.
  if (curDevBounds.Size() == aSize) {
    return;
  }

  Element* popup = menuPopupFrame->GetContent()->AsElement();

  // Only set the width and height if the popup already has these attributes.
  if (!popup->HasAttr(nsGkAtoms::width) || !popup->HasAttr(nsGkAtoms::height)) {
    return;
  }

  // The size is different. Convert the actual size to css pixels and store it
  // as 'width' and 'height' attributes on the popup.
  nsPresContext* presContext = menuPopupFrame->PresContext();

  CSSIntSize newCSS(presContext->DevPixelsToIntCSSPixels(aSize.width),
                    presContext->DevPixelsToIntCSSPixels(aSize.height));

  nsAutoString width, height;
  width.AppendInt(newCSS.width);
  height.AppendInt(newCSS.height);
  // FIXME(emilio): aNotify should be consistent (probably true in the two calls
  // below?).
  popup->SetAttr(kNameSpaceID_None, nsGkAtoms::width, width, false);
  popup->SetAttr(kNameSpaceID_None, nsGkAtoms::height, height, true);
}

nsMenuPopupFrame* nsXULPopupManager::GetPopupFrameForContent(
    nsIContent* aContent, bool aShouldFlush) {
  if (aShouldFlush) {
    Document* document = aContent->GetUncomposedDoc();
    if (document) {
      if (RefPtr<PresShell> presShell = document->GetPresShell()) {
        presShell->FlushPendingNotifications(FlushType::Layout);
      }
    }
  }

  return do_QueryFrame(aContent->GetPrimaryFrame());
}

nsMenuChainItem* nsXULPopupManager::GetRollupItem(RollupKind aKind) {
  for (nsMenuChainItem* item = mPopups.get(); item; item = item->GetParent()) {
    if (item->Frame()->PopupState() == ePopupInvisible) {
      continue;
    }
    MOZ_ASSERT_IF(item->Frame()->GetPopupType() == PopupType::Tooltip,
                  item->IsNoAutoHide());
    const bool valid = aKind == RollupKind::Tooltip
                           ? item->Frame()->GetPopupType() == PopupType::Tooltip
                           : !item->IsNoAutoHide();
    if (valid) {
      return item;
    }
  }
  return nullptr;
}

void nsXULPopupManager::SetActiveMenuBar(XULMenuBarElement* aMenuBar,
                                         bool aActivate) {
  if (aActivate) {
    mActiveMenuBar = aMenuBar;
  } else if (mActiveMenuBar == aMenuBar) {
    mActiveMenuBar = nullptr;
  }
  UpdateKeyboardListeners();
}

static CloseMenuMode GetCloseMenuMode(nsIContent* aMenu) {
  if (!aMenu->IsElement()) {
    return CloseMenuMode_Auto;
  }

  static Element::AttrValuesArray strings[] = {nsGkAtoms::none,
                                               nsGkAtoms::single, nullptr};
  switch (aMenu->AsElement()->FindAttrValueIn(
      kNameSpaceID_None, nsGkAtoms::closemenu, strings, eCaseMatters)) {
    case 0:
      return CloseMenuMode_None;
    case 1:
      return CloseMenuMode_Single;
    default:
      return CloseMenuMode_Auto;
  }
}

auto nsXULPopupManager::MayShowMenu(nsIContent* aMenu) -> MayShowMenuResult {
  if (mNativeMenu && aMenu->IsElement() &&
      mNativeMenu->Element()->Contains(aMenu)) {
    return {true};
  }

  auto* menu = XULButtonElement::FromNode(aMenu);
  if (!menu) {
    return {};
  }

  nsMenuPopupFrame* popupFrame = menu->GetMenuPopup(FlushType::None);
  if (!popupFrame || !MayShowPopup(popupFrame)) {
    return {};
  }
  return {false, menu, popupFrame};
}

void nsXULPopupManager::ShowMenu(nsIContent* aMenu, bool aSelectFirstItem) {
  auto mayShowResult = MayShowMenu(aMenu);
  if (NS_WARN_IF(!mayShowResult)) {
    return;
  }

  if (mayShowResult.mIsNative) {
    mNativeMenu->OpenSubmenu(aMenu->AsElement());
    return;
  }

  nsMenuPopupFrame* popupFrame = mayShowResult.mMenuPopupFrame;

  // inherit whether or not we're a context menu from the parent
  const bool onMenuBar = mayShowResult.mMenuButton->IsOnMenuBar();
  const bool onmenu = mayShowResult.mMenuButton->IsOnMenu();
  const bool parentIsContextMenu = mayShowResult.mMenuButton->IsOnContextMenu();

  nsAutoString position;

#ifdef XP_MACOSX
  if (aMenu->IsXULElement(nsGkAtoms::menulist)) {
    position.AssignLiteral("selection");
  } else
#endif

      if (onMenuBar || !onmenu) {
    position.AssignLiteral("after_start");
  } else {
    position.AssignLiteral("end_before");
  }

  // there is no trigger event for menus
  popupFrame->InitializePopup(aMenu, nullptr, position, 0, 0,
                              MenuPopupAnchorType::Node, true);
  PendingPopup pendingPopup(&popupFrame->PopupElement(), nullptr);
  BeginShowingPopup(pendingPopup, parentIsContextMenu, aSelectFirstItem);
}

static bool ShouldUseNativeContextMenus() {
#ifdef HAS_NATIVE_MENU_SUPPORT
  return mozilla::widget::NativeMenuSupport::ShouldUseNativeContextMenus();
#else
  return false;
#endif
}

void nsXULPopupManager::ShowPopup(Element* aPopup, nsIContent* aAnchorContent,
                                  const nsAString& aPosition, int32_t aXPos,
                                  int32_t aYPos, bool aIsContextMenu,
                                  bool aAttributesOverride,
                                  bool aSelectFirstItem, Event* aTriggerEvent) {
#ifdef XP_MACOSX
  // On Mac, use a native menu if possible since the non-native menu looks out
  // of place. Native menus for anchored popups are not currently implemented,
  // so fall back to the non-native path below if `aAnchorContent` is given. We
  // also fall back if the position string is not empty so we don't break tests
  // that either themselves call or test app features that call
  // `openPopup(null, "position")`.
  if (!aAnchorContent && aPosition.IsEmpty() && ShouldUseNativeContextMenus() &&
      aPopup->IsAnyOfXULElements(nsGkAtoms::menu, nsGkAtoms::menupopup) &&
      ShowPopupAsNativeMenu(aPopup, aXPos, aYPos, aIsContextMenu,
                            aTriggerEvent)) {
    return;
  }
#endif

  nsMenuPopupFrame* popupFrame = GetPopupFrameForContent(aPopup, true);
  if (!popupFrame || !MayShowPopup(popupFrame)) {
    return;
  }

  PendingPopup pendingPopup(aPopup, aTriggerEvent);
  nsCOMPtr<nsIContent> triggerContent = pendingPopup.GetTriggerContent();

  popupFrame->InitializePopup(aAnchorContent, triggerContent, aPosition, aXPos,
                              aYPos, MenuPopupAnchorType::Node,
                              aAttributesOverride);

  BeginShowingPopup(pendingPopup, aIsContextMenu, aSelectFirstItem);
}

void nsXULPopupManager::ShowPopupAtScreen(Element* aPopup, int32_t aXPos,
                                          int32_t aYPos, bool aIsContextMenu,
                                          Event* aTriggerEvent) {
  if (aIsContextMenu && ShouldUseNativeContextMenus() &&
      ShowPopupAsNativeMenu(aPopup, aXPos, aYPos, aIsContextMenu,
                            aTriggerEvent)) {
    return;
  }

  nsMenuPopupFrame* popupFrame = GetPopupFrameForContent(aPopup, true);
  if (!popupFrame || !MayShowPopup(popupFrame)) {
    return;
  }

  PendingPopup pendingPopup(aPopup, aTriggerEvent);
  nsCOMPtr<nsIContent> triggerContent = pendingPopup.GetTriggerContent();

  popupFrame->InitializePopupAtScreen(triggerContent, aXPos, aYPos,
                                      aIsContextMenu);
  BeginShowingPopup(pendingPopup, aIsContextMenu, false);
}

bool nsXULPopupManager::ShowPopupAsNativeMenu(Element* aPopup, int32_t aXPos,
                                              int32_t aYPos,
                                              bool aIsContextMenu,
                                              Event* aTriggerEvent) {
  if (mNativeMenu) {
    NS_WARNING("Native menu still open when trying to open another");
    RefPtr<NativeMenu> menu = mNativeMenu;
    (void)menu->Close();
    menu->RemoveObserver(this);
    mNativeMenu = nullptr;
  }

  RefPtr<NativeMenu> menu;
#ifdef HAS_NATIVE_MENU_SUPPORT
  menu = mozilla::widget::NativeMenuSupport::CreateNativeContextMenu(aPopup);
#endif

  if (!menu) {
    return false;
  }

  nsMenuPopupFrame* popupFrame = GetPopupFrameForContent(aPopup, true);
  if (!popupFrame) {
    return true;
  }

  // Hide the menu from our accessibility code so that we don't dispatch custom
  // accessibility notifications which would conflict with the system ones.
  aPopup->SetAttr(kNameSpaceID_None, nsGkAtoms::aria_hidden, u"true"_ns, true);

  PendingPopup pendingPopup(aPopup, aTriggerEvent);
  nsCOMPtr<nsIContent> triggerContent = pendingPopup.GetTriggerContent();

  popupFrame->InitializePopupAsNativeContextMenu(triggerContent, aXPos, aYPos);

  RefPtr<nsPresContext> presContext = popupFrame->PresContext();
  nsEventStatus status = FirePopupShowingEvent(pendingPopup, presContext);

  // if the event was cancelled, don't open the popup, reset its state back
  // to closed and clear its trigger content.
  if (status == nsEventStatus_eConsumeNoDefault) {
    if ((popupFrame = GetPopupFrameForContent(aPopup, true))) {
      popupFrame->SetPopupState(ePopupClosed);
      popupFrame->ClearTriggerContent();
    }
    return true;
  }

  mNativeMenu = menu;
  mNativeMenu->AddObserver(this);
  nsIFrame* frame = presContext->PresShell()->GetCurrentEventFrame();
  if (!frame) {
    frame = presContext->PresShell()->GetRootFrame();
  }
  mNativeMenu->ShowAsContextMenu(frame, CSSIntPoint(aXPos, aYPos),
                                 aIsContextMenu);

  // While the native menu is open, it consumes mouseup events.
  // Clear any :active state, mouse capture state and drag tracking now.
  EventStateManager* activeESM = static_cast<EventStateManager*>(
      EventStateManager::GetActiveEventStateManager());
  if (activeESM) {
    EventStateManager::ClearGlobalActiveContent(activeESM);
    activeESM->StopTrackingDragGesture(true);
  }
  PointerLockManager::Unlock("ShowPopupAsNativeMenu");
  PresShell::ReleaseCapturingContent();

  return true;
}

void nsXULPopupManager::OnNativeMenuOpened() {
  if (!mNativeMenu) {
    return;
  }

  RefPtr<nsXULPopupManager> kungFuDeathGrip(this);

  nsCOMPtr<nsIContent> popup = mNativeMenu->Element();
  nsMenuPopupFrame* popupFrame = GetPopupFrameForContent(popup, true);
  if (popupFrame) {
    popupFrame->SetPopupState(ePopupShown);
  }
}

void nsXULPopupManager::OnNativeMenuClosed() {
  if (!mNativeMenu) {
    return;
  }

  RefPtr<nsXULPopupManager> kungFuDeathGrip(this);

  bool shouldHideChain =
      mNativeMenuActivatedItemCloseMenuMode == Some(CloseMenuMode_Auto);

  nsCOMPtr<nsIContent> popup = mNativeMenu->Element();
  nsMenuPopupFrame* popupFrame = GetPopupFrameForContent(popup, true);
  if (popupFrame) {
    popupFrame->ClearTriggerContentIncludingDocument();
    popupFrame->SetPopupState(ePopupClosed);
  }
  mNativeMenu->RemoveObserver(this);
  mNativeMenu = nullptr;
  mNativeMenuActivatedItemCloseMenuMode = Nothing();
  mNativeMenuSubmenuStates.Clear();

  // Stop hiding the menu from accessibility code, in case it gets opened as a
  // non-native menu in the future.
  popup->AsElement()->UnsetAttr(kNameSpaceID_None, nsGkAtoms::aria_hidden,
                                true);

  if (shouldHideChain && mPopups &&
      mPopups->GetPopupType() == PopupType::Menu) {
    // A menu item was activated before this menu closed, and the item requested
    // the entire popup chain to be closed, which includes any open non-native
    // menus.
    // Close the non-native menus now. This matches the HidePopup call in
    // nsXULMenuCommandEvent::Run.
    HidePopup(mPopups->Element(), {HidePopupOption::HideChain});
  }
}

void nsXULPopupManager::OnNativeSubMenuWillOpen(
    mozilla::dom::Element* aPopupElement) {
  mNativeMenuSubmenuStates.InsertOrUpdate(aPopupElement, ePopupShowing);
}

void nsXULPopupManager::OnNativeSubMenuDidOpen(
    mozilla::dom::Element* aPopupElement) {
  mNativeMenuSubmenuStates.InsertOrUpdate(aPopupElement, ePopupShown);
}

void nsXULPopupManager::OnNativeSubMenuClosed(
    mozilla::dom::Element* aPopupElement) {
  mNativeMenuSubmenuStates.Remove(aPopupElement);
}

void nsXULPopupManager::OnNativeMenuWillActivateItem(
    mozilla::dom::Element* aMenuItemElement) {
  if (!mNativeMenu) {
    return;
  }

  CloseMenuMode cmm = GetCloseMenuMode(aMenuItemElement);
  mNativeMenuActivatedItemCloseMenuMode = Some(cmm);

  if (cmm == CloseMenuMode_Auto) {
    // If any non-native menus are visible (for example because the context menu
    // was opened on a non-native menu item, e.g. in a bookmarks folder), hide
    // the non-native menus before executing the item.
    HideOpenMenusBeforeExecutingMenu(CloseMenuMode_Auto);
  }
}

void nsXULPopupManager::ShowPopupAtScreenRect(
    Element* aPopup, const nsAString& aPosition, const nsIntRect& aRect,
    bool aIsContextMenu, bool aAttributesOverride, Event* aTriggerEvent) {
  nsMenuPopupFrame* popupFrame = GetPopupFrameForContent(aPopup, true);
  if (!popupFrame || !MayShowPopup(popupFrame)) {
    return;
  }

  PendingPopup pendingPopup(aPopup, aTriggerEvent);
  nsCOMPtr<nsIContent> triggerContent = pendingPopup.GetTriggerContent();

  popupFrame->InitializePopupAtRect(triggerContent, aPosition, aRect,
                                    aAttributesOverride);

  BeginShowingPopup(pendingPopup, aIsContextMenu, false);
}

void nsXULPopupManager::ShowTooltipAtScreen(
    Element* aPopup, nsIContent* aTriggerContent,
    const LayoutDeviceIntPoint& aScreenPoint) {
  nsMenuPopupFrame* popupFrame = GetPopupFrameForContent(aPopup, true);
  if (!popupFrame || !MayShowPopup(popupFrame)) {
    return;
  }

  PendingPopup pendingPopup(aPopup, nullptr);

  nsPresContext* pc = popupFrame->PresContext();
  pendingPopup.SetMousePoint([&] {
    // Event coordinates are relative to the root widget
    if (nsPresContext* rootPresContext = pc->GetRootPresContext()) {
      if (nsCOMPtr<nsIWidget> rootWidget = rootPresContext->GetRootWidget()) {
        return aScreenPoint - rootWidget->WidgetToScreenOffset();
      }
    }
    return aScreenPoint;
  }());

  auto screenCSSPoint =
      CSSIntPoint::Round(aScreenPoint / pc->CSSToDevPixelScale());
  popupFrame->InitializePopupAtScreen(aTriggerContent, screenCSSPoint.x,
                                      screenCSSPoint.y, false);

  BeginShowingPopup(pendingPopup, falsefalse);
}

static void CheckCaretDrawingState() {
  // There is 1 caret per document, we need to find the focused
  // document and erase its caret.
  nsFocusManager* fm = nsFocusManager::GetFocusManager();
  if (fm) {
    nsCOMPtr<mozIDOMWindowProxy> window;
    fm->GetFocusedWindow(getter_AddRefs(window));
    if (!window) {
      return;
    }

    auto* piWindow = nsPIDOMWindowOuter::From(window);
    MOZ_ASSERT(piWindow);

    nsCOMPtr<Document> focusedDoc = piWindow->GetDoc();
    if (!focusedDoc) {
      return;
    }

    PresShell* presShell = focusedDoc->GetPresShell();
    if (!presShell) {
      return;
    }

    RefPtr<nsCaret> caret = presShell->GetCaret();
    if (!caret) {
      return;
    }
    caret->SchedulePaint();
  }
}

void nsXULPopupManager::ShowPopupCallback(Element* aPopup,
                                          nsMenuPopupFrame* aPopupFrame,
                                          bool aIsContextMenu,
                                          bool aSelectFirstItem) {
  PopupType popupType = aPopupFrame->GetPopupType();
  const bool isMenu = popupType == PopupType::Menu;

  // Popups normally hide when an outside click occurs. Panels may use
  // the noautohide attribute to disable this behaviour. It is expected
  // that the application will hide these popups manually. The tooltip
  // listener will handle closing the tooltip also.
  bool isNoAutoHide =
      aPopupFrame->IsNoAutoHide() || popupType == PopupType::Tooltip;

  auto item = MakeUnique<nsMenuChainItem>(aPopupFrame, isNoAutoHide,
                                          aIsContextMenu, popupType);

  // install keyboard event listeners for navigating menus. For panels, the
  // escape key may be used to close the panel. However, the ignorekeys
  // attribute may be used to disable adding these event listeners for popups
  // that want to handle their own keyboard events.
  nsAutoString ignorekeys;
  aPopup->GetAttr(nsGkAtoms::ignorekeys, ignorekeys);
  if (ignorekeys.EqualsLiteral("true")) {
    item->SetIgnoreKeys(eIgnoreKeys_True);
  } else if (ignorekeys.EqualsLiteral("shortcuts")) {
    item->SetIgnoreKeys(eIgnoreKeys_Shortcuts);
  }

  if (isMenu) {
    // if the menu is on a menubar, use the menubar's listener instead
    if (auto* menu = aPopupFrame->PopupElement().GetContainingMenu()) {
      item->SetOnMenuBar(menu->IsOnMenuBar());
    }
  }

  // use a weak frame as the popup will set an open attribute if it is a menu
  AutoWeakFrame weakFrame(aPopupFrame);
  aPopupFrame->ShowPopup(aIsContextMenu);
  NS_ENSURE_TRUE_VOID(weakFrame.IsAlive());

  item->UpdateFollowAnchor();

  AddMenuChainItem(std::move(item));
  NS_ENSURE_TRUE_VOID(weakFrame.IsAlive());

  RefPtr popup = &aPopupFrame->PopupElement();
  popup->PopupOpened(aSelectFirstItem);

  if (isMenu) {
    UpdateMenuItems(aPopup);
  }

  // Caret visibility may have been affected, ensure that
  // the caret isn't now drawn when it shouldn't be.
  CheckCaretDrawingState();

  if (popupType != PopupType::Tooltip) {
    PointerLockManager::Unlock("ShowPopupCallback");
  }
}

nsMenuChainItem* nsXULPopupManager::FindPopup(Element* aPopup) const {
  auto matcher = [&](nsMenuChainItem* aItem) -> bool {
    return aItem->Frame()->GetContent() == aPopup;
  };
  return FirstMatchingPopup(matcher);
}

void nsXULPopupManager::HidePopup(Element* aPopup, HidePopupOptions aOptions,
                                  Element* aLastPopup) {
  if (mNativeMenu && mNativeMenu->Element() == aPopup) {
    RefPtr<NativeMenu> menu = mNativeMenu;
    (void)menu->Close();
    return;
  }

  nsMenuPopupFrame* popupFrame = do_QueryFrame(aPopup->GetPrimaryFrame());
  if (!popupFrame) {
    return;
  }

  nsMenuChainItem* foundPopup = FindPopup(aPopup);

  RefPtr<Element> popupToHide, nextPopup, lastPopup;

  if (foundPopup) {
    if (foundPopup->IsNoAutoHide()) {
      // If this is a noautohide panel, remove it but don't close any other
      // panels.
      popupToHide = aPopup;
      // XXX This preserves behavior but why is it the right thing to do?
      aOptions -= HidePopupOption::DeselectMenu;
    } else {
      // At this point, foundPopup will be set to the found item in the list. If
      // foundPopup is the topmost menu, the one to remove, then there are no
      // other popups to hide. If foundPopup is not the topmost menu, then there
      // may be open submenus below it. In this case, we need to make sure that
      // those submenus are closed up first. To do this, we scan up the menu
      // list to find the topmost popup with only menus between it and
      // foundPopup and close that menu first. In synchronous mode, the
      // FirePopupHidingEvent method will be called which in turn calls
      // HidePopupCallback to close up the next popup in the chain. These two
      // methods will be called in sequence recursively to close up all the
      // necessary popups. In asynchronous mode, a similar process occurs except
      // that the FirePopupHidingEvent method is called asynchronously. In
      // either case, nextPopup is set to the content node of the next popup to
      // close, and lastPopup is set to the last popup in the chain to close,
      // which will be aPopup, or null to close up all menus.

      nsMenuChainItem* topMenu = foundPopup;
      // Use IsMenu to ensure that foundPopup is a menu and scan down the child
      // list until a non-menu is found. If foundPopup isn't a menu at all,
      // don't scan and just close up this menu.
      if (foundPopup->IsMenu()) {
        nsMenuChainItem* child = foundPopup->GetChild();
        while (child && child->IsMenu()) {
          topMenu = child;
          child = child->GetChild();
        }
      }

      popupToHide = topMenu->Element();
      popupFrame = topMenu->Frame();

      const bool hideChain = aOptions.contains(HidePopupOption::HideChain);

      // Close up another popup if there is one, and we are either hiding the
      // entire chain or the item to hide isn't the topmost popup.
      nsMenuChainItem* parent = topMenu->GetParent();
      if (parent && (hideChain || topMenu != foundPopup)) {
        while (parent && parent->IsNoAutoHide()) {
          parent = parent->GetParent();
        }

        if (parent) {
          nextPopup = parent->Element();
        }
      }

      lastPopup = aLastPopup ? aLastPopup : (hideChain ? nullptr : aPopup);
    }
  } else if (popupFrame->PopupState() == ePopupPositioning) {
    // When the popup is in the popuppositioning state, it will not be in the
    // mPopups list. We need another way to find it and make sure it does not
    // continue the popup showing process.
    popupToHide = aPopup;
  }

  if (!popupToHide) {
    return;
  }

  nsPopupState state = popupFrame->PopupState();
  if (state == ePopupHiding) {
    // If the popup is already being hidden, don't fire another popuphiding
    // event. But finish hiding it sync if we need to.
    if (aOptions.contains(HidePopupOption::DisableAnimations) &&
        !aOptions.contains(HidePopupOption::Async)) {
      HidePopupCallback(popupToHide, popupFrame, nullptr, nullptr,
                        popupFrame->GetPopupType(), aOptions);
    }
    return;
  }

  // Change the popup state to hiding. Don't set the hiding state if the
  // popup is invisible, otherwise nsMenuPopupFrame::HidePopup will
  // run again. In the invisible state, we just want the events to fire.
  if (state != ePopupInvisible) {
    popupFrame->SetPopupState(ePopupHiding);
  }

  // For menus, popupToHide is always the frontmost item in the list to hide.
  if (aOptions.contains(HidePopupOption::Async)) {
    nsCOMPtr<nsIRunnable> event =
        new nsXULPopupHidingEvent(popupToHide, nextPopup, lastPopup,
                                  popupFrame->GetPopupType(), aOptions);
    aPopup->OwnerDoc()->Dispatch(event.forget());
  } else {
    RefPtr<nsPresContext> presContext = popupFrame->PresContext();
    FirePopupHidingEvent(popupToHide, nextPopup, lastPopup, presContext,
                         popupFrame->GetPopupType(), aOptions);
  }
}

void nsXULPopupManager::HideMenu(nsIContent* aMenu) {
  if (mNativeMenu && aMenu->IsElement() &&
      mNativeMenu->Element()->Contains(aMenu)) {
    mNativeMenu->CloseSubmenu(aMenu->AsElement());
    return;
  }

  auto* button = XULButtonElement::FromNode(aMenu);
  if (!button || !button->IsMenu()) {
    return;
  }
  auto* popup = button->GetMenuPopupContent();
  if (!popup) {
    return;
  }
  HidePopup(popup, {HidePopupOption::DeselectMenu});
}

// This is used to hide the popup after a transition finishes.
class TransitionEnder final : public nsIDOMEventListener {
 private:
  // Effectively const but is cycle collected
  MOZ_KNOWN_LIVE RefPtr<Element> mElement;

 protected:
  virtual ~TransitionEnder() = default;

 public:
  HidePopupOptions mOptions;

  NS_DECL_CYCLE_COLLECTING_ISUPPORTS
  NS_DECL_CYCLE_COLLECTION_CLASS(TransitionEnder)

  TransitionEnder(Element* aElement, HidePopupOptions aOptions)
      : mElement(aElement), mOptions(aOptions) {}

  MOZ_CAN_RUN_SCRIPT NS_IMETHOD HandleEvent(Event* aEvent) override {
    mElement->RemoveSystemEventListener(u"transitionend"_ns, thisfalse);
    mElement->RemoveSystemEventListener(u"transitioncancel"_ns, thisfalse);

    nsMenuPopupFrame* popupFrame = do_QueryFrame(mElement->GetPrimaryFrame());
    if (!popupFrame || popupFrame->PopupState() != ePopupHiding) {
      return NS_OK;
    }

    // Now hide the popup. There could be other properties transitioning, but
    // we'll assume they all end at the same time and just hide the popup upon
    // the first one ending.
    if (RefPtr<nsXULPopupManager> pm = nsXULPopupManager::GetInstance()) {
      pm->HidePopupCallback(mElement, popupFrame, nullptr, nullptr,
                            popupFrame->GetPopupType(), mOptions);
    }

    return NS_OK;
  }
};

NS_IMPL_CYCLE_COLLECTING_ADDREF(TransitionEnder)
NS_IMPL_CYCLE_COLLECTING_RELEASE(TransitionEnder)
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(TransitionEnder)
  NS_INTERFACE_MAP_ENTRY(nsIDOMEventListener)
  NS_INTERFACE_MAP_ENTRY(nsISupports)
NS_INTERFACE_MAP_END

NS_IMPL_CYCLE_COLLECTION(TransitionEnder, mElement);
void nsXULPopupManager::HidePopupCallback(
    Element* aPopup, nsMenuPopupFrame* aPopupFrame, Element* aNextPopup,
    Element* aLastPopup, PopupType aPopupType, HidePopupOptions aOptions) {
  if (mCloseTimer && mTimerMenu == aPopupFrame) {
    mCloseTimer->Cancel();
    mCloseTimer = nullptr;
    mTimerMenu = nullptr;
  }

  // The popup to hide is aPopup. Search the list again to find the item that
  // corresponds to the popup to hide aPopup. This is done because it's
  // possible someone added another item (attempted to open another popup)
  // or removed a popup frame during the event processing so the item isn't at
  // the front anymore.
  for (nsMenuChainItem* item = mPopups.get(); item; item = item->GetParent()) {
    if (item->Element() == aPopup) {
      RemoveMenuChainItem(item);
      SetCaptureState(aPopup);
      break;
    }
  }

  AutoWeakFrame weakFrame(aPopupFrame);
  aPopupFrame->HidePopup(aOptions.contains(HidePopupOption::DeselectMenu),
                         ePopupClosed);
  NS_ENSURE_TRUE_VOID(weakFrame.IsAlive());

  // send the popuphidden event synchronously. This event has no default
  // behaviour.
  nsEventStatus status = nsEventStatus_eIgnore;
  WidgetMouseEvent event(true, eXULPopupHidden, nullptr,
                         WidgetMouseEvent::eReal);
  RefPtr<nsPresContext> presContext = aPopupFrame->PresContext();
  EventDispatcher::Dispatch(aPopup, presContext, &event, nullptr, &status);
  NS_ENSURE_TRUE_VOID(weakFrame.IsAlive());

  // Force any popups that might be anchored on elements within this popup to
  // update.
  UpdatePopupPositions(presContext->RefreshDriver());

  // if there are more popups to close, look for the next one
  if (aNextPopup && aPopup != aLastPopup) {
    nsMenuChainItem* foundMenu = FindPopup(aNextPopup);

    // continue hiding the chain of popups until the last popup aLastPopup
    // is reached, or until a popup of a different type is reached. This
    // last check is needed so that a menulist inside a non-menu panel only
    // closes the menu and not the panel as well.
    if (foundMenu && (aLastPopup || aPopupType == foundMenu->GetPopupType())) {
      nsCOMPtr<Element> popupToHide = foundMenu->Element();
      nsMenuChainItem* parent = foundMenu->GetParent();

      nsCOMPtr<Element> nextPopup;
      if (parent && popupToHide != aLastPopup) {
        nextPopup = parent->Element();
      }

      nsMenuPopupFrame* popupFrame = foundMenu->Frame();
      nsPopupState state = popupFrame->PopupState();
      if (state == ePopupHiding) {
        return;
      }
      if (state != ePopupInvisible) {
        popupFrame->SetPopupState(ePopupHiding);
      }

      RefPtr<nsPresContext> presContext = popupFrame->PresContext();
      FirePopupHidingEvent(popupToHide, nextPopup, aLastPopup, presContext,
                           foundMenu->GetPopupType(), aOptions);
    }
  }
}

void nsXULPopupManager::HidePopupAfterDelay(nsMenuPopupFrame* aPopup,
                                            int32_t aDelay) {
  // Don't close up immediately.
  // Kick off a close timer.
  KillMenuTimer();

  // Kick off the timer.
  nsIEventTarget* target = GetMainThreadSerialEventTarget();
  NS_NewTimerWithFuncCallback(
      getter_AddRefs(mCloseTimer),
      [](nsITimer* aTimer, void* aClosure) {
        if (nsXULPopupManager* pm = nsXULPopupManager::GetInstance()) {
          pm->KillMenuTimer();
        }
      },
      nullptr, aDelay, nsITimer::TYPE_ONE_SHOT, "KillMenuTimer", target);
  // the popup will call PopupDestroyed if it is destroyed, which checks if it
  // is set to mTimerMenu, so it should be safe to keep a reference to it
  mTimerMenu = aPopup;
}

void nsXULPopupManager::HidePopupsInList(
    const nsTArray<nsMenuPopupFrame*>& aFrames) {
  // Create a weak frame list. This is done in a separate array with the
  // right capacity predetermined to avoid multiple allocations.
  nsTArray<WeakFrame> weakPopups(aFrames.Length());
  uint32_t f;
  for (f = 0; f < aFrames.Length(); f++) {
    WeakFrame* wframe = weakPopups.AppendElement();
    if (wframe) {
      *wframe = aFrames[f];
    }
  }

  for (f = 0; f < weakPopups.Length(); f++) {
    // check to ensure that the frame is still alive before hiding it.
    if (weakPopups[f].IsAlive()) {
      auto* frame = static_cast<nsMenuPopupFrame*>(weakPopups[f].GetFrame());
      frame->HidePopup(true, ePopupInvisible);
    }
  }

  SetCaptureState(nullptr);
}

bool nsXULPopupManager::IsChildOfDocShell(Document* aDoc,
                                          nsIDocShellTreeItem* aExpected) {
  nsCOMPtr<nsIDocShellTreeItem> docShellItem(aDoc->GetDocShell());
  while (docShellItem) {
    if (docShellItem == aExpected) {
      return true;
    }

    nsCOMPtr<nsIDocShellTreeItem> parent;
    docShellItem->GetInProcessParent(getter_AddRefs(parent));
    docShellItem = parent;
  }

  return false;
}

void nsXULPopupManager::HidePopupsInDocShell(
    nsIDocShellTreeItem* aDocShellToHide) {
  nsTArray<nsMenuPopupFrame*> popupsToHide;

  // Iterate to get the set of popup frames to hide
  nsMenuChainItem* item = mPopups.get();
  while (item) {
    // Get the parent before calling detach so that we can keep iterating.
    nsMenuChainItem* parent = item->GetParent();
    if (item->Frame()->PopupState() != ePopupInvisible &&
        IsChildOfDocShell(item->Element()->OwnerDoc(), aDocShellToHide)) {
      nsMenuPopupFrame* frame = item->Frame();
      RemoveMenuChainItem(item);
      popupsToHide.AppendElement(frame);
    }
    item = parent;
  }

  HidePopupsInList(popupsToHide);
}

void nsXULPopupManager::UpdatePopupPositions(nsRefreshDriver* aRefreshDriver) {
  for (nsMenuChainItem* item = mPopups.get(); item; item = item->GetParent()) {
    if (item->Frame()->PresContext()->RefreshDriver() == aRefreshDriver) {
      item->CheckForAnchorChange();
    }
  }
}

void nsXULPopupManager::UpdateFollowAnchor(nsMenuPopupFrame* aPopup) {
  for (nsMenuChainItem* item = mPopups.get(); item; item = item->GetParent()) {
    if (item->Frame() == aPopup) {
      item->UpdateFollowAnchor();
      break;
    }
  }
}

void nsXULPopupManager::HideOpenMenusBeforeExecutingMenu(CloseMenuMode aMode) {
  if (aMode == CloseMenuMode_None) {
    return;
  }

  // When a menuitem is selected to be executed, first hide all the open
  // popups, but don't remove them yet. This is needed when a menu command
  // opens a modal dialog. The views associated with the popups needed to be
  // hidden and the accesibility events fired before the command executes, but
  // the popuphiding/popuphidden events are fired afterwards.
  nsTArray<nsMenuPopupFrame*> popupsToHide;
  nsMenuChainItem* item = GetTopVisibleMenu();
  while (item) {
    // if it isn't a <menupopup>, don't close it automatically
    if (!item->IsMenu()) {
      break;
    }

    nsMenuChainItem* next = item->GetParent();
    popupsToHide.AppendElement(item->Frame());
    if (aMode == CloseMenuMode_Single) {
      // only close one level of menu
      break;
    }
    item = next;
  }

  // Now hide the popups. If the closemenu mode is auto, deselect the menu,
  // otherwise only one popup is closing, so keep the parent menu selected.
  HidePopupsInList(popupsToHide);
}

void nsXULPopupManager::ExecuteMenu(nsIContent* aMenu,
                                    nsXULMenuCommandEvent* aEvent) {
  CloseMenuMode cmm = GetCloseMenuMode(aMenu);
  HideOpenMenusBeforeExecutingMenu(cmm);
  aEvent->SetCloseMenuMode(cmm);
  nsCOMPtr<nsIRunnable> event = aEvent;
  aMenu->OwnerDoc()->Dispatch(event.forget());
}

bool nsXULPopupManager::ActivateNativeMenuItem(nsIContent* aItem,
                                               mozilla::Modifiers aModifiers,
                                               int16_t aButton,
                                               mozilla::ErrorResult& aRv) {
  if (mNativeMenu && aItem->IsElement() &&
      mNativeMenu->Element()->Contains(aItem)) {
    mNativeMenu->ActivateItem(aItem->AsElement(), aModifiers, aButton, aRv);
    return true;
  }
  return false;
}

nsEventStatus nsXULPopupManager::FirePopupShowingEvent(
    const PendingPopup& aPendingPopup, nsPresContext* aPresContext) {
  // Cache the pending popup so that the trigger node and other properties can
  // be retrieved during the popupshowing event. It will be cleared below after
  // the event has fired.
  AutoRestore<const PendingPopup*> restorePendingPopup(mPendingPopup);
  mPendingPopup = &aPendingPopup;

  nsEventStatus status = nsEventStatus_eIgnore;
  WidgetMouseEvent event(true, eXULPopupShowing, nullptr,
                         WidgetMouseEvent::eReal);

  // coordinates are relative to the root widget
  nsPresContext* rootPresContext = aPresContext->GetRootPresContext();
  if (rootPresContext) {
    event.mWidget =
        rootPresContext->PresShell()->GetViewManager()->GetRootWidget();
  } else {
    event.mWidget = nullptr;
  }

  event.mInputSource = aPendingPopup.MouseInputSource();
  event.mRefPoint = aPendingPopup.mMousePoint;
  event.mModifiers = aPendingPopup.mModifiers;
  RefPtr<nsIContent> popup = aPendingPopup.mPopup;
  EventDispatcher::Dispatch(popup, aPresContext, &event, nullptr, &status);

  return status;
}

void nsXULPopupManager::BeginShowingPopup(const PendingPopup& aPendingPopup,
                                          bool aIsContextMenu,
                                          bool aSelectFirstItem) {
  RefPtr<Element> popup = aPendingPopup.mPopup;

  nsMenuPopupFrame* popupFrame = do_QueryFrame(popup->GetPrimaryFrame());
  if (NS_WARN_IF(!popupFrame)) {
    return;
  }

  RefPtr<nsPresContext> presContext = popupFrame->PresContext();
  RefPtr<PresShell> presShell = presContext->PresShell();
  presShell->FrameNeedsReflow(popupFrame, IntrinsicDirty::FrameAndAncestors,
                              NS_FRAME_IS_DIRTY);

  PopupType popupType = popupFrame->GetPopupType();

  nsEventStatus status = FirePopupShowingEvent(aPendingPopup, presContext);

  // if a panel, blur whatever has focus so that the panel can take the focus.
  // This is done after the popupshowing event in case that event is cancelled.
  // Using noautofocus="true" will disable this behaviour, which is needed for
  // the autocomplete widget as it manages focus itself.
  if (popupType == PopupType::Panel &&
      !popup->AttrValueIs(kNameSpaceID_None, nsGkAtoms::noautofocus,
                          nsGkAtoms::_true, eCaseMatters)) {
    if (RefPtr<nsFocusManager> fm = nsFocusManager::GetFocusManager()) {
      Document* doc = popup->GetUncomposedDoc();

      // Only remove the focus if the currently focused item is ouside the
      // popup. It isn't a big deal if the current focus is in a child popup
      // inside the popup as that shouldn't be visible. This check ensures that
      // a node inside the popup that is focused during a popupshowing event
      // remains focused.
      RefPtr<Element> currentFocus = fm->GetFocusedElement();
      if (doc && currentFocus &&
          !nsContentUtils::ContentIsCrossDocDescendantOf(currentFocus, popup)) {
        nsCOMPtr<nsPIDOMWindowOuter> outerWindow = doc->GetWindow();
        fm->ClearFocus(outerWindow);
      }
    }
  }

  popup->OwnerDoc()->FlushPendingNotifications(FlushType::Frames);

  // get the frame again in case it went away
  popupFrame = do_QueryFrame(popup->GetPrimaryFrame());
  if (!popupFrame) {
    return;
  }
  // if the event was cancelled or the popup was closed in the mean time, don't
  // open the popup, reset its state back to closed and clear its trigger
  // content.
  if (popupFrame->PopupState() == ePopupClosed ||
      status == nsEventStatus_eConsumeNoDefault) {
    popupFrame->SetPopupState(ePopupClosed);
    popupFrame->ClearTriggerContent();
    return;
  }
  // Now check if we need to fire the popuppositioned event. If not, call
  // ShowPopupCallback directly.
  // The popuppositioned event only fires on arrow panels for now.
  if (popup->AttrValueIs(kNameSpaceID_None, nsGkAtoms::type, nsGkAtoms::arrow,
                         eCaseMatters)) {
    popupFrame->ShowWithPositionedEvent();
    presShell->FrameNeedsReflow(popupFrame, IntrinsicDirty::FrameAndAncestors,
                                NS_FRAME_HAS_DIRTY_CHILDREN);
  } else {
    ShowPopupCallback(popup, popupFrame, aIsContextMenu, aSelectFirstItem);
  }
}

void nsXULPopupManager::FirePopupHidingEvent(Element* aPopup,
                                             Element* aNextPopup,
                                             Element* aLastPopup,
                                             nsPresContext* aPresContext,
                                             PopupType aPopupType,
                                             HidePopupOptions aOptions) {
  nsCOMPtr<nsIContent> popup = aPopup;
  RefPtr<PresShell> presShell = aPresContext->PresShell();
  Unused << presShell;  // This presShell may be keeping things alive
                        // on non GTK platforms

  nsEventStatus status = nsEventStatus_eIgnore;
  WidgetMouseEvent event(true, eXULPopupHiding, nullptr,
                         WidgetMouseEvent::eReal);
  EventDispatcher::Dispatch(aPopup, aPresContext, &event, nullptr, &status);

  // when a panel is closed, blur whatever has focus inside the popup
  if (aPopupType == PopupType::Panel &&
      (!aPopup->AttrValueIs(kNameSpaceID_None, nsGkAtoms::noautofocus,
                            nsGkAtoms::_true, eCaseMatters))) {
    if (RefPtr<nsFocusManager> fm = nsFocusManager::GetFocusManager()) {
      Document* doc = aPopup->GetUncomposedDoc();

      // Remove the focus from the focused node only if it is inside the popup.
      RefPtr<Element> currentFocus = fm->GetFocusedElement();
      if (doc && currentFocus &&
          nsContentUtils::ContentIsCrossDocDescendantOf(currentFocus, aPopup)) {
        nsCOMPtr<nsPIDOMWindowOuter> outerWindow = doc->GetWindow();
        fm->ClearFocus(outerWindow);
      }
    }
  }

  aPopup->OwnerDoc()->FlushPendingNotifications(FlushType::Frames);

  // get frame again in case it went away
  nsMenuPopupFrame* popupFrame = do_QueryFrame(aPopup->GetPrimaryFrame());
  if (!popupFrame) {
    return;
  }

  // If the event was cancelled, don't hide the popup, and reset its
  // state back to open. Only popups in chrome shells can prevent a popup
  // from hiding.
  if (status == nsEventStatus_eConsumeNoDefault &&
      !popupFrame->IsInContentShell()) {
    // XXXndeakin
    // If an attempt was made to hide this popup before the popupshown event
    // fired, then ePopupShown is set here even though it should be
    // ePopupVisible. This probably isn't worth the hassle of handling.
    popupFrame->SetPopupState(ePopupShown);
    return;
  }

  const bool shouldAnimate = [&] {
    if (!LookAndFeel::GetInt(LookAndFeel::IntID::PanelAnimations)) {
      // Animations are not supported by the platform, avoid transitioning.
      return false;
    }
    if (aOptions.contains(HidePopupOption::DisableAnimations)) {
      // Animations are not allowed by our caller.
      return false;
    }
    if (aNextPopup) {
      // If there is a next popup, indicating that mutliple popups are rolling
      // up, don't wait and hide the popup right away since the effect would
      // likely be undesirable.
      return false;
    }
    nsAutoString animate;
    if (!aPopup->GetAttr(nsGkAtoms::animate, animate)) {
      return false;
    }
    // If animate="false" then don't transition at all.
    if (animate.EqualsLiteral("false")) {
      return false;
    }
    // If animate="cancel", only show the transition if cancelling the popup
    // or rolling up.
    if (animate.EqualsLiteral("cancel") &&
        !aOptions.contains(HidePopupOption::IsRollup)) {
      return false;
    }
    return true;
  }();
--> --------------------

--> maximum size reached

--> --------------------

Messung V0.5
C=93 H=100 G=96

¤ Dauer der Verarbeitung: 0.21 Sekunden  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.