Bug Summary

File:root/firefox-clang/obj-x86_64-pc-linux-gnu/layout/xul/./../../../layout/xul/nsMenuPopupFrame.cpp
Warning:line 2116, 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-10-03-230936-2302665-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
1154void nsMenuPopupFrame::FlipAnchorForRTL(int8_t& aPopupAnchor,
1155 int8_t& aPopupAlignment) {
1156 // no need to flip the centered anchor types vertically
1157 if (aPopupAnchor <= POPUPALIGNMENT_LEFTCENTER16) {
1158 aPopupAnchor = -aPopupAnchor;
1159 }
1160 if (aPopupAlignment <= POPUPALIGNMENT_LEFTCENTER16) {
1161 aPopupAlignment = -aPopupAlignment;
1162 }
1163}
1164
1165nsPoint nsMenuPopupFrame::AdjustPositionForAnchorAlign(
1166 nsRect& anchorRect, const nsSize& aPrefSize, FlipStyle& aHFlip,
1167 FlipStyle& aVFlip) const {
1168 int8_t popupAnchor(mPopupAnchor);
1169 int8_t popupAlign(mPopupAlignment);
1170 if (IsDirectionRTL()) {
1171 FlipAnchorForRTL(popupAnchor, popupAlign);
1172 }
1173
1174 nsRect originalAnchorRect(anchorRect);
1175
1176 // first, determine at which corner of the anchor the popup should appear
1177 nsPoint pnt;
1178 switch (popupAnchor) {
1179 case POPUPALIGNMENT_LEFTCENTER16:
1180 pnt = nsPoint(anchorRect.x, anchorRect.y + anchorRect.height / 2);
1181 anchorRect.y = pnt.y;
1182 anchorRect.height = 0;
1183 break;
1184 case POPUPALIGNMENT_RIGHTCENTER-16:
1185 pnt = nsPoint(anchorRect.XMost(), anchorRect.y + anchorRect.height / 2);
1186 anchorRect.y = pnt.y;
1187 anchorRect.height = 0;
1188 break;
1189 case POPUPALIGNMENT_TOPCENTER17:
1190 pnt = nsPoint(anchorRect.x + anchorRect.width / 2, anchorRect.y);
1191 anchorRect.x = pnt.x;
1192 anchorRect.width = 0;
1193 break;
1194 case POPUPALIGNMENT_BOTTOMCENTER18:
1195 pnt = nsPoint(anchorRect.x + anchorRect.width / 2, anchorRect.YMost());
1196 anchorRect.x = pnt.x;
1197 anchorRect.width = 0;
1198 break;
1199 case POPUPALIGNMENT_TOPRIGHT-1:
1200 pnt = anchorRect.TopRight();
1201 break;
1202 case POPUPALIGNMENT_BOTTOMLEFT2:
1203 pnt = anchorRect.BottomLeft();
1204 break;
1205 case POPUPALIGNMENT_BOTTOMRIGHT-2:
1206 pnt = anchorRect.BottomRight();
1207 break;
1208 case POPUPALIGNMENT_TOPLEFT1:
1209 default:
1210 pnt = anchorRect.TopLeft();
1211 break;
1212 }
1213
1214 // If the alignment is on the right edge of the popup, move the popup left
1215 // by the width. Similarly, if the alignment is on the bottom edge of the
1216 // popup, move the popup up by the height. In addition, account for the
1217 // margins of the popup on the edge on which it is aligned.
1218 nsMargin margin = GetMargin();
1219 switch (popupAlign) {
1220 case POPUPALIGNMENT_LEFTCENTER16:
1221 pnt.MoveBy(margin.left, -aPrefSize.height / 2);
1222 break;
1223 case POPUPALIGNMENT_RIGHTCENTER-16:
1224 pnt.MoveBy(-aPrefSize.width - margin.right, -aPrefSize.height / 2);
1225 break;
1226 case POPUPALIGNMENT_TOPCENTER17:
1227 pnt.MoveBy(-aPrefSize.width / 2, margin.top);
1228 break;
1229 case POPUPALIGNMENT_BOTTOMCENTER18:
1230 pnt.MoveBy(-aPrefSize.width / 2, -aPrefSize.height - margin.bottom);
1231 break;
1232 case POPUPALIGNMENT_TOPRIGHT-1:
1233 pnt.MoveBy(-aPrefSize.width - margin.right, margin.top);
1234 break;
1235 case POPUPALIGNMENT_BOTTOMLEFT2:
1236 pnt.MoveBy(margin.left, -aPrefSize.height - margin.bottom);
1237 break;
1238 case POPUPALIGNMENT_BOTTOMRIGHT-2:
1239 pnt.MoveBy(-aPrefSize.width - margin.right,
1240 -aPrefSize.height - margin.bottom);
1241 break;
1242 case POPUPALIGNMENT_TOPLEFT1:
1243 default:
1244 pnt.MoveBy(margin.left, margin.top);
1245 break;
1246 }
1247
1248 // If we aligning to the selected item in the popup, adjust the vertical
1249 // position by the height of the menulist label and the selected item's
1250 // position.
1251 if (mPosition == POPUPPOSITION_SELECTION10) {
1252 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", 1253); AnnotateMozCrashReason
("MOZ_ASSERT" "(" "popupAnchor == 2 || popupAnchor == -2" ")"
); do { MOZ_CrashSequence(__null, 1253); __attribute__((nomerge
)) ::abort(); } while (false); } } while (false)
1253 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", 1253); AnnotateMozCrashReason
("MOZ_ASSERT" "(" "popupAnchor == 2 || popupAnchor == -2" ")"
); do { MOZ_CrashSequence(__null, 1253); __attribute__((nomerge
)) ::abort(); } while (false); } } while (false)
;
1254 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"
, 1255); AnnotateMozCrashReason("MOZ_ASSERT" "(" "popupAlign == 1 || popupAlign == -1"
")"); do { MOZ_CrashSequence(__null, 1255); __attribute__((nomerge
)) ::abort(); } while (false); } } while (false)
1255 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"
, 1255); AnnotateMozCrashReason("MOZ_ASSERT" "(" "popupAlign == 1 || popupAlign == -1"
")"); do { MOZ_CrashSequence(__null, 1255); __attribute__((nomerge
)) ::abort(); } while (false); } } while (false)
;
1256
1257 // Only adjust the popup if it just opened, otherwise the popup will move
1258 // around if its gets resized or the selection changed. Cache the value in
1259 // mPositionedOffset and use that instead for any future calculations.
1260 if (mIsOpenChanged) {
1261 if (nsIFrame* selectedItemFrame = GetSelectedItemForAlignment()) {
1262 const nscoord itemHeight = selectedItemFrame->GetRect().height;
1263 const nscoord itemOffset =
1264 selectedItemFrame->GetOffsetToIgnoringScrolling(this).y;
1265 // We want to line-up the anchor rect with the selected item, but if the
1266 // selected item is outside of our bounds, we don't want to shift the
1267 // popup up in a way that our box would no longer intersect with the
1268 // anchor.
1269 nscoord maxOffset = aPrefSize.height - itemHeight;
1270 if (const ScrollContainerFrame* sf = GetScrollContainerFrame()) {
1271 // HACK: We ideally would want to use the offset from the bottom
1272 // bottom of our scroll-frame to the bottom of our frame (so as to
1273 // ensure that the bottom of the scrollport is inside the anchor
1274 // rect).
1275 //
1276 // But at this point of the code, the scroll frame may not be laid out
1277 // with a definite size (might be overflowing us).
1278 //
1279 // So, we assume the offset from the bottom is symmetric to the offset
1280 // from the top. This holds for all the popups where this matters
1281 // (menulists on macOS, effectively), and seems better than somehow
1282 // moving the popup after the fact as we used to do.
1283 maxOffset -= sf->GetOffsetTo(this).y;
1284 }
1285 mPositionedOffset =
1286 originalAnchorRect.height + std::min(itemOffset, maxOffset);
1287 }
1288 }
1289
1290 pnt.y -= mPositionedOffset;
1291 }
1292
1293 // Flipping horizontally is allowed as long as the popup is above or below
1294 // the anchor. This will happen if both the anchor and alignment are top or
1295 // both are bottom, but different values. Similarly, flipping vertically is
1296 // allowed if the popup is to the left or right of the anchor. In this case,
1297 // the values of the constants are such that both must be positive or both
1298 // must be negative. A special case, used for overlap, allows flipping
1299 // vertically as well.
1300 // If we are flipping in both directions, we want to set a flip style both
1301 // horizontally and vertically. However, we want to flip on the inside edge
1302 // of the anchor. Consider the example of a typical dropdown menu.
1303 // Vertically, we flip the popup on the outside edges of the anchor menu,
1304 // however horizontally, we want to to use the inside edges so the popup
1305 // still appears underneath the anchor menu instead of floating off the
1306 // side of the menu.
1307 switch (popupAnchor) {
1308 case POPUPALIGNMENT_LEFTCENTER16:
1309 case POPUPALIGNMENT_RIGHTCENTER-16:
1310 aHFlip = FlipStyle::Outside;
1311 aVFlip = FlipStyle::Inside;
1312 break;
1313 case POPUPALIGNMENT_TOPCENTER17:
1314 case POPUPALIGNMENT_BOTTOMCENTER18:
1315 aHFlip = FlipStyle::Inside;
1316 aVFlip = FlipStyle::Outside;
1317 break;
1318 default: {
1319 FlipStyle anchorEdge =
1320 mFlip == FlipType::Both ? FlipStyle::Inside : FlipStyle::None;
1321 aHFlip = (popupAnchor == -popupAlign) ? FlipStyle::Outside : anchorEdge;
1322 if (((popupAnchor > 0) == (popupAlign > 0)) ||
1323 (popupAnchor == POPUPALIGNMENT_TOPLEFT1 &&
1324 popupAlign == POPUPALIGNMENT_TOPLEFT1)) {
1325 aVFlip = FlipStyle::Outside;
1326 } else {
1327 aVFlip = anchorEdge;
1328 }
1329 break;
1330 }
1331 }
1332
1333 return pnt;
1334}
1335
1336nsIFrame* nsMenuPopupFrame::GetSelectedItemForAlignment() const {
1337 // This method adjusts a menulist's popup such that the selected item is under
1338 // the cursor, aligned with the menulist label.
1339 nsCOMPtr<nsIDOMXULSelectControlElement> select;
1340 if (mAnchorContent) {
1341 select = mAnchorContent->AsElement()->AsXULSelectControl();
1342 }
1343
1344 if (!select) {
1345 // If there isn't an anchor, then try just getting the parent of the popup.
1346 select = mContent->GetParent()->AsElement()->AsXULSelectControl();
1347 if (!select) {
1348 return nullptr;
1349 }
1350 }
1351
1352 nsCOMPtr<Element> selectedElement;
1353 select->GetSelectedItem(getter_AddRefs(selectedElement));
1354 return selectedElement ? selectedElement->GetPrimaryFrame() : nullptr;
1355}
1356
1357nscoord nsMenuPopupFrame::SlideOrResize(nscoord& aScreenPoint, nscoord aSize,
1358 nscoord aScreenBegin,
1359 nscoord aScreenEnd,
1360 nscoord* aOffset) const {
1361 // The popup may be positioned such that either the left/top or bottom/right
1362 // is outside the screen - but never both.
1363 nscoord newPos =
1364 std::max(aScreenBegin, std::min(aScreenEnd - aSize, aScreenPoint));
1365 *aOffset = newPos - aScreenPoint;
1366 aScreenPoint = newPos;
1367 return std::min(aSize, aScreenEnd - aScreenPoint);
1368}
1369
1370nscoord nsMenuPopupFrame::FlipOrResize(nscoord& aScreenPoint, nscoord aSize,
1371 nscoord aScreenBegin, nscoord aScreenEnd,
1372 nscoord aAnchorBegin, nscoord aAnchorEnd,
1373 nscoord aMarginBegin, nscoord aMarginEnd,
1374 FlipStyle aFlip, bool aEndAligned,
1375 bool* aFlipSide) const {
1376 // The flip side argument will be set to true if there wasn't room and we
1377 // flipped to the opposite side.
1378 *aFlipSide = false;
1379
1380 // all of the coordinates used here are in app units relative to the screen
1381 nscoord popupSize = aSize;
1382 if (aScreenPoint < aScreenBegin) {
1383 // at its current position, the popup would extend past the left or top
1384 // edge of the screen, so it will have to be moved or resized.
1385 if (aFlip != FlipStyle::None) {
1386 // for inside flips, we flip on the opposite side of the anchor
1387 nscoord startpos =
1388 aFlip == FlipStyle::Outside ? aAnchorBegin : aAnchorEnd;
1389 nscoord endpos = aFlip == FlipStyle::Outside ? aAnchorEnd : aAnchorBegin;
1390
1391 // check whether there is more room to the left and right (or top and
1392 // bottom) of the anchor and put the popup on the side with more room.
1393 if (startpos - aScreenBegin >= aScreenEnd - endpos) {
1394 aScreenPoint = aScreenBegin;
1395 popupSize = startpos - aScreenPoint - aMarginEnd;
1396 *aFlipSide = !aEndAligned;
1397 } else {
1398 // If the newly calculated position is different than the existing
1399 // position, flip such that the popup is to the right or bottom of the
1400 // anchor point instead . However, when flipping use the same margin
1401 // size.
1402 nscoord newScreenPoint = endpos + aMarginEnd;
1403 if (newScreenPoint != aScreenPoint) {
1404 *aFlipSide = aEndAligned;
1405 aScreenPoint = newScreenPoint;
1406 // check if the new position is still off the right or bottom edge of
1407 // the screen. If so, resize the popup.
1408 if (aScreenPoint + aSize > aScreenEnd) {
1409 popupSize = aScreenEnd - aScreenPoint;
1410 }
1411 }
1412 }
1413 } else {
1414 aScreenPoint = aScreenBegin;
1415 }
1416 } else if (aScreenPoint + aSize > aScreenEnd) {
1417 // at its current position, the popup would extend past the right or
1418 // bottom edge of the screen, so it will have to be moved or resized.
1419 if (aFlip != FlipStyle::None) {
1420 // for inside flips, we flip on the opposite side of the anchor
1421 nscoord startpos =
1422 aFlip == FlipStyle::Outside ? aAnchorBegin : aAnchorEnd;
1423 nscoord endpos = aFlip == FlipStyle::Outside ? aAnchorEnd : aAnchorBegin;
1424
1425 // check whether there is more room to the left and right (or top and
1426 // bottom) of the anchor and put the popup on the side with more room.
1427 if (aScreenEnd - endpos >= startpos - aScreenBegin) {
1428 *aFlipSide = aEndAligned;
1429 if (mIsContextMenu) {
1430 aScreenPoint = aScreenEnd - aSize;
1431 } else {
1432 aScreenPoint = endpos + aMarginBegin;
1433 popupSize = aScreenEnd - aScreenPoint;
1434 }
1435 } else {
1436 // if the newly calculated position is different than the existing
1437 // position, we flip such that the popup is to the left or top of the
1438 // anchor point instead.
1439 nscoord newScreenPoint = startpos - aSize - aMarginBegin;
1440 if (newScreenPoint != aScreenPoint) {
1441 *aFlipSide = !aEndAligned;
1442 aScreenPoint = newScreenPoint;
1443
1444 // check if the new position is still off the left or top edge of the
1445 // screen. If so, resize the popup.
1446 if (aScreenPoint < aScreenBegin) {
1447 aScreenPoint = aScreenBegin;
1448 if (!mIsContextMenu) {
1449 popupSize = startpos - aScreenPoint - aMarginBegin;
1450 }
1451 }
1452 }
1453 }
1454 } else {
1455 aScreenPoint = aScreenEnd - aSize;
1456 }
1457 }
1458
1459 // Make sure that the point is within the screen boundaries and that the
1460 // size isn't off the edge of the screen. This can happen when a large
1461 // positive or negative margin is used.
1462 if (aScreenPoint < aScreenBegin) {
1463 aScreenPoint = aScreenBegin;
1464 }
1465 if (aScreenPoint > aScreenEnd) {
1466 aScreenPoint = aScreenEnd - aSize;
1467 }
1468
1469 // If popupSize ended up being negative, or the original size was actually
1470 // smaller than the calculated popup size, just use the original size instead.
1471 if (popupSize <= 0 || aSize < popupSize) {
1472 popupSize = aSize;
1473 }
1474
1475 return std::min(popupSize, aScreenEnd - aScreenPoint);
1476}
1477
1478nsRect nsMenuPopupFrame::ComputeAnchorRect(nsPresContext* aRootPresContext,
1479 nsIFrame* aAnchorFrame) const {
1480 // Get the root frame for a reference
1481 nsIFrame* rootFrame = aRootPresContext->PresShell()->GetRootFrame();
1482
1483 // The dimensions of the anchor
1484 nsRect anchorRect = aAnchorFrame->GetRectRelativeToSelf();
1485
1486 // Relative to the root
1487 anchorRect = nsLayoutUtils::TransformFrameRectToAncestor(
1488 aAnchorFrame, anchorRect, rootFrame);
1489 // Relative to the screen
1490 anchorRect.MoveBy(rootFrame->GetScreenRectInAppUnits().TopLeft());
1491
1492 // In its own app units
1493 return anchorRect.ScaleToOtherAppUnitsRoundOut(
1494 aRootPresContext->AppUnitsPerDevPixel(),
1495 PresContext()->AppUnitsPerDevPixel());
1496}
1497
1498static nsIFrame* MaybeDelegatedAnchorFrame(nsIFrame* aFrame) {
1499 if (!aFrame) {
1500 return nullptr;
1501 }
1502 if (auto* element = Element::FromNodeOrNull(aFrame->GetContent())) {
1503 if (element->HasAttr(nsGkAtoms::delegatesanchor)) {
1504 for (nsIFrame* f : aFrame->PrincipalChildList()) {
1505 if (!f->IsPlaceholderFrame()) {
1506 return f;
1507 }
1508 }
1509 }
1510 }
1511 return aFrame;
1512}
1513
1514auto nsMenuPopupFrame::GetRects(const nsSize& aPrefSize) const -> Rects {
1515 if (NS_WARN_IF(aPrefSize == nsSize(-1, -1))NS_warn_if_impl(aPrefSize == nsSize(-1, -1), "aPrefSize == nsSize(-1, -1)"
, "./../../../layout/xul/nsMenuPopupFrame.cpp", 1515)
) {
1516 // Return early if the popup hasn't been laid out yet. On Windows, this can
1517 // happen when using a drag popup before it opens.
1518 return {};
1519 }
1520
1521 nsPresContext* pc = PresContext();
1522 nsIFrame* rootFrame = pc->PresShell()->GetRootFrame();
1523
1524 // Indicators of whether the popup should be flipped or resized.
1525 FlipStyle hFlip = FlipStyle::None;
1526 FlipStyle vFlip = FlipStyle::None;
1527
1528 const nsMargin margin = GetMargin();
1529
1530 // the screen rectangle of the root frame, in dev pixels.
1531 const nsRect rootScreenRect = rootFrame->GetScreenRectInAppUnits();
1532
1533 const PopupLevel popupLevel = GetPopupLevel();
1534
1535 Rects result;
1536
1537 // Set the popup's size to the preferred size. Below, this size will be
1538 // adjusted to fit on the screen or within the content area. If the anchor is
1539 // sized to the popup, use the anchor's width instead of the preferred width.
1540 result.mUsedRect = nsRect(nsPoint(), aPrefSize);
1541
1542 const bool anchored = IsAnchored();
1543 if (anchored) {
1544 // In order to deal with transforms, we need the root prescontext:
1545 nsPresContext* rootPc = pc->GetRootPresContext();
1546 if (NS_WARN_IF(!rootPc)NS_warn_if_impl(!rootPc, "!rootPc", "./../../../layout/xul/nsMenuPopupFrame.cpp"
, 1546)
) {
1547 // If we can't reach a root pres context, don't bother continuing.
1548 return result;
1549 }
1550
1551 result.mAnchorRect = result.mUntransformedAnchorRect = [&] {
1552 // If anchored to a rectangle, use that rectangle. Otherwise, determine
1553 // the rectangle from the anchor.
1554 if (mAnchorType == MenuPopupAnchorType::Rect) {
1555 return mScreenRect;
1556 }
1557 // if the frame is not specified, use the anchor node passed to OpenPopup.
1558 // If that wasn't specified either, use the root frame. Note that
1559 // mAnchorContent might be a different document so its presshell must be
1560 // used.
1561 nsIFrame* anchorFrame = GetAnchorFrame();
1562 if (!anchorFrame) {
1563 return rootScreenRect;
1564 }
1565 return ComputeAnchorRect(rootPc, anchorFrame);
1566 }();
1567
1568 // if we are anchored, there are certain things we don't want to do when
1569 // repositioning the popup to fit on the screen, such as end up positioned
1570 // over the anchor, for instance a popup appearing over the menu label.
1571 // When doing this reposition, we want to move the popup to the side with
1572 // the most room. The combination of anchor and alignment dictate if we
1573 // readjust above/below or to the left/right.
1574 if (mAnchorContent || mAnchorType == MenuPopupAnchorType::Rect) {
1575 // move the popup according to the anchor and alignment. This will also
1576 // tell us which axis the popup is flush against in case we have to move
1577 // it around later. The AdjustPositionForAnchorAlign method accounts for
1578 // the popup's margin.
1579 result.mUsedRect.MoveTo(AdjustPositionForAnchorAlign(
1580 result.mAnchorRect, aPrefSize, hFlip, vFlip));
1581 } else {
1582 // With no anchor, the popup is positioned relative to the root frame.
1583 result.mUsedRect.MoveTo(result.mAnchorRect.TopLeft() +
1584 nsPoint(margin.left, margin.top));
1585 }
1586 } else {
1587 // Not anchored, use mScreenRect
1588 result.mUsedRect.MoveTo(mScreenRect.TopLeft());
1589 result.mAnchorRect = result.mUntransformedAnchorRect =
1590 nsRect(mScreenRect.TopLeft(), nsSize());
1591
1592 // Right-align RTL context menus, and apply margin and offsets as per the
1593 // platform conventions.
1594 if (mIsContextMenu && IsDirectionRTL()) {
1595 result.mUsedRect.x -= aPrefSize.Width();
1596 result.mUsedRect.MoveBy(-margin.right, margin.top);
1597 } else {
1598 result.mUsedRect.MoveBy(margin.left, margin.top);
1599 }
1600#ifdef XP_MACOSX
1601 // On macOS, tooltips follow standard flip rule but other popups like
1602 // context menus flip horizontally, not vertically.
1603 if (mPopupType == PopupType::Tooltip) {
1604 vFlip = FlipStyle::Outside;
1605 } else {
1606 hFlip = FlipStyle::Outside;
1607 }
1608#else
1609 // On Windows and Linux, other OS screen positioned popups can be flipped
1610 // vertically. Only the context menu can be flipped horizontally as well, in
1611 // order to avoid showing the context menu accidentally under the mouse.
1612 vFlip = FlipStyle::Outside;
1613 if (mIsContextMenu) {
1614 hFlip = FlipStyle::Outside;
1615 }
1616#endif // #ifdef XP_MACOSX
1617 }
1618
1619 const int32_t a2d = pc->AppUnitsPerDevPixel();
1620
1621 nsIWidget* widget = mWidget;
1622
1623 // If a panel has flip="none", don't constrain or flip it.
1624 // Also, always do this for content shells, so that the popup doesn't extend
1625 // outside the containing frame.
1626 if (mInContentShell || mFlip != FlipType::None) {
1627 const Maybe<nsRect> constraintRect =
1628 GetConstraintRect(result.mAnchorRect, rootScreenRect, popupLevel);
1629
1630 if (constraintRect) {
1631 // Ensure that anchorRect is on the constraint rect.
1632 result.mAnchorRect = result.mAnchorRect.Intersect(*constraintRect);
1633 // Shrink the popup down if it is larger than the constraint size
1634 if (result.mUsedRect.width > constraintRect->width) {
1635 result.mUsedRect.width = constraintRect->width;
1636 }
1637 if (result.mUsedRect.height > constraintRect->height) {
1638 result.mUsedRect.height = constraintRect->height;
1639 }
1640 result.mConstrainedByLayout = true;
1641 }
1642
1643 if (IS_WAYLAND_DISPLAY()mozilla::widget::GdkIsWaylandDisplay() && widget) {
1644 // Shrink the popup down if it's larger than popup size received from
1645 // Wayland compositor. We don't know screen size on Wayland so this is the
1646 // only info we have there.
1647 const nsSize waylandSize = LayoutDeviceIntRect::ToAppUnits(
1648 widget->GetMoveToRectPopupSize(), a2d);
1649
1650 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)
1651 "[%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)
1652 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)
;
1653 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)
1654 "[%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)
1655 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)
1656 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)
;
1657
1658 if (waylandSize.width > 0 && result.mUsedRect.width > waylandSize.width) {
1659 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)
1660 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)
;
1661 result.mUsedRect.width = waylandSize.width;
1662 }
1663 if (waylandSize.height > 0 &&
1664 result.mUsedRect.height > waylandSize.height) {
1665 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)
1666 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)
;
1667 result.mUsedRect.height = waylandSize.height;
1668 }
1669 if (RefPtr<widget::Screen> s = widget->GetWidgetScreen()) {
1670 const nsSize screenSize =
1671 LayoutDeviceIntSize::ToAppUnits(s->GetAvailRect().Size(), a2d);
1672 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)
1673 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)
1674 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)
;
1675
1676 if (result.mUsedRect.height > screenSize.height) {
1677 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)
1678 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)
1679 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)
;
1680 result.mUsedRect.height = screenSize.height;
1681 }
1682 if (result.mUsedRect.width > screenSize.width) {
1683 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)
1684 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)
;
1685 result.mUsedRect.width = screenSize.width;
1686 }
1687 }
1688 }
1689
1690 // At this point the anchor (anchorRect) is within the available screen
1691 // area (constraintRect) and the popup is known to be no larger than the
1692 // screen.
1693 if (constraintRect) {
1694 // We might want to "slide" an arrow if the panel is of the correct type -
1695 // but we can only slide on one axis - the other axis must be "flipped or
1696 // resized" as normal.
1697 bool slideHorizontal = false, slideVertical = false;
1698 if (mFlip == FlipType::Slide) {
1699 int8_t position = GetAlignmentPosition();
1700 slideHorizontal = position >= POPUPPOSITION_BEFORESTART0 &&
1701 position <= POPUPPOSITION_AFTEREND3;
1702 slideVertical = position >= POPUPPOSITION_STARTBEFORE4 &&
1703 position <= POPUPPOSITION_ENDAFTER7;
1704 }
1705
1706 // Next, check if there is enough space to show the popup at full size
1707 // when positioned at screenPoint. If not, flip the popups to the opposite
1708 // side of their anchor point, or resize them as necessary.
1709 if (slideHorizontal) {
1710 result.mUsedRect.width = SlideOrResize(
1711 result.mUsedRect.x, result.mUsedRect.width, constraintRect->x,
1712 constraintRect->XMost(), &result.mAlignmentOffset);
1713 } else {
1714 const bool endAligned =
1715 IsDirectionRTL()
1716 ? mPopupAlignment == POPUPALIGNMENT_TOPLEFT1 ||
1717 mPopupAlignment == POPUPALIGNMENT_BOTTOMLEFT2 ||
1718 mPopupAlignment == POPUPALIGNMENT_LEFTCENTER16
1719 : mPopupAlignment == POPUPALIGNMENT_TOPRIGHT-1 ||
1720 mPopupAlignment == POPUPALIGNMENT_BOTTOMRIGHT-2 ||
1721 mPopupAlignment == POPUPALIGNMENT_RIGHTCENTER-16;
1722 result.mUsedRect.width = FlipOrResize(
1723 result.mUsedRect.x, result.mUsedRect.width, constraintRect->x,
1724 constraintRect->XMost(), result.mAnchorRect.x,
1725 result.mAnchorRect.XMost(), margin.left, margin.right, hFlip,
1726 endAligned, &result.mHFlip);
1727 }
1728 if (slideVertical) {
1729 result.mUsedRect.height = SlideOrResize(
1730 result.mUsedRect.y, result.mUsedRect.height, constraintRect->y,
1731 constraintRect->YMost(), &result.mAlignmentOffset);
1732 } else {
1733 bool endAligned = mPopupAlignment == POPUPALIGNMENT_BOTTOMLEFT2 ||
1734 mPopupAlignment == POPUPALIGNMENT_BOTTOMRIGHT-2 ||
1735 mPopupAlignment == POPUPALIGNMENT_BOTTOMCENTER18;
1736 result.mUsedRect.height = FlipOrResize(
1737 result.mUsedRect.y, result.mUsedRect.height, constraintRect->y,
1738 constraintRect->YMost(), result.mAnchorRect.y,
1739 result.mAnchorRect.YMost(), margin.top, margin.bottom, vFlip,
1740 endAligned, &result.mVFlip);
1741 }
1742
1743#ifdef DEBUG1
1744 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", 1745); MOZ_PretendNoReturn
(); } } while (0)
1745 "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", 1745); MOZ_PretendNoReturn
(); } } while (0)
;
1746 if (!constraintRect->Contains(result.mUsedRect)) {
1747 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"
, 1750)
1748 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"
, 1750)
1749 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"
, 1750)
1750 .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"
, 1750)
;
1751 }
1752#endif
1753 }
1754 }
1755 // snap the popup's position in screen coordinates to device pixels, see
1756 // bug 622507, bug 961431
1757 result.mUsedRect.x = pc->RoundAppUnitsToNearestDevPixels(result.mUsedRect.x);
1758 result.mUsedRect.y = pc->RoundAppUnitsToNearestDevPixels(result.mUsedRect.y);
1759
1760 // determine the x and y position of the view by subtracting the desired
1761 // screen position from the screen position of the root frame.
1762 result.mViewPoint = result.mUsedRect.TopLeft() - rootScreenRect.TopLeft();
1763 return result;
1764}
1765
1766void nsMenuPopupFrame::SetPopupPosition(bool aIsMove) {
1767 if (aIsMove && (mPrefSize.width == -1 || mPrefSize.height == -1)) {
1768 return;
1769 }
1770
1771 auto rects = GetRects(mPrefSize);
1772 if (rects.mUsedRect.Size() != mRect.Size()) {
1773 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"
, 1773); AnnotateMozCrashReason("MOZ_ASSERT" "(" "!HasAnyStateBits(NS_FRAME_IN_REFLOW)"
")"); do { MOZ_CrashSequence(__null, 1773); __attribute__((nomerge
)) ::abort(); } while (false); } } while (false)
;
1774 // We need to resize on top of moving, trigger an actual reflow.
1775 PresShell()->FrameNeedsReflow(this, IntrinsicDirty::FrameAndAncestors,
1776 NS_FRAME_IS_DIRTY);
1777 return;
1778 }
1779 PerformMove(rects);
1780}
1781
1782void nsMenuPopupFrame::PerformMove(const Rects& aRects) {
1783 auto* ps = PresShell();
1784
1785 // Now that we've positioned the view, sync up the frame's origin.
1786 const nsPoint oldPos = mRect.TopLeft();
1787 const nsPoint newPos =
1788 aRects.mViewPoint - GetParent()->GetOffsetTo(ps->GetRootFrame());
1789 nsBlockFrame::SetPosition(newPos);
1790 if (oldPos != newPos) {
1791 SchedulePendingWidgetMoveResize();
1792 }
1793
1794 // If the popup is in the positioned state or if it is shown and the position
1795 // or size changed, dispatch a popuppositioned event if the popup wants it.
1796 if (mPopupState == ePopupPositioning ||
1797 (mPopupState == ePopupShown &&
1798 !aRects.mUsedRect.IsEqualEdges(mUsedScreenRect)) ||
1799 (mPopupState == ePopupShown &&
1800 aRects.mAlignmentOffset != mAlignmentOffset)) {
1801 mUsedScreenRect = aRects.mUsedRect;
1802 if (!HasAnyStateBits(NS_FRAME_FIRST_REFLOW) && !mPendingPositionedEvent) {
1803 mPendingPositionedEvent =
1804 nsXULPopupPositionedEvent::DispatchIfNeeded(mContent->AsElement());
1805 }
1806 }
1807
1808 if (!mPositionedByMoveToRect) {
1809 mUntransformedAnchorRect = aRects.mUntransformedAnchorRect;
1810 }
1811
1812 mAlignmentOffset = aRects.mAlignmentOffset;
1813 mHFlip = aRects.mHFlip;
1814 mVFlip = aRects.mVFlip;
1815 mConstrainedByLayout = aRects.mConstrainedByLayout;
1816
1817 // If this is a noautohide popup, set the screen coordinates of the popup.
1818 // This way, the popup stays at the location where it was opened even when the
1819 // window is moved. Popups at the parent level follow the parent window as it
1820 // is moved and remained anchored, so we want to maintain the anchoring
1821 // instead.
1822 //
1823 // FIXME: This suffers from issues like bug 1823552, where constraints imposed
1824 // by the anchor are lost, but this is super-old behavior.
1825 const bool fixPositionToPoint =
1826 IsNoAutoHide() && (GetPopupLevel() != PopupLevel::Parent ||
1827 mAnchorType == MenuPopupAnchorType::Rect);
1828 if (fixPositionToPoint) {
1829 // Account for the margin that will end up being added to the screen
1830 // coordinate the next time SetPopupPosition is called.
1831 const auto& margin = GetMargin();
1832 mAnchorType = MenuPopupAnchorType::Point;
1833 mScreenRect.x = aRects.mUsedRect.x - margin.left;
1834 mScreenRect.y = aRects.mUsedRect.y - margin.top;
1835 }
1836
1837 // For anchored popups that shouldn't follow the anchor, fix the original
1838 // anchor rect.
1839 if (IsAnchored() && !ShouldFollowAnchor() && !mUsedScreenRect.IsEmpty() &&
1840 mAnchorType != MenuPopupAnchorType::Rect) {
1841 mAnchorType = MenuPopupAnchorType::Rect;
1842 mScreenRect = aRects.mUntransformedAnchorRect;
1843 }
1844}
1845
1846Maybe<nsRect> nsMenuPopupFrame::GetConstraintRect(
1847 const nsRect& aAnchorRect, const nsRect& aRootScreenRect,
1848 PopupLevel aPopupLevel) const {
1849 const nsPresContext* pc = PresContext();
1850 const int32_t a2d = PresContext()->AppUnitsPerDevPixel();
1851 Maybe<nsRect> result;
1852
1853 auto AddConstraint = [&result](const nsRect& aConstraint) {
1854 if (result) {
1855 *result = result->Intersect(aConstraint);
1856 } else {
1857 result.emplace(aConstraint);
1858 }
1859 };
1860
1861 // Determine the available screen space. It will be reduced by the OS chrome
1862 // such as menubars. It addition, for content shells, it will be the area of
1863 // the content rather than the screen.
1864 if (IS_WAYLAND_DISPLAY()mozilla::widget::GdkIsWaylandDisplay()) {
1865 // In Wayland we can't use the screen rect, because we can't know the
1866 // absolute window position. MoveToRect usually deals with this, but we
1867 // can't use it unconditionally. Tooltips are presumed small enough, and
1868 // they can open basically at any time with any other open menu, so we
1869 // constrain them to the window area, see bug 1941237.
1870 // TODO(emilio, stransky): Do we want to constrain other popups to the
1871 // window area, if the window is big enough or maximized?
1872 if (mPopupType == PopupType::Tooltip) {
1873 AddConstraint(aRootScreenRect);
1874 }
1875 } else {
1876 const DesktopToLayoutDeviceScale scale =
1877 pc->DeviceContext()->GetDesktopToDeviceScale();
1878 // For content shells, get the screen where the root frame is located. This
1879 // is because we need to constrain the content to this content area, so we
1880 // should use the same screen. Otherwise, use the screen where the anchor is
1881 // located.
1882 const nsRect& rect = mInContentShell ? aRootScreenRect : aAnchorRect;
1883 auto desktopRect = DesktopIntRect::RoundOut(
1884 LayoutDeviceRect::FromAppUnits(rect, a2d) / scale);
1885 desktopRect.width = std::max(1, desktopRect.width);
1886 desktopRect.height = std::max(1, desktopRect.height);
1887
1888 RefPtr<nsIScreen> screen =
1889 widget::ScreenManager::GetSingleton().ScreenForRect(desktopRect);
1890 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", 1890); AnnotateMozCrashReason
("MOZ_ASSERT" "(" "screen" ") (" "We always fall back to the primary screen"
")"); do { MOZ_CrashSequence(__null, 1890); __attribute__((nomerge
)) ::abort(); } while (false); } } while (false)
;
1891 // Non-top-level popups (which will always be panels) should never overlap
1892 // the OS bar.
1893 const bool canOverlapOSBar =
1894 aPopupLevel == PopupLevel::Top &&
1895 LookAndFeel::GetInt(LookAndFeel::IntID::MenusCanOverlapOSBar) &&
1896 !mInContentShell;
1897 // Get the total screen area if the popup is allowed to overlap it.
1898 const auto screenRect =
1899 canOverlapOSBar ? screen->GetRect() : screen->GetAvailRect();
1900 AddConstraint(LayoutDeviceRect::ToAppUnits(screenRect, a2d));
1901 }
1902
1903 if (mInContentShell) {
1904 // For content shells, clip to the client area rather than the screen area
1905 AddConstraint(aRootScreenRect);
1906 } else if (!mOverrideConstraintRect.IsEmpty()) {
1907 AddConstraint(mOverrideConstraintRect);
1908 // This is currently only used for <select> elements where we want to
1909 // constrain vertically to the screen but not horizontally, so do the
1910 // intersection and then reset the horizontal values.
1911 //
1912 // FIXME(emilio): This doesn't make any sense to me...
1913 result->x = mOverrideConstraintRect.x;
1914 result->width = mOverrideConstraintRect.width;
1915 }
1916
1917 // Expand the allowable screen rect by the input margin (which can't be
1918 // interacted with).
1919 if (result) {
1920 const nscoord inputMargin =
1921 StyleUIReset()->mMozWindowInputRegionMargin.ToAppUnits();
1922 result->Inflate(inputMargin);
1923 }
1924 return result;
1925}
1926
1927ConsumeOutsideClicksResult nsMenuPopupFrame::ConsumeOutsideClicks() {
1928 if (mContent->AsElement()->AttrValueIs(kNameSpaceID_None,
1929 nsGkAtoms::consumeoutsideclicks,
1930 nsGkAtoms::_true, eCaseMatters)) {
1931 return ConsumeOutsideClicks_True;
1932 }
1933 if (mContent->AsElement()->AttrValueIs(kNameSpaceID_None,
1934 nsGkAtoms::consumeoutsideclicks,
1935 nsGkAtoms::_false, eCaseMatters)) {
1936 return ConsumeOutsideClicks_ParentOnly;
1937 }
1938 if (mContent->AsElement()->AttrValueIs(kNameSpaceID_None,
1939 nsGkAtoms::consumeoutsideclicks,
1940 nsGkAtoms::never, eCaseMatters)) {
1941 return ConsumeOutsideClicks_Never;
1942 }
1943
1944 nsCOMPtr<nsIContent> parentContent = mContent->GetParent();
1945 if (parentContent) {
1946 dom::NodeInfo* ni = parentContent->NodeInfo();
1947 if (ni->Equals(nsGkAtoms::menulist, kNameSpaceID_XUL8)) {
1948 return ConsumeOutsideClicks_True; // Consume outside clicks for combo
1949 // boxes on all platforms
1950 }
1951#if defined(XP_WIN)
1952 // Don't consume outside clicks for menus in Windows
1953 if (ni->Equals(nsGkAtoms::menu, kNameSpaceID_XUL8) ||
1954 ni->Equals(nsGkAtoms::popupset, kNameSpaceID_XUL8) ||
1955 ((ni->Equals(nsGkAtoms::button, kNameSpaceID_XUL8) ||
1956 ni->Equals(nsGkAtoms::toolbarbutton, kNameSpaceID_XUL8)) &&
1957 parentContent->AsElement()->AttrValueIs(
1958 kNameSpaceID_None, nsGkAtoms::type, nsGkAtoms::menu,
1959 eCaseMatters))) {
1960 return ConsumeOutsideClicks_Never;
1961 }
1962#endif
1963 }
1964
1965 return ConsumeOutsideClicks_True;
1966}
1967
1968static ScrollContainerFrame* DoGetScrollContainerFrame(const nsIFrame* aFrame) {
1969 if (const ScrollContainerFrame* sf = do_QueryFrame(aFrame)) {
1970 return const_cast<ScrollContainerFrame*>(sf);
1971 }
1972 for (nsIFrame* childFrame : aFrame->PrincipalChildList()) {
1973 if (auto* sf = DoGetScrollContainerFrame(childFrame)) {
1974 return sf;
1975 }
1976 }
1977 return nullptr;
1978}
1979
1980// XXXroc this is megalame. Fossicking around for a frame of the right
1981// type is a recipe for disaster in the long term.
1982ScrollContainerFrame* nsMenuPopupFrame::GetScrollContainerFrame() const {
1983 return DoGetScrollContainerFrame(this);
1984}
1985
1986void nsMenuPopupFrame::ChangeByPage(bool aIsUp) {
1987 // Only scroll by page within menulists.
1988 if (!IsMenuList()) {
1989 return;
1990 }
1991
1992 ScrollContainerFrame* scrollContainerFrame = GetScrollContainerFrame();
1993
1994 RefPtr popup = &PopupElement();
1995 XULButtonElement* currentMenu = popup->GetActiveMenuChild();
1996 XULButtonElement* newMenu = nullptr;
1997 if (!currentMenu) {
1998 // If there is no current menu item, get the first item. When moving up,
1999 // just use this as the newMenu and leave currentMenu null so that no check
2000 // for a later element is performed. When moving down, set currentMenu so
2001 // that we look for one page down from the first item.
2002 newMenu = popup->GetFirstMenuItem();
2003 if (!aIsUp) {
2004 currentMenu = newMenu;
2005 }
2006 }
2007
2008 if (currentMenu && currentMenu->GetPrimaryFrame()) {
2009 const nscoord scrollHeight =
2010 scrollContainerFrame ? scrollContainerFrame->GetScrollPortRect().height
2011 : mRect.height;
2012 const nsRect currentRect = currentMenu->GetPrimaryFrame()->GetRect();
2013 const XULButtonElement* startMenu = currentMenu;
2014
2015 // Get the position of the current item and add or subtract one popup's
2016 // height to or from it.
2017 const nscoord targetPos = aIsUp ? currentRect.YMost() - scrollHeight
2018 : currentRect.y + scrollHeight;
2019 // Look for the next child which is just past the target position. This
2020 // child will need to be selected.
2021 for (; currentMenu;
2022 currentMenu = aIsUp ? popup->GetPrevMenuItemFrom(*currentMenu)
2023 : popup->GetNextMenuItemFrom(*currentMenu)) {
2024 if (!currentMenu->GetPrimaryFrame()) {
2025 continue;
2026 }
2027 const nsRect curRect = currentMenu->GetPrimaryFrame()->GetRect();
2028 const nscoord curPos = aIsUp ? curRect.y : curRect.YMost();
2029 // If the right position was found, break out. Otherwise, look for another
2030 // item.
2031 if (aIsUp ? (curPos < targetPos) : (curPos > targetPos)) {
2032 if (!newMenu || newMenu == startMenu) {
2033 newMenu = currentMenu;
2034 }
2035 break;
2036 }
2037
2038 // Assign this item to newMenu. This item will be selected in case we
2039 // don't find any more.
2040 newMenu = currentMenu;
2041 }
2042 }
2043
2044 // Select the new menuitem.
2045 if (RefPtr newMenuRef = newMenu) {
2046 popup->SetActiveMenuChild(newMenuRef);
2047 }
2048}
2049
2050dom::XULPopupElement& nsMenuPopupFrame::PopupElement() const {
2051 auto* popup = dom::XULPopupElement::FromNode(GetContent());
2052 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"
, 2052); AnnotateMozCrashReason("MOZ_DIAGNOSTIC_ASSERT" "(" "popup"
")"); do { MOZ_CrashSequence(__null, 2052); __attribute__((nomerge
)) ::abort(); } while (false); } } while (false)
;
2053 return *popup;
2054}
2055
2056XULButtonElement* nsMenuPopupFrame::GetCurrentMenuItem() const {
2057 return PopupElement().GetActiveMenuChild();
2058}
2059
2060nsIFrame* nsMenuPopupFrame::GetCurrentMenuItemFrame() const {
2061 auto* child = GetCurrentMenuItem();
2062 return child ? child->GetPrimaryFrame() : nullptr;
2063}
2064
2065void nsMenuPopupFrame::HandleEnterKeyPress(WidgetEvent& aEvent) {
2066 mIncrementalString.Truncate();
2067 RefPtr popup = &PopupElement();
2068 popup->HandleEnterKeyPress(aEvent);
2069}
2070
2071XULButtonElement* nsMenuPopupFrame::FindMenuWithShortcut(
2072 mozilla::dom::KeyboardEvent& aKeyEvent, bool& aDoAction) {
2073 uint32_t charCode = aKeyEvent.CharCode();
2074 uint32_t keyCode = aKeyEvent.KeyCode();
2075
2076 aDoAction = false;
2077
2078 // Enumerate over our list of frames.
2079 const bool isMenu = !IsMenuList();
2080 TimeStamp keyTime = aKeyEvent.WidgetEventPtr()->mTimeStamp;
2081 if (charCode == 0) {
2082 if (keyCode == dom::KeyboardEvent_Binding::DOM_VK_BACK_SPACE) {
2083 if (!isMenu && !mIncrementalString.IsEmpty()) {
2084 mIncrementalString.SetLength(mIncrementalString.Length() - 1);
2085 return nullptr;
2086 }
2087#ifdef XP_WIN
2088 if (nsCOMPtr<nsISound> sound = do_GetService("@mozilla.org/sound;1")) {
2089 sound->Beep();
2090 }
2091#endif // #ifdef XP_WIN
2092 }
2093 return nullptr;
2094 }
2095 char16_t uniChar = ToLowerCase(static_cast<char16_t>(charCode));
2096 if (isMenu) {
2097 // Menu supports only first-letter navigation
2098 mIncrementalString = uniChar;
2099 } else if (IsWithinIncrementalTime(keyTime)) {
2100 mIncrementalString.Append(uniChar);
2101 } else {
2102 // Interval too long, treat as new typing
2103 mIncrementalString = uniChar;
2104 }
2105
2106 // See bug 188199 & 192346, if all letters in incremental string are same,
2107 // just try to match the first one
2108 nsAutoString incrementalString(mIncrementalString);
2109 uint32_t charIndex = 1, stringLength = incrementalString.Length();
2110 while (charIndex < stringLength &&
2111 incrementalString[charIndex] == incrementalString[charIndex - 1]) {
2112 charIndex++;
2113 }
2114 if (charIndex == stringLength) {
2115 incrementalString.Truncate(1);
2116 stringLength = 1;
Value stored to 'stringLength' is never read
2117 }
2118
2119 sLastKeyTime = keyTime;
2120
2121 auto* item =
2122 PopupElement().FindMenuWithShortcut(incrementalString, aDoAction);
2123 if (item) {
2124 return item;
2125 }
2126
2127 // If we don't match anything, rollback the last typing
2128 mIncrementalString.SetLength(mIncrementalString.Length() - 1);
2129
2130 // didn't find a matching menu item
2131#ifdef XP_WIN
2132 // behavior on Windows - this item is in a menu popup off of the
2133 // menu bar, so beep and do nothing else
2134 if (isMenu) {
2135 if (nsCOMPtr<nsISound> sound = do_GetService("@mozilla.org/sound;1")) {
2136 sound->Beep();
2137 }
2138 }
2139#endif // #ifdef XP_WIN
2140
2141 return nullptr;
2142}
2143
2144nsIWidget* nsMenuPopupFrame::GetWidget() const { return mWidget.get(); }
2145
2146// helpers /////////////////////////////////////////////////////////////
2147
2148nsresult nsMenuPopupFrame::AttributeChanged(int32_t aNameSpaceID,
2149 nsAtom* aAttribute,
2150 AttrModType aModType)
2151
2152{
2153 nsresult rv =
2154 nsBlockFrame::AttributeChanged(aNameSpaceID, aAttribute, aModType);
2155
2156 if (aAttribute == nsGkAtoms::left || aAttribute == nsGkAtoms::top) {
2157 MoveToAttributePosition();
2158 }
2159
2160 if ((aAttribute == nsGkAtoms::remote || aAttribute == nsGkAtoms::level) &&
2161 GetWidget() && !IsOpen()) {
2162 // The remote and level attributes are only read when the widget is created,
2163 // so recreate the widget to apply a changed value. The remote attribute
2164 // affects the compositor and transparency settings, and the level attribute
2165 // affects the popup's z-order. Only do this while the popup is closed to
2166 // avoid tearing it down mid-display.
2167 // TODO(emilio): We should consider doing it only when we get re-shown or
2168 // so.
2169 PrepareWidget(true);
2170 }
2171
2172 if (aAttribute == nsGkAtoms::followanchor) {
2173 if (nsXULPopupManager* pm = nsXULPopupManager::GetInstance()) {
2174 pm->UpdateFollowAnchor(this);
2175 }
2176 }
2177
2178 if (aAttribute == nsGkAtoms::label) {
2179 // set the label for the titlebar
2180 if (nsIWidget* widget = GetWidget()) {
2181 nsAutoString title;
2182 mContent->AsElement()->GetAttr(nsGkAtoms::label, title);
2183 if (!title.IsEmpty()) {
2184 widget->SetTitle(title);
2185 }
2186 }
2187 } else if (aAttribute == nsGkAtoms::ignorekeys) {
2188 nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
2189 if (pm) {
2190 nsAutoString ignorekeys;
2191 mContent->AsElement()->GetAttr(nsGkAtoms::ignorekeys, ignorekeys);
2192 pm->UpdateIgnoreKeys(ignorekeys.EqualsLiteral("true"));
2193 }
2194 }
2195
2196 return rv;
2197}
2198
2199void nsMenuPopupFrame::MoveToAttributePosition() {
2200 // Move the widget around when the user sets the |left| and |top| attributes.
2201 // Note that this is not the best way to move the widget, as it results in
2202 // lots of FE notifications and is likely to be slow as molasses. Use |moveTo|
2203 // on the element if possible.
2204 nsAutoString left, top;
2205 mContent->AsElement()->GetAttr(nsGkAtoms::left, left);
2206 mContent->AsElement()->GetAttr(nsGkAtoms::top, top);
2207 nsresult err1, err2;
2208 const CSSIntPoint pos(left.ToInteger(&err1), top.ToInteger(&err2));
2209 if (NS_SUCCEEDED(err1)((bool)(__builtin_expect(!!(!NS_FAILED_impl(err1)), 1))) && NS_SUCCEEDED(err2)((bool)(__builtin_expect(!!(!NS_FAILED_impl(err2)), 1)))) {
2210 MoveTo(pos, false);
2211 }
2212
2213 PresShell()->FrameNeedsReflow(
2214 this, IntrinsicDirty::FrameAncestorsAndDescendants, NS_FRAME_IS_DIRTY);
2215}
2216
2217void nsMenuPopupFrame::Destroy(DestroyContext& aContext) {
2218 // XXX: Currently we don't fire popuphidden for these popups, that seems wrong
2219 // but alas, also pre-existing.
2220 HidePopup(/* aDeselectMenu = */ false, ePopupClosed,
2221 /* aFromFrameDestruction = */ true);
2222 if (mExpirationState.IsTracked()) {
2223 PopupExpirationTracker::Get()->RemoveObject(this);
2224 }
2225
2226 if (RefPtr<nsXULPopupManager> pm = nsXULPopupManager::GetInstance()) {
2227 pm->PopupDestroyed(this);
2228 }
2229
2230 DestroyWidget();
2231 nsBlockFrame::Destroy(aContext);
2232}
2233
2234nsMargin nsMenuPopupFrame::GetMargin() const {
2235 nsMargin margin;
2236 StyleMargin()->GetMargin(margin);
2237 if (mIsTopLevelContextMenu) {
2238 const CSSIntPoint offset(
2239 LookAndFeel::GetInt(LookAndFeel::IntID::ContextMenuOffsetHorizontal),
2240 LookAndFeel::GetInt(LookAndFeel::IntID::ContextMenuOffsetVertical));
2241 auto auOffset = CSSIntPoint::ToAppUnits(offset);
2242 margin.top += auOffset.y;
2243 margin.bottom += auOffset.y;
2244 margin.left += auOffset.x;
2245 margin.right += auOffset.x;
2246 }
2247 if (mPopupType == PopupType::Tooltip && !IsAnchored()) {
2248 const auto auOffset =
2249 CSSPixel::ToAppUnits(LookAndFeel::TooltipOffsetVertical());
2250 margin.top += auOffset;
2251 margin.bottom += auOffset;
2252 }
2253 // TODO(emilio): We should consider make these properly mirrored (that is,
2254 // changing -= to += here, and removing the rtl special case), but some tests
2255 // rely on the old behavior of the anchor moving physically regardless of
2256 // alignment...
2257 margin.top += mExtraMargin.y;
2258 margin.bottom -= mExtraMargin.y;
2259 if (IsDirectionRTL()) {
2260 margin.left -= mExtraMargin.x;
2261 margin.right += mExtraMargin.x;
2262 } else {
2263 margin.left += mExtraMargin.x;
2264 margin.right -= mExtraMargin.x;
2265 }
2266 return margin;
2267}
2268
2269void nsMenuPopupFrame::DestroyWidgetIfNeeded() {
2270 if (IsVisibleOrShowing()) {
2271 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"
, 2271); AnnotateMozCrashReason("MOZ_ASSERT" "(" "false" ") ("
"MOZ_ASSERT_UNREACHABLE: " "Shouldn't be tracked while visible"
")"); do { MOZ_CrashSequence(__null, 2271); __attribute__((nomerge
)) ::abort(); } while (false); } } while (false)
;
2272 return;
2273 }
2274 DestroyWidget();
2275}
2276
2277void nsMenuPopupFrame::MoveTo(const CSSPoint& aPos, bool aUpdateAttrs,
2278 bool aByMoveToRect) {
2279 nsPoint appUnitsPos = CSSPixel::ToAppUnits(aPos);
2280
2281 const bool rtl = IsDirectionRTL();
2282
2283 // reposition the popup at the specified coordinates. Don't clear the anchor
2284 // and position, because the popup can be reset to its anchor position by
2285 // using (-1, -1) as coordinates.
2286 //
2287 // Subtract off the margin as it will be added to the position when
2288 // SetPopupPosition is called.
2289 {
2290 nsMargin margin = GetMargin();
2291 if (rtl && mIsContextMenu) {
2292 appUnitsPos.x += margin.right + mRect.Width();
2293 } else {
2294 appUnitsPos.x -= margin.left;
2295 }
2296 appUnitsPos.y -= margin.top;
2297 }
2298
2299 if (mScreenRect.TopLeft() == appUnitsPos) {
2300 return;
2301 }
2302
2303 mPositionedByMoveToRect = aByMoveToRect;
2304 mScreenRect.MoveTo(appUnitsPos);
2305 if (mAnchorType == MenuPopupAnchorType::Rect) {
2306 // This ensures that the anchor width is still honored, to prevent it from
2307 // changing spuriously.
2308 mScreenRect.height = 0;
2309 // But we still need to make sure that our top left position ends up in
2310 // appUnitsPos.
2311 mPopupAlignment = rtl ? POPUPALIGNMENT_TOPRIGHT-1 : POPUPALIGNMENT_TOPLEFT1;
2312 mPopupAnchor = rtl ? POPUPALIGNMENT_BOTTOMRIGHT-2 : POPUPALIGNMENT_BOTTOMLEFT2;
2313 } else {
2314 mAnchorType = MenuPopupAnchorType::Point;
2315 }
2316
2317 SetPopupPosition(true);
2318
2319 RefPtr<Element> popup = mContent->AsElement();
2320 if (aUpdateAttrs &&
2321 (popup->HasAttr(nsGkAtoms::left) || popup->HasAttr(nsGkAtoms::top))) {
2322 nsAutoString left, top;
2323 left.AppendInt(RoundedToInt(aPos).x);
2324 top.AppendInt(RoundedToInt(aPos).y);
2325 popup->SetAttr(kNameSpaceID_None, nsGkAtoms::left, left, false);
2326 popup->SetAttr(kNameSpaceID_None, nsGkAtoms::top, top, false);
2327 }
2328}
2329
2330void nsMenuPopupFrame::MoveToAnchor(nsIContent* aAnchorContent,
2331 const nsAString& aPosition, int32_t aXPos,
2332 int32_t aYPos, bool aAttributesOverride) {
2333 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", 2334); MOZ_PretendNoReturn
(); } } while (0)
2334 "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", 2334); MOZ_PretendNoReturn
(); } } while (0)
;
2335
2336 nsPopupState oldstate = mPopupState;
2337 InitializePopup(aAnchorContent, mTriggerContent, aPosition, aXPos, aYPos,
2338 MenuPopupAnchorType::Node, aAttributesOverride,
2339 IsNativeMenu() ? IsNativeMenu::Yes : IsNativeMenu::No);
2340 // InitializePopup changed the state so reset it.
2341 mPopupState = oldstate;
2342
2343 // Pass false here so that flipping and adjusting to fit on the screen happen.
2344 SetPopupPosition(false);
2345}
2346
2347int8_t nsMenuPopupFrame::GetAlignmentPosition() const {
2348 // The code below handles most cases of alignment, anchor and position values.
2349 // Those that are not handled just return POPUPPOSITION_UNKNOWN.
2350
2351 if (mPosition == POPUPPOSITION_OVERLAP8 ||
2352 mPosition == POPUPPOSITION_AFTERPOINTER9 ||
2353 mPosition == POPUPPOSITION_SELECTION10) {
2354 return mPosition;
2355 }
2356
2357 int8_t position = mPosition;
2358
2359 if (position == POPUPPOSITION_UNKNOWN-1) {
2360 switch (mPopupAnchor) {
2361 case POPUPALIGNMENT_BOTTOMRIGHT-2:
2362 case POPUPALIGNMENT_BOTTOMLEFT2:
2363 case POPUPALIGNMENT_BOTTOMCENTER18:
2364 position = mPopupAlignment == POPUPALIGNMENT_TOPRIGHT-1
2365 ? POPUPPOSITION_AFTEREND3
2366 : POPUPPOSITION_AFTERSTART2;
2367 break;
2368 case POPUPALIGNMENT_TOPRIGHT-1:
2369 case POPUPALIGNMENT_TOPLEFT1:
2370 case POPUPALIGNMENT_TOPCENTER17:
2371 position = mPopupAlignment == POPUPALIGNMENT_BOTTOMRIGHT-2
2372 ? POPUPPOSITION_BEFOREEND1
2373 : POPUPPOSITION_BEFORESTART0;
2374 break;
2375 case POPUPALIGNMENT_LEFTCENTER16:
2376 position = mPopupAlignment == POPUPALIGNMENT_BOTTOMRIGHT-2
2377 ? POPUPPOSITION_STARTAFTER6
2378 : POPUPPOSITION_STARTBEFORE4;
2379 break;
2380 case POPUPALIGNMENT_RIGHTCENTER-16:
2381 position = mPopupAlignment == POPUPALIGNMENT_BOTTOMLEFT2
2382 ? POPUPPOSITION_ENDAFTER7
2383 : POPUPPOSITION_ENDBEFORE5;
2384 break;
2385 default:
2386 break;
2387 }
2388 }
2389
2390 if (mHFlip) {
2391 position = POPUPPOSITION_HFLIP(position)(position ^ 1);
2392 }
2393
2394 if (mVFlip) {
2395 position = POPUPPOSITION_VFLIP(position)(position ^ 2);
2396 }
2397
2398 return position;
2399}
2400
2401bool nsMenuPopupFrame::ShouldFollowAnchor() const {
2402 if (mAnchorType != MenuPopupAnchorType::Node || !mAnchorContent) {
2403 return false;
2404 }
2405
2406 // Follow anchor mode is used when followanchor="true" is set or for arrow
2407 // panels.
2408 if (mContent->AsElement()->AttrValueIs(kNameSpaceID_None,
2409 nsGkAtoms::followanchor,
2410 nsGkAtoms::_true, eCaseMatters)) {
2411 return true;
2412 }
2413
2414 if (mContent->AsElement()->AttrValueIs(kNameSpaceID_None,
2415 nsGkAtoms::followanchor,
2416 nsGkAtoms::_false, eCaseMatters)) {
2417 return false;
2418 }
2419
2420 return mPopupType == PopupType::Panel &&
2421 mContent->AsElement()->AttrValueIs(kNameSpaceID_None, nsGkAtoms::type,
2422 nsGkAtoms::arrow, eCaseMatters);
2423}
2424
2425bool nsMenuPopupFrame::ShouldFollowAnchor(nsRect& aRect) {
2426 if (!ShouldFollowAnchor()) {
2427 return false;
2428 }
2429
2430 if (nsIFrame* anchorFrame = GetAnchorFrame()) {
2431 if (nsPresContext* rootPresContext = PresContext()->GetRootPresContext()) {
2432 aRect = ComputeAnchorRect(rootPresContext, anchorFrame);
2433 }
2434 }
2435
2436 return true;
2437}
2438
2439bool nsMenuPopupFrame::IsDirectionRTL() const {
2440 const nsIFrame* anchor = GetAnchorFrame();
2441 const nsIFrame* f = anchor ? anchor : this;
2442 return f->StyleVisibility()->mDirection == StyleDirection::Rtl;
2443}
2444
2445nsIFrame* nsMenuPopupFrame::GetAnchorFrame() const {
2446 nsIContent* anchor = mAnchorContent;
2447 if (!anchor) {
2448 return nullptr;
2449 }
2450 return MaybeDelegatedAnchorFrame(anchor->GetPrimaryFrame());
2451}
2452
2453void nsMenuPopupFrame::CheckForAnchorChange(nsRect& aRect) {
2454 // Don't update if the popup isn't visible or we shouldn't be following the
2455 // anchor.
2456 if (!IsVisible() || !ShouldFollowAnchor()) {
2457 return;
2458 }
2459
2460 bool shouldHide = false;
2461
2462 nsPresContext* rootPresContext = PresContext()->GetRootPresContext();
2463
2464 // If the frame for the anchor has gone away, hide the popup.
2465 nsIFrame* anchor = GetAnchorFrame();
2466 if (!anchor || !rootPresContext) {
2467 shouldHide = true;
2468 } else if (!anchor->IsVisibleConsideringAncestors(
2469 VISIBILITY_CROSS_CHROME_CONTENT_BOUNDARY)) {
2470 // If the anchor is now inside something that is invisible, hide the popup.
2471 shouldHide = true;
2472 } else {
2473 // If the anchor is now inside a hidden parent popup, hide the popup.
2474 nsIFrame* frame = anchor;
2475 while (frame) {
2476 nsMenuPopupFrame* popup = do_QueryFrame(frame);
2477 if (popup && popup->PopupState() != ePopupShown) {
2478 shouldHide = true;
2479 break;
2480 }
2481
2482 frame = frame->GetParent();
2483 }
2484 }
2485
2486 if (shouldHide) {
2487 if (nsXULPopupManager* pm = nsXULPopupManager::GetInstance()) {
2488 // As the caller will be iterating over the open popups, hide
2489 // asyncronously.
2490 pm->HidePopup(mContent->AsElement(),
2491 {HidePopupOption::DeselectMenu, HidePopupOption::Async});
2492 }
2493
2494 return;
2495 }
2496
2497 nsRect anchorRect = ComputeAnchorRect(rootPresContext, anchor);
2498
2499 // If the rectangles are different, move the popup.
2500 if (!anchorRect.IsEqualEdges(aRect)) {
2501 aRect = anchorRect;
2502 SetPopupPosition(true);
2503 }
2504}
2505
2506void nsMenuPopupFrame::WindowMoved(nsIWidget* aWidget,
2507 const LayoutDeviceIntPoint& aPoint,
2508 ByMoveToRect aByMoveToRect) {
2509 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", 2509); AnnotateMozCrashReason
("MOZ_ASSERT" "(" "aWidget == mWidget" ")"); do { MOZ_CrashSequence
(__null, 2509); __attribute__((nomerge)) ::abort(); } while (
false); } } while (false)
;
2510
2511 if (!IsVisibleOrShowing()) {
2512 return;
2513 }
2514
2515 // Don't do anything if the popup is already at the specified location. This
2516 // prevents recursive calls when a popup is positioned.
2517 LayoutDeviceIntRect curDevBounds = CalcWidgetBounds();
2518 if (curDevBounds.TopLeft() == aPoint) {
2519 return;
2520 }
2521
2522 // Update the popup's position using SetPopupPosition if the popup is
2523 // anchored and at the parent level as these maintain their position
2524 // relative to the parent window (except if positioned by move to rect, in
2525 // which case we better make sure that layout matches that). Otherwise, just
2526 // update the popup to the specified screen coordinates.
2527 if (IsAnchored() && GetPopupLevel() == widget::PopupLevel::Parent &&
2528 aByMoveToRect == ByMoveToRect::No) {
2529 SetPopupPosition(true);
2530 } else {
2531 CSSPoint cssPos = aPoint / PresContext()->CSSToDevPixelScale();
2532 MoveTo(cssPos, false, aByMoveToRect == ByMoveToRect::Yes);
2533 }
2534}
2535
2536void nsMenuPopupFrame::WindowResized(nsIWidget* aWidget,
2537 const LayoutDeviceIntSize& aSize) {
2538 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", 2538); AnnotateMozCrashReason
("MOZ_ASSERT" "(" "aWidget == mWidget" ")"); do { MOZ_CrashSequence
(__null, 2538); __attribute__((nomerge)) ::abort(); } while (
false); } } while (false)
;
2539 if (!IsVisibleOrShowing()) {
2540 return;
2541 }
2542
2543 const LayoutDeviceIntRect curDevBounds = CalcWidgetBounds();
2544 // If the size is what we think it is, we have nothing to do.
2545 if (curDevBounds.Size() == aSize) {
2546 return;
2547 }
2548
2549 RefPtr<Element> popup = &PopupElement();
2550
2551 // Only set the width and height if the popup already has these attributes.
2552 if (!popup->HasAttr(nsGkAtoms::width) || !popup->HasAttr(nsGkAtoms::height)) {
2553 return;
2554 }
2555
2556 // The size is different. Convert the actual size to css pixels and store it
2557 // as 'width' and 'height' attributes on the popup.
2558 nsPresContext* presContext = PresContext();
2559
2560 CSSIntSize newCSS(presContext->DevPixelsToIntCSSPixels(aSize.width),
2561 presContext->DevPixelsToIntCSSPixels(aSize.height));
2562
2563 nsAutoString width, height;
2564 width.AppendInt(newCSS.width);
2565 height.AppendInt(newCSS.height);
2566 popup->SetAttr(kNameSpaceID_None, nsGkAtoms::width, width, true);
2567 popup->SetAttr(kNameSpaceID_None, nsGkAtoms::height, height, true);
2568}
2569
2570bool nsMenuPopupFrame::RequestWindowClose(nsIWidget* aWidget) {
2571 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", 2571); AnnotateMozCrashReason
("MOZ_ASSERT" "(" "aWidget == mWidget" ")"); do { MOZ_CrashSequence
(__null, 2571); __attribute__((nomerge)) ::abort(); } while (
false); } } while (false)
;
2572 if (nsXULPopupManager* pm = nsXULPopupManager::GetInstance()) {
2573 pm->HidePopup(&PopupElement(), {HidePopupOption::DeselectMenu});
2574 return true;
2575 }
2576 return false;
2577}
2578
2579nsEventStatus nsMenuPopupFrame::HandleEvent(mozilla::WidgetGUIEvent* aEvent) {
2580 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", 2580); AnnotateMozCrashReason
("MOZ_ASSERT" "(" "aEvent->mWidget" ")"); do { MOZ_CrashSequence
(__null, 2580); __attribute__((nomerge)) ::abort(); } while (
false); } } while (false)
;
2581 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"
, 2581); AnnotateMozCrashReason("MOZ_ASSERT" "(" "aEvent->mWidget == mWidget"
")"); do { MOZ_CrashSequence(__null, 2581); __attribute__((nomerge
)) ::abort(); } while (false); } } while (false)
;
2582 nsEventStatus status = nsEventStatus_eIgnore;
2583 RefPtr ps = PresShell();
2584 ps->HandleEvent(this, aEvent, false, &status);
2585 return status;
2586}
2587
2588void nsMenuPopupFrame::PaintWindow(nsIWidget* aWidget) {
2589 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", 2589); AnnotateMozCrashReason
("MOZ_ASSERT" "(" "aWidget == mWidget" ")"); do { MOZ_CrashSequence
(__null, 2589); __attribute__((nomerge)) ::abort(); } while (
false); } } while (false)
;
2590 nsAutoScriptBlocker scriptBlocker;
2591 RefPtr ps = PresShell();
2592 RefPtr<WindowRenderer> renderer = aWidget->GetWindowRenderer();
2593 if (!renderer->NeedsWidgetInvalidation()) {
2594 renderer->FlushRendering(wr::RenderReasons::WIDGET);
2595 } else {
2596 ps->SyncPaintFallback(this, renderer);
2597 }
2598}
2599
2600void nsMenuPopupFrame::DidCompositeWindow(
2601 mozilla::layers::TransactionId aTransactionId,
2602 const TimeStamp& aCompositeStart, const TimeStamp& aCompositeEnd) {
2603 RefPtr rootPc = PresContext()->GetRootPresContext();
2604 if (!rootPc) {
2605 return;
2606 }
2607 nsAutoScriptBlocker scriptBlocker;
2608 rootPc->NotifyDidPaintForSubtree(aTransactionId, aCompositeEnd);
2609}