/* -*- 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/. */
staticbool IsMouseTransparent(const ComputedStyle& aStyle) { // If pointer-events: none; is set on the popup, then the widget should // ignore mouse events, passing them through to the content behind. return aStyle.PointerEvents() == StylePointerEvents::None;
}
bool nsMenuPopupFrame::ShouldCreateWidgetUpfront() const { if (mPopupType != PopupType::Menu) { // Any panel with a type attribute, such as the autocomplete popup, is // always generated right away. return mContent->AsElement()->HasAttr(nsGkAtoms::type);
}
// Generate the widget up-front if the parent menu is a <menulist> unless its // sizetopopup is set to "none". return ShouldExpandToInflowParentOrAnchor();
}
if (PresContext()->IsChrome()) {
mInContentShell = false;
}
// Support incontentshell=false attribute to allow popups to be displayed // outside of the content shell. Chrome only. if (el.NodePrincipal()->IsSystemPrincipal()) { if (el.GetXULBoolAttr(nsGkAtoms::incontentshell)) {
mInContentShell = true;
} elseif (el.AttrValueIs(kNameSpaceID_None, nsGkAtoms::incontentshell,
nsGkAtoms::_false, eCaseMatters)) {
mInContentShell = false;
}
}
// To improve performance, create the widget for the popup if needed. Popups // such as menus will create their widgets later when the popup opens. // // FIXME(emilio): Doing this up-front for all menupopups causes a bunch of // assertions, while it's supposed to be just an optimization. if (!ourView->HasWidget() && ShouldCreateWidgetUpfront()) {
CreateWidgetForView(ourView);
}
bool nsMenuPopupFrame::IsNoAutoHide() const { // Panels with noautohide="true" don't hide when the mouse is clicked // outside of them, or when another application is made active. Non-autohide // panels cannot be used in content windows. return !mInContentShell && mPopupType == PopupType::Panel &&
mContent->AsElement()->AttrValueIs(kNameSpaceID_None,
nsGkAtoms::noautohide,
nsGkAtoms::_true, eIgnoreCase);
}
widget::PopupLevel nsMenuPopupFrame::GetPopupLevel(bool aIsNoAutoHide) const { // The popup level is determined as follows, in this order: // 1. non-panels (menus and tooltips) are always topmost // 2. any specified level attribute // 3. if a titlebar attribute is set, use the 'floating' level // 4. if this is a noautohide panel, use the 'parent' level // 5. use the platform-specific default level
// If this is not a panel, this is always a top-most popup. if (mPopupType != PopupType::Panel) { return PopupLevel::Top;
}
// If the level attribute has been set, use that. static Element::AttrValuesArray strings[] = {nsGkAtoms::top,
nsGkAtoms::parent, nullptr}; switch (mContent->AsElement()->FindAttrValueIn(
kNameSpaceID_None, nsGkAtoms::level, strings, eCaseMatters)) { case 0: return PopupLevel::Top; case 1: return PopupLevel::Parent; default: break;
}
// If this panel is a noautohide panel, the default is the parent level. if (aIsNoAutoHide) { return PopupLevel::Parent;
}
// Otherwise, the result depends on the platform. return StaticPrefs::ui_panel_default_level_parent() ? PopupLevel::Parent
: PopupLevel::Top;
}
void nsMenuPopupFrame::PrepareWidget(bool aForceRecreate) {
nsView* ourView = GetView(); if (auto* widget = GetWidget()) {
nsCOMPtr<nsIWidget> parent = ComputeParentWidget(); if (aForceRecreate || widget->GetParent() != parent ||
widget->NeedsRecreateToReshow()) { // Widget's WebRender resources needs to be cleared before creating new // widget.
widget->ClearCachedWebrenderResources();
ourView->DestroyWidget();
}
} if (!ourView->HasWidget()) {
CreateWidgetForView(ourView);
} else {
PropagateStyleToWidget();
}
}
already_AddRefed<nsIWidget> nsMenuPopupFrame::ComputeParentWidget() const { auto popupLevel = GetPopupLevel(IsNoAutoHide()); // Panels which have a parent level need a parent widget. This allows them to // always appear in front of the parent window but behind other windows that // should be in front of it.
nsCOMPtr<nsIWidget> parentWidget; if (popupLevel != PopupLevel::Top) {
nsCOMPtr<nsIDocShellTreeItem> dsti = PresContext()->GetDocShell(); if (!dsti) { return nullptr;
}
nsCOMPtr<nsIDocShellTreeOwner> treeOwner;
dsti->GetTreeOwner(getter_AddRefs(treeOwner)); if (!treeOwner) { return nullptr;
}
if (!mInContentShell) { // A drag popup may be used for non-static translucent drag feedback if (mPopupType == PopupType::Panel &&
mContent->AsElement()->AttrValueIs(kNameSpaceID_None, nsGkAtoms::type,
nsGkAtoms::drag, eIgnoreCase)) {
widgetData.mIsDragPopup = true;
}
}
// Work around https://gitlab.gnome.org/GNOME/gtk/-/issues/4166 if (aState == ePopupShown && IS_WAYLAND_DISPLAY()) { if (nsIWidget* widget = GetWidget()) {
widget->SetInputRegion(ComputeInputRegion(*Style(), *PresContext()));
}
}
}
// TODO: Convert this to MOZ_CAN_RUN_SCRIPT (bug 1415230, bug 1535398)
MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHODIMP nsXULPopupShownEvent::Run() {
nsMenuPopupFrame* popup = do_QueryFrame(mPopup->GetPrimaryFrame()); // Set the state to visible if the popup is still open. if (popup && popup->IsOpen()) {
popup->SetPopupState(ePopupShown);
}
NS_IMETHODIMP nsXULPopupShownEvent::HandleEvent(Event* aEvent) {
nsMenuPopupFrame* popup = do_QueryFrame(mPopup->GetPrimaryFrame()); // Ignore events not targeted at the popup itself (ie targeted at // descendants): if (mPopup != aEvent->GetTarget()) { return NS_OK;
} if (popup) { // ResetPopupShownDispatcher will delete the reference to this, so keep // another one until Run is finished.
RefPtr<nsXULPopupShownEvent> event = this; // Only call Run if it the dispatcher was assigned. This avoids calling the // Run method if the transitionend event fires multiple times. if (popup->ClearPopupShownDispatcher()) { return Run();
}
}
if (newUI.mMozWindowTransform != oldUI.mMozWindowTransform) {
flags += WidgetStyle::Transform;
}
if (newUI.mWindowShadow != oldUI.mWindowShadow) {
flags += WidgetStyle::Shadow;
}
constauto& pc = *PresContext(); auto oldRegion = ComputeInputRegion(*aOldStyle, pc); auto newRegion = ComputeInputRegion(*Style(), pc); if (oldRegion.mFullyTransparent != newRegion.mFullyTransparent ||
oldRegion.mMargin != newRegion.mMargin) {
flags += WidgetStyle::InputRegion;
}
PropagateStyleToWidget(flags);
}
nscoord nsMenuPopupFrame::IntrinsicISize(const IntrinsicSizeInput& aInput,
IntrinsicISizeType aType) {
nscoord iSize = nsBlockFrame::IntrinsicISize(aInput, aType); if (!ShouldExpandToInflowParentOrAnchor()) { return iSize;
} // Make sure to accommodate for our scrollbar if needed. Do it only for // menulists to match previous behavior. // // NOTE(emilio): This is somewhat hacky. The "right" fix (which would be // using scrollbar-gutter: stable on the scroller) isn't great, because even // though we want a stable gutter, we want to draw on top of the gutter when // there's no scrollbar, otherwise it looks rather weird. // // Automatically accommodating for the scrollbar otherwise would be bug // 764076, but that has its own set of problems. if (ScrollContainerFrame* sf = GetScrollContainerFrame()) {
iSize += sf->GetDesiredScrollbarSizes().LeftRight();
}
nscoord menuListOrAnchorWidth = 0; if (nsIFrame* menuList = GetInFlowParent()) {
menuListOrAnchorWidth = menuList->GetRect().width;
} if (mAnchorType == MenuPopupAnchorType::Rect) {
menuListOrAnchorWidth = std::max(menuListOrAnchorWidth, mScreenRect.width);
} // Input margin doesn't have contents, so account for it for popup sizing // purposes.
menuListOrAnchorWidth +=
2 * StyleUIReset()->mMozWindowInputRegionMargin.ToAppUnits();
constbool isOpen = IsOpen(); if (!isOpen) { // If the popup is not open, only do layout while showing or if we're a // menulist. // // This is needed because the SelectParent code wants to limit the height of // the popup before opening it. // // TODO(emilio): We should consider adding a way to do that more reliably // instead, but this preserves existing behavior. constbool needsLayout = mPopupState == ePopupShowing ||
mPopupState == ePopupPositioning || IsMenuList(); if (!needsLayout) {
RemoveStateBits(NS_FRAME_FIRST_REFLOW); return;
}
}
// Do a first reflow, with all our content, in order to find our preferred // size. Then, we do a second reflow with the updated dimensions. constbool needsPrefSize = mPrefSize == nsSize(-1, -1) || IsSubtreeDirty(); if (needsPrefSize) { // Get the preferred, minimum and maximum size. If the menu is sized to the // popup, then the popup's width is the menu's width.
ReflowOutput preferredSize(aReflowInput);
nsBlockFrame::Reflow(aPresContext, preferredSize, aReflowInput, aStatus);
mPrefSize = preferredSize.PhysicalSize();
}
// Get our desired position and final size, now that we have a preferred size. auto constraints = GetRects(mPrefSize); constauto finalSize = constraints.mUsedRect.Size();
// We need to do an extra reflow if we haven't reflowed, our size doesn't // match with our final intended size, or our bsize is unconstrained (in which // case we need to specify the final size so that percentages work). constbool needDefiniteReflow =
aReflowInput.ComputedBSize() == NS_UNCONSTRAINEDSIZE || !needsPrefSize ||
finalSize != mPrefSize;
if (needDefiniteReflow) {
ReflowInput constrainedReflowInput(aReflowInput); constauto& bp = aReflowInput.ComputedPhysicalBorderPadding(); // TODO: writing-mode handling not terribly correct, but it doesn't matter. const nsSize finalContentSize(finalSize.width - bp.LeftRight(),
finalSize.height - bp.TopBottom());
constrainedReflowInput.SetComputedISize(finalContentSize.width);
constrainedReflowInput.SetComputedBSize(finalContentSize.height);
constrainedReflowInput.SetIResize(finalSize.width != mPrefSize.width);
constrainedReflowInput.SetBResize([&] { if (finalSize.height != mPrefSize.height) { returntrue;
} if (needsPrefSize &&
aReflowInput.ComputedBSize() == NS_UNCONSTRAINEDSIZE &&
aReflowInput.ComputedMaxBSize() == finalContentSize.height) { // If we have measured, and maybe clamped our children via max-height, // they might need to get percentages in the block axis re-resolved. returntrue;
} returnfalse;
}());
// Perform our move now. That will position the view and so on.
PerformMove(constraints);
// finally, if the popup just opened, send a popupshown event bool openChanged = mIsOpenChanged; if (openChanged) {
mIsOpenChanged = false;
// Make sure the current selection in a menulist is visible.
EnsureActiveMenuListItemIsVisible();
// If the animate attribute is set to open, check for a transition and wait // for it to finish before firing the popupshown event. if (LookAndFeel::GetInt(LookAndFeel::IntID::PanelAnimations) &&
mContent->AsElement()->AttrValueIs(kNameSpaceID_None,
nsGkAtoms::animate, nsGkAtoms::open,
eCaseMatters) &&
AnimationUtils::HasCurrentTransitions(mContent->AsElement())) {
mPopupShownDispatcher = new nsXULPopupShownEvent(mContent, aPresContext);
mContent->AddSystemEventListener(u"transitionend"_ns,
mPopupShownDispatcher, false, false); return;
}
// If there are no transitions, fire the popupshown event right away.
nsCOMPtr<nsIRunnable> event = new nsXULPopupShownEvent(GetContent(), aPresContext);
mContent->OwnerDoc()->Dispatch(event.forget());
}
}
mPopupState = ePopupShowing;
mAnchorContent = aAnchorContent;
mAnchorType = aAnchorType; const nscoord auPerCssPx = AppUnitsPerCSSPixel(); const nsPoint pos = CSSPixel::ToAppUnits(CSSIntPoint(aXPos, aYPos)); // When converted back to CSSIntRect it is (-1, -1, 0, 0) - as expected in // nsXULPopupManager::Rollup
mScreenRect = nsRect(-auPerCssPx, -auPerCssPx, 0, 0);
mExtraMargin = pos; // If we have no anchor node, anchor to the given position instead. if (mAnchorType == MenuPopupAnchorType::Node && !aAnchorContent) {
mAnchorType = MenuPopupAnchorType::Point;
mScreenRect = nsRect(
pos + PresShell()->GetRootFrame()->GetScreenRectInAppUnits().TopLeft(),
nsSize());
mExtraMargin = {};
}
mTriggerContent = aTriggerContent;
mIsNativeMenu = false;
mIsTopLevelContextMenu = false;
mVFlip = false;
mHFlip = false;
mConstrainedByLayout = false;
mAlignmentOffset = 0;
mPositionedOffset = 0;
mPositionedByMoveToRect = false;
// if aAttributesOverride is true, then the popupanchor, popupalign and // position attributes on the <menupopup> override those values passed in. // If false, those attributes are only used if the values passed in are empty if (aAnchorContent || aAnchorType == MenuPopupAnchorType::Rect) {
nsAutoString anchor, align, position;
mContent->AsElement()->GetAttr(nsGkAtoms::popupanchor, anchor);
mContent->AsElement()->GetAttr(nsGkAtoms::popupalign, align);
mContent->AsElement()->GetAttr(nsGkAtoms::position, position);
if (aAttributesOverride) { // if the attributes are set, clear the offset position. Otherwise, // the offset is used to adjust the position from the anchor point if (anchor.IsEmpty() && align.IsEmpty() && position.IsEmpty()) {
position.Assign(aPosition);
}
} elseif (!aPosition.IsEmpty()) {
position.Assign(aPosition);
}
if (aAttributesOverride) { // Use |left| and |top| dimension attributes to position the popup if // present, as they may have been persisted.
nsAutoString left, top;
mContent->AsElement()->GetAttr(nsGkAtoms::left, left);
mContent->AsElement()->GetAttr(nsGkAtoms::top, top);
nsresult err; if (!left.IsEmpty()) {
int32_t x = left.ToInteger(&err); if (NS_SUCCEEDED(err)) {
mScreenRect.x = CSSPixel::ToAppUnits(x);
}
} if (!top.IsEmpty()) {
int32_t y = top.ToInteger(&err); if (NS_SUCCEEDED(err)) {
mScreenRect.y = CSSPixel::ToAppUnits(y);
}
}
}
}
// Clear mouse capture when a popup is opened. if (mPopupType == PopupType::Menu) { if (auto* activeESM = EventStateManager::GetActiveEventStateManager()) {
EventStateManager::ClearGlobalActiveContent(activeESM);
}
PresShell::ReleaseCapturingContent();
}
if (RefPtr menu = PopupElement().GetContainingMenu()) {
menu->PopupOpened();
}
// We skip laying out children if we're closed, so make sure that we do a // full dirty reflow when opening to pick up any potential change.
PresShell()->FrameNeedsReflow( this, IntrinsicDirty::FrameAncestorsAndDescendants, NS_FRAME_IS_DIRTY);
if (mPopupType == PopupType::Menu) {
nsCOMPtr<nsISound> sound(do_GetService("@mozilla.org/sound;1")); if (sound) {
sound->PlayEventSound(nsISound::EVENT_MENU_POPUP);
}
}
}
}
void nsMenuPopupFrame::ClearTriggerContentIncludingDocument() { // clear the trigger content if the popup is being closed. But don't clear // it if the popup is just being made invisible as a popuphiding or command if (mTriggerContent) { // if the popup had a trigger node set, clear the global window popup node // as well
Document* doc = mContent->GetUncomposedDoc(); if (doc) { if (nsPIDOMWindowOuter* win = doc->GetWindow()) {
nsCOMPtr<nsPIWindowRoot> root = win->GetTopWindowRoot(); if (root) {
root->SetPopupNode(nullptr);
}
}
}
}
mTriggerContent = nullptr;
}
void nsMenuPopupFrame::HidePopup(bool aDeselectMenu, nsPopupState aNewState, bool aFromFrameDestruction) {
NS_ASSERTION(aNewState == ePopupClosed || aNewState == ePopupInvisible, "popup being set to unexpected state");
ClearPopupShownDispatcher();
// don't hide the popup when it isn't open if (mPopupState == ePopupClosed || mPopupState == ePopupShowing ||
mPopupState == ePopupPositioning) { return;
}
if (aNewState == ePopupClosed) { // clear the trigger content if the popup is being closed. But don't clear // it if the popup is just being made invisible as a popuphiding or command // event may want to retrieve it.
ClearTriggerContentIncludingDocument();
mAnchorContent = nullptr;
}
// when invisible and about to be closed, HidePopup has already been called, // so just set the new state to closed and return if (mPopupState == ePopupInvisible) { if (aNewState == ePopupClosed) {
mPopupState = ePopupClosed;
} return;
}
if (auto* widget = GetWidget()) { // Ideally we should call ClearCachedWebrenderResources but there are // intermittent failures (see bug 1748788), so we currently call // ClearWebrenderAnimationResources instead.
widget->ClearWebrenderAnimationResources();
}
RefPtr popup = &PopupElement(); // XXX, bug 137033, In Windows, if mouse is outside the window when the // menupopup closes, no mouse_enter/mouse_exit event will be fired to clear // current hover state, we should clear it manually. This code may not the // best solution, but we can leave it here until we find the better approach. if (!aFromFrameDestruction &&
popup->State().HasState(dom::ElementState::HOVER)) {
EventStateManager* esm = PresContext()->EventStateManager();
esm->SetContentState(nullptr, dom::ElementState::HOVER);
}
popup->PopupClosed(aDeselectMenu);
}
nsPoint nsMenuPopupFrame::AdjustPositionForAnchorAlign(
nsRect& anchorRect, const nsSize& aPrefSize, FlipStyle& aHFlip,
FlipStyle& aVFlip) const { // flip the anchor and alignment for right-to-left
int8_t popupAnchor(mPopupAnchor);
int8_t popupAlign(mPopupAlignment); if (IsDirectionRTL()) { // no need to flip the centered anchor types vertically if (popupAnchor <= POPUPALIGNMENT_LEFTCENTER) {
popupAnchor = -popupAnchor;
}
popupAlign = -popupAlign;
}
nsRect originalAnchorRect(anchorRect);
// first, determine at which corner of the anchor the popup should appear
nsPoint pnt; switch (popupAnchor) { case POPUPALIGNMENT_LEFTCENTER:
pnt = nsPoint(anchorRect.x, anchorRect.y + anchorRect.height / 2);
anchorRect.y = pnt.y;
anchorRect.height = 0; break; case POPUPALIGNMENT_RIGHTCENTER:
pnt = nsPoint(anchorRect.XMost(), anchorRect.y + anchorRect.height / 2);
anchorRect.y = pnt.y;
anchorRect.height = 0; break; case POPUPALIGNMENT_TOPCENTER:
pnt = nsPoint(anchorRect.x + anchorRect.width / 2, anchorRect.y);
anchorRect.x = pnt.x;
anchorRect.width = 0; break; case POPUPALIGNMENT_BOTTOMCENTER:
pnt = nsPoint(anchorRect.x + anchorRect.width / 2, anchorRect.YMost());
anchorRect.x = pnt.x;
anchorRect.width = 0; break; case POPUPALIGNMENT_TOPRIGHT:
pnt = anchorRect.TopRight(); break; case POPUPALIGNMENT_BOTTOMLEFT:
pnt = anchorRect.BottomLeft(); break; case POPUPALIGNMENT_BOTTOMRIGHT:
pnt = anchorRect.BottomRight(); break; case POPUPALIGNMENT_TOPLEFT: default:
pnt = anchorRect.TopLeft(); break;
}
// If the alignment is on the right edge of the popup, move the popup left // by the width. Similarly, if the alignment is on the bottom edge of the // popup, move the popup up by the height. In addition, account for the // margins of the popup on the edge on which it is aligned.
nsMargin margin = GetMargin(); switch (popupAlign) { case POPUPALIGNMENT_LEFTCENTER:
pnt.MoveBy(margin.left, -aPrefSize.height / 2); break; case POPUPALIGNMENT_RIGHTCENTER:
pnt.MoveBy(-aPrefSize.width - margin.right, -aPrefSize.height / 2); break; case POPUPALIGNMENT_TOPCENTER:
pnt.MoveBy(-aPrefSize.width / 2, margin.top); break; case POPUPALIGNMENT_BOTTOMCENTER:
pnt.MoveBy(-aPrefSize.width / 2, -aPrefSize.height - margin.bottom); break; case POPUPALIGNMENT_TOPRIGHT:
pnt.MoveBy(-aPrefSize.width - margin.right, margin.top); break; case POPUPALIGNMENT_BOTTOMLEFT:
pnt.MoveBy(margin.left, -aPrefSize.height - margin.bottom); break; case POPUPALIGNMENT_BOTTOMRIGHT:
pnt.MoveBy(-aPrefSize.width - margin.right,
-aPrefSize.height - margin.bottom); break; case POPUPALIGNMENT_TOPLEFT: default:
pnt.MoveBy(margin.left, margin.top); break;
}
// If we aligning to the selected item in the popup, adjust the vertical // position by the height of the menulist label and the selected item's // position. if (mPosition == POPUPPOSITION_SELECTION) {
MOZ_ASSERT(popupAnchor == POPUPALIGNMENT_BOTTOMLEFT ||
popupAnchor == POPUPALIGNMENT_BOTTOMRIGHT);
MOZ_ASSERT(popupAlign == POPUPALIGNMENT_TOPLEFT ||
popupAlign == POPUPALIGNMENT_TOPRIGHT);
// Only adjust the popup if it just opened, otherwise the popup will move // around if its gets resized or the selection changed. Cache the value in // mPositionedOffset and use that instead for any future calculations. if (mIsOpenChanged) { if (nsIFrame* selectedItemFrame = GetSelectedItemForAlignment()) { const nscoord itemHeight = selectedItemFrame->GetRect().height; const nscoord itemOffset =
selectedItemFrame->GetOffsetToIgnoringScrolling(this).y; // We want to line-up the anchor rect with the selected item, but if the // selected item is outside of our bounds, we don't want to shift the // popup up in a way that our box would no longer intersect with the // anchor.
nscoord maxOffset = aPrefSize.height - itemHeight; if (const ScrollContainerFrame* sf = GetScrollContainerFrame()) { // HACK: We ideally would want to use the offset from the bottom // bottom of our scroll-frame to the bottom of our frame (so as to // ensure that the bottom of the scrollport is inside the anchor // rect). // // But at this point of the code, the scroll frame may not be laid out // with a definite size (might be overflowing us). // // So, we assume the offset from the bottom is symmetric to the offset // from the top. This holds for all the popups where this matters // (menulists on macOS, effectively), and seems better than somehow // moving the popup after the fact as we used to do.
maxOffset -= sf->GetOffsetTo(this).y;
}
mPositionedOffset =
originalAnchorRect.height + std::min(itemOffset, maxOffset);
}
}
pnt.y -= mPositionedOffset;
}
// Flipping horizontally is allowed as long as the popup is above or below // the anchor. This will happen if both the anchor and alignment are top or // both are bottom, but different values. Similarly, flipping vertically is // allowed if the popup is to the left or right of the anchor. In this case, // the values of the constants are such that both must be positive or both // must be negative. A special case, used for overlap, allows flipping // vertically as well. // If we are flipping in both directions, we want to set a flip style both // horizontally and vertically. However, we want to flip on the inside edge // of the anchor. Consider the example of a typical dropdown menu. // Vertically, we flip the popup on the outside edges of the anchor menu, // however horizontally, we want to to use the inside edges so the popup // still appears underneath the anchor menu instead of floating off the // side of the menu. switch (popupAnchor) { case POPUPALIGNMENT_LEFTCENTER: case POPUPALIGNMENT_RIGHTCENTER:
aHFlip = FlipStyle_Outside;
aVFlip = FlipStyle_Inside; break; case POPUPALIGNMENT_TOPCENTER: case POPUPALIGNMENT_BOTTOMCENTER:
aHFlip = FlipStyle_Inside;
aVFlip = FlipStyle_Outside; break; default: {
FlipStyle anchorEdge =
mFlip == FlipType_Both ? FlipStyle_Inside : FlipStyle_None;
aHFlip = (popupAnchor == -popupAlign) ? FlipStyle_Outside : anchorEdge; if (((popupAnchor > 0) == (popupAlign > 0)) ||
(popupAnchor == POPUPALIGNMENT_TOPLEFT &&
popupAlign == POPUPALIGNMENT_TOPLEFT)) {
aVFlip = FlipStyle_Outside;
} else {
aVFlip = anchorEdge;
} break;
}
}
return pnt;
}
nsIFrame* nsMenuPopupFrame::GetSelectedItemForAlignment() const { // This method adjusts a menulist's popup such that the selected item is under // the cursor, aligned with the menulist label.
nsCOMPtr<nsIDOMXULSelectControlElement> select; if (mAnchorContent) {
select = mAnchorContent->AsElement()->AsXULSelectControl();
}
if (!select) { // If there isn't an anchor, then try just getting the parent of the popup.
select = mContent->GetParent()->AsElement()->AsXULSelectControl(); if (!select) { return nullptr;
}
}
nscoord nsMenuPopupFrame::SlideOrResize(nscoord& aScreenPoint, nscoord aSize,
nscoord aScreenBegin,
nscoord aScreenEnd,
nscoord* aOffset) const { // The popup may be positioned such that either the left/top or bottom/right // is outside the screen - but never both.
nscoord newPos =
std::max(aScreenBegin, std::min(aScreenEnd - aSize, aScreenPoint));
*aOffset = newPos - aScreenPoint;
aScreenPoint = newPos; return std::min(aSize, aScreenEnd - aScreenPoint);
}
nscoord nsMenuPopupFrame::FlipOrResize(nscoord& aScreenPoint, nscoord aSize,
nscoord aScreenBegin, nscoord aScreenEnd,
nscoord aAnchorBegin, nscoord aAnchorEnd,
nscoord aMarginBegin, nscoord aMarginEnd,
FlipStyle aFlip, bool aEndAligned, bool* aFlipSide) const { // The flip side argument will be set to true if there wasn't room and we // flipped to the opposite side.
*aFlipSide = false;
// all of the coordinates used here are in app units relative to the screen
nscoord popupSize = aSize; if (aScreenPoint < aScreenBegin) { // at its current position, the popup would extend past the left or top // edge of the screen, so it will have to be moved or resized. if (aFlip) { // for inside flips, we flip on the opposite side of the anchor
nscoord startpos = aFlip == FlipStyle_Outside ? aAnchorBegin : aAnchorEnd;
nscoord endpos = aFlip == FlipStyle_Outside ? aAnchorEnd : aAnchorBegin;
// check whether there is more room to the left and right (or top and // bottom) of the anchor and put the popup on the side with more room. if (startpos - aScreenBegin >= aScreenEnd - endpos) {
aScreenPoint = aScreenBegin;
popupSize = startpos - aScreenPoint - aMarginEnd;
*aFlipSide = !aEndAligned;
} else { // If the newly calculated position is different than the existing // position, flip such that the popup is to the right or bottom of the // anchor point instead . However, when flipping use the same margin // size.
nscoord newScreenPoint = endpos + aMarginEnd; if (newScreenPoint != aScreenPoint) {
*aFlipSide = aEndAligned;
aScreenPoint = newScreenPoint; // check if the new position is still off the right or bottom edge of // the screen. If so, resize the popup. if (aScreenPoint + aSize > aScreenEnd) {
popupSize = aScreenEnd - aScreenPoint;
}
}
}
} else {
aScreenPoint = aScreenBegin;
}
} elseif (aScreenPoint + aSize > aScreenEnd) { // at its current position, the popup would extend past the right or // bottom edge of the screen, so it will have to be moved or resized. if (aFlip) { // for inside flips, we flip on the opposite side of the anchor
nscoord startpos = aFlip == FlipStyle_Outside ? aAnchorBegin : aAnchorEnd;
nscoord endpos = aFlip == FlipStyle_Outside ? aAnchorEnd : aAnchorBegin;
// check whether there is more room to the left and right (or top and // bottom) of the anchor and put the popup on the side with more room. if (aScreenEnd - endpos >= startpos - aScreenBegin) {
*aFlipSide = aEndAligned; if (mIsContextMenu) {
aScreenPoint = aScreenEnd - aSize;
} else {
aScreenPoint = endpos + aMarginBegin;
popupSize = aScreenEnd - aScreenPoint;
}
} else { // if the newly calculated position is different than the existing // position, we flip such that the popup is to the left or top of the // anchor point instead.
nscoord newScreenPoint = startpos - aSize - aMarginBegin; if (newScreenPoint != aScreenPoint) {
*aFlipSide = !aEndAligned;
aScreenPoint = newScreenPoint;
// check if the new position is still off the left or top edge of the // screen. If so, resize the popup. if (aScreenPoint < aScreenBegin) {
aScreenPoint = aScreenBegin; if (!mIsContextMenu) {
popupSize = startpos - aScreenPoint - aMarginBegin;
}
}
}
}
} else {
aScreenPoint = aScreenEnd - aSize;
}
}
// Make sure that the point is within the screen boundaries and that the // size isn't off the edge of the screen. This can happen when a large // positive or negative margin is used. if (aScreenPoint < aScreenBegin) {
aScreenPoint = aScreenBegin;
} if (aScreenPoint > aScreenEnd) {
aScreenPoint = aScreenEnd - aSize;
}
// If popupSize ended up being negative, or the original size was actually // smaller than the calculated popup size, just use the original size instead. if (popupSize <= 0 || aSize < popupSize) {
popupSize = aSize;
}
nsRect nsMenuPopupFrame::ComputeAnchorRect(nsPresContext* aRootPresContext,
nsIFrame* aAnchorFrame) const { // Get the root frame for a reference
nsIFrame* rootFrame = aRootPresContext->PresShell()->GetRootFrame();
// The dimensions of the anchor
nsRect anchorRect = aAnchorFrame->GetRectRelativeToSelf();
// Relative to the root
anchorRect = nsLayoutUtils::TransformFrameRectToAncestor(
aAnchorFrame, anchorRect, rootFrame); // Relative to the screen
anchorRect.MoveBy(rootFrame->GetScreenRectInAppUnits().TopLeft());
// In its own app units return anchorRect.ScaleToOtherAppUnitsRoundOut(
aRootPresContext->AppUnitsPerDevPixel(),
PresContext()->AppUnitsPerDevPixel());
}
static nsIFrame* MaybeDelegatedAnchorFrame(nsIFrame* aFrame) { if (!aFrame) { return nullptr;
} if (auto* element = Element::FromNodeOrNull(aFrame->GetContent())) { if (element->HasAttr(nsGkAtoms::delegatesanchor)) { for (nsIFrame* f : aFrame->PrincipalChildList()) { if (!f->IsPlaceholderFrame()) { return f;
}
}
}
} return aFrame;
}
auto nsMenuPopupFrame::GetRects(const nsSize& aPrefSize) const -> Rects { if (NS_WARN_IF(aPrefSize == nsSize(-1, -1))) { // Return early if the popup hasn't been laid out yet. On Windows, this can // happen when using a drag popup before it opens. return {};
}
nsPresContext* pc = PresContext();
nsIFrame* rootFrame = pc->PresShell()->GetRootFrame();
NS_ASSERTION(rootFrame->GetView() && GetView() &&
rootFrame->GetView() == GetView()->GetParent(), "rootFrame's view is not our view's parent???");
// Indicators of whether the popup should be flipped or resized.
FlipStyle hFlip = FlipStyle_None, vFlip = FlipStyle_None;
const nsMargin margin = GetMargin();
// the screen rectangle of the root frame, in dev pixels. const nsRect rootScreenRect = rootFrame->GetScreenRectInAppUnits();
// Set the popup's size to the preferred size. Below, this size will be // adjusted to fit on the screen or within the content area. If the anchor is // sized to the popup, use the anchor's width instead of the preferred width.
result.mUsedRect = nsRect(nsPoint(), aPrefSize);
constbool anchored = IsAnchored(); if (anchored) { // In order to deal with transforms, we need the root prescontext:
nsPresContext* rootPc = pc->GetRootPresContext(); if (NS_WARN_IF(!rootPc)) { // If we can't reach a root pres context, don't bother continuing. return result;
}
result.mAnchorRect = result.mUntransformedAnchorRect = [&] { // If anchored to a rectangle, use that rectangle. Otherwise, determine // the rectangle from the anchor. if (mAnchorType == MenuPopupAnchorType::Rect) { return mScreenRect;
} // if the frame is not specified, use the anchor node passed to OpenPopup. // If that wasn't specified either, use the root frame. Note that // mAnchorContent might be a different document so its presshell must be // used.
nsIFrame* anchorFrame = GetAnchorFrame(); if (!anchorFrame) { return rootScreenRect;
} return ComputeAnchorRect(rootPc, anchorFrame);
}();
// if we are anchored, there are certain things we don't want to do when // repositioning the popup to fit on the screen, such as end up positioned // over the anchor, for instance a popup appearing over the menu label. // When doing this reposition, we want to move the popup to the side with // the most room. The combination of anchor and alignment dictate if we // readjust above/below or to the left/right. if (mAnchorContent || mAnchorType == MenuPopupAnchorType::Rect) { // move the popup according to the anchor and alignment. This will also // tell us which axis the popup is flush against in case we have to move // it around later. The AdjustPositionForAnchorAlign method accounts for // the popup's margin.
result.mUsedRect.MoveTo(AdjustPositionForAnchorAlign(
result.mAnchorRect, aPrefSize, hFlip, vFlip));
} else { // With no anchor, the popup is positioned relative to the root frame.
result.mUsedRect.MoveTo(result.mAnchorRect.TopLeft() +
nsPoint(margin.left, margin.top));
}
} else { // Not anchored, use mScreenRect
result.mUsedRect.MoveTo(mScreenRect.TopLeft());
result.mAnchorRect = result.mUntransformedAnchorRect =
nsRect(mScreenRect.TopLeft(), nsSize());
// Right-align RTL context menus, and apply margin and offsets as per the // platform conventions. if (mIsContextMenu && IsDirectionRTL()) {
result.mUsedRect.x -= aPrefSize.Width();
result.mUsedRect.MoveBy(-margin.right, margin.top);
} else {
result.mUsedRect.MoveBy(margin.left, margin.top);
} #ifdef XP_MACOSX // OSX tooltips follow standard flip rule but other popups flip horizontally // not vertically if (mPopupType == PopupType::Tooltip) {
vFlip = FlipStyle_Outside;
} else {
hFlip = FlipStyle_Outside;
} #else // Other OS screen positioned popups can be flipped vertically but never // horizontally
vFlip = FlipStyle_Outside; #endif// #ifdef XP_MACOSX
}
const int32_t a2d = pc->AppUnitsPerDevPixel();
nsView* view = GetView();
NS_ASSERTION(view, "popup with no view");
nsIWidget* widget = view->GetWidget();
// If a panel has flip="none", don't constrain or flip it. // Also, always do this for content shells, so that the popup doesn't extend // outside the containing frame. if (mInContentShell || mFlip != FlipType_None) { const Maybe<nsRect> constraintRect =
GetConstraintRect(result.mAnchorRect, rootScreenRect, popupLevel);
if (constraintRect) { // Ensure that anchorRect is on the constraint rect.
result.mAnchorRect = result.mAnchorRect.Intersect(*constraintRect); // Shrink the popup down if it is larger than the constraint size if (result.mUsedRect.width > constraintRect->width) {
result.mUsedRect.width = constraintRect->width;
} if (result.mUsedRect.height > constraintRect->height) {
result.mUsedRect.height = constraintRect->height;
}
result.mConstrainedByLayout = true;
}
if (IS_WAYLAND_DISPLAY() && widget) { // Shrink the popup down if it's larger than popup size received from // Wayland compositor. We don't know screen size on Wayland so this is the // only info we have there. const nsSize waylandSize = LayoutDeviceIntRect::ToAppUnits(
widget->GetMoveToRectPopupSize(), a2d); if (waylandSize.width > 0 && result.mUsedRect.width > waylandSize.width) {
LOG_WAYLAND("Wayland constraint width [%p]: %d to %d", widget,
result.mUsedRect.width, waylandSize.width);
result.mUsedRect.width = waylandSize.width;
} if (waylandSize.height > 0 &&
result.mUsedRect.height > waylandSize.height) {
LOG_WAYLAND("Wayland constraint height [%p]: %d to %d", widget,
result.mUsedRect.height, waylandSize.height);
result.mUsedRect.height = waylandSize.height;
} if (RefPtr<widget::Screen> s = widget->GetWidgetScreen()) { const nsSize screenSize =
LayoutDeviceIntSize::ToAppUnits(s->GetAvailRect().Size(), a2d); if (result.mUsedRect.height > screenSize.height) {
LOG_WAYLAND("Wayland constraint height to screen [%p]: %d to %d",
widget, result.mUsedRect.height, screenSize.height);
result.mUsedRect.height = screenSize.height;
} if (result.mUsedRect.width > screenSize.width) {
LOG_WAYLAND("Wayland constraint widthto screen [%p]: %d to %d",
widget, result.mUsedRect.width, screenSize.width);
result.mUsedRect.width = screenSize.width;
}
}
}
// At this point the anchor (anchorRect) is within the available screen // area (constraintRect) and the popup is known to be no larger than the // screen. if (constraintRect) { // We might want to "slide" an arrow if the panel is of the correct type - // but we can only slide on one axis - the other axis must be "flipped or // resized" as normal. bool slideHorizontal = false, slideVertical = false; if (mFlip == FlipType_Slide) {
int8_t position = GetAlignmentPosition();
slideHorizontal = position >= POPUPPOSITION_BEFORESTART &&
position <= POPUPPOSITION_AFTEREND;
slideVertical = position >= POPUPPOSITION_STARTBEFORE &&
position <= POPUPPOSITION_ENDAFTER;
}
// Next, check if there is enough space to show the popup at full size // when positioned at screenPoint. If not, flip the popups to the opposite // side of their anchor point, or resize them as necessary. if (slideHorizontal) {
result.mUsedRect.width = SlideOrResize(
result.mUsedRect.x, result.mUsedRect.width, constraintRect->x,
constraintRect->XMost(), &result.mAlignmentOffset);
} else { constbool endAligned =
IsDirectionRTL()
? mPopupAlignment == POPUPALIGNMENT_TOPLEFT ||
mPopupAlignment == POPUPALIGNMENT_BOTTOMLEFT ||
mPopupAlignment == POPUPALIGNMENT_LEFTCENTER
: mPopupAlignment == POPUPALIGNMENT_TOPRIGHT ||
mPopupAlignment == POPUPALIGNMENT_BOTTOMRIGHT ||
mPopupAlignment == POPUPALIGNMENT_RIGHTCENTER;
result.mUsedRect.width = FlipOrResize(
result.mUsedRect.x, result.mUsedRect.width, constraintRect->x,
constraintRect->XMost(), result.mAnchorRect.x,
result.mAnchorRect.XMost(), margin.left, margin.right, hFlip,
endAligned, &result.mHFlip);
} if (slideVertical) {
result.mUsedRect.height = SlideOrResize(
result.mUsedRect.y, result.mUsedRect.height, constraintRect->y,
constraintRect->YMost(), &result.mAlignmentOffset);
} else { bool endAligned = mPopupAlignment == POPUPALIGNMENT_BOTTOMLEFT ||
mPopupAlignment == POPUPALIGNMENT_BOTTOMRIGHT ||
mPopupAlignment == POPUPALIGNMENT_BOTTOMCENTER;
result.mUsedRect.height = FlipOrResize(
result.mUsedRect.y, result.mUsedRect.height, constraintRect->y,
constraintRect->YMost(), result.mAnchorRect.y,
result.mAnchorRect.YMost(), margin.top, margin.bottom, vFlip,
endAligned, &result.mVFlip);
}
#ifdef DEBUG
NS_ASSERTION(constraintRect->Contains(result.mUsedRect), "Popup is offscreen"); if (!constraintRect->Contains(result.mUsedRect)) {
NS_WARNING(nsPrintfCString("Popup is offscreen (%s vs. %s)",
ToString(constraintRect).c_str(),
ToString(result.mUsedRect).c_str())
.get());
} #endif
}
} // snap the popup's position in screen coordinates to device pixels, see // bug 622507, bug 961431
result.mUsedRect.x = pc->RoundAppUnitsToNearestDevPixels(result.mUsedRect.x);
result.mUsedRect.y = pc->RoundAppUnitsToNearestDevPixels(result.mUsedRect.y);
// determine the x and y position of the view by subtracting the desired // screen position from the screen position of the root frame.
result.mViewPoint = result.mUsedRect.TopLeft() - rootScreenRect.TopLeft();
// Offset the position by the width and height of the borders and titlebar. // Even though GetClientOffset should return (0, 0) when there is no titlebar // or borders, we skip these calculations anyway for non-panels to save time // since they will never have a titlebar. if (mPopupType == PopupType::Panel && widget) {
result.mClientOffset = widget->GetClientOffset();
result.mViewPoint +=
LayoutDeviceIntPoint::ToAppUnits(result.mClientOffset, a2d);
}
auto rects = GetRects(mPrefSize); if (rects.mUsedRect.Size() != mRect.Size()) {
MOZ_ASSERT(!HasAnyStateBits(NS_FRAME_IN_REFLOW)); // We need to resize on top of moving, trigger an actual reflow.
PresShell()->FrameNeedsReflow(this, IntrinsicDirty::FrameAndAncestors,
NS_FRAME_IS_DIRTY); return;
}
PerformMove(rects);
}
// We're just moving, sync frame position and offset as needed.
ps->GetViewManager()->MoveViewTo(GetView(), aRects.mViewPoint.x,
aRects.mViewPoint.y);
// Now that we've positioned the view, sync up the frame's origin.
nsBlockFrame::SetPosition(aRects.mViewPoint -
GetParent()->GetOffsetTo(ps->GetRootFrame()));
// If the popup is in the positioned state or if it is shown and the position // or size changed, dispatch a popuppositioned event if the popup wants it. if (mPopupState == ePopupPositioning ||
(mPopupState == ePopupShown &&
!aRects.mUsedRect.IsEqualEdges(mUsedScreenRect)) ||
(mPopupState == ePopupShown &&
aRects.mAlignmentOffset != mAlignmentOffset)) {
mUsedScreenRect = aRects.mUsedRect; if (!HasAnyStateBits(NS_FRAME_FIRST_REFLOW) && !mPendingPositionedEvent) {
mPendingPositionedEvent =
nsXULPopupPositionedEvent::DispatchIfNeeded(mContent->AsElement());
}
}
if (!mPositionedByMoveToRect) {
mUntransformedAnchorRect = aRects.mUntransformedAnchorRect;
}
// If this is a noautohide popup, set the screen coordinates of the popup. // This way, the popup stays at the location where it was opened even when the // window is moved. Popups at the parent level follow the parent window as it // is moved and remained anchored, so we want to maintain the anchoring // instead. // // FIXME: This suffers from issues like bug 1823552, where constraints imposed // by the anchor are lost, but this is super-old behavior. constbool fixPositionToPoint =
IsNoAutoHide() && (GetPopupLevel() != PopupLevel::Parent ||
--> --------------------
--> maximum size reached
--> --------------------
¤ Dauer der Verarbeitung: 0.35 Sekunden
(vorverarbeitet)
¤
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 ist noch experimentell.