Bug Summary

File:root/firefox-clang/obj-x86_64-pc-linux-gnu/layout/xul/./../../../layout/xul/nsMenuPopupFrame.cpp
Warning:line 2112, column 5
Value stored to 'stringLength' is never read

Annotated Source Code

Press '?' to see keyboard shortcuts

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