Line data Source code
1 : /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 : /* vim: set ts=2 sw=2 et tw=78: */
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 "nsGkAtoms.h"
9 : #include "nsIContent.h"
10 : #include "nsIAtom.h"
11 : #include "nsPresContext.h"
12 : #include "nsStyleContext.h"
13 : #include "nsCSSRendering.h"
14 : #include "nsNameSpaceManager.h"
15 : #include "nsViewManager.h"
16 : #include "nsWidgetsCID.h"
17 : #include "nsMenuFrame.h"
18 : #include "nsMenuBarFrame.h"
19 : #include "nsPopupSetFrame.h"
20 : #include "nsPIDOMWindow.h"
21 : #include "nsIDOMEvent.h"
22 : #include "nsIDOMKeyEvent.h"
23 : #include "nsIDOMScreen.h"
24 : #include "nsIDOMXULMenuListElement.h"
25 : #include "nsIPresShell.h"
26 : #include "nsFrameManager.h"
27 : #include "nsIDocument.h"
28 : #include "nsRect.h"
29 : #include "nsIComponentManager.h"
30 : #include "nsBoxLayoutState.h"
31 : #include "nsIScrollableFrame.h"
32 : #include "nsIRootBox.h"
33 : #include "nsIDocShell.h"
34 : #include "nsReadableUtils.h"
35 : #include "nsUnicharUtils.h"
36 : #include "nsLayoutUtils.h"
37 : #include "nsContentUtils.h"
38 : #include "nsCSSFrameConstructor.h"
39 : #include "nsPIWindowRoot.h"
40 : #include "nsIReflowCallback.h"
41 : #include "nsBindingManager.h"
42 : #include "nsIDocShellTreeOwner.h"
43 : #include "nsIBaseWindow.h"
44 : #include "nsISound.h"
45 : #include "nsIScreenManager.h"
46 : #include "nsIServiceManager.h"
47 : #include "nsThemeConstants.h"
48 : #include "nsTransitionManager.h"
49 : #include "nsDisplayList.h"
50 : #include "nsIDOMXULSelectCntrlItemEl.h"
51 : #include "mozilla/EventDispatcher.h"
52 : #include "mozilla/EventStateManager.h"
53 : #include "mozilla/EventStates.h"
54 : #include "mozilla/Preferences.h"
55 : #include "mozilla/LookAndFeel.h"
56 : #include "mozilla/MouseEvents.h"
57 : #include "mozilla/dom/Element.h"
58 : #include "mozilla/dom/Event.h"
59 : #include "mozilla/dom/PopupBoxObject.h"
60 : #include <algorithm>
61 :
62 : using namespace mozilla;
63 : using mozilla::dom::PopupBoxObject;
64 :
65 : int8_t nsMenuPopupFrame::sDefaultLevelIsTop = -1;
66 :
67 : DOMTimeStamp nsMenuPopupFrame::sLastKeyTime = 0;
68 :
69 : // XXX, kyle.yuan@sun.com, there are 4 definitions for the same purpose:
70 : // nsMenuPopupFrame.h, nsListControlFrame.cpp, listbox.xml, tree.xml
71 : // need to find a good place to put them together.
72 : // if someone changes one, please also change the other.
73 : uint32_t nsMenuPopupFrame::sTimeoutOfIncrementalSearch = 1000;
74 :
75 : const char* kPrefIncrementalSearchTimeout =
76 : "ui.menu.incremental_search.timeout";
77 :
78 : // NS_NewMenuPopupFrame
79 : //
80 : // Wrapper for creating a new menu popup container
81 : //
82 : nsIFrame*
83 44 : NS_NewMenuPopupFrame(nsIPresShell* aPresShell, nsStyleContext* aContext)
84 : {
85 44 : return new (aPresShell) nsMenuPopupFrame(aContext);
86 : }
87 :
88 44 : NS_IMPL_FRAMEARENA_HELPERS(nsMenuPopupFrame)
89 :
90 166 : NS_QUERYFRAME_HEAD(nsMenuPopupFrame)
91 19 : NS_QUERYFRAME_ENTRY(nsMenuPopupFrame)
92 147 : NS_QUERYFRAME_TAIL_INHERITING(nsBoxFrame)
93 :
94 : //
95 : // nsMenuPopupFrame ctor
96 : //
97 44 : nsMenuPopupFrame::nsMenuPopupFrame(nsStyleContext* aContext)
98 : : nsBoxFrame(aContext, kClassID)
99 : , mCurrentMenu(nullptr)
100 : , mView(nullptr)
101 : , mPrefSize(-1, -1)
102 : , mXPos(0)
103 : , mYPos(0)
104 : , mLastClientOffset(0, 0)
105 : , mPopupType(ePopupTypePanel)
106 : , mPopupState(ePopupClosed)
107 : , mPopupAlignment(POPUPALIGNMENT_NONE)
108 : , mPopupAnchor(POPUPALIGNMENT_NONE)
109 : , mPosition(POPUPPOSITION_UNKNOWN)
110 : , mConsumeRollupEvent(PopupBoxObject::ROLLUP_DEFAULT)
111 : , mFlip(FlipType_Default)
112 : , mIsOpenChanged(false)
113 : , mIsContextMenu(false)
114 : , mAdjustOffsetForContextMenu(false)
115 : , mGeneratedChildren(false)
116 : , mMenuCanOverlapOSBar(false)
117 : , mShouldAutoPosition(true)
118 : , mInContentShell(true)
119 : , mIsMenuLocked(false)
120 : , mMouseTransparent(false)
121 : , mIsOffset(false)
122 : , mHFlip(false)
123 : , mVFlip(false)
124 44 : , mAnchorType(MenuPopupAnchorType_Node)
125 : {
126 : // the preference name is backwards here. True means that the 'top' level is
127 : // the default, and false means that the 'parent' level is the default.
128 44 : if (sDefaultLevelIsTop >= 0)
129 43 : return;
130 1 : sDefaultLevelIsTop =
131 1 : Preferences::GetBool("ui.panel.default_level_parent", false);
132 : Preferences::AddUintVarCache(&sTimeoutOfIncrementalSearch,
133 1 : kPrefIncrementalSearchTimeout, 1000);
134 : } // ctor
135 :
136 : void
137 44 : nsMenuPopupFrame::Init(nsIContent* aContent,
138 : nsContainerFrame* aParent,
139 : nsIFrame* aPrevInFlow)
140 : {
141 44 : nsBoxFrame::Init(aContent, aParent, aPrevInFlow);
142 :
143 : // lookup if we're allowed to overlap the OS bar (menubar/taskbar) from the
144 : // look&feel object
145 44 : mMenuCanOverlapOSBar =
146 44 : LookAndFeel::GetInt(LookAndFeel::eIntID_MenusCanOverlapOSBar) != 0;
147 :
148 44 : 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 44 : nsView* ourView = GetView();
154 44 : nsViewManager* viewManager = ourView->GetViewManager();
155 44 : viewManager->SetViewFloating(ourView, true);
156 :
157 44 : mPopupType = ePopupTypePanel;
158 44 : nsIDocument* doc = aContent->OwnerDoc();
159 : int32_t namespaceID;
160 88 : nsCOMPtr<nsIAtom> tag = doc->BindingManager()->ResolveTag(aContent, &namespaceID);
161 44 : if (namespaceID == kNameSpaceID_XUL) {
162 44 : if (tag == nsGkAtoms::menupopup || tag == nsGkAtoms::popup)
163 34 : mPopupType = ePopupTypeMenu;
164 10 : else if (tag == nsGkAtoms::tooltip)
165 9 : mPopupType = ePopupTypeTooltip;
166 : }
167 :
168 88 : nsCOMPtr<nsIDocShellTreeItem> dsti = PresContext()->GetDocShell();
169 44 : if (dsti && dsti->ItemType() == nsIDocShellTreeItem::typeChrome) {
170 44 : mInContentShell = false;
171 : }
172 :
173 : // To improve performance, create the widget for the popup only if it is not
174 : // a leaf. Leaf popups such as menus will create their widgets later when
175 : // the popup opens.
176 44 : if (!IsLeaf() && !ourView->HasWidget()) {
177 0 : CreateWidgetForView(ourView);
178 : }
179 :
180 53 : if (aContent->NodeInfo()->Equals(nsGkAtoms::tooltip, kNameSpaceID_XUL) &&
181 9 : aContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::_default,
182 : nsGkAtoms::_true, eIgnoreCase)) {
183 : nsIRootBox* rootBox =
184 1 : nsIRootBox::GetRootBox(PresContext()->GetPresShell());
185 1 : if (rootBox) {
186 1 : rootBox->SetDefaultTooltip(aContent);
187 : }
188 : }
189 :
190 44 : AddStateBits(NS_FRAME_IN_POPUP);
191 44 : }
192 :
193 : bool
194 0 : nsMenuPopupFrame::HasRemoteContent() const
195 : {
196 0 : return (!mInContentShell && mPopupType == ePopupTypePanel &&
197 0 : mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::remote,
198 0 : nsGkAtoms::_true, eIgnoreCase));
199 : }
200 :
201 : bool
202 0 : nsMenuPopupFrame::IsNoAutoHide() const
203 : {
204 : // Panels with noautohide="true" don't hide when the mouse is clicked
205 : // outside of them, or when another application is made active. Non-autohide
206 : // panels cannot be used in content windows.
207 0 : return (!mInContentShell && mPopupType == ePopupTypePanel &&
208 0 : mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::noautohide,
209 0 : nsGkAtoms::_true, eIgnoreCase));
210 : }
211 :
212 : nsPopupLevel
213 0 : nsMenuPopupFrame::PopupLevel(bool aIsNoAutoHide) const
214 : {
215 : // The popup level is determined as follows, in this order:
216 : // 1. non-panels (menus and tooltips) are always topmost
217 : // 2. any specified level attribute
218 : // 3. if a titlebar attribute is set, use the 'floating' level
219 : // 4. if this is a noautohide panel, use the 'parent' level
220 : // 5. use the platform-specific default level
221 :
222 : // If this is not a panel, this is always a top-most popup.
223 0 : if (mPopupType != ePopupTypePanel)
224 0 : return ePopupLevelTop;
225 :
226 : // If the level attribute has been set, use that.
227 : static nsIContent::AttrValuesArray strings[] =
228 : {&nsGkAtoms::top, &nsGkAtoms::parent, &nsGkAtoms::floating, nullptr};
229 0 : switch (mContent->FindAttrValueIn(kNameSpaceID_None, nsGkAtoms::level,
230 0 : strings, eCaseMatters)) {
231 : case 0:
232 0 : return ePopupLevelTop;
233 : case 1:
234 0 : return ePopupLevelParent;
235 : case 2:
236 0 : return ePopupLevelFloating;
237 : }
238 :
239 : // Panels with titlebars most likely want to be floating popups.
240 0 : if (mContent->HasAttr(kNameSpaceID_None, nsGkAtoms::titlebar))
241 0 : return ePopupLevelFloating;
242 :
243 : // If this panel is a noautohide panel, the default is the parent level.
244 0 : if (aIsNoAutoHide)
245 0 : return ePopupLevelParent;
246 :
247 : // Otherwise, the result depends on the platform.
248 0 : return sDefaultLevelIsTop ? ePopupLevelTop : ePopupLevelParent;
249 : }
250 :
251 : void
252 0 : nsMenuPopupFrame::EnsureWidget(bool aRecreate)
253 : {
254 0 : nsView* ourView = GetView();
255 0 : if (aRecreate) {
256 0 : ourView->DestroyWidget();
257 : }
258 0 : if (!ourView->HasWidget()) {
259 0 : NS_ASSERTION(!mGeneratedChildren && !PrincipalChildList().FirstChild(),
260 : "Creating widget for MenuPopupFrame with children");
261 0 : CreateWidgetForView(ourView);
262 : }
263 0 : }
264 :
265 : nsresult
266 0 : nsMenuPopupFrame::CreateWidgetForView(nsView* aView)
267 : {
268 : // Create a widget for ourselves.
269 0 : nsWidgetInitData widgetData;
270 0 : widgetData.mWindowType = eWindowType_popup;
271 0 : widgetData.mBorderStyle = eBorderStyle_default;
272 0 : widgetData.clipSiblings = true;
273 0 : widgetData.mPopupHint = mPopupType;
274 0 : widgetData.mNoAutoHide = IsNoAutoHide();
275 :
276 0 : if (!mInContentShell) {
277 : // A drag popup may be used for non-static translucent drag feedback
278 0 : if (mPopupType == ePopupTypePanel &&
279 0 : mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::type,
280 : nsGkAtoms::drag, eIgnoreCase)) {
281 0 : widgetData.mIsDragPopup = true;
282 : }
283 :
284 : // If mousethrough="always" is set directly on the popup, then the widget
285 : // should ignore mouse events, passing them through to the content behind.
286 0 : mMouseTransparent = GetStateBits() & NS_FRAME_MOUSE_THROUGH_ALWAYS;
287 0 : widgetData.mMouseTransparent = mMouseTransparent;
288 : }
289 :
290 0 : nsAutoString title;
291 0 : if (mContent && widgetData.mNoAutoHide) {
292 0 : if (mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::titlebar,
293 : nsGkAtoms::normal, eCaseMatters)) {
294 0 : widgetData.mBorderStyle = eBorderStyle_title;
295 :
296 0 : mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::label, title);
297 :
298 0 : if (mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::close,
299 : nsGkAtoms::_true, eCaseMatters)) {
300 0 : widgetData.mBorderStyle =
301 0 : static_cast<enum nsBorderStyle>(widgetData.mBorderStyle | eBorderStyle_close);
302 : }
303 : }
304 : }
305 :
306 0 : bool remote = HasRemoteContent();
307 :
308 0 : nsTransparencyMode mode = nsLayoutUtils::GetFrameTransparency(this, this);
309 : #ifdef MOZ_WIDGET_GTK
310 0 : if (remote) {
311 : // Paradoxically, on Linux, setting the transparency mode to opaque will
312 : // give us proper transparency for composited popups. The shape-mask-based
313 : // pseudo-transparency that we use otherwise will render transparent areas
314 : // as opaque black when compositing is enabled.
315 : // See bug 630346.
316 0 : mode = eTransparencyOpaque;
317 : }
318 : #endif
319 :
320 0 : nsIContent* parentContent = GetContent()->GetParent();
321 0 : nsIAtom *tag = nullptr;
322 0 : if (parentContent && parentContent->IsXULElement())
323 0 : tag = parentContent->NodeInfo()->NameAtom();
324 0 : widgetData.mHasRemoteContent = remote;
325 0 : widgetData.mSupportTranslucency = mode == eTransparencyTransparent;
326 0 : widgetData.mDropShadow = !(mode == eTransparencyTransparent || tag == nsGkAtoms::menulist);
327 0 : widgetData.mPopupLevel = PopupLevel(widgetData.mNoAutoHide);
328 :
329 : // panels which have a parent level need a parent widget. This allows them to
330 : // always appear in front of the parent window but behind other windows that
331 : // should be in front of it.
332 0 : nsCOMPtr<nsIWidget> parentWidget;
333 0 : if (widgetData.mPopupLevel != ePopupLevelTop) {
334 0 : nsCOMPtr<nsIDocShellTreeItem> dsti = PresContext()->GetDocShell();
335 0 : if (!dsti)
336 0 : return NS_ERROR_FAILURE;
337 :
338 0 : nsCOMPtr<nsIDocShellTreeOwner> treeOwner;
339 0 : dsti->GetTreeOwner(getter_AddRefs(treeOwner));
340 0 : if (!treeOwner) return NS_ERROR_FAILURE;
341 :
342 0 : nsCOMPtr<nsIBaseWindow> baseWindow(do_QueryInterface(treeOwner));
343 0 : if (baseWindow)
344 0 : baseWindow->GetMainWidget(getter_AddRefs(parentWidget));
345 : }
346 :
347 0 : nsresult rv = aView->CreateWidgetForPopup(&widgetData, parentWidget,
348 0 : true, true);
349 0 : if (NS_FAILED(rv)) {
350 0 : return rv;
351 : }
352 :
353 0 : nsIWidget* widget = aView->GetWidget();
354 0 : widget->SetTransparencyMode(mode);
355 0 : widget->SetWindowShadowStyle(GetShadowStyle());
356 :
357 : // most popups don't have a title so avoid setting the title if there isn't one
358 0 : if (!title.IsEmpty()) {
359 0 : widget->SetTitle(title);
360 : }
361 :
362 0 : return NS_OK;
363 : }
364 :
365 : uint8_t
366 0 : nsMenuPopupFrame::GetShadowStyle()
367 : {
368 0 : uint8_t shadow = StyleUIReset()->mWindowShadow;
369 0 : if (shadow != NS_STYLE_WINDOW_SHADOW_DEFAULT)
370 0 : return shadow;
371 :
372 0 : switch (StyleDisplay()->mAppearance) {
373 : case NS_THEME_TOOLTIP:
374 0 : return NS_STYLE_WINDOW_SHADOW_TOOLTIP;
375 : case NS_THEME_MENUPOPUP:
376 0 : return NS_STYLE_WINDOW_SHADOW_MENU;
377 : }
378 0 : return NS_STYLE_WINDOW_SHADOW_DEFAULT;
379 : }
380 :
381 0 : NS_IMETHODIMP nsXULPopupShownEvent::Run()
382 : {
383 0 : nsMenuPopupFrame* popup = do_QueryFrame(mPopup->GetPrimaryFrame());
384 : // Set the state to visible if the popup is still open.
385 0 : if (popup && popup->IsOpen()) {
386 0 : popup->SetPopupState(ePopupShown);
387 : }
388 :
389 : WidgetMouseEvent event(true, eXULPopupShown, nullptr,
390 0 : WidgetMouseEvent::eReal);
391 0 : return EventDispatcher::Dispatch(mPopup, mPresContext, &event);
392 : }
393 :
394 0 : NS_IMETHODIMP nsXULPopupShownEvent::HandleEvent(nsIDOMEvent* aEvent)
395 : {
396 0 : nsMenuPopupFrame* popup = do_QueryFrame(mPopup->GetPrimaryFrame());
397 0 : nsCOMPtr<nsIDOMEventTarget> eventTarget;
398 0 : aEvent->GetTarget(getter_AddRefs(eventTarget));
399 : // Ignore events not targeted at the popup itself (ie targeted at
400 : // descendants):
401 0 : if (!SameCOMIdentity(mPopup, eventTarget)) {
402 0 : return NS_OK;
403 : }
404 0 : if (popup) {
405 : // ResetPopupShownDispatcher will delete the reference to this, so keep
406 : // another one until Run is finished.
407 0 : RefPtr<nsXULPopupShownEvent> event = this;
408 : // Only call Run if it the dispatcher was assigned. This avoids calling the
409 : // Run method if the transitionend event fires multiple times.
410 0 : if (popup->ClearPopupShownDispatcher()) {
411 0 : return Run();
412 : }
413 : }
414 :
415 0 : CancelListener();
416 0 : return NS_OK;
417 : }
418 :
419 0 : void nsXULPopupShownEvent::CancelListener()
420 : {
421 0 : mPopup->RemoveSystemEventListener(NS_LITERAL_STRING("transitionend"), this, false);
422 0 : }
423 :
424 0 : NS_IMPL_ISUPPORTS_INHERITED(nsXULPopupShownEvent, Runnable, nsIDOMEventListener);
425 :
426 : void
427 44 : nsMenuPopupFrame::SetInitialChildList(ChildListID aListID,
428 : nsFrameList& aChildList)
429 : {
430 : // unless the list is empty, indicate that children have been generated.
431 44 : if (aListID == kPrincipalList && aChildList.NotEmpty()) {
432 0 : mGeneratedChildren = true;
433 : }
434 44 : nsBoxFrame::SetInitialChildList(aListID, aChildList);
435 44 : }
436 :
437 : bool
438 88 : nsMenuPopupFrame::IsLeafDynamic() const
439 : {
440 88 : if (mGeneratedChildren)
441 0 : return false;
442 :
443 88 : if (mPopupType != ePopupTypeMenu) {
444 : // any panel with a type attribute, such as the autocomplete popup,
445 : // is always generated right away.
446 20 : return !mContent->HasAttr(kNameSpaceID_None, nsGkAtoms::type);
447 : }
448 :
449 : // menu popups generate their child frames lazily only when opened, so
450 : // behave like a leaf frame. However, generate child frames normally if
451 : // the parent menu has a sizetopopup attribute. In this case the size of
452 : // the parent menu is dependent on the size of the popup, so the frames
453 : // need to exist in order to calculate this size.
454 68 : nsIContent* parentContent = mContent->GetParent();
455 136 : return (parentContent &&
456 136 : !parentContent->HasAttr(kNameSpaceID_None, nsGkAtoms::sizetopopup));
457 : }
458 :
459 : void
460 0 : nsMenuPopupFrame::UpdateWidgetProperties()
461 : {
462 0 : if (nsIWidget* widget = GetWidget()) {
463 0 : widget->SetWindowOpacity(StyleUIReset()->mWindowOpacity);
464 0 : widget->SetWindowTransform(ComputeWidgetTransform());
465 : }
466 0 : }
467 :
468 : void
469 105 : nsMenuPopupFrame::LayoutPopup(nsBoxLayoutState& aState, nsIFrame* aParentMenu,
470 : nsIFrame* aAnchor, bool aSizedToPopup)
471 : {
472 105 : if (!mGeneratedChildren)
473 210 : return;
474 :
475 0 : SchedulePaint();
476 :
477 0 : bool shouldPosition = true;
478 0 : bool isOpen = IsOpen();
479 0 : if (!isOpen) {
480 : // if the popup is not open, only do layout while showing or if the menu
481 : // is sized to the popup
482 0 : shouldPosition = (mPopupState == ePopupShowing || mPopupState == ePopupPositioning);
483 0 : if (!shouldPosition && !aSizedToPopup) {
484 0 : RemoveStateBits(NS_FRAME_FIRST_REFLOW);
485 0 : return;
486 : }
487 : }
488 :
489 : // if the popup has just been opened, make sure the scrolled window is at 0,0
490 : // Don't scroll menulists as they will scroll to their selected item on their own.
491 0 : if (mIsOpenChanged && !IsMenuList()) {
492 0 : nsIScrollableFrame *scrollframe = do_QueryFrame(nsBox::GetChildXULBox(this));
493 0 : if (scrollframe) {
494 0 : AutoWeakFrame weakFrame(this);
495 0 : scrollframe->ScrollTo(nsPoint(0,0), nsIScrollableFrame::INSTANT);
496 0 : if (!weakFrame.IsAlive()) {
497 0 : return;
498 : }
499 : }
500 : }
501 :
502 : // get the preferred, minimum and maximum size. If the menu is sized to the
503 : // popup, then the popup's width is the menu's width.
504 0 : nsSize prefSize = GetXULPrefSize(aState);
505 0 : nsSize minSize = GetXULMinSize(aState);
506 0 : nsSize maxSize = GetXULMaxSize(aState);
507 :
508 0 : if (aSizedToPopup) {
509 0 : prefSize.width = aParentMenu->GetRect().width;
510 : }
511 0 : prefSize = BoundsCheck(minSize, prefSize, maxSize);
512 :
513 : // if the size changed then set the bounds to be the preferred size
514 0 : bool sizeChanged = (mPrefSize != prefSize);
515 0 : if (sizeChanged) {
516 0 : SetXULBounds(aState, nsRect(0, 0, prefSize.width, prefSize.height), false);
517 0 : mPrefSize = prefSize;
518 : }
519 :
520 0 : bool needCallback = false;
521 0 : if (shouldPosition) {
522 0 : SetPopupPosition(aAnchor, false, aSizedToPopup, mPopupState == ePopupPositioning);
523 0 : needCallback = true;
524 : }
525 :
526 0 : nsRect bounds(GetRect());
527 0 : XULLayout(aState);
528 :
529 : // if the width or height changed, readjust the popup position. This is a
530 : // special case for tooltips where the preferred height doesn't include the
531 : // real height for its inline element, but does once it is laid out.
532 : // This is bug 228673 which doesn't have a simple fix.
533 0 : bool rePosition = shouldPosition && (mPosition == POPUPPOSITION_SELECTION);
534 0 : if (!aParentMenu) {
535 0 : nsSize newsize = GetSize();
536 0 : if (newsize.width > bounds.width || newsize.height > bounds.height) {
537 : // the size after layout was larger than the preferred size,
538 : // so set the preferred size accordingly
539 0 : mPrefSize = newsize;
540 0 : if (isOpen) {
541 0 : rePosition = true;
542 0 : needCallback = true;
543 : }
544 : }
545 : }
546 :
547 0 : if (rePosition) {
548 0 : SetPopupPosition(aAnchor, false, aSizedToPopup, false);
549 : }
550 :
551 0 : nsPresContext* pc = PresContext();
552 0 : nsView* view = GetView();
553 :
554 0 : if (sizeChanged) {
555 : // If the size of the popup changed, apply any size constraints.
556 0 : nsIWidget* widget = view->GetWidget();
557 0 : if (widget) {
558 0 : SetSizeConstraints(pc, widget, minSize, maxSize);
559 : }
560 : }
561 :
562 0 : if (isOpen) {
563 0 : nsViewManager* viewManager = view->GetViewManager();
564 0 : nsRect rect = GetRect();
565 0 : rect.x = rect.y = 0;
566 0 : viewManager->ResizeView(view, rect);
567 :
568 0 : if (mPopupState == ePopupOpening) {
569 0 : mPopupState = ePopupVisible;
570 : }
571 :
572 0 : viewManager->SetViewVisibility(view, nsViewVisibility_kShow);
573 0 : SyncFrameViewProperties(view);
574 : }
575 :
576 : // finally, if the popup just opened, send a popupshown event
577 0 : if (mIsOpenChanged) {
578 0 : mIsOpenChanged = false;
579 :
580 : // Make sure the current selection in a menulist is visible.
581 0 : if (IsMenuList() && mCurrentMenu) {
582 0 : EnsureMenuItemIsVisible(mCurrentMenu);
583 : }
584 :
585 : #ifndef MOZ_WIDGET_GTK
586 : // If the animate attribute is set to open, check for a transition and wait
587 : // for it to finish before firing the popupshown event.
588 : if (mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::animate,
589 : nsGkAtoms::open, eCaseMatters) &&
590 : nsLayoutUtils::HasCurrentTransitions(this)) {
591 : mPopupShownDispatcher = new nsXULPopupShownEvent(mContent, pc);
592 : mContent->AddSystemEventListener(NS_LITERAL_STRING("transitionend"),
593 : mPopupShownDispatcher, false, false);
594 : return;
595 : }
596 : #endif
597 :
598 : // If there are no transitions, fire the popupshown event right away.
599 0 : nsCOMPtr<nsIRunnable> event = new nsXULPopupShownEvent(GetContent(), pc);
600 0 : mContent->OwnerDoc()->Dispatch("nsXULPopupShownEvent",
601 : TaskCategory::Other,
602 0 : event.forget());
603 : }
604 :
605 0 : if (needCallback && !mReflowCallbackData.mPosted) {
606 0 : pc->PresShell()->PostReflowCallback(this);
607 0 : mReflowCallbackData.MarkPosted(aAnchor, aSizedToPopup);
608 : }
609 : }
610 :
611 : bool
612 0 : nsMenuPopupFrame::ReflowFinished()
613 : {
614 0 : SetPopupPosition(mReflowCallbackData.mAnchor, false, mReflowCallbackData.mSizedToPopup, true);
615 :
616 0 : mReflowCallbackData.Clear();
617 :
618 0 : return false;
619 : }
620 :
621 : void
622 0 : nsMenuPopupFrame::ReflowCallbackCanceled()
623 : {
624 0 : mReflowCallbackData.Clear();
625 0 : }
626 :
627 : bool
628 0 : nsMenuPopupFrame::IsMenuList()
629 : {
630 0 : nsIFrame* parentMenu = GetParent();
631 0 : if (!parentMenu) {
632 0 : return false;
633 : }
634 :
635 0 : nsCOMPtr<nsIDOMXULMenuListElement> menulist = do_QueryInterface(parentMenu->GetContent());
636 0 : return menulist != nullptr;
637 : }
638 :
639 : nsIContent*
640 1 : nsMenuPopupFrame::GetTriggerContent(nsMenuPopupFrame* aMenuPopupFrame)
641 : {
642 1 : while (aMenuPopupFrame) {
643 1 : if (aMenuPopupFrame->mTriggerContent)
644 0 : return aMenuPopupFrame->mTriggerContent;
645 :
646 : // check up the menu hierarchy until a popup with a trigger node is found
647 1 : nsMenuFrame* menuFrame = do_QueryFrame(aMenuPopupFrame->GetParent());
648 1 : if (!menuFrame)
649 1 : break;
650 :
651 0 : nsMenuParent* parentPopup = menuFrame->GetMenuParent();
652 0 : if (!parentPopup || !parentPopup->IsMenu())
653 0 : break;
654 :
655 0 : aMenuPopupFrame = static_cast<nsMenuPopupFrame *>(parentPopup);
656 : }
657 :
658 1 : return nullptr;
659 : }
660 :
661 : void
662 0 : nsMenuPopupFrame::InitPositionFromAnchorAlign(const nsAString& aAnchor,
663 : const nsAString& aAlign)
664 : {
665 0 : mTriggerContent = nullptr;
666 :
667 0 : if (aAnchor.EqualsLiteral("topleft"))
668 0 : mPopupAnchor = POPUPALIGNMENT_TOPLEFT;
669 0 : else if (aAnchor.EqualsLiteral("topright"))
670 0 : mPopupAnchor = POPUPALIGNMENT_TOPRIGHT;
671 0 : else if (aAnchor.EqualsLiteral("bottomleft"))
672 0 : mPopupAnchor = POPUPALIGNMENT_BOTTOMLEFT;
673 0 : else if (aAnchor.EqualsLiteral("bottomright"))
674 0 : mPopupAnchor = POPUPALIGNMENT_BOTTOMRIGHT;
675 0 : else if (aAnchor.EqualsLiteral("leftcenter"))
676 0 : mPopupAnchor = POPUPALIGNMENT_LEFTCENTER;
677 0 : else if (aAnchor.EqualsLiteral("rightcenter"))
678 0 : mPopupAnchor = POPUPALIGNMENT_RIGHTCENTER;
679 0 : else if (aAnchor.EqualsLiteral("topcenter"))
680 0 : mPopupAnchor = POPUPALIGNMENT_TOPCENTER;
681 0 : else if (aAnchor.EqualsLiteral("bottomcenter"))
682 0 : mPopupAnchor = POPUPALIGNMENT_BOTTOMCENTER;
683 : else
684 0 : mPopupAnchor = POPUPALIGNMENT_NONE;
685 :
686 0 : if (aAlign.EqualsLiteral("topleft"))
687 0 : mPopupAlignment = POPUPALIGNMENT_TOPLEFT;
688 0 : else if (aAlign.EqualsLiteral("topright"))
689 0 : mPopupAlignment = POPUPALIGNMENT_TOPRIGHT;
690 0 : else if (aAlign.EqualsLiteral("bottomleft"))
691 0 : mPopupAlignment = POPUPALIGNMENT_BOTTOMLEFT;
692 0 : else if (aAlign.EqualsLiteral("bottomright"))
693 0 : mPopupAlignment = POPUPALIGNMENT_BOTTOMRIGHT;
694 : else
695 0 : mPopupAlignment = POPUPALIGNMENT_NONE;
696 :
697 0 : mPosition = POPUPPOSITION_UNKNOWN;
698 0 : }
699 :
700 : void
701 0 : nsMenuPopupFrame::InitializePopup(nsIContent* aAnchorContent,
702 : nsIContent* aTriggerContent,
703 : const nsAString& aPosition,
704 : int32_t aXPos, int32_t aYPos,
705 : MenuPopupAnchorType aAnchorType,
706 : bool aAttributesOverride)
707 : {
708 0 : EnsureWidget();
709 :
710 0 : mPopupState = ePopupShowing;
711 0 : mAnchorContent = aAnchorContent;
712 0 : mTriggerContent = aTriggerContent;
713 0 : mXPos = aXPos;
714 0 : mYPos = aYPos;
715 0 : mAdjustOffsetForContextMenu = false;
716 0 : mVFlip = false;
717 0 : mHFlip = false;
718 0 : mAlignmentOffset = 0;
719 :
720 0 : mAnchorType = aAnchorType;
721 :
722 : // if aAttributesOverride is true, then the popupanchor, popupalign and
723 : // position attributes on the <popup> override those values passed in.
724 : // If false, those attributes are only used if the values passed in are empty
725 0 : if (aAnchorContent || aAnchorType == MenuPopupAnchorType_Rect) {
726 0 : nsAutoString anchor, align, position, flip;
727 0 : mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::popupanchor, anchor);
728 0 : mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::popupalign, align);
729 0 : mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::position, position);
730 0 : mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::flip, flip);
731 :
732 0 : if (aAttributesOverride) {
733 : // if the attributes are set, clear the offset position. Otherwise,
734 : // the offset is used to adjust the position from the anchor point
735 0 : if (anchor.IsEmpty() && align.IsEmpty() && position.IsEmpty())
736 0 : position.Assign(aPosition);
737 : else
738 0 : mXPos = mYPos = 0;
739 : }
740 0 : else if (!aPosition.IsEmpty()) {
741 0 : position.Assign(aPosition);
742 : }
743 :
744 0 : if (flip.EqualsLiteral("none")) {
745 0 : mFlip = FlipType_None;
746 0 : } else if (flip.EqualsLiteral("both")) {
747 0 : mFlip = FlipType_Both;
748 0 : } else if (flip.EqualsLiteral("slide")) {
749 0 : mFlip = FlipType_Slide;
750 : }
751 :
752 0 : position.CompressWhitespace();
753 0 : int32_t spaceIdx = position.FindChar(' ');
754 : // if there is a space in the position, assume it is the anchor and
755 : // alignment as two separate tokens.
756 0 : if (spaceIdx >= 0) {
757 0 : InitPositionFromAnchorAlign(Substring(position, 0, spaceIdx), Substring(position, spaceIdx + 1));
758 : }
759 0 : else if (position.EqualsLiteral("before_start")) {
760 0 : mPopupAnchor = POPUPALIGNMENT_TOPLEFT;
761 0 : mPopupAlignment = POPUPALIGNMENT_BOTTOMLEFT;
762 0 : mPosition = POPUPPOSITION_BEFORESTART;
763 : }
764 0 : else if (position.EqualsLiteral("before_end")) {
765 0 : mPopupAnchor = POPUPALIGNMENT_TOPRIGHT;
766 0 : mPopupAlignment = POPUPALIGNMENT_BOTTOMRIGHT;
767 0 : mPosition = POPUPPOSITION_BEFOREEND;
768 : }
769 0 : else if (position.EqualsLiteral("after_start")) {
770 0 : mPopupAnchor = POPUPALIGNMENT_BOTTOMLEFT;
771 0 : mPopupAlignment = POPUPALIGNMENT_TOPLEFT;
772 0 : mPosition = POPUPPOSITION_AFTERSTART;
773 : }
774 0 : else if (position.EqualsLiteral("after_end")) {
775 0 : mPopupAnchor = POPUPALIGNMENT_BOTTOMRIGHT;
776 0 : mPopupAlignment = POPUPALIGNMENT_TOPRIGHT;
777 0 : mPosition = POPUPPOSITION_AFTEREND;
778 : }
779 0 : else if (position.EqualsLiteral("start_before")) {
780 0 : mPopupAnchor = POPUPALIGNMENT_TOPLEFT;
781 0 : mPopupAlignment = POPUPALIGNMENT_TOPRIGHT;
782 0 : mPosition = POPUPPOSITION_STARTBEFORE;
783 : }
784 0 : else if (position.EqualsLiteral("start_after")) {
785 0 : mPopupAnchor = POPUPALIGNMENT_BOTTOMLEFT;
786 0 : mPopupAlignment = POPUPALIGNMENT_BOTTOMRIGHT;
787 0 : mPosition = POPUPPOSITION_STARTAFTER;
788 : }
789 0 : else if (position.EqualsLiteral("end_before")) {
790 0 : mPopupAnchor = POPUPALIGNMENT_TOPRIGHT;
791 0 : mPopupAlignment = POPUPALIGNMENT_TOPLEFT;
792 0 : mPosition = POPUPPOSITION_ENDBEFORE;
793 : }
794 0 : else if (position.EqualsLiteral("end_after")) {
795 0 : mPopupAnchor = POPUPALIGNMENT_BOTTOMRIGHT;
796 0 : mPopupAlignment = POPUPALIGNMENT_BOTTOMLEFT;
797 0 : mPosition = POPUPPOSITION_ENDAFTER;
798 : }
799 0 : else if (position.EqualsLiteral("overlap")) {
800 0 : mPopupAnchor = POPUPALIGNMENT_TOPLEFT;
801 0 : mPopupAlignment = POPUPALIGNMENT_TOPLEFT;
802 0 : mPosition = POPUPPOSITION_OVERLAP;
803 : }
804 0 : else if (position.EqualsLiteral("after_pointer")) {
805 0 : mPopupAnchor = POPUPALIGNMENT_TOPLEFT;
806 0 : mPopupAlignment = POPUPALIGNMENT_TOPLEFT;
807 0 : mPosition = POPUPPOSITION_AFTERPOINTER;
808 : // XXXndeakin this is supposed to anchor vertically after, but with the
809 : // horizontal position as the mouse pointer.
810 0 : mYPos += 21;
811 : }
812 0 : else if (position.EqualsLiteral("selection")) {
813 0 : mPopupAnchor = POPUPALIGNMENT_BOTTOMLEFT;
814 0 : mPopupAlignment = POPUPALIGNMENT_TOPLEFT;
815 0 : mPosition = POPUPPOSITION_SELECTION;
816 : }
817 : else {
818 0 : InitPositionFromAnchorAlign(anchor, align);
819 : }
820 : }
821 :
822 0 : mScreenRect = nsIntRect(-1, -1, 0, 0);
823 :
824 0 : if (aAttributesOverride) {
825 : // Use |left| and |top| dimension attributes to position the popup if
826 : // present, as they may have been persisted.
827 0 : nsAutoString left, top;
828 0 : mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::left, left);
829 0 : mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::top, top);
830 :
831 : nsresult err;
832 0 : if (!left.IsEmpty()) {
833 0 : int32_t x = left.ToInteger(&err);
834 0 : if (NS_SUCCEEDED(err))
835 0 : mScreenRect.x = x;
836 : }
837 0 : if (!top.IsEmpty()) {
838 0 : int32_t y = top.ToInteger(&err);
839 0 : if (NS_SUCCEEDED(err))
840 0 : mScreenRect.y = y;
841 : }
842 : }
843 0 : }
844 :
845 : void
846 0 : nsMenuPopupFrame::InitializePopupAtScreen(nsIContent* aTriggerContent,
847 : int32_t aXPos, int32_t aYPos,
848 : bool aIsContextMenu)
849 : {
850 0 : EnsureWidget();
851 :
852 0 : mPopupState = ePopupShowing;
853 0 : mAnchorContent = nullptr;
854 0 : mTriggerContent = aTriggerContent;
855 0 : mScreenRect = nsIntRect(aXPos, aYPos, 0, 0);
856 0 : mXPos = 0;
857 0 : mYPos = 0;
858 0 : mFlip = FlipType_Default;
859 0 : mPopupAnchor = POPUPALIGNMENT_NONE;
860 0 : mPopupAlignment = POPUPALIGNMENT_NONE;
861 0 : mPosition = POPUPPOSITION_UNKNOWN;
862 0 : mIsContextMenu = aIsContextMenu;
863 0 : mAdjustOffsetForContextMenu = aIsContextMenu;
864 0 : mAnchorType = MenuPopupAnchorType_Point;
865 0 : }
866 :
867 : void
868 0 : nsMenuPopupFrame::InitializePopupAtRect(nsIContent* aTriggerContent,
869 : const nsAString& aPosition,
870 : const nsIntRect& aRect,
871 : bool aAttributesOverride)
872 : {
873 0 : InitializePopup(nullptr, aTriggerContent, aPosition, 0, 0,
874 0 : MenuPopupAnchorType_Rect, aAttributesOverride);
875 0 : mScreenRect = aRect;
876 0 : }
877 :
878 : void
879 0 : nsMenuPopupFrame::InitializePopupWithAnchorAlign(nsIContent* aAnchorContent,
880 : nsAString& aAnchor,
881 : nsAString& aAlign,
882 : int32_t aXPos, int32_t aYPos)
883 : {
884 0 : EnsureWidget();
885 :
886 0 : mPopupState = ePopupShowing;
887 0 : mAdjustOffsetForContextMenu = false;
888 0 : mFlip = FlipType_Default;
889 :
890 : // this popup opening function is provided for backwards compatibility
891 : // only. It accepts either coordinates or an anchor and alignment value
892 : // but doesn't use both together.
893 0 : if (aXPos == -1 && aYPos == -1) {
894 0 : mAnchorContent = aAnchorContent;
895 0 : mAnchorType = MenuPopupAnchorType_Node;
896 0 : mScreenRect = nsIntRect(-1, -1, 0, 0);
897 0 : mXPos = 0;
898 0 : mYPos = 0;
899 0 : InitPositionFromAnchorAlign(aAnchor, aAlign);
900 : }
901 : else {
902 0 : mAnchorContent = nullptr;
903 0 : mAnchorType = MenuPopupAnchorType_Point;
904 0 : mPopupAnchor = POPUPALIGNMENT_NONE;
905 0 : mPopupAlignment = POPUPALIGNMENT_NONE;
906 0 : mPosition = POPUPPOSITION_UNKNOWN;
907 0 : mScreenRect = nsIntRect(aXPos, aYPos, 0, 0);
908 0 : mXPos = aXPos;
909 0 : mYPos = aYPos;
910 : }
911 0 : }
912 :
913 : void
914 0 : nsMenuPopupFrame::ShowPopup(bool aIsContextMenu)
915 : {
916 0 : mIsContextMenu = aIsContextMenu;
917 :
918 0 : InvalidateFrameSubtree();
919 :
920 0 : if (mPopupState == ePopupShowing || mPopupState == ePopupPositioning) {
921 0 : mPopupState = ePopupOpening;
922 0 : mIsOpenChanged = true;
923 :
924 : // Clear mouse capture when a popup is opened.
925 0 : if (mPopupType == ePopupTypeMenu) {
926 : EventStateManager* activeESM =
927 : static_cast<EventStateManager*>(
928 0 : EventStateManager::GetActiveEventStateManager());
929 0 : if (activeESM) {
930 0 : EventStateManager::ClearGlobalActiveContent(activeESM);
931 : }
932 :
933 0 : nsIPresShell::SetCapturingContent(nullptr, 0);
934 : }
935 :
936 0 : nsMenuFrame* menuFrame = do_QueryFrame(GetParent());
937 0 : if (menuFrame) {
938 0 : AutoWeakFrame weakFrame(this);
939 0 : menuFrame->PopupOpened();
940 0 : if (!weakFrame.IsAlive())
941 0 : return;
942 : }
943 :
944 : // do we need an actual reflow here?
945 : // is SetPopupPosition all that is needed?
946 0 : PresContext()->PresShell()->FrameNeedsReflow(this, nsIPresShell::eTreeChange,
947 0 : NS_FRAME_HAS_DIRTY_CHILDREN);
948 :
949 0 : if (mPopupType == ePopupTypeMenu) {
950 0 : nsCOMPtr<nsISound> sound(do_CreateInstance("@mozilla.org/sound;1"));
951 0 : if (sound)
952 0 : sound->PlayEventSound(nsISound::EVENT_MENU_POPUP);
953 : }
954 : }
955 :
956 0 : mShouldAutoPosition = true;
957 : }
958 :
959 : void
960 0 : nsMenuPopupFrame::HidePopup(bool aDeselectMenu, nsPopupState aNewState)
961 : {
962 0 : NS_ASSERTION(aNewState == ePopupClosed || aNewState == ePopupInvisible,
963 : "popup being set to unexpected state");
964 :
965 0 : ClearPopupShownDispatcher();
966 :
967 : // don't hide the popup when it isn't open
968 0 : if (mPopupState == ePopupClosed || mPopupState == ePopupShowing ||
969 0 : mPopupState == ePopupPositioning)
970 0 : return;
971 :
972 : // clear the trigger content if the popup is being closed. But don't clear
973 : // it if the popup is just being made invisible as a popuphiding or command
974 : // event may want to retrieve it.
975 0 : if (aNewState == ePopupClosed) {
976 : // if the popup had a trigger node set, clear the global window popup node
977 : // as well
978 0 : if (mTriggerContent) {
979 0 : nsIDocument* doc = mContent->GetUncomposedDoc();
980 0 : if (doc) {
981 0 : if (nsPIDOMWindowOuter* win = doc->GetWindow()) {
982 0 : nsCOMPtr<nsPIWindowRoot> root = win->GetTopWindowRoot();
983 0 : if (root) {
984 0 : root->SetPopupNode(nullptr);
985 : }
986 : }
987 : }
988 : }
989 0 : mTriggerContent = nullptr;
990 0 : mAnchorContent = nullptr;
991 : }
992 :
993 : // when invisible and about to be closed, HidePopup has already been called,
994 : // so just set the new state to closed and return
995 0 : if (mPopupState == ePopupInvisible) {
996 0 : if (aNewState == ePopupClosed)
997 0 : mPopupState = ePopupClosed;
998 0 : return;
999 : }
1000 :
1001 0 : mPopupState = aNewState;
1002 :
1003 0 : if (IsMenu())
1004 0 : SetCurrentMenuItem(nullptr);
1005 :
1006 0 : mIncrementalString.Truncate();
1007 :
1008 0 : LockMenuUntilClosed(false);
1009 :
1010 0 : mIsOpenChanged = false;
1011 0 : mCurrentMenu = nullptr; // make sure no current menu is set
1012 0 : mHFlip = mVFlip = false;
1013 :
1014 0 : nsView* view = GetView();
1015 0 : nsViewManager* viewManager = view->GetViewManager();
1016 0 : viewManager->SetViewVisibility(view, nsViewVisibility_kHide);
1017 :
1018 0 : FireDOMEvent(NS_LITERAL_STRING("DOMMenuInactive"), mContent);
1019 :
1020 : // XXX, bug 137033, In Windows, if mouse is outside the window when the menupopup closes, no
1021 : // mouse_enter/mouse_exit event will be fired to clear current hover state, we should clear it manually.
1022 : // This code may not the best solution, but we can leave it here until we find the better approach.
1023 0 : NS_ASSERTION(mContent->IsElement(), "How do we have a non-element?");
1024 0 : EventStates state = mContent->AsElement()->State();
1025 :
1026 0 : if (state.HasState(NS_EVENT_STATE_HOVER)) {
1027 0 : EventStateManager* esm = PresContext()->EventStateManager();
1028 0 : esm->SetContentState(nullptr, NS_EVENT_STATE_HOVER);
1029 : }
1030 :
1031 0 : nsMenuFrame* menuFrame = do_QueryFrame(GetParent());
1032 0 : if (menuFrame) {
1033 0 : menuFrame->PopupClosed(aDeselectMenu);
1034 : }
1035 : }
1036 :
1037 : uint32_t
1038 0 : nsMenuPopupFrame::GetXULLayoutFlags()
1039 : {
1040 0 : return NS_FRAME_NO_SIZE_VIEW | NS_FRAME_NO_MOVE_VIEW | NS_FRAME_NO_VISIBILITY;
1041 : }
1042 :
1043 : ///////////////////////////////////////////////////////////////////////////////
1044 : // GetRootViewForPopup
1045 : // Retrieves the view for the popup widget that contains the given frame.
1046 : // If the given frame is not contained by a popup widget, return the
1047 : // root view of the root viewmanager.
1048 : nsView*
1049 0 : nsMenuPopupFrame::GetRootViewForPopup(nsIFrame* aStartFrame)
1050 : {
1051 0 : nsView* view = aStartFrame->GetClosestView();
1052 0 : NS_ASSERTION(view, "frame must have a closest view!");
1053 0 : while (view) {
1054 : // Walk up the view hierarchy looking for a view whose widget has a
1055 : // window type of eWindowType_popup - in other words a popup window
1056 : // widget. If we find one, this is the view we want.
1057 0 : nsIWidget* widget = view->GetWidget();
1058 0 : if (widget && widget->WindowType() == eWindowType_popup) {
1059 0 : return view;
1060 : }
1061 :
1062 0 : nsView* temp = view->GetParent();
1063 0 : if (!temp) {
1064 : // Otherwise, we've walked all the way up to the root view and not
1065 : // found a view for a popup window widget. Just return the root view.
1066 0 : return view;
1067 : }
1068 0 : view = temp;
1069 : }
1070 :
1071 0 : return nullptr;
1072 : }
1073 :
1074 : nsPoint
1075 0 : nsMenuPopupFrame::AdjustPositionForAnchorAlign(nsRect& anchorRect,
1076 : FlipStyle& aHFlip, FlipStyle& aVFlip)
1077 : {
1078 : // flip the anchor and alignment for right-to-left
1079 0 : int8_t popupAnchor(mPopupAnchor);
1080 0 : int8_t popupAlign(mPopupAlignment);
1081 0 : if (IsDirectionRTL()) {
1082 : // no need to flip the centered anchor types vertically
1083 0 : if (popupAnchor <= POPUPALIGNMENT_LEFTCENTER) {
1084 0 : popupAnchor = -popupAnchor;
1085 : }
1086 0 : popupAlign = -popupAlign;
1087 : }
1088 :
1089 0 : nsRect originalAnchorRect(anchorRect);
1090 :
1091 : // first, determine at which corner of the anchor the popup should appear
1092 0 : nsPoint pnt;
1093 0 : switch (popupAnchor) {
1094 : case POPUPALIGNMENT_LEFTCENTER:
1095 0 : pnt = nsPoint(anchorRect.x, anchorRect.y + anchorRect.height / 2);
1096 0 : anchorRect.y = pnt.y;
1097 0 : anchorRect.height = 0;
1098 0 : break;
1099 : case POPUPALIGNMENT_RIGHTCENTER:
1100 0 : pnt = nsPoint(anchorRect.XMost(), anchorRect.y + anchorRect.height / 2);
1101 0 : anchorRect.y = pnt.y;
1102 0 : anchorRect.height = 0;
1103 0 : break;
1104 : case POPUPALIGNMENT_TOPCENTER:
1105 0 : pnt = nsPoint(anchorRect.x + anchorRect.width / 2, anchorRect.y);
1106 0 : anchorRect.x = pnt.x;
1107 0 : anchorRect.width = 0;
1108 0 : break;
1109 : case POPUPALIGNMENT_BOTTOMCENTER:
1110 0 : pnt = nsPoint(anchorRect.x + anchorRect.width / 2, anchorRect.YMost());
1111 0 : anchorRect.x = pnt.x;
1112 0 : anchorRect.width = 0;
1113 0 : break;
1114 : case POPUPALIGNMENT_TOPRIGHT:
1115 0 : pnt = anchorRect.TopRight();
1116 0 : break;
1117 : case POPUPALIGNMENT_BOTTOMLEFT:
1118 0 : pnt = anchorRect.BottomLeft();
1119 0 : break;
1120 : case POPUPALIGNMENT_BOTTOMRIGHT:
1121 0 : pnt = anchorRect.BottomRight();
1122 0 : break;
1123 : case POPUPALIGNMENT_TOPLEFT:
1124 : default:
1125 0 : pnt = anchorRect.TopLeft();
1126 0 : break;
1127 : }
1128 :
1129 : // If the alignment is on the right edge of the popup, move the popup left
1130 : // by the width. Similarly, if the alignment is on the bottom edge of the
1131 : // popup, move the popup up by the height. In addition, account for the
1132 : // margins of the popup on the edge on which it is aligned.
1133 0 : nsMargin margin(0, 0, 0, 0);
1134 0 : StyleMargin()->GetMargin(margin);
1135 0 : switch (popupAlign) {
1136 : case POPUPALIGNMENT_TOPRIGHT:
1137 0 : pnt.MoveBy(-mRect.width - margin.right, margin.top);
1138 0 : break;
1139 : case POPUPALIGNMENT_BOTTOMLEFT:
1140 0 : pnt.MoveBy(margin.left, -mRect.height - margin.bottom);
1141 0 : break;
1142 : case POPUPALIGNMENT_BOTTOMRIGHT:
1143 0 : pnt.MoveBy(-mRect.width - margin.right, -mRect.height - margin.bottom);
1144 0 : break;
1145 : case POPUPALIGNMENT_TOPLEFT:
1146 : default:
1147 0 : pnt.MoveBy(margin.left, margin.top);
1148 0 : break;
1149 : }
1150 :
1151 : // If we aligning to the selected item in the popup, adjust the vertical
1152 : // position by the height of the menulist label and the selected item's
1153 : // position.
1154 0 : if (mPosition == POPUPPOSITION_SELECTION) {
1155 0 : MOZ_ASSERT(popupAnchor == POPUPALIGNMENT_BOTTOMLEFT ||
1156 : popupAnchor == POPUPALIGNMENT_BOTTOMRIGHT);
1157 0 : MOZ_ASSERT(popupAlign == POPUPALIGNMENT_TOPLEFT ||
1158 : popupAlign == POPUPALIGNMENT_TOPRIGHT);
1159 :
1160 0 : nsIFrame* selectedItemFrame = GetSelectedItemForAlignment();
1161 0 : if (selectedItemFrame) {
1162 0 : int32_t scrolly = 0;
1163 0 : nsIScrollableFrame *scrollframe = do_QueryFrame(nsBox::GetChildXULBox(this));
1164 0 : if (scrollframe) {
1165 0 : scrolly = scrollframe->GetScrollPosition().y;
1166 : }
1167 :
1168 0 : pnt.y -= originalAnchorRect.height + selectedItemFrame->GetRect().y - scrolly;
1169 : }
1170 : }
1171 :
1172 : // Flipping horizontally is allowed as long as the popup is above or below
1173 : // the anchor. This will happen if both the anchor and alignment are top or
1174 : // both are bottom, but different values. Similarly, flipping vertically is
1175 : // allowed if the popup is to the left or right of the anchor. In this case,
1176 : // the values of the constants are such that both must be positive or both
1177 : // must be negative. A special case, used for overlap, allows flipping
1178 : // vertically as well.
1179 : // If we are flipping in both directions, we want to set a flip style both
1180 : // horizontally and vertically. However, we want to flip on the inside edge
1181 : // of the anchor. Consider the example of a typical dropdown menu.
1182 : // Vertically, we flip the popup on the outside edges of the anchor menu,
1183 : // however horizontally, we want to to use the inside edges so the popup
1184 : // still appears underneath the anchor menu instead of floating off the
1185 : // side of the menu.
1186 0 : switch (popupAnchor) {
1187 : case POPUPALIGNMENT_LEFTCENTER:
1188 : case POPUPALIGNMENT_RIGHTCENTER:
1189 0 : aHFlip = FlipStyle_Outside;
1190 0 : aVFlip = FlipStyle_Inside;
1191 0 : break;
1192 : case POPUPALIGNMENT_TOPCENTER:
1193 : case POPUPALIGNMENT_BOTTOMCENTER:
1194 0 : aHFlip = FlipStyle_Inside;
1195 0 : aVFlip = FlipStyle_Outside;
1196 0 : break;
1197 : default:
1198 : {
1199 0 : FlipStyle anchorEdge = mFlip == FlipType_Both ? FlipStyle_Inside : FlipStyle_None;
1200 0 : aHFlip = (popupAnchor == -popupAlign) ? FlipStyle_Outside : anchorEdge;
1201 0 : if (((popupAnchor > 0) == (popupAlign > 0)) ||
1202 0 : (popupAnchor == POPUPALIGNMENT_TOPLEFT && popupAlign == POPUPALIGNMENT_TOPLEFT))
1203 0 : aVFlip = FlipStyle_Outside;
1204 : else
1205 0 : aVFlip = anchorEdge;
1206 0 : break;
1207 : }
1208 : }
1209 :
1210 0 : return pnt;
1211 : }
1212 :
1213 0 : nsIFrame* nsMenuPopupFrame::GetSelectedItemForAlignment()
1214 : {
1215 : // This method adjusts a menulist's popup such that the selected item is under the cursor, aligned
1216 : // with the menulist label.
1217 0 : nsCOMPtr<nsIDOMXULSelectControlElement> select = do_QueryInterface(mAnchorContent);
1218 0 : if (!select) {
1219 : // If there isn't an anchor, then try just getting the parent of the popup.
1220 0 : select = do_QueryInterface(mContent->GetParent());
1221 0 : if (!select) {
1222 0 : return nullptr;
1223 : }
1224 : }
1225 :
1226 0 : nsCOMPtr<nsIDOMXULSelectControlItemElement> item;
1227 0 : select->GetSelectedItem(getter_AddRefs(item));
1228 :
1229 0 : nsCOMPtr<nsIContent> selectedElement = do_QueryInterface(item);
1230 0 : return selectedElement ? selectedElement->GetPrimaryFrame() : nullptr;
1231 : }
1232 :
1233 : nscoord
1234 0 : nsMenuPopupFrame::SlideOrResize(nscoord& aScreenPoint, nscoord aSize,
1235 : nscoord aScreenBegin, nscoord aScreenEnd,
1236 : nscoord *aOffset)
1237 : {
1238 : // The popup may be positioned such that either the left/top or bottom/right
1239 : // is outside the screen - but never both.
1240 : nscoord newPos =
1241 0 : std::max(aScreenBegin, std::min(aScreenEnd - aSize, aScreenPoint));
1242 0 : *aOffset = newPos - aScreenPoint;
1243 0 : aScreenPoint = newPos;
1244 0 : return std::min(aSize, aScreenEnd - aScreenPoint);
1245 : }
1246 :
1247 : nscoord
1248 0 : nsMenuPopupFrame::FlipOrResize(nscoord& aScreenPoint, nscoord aSize,
1249 : nscoord aScreenBegin, nscoord aScreenEnd,
1250 : nscoord aAnchorBegin, nscoord aAnchorEnd,
1251 : nscoord aMarginBegin, nscoord aMarginEnd,
1252 : nscoord aOffsetForContextMenu, FlipStyle aFlip,
1253 : bool aEndAligned, bool* aFlipSide)
1254 : {
1255 : // The flip side argument will be set to true if there wasn't room and we
1256 : // flipped to the opposite side.
1257 0 : *aFlipSide = false;
1258 :
1259 : // all of the coordinates used here are in app units relative to the screen
1260 0 : nscoord popupSize = aSize;
1261 0 : if (aScreenPoint < aScreenBegin) {
1262 : // at its current position, the popup would extend past the left or top
1263 : // edge of the screen, so it will have to be moved or resized.
1264 0 : if (aFlip) {
1265 : // for inside flips, we flip on the opposite side of the anchor
1266 0 : nscoord startpos = aFlip == FlipStyle_Outside ? aAnchorBegin : aAnchorEnd;
1267 0 : nscoord endpos = aFlip == FlipStyle_Outside ? aAnchorEnd : aAnchorBegin;
1268 :
1269 : // check whether there is more room to the left and right (or top and
1270 : // bottom) of the anchor and put the popup on the side with more room.
1271 0 : if (startpos - aScreenBegin >= aScreenEnd - endpos) {
1272 0 : aScreenPoint = aScreenBegin;
1273 0 : popupSize = startpos - aScreenPoint - aMarginEnd;
1274 0 : *aFlipSide = !aEndAligned;
1275 : }
1276 : else {
1277 : // If the newly calculated position is different than the existing
1278 : // position, flip such that the popup is to the right or bottom of the
1279 : // anchor point instead . However, when flipping use the same margin
1280 : // size.
1281 0 : nscoord newScreenPoint = endpos + aMarginEnd;
1282 0 : if (newScreenPoint != aScreenPoint) {
1283 0 : *aFlipSide = aEndAligned;
1284 0 : aScreenPoint = newScreenPoint;
1285 : // check if the new position is still off the right or bottom edge of
1286 : // the screen. If so, resize the popup.
1287 0 : if (aScreenPoint + aSize > aScreenEnd) {
1288 0 : popupSize = aScreenEnd - aScreenPoint;
1289 : }
1290 : }
1291 : }
1292 : }
1293 : else {
1294 0 : aScreenPoint = aScreenBegin;
1295 : }
1296 : }
1297 0 : else if (aScreenPoint + aSize > aScreenEnd) {
1298 : // at its current position, the popup would extend past the right or
1299 : // bottom edge of the screen, so it will have to be moved or resized.
1300 0 : if (aFlip) {
1301 : // for inside flips, we flip on the opposite side of the anchor
1302 0 : nscoord startpos = aFlip == FlipStyle_Outside ? aAnchorBegin : aAnchorEnd;
1303 0 : nscoord endpos = aFlip == FlipStyle_Outside ? aAnchorEnd : aAnchorBegin;
1304 :
1305 : // check whether there is more room to the left and right (or top and
1306 : // bottom) of the anchor and put the popup on the side with more room.
1307 0 : if (aScreenEnd - endpos >= startpos - aScreenBegin) {
1308 0 : *aFlipSide = aEndAligned;
1309 0 : if (mIsContextMenu) {
1310 0 : aScreenPoint = aScreenEnd - aSize;
1311 : }
1312 : else {
1313 0 : aScreenPoint = endpos + aMarginBegin;
1314 0 : popupSize = aScreenEnd - aScreenPoint;
1315 : }
1316 : }
1317 : else {
1318 : // if the newly calculated position is different than the existing
1319 : // position, we flip such that the popup is to the left or top of the
1320 : // anchor point instead.
1321 0 : nscoord newScreenPoint = startpos - aSize - aMarginBegin - std::max(aOffsetForContextMenu, 0);
1322 0 : if (newScreenPoint != aScreenPoint) {
1323 0 : *aFlipSide = !aEndAligned;
1324 0 : aScreenPoint = newScreenPoint;
1325 :
1326 : // check if the new position is still off the left or top edge of the
1327 : // screen. If so, resize the popup.
1328 0 : if (aScreenPoint < aScreenBegin) {
1329 0 : aScreenPoint = aScreenBegin;
1330 0 : if (!mIsContextMenu) {
1331 0 : popupSize = startpos - aScreenPoint - aMarginBegin;
1332 : }
1333 : }
1334 : }
1335 : }
1336 : }
1337 : else {
1338 0 : aScreenPoint = aScreenEnd - aSize;
1339 : }
1340 : }
1341 :
1342 : // Make sure that the point is within the screen boundaries and that the
1343 : // size isn't off the edge of the screen. This can happen when a large
1344 : // positive or negative margin is used.
1345 0 : if (aScreenPoint < aScreenBegin) {
1346 0 : aScreenPoint = aScreenBegin;
1347 : }
1348 0 : if (aScreenPoint > aScreenEnd) {
1349 0 : aScreenPoint = aScreenEnd - aSize;
1350 : }
1351 :
1352 : // If popupSize ended up being negative, or the original size was actually
1353 : // smaller than the calculated popup size, just use the original size instead.
1354 0 : if (popupSize <= 0 || aSize < popupSize) {
1355 0 : popupSize = aSize;
1356 : }
1357 0 : return std::min(popupSize, aScreenEnd - aScreenPoint);
1358 : }
1359 :
1360 : nsRect
1361 0 : nsMenuPopupFrame::ComputeAnchorRect(nsPresContext* aRootPresContext, nsIFrame* aAnchorFrame)
1362 : {
1363 : // Get the root frame for a reference
1364 0 : nsIFrame* rootFrame = aRootPresContext->FrameManager()->GetRootFrame();
1365 :
1366 : // The dimensions of the anchor
1367 0 : nsRect anchorRect = aAnchorFrame->GetRectRelativeToSelf();
1368 :
1369 : // Relative to the root
1370 0 : anchorRect = nsLayoutUtils::TransformFrameRectToAncestor(aAnchorFrame,
1371 : anchorRect,
1372 : rootFrame);
1373 : // Relative to the screen
1374 0 : anchorRect.MoveBy(rootFrame->GetScreenRectInAppUnits().TopLeft());
1375 :
1376 : // In its own app units
1377 : return anchorRect.ScaleToOtherAppUnitsRoundOut(aRootPresContext->AppUnitsPerDevPixel(),
1378 0 : PresContext()->AppUnitsPerDevPixel());
1379 : }
1380 :
1381 : nsresult
1382 0 : nsMenuPopupFrame::SetPopupPosition(nsIFrame* aAnchorFrame, bool aIsMove, bool aSizedToPopup, bool aNotify)
1383 : {
1384 0 : if (!mShouldAutoPosition)
1385 0 : return NS_OK;
1386 :
1387 : // If this is due to a move, return early if the popup hasn't been laid out
1388 : // yet. On Windows, this can happen when using a drag popup before it opens.
1389 0 : if (aIsMove && (mPrefSize.width == -1 || mPrefSize.height == -1)) {
1390 0 : return NS_OK;
1391 : }
1392 :
1393 0 : nsPresContext* presContext = PresContext();
1394 0 : nsIFrame* rootFrame = presContext->PresShell()->FrameManager()->GetRootFrame();
1395 0 : NS_ASSERTION(rootFrame->GetView() && GetView() &&
1396 : rootFrame->GetView() == GetView()->GetParent(),
1397 : "rootFrame's view is not our view's parent???");
1398 :
1399 : // For anchored popups, the anchor rectangle. For non-anchored popups, the
1400 : // size will be 0.
1401 0 : nsRect anchorRect;
1402 :
1403 : // Width of the parent, used when aSizedToPopup is true.
1404 0 : int32_t parentWidth = 0;
1405 :
1406 0 : bool anchored = IsAnchored();
1407 0 : if (anchored || aSizedToPopup) {
1408 : // In order to deal with transforms, we need the root prescontext:
1409 0 : nsPresContext* rootPresContext = presContext->GetRootPresContext();
1410 :
1411 : // If we can't reach a root pres context, don't bother continuing:
1412 0 : if (!rootPresContext) {
1413 0 : return NS_OK;
1414 : }
1415 :
1416 : // If anchored to a rectangle, use that rectangle. Otherwise, determine the
1417 : // rectangle from the anchor.
1418 0 : if (mAnchorType == MenuPopupAnchorType_Rect) {
1419 0 : anchorRect = ToAppUnits(mScreenRect, presContext->AppUnitsPerCSSPixel());
1420 : }
1421 : else {
1422 : // if the frame is not specified, use the anchor node passed to OpenPopup. If
1423 : // that wasn't specified either, use the root frame. Note that mAnchorContent
1424 : // might be a different document so its presshell must be used.
1425 0 : if (!aAnchorFrame) {
1426 0 : if (mAnchorContent) {
1427 0 : aAnchorFrame = mAnchorContent->GetPrimaryFrame();
1428 : }
1429 :
1430 0 : if (!aAnchorFrame) {
1431 0 : aAnchorFrame = rootFrame;
1432 0 : if (!aAnchorFrame)
1433 0 : return NS_OK;
1434 : }
1435 : }
1436 :
1437 0 : anchorRect = ComputeAnchorRect(rootPresContext, aAnchorFrame);
1438 : }
1439 :
1440 : // The width is needed when aSizedToPopup is true
1441 0 : parentWidth = anchorRect.width;
1442 : }
1443 :
1444 : // Set the popup's size to the preferred size. Below, this size will be
1445 : // adjusted to fit on the screen or within the content area. If the anchor
1446 : // is sized to the popup, use the anchor's width instead of the preferred
1447 : // width. The preferred size should already be set by the parent frame.
1448 0 : NS_ASSERTION(mPrefSize.width >= 0 || mPrefSize.height >= 0,
1449 : "preferred size of popup not set");
1450 0 : mRect.width = aSizedToPopup ? parentWidth : mPrefSize.width;
1451 0 : mRect.height = mPrefSize.height;
1452 :
1453 : // If we're anchoring to a rect, and the rect is smaller than the preferred size
1454 : // of the popup, change its width accordingly.
1455 0 : if (mAnchorType == MenuPopupAnchorType_Rect &&
1456 0 : parentWidth < mPrefSize.width) {
1457 0 : mRect.width = mPrefSize.width;
1458 : }
1459 :
1460 : // the screen position in app units where the popup should appear
1461 0 : nsPoint screenPoint;
1462 :
1463 : // indicators of whether the popup should be flipped or resized.
1464 0 : FlipStyle hFlip = FlipStyle_None, vFlip = FlipStyle_None;
1465 :
1466 0 : nsMargin margin(0, 0, 0, 0);
1467 0 : StyleMargin()->GetMargin(margin);
1468 :
1469 : // the screen rectangle of the root frame, in dev pixels.
1470 0 : nsRect rootScreenRect = rootFrame->GetScreenRectInAppUnits();
1471 :
1472 0 : nsDeviceContext* devContext = presContext->DeviceContext();
1473 0 : nsPoint offsetForContextMenu;
1474 :
1475 0 : bool isNoAutoHide = IsNoAutoHide();
1476 0 : nsPopupLevel popupLevel = PopupLevel(isNoAutoHide);
1477 :
1478 0 : if (anchored) {
1479 : // if we are anchored, there are certain things we don't want to do when
1480 : // repositioning the popup to fit on the screen, such as end up positioned
1481 : // over the anchor, for instance a popup appearing over the menu label.
1482 : // When doing this reposition, we want to move the popup to the side with
1483 : // the most room. The combination of anchor and alignment dictate if we
1484 : // readjust above/below or to the left/right.
1485 0 : if (mAnchorContent || mAnchorType == MenuPopupAnchorType_Rect) {
1486 : // move the popup according to the anchor and alignment. This will also
1487 : // tell us which axis the popup is flush against in case we have to move
1488 : // it around later. The AdjustPositionForAnchorAlign method accounts for
1489 : // the popup's margin.
1490 0 : screenPoint = AdjustPositionForAnchorAlign(anchorRect, hFlip, vFlip);
1491 : }
1492 : else {
1493 : // with no anchor, the popup is positioned relative to the root frame
1494 0 : anchorRect = rootScreenRect;
1495 0 : screenPoint = anchorRect.TopLeft() + nsPoint(margin.left, margin.top);
1496 : }
1497 :
1498 : // mXPos and mYPos specify an additonal offset passed to OpenPopup that
1499 : // should be added to the position. We also add the offset to the anchor
1500 : // pos so a later flip/resize takes the offset into account.
1501 0 : nscoord anchorXOffset = presContext->CSSPixelsToAppUnits(mXPos);
1502 0 : if (IsDirectionRTL()) {
1503 0 : screenPoint.x -= anchorXOffset;
1504 0 : anchorRect.x -= anchorXOffset;
1505 : } else {
1506 0 : screenPoint.x += anchorXOffset;
1507 0 : anchorRect.x += anchorXOffset;
1508 : }
1509 0 : nscoord anchorYOffset = presContext->CSSPixelsToAppUnits(mYPos);
1510 0 : screenPoint.y += anchorYOffset;
1511 0 : anchorRect.y += anchorYOffset;
1512 :
1513 : // If this is a noautohide popup, set the screen coordinates of the popup.
1514 : // This way, the popup stays at the location where it was opened even when
1515 : // the window is moved. Popups at the parent level follow the parent
1516 : // window as it is moved and remained anchored, so we want to maintain the
1517 : // anchoring instead.
1518 0 : if (isNoAutoHide &&
1519 0 : (popupLevel != ePopupLevelParent || mAnchorType == MenuPopupAnchorType_Rect)) {
1520 : // Account for the margin that will end up being added to the screen coordinate
1521 : // the next time SetPopupPosition is called.
1522 0 : mAnchorType = MenuPopupAnchorType_Point;
1523 0 : mScreenRect.x = presContext->AppUnitsToIntCSSPixels(screenPoint.x - margin.left);
1524 0 : mScreenRect.y = presContext->AppUnitsToIntCSSPixels(screenPoint.y - margin.top);
1525 : }
1526 : }
1527 : else {
1528 : // The popup is positioned at a screen coordinate.
1529 : // First convert the screen position in mScreenRect from CSS pixels into
1530 : // device pixels, ignoring any zoom as mScreenRect holds unzoomed screen
1531 : // coordinates.
1532 0 : int32_t factor = devContext->AppUnitsPerDevPixelAtUnitFullZoom();
1533 :
1534 : // Depending on the platform, context menus should be offset by varying amounts
1535 : // to ensure that they don't appear directly where the cursor is. Otherwise,
1536 : // it is too easy to have the context menu close up again.
1537 0 : if (mAdjustOffsetForContextMenu) {
1538 0 : nsPoint offsetForContextMenuDev;
1539 0 : offsetForContextMenuDev.x = nsPresContext::CSSPixelsToAppUnits(LookAndFeel::GetInt(
1540 0 : LookAndFeel::eIntID_ContextMenuOffsetHorizontal)) / factor;
1541 0 : offsetForContextMenuDev.y = nsPresContext::CSSPixelsToAppUnits(LookAndFeel::GetInt(
1542 0 : LookAndFeel::eIntID_ContextMenuOffsetVertical)) / factor;
1543 0 : offsetForContextMenu.x = presContext->DevPixelsToAppUnits(offsetForContextMenuDev.x);
1544 0 : offsetForContextMenu.y = presContext->DevPixelsToAppUnits(offsetForContextMenuDev.y);
1545 : }
1546 :
1547 : // next, convert into app units accounting for the zoom
1548 0 : screenPoint.x = presContext->DevPixelsToAppUnits(
1549 0 : nsPresContext::CSSPixelsToAppUnits(mScreenRect.x) / factor);
1550 0 : screenPoint.y = presContext->DevPixelsToAppUnits(
1551 0 : nsPresContext::CSSPixelsToAppUnits(mScreenRect.y) / factor);
1552 0 : anchorRect = nsRect(screenPoint, nsSize(0, 0));
1553 :
1554 : // add the margins on the popup
1555 0 : screenPoint.MoveBy(margin.left + offsetForContextMenu.x,
1556 0 : margin.top + offsetForContextMenu.y);
1557 :
1558 : #ifdef XP_MACOSX
1559 : // OSX tooltips follow standard flip rule but other popups flip horizontally not vertically
1560 : if (mPopupType == ePopupTypeTooltip) {
1561 : vFlip = FlipStyle_Outside;
1562 : } else {
1563 : hFlip = FlipStyle_Outside;
1564 : }
1565 : #else
1566 : // Other OS screen positioned popups can be flipped vertically but never horizontally
1567 0 : vFlip = FlipStyle_Outside;
1568 : #endif // #ifdef XP_MACOSX
1569 : }
1570 :
1571 0 : nscoord oldAlignmentOffset = mAlignmentOffset;
1572 :
1573 : // If a panel is being moved or has flip="none", don't constrain or flip it, in order to avoid
1574 : // visual noise when moving windows between screens. However, if a panel is already constrained
1575 : // or flipped (mIsOffset), then we want to continue to calculate this. Also, always do this for
1576 : // content shells, so that the popup doesn't extend outside the containing frame.
1577 0 : if (mInContentShell || (mFlip != FlipType_None &&
1578 0 : (!aIsMove || mIsOffset || mPopupType != ePopupTypePanel))) {
1579 0 : int32_t appPerDev = presContext->AppUnitsPerDevPixel();
1580 : LayoutDeviceIntRect anchorRectDevPix =
1581 0 : LayoutDeviceIntRect::FromAppUnitsToNearest(anchorRect, appPerDev);
1582 : LayoutDeviceIntRect rootScreenRectDevPix =
1583 0 : LayoutDeviceIntRect::FromAppUnitsToNearest(rootScreenRect, appPerDev);
1584 : LayoutDeviceIntRect screenRectDevPix =
1585 0 : GetConstraintRect(anchorRectDevPix, rootScreenRectDevPix, popupLevel);
1586 : nsRect screenRect =
1587 0 : LayoutDeviceIntRect::ToAppUnits(screenRectDevPix, appPerDev);
1588 :
1589 : // Ensure that anchorRect is on screen.
1590 0 : anchorRect = anchorRect.Intersect(screenRect);
1591 :
1592 : // shrink the the popup down if it is larger than the screen size
1593 0 : if (mRect.width > screenRect.width)
1594 0 : mRect.width = screenRect.width;
1595 0 : if (mRect.height > screenRect.height)
1596 0 : mRect.height = screenRect.height;
1597 :
1598 : // at this point the anchor (anchorRect) is within the available screen
1599 : // area (screenRect) and the popup is known to be no larger than the screen.
1600 :
1601 : // We might want to "slide" an arrow if the panel is of the correct type -
1602 : // but we can only slide on one axis - the other axis must be "flipped or
1603 : // resized" as normal.
1604 0 : bool slideHorizontal = false, slideVertical = false;
1605 0 : if (mFlip == FlipType_Slide) {
1606 0 : int8_t position = GetAlignmentPosition();
1607 0 : slideHorizontal = position >= POPUPPOSITION_BEFORESTART &&
1608 : position <= POPUPPOSITION_AFTEREND;
1609 0 : slideVertical = position >= POPUPPOSITION_STARTBEFORE &&
1610 : position <= POPUPPOSITION_ENDAFTER;
1611 : }
1612 :
1613 : // Next, check if there is enough space to show the popup at full size when
1614 : // positioned at screenPoint. If not, flip the popups to the opposite side
1615 : // of their anchor point, or resize them as necessary.
1616 0 : bool endAligned = IsDirectionRTL() ?
1617 0 : mPopupAlignment == POPUPALIGNMENT_TOPLEFT || mPopupAlignment == POPUPALIGNMENT_BOTTOMLEFT :
1618 0 : mPopupAlignment == POPUPALIGNMENT_TOPRIGHT || mPopupAlignment == POPUPALIGNMENT_BOTTOMRIGHT;
1619 0 : nscoord preOffsetScreenPoint = screenPoint.x;
1620 0 : if (slideHorizontal) {
1621 0 : mRect.width = SlideOrResize(screenPoint.x, mRect.width, screenRect.x,
1622 : screenRect.XMost(), &mAlignmentOffset);
1623 : } else {
1624 0 : mRect.width = FlipOrResize(screenPoint.x, mRect.width, screenRect.x,
1625 : screenRect.XMost(), anchorRect.x, anchorRect.XMost(),
1626 : margin.left, margin.right, offsetForContextMenu.x, hFlip,
1627 : endAligned, &mHFlip);
1628 : }
1629 0 : mIsOffset = preOffsetScreenPoint != screenPoint.x;
1630 :
1631 0 : endAligned = mPopupAlignment == POPUPALIGNMENT_BOTTOMLEFT ||
1632 0 : mPopupAlignment == POPUPALIGNMENT_BOTTOMRIGHT;
1633 0 : preOffsetScreenPoint = screenPoint.y;
1634 0 : if (slideVertical) {
1635 0 : mRect.height = SlideOrResize(screenPoint.y, mRect.height, screenRect.y,
1636 : screenRect.YMost(), &mAlignmentOffset);
1637 : } else {
1638 0 : mRect.height = FlipOrResize(screenPoint.y, mRect.height, screenRect.y,
1639 : screenRect.YMost(), anchorRect.y, anchorRect.YMost(),
1640 : margin.top, margin.bottom, offsetForContextMenu.y, vFlip,
1641 : endAligned, &mVFlip);
1642 : }
1643 0 : mIsOffset = mIsOffset || (preOffsetScreenPoint != screenPoint.y);
1644 :
1645 0 : NS_ASSERTION(screenPoint.x >= screenRect.x && screenPoint.y >= screenRect.y &&
1646 : screenPoint.x + mRect.width <= screenRect.XMost() &&
1647 : screenPoint.y + mRect.height <= screenRect.YMost(),
1648 : "Popup is offscreen");
1649 : }
1650 :
1651 : // snap the popup's position in screen coordinates to device pixels,
1652 : // see bug 622507, bug 961431
1653 0 : screenPoint.x = presContext->RoundAppUnitsToNearestDevPixels(screenPoint.x);
1654 0 : screenPoint.y = presContext->RoundAppUnitsToNearestDevPixels(screenPoint.y);
1655 :
1656 : // determine the x and y position of the view by subtracting the desired
1657 : // screen position from the screen position of the root frame.
1658 0 : nsPoint viewPoint = screenPoint - rootScreenRect.TopLeft();
1659 :
1660 0 : nsView* view = GetView();
1661 0 : NS_ASSERTION(view, "popup with no view");
1662 :
1663 : // Offset the position by the width and height of the borders and titlebar.
1664 : // Even though GetClientOffset should return (0, 0) when there is no
1665 : // titlebar or borders, we skip these calculations anyway for non-panels
1666 : // to save time since they will never have a titlebar.
1667 0 : nsIWidget* widget = view->GetWidget();
1668 0 : if (mPopupType == ePopupTypePanel && widget) {
1669 0 : mLastClientOffset = widget->GetClientOffset();
1670 0 : viewPoint.x += presContext->DevPixelsToAppUnits(mLastClientOffset.x);
1671 0 : viewPoint.y += presContext->DevPixelsToAppUnits(mLastClientOffset.y);
1672 : }
1673 :
1674 : presContext->GetPresShell()->GetViewManager()->
1675 0 : MoveViewTo(view, viewPoint.x, viewPoint.y);
1676 :
1677 : // Now that we've positioned the view, sync up the frame's origin.
1678 0 : nsBoxFrame::SetPosition(viewPoint - GetParent()->GetOffsetTo(rootFrame));
1679 :
1680 0 : if (aSizedToPopup) {
1681 0 : nsBoxLayoutState state(PresContext());
1682 : // XXXndeakin can parentSize.width still extend outside?
1683 0 : SetXULBounds(state, mRect);
1684 : }
1685 :
1686 : // If the popup is in the positioned state or if it is shown and the position
1687 : // or size changed, dispatch a popuppositioned event if the popup wants it.
1688 0 : nsIntRect newRect(screenPoint.x, screenPoint.y, mRect.width, mRect.height);
1689 0 : if (mPopupState == ePopupPositioning ||
1690 0 : (mPopupState == ePopupShown && !newRect.IsEqualEdges(mUsedScreenRect)) ||
1691 0 : (mPopupState == ePopupShown && oldAlignmentOffset != mAlignmentOffset)) {
1692 0 : mUsedScreenRect = newRect;
1693 0 : if (aNotify) {
1694 0 : nsXULPopupPositionedEvent::DispatchIfNeeded(mContent, false, false);
1695 : }
1696 : }
1697 :
1698 0 : return NS_OK;
1699 : }
1700 :
1701 : /* virtual */ nsMenuFrame*
1702 0 : nsMenuPopupFrame::GetCurrentMenuItem()
1703 : {
1704 0 : return mCurrentMenu;
1705 : }
1706 :
1707 : LayoutDeviceIntRect
1708 0 : nsMenuPopupFrame::GetConstraintRect(const LayoutDeviceIntRect& aAnchorRect,
1709 : const LayoutDeviceIntRect& aRootScreenRect,
1710 : nsPopupLevel aPopupLevel)
1711 : {
1712 0 : LayoutDeviceIntRect screenRectPixels;
1713 :
1714 : // determine the available screen space. It will be reduced by the OS chrome
1715 : // such as menubars. It addition, for content shells, it will be the area of
1716 : // the content rather than the screen.
1717 0 : nsCOMPtr<nsIScreen> screen;
1718 0 : nsCOMPtr<nsIScreenManager> sm(do_GetService("@mozilla.org/gfx/screenmanager;1"));
1719 0 : if (sm) {
1720 : // for content shells, get the screen where the root frame is located.
1721 : // This is because we need to constrain the content to this content area,
1722 : // so we should use the same screen. Otherwise, use the screen where the
1723 : // anchor is located.
1724 : DesktopToLayoutDeviceScale scale =
1725 0 : PresContext()->DeviceContext()->GetDesktopToDeviceScale();
1726 : DesktopRect rect =
1727 0 : (mInContentShell ? aRootScreenRect : aAnchorRect) / scale;
1728 0 : int32_t width = std::max(1, NSToIntRound(rect.width));
1729 0 : int32_t height = std::max(1, NSToIntRound(rect.height));
1730 0 : sm->ScreenForRect(rect.x, rect.y, width, height, getter_AddRefs(screen));
1731 0 : if (screen) {
1732 : // Non-top-level popups (which will always be panels)
1733 : // should never overlap the OS bar:
1734 0 : bool dontOverlapOSBar = aPopupLevel != ePopupLevelTop;
1735 : // get the total screen area if the popup is allowed to overlap it.
1736 0 : if (!dontOverlapOSBar && mMenuCanOverlapOSBar && !mInContentShell)
1737 0 : screen->GetRect(&screenRectPixels.x, &screenRectPixels.y,
1738 0 : &screenRectPixels.width, &screenRectPixels.height);
1739 : else
1740 0 : screen->GetAvailRect(&screenRectPixels.x, &screenRectPixels.y,
1741 0 : &screenRectPixels.width, &screenRectPixels.height);
1742 : }
1743 : }
1744 :
1745 0 : if (mInContentShell) {
1746 : // for content shells, clip to the client area rather than the screen area
1747 0 : screenRectPixels.IntersectRect(screenRectPixels, aRootScreenRect);
1748 : }
1749 0 : else if (!mOverrideConstraintRect.IsEmpty()) {
1750 : LayoutDeviceIntRect overrideConstrainRect =
1751 : LayoutDeviceIntRect::FromAppUnitsToNearest(mOverrideConstraintRect,
1752 0 : PresContext()->AppUnitsPerDevPixel());
1753 : // This is currently only used for <select> elements where we want to constrain
1754 : // vertically to the screen but not horizontally, so do the intersection and then
1755 : // reset the horizontal values.
1756 0 : screenRectPixels.IntersectRect(screenRectPixels, overrideConstrainRect);
1757 0 : screenRectPixels.x = overrideConstrainRect.x;
1758 0 : screenRectPixels.width = overrideConstrainRect.width;
1759 : }
1760 :
1761 0 : return screenRectPixels;
1762 : }
1763 :
1764 0 : void nsMenuPopupFrame::CanAdjustEdges(Side aHorizontalSide,
1765 : Side aVerticalSide,
1766 : LayoutDeviceIntPoint& aChange)
1767 : {
1768 0 : int8_t popupAlign(mPopupAlignment);
1769 0 : if (IsDirectionRTL()) {
1770 0 : popupAlign = -popupAlign;
1771 : }
1772 :
1773 0 : if (aHorizontalSide == (mHFlip ? eSideRight : eSideLeft)) {
1774 0 : if (popupAlign == POPUPALIGNMENT_TOPLEFT || popupAlign == POPUPALIGNMENT_BOTTOMLEFT) {
1775 0 : aChange.x = 0;
1776 : }
1777 : }
1778 0 : else if (aHorizontalSide == (mHFlip ? eSideLeft : eSideRight)) {
1779 0 : if (popupAlign == POPUPALIGNMENT_TOPRIGHT || popupAlign == POPUPALIGNMENT_BOTTOMRIGHT) {
1780 0 : aChange.x = 0;
1781 : }
1782 : }
1783 :
1784 0 : if (aVerticalSide == (mVFlip ? eSideBottom : eSideTop)) {
1785 0 : if (popupAlign == POPUPALIGNMENT_TOPLEFT || popupAlign == POPUPALIGNMENT_TOPRIGHT) {
1786 0 : aChange.y = 0;
1787 : }
1788 : }
1789 0 : else if (aVerticalSide == (mVFlip ? eSideTop : eSideBottom)) {
1790 0 : if (popupAlign == POPUPALIGNMENT_BOTTOMLEFT || popupAlign == POPUPALIGNMENT_BOTTOMRIGHT) {
1791 0 : aChange.y = 0;
1792 : }
1793 : }
1794 0 : }
1795 :
1796 0 : ConsumeOutsideClicksResult nsMenuPopupFrame::ConsumeOutsideClicks()
1797 : {
1798 : // If the popup has explicitly set a consume mode, honor that.
1799 0 : if (mConsumeRollupEvent != PopupBoxObject::ROLLUP_DEFAULT) {
1800 0 : return (mConsumeRollupEvent == PopupBoxObject::ROLLUP_CONSUME) ?
1801 0 : ConsumeOutsideClicks_True : ConsumeOutsideClicks_ParentOnly;
1802 : }
1803 :
1804 0 : if (mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::consumeoutsideclicks,
1805 : nsGkAtoms::_true, eCaseMatters)) {
1806 0 : return ConsumeOutsideClicks_True;
1807 : }
1808 0 : if (mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::consumeoutsideclicks,
1809 : nsGkAtoms::_false, eCaseMatters)) {
1810 0 : return ConsumeOutsideClicks_ParentOnly;
1811 : }
1812 0 : if (mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::consumeoutsideclicks,
1813 : nsGkAtoms::never, eCaseMatters)) {
1814 0 : return ConsumeOutsideClicks_Never;
1815 : }
1816 :
1817 0 : nsCOMPtr<nsIContent> parentContent = mContent->GetParent();
1818 0 : if (parentContent) {
1819 0 : dom::NodeInfo *ni = parentContent->NodeInfo();
1820 0 : if (ni->Equals(nsGkAtoms::menulist, kNameSpaceID_XUL)) {
1821 0 : return ConsumeOutsideClicks_True; // Consume outside clicks for combo boxes on all platforms
1822 : }
1823 : #if defined(XP_WIN)
1824 : // Don't consume outside clicks for menus in Windows
1825 : if (ni->Equals(nsGkAtoms::menu, kNameSpaceID_XUL) ||
1826 : ni->Equals(nsGkAtoms::popupset, kNameSpaceID_XUL) ||
1827 : ((ni->Equals(nsGkAtoms::button, kNameSpaceID_XUL) ||
1828 : ni->Equals(nsGkAtoms::toolbarbutton, kNameSpaceID_XUL)) &&
1829 : (parentContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::type,
1830 : nsGkAtoms::menu, eCaseMatters) ||
1831 : parentContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::type,
1832 : nsGkAtoms::menuButton, eCaseMatters)))) {
1833 : return ConsumeOutsideClicks_Never;
1834 : }
1835 : #endif
1836 0 : if (ni->Equals(nsGkAtoms::textbox, kNameSpaceID_XUL)) {
1837 : // Don't consume outside clicks for autocomplete widget
1838 0 : if (parentContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::type,
1839 : nsGkAtoms::autocomplete, eCaseMatters)) {
1840 0 : return ConsumeOutsideClicks_Never;
1841 : }
1842 : }
1843 : }
1844 :
1845 0 : return ConsumeOutsideClicks_True;
1846 : }
1847 :
1848 : // XXXroc this is megalame. Fossicking around for a frame of the right
1849 : // type is a recipe for disaster in the long term.
1850 0 : nsIScrollableFrame* nsMenuPopupFrame::GetScrollFrame(nsIFrame* aStart)
1851 : {
1852 0 : if (!aStart)
1853 0 : return nullptr;
1854 :
1855 : // try start frame and siblings
1856 0 : nsIFrame* currFrame = aStart;
1857 0 : do {
1858 0 : nsIScrollableFrame* sf = do_QueryFrame(currFrame);
1859 0 : if (sf)
1860 0 : return sf;
1861 0 : currFrame = currFrame->GetNextSibling();
1862 0 : } while (currFrame);
1863 :
1864 : // try children
1865 0 : currFrame = aStart;
1866 0 : do {
1867 0 : nsIFrame* childFrame = currFrame->PrincipalChildList().FirstChild();
1868 0 : nsIScrollableFrame* sf = GetScrollFrame(childFrame);
1869 0 : if (sf)
1870 0 : return sf;
1871 0 : currFrame = currFrame->GetNextSibling();
1872 0 : } while (currFrame);
1873 :
1874 0 : return nullptr;
1875 : }
1876 :
1877 0 : void nsMenuPopupFrame::EnsureMenuItemIsVisible(nsMenuFrame* aMenuItem)
1878 : {
1879 0 : if (aMenuItem) {
1880 0 : aMenuItem->PresContext()->PresShell()->ScrollFrameRectIntoView(
1881 : aMenuItem,
1882 0 : nsRect(nsPoint(0,0), aMenuItem->GetRect().Size()),
1883 : nsIPresShell::ScrollAxis(),
1884 : nsIPresShell::ScrollAxis(),
1885 : nsIPresShell::SCROLL_OVERFLOW_HIDDEN |
1886 0 : nsIPresShell::SCROLL_FIRST_ANCESTOR_ONLY);
1887 : }
1888 0 : }
1889 :
1890 0 : void nsMenuPopupFrame::ChangeByPage(bool aIsUp)
1891 : {
1892 : // Only scroll by page within menulists.
1893 0 : if (!IsMenuList()) {
1894 0 : return;
1895 : }
1896 :
1897 0 : nsMenuFrame* newMenu = nullptr;
1898 0 : nsIFrame* currentMenu = mCurrentMenu;
1899 0 : if (!currentMenu) {
1900 : // If there is no current menu item, get the first item. When moving up,
1901 : // just use this as the newMenu and leave currentMenu null so that no
1902 : // check for a later element is performed. When moving down, set currentMenu
1903 : // so that we look for one page down from the first item.
1904 0 : newMenu = nsXULPopupManager::GetNextMenuItem(this, nullptr, true, false);
1905 0 : if (!aIsUp) {
1906 0 : currentMenu = newMenu;
1907 : }
1908 : }
1909 :
1910 0 : if (currentMenu) {
1911 0 : nscoord scrollHeight = mRect.height;
1912 0 : nsIScrollableFrame *scrollframe = GetScrollFrame(this);
1913 0 : if (scrollframe) {
1914 0 : scrollHeight = scrollframe->GetScrollPortRect().height;
1915 : }
1916 :
1917 : // Get the position of the current item and add or subtract one popup's
1918 : // height to or from it.
1919 0 : nscoord targetPosition = aIsUp ? currentMenu->GetRect().YMost() - scrollHeight :
1920 0 : currentMenu->GetRect().y + scrollHeight;
1921 :
1922 : // Indicates that the last visible child was a valid menuitem.
1923 0 : bool lastWasValid = false;
1924 :
1925 : // Look for the next child which is just past the target position. This child
1926 : // will need to be selected.
1927 0 : while (currentMenu) {
1928 : // Only consider menu frames.
1929 0 : nsMenuFrame* menuFrame = do_QueryFrame(currentMenu);
1930 0 : if (menuFrame &&
1931 0 : nsXULPopupManager::IsValidMenuItem(menuFrame->GetContent(), true)) {
1932 :
1933 : // If the right position was found, break out. Otherwise, look for another item.
1934 0 : if ((!aIsUp && currentMenu->GetRect().YMost() > targetPosition) ||
1935 0 : (aIsUp && currentMenu->GetRect().y < targetPosition)) {
1936 :
1937 : // If the last visible child was not a valid menuitem or was disabled,
1938 : // use this as the menu to select, skipping over any non-valid items at
1939 : // the edge of the page.
1940 0 : if (!lastWasValid) {
1941 0 : newMenu = menuFrame;
1942 : }
1943 :
1944 0 : break;
1945 : }
1946 :
1947 : // Assign this item to newMenu. This item will be selected in case we
1948 : // don't find any more.
1949 0 : lastWasValid = true;
1950 0 : newMenu = menuFrame;
1951 : }
1952 : else {
1953 0 : lastWasValid = false;
1954 : }
1955 :
1956 0 : currentMenu = aIsUp ? currentMenu->GetPrevSibling() :
1957 : currentMenu->GetNextSibling();
1958 : }
1959 : }
1960 :
1961 : // Select the new menuitem.
1962 0 : if (newMenu) {
1963 0 : ChangeMenuItem(newMenu, false, true);
1964 : }
1965 : }
1966 :
1967 0 : NS_IMETHODIMP nsMenuPopupFrame::SetCurrentMenuItem(nsMenuFrame* aMenuItem)
1968 : {
1969 0 : if (mCurrentMenu == aMenuItem)
1970 0 : return NS_OK;
1971 :
1972 0 : if (mCurrentMenu) {
1973 0 : mCurrentMenu->SelectMenu(false);
1974 : }
1975 :
1976 0 : if (aMenuItem) {
1977 0 : EnsureMenuItemIsVisible(aMenuItem);
1978 0 : aMenuItem->SelectMenu(true);
1979 : }
1980 :
1981 0 : mCurrentMenu = aMenuItem;
1982 :
1983 0 : return NS_OK;
1984 : }
1985 :
1986 : void
1987 0 : nsMenuPopupFrame::CurrentMenuIsBeingDestroyed()
1988 : {
1989 0 : mCurrentMenu = nullptr;
1990 0 : }
1991 :
1992 : NS_IMETHODIMP
1993 0 : nsMenuPopupFrame::ChangeMenuItem(nsMenuFrame* aMenuItem,
1994 : bool aSelectFirstItem,
1995 : bool aFromKey)
1996 : {
1997 0 : if (mCurrentMenu == aMenuItem)
1998 0 : return NS_OK;
1999 :
2000 : // When a context menu is open, the current menu is locked, and no change
2001 : // to the menu is allowed.
2002 0 : nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
2003 0 : if (!mIsContextMenu && pm && pm->HasContextMenu(this))
2004 0 : return NS_OK;
2005 :
2006 : // Unset the current child.
2007 0 : if (mCurrentMenu) {
2008 0 : mCurrentMenu->SelectMenu(false);
2009 0 : nsMenuPopupFrame* popup = mCurrentMenu->GetPopup();
2010 0 : if (popup) {
2011 0 : if (mCurrentMenu->IsOpen()) {
2012 0 : if (pm)
2013 0 : pm->HidePopupAfterDelay(popup);
2014 : }
2015 : }
2016 : }
2017 :
2018 : // Set the new child.
2019 0 : if (aMenuItem) {
2020 0 : EnsureMenuItemIsVisible(aMenuItem);
2021 0 : aMenuItem->SelectMenu(true);
2022 :
2023 : // On Windows, a menulist should update its value whenever navigation was
2024 : // done by the keyboard.
2025 : #ifdef XP_WIN
2026 : if (aFromKey && IsOpen() && IsMenuList()) {
2027 : // Fire a command event as the new item, but we don't want to close
2028 : // the menu, blink it, or update any other state of the menuitem. The
2029 : // command event will cause the item to be selected.
2030 : nsContentUtils::DispatchXULCommand(aMenuItem->GetContent(), /* aTrusted = */ true,
2031 : nullptr, PresContext()->PresShell(),
2032 : false, false, false, false);
2033 : }
2034 : #endif
2035 : }
2036 :
2037 0 : mCurrentMenu = aMenuItem;
2038 :
2039 0 : return NS_OK;
2040 : }
2041 :
2042 : nsMenuFrame*
2043 0 : nsMenuPopupFrame::Enter(WidgetGUIEvent* aEvent)
2044 : {
2045 0 : mIncrementalString.Truncate();
2046 :
2047 : // Give it to the child.
2048 0 : if (mCurrentMenu)
2049 0 : return mCurrentMenu->Enter(aEvent);
2050 :
2051 0 : return nullptr;
2052 : }
2053 :
2054 : nsMenuFrame*
2055 0 : nsMenuPopupFrame::FindMenuWithShortcut(nsIDOMKeyEvent* aKeyEvent, bool& doAction)
2056 : {
2057 : uint32_t charCode, keyCode;
2058 0 : aKeyEvent->GetCharCode(&charCode);
2059 0 : aKeyEvent->GetKeyCode(&keyCode);
2060 :
2061 0 : doAction = false;
2062 :
2063 : // Enumerate over our list of frames.
2064 : auto insertion = PresContext()->PresShell()->
2065 0 : FrameConstructor()->GetInsertionPoint(GetContent(), nullptr);
2066 0 : nsContainerFrame* immediateParent = insertion.mParentFrame;
2067 0 : if (!immediateParent)
2068 0 : immediateParent = this;
2069 :
2070 0 : uint32_t matchCount = 0, matchShortcutCount = 0;
2071 0 : bool foundActive = false;
2072 : bool isShortcut;
2073 0 : nsMenuFrame* frameBefore = nullptr;
2074 0 : nsMenuFrame* frameAfter = nullptr;
2075 0 : nsMenuFrame* frameShortcut = nullptr;
2076 :
2077 0 : nsIContent* parentContent = mContent->GetParent();
2078 :
2079 0 : bool isMenu = parentContent &&
2080 0 : !parentContent->NodeInfo()->Equals(nsGkAtoms::menulist, kNameSpaceID_XUL);
2081 :
2082 : DOMTimeStamp keyTime;
2083 0 : aKeyEvent->AsEvent()->GetTimeStamp(&keyTime);
2084 :
2085 0 : if (charCode == 0) {
2086 0 : if (keyCode == nsIDOMKeyEvent::DOM_VK_BACK_SPACE) {
2087 0 : if (!isMenu && !mIncrementalString.IsEmpty()) {
2088 0 : mIncrementalString.SetLength(mIncrementalString.Length() - 1);
2089 0 : return nullptr;
2090 : }
2091 : else {
2092 : #ifdef XP_WIN
2093 : nsCOMPtr<nsISound> soundInterface = do_CreateInstance("@mozilla.org/sound;1");
2094 : if (soundInterface)
2095 : soundInterface->Beep();
2096 : #endif // #ifdef XP_WIN
2097 : }
2098 : }
2099 0 : return nullptr;
2100 : }
2101 : else {
2102 0 : char16_t uniChar = ToLowerCase(static_cast<char16_t>(charCode));
2103 0 : if (isMenu) {
2104 : // Menu supports only first-letter navigation
2105 0 : mIncrementalString = uniChar;
2106 0 : } else if (IsWithinIncrementalTime(keyTime)) {
2107 0 : mIncrementalString.Append(uniChar);
2108 : } else {
2109 : // Interval too long, treat as new typing
2110 0 : mIncrementalString = uniChar;
2111 : }
2112 : }
2113 :
2114 : // See bug 188199 & 192346, if all letters in incremental string are same, just try to match the first one
2115 0 : nsAutoString incrementalString(mIncrementalString);
2116 0 : uint32_t charIndex = 1, stringLength = incrementalString.Length();
2117 0 : while (charIndex < stringLength && incrementalString[charIndex] == incrementalString[charIndex - 1]) {
2118 0 : charIndex++;
2119 : }
2120 0 : if (charIndex == stringLength) {
2121 0 : incrementalString.Truncate(1);
2122 0 : stringLength = 1;
2123 : }
2124 :
2125 0 : sLastKeyTime = keyTime;
2126 :
2127 : // NOTE: If you crashed here due to a bogus |immediateParent| it is
2128 : // possible that the menu whose shortcut is being looked up has
2129 : // been destroyed already. One strategy would be to
2130 : // setTimeout(<func>,0) as detailed in:
2131 : // <http://bugzilla.mozilla.org/show_bug.cgi?id=126675#c32>
2132 0 : nsIFrame* firstMenuItem = nsXULPopupManager::GetNextMenuItem(immediateParent, nullptr, true, false);
2133 0 : nsIFrame* currFrame = firstMenuItem;
2134 :
2135 0 : int32_t menuAccessKey = -1;
2136 0 : nsMenuBarListener::GetMenuAccessKey(&menuAccessKey);
2137 :
2138 : // We start searching from first child. This process is divided into two parts
2139 : // -- before current and after current -- by the current item
2140 0 : while (currFrame) {
2141 0 : nsIContent* current = currFrame->GetContent();
2142 0 : nsAutoString textKey;
2143 0 : if (menuAccessKey >= 0) {
2144 : // Get the shortcut attribute.
2145 0 : current->GetAttr(kNameSpaceID_None, nsGkAtoms::accesskey, textKey);
2146 : }
2147 0 : if (textKey.IsEmpty()) { // No shortcut, try first letter
2148 0 : isShortcut = false;
2149 0 : current->GetAttr(kNameSpaceID_None, nsGkAtoms::label, textKey);
2150 0 : if (textKey.IsEmpty()) // No label, try another attribute (value)
2151 0 : current->GetAttr(kNameSpaceID_None, nsGkAtoms::value, textKey);
2152 : }
2153 : else
2154 0 : isShortcut = true;
2155 :
2156 0 : if (StringBeginsWith(textKey, incrementalString,
2157 0 : nsCaseInsensitiveStringComparator())) {
2158 : // mIncrementalString is a prefix of textKey
2159 0 : nsMenuFrame* menu = do_QueryFrame(currFrame);
2160 0 : if (menu) {
2161 : // There is one match
2162 0 : matchCount++;
2163 0 : if (isShortcut) {
2164 : // There is one shortcut-key match
2165 0 : matchShortcutCount++;
2166 : // Record the matched item. If there is only one matched shortcut item, do it
2167 0 : frameShortcut = menu;
2168 : }
2169 0 : if (!foundActive) {
2170 : // It's a first candidate item located before/on the current item
2171 0 : if (!frameBefore)
2172 0 : frameBefore = menu;
2173 : }
2174 : else {
2175 : // It's a first candidate item located after the current item
2176 0 : if (!frameAfter)
2177 0 : frameAfter = menu;
2178 : }
2179 : }
2180 : else
2181 0 : return nullptr;
2182 : }
2183 :
2184 : // Get the active status
2185 0 : if (current->AttrValueIs(kNameSpaceID_None, nsGkAtoms::menuactive,
2186 : nsGkAtoms::_true, eCaseMatters)) {
2187 0 : foundActive = true;
2188 0 : if (stringLength > 1) {
2189 : // If there is more than one char typed, the current item has highest priority,
2190 : // otherwise the item next to current has highest priority
2191 0 : if (currFrame == frameBefore)
2192 0 : return frameBefore;
2193 : }
2194 : }
2195 :
2196 0 : nsMenuFrame* menu = do_QueryFrame(currFrame);
2197 0 : currFrame = nsXULPopupManager::GetNextMenuItem(immediateParent, menu, true, true);
2198 0 : if (currFrame == firstMenuItem)
2199 0 : break;
2200 : }
2201 :
2202 0 : doAction = (isMenu && (matchCount == 1 || matchShortcutCount == 1));
2203 :
2204 0 : if (matchShortcutCount == 1) // We have one matched shortcut item
2205 0 : return frameShortcut;
2206 0 : if (frameAfter) // If we have matched item after the current, use it
2207 0 : return frameAfter;
2208 0 : else if (frameBefore) // If we haven't, use the item before the current
2209 0 : return frameBefore;
2210 :
2211 : // If we don't match anything, rollback the last typing
2212 0 : mIncrementalString.SetLength(mIncrementalString.Length() - 1);
2213 :
2214 : // didn't find a matching menu item
2215 : #ifdef XP_WIN
2216 : // behavior on Windows - this item is in a menu popup off of the
2217 : // menu bar, so beep and do nothing else
2218 : if (isMenu) {
2219 : nsCOMPtr<nsISound> soundInterface = do_CreateInstance("@mozilla.org/sound;1");
2220 : if (soundInterface)
2221 : soundInterface->Beep();
2222 : }
2223 : #endif // #ifdef XP_WIN
2224 :
2225 0 : return nullptr;
2226 : }
2227 :
2228 : void
2229 0 : nsMenuPopupFrame::LockMenuUntilClosed(bool aLock)
2230 : {
2231 0 : mIsMenuLocked = aLock;
2232 :
2233 : // Lock / unlock the parent, too.
2234 0 : nsMenuFrame* menu = do_QueryFrame(GetParent());
2235 0 : if (menu) {
2236 0 : nsMenuParent* parentParent = menu->GetMenuParent();
2237 0 : if (parentParent) {
2238 0 : parentParent->LockMenuUntilClosed(aLock);
2239 : }
2240 : }
2241 0 : }
2242 :
2243 : nsIWidget*
2244 0 : nsMenuPopupFrame::GetWidget()
2245 : {
2246 0 : nsView * view = GetRootViewForPopup(this);
2247 0 : if (!view)
2248 0 : return nullptr;
2249 :
2250 0 : return view->GetWidget();
2251 : }
2252 :
2253 : void
2254 0 : nsMenuPopupFrame::AttachedDismissalListener()
2255 : {
2256 0 : mConsumeRollupEvent = PopupBoxObject::ROLLUP_DEFAULT;
2257 0 : }
2258 :
2259 : // helpers /////////////////////////////////////////////////////////////
2260 :
2261 : nsresult
2262 1 : nsMenuPopupFrame::AttributeChanged(int32_t aNameSpaceID,
2263 : nsIAtom* aAttribute,
2264 : int32_t aModType)
2265 :
2266 : {
2267 1 : nsresult rv = nsBoxFrame::AttributeChanged(aNameSpaceID, aAttribute,
2268 1 : aModType);
2269 :
2270 1 : if (aAttribute == nsGkAtoms::left || aAttribute == nsGkAtoms::top)
2271 0 : MoveToAttributePosition();
2272 :
2273 : #ifndef MOZ_GTK2
2274 1 : if (aAttribute == nsGkAtoms::noautohide) {
2275 0 : nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
2276 0 : if (pm)
2277 0 : pm->EnableRollup(mContent, !IsNoAutoHide());
2278 : }
2279 : #endif
2280 :
2281 1 : if (aAttribute == nsGkAtoms::remote) {
2282 : // When the remote attribute changes, we need to create a new widget to
2283 : // ensure that it has the correct compositor and transparency settings to
2284 : // match the new value.
2285 0 : EnsureWidget(true);
2286 : }
2287 :
2288 1 : if (aAttribute == nsGkAtoms::followanchor) {
2289 0 : nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
2290 0 : if (pm) {
2291 0 : pm->UpdateFollowAnchor(this);
2292 : }
2293 : }
2294 :
2295 1 : if (aAttribute == nsGkAtoms::label) {
2296 : // set the label for the titlebar
2297 0 : nsView* view = GetView();
2298 0 : if (view) {
2299 0 : nsIWidget* widget = view->GetWidget();
2300 0 : if (widget) {
2301 0 : nsAutoString title;
2302 0 : mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::label, title);
2303 0 : if (!title.IsEmpty()) {
2304 0 : widget->SetTitle(title);
2305 : }
2306 : }
2307 : }
2308 1 : } else if (aAttribute == nsGkAtoms::ignorekeys) {
2309 0 : nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
2310 0 : if (pm) {
2311 0 : nsAutoString ignorekeys;
2312 0 : mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::ignorekeys, ignorekeys);
2313 0 : pm->UpdateIgnoreKeys(ignorekeys.EqualsLiteral("true"));
2314 : }
2315 : }
2316 :
2317 1 : return rv;
2318 : }
2319 :
2320 : void
2321 0 : nsMenuPopupFrame::MoveToAttributePosition()
2322 : {
2323 : // Move the widget around when the user sets the |left| and |top| attributes.
2324 : // Note that this is not the best way to move the widget, as it results in lots
2325 : // of FE notifications and is likely to be slow as molasses. Use |moveTo| on
2326 : // PopupBoxObject if possible.
2327 0 : nsAutoString left, top;
2328 0 : mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::left, left);
2329 0 : mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::top, top);
2330 : nsresult err1, err2;
2331 0 : mozilla::CSSIntPoint pos(left.ToInteger(&err1), top.ToInteger(&err2));
2332 :
2333 0 : if (NS_SUCCEEDED(err1) && NS_SUCCEEDED(err2))
2334 0 : MoveTo(pos, false);
2335 0 : }
2336 :
2337 : void
2338 7 : nsMenuPopupFrame::DestroyFrom(nsIFrame* aDestructRoot)
2339 : {
2340 7 : if (mReflowCallbackData.mPosted) {
2341 0 : PresContext()->PresShell()->CancelReflowCallback(this);
2342 0 : mReflowCallbackData.Clear();
2343 : }
2344 :
2345 7 : nsMenuFrame* menu = do_QueryFrame(GetParent());
2346 7 : if (menu) {
2347 : // clear the open attribute on the parent menu
2348 : nsContentUtils::AddScriptRunner(
2349 2 : new nsUnsetAttrRunnable(menu->GetContent(), nsGkAtoms::open));
2350 : }
2351 :
2352 7 : ClearPopupShownDispatcher();
2353 :
2354 7 : nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
2355 7 : if (pm)
2356 7 : pm->PopupDestroyed(this);
2357 :
2358 : nsIRootBox* rootBox =
2359 7 : nsIRootBox::GetRootBox(PresContext()->GetPresShell());
2360 7 : if (rootBox && rootBox->GetDefaultTooltip() == mContent) {
2361 0 : rootBox->SetDefaultTooltip(nullptr);
2362 : }
2363 :
2364 7 : nsBoxFrame::DestroyFrom(aDestructRoot);
2365 7 : }
2366 :
2367 :
2368 : void
2369 0 : nsMenuPopupFrame::MoveTo(const CSSIntPoint& aPos, bool aUpdateAttrs)
2370 : {
2371 0 : nsIWidget* widget = GetWidget();
2372 0 : if ((mScreenRect.x == aPos.x && mScreenRect.y == aPos.y) &&
2373 0 : (!widget || widget->GetClientOffset() == mLastClientOffset)) {
2374 0 : return;
2375 : }
2376 :
2377 : // reposition the popup at the specified coordinates. Don't clear the anchor
2378 : // and position, because the popup can be reset to its anchor position by
2379 : // using (-1, -1) as coordinates. Subtract off the margin as it will be
2380 : // added to the position when SetPopupPosition is called.
2381 0 : nsMargin margin(0, 0, 0, 0);
2382 0 : StyleMargin()->GetMargin(margin);
2383 :
2384 : // Workaround for bug 788189. See also bug 708278 comment #25 and following.
2385 0 : if (mAdjustOffsetForContextMenu) {
2386 0 : margin.left += nsPresContext::CSSPixelsToAppUnits(LookAndFeel::GetInt(
2387 0 : LookAndFeel::eIntID_ContextMenuOffsetHorizontal));
2388 0 : margin.top += nsPresContext::CSSPixelsToAppUnits(LookAndFeel::GetInt(
2389 0 : LookAndFeel::eIntID_ContextMenuOffsetVertical));
2390 : }
2391 :
2392 0 : nsPresContext* presContext = PresContext();
2393 0 : mAnchorType = MenuPopupAnchorType_Point;
2394 0 : mScreenRect.x = aPos.x - presContext->AppUnitsToIntCSSPixels(margin.left);
2395 0 : mScreenRect.y = aPos.y - presContext->AppUnitsToIntCSSPixels(margin.top);
2396 :
2397 0 : SetPopupPosition(nullptr, true, false, true);
2398 :
2399 0 : nsCOMPtr<nsIContent> popup = mContent;
2400 0 : if (aUpdateAttrs && (popup->HasAttr(kNameSpaceID_None, nsGkAtoms::left) ||
2401 0 : popup->HasAttr(kNameSpaceID_None, nsGkAtoms::top)))
2402 : {
2403 0 : nsAutoString left, top;
2404 0 : left.AppendInt(aPos.x);
2405 0 : top.AppendInt(aPos.y);
2406 0 : popup->SetAttr(kNameSpaceID_None, nsGkAtoms::left, left, false);
2407 0 : popup->SetAttr(kNameSpaceID_None, nsGkAtoms::top, top, false);
2408 : }
2409 : }
2410 :
2411 : void
2412 0 : nsMenuPopupFrame::MoveToAnchor(nsIContent* aAnchorContent,
2413 : const nsAString& aPosition,
2414 : int32_t aXPos, int32_t aYPos,
2415 : bool aAttributesOverride)
2416 : {
2417 0 : NS_ASSERTION(IsVisible(), "popup must be visible to move it");
2418 :
2419 0 : nsPopupState oldstate = mPopupState;
2420 0 : InitializePopup(aAnchorContent, mTriggerContent, aPosition,
2421 0 : aXPos, aYPos, MenuPopupAnchorType_Node, aAttributesOverride);
2422 : // InitializePopup changed the state so reset it.
2423 0 : mPopupState = oldstate;
2424 :
2425 : // Pass false here so that flipping and adjusting to fit on the screen happen.
2426 0 : SetPopupPosition(nullptr, false, false, true);
2427 0 : }
2428 :
2429 : bool
2430 0 : nsMenuPopupFrame::GetAutoPosition()
2431 : {
2432 0 : return mShouldAutoPosition;
2433 : }
2434 :
2435 : void
2436 0 : nsMenuPopupFrame::SetAutoPosition(bool aShouldAutoPosition)
2437 : {
2438 0 : mShouldAutoPosition = aShouldAutoPosition;
2439 0 : nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
2440 0 : if (pm) {
2441 0 : pm->UpdateFollowAnchor(this);
2442 : }
2443 0 : }
2444 :
2445 : void
2446 0 : nsMenuPopupFrame::SetConsumeRollupEvent(uint32_t aConsumeMode)
2447 : {
2448 0 : mConsumeRollupEvent = aConsumeMode;
2449 0 : }
2450 :
2451 : int8_t
2452 0 : nsMenuPopupFrame::GetAlignmentPosition() const
2453 : {
2454 : // The code below handles most cases of alignment, anchor and position values. Those that are
2455 : // not handled just return POPUPPOSITION_UNKNOWN.
2456 :
2457 0 : if (mPosition == POPUPPOSITION_OVERLAP || mPosition == POPUPPOSITION_AFTERPOINTER ||
2458 0 : mPosition == POPUPPOSITION_SELECTION)
2459 0 : return mPosition;
2460 :
2461 0 : int8_t position = mPosition;
2462 :
2463 0 : if (position == POPUPPOSITION_UNKNOWN) {
2464 0 : switch (mPopupAnchor) {
2465 : case POPUPALIGNMENT_BOTTOMCENTER:
2466 0 : position = mPopupAlignment == POPUPALIGNMENT_TOPRIGHT ?
2467 0 : POPUPPOSITION_AFTEREND : POPUPPOSITION_AFTERSTART;
2468 0 : break;
2469 : case POPUPALIGNMENT_TOPCENTER:
2470 0 : position = mPopupAlignment == POPUPALIGNMENT_BOTTOMRIGHT ?
2471 0 : POPUPPOSITION_BEFOREEND : POPUPPOSITION_BEFORESTART;
2472 0 : break;
2473 : case POPUPALIGNMENT_LEFTCENTER:
2474 0 : position = mPopupAlignment == POPUPALIGNMENT_BOTTOMRIGHT ?
2475 0 : POPUPPOSITION_STARTAFTER : POPUPPOSITION_STARTBEFORE;
2476 0 : break;
2477 : case POPUPALIGNMENT_RIGHTCENTER:
2478 0 : position = mPopupAlignment == POPUPALIGNMENT_BOTTOMLEFT ?
2479 0 : POPUPPOSITION_ENDAFTER : POPUPPOSITION_ENDBEFORE;
2480 0 : break;
2481 : default:
2482 0 : break;
2483 : }
2484 : }
2485 :
2486 0 : if (mHFlip) {
2487 0 : position = POPUPPOSITION_HFLIP(position);
2488 : }
2489 :
2490 0 : if (mVFlip) {
2491 0 : position = POPUPPOSITION_VFLIP(position);
2492 : }
2493 :
2494 0 : return position;
2495 : }
2496 :
2497 : /**
2498 : * KEEP THIS IN SYNC WITH nsFrame::CreateView
2499 : * as much as possible. Until we get rid of views finally...
2500 : */
2501 : void
2502 44 : nsMenuPopupFrame::CreatePopupView()
2503 : {
2504 44 : if (HasView()) {
2505 0 : return;
2506 : }
2507 :
2508 44 : nsViewManager* viewManager = PresContext()->GetPresShell()->GetViewManager();
2509 44 : NS_ASSERTION(nullptr != viewManager, "null view manager");
2510 :
2511 : // Create a view
2512 44 : nsView* parentView = viewManager->GetRootView();
2513 44 : nsViewVisibility visibility = nsViewVisibility_kHide;
2514 44 : int32_t zIndex = INT32_MAX;
2515 44 : bool autoZIndex = false;
2516 :
2517 44 : NS_ASSERTION(parentView, "no parent view");
2518 :
2519 : // Create a view
2520 44 : nsView *view = viewManager->CreateView(GetRect(), parentView, visibility);
2521 44 : viewManager->SetViewZIndex(view, autoZIndex, zIndex);
2522 : // XXX put view last in document order until we can do better
2523 44 : viewManager->InsertChild(parentView, view, nullptr, true);
2524 :
2525 : // Remember our view
2526 44 : SetView(view);
2527 :
2528 44 : NS_FRAME_LOG(NS_FRAME_TRACE_CALLS,
2529 : ("nsMenuPopupFrame::CreatePopupView: frame=%p view=%p", this, view));
2530 : }
2531 :
2532 : bool
2533 0 : nsMenuPopupFrame::ShouldFollowAnchor()
2534 : {
2535 0 : if (!mShouldAutoPosition ||
2536 0 : mAnchorType != MenuPopupAnchorType_Node || !mAnchorContent) {
2537 0 : return false;
2538 : }
2539 :
2540 : // Follow anchor mode is used when followanchor="true" is set or for arrow panels.
2541 0 : if (mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::followanchor,
2542 : nsGkAtoms::_true, eCaseMatters)) {
2543 0 : return true;
2544 : }
2545 :
2546 0 : if (mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::followanchor,
2547 : nsGkAtoms::_false, eCaseMatters)) {
2548 0 : return false;
2549 : }
2550 :
2551 0 : return (mPopupType == ePopupTypePanel &&
2552 0 : mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::type,
2553 0 : nsGkAtoms::arrow, eCaseMatters));
2554 : }
2555 :
2556 : bool
2557 0 : nsMenuPopupFrame::ShouldFollowAnchor(nsRect& aRect)
2558 : {
2559 0 : if (!ShouldFollowAnchor()) {
2560 0 : return false;
2561 : }
2562 :
2563 0 : nsIFrame* anchorFrame = mAnchorContent->GetPrimaryFrame();
2564 0 : if (anchorFrame) {
2565 0 : nsPresContext* rootPresContext = PresContext()->GetRootPresContext();
2566 0 : if (rootPresContext) {
2567 0 : aRect = ComputeAnchorRect(rootPresContext, anchorFrame);
2568 : }
2569 : }
2570 :
2571 0 : return true;
2572 : }
2573 :
2574 : void
2575 0 : nsMenuPopupFrame::CheckForAnchorChange(nsRect& aRect)
2576 : {
2577 : // Don't update if the popup isn't visible or we shouldn't be following the anchor.
2578 0 : if (!IsVisible() || !ShouldFollowAnchor()) {
2579 0 : return;
2580 : }
2581 :
2582 0 : bool shouldHide = false;
2583 :
2584 0 : nsPresContext* rootPresContext = PresContext()->GetRootPresContext();
2585 :
2586 : // If the frame for the anchor has gone away, hide the popup.
2587 0 : nsIFrame* anchor = mAnchorContent->GetPrimaryFrame();
2588 0 : if (!anchor || !rootPresContext) {
2589 0 : shouldHide = true;
2590 0 : } else if (!anchor->IsVisibleConsideringAncestors(VISIBILITY_CROSS_CHROME_CONTENT_BOUNDARY)) {
2591 : // If the anchor is now inside something that is invisible, hide the popup.
2592 0 : shouldHide = true;
2593 : } else {
2594 : // If the anchor is now inside a hidden parent popup, hide the popup.
2595 0 : nsIFrame* frame = anchor;
2596 0 : while (frame) {
2597 0 : nsMenuPopupFrame* popup = do_QueryFrame(frame);
2598 0 : if (popup && popup->PopupState() != ePopupShown) {
2599 0 : shouldHide = true;
2600 0 : break;
2601 : }
2602 :
2603 0 : frame = frame->GetParent();
2604 : }
2605 : }
2606 :
2607 0 : if (shouldHide) {
2608 0 : nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
2609 0 : if (pm) {
2610 : // As the caller will be iterating over the open popups, hide asyncronously.
2611 0 : pm->HidePopup(mContent, false, true, true, false);
2612 : }
2613 :
2614 0 : return;
2615 : }
2616 :
2617 0 : nsRect anchorRect = ComputeAnchorRect(rootPresContext, anchor);
2618 :
2619 : // If the rectangles are different, move the popup.
2620 0 : if (!anchorRect.IsEqualEdges(aRect)) {
2621 0 : aRect = anchorRect;
2622 0 : SetPopupPosition(nullptr, true, false, true);
2623 : }
2624 : }
|