Bug Summary

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