Tue, 06 Jan 2015 21:39:09 +0100
Conditionally force memory storage according to privacy.thirdparty.isolate;
This solves Tor bug #9701, complying with disk avoidance documented in
https://www.torproject.org/projects/torbrowser/design/#disk-avoidance.
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/. */
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 "nsIDOMKeyEvent.h"
22 #include "nsIDOMScreen.h"
23 #include "nsIPresShell.h"
24 #include "nsFrameManager.h"
25 #include "nsIDocument.h"
26 #include "nsRect.h"
27 #include "nsIComponentManager.h"
28 #include "nsBoxLayoutState.h"
29 #include "nsIScrollableFrame.h"
30 #include "nsIRootBox.h"
31 #include "nsIDocShell.h"
32 #include "nsReadableUtils.h"
33 #include "nsUnicharUtils.h"
34 #include "nsLayoutUtils.h"
35 #include "nsContentUtils.h"
36 #include "nsCSSFrameConstructor.h"
37 #include "nsIPopupBoxObject.h"
38 #include "nsPIWindowRoot.h"
39 #include "nsIReflowCallback.h"
40 #include "nsBindingManager.h"
41 #include "nsIDocShellTreeOwner.h"
42 #include "nsIBaseWindow.h"
43 #include "nsISound.h"
44 #include "nsIScreenManager.h"
45 #include "nsIServiceManager.h"
46 #include "nsThemeConstants.h"
47 #include "nsDisplayList.h"
48 #include "mozilla/EventDispatcher.h"
49 #include "mozilla/EventStateManager.h"
50 #include "mozilla/EventStates.h"
51 #include "mozilla/Preferences.h"
52 #include "mozilla/LookAndFeel.h"
53 #include "mozilla/MouseEvents.h"
54 #include "mozilla/dom/Element.h"
55 #include <algorithm>
57 using namespace mozilla;
59 int8_t nsMenuPopupFrame::sDefaultLevelIsTop = -1;
61 // NS_NewMenuPopupFrame
62 //
63 // Wrapper for creating a new menu popup container
64 //
65 nsIFrame*
66 NS_NewMenuPopupFrame(nsIPresShell* aPresShell, nsStyleContext* aContext)
67 {
68 return new (aPresShell) nsMenuPopupFrame (aPresShell, aContext);
69 }
71 NS_IMPL_FRAMEARENA_HELPERS(nsMenuPopupFrame)
73 NS_QUERYFRAME_HEAD(nsMenuPopupFrame)
74 NS_QUERYFRAME_ENTRY(nsMenuPopupFrame)
75 NS_QUERYFRAME_TAIL_INHERITING(nsBoxFrame)
77 //
78 // nsMenuPopupFrame ctor
79 //
80 nsMenuPopupFrame::nsMenuPopupFrame(nsIPresShell* aShell, nsStyleContext* aContext)
81 :nsBoxFrame(aShell, aContext),
82 mCurrentMenu(nullptr),
83 mPrefSize(-1, -1),
84 mLastClientOffset(0, 0),
85 mPopupType(ePopupTypePanel),
86 mPopupState(ePopupClosed),
87 mPopupAlignment(POPUPALIGNMENT_NONE),
88 mPopupAnchor(POPUPALIGNMENT_NONE),
89 mPosition(POPUPPOSITION_UNKNOWN),
90 mConsumeRollupEvent(nsIPopupBoxObject::ROLLUP_DEFAULT),
91 mFlip(FlipType_Default),
92 mIsOpenChanged(false),
93 mIsContextMenu(false),
94 mAdjustOffsetForContextMenu(false),
95 mGeneratedChildren(false),
96 mMenuCanOverlapOSBar(false),
97 mShouldAutoPosition(true),
98 mInContentShell(true),
99 mIsMenuLocked(false),
100 mMouseTransparent(false),
101 mHFlip(false),
102 mVFlip(false)
103 {
104 // the preference name is backwards here. True means that the 'top' level is
105 // the default, and false means that the 'parent' level is the default.
106 if (sDefaultLevelIsTop >= 0)
107 return;
108 sDefaultLevelIsTop =
109 Preferences::GetBool("ui.panel.default_level_parent", false);
110 } // ctor
113 void
114 nsMenuPopupFrame::Init(nsIContent* aContent,
115 nsIFrame* aParent,
116 nsIFrame* aPrevInFlow)
117 {
118 nsBoxFrame::Init(aContent, aParent, aPrevInFlow);
120 // lookup if we're allowed to overlap the OS bar (menubar/taskbar) from the
121 // look&feel object
122 mMenuCanOverlapOSBar =
123 LookAndFeel::GetInt(LookAndFeel::eIntID_MenusCanOverlapOSBar) != 0;
125 CreatePopupView();
127 // XXX Hack. The popup's view should float above all other views,
128 // so we use the nsView::SetFloating() to tell the view manager
129 // about that constraint.
130 nsView* ourView = GetView();
131 nsViewManager* viewManager = ourView->GetViewManager();
132 viewManager->SetViewFloating(ourView, true);
134 mPopupType = ePopupTypePanel;
135 nsIDocument* doc = aContent->OwnerDoc();
136 int32_t namespaceID;
137 nsCOMPtr<nsIAtom> tag = doc->BindingManager()->ResolveTag(aContent, &namespaceID);
138 if (namespaceID == kNameSpaceID_XUL) {
139 if (tag == nsGkAtoms::menupopup || tag == nsGkAtoms::popup)
140 mPopupType = ePopupTypeMenu;
141 else if (tag == nsGkAtoms::tooltip)
142 mPopupType = ePopupTypeTooltip;
143 }
145 nsCOMPtr<nsIDocShellTreeItem> dsti = PresContext()->GetDocShell();
146 if (dsti && dsti->ItemType() == nsIDocShellTreeItem::typeChrome) {
147 mInContentShell = false;
148 }
150 // To improve performance, create the widget for the popup only if it is not
151 // a leaf. Leaf popups such as menus will create their widgets later when
152 // the popup opens.
153 if (!IsLeaf() && !ourView->HasWidget()) {
154 CreateWidgetForView(ourView);
155 }
157 if (aContent->NodeInfo()->Equals(nsGkAtoms::tooltip, kNameSpaceID_XUL) &&
158 aContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::_default,
159 nsGkAtoms::_true, eIgnoreCase)) {
160 nsIRootBox* rootBox =
161 nsIRootBox::GetRootBox(PresContext()->GetPresShell());
162 if (rootBox) {
163 rootBox->SetDefaultTooltip(aContent);
164 }
165 }
167 AddStateBits(NS_FRAME_IN_POPUP);
168 }
170 bool
171 nsMenuPopupFrame::IsNoAutoHide() const
172 {
173 // Panels with noautohide="true" don't hide when the mouse is clicked
174 // outside of them, or when another application is made active. Non-autohide
175 // panels cannot be used in content windows.
176 return (!mInContentShell && mPopupType == ePopupTypePanel &&
177 mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::noautohide,
178 nsGkAtoms::_true, eIgnoreCase));
179 }
181 nsPopupLevel
182 nsMenuPopupFrame::PopupLevel(bool aIsNoAutoHide) const
183 {
184 // The popup level is determined as follows, in this order:
185 // 1. non-panels (menus and tooltips) are always topmost
186 // 2. any specified level attribute
187 // 3. if a titlebar attribute is set, use the 'floating' level
188 // 4. if this is a noautohide panel, use the 'parent' level
189 // 5. use the platform-specific default level
191 // If this is not a panel, this is always a top-most popup.
192 if (mPopupType != ePopupTypePanel)
193 return ePopupLevelTop;
195 // If the level attribute has been set, use that.
196 static nsIContent::AttrValuesArray strings[] =
197 {&nsGkAtoms::top, &nsGkAtoms::parent, &nsGkAtoms::floating, nullptr};
198 switch (mContent->FindAttrValueIn(kNameSpaceID_None, nsGkAtoms::level,
199 strings, eCaseMatters)) {
200 case 0:
201 return ePopupLevelTop;
202 case 1:
203 return ePopupLevelParent;
204 case 2:
205 return ePopupLevelFloating;
206 }
208 // Panels with titlebars most likely want to be floating popups.
209 if (mContent->HasAttr(kNameSpaceID_None, nsGkAtoms::titlebar))
210 return ePopupLevelFloating;
212 // If this panel is a noautohide panel, the default is the parent level.
213 if (aIsNoAutoHide)
214 return ePopupLevelParent;
216 // Otherwise, the result depends on the platform.
217 return sDefaultLevelIsTop ? ePopupLevelTop : ePopupLevelParent;
218 }
220 void
221 nsMenuPopupFrame::EnsureWidget()
222 {
223 nsView* ourView = GetView();
224 if (!ourView->HasWidget()) {
225 NS_ASSERTION(!mGeneratedChildren && !GetFirstPrincipalChild(),
226 "Creating widget for MenuPopupFrame with children");
227 CreateWidgetForView(ourView);
228 }
229 }
231 nsresult
232 nsMenuPopupFrame::CreateWidgetForView(nsView* aView)
233 {
234 // Create a widget for ourselves.
235 nsWidgetInitData widgetData;
236 widgetData.mWindowType = eWindowType_popup;
237 widgetData.mBorderStyle = eBorderStyle_default;
238 widgetData.clipSiblings = true;
239 widgetData.mPopupHint = mPopupType;
240 widgetData.mNoAutoHide = IsNoAutoHide();
242 if (!mInContentShell) {
243 // A drag popup may be used for non-static translucent drag feedback
244 if (mPopupType == ePopupTypePanel &&
245 mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::type,
246 nsGkAtoms::drag, eIgnoreCase)) {
247 widgetData.mIsDragPopup = true;
248 }
250 // If mousethrough="always" is set directly on the popup, then the widget
251 // should ignore mouse events, passing them through to the content behind.
252 mMouseTransparent = GetStateBits() & NS_FRAME_MOUSE_THROUGH_ALWAYS;
253 widgetData.mMouseTransparent = mMouseTransparent;
254 }
256 nsAutoString title;
257 if (mContent && widgetData.mNoAutoHide) {
258 if (mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::titlebar,
259 nsGkAtoms::normal, eCaseMatters)) {
260 widgetData.mBorderStyle = eBorderStyle_title;
262 mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::label, title);
264 if (mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::close,
265 nsGkAtoms::_true, eCaseMatters)) {
266 widgetData.mBorderStyle =
267 static_cast<enum nsBorderStyle>(widgetData.mBorderStyle | eBorderStyle_close);
268 }
269 }
270 }
272 nsTransparencyMode mode = nsLayoutUtils::GetFrameTransparency(this, this);
273 nsIContent* parentContent = GetContent()->GetParent();
274 nsIAtom *tag = nullptr;
275 if (parentContent)
276 tag = parentContent->Tag();
277 widgetData.mSupportTranslucency = mode == eTransparencyTransparent;
278 widgetData.mDropShadow = !(mode == eTransparencyTransparent || tag == nsGkAtoms::menulist);
279 widgetData.mPopupLevel = PopupLevel(widgetData.mNoAutoHide);
281 // panels which have a parent level need a parent widget. This allows them to
282 // always appear in front of the parent window but behind other windows that
283 // should be in front of it.
284 nsCOMPtr<nsIWidget> parentWidget;
285 if (widgetData.mPopupLevel != ePopupLevelTop) {
286 nsCOMPtr<nsIDocShellTreeItem> dsti = PresContext()->GetDocShell();
287 if (!dsti)
288 return NS_ERROR_FAILURE;
290 nsCOMPtr<nsIDocShellTreeOwner> treeOwner;
291 dsti->GetTreeOwner(getter_AddRefs(treeOwner));
292 if (!treeOwner) return NS_ERROR_FAILURE;
294 nsCOMPtr<nsIBaseWindow> baseWindow(do_QueryInterface(treeOwner));
295 if (baseWindow)
296 baseWindow->GetMainWidget(getter_AddRefs(parentWidget));
297 }
299 nsresult rv = aView->CreateWidgetForPopup(&widgetData, parentWidget,
300 true, true);
301 if (NS_FAILED(rv)) {
302 return rv;
303 }
305 nsIWidget* widget = aView->GetWidget();
306 widget->SetTransparencyMode(mode);
307 widget->SetWindowShadowStyle(GetShadowStyle());
309 // most popups don't have a title so avoid setting the title if there isn't one
310 if (!title.IsEmpty()) {
311 widget->SetTitle(title);
312 }
314 return NS_OK;
315 }
317 uint8_t
318 nsMenuPopupFrame::GetShadowStyle()
319 {
320 uint8_t shadow = StyleUIReset()->mWindowShadow;
321 if (shadow != NS_STYLE_WINDOW_SHADOW_DEFAULT)
322 return shadow;
324 switch (StyleDisplay()->mAppearance) {
325 case NS_THEME_TOOLTIP:
326 return NS_STYLE_WINDOW_SHADOW_TOOLTIP;
327 case NS_THEME_MENUPOPUP:
328 return NS_STYLE_WINDOW_SHADOW_MENU;
329 }
330 return NS_STYLE_WINDOW_SHADOW_DEFAULT;
331 }
333 // this class is used for dispatching popupshown events asynchronously.
334 class nsXULPopupShownEvent : public nsRunnable
335 {
336 public:
337 nsXULPopupShownEvent(nsIContent *aPopup, nsPresContext* aPresContext)
338 : mPopup(aPopup), mPresContext(aPresContext)
339 {
340 }
342 NS_IMETHOD Run() MOZ_OVERRIDE
343 {
344 WidgetMouseEvent event(true, NS_XUL_POPUP_SHOWN, nullptr,
345 WidgetMouseEvent::eReal);
346 return EventDispatcher::Dispatch(mPopup, mPresContext, &event);
347 }
349 private:
350 nsCOMPtr<nsIContent> mPopup;
351 nsRefPtr<nsPresContext> mPresContext;
352 };
354 nsresult
355 nsMenuPopupFrame::SetInitialChildList(ChildListID aListID,
356 nsFrameList& aChildList)
357 {
358 // unless the list is empty, indicate that children have been generated.
359 if (aChildList.NotEmpty())
360 mGeneratedChildren = true;
361 return nsBoxFrame::SetInitialChildList(aListID, aChildList);
362 }
364 bool
365 nsMenuPopupFrame::IsLeaf() const
366 {
367 if (mGeneratedChildren)
368 return false;
370 if (mPopupType != ePopupTypeMenu) {
371 // any panel with a type attribute, such as the autocomplete popup,
372 // is always generated right away.
373 return !mContent->HasAttr(kNameSpaceID_None, nsGkAtoms::type);
374 }
376 // menu popups generate their child frames lazily only when opened, so
377 // behave like a leaf frame. However, generate child frames normally if
378 // the parent menu has a sizetopopup attribute. In this case the size of
379 // the parent menu is dependent on the size of the popup, so the frames
380 // need to exist in order to calculate this size.
381 nsIContent* parentContent = mContent->GetParent();
382 return (parentContent &&
383 !parentContent->HasAttr(kNameSpaceID_None, nsGkAtoms::sizetopopup));
384 }
386 void
387 nsMenuPopupFrame::LayoutPopup(nsBoxLayoutState& aState, nsIFrame* aParentMenu,
388 nsIFrame* aAnchor, bool aSizedToPopup)
389 {
390 if (!mGeneratedChildren)
391 return;
393 SchedulePaint();
395 bool shouldPosition = true;
396 bool isOpen = IsOpen();
397 if (!isOpen) {
398 // if the popup is not open, only do layout while showing or if the menu
399 // is sized to the popup
400 shouldPosition = (mPopupState == ePopupShowing);
401 if (!shouldPosition && !aSizedToPopup) {
402 RemoveStateBits(NS_FRAME_FIRST_REFLOW);
403 return;
404 }
405 }
407 // if the popup has just been opened, make sure the scrolled window is at 0,0
408 if (mIsOpenChanged) {
409 nsIScrollableFrame *scrollframe = do_QueryFrame(GetChildBox());
410 if (scrollframe) {
411 nsWeakFrame weakFrame(this);
412 scrollframe->ScrollTo(nsPoint(0,0), nsIScrollableFrame::INSTANT);
413 if (!weakFrame.IsAlive()) {
414 return;
415 }
416 }
417 }
419 // get the preferred, minimum and maximum size. If the menu is sized to the
420 // popup, then the popup's width is the menu's width.
421 nsSize prefSize = GetPrefSize(aState);
422 nsSize minSize = GetMinSize(aState);
423 nsSize maxSize = GetMaxSize(aState);
425 if (aSizedToPopup) {
426 prefSize.width = aParentMenu->GetRect().width;
427 }
428 prefSize = BoundsCheck(minSize, prefSize, maxSize);
430 // if the size changed then set the bounds to be the preferred size
431 bool sizeChanged = (mPrefSize != prefSize);
432 if (sizeChanged) {
433 SetBounds(aState, nsRect(0, 0, prefSize.width, prefSize.height), false);
434 mPrefSize = prefSize;
435 }
437 if (shouldPosition) {
438 SetPopupPosition(aAnchor, false, aSizedToPopup);
439 }
441 nsRect bounds(GetRect());
442 Layout(aState);
444 // if the width or height changed, readjust the popup position. This is a
445 // special case for tooltips where the preferred height doesn't include the
446 // real height for its inline element, but does once it is laid out.
447 // This is bug 228673 which doesn't have a simple fix.
448 if (!aParentMenu) {
449 nsSize newsize = GetSize();
450 if (newsize.width > bounds.width || newsize.height > bounds.height) {
451 // the size after layout was larger than the preferred size,
452 // so set the preferred size accordingly
453 mPrefSize = newsize;
454 if (isOpen) {
455 SetPopupPosition(nullptr, false, aSizedToPopup);
456 }
457 }
458 }
460 nsPresContext* pc = PresContext();
461 nsView* view = GetView();
463 if (sizeChanged) {
464 // If the size of the popup changed, apply any size constraints.
465 nsIWidget* widget = view->GetWidget();
466 if (widget) {
467 SetSizeConstraints(pc, widget, minSize, maxSize);
468 }
469 }
471 if (isOpen) {
472 nsViewManager* viewManager = view->GetViewManager();
473 nsRect rect = GetRect();
474 rect.x = rect.y = 0;
475 viewManager->ResizeView(view, rect);
477 viewManager->SetViewVisibility(view, nsViewVisibility_kShow);
478 mPopupState = ePopupOpenAndVisible;
479 nsContainerFrame::SyncFrameViewProperties(pc, this, nullptr, view, 0);
480 }
482 // finally, if the popup just opened, send a popupshown event
483 if (mIsOpenChanged) {
484 mIsOpenChanged = false;
485 nsCOMPtr<nsIRunnable> event = new nsXULPopupShownEvent(GetContent(), pc);
486 NS_DispatchToCurrentThread(event);
487 }
488 }
490 nsIContent*
491 nsMenuPopupFrame::GetTriggerContent(nsMenuPopupFrame* aMenuPopupFrame)
492 {
493 while (aMenuPopupFrame) {
494 if (aMenuPopupFrame->mTriggerContent)
495 return aMenuPopupFrame->mTriggerContent;
497 // check up the menu hierarchy until a popup with a trigger node is found
498 nsMenuFrame* menuFrame = do_QueryFrame(aMenuPopupFrame->GetParent());
499 if (!menuFrame)
500 break;
502 nsMenuParent* parentPopup = menuFrame->GetMenuParent();
503 if (!parentPopup || !parentPopup->IsMenu())
504 break;
506 aMenuPopupFrame = static_cast<nsMenuPopupFrame *>(parentPopup);
507 }
509 return nullptr;
510 }
512 void
513 nsMenuPopupFrame::InitPositionFromAnchorAlign(const nsAString& aAnchor,
514 const nsAString& aAlign)
515 {
516 mTriggerContent = nullptr;
518 if (aAnchor.EqualsLiteral("topleft"))
519 mPopupAnchor = POPUPALIGNMENT_TOPLEFT;
520 else if (aAnchor.EqualsLiteral("topright"))
521 mPopupAnchor = POPUPALIGNMENT_TOPRIGHT;
522 else if (aAnchor.EqualsLiteral("bottomleft"))
523 mPopupAnchor = POPUPALIGNMENT_BOTTOMLEFT;
524 else if (aAnchor.EqualsLiteral("bottomright"))
525 mPopupAnchor = POPUPALIGNMENT_BOTTOMRIGHT;
526 else if (aAnchor.EqualsLiteral("leftcenter"))
527 mPopupAnchor = POPUPALIGNMENT_LEFTCENTER;
528 else if (aAnchor.EqualsLiteral("rightcenter"))
529 mPopupAnchor = POPUPALIGNMENT_RIGHTCENTER;
530 else if (aAnchor.EqualsLiteral("topcenter"))
531 mPopupAnchor = POPUPALIGNMENT_TOPCENTER;
532 else if (aAnchor.EqualsLiteral("bottomcenter"))
533 mPopupAnchor = POPUPALIGNMENT_BOTTOMCENTER;
534 else
535 mPopupAnchor = POPUPALIGNMENT_NONE;
537 if (aAlign.EqualsLiteral("topleft"))
538 mPopupAlignment = POPUPALIGNMENT_TOPLEFT;
539 else if (aAlign.EqualsLiteral("topright"))
540 mPopupAlignment = POPUPALIGNMENT_TOPRIGHT;
541 else if (aAlign.EqualsLiteral("bottomleft"))
542 mPopupAlignment = POPUPALIGNMENT_BOTTOMLEFT;
543 else if (aAlign.EqualsLiteral("bottomright"))
544 mPopupAlignment = POPUPALIGNMENT_BOTTOMRIGHT;
545 else
546 mPopupAlignment = POPUPALIGNMENT_NONE;
548 mPosition = POPUPPOSITION_UNKNOWN;
549 }
551 void
552 nsMenuPopupFrame::InitializePopup(nsIContent* aAnchorContent,
553 nsIContent* aTriggerContent,
554 const nsAString& aPosition,
555 int32_t aXPos, int32_t aYPos,
556 bool aAttributesOverride)
557 {
558 EnsureWidget();
560 mPopupState = ePopupShowing;
561 mAnchorContent = aAnchorContent;
562 mTriggerContent = aTriggerContent;
563 mXPos = aXPos;
564 mYPos = aYPos;
565 mAdjustOffsetForContextMenu = false;
566 mVFlip = false;
567 mHFlip = false;
568 mAlignmentOffset = 0;
570 // if aAttributesOverride is true, then the popupanchor, popupalign and
571 // position attributes on the <popup> override those values passed in.
572 // If false, those attributes are only used if the values passed in are empty
573 if (aAnchorContent) {
574 nsAutoString anchor, align, position, flip;
575 mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::popupanchor, anchor);
576 mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::popupalign, align);
577 mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::position, position);
578 mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::flip, flip);
580 if (aAttributesOverride) {
581 // if the attributes are set, clear the offset position. Otherwise,
582 // the offset is used to adjust the position from the anchor point
583 if (anchor.IsEmpty() && align.IsEmpty() && position.IsEmpty())
584 position.Assign(aPosition);
585 else
586 mXPos = mYPos = 0;
587 }
588 else if (!aPosition.IsEmpty()) {
589 position.Assign(aPosition);
590 }
592 if (flip.EqualsLiteral("none")) {
593 mFlip = FlipType_None;
594 } else if (flip.EqualsLiteral("both")) {
595 mFlip = FlipType_Both;
596 } else if (flip.EqualsLiteral("slide")) {
597 mFlip = FlipType_Slide;
598 }
600 position.CompressWhitespace();
601 int32_t spaceIdx = position.FindChar(' ');
602 // if there is a space in the position, assume it is the anchor and
603 // alignment as two separate tokens.
604 if (spaceIdx >= 0) {
605 InitPositionFromAnchorAlign(Substring(position, 0, spaceIdx), Substring(position, spaceIdx + 1));
606 }
607 else if (position.EqualsLiteral("before_start")) {
608 mPopupAnchor = POPUPALIGNMENT_TOPLEFT;
609 mPopupAlignment = POPUPALIGNMENT_BOTTOMLEFT;
610 mPosition = POPUPPOSITION_BEFORESTART;
611 }
612 else if (position.EqualsLiteral("before_end")) {
613 mPopupAnchor = POPUPALIGNMENT_TOPRIGHT;
614 mPopupAlignment = POPUPALIGNMENT_BOTTOMRIGHT;
615 mPosition = POPUPPOSITION_BEFOREEND;
616 }
617 else if (position.EqualsLiteral("after_start")) {
618 mPopupAnchor = POPUPALIGNMENT_BOTTOMLEFT;
619 mPopupAlignment = POPUPALIGNMENT_TOPLEFT;
620 mPosition = POPUPPOSITION_AFTERSTART;
621 }
622 else if (position.EqualsLiteral("after_end")) {
623 mPopupAnchor = POPUPALIGNMENT_BOTTOMRIGHT;
624 mPopupAlignment = POPUPALIGNMENT_TOPRIGHT;
625 mPosition = POPUPPOSITION_AFTEREND;
626 }
627 else if (position.EqualsLiteral("start_before")) {
628 mPopupAnchor = POPUPALIGNMENT_TOPLEFT;
629 mPopupAlignment = POPUPALIGNMENT_TOPRIGHT;
630 mPosition = POPUPPOSITION_STARTBEFORE;
631 }
632 else if (position.EqualsLiteral("start_after")) {
633 mPopupAnchor = POPUPALIGNMENT_BOTTOMLEFT;
634 mPopupAlignment = POPUPALIGNMENT_BOTTOMRIGHT;
635 mPosition = POPUPPOSITION_STARTAFTER;
636 }
637 else if (position.EqualsLiteral("end_before")) {
638 mPopupAnchor = POPUPALIGNMENT_TOPRIGHT;
639 mPopupAlignment = POPUPALIGNMENT_TOPLEFT;
640 mPosition = POPUPPOSITION_ENDBEFORE;
641 }
642 else if (position.EqualsLiteral("end_after")) {
643 mPopupAnchor = POPUPALIGNMENT_BOTTOMRIGHT;
644 mPopupAlignment = POPUPALIGNMENT_BOTTOMLEFT;
645 mPosition = POPUPPOSITION_ENDAFTER;
646 }
647 else if (position.EqualsLiteral("overlap")) {
648 mPopupAnchor = POPUPALIGNMENT_TOPLEFT;
649 mPopupAlignment = POPUPALIGNMENT_TOPLEFT;
650 mPosition = POPUPPOSITION_OVERLAP;
651 }
652 else if (position.EqualsLiteral("after_pointer")) {
653 mPopupAnchor = POPUPALIGNMENT_TOPLEFT;
654 mPopupAlignment = POPUPALIGNMENT_TOPLEFT;
655 mPosition = POPUPPOSITION_AFTERPOINTER;
656 // XXXndeakin this is supposed to anchor vertically after, but with the
657 // horizontal position as the mouse pointer.
658 mYPos += 21;
659 }
660 else {
661 InitPositionFromAnchorAlign(anchor, align);
662 }
663 }
665 mScreenXPos = -1;
666 mScreenYPos = -1;
668 if (aAttributesOverride) {
669 // Use |left| and |top| dimension attributes to position the popup if
670 // present, as they may have been persisted.
671 nsAutoString left, top;
672 mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::left, left);
673 mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::top, top);
675 nsresult err;
676 if (!left.IsEmpty()) {
677 int32_t x = left.ToInteger(&err);
678 if (NS_SUCCEEDED(err))
679 mScreenXPos = x;
680 }
681 if (!top.IsEmpty()) {
682 int32_t y = top.ToInteger(&err);
683 if (NS_SUCCEEDED(err))
684 mScreenYPos = y;
685 }
686 }
687 }
689 void
690 nsMenuPopupFrame::InitializePopupAtScreen(nsIContent* aTriggerContent,
691 int32_t aXPos, int32_t aYPos,
692 bool aIsContextMenu)
693 {
694 EnsureWidget();
696 mPopupState = ePopupShowing;
697 mAnchorContent = nullptr;
698 mTriggerContent = aTriggerContent;
699 mScreenXPos = aXPos;
700 mScreenYPos = aYPos;
701 mFlip = FlipType_Default;
702 mPopupAnchor = POPUPALIGNMENT_NONE;
703 mPopupAlignment = POPUPALIGNMENT_NONE;
704 mIsContextMenu = aIsContextMenu;
705 mAdjustOffsetForContextMenu = aIsContextMenu;
706 }
708 void
709 nsMenuPopupFrame::InitializePopupWithAnchorAlign(nsIContent* aAnchorContent,
710 nsAString& aAnchor,
711 nsAString& aAlign,
712 int32_t aXPos, int32_t aYPos)
713 {
714 EnsureWidget();
716 mPopupState = ePopupShowing;
717 mAdjustOffsetForContextMenu = false;
718 mFlip = FlipType_Default;
720 // this popup opening function is provided for backwards compatibility
721 // only. It accepts either coordinates or an anchor and alignment value
722 // but doesn't use both together.
723 if (aXPos == -1 && aYPos == -1) {
724 mAnchorContent = aAnchorContent;
725 mScreenXPos = -1;
726 mScreenYPos = -1;
727 mXPos = 0;
728 mYPos = 0;
729 InitPositionFromAnchorAlign(aAnchor, aAlign);
730 }
731 else {
732 mAnchorContent = nullptr;
733 mPopupAnchor = POPUPALIGNMENT_NONE;
734 mPopupAlignment = POPUPALIGNMENT_NONE;
735 mScreenXPos = aXPos;
736 mScreenYPos = aYPos;
737 mXPos = aXPos;
738 mYPos = aYPos;
739 }
740 }
742 void
743 nsMenuPopupFrame::ShowPopup(bool aIsContextMenu, bool aSelectFirstItem)
744 {
745 mIsContextMenu = aIsContextMenu;
747 InvalidateFrameSubtree();
749 if (mPopupState == ePopupShowing) {
750 mPopupState = ePopupOpen;
751 mIsOpenChanged = true;
753 nsMenuFrame* menuFrame = do_QueryFrame(GetParent());
754 if (menuFrame) {
755 nsWeakFrame weakFrame(this);
756 menuFrame->PopupOpened();
757 if (!weakFrame.IsAlive())
758 return;
759 }
761 // do we need an actual reflow here?
762 // is SetPopupPosition all that is needed?
763 PresContext()->PresShell()->FrameNeedsReflow(this, nsIPresShell::eTreeChange,
764 NS_FRAME_HAS_DIRTY_CHILDREN);
766 if (mPopupType == ePopupTypeMenu) {
767 nsCOMPtr<nsISound> sound(do_CreateInstance("@mozilla.org/sound;1"));
768 if (sound)
769 sound->PlayEventSound(nsISound::EVENT_MENU_POPUP);
770 }
771 }
773 mShouldAutoPosition = true;
774 }
776 void
777 nsMenuPopupFrame::HidePopup(bool aDeselectMenu, nsPopupState aNewState)
778 {
779 NS_ASSERTION(aNewState == ePopupClosed || aNewState == ePopupInvisible,
780 "popup being set to unexpected state");
782 // don't hide the popup when it isn't open
783 if (mPopupState == ePopupClosed || mPopupState == ePopupShowing)
784 return;
786 // clear the trigger content if the popup is being closed. But don't clear
787 // it if the popup is just being made invisible as a popuphiding or command
788 // event may want to retrieve it.
789 if (aNewState == ePopupClosed) {
790 // if the popup had a trigger node set, clear the global window popup node
791 // as well
792 if (mTriggerContent) {
793 nsIDocument* doc = mContent->GetCurrentDoc();
794 if (doc) {
795 nsPIDOMWindow* win = doc->GetWindow();
796 if (win) {
797 nsCOMPtr<nsPIWindowRoot> root = win->GetTopWindowRoot();
798 if (root) {
799 root->SetPopupNode(nullptr);
800 }
801 }
802 }
803 }
804 mTriggerContent = nullptr;
805 mAnchorContent = nullptr;
806 }
808 // when invisible and about to be closed, HidePopup has already been called,
809 // so just set the new state to closed and return
810 if (mPopupState == ePopupInvisible) {
811 if (aNewState == ePopupClosed)
812 mPopupState = ePopupClosed;
813 return;
814 }
816 mPopupState = aNewState;
818 if (IsMenu())
819 SetCurrentMenuItem(nullptr);
821 mIncrementalString.Truncate();
823 LockMenuUntilClosed(false);
825 mIsOpenChanged = false;
826 mCurrentMenu = nullptr; // make sure no current menu is set
827 mHFlip = mVFlip = false;
829 nsView* view = GetView();
830 nsViewManager* viewManager = view->GetViewManager();
831 viewManager->SetViewVisibility(view, nsViewVisibility_kHide);
833 FireDOMEvent(NS_LITERAL_STRING("DOMMenuInactive"), mContent);
835 // XXX, bug 137033, In Windows, if mouse is outside the window when the menupopup closes, no
836 // mouse_enter/mouse_exit event will be fired to clear current hover state, we should clear it manually.
837 // This code may not the best solution, but we can leave it here until we find the better approach.
838 NS_ASSERTION(mContent->IsElement(), "How do we have a non-element?");
839 EventStates state = mContent->AsElement()->State();
841 if (state.HasState(NS_EVENT_STATE_HOVER)) {
842 EventStateManager* esm = PresContext()->EventStateManager();
843 esm->SetContentState(nullptr, NS_EVENT_STATE_HOVER);
844 }
846 nsMenuFrame* menuFrame = do_QueryFrame(GetParent());
847 if (menuFrame) {
848 menuFrame->PopupClosed(aDeselectMenu);
849 }
850 }
852 void
853 nsMenuPopupFrame::GetLayoutFlags(uint32_t& aFlags)
854 {
855 aFlags = NS_FRAME_NO_SIZE_VIEW | NS_FRAME_NO_MOVE_VIEW | NS_FRAME_NO_VISIBILITY;
856 }
858 ///////////////////////////////////////////////////////////////////////////////
859 // GetRootViewForPopup
860 // Retrieves the view for the popup widget that contains the given frame.
861 // If the given frame is not contained by a popup widget, return the
862 // root view of the root viewmanager.
863 nsView*
864 nsMenuPopupFrame::GetRootViewForPopup(nsIFrame* aStartFrame)
865 {
866 nsView* view = aStartFrame->GetClosestView();
867 NS_ASSERTION(view, "frame must have a closest view!");
868 while (view) {
869 // Walk up the view hierarchy looking for a view whose widget has a
870 // window type of eWindowType_popup - in other words a popup window
871 // widget. If we find one, this is the view we want.
872 nsIWidget* widget = view->GetWidget();
873 if (widget && widget->WindowType() == eWindowType_popup) {
874 return view;
875 }
877 nsView* temp = view->GetParent();
878 if (!temp) {
879 // Otherwise, we've walked all the way up to the root view and not
880 // found a view for a popup window widget. Just return the root view.
881 return view;
882 }
883 view = temp;
884 }
886 return nullptr;
887 }
889 nsPoint
890 nsMenuPopupFrame::AdjustPositionForAnchorAlign(nsRect& anchorRect,
891 FlipStyle& aHFlip, FlipStyle& aVFlip)
892 {
893 // flip the anchor and alignment for right-to-left
894 int8_t popupAnchor(mPopupAnchor);
895 int8_t popupAlign(mPopupAlignment);
896 if (IsDirectionRTL()) {
897 // no need to flip the centered anchor types vertically
898 if (popupAnchor <= POPUPALIGNMENT_LEFTCENTER) {
899 popupAnchor = -popupAnchor;
900 }
901 popupAlign = -popupAlign;
902 }
904 // first, determine at which corner of the anchor the popup should appear
905 nsPoint pnt;
906 switch (popupAnchor) {
907 case POPUPALIGNMENT_LEFTCENTER:
908 pnt = nsPoint(anchorRect.x, anchorRect.y + anchorRect.height / 2);
909 anchorRect.y = pnt.y;
910 anchorRect.height = 0;
911 break;
912 case POPUPALIGNMENT_RIGHTCENTER:
913 pnt = nsPoint(anchorRect.XMost(), anchorRect.y + anchorRect.height / 2);
914 anchorRect.y = pnt.y;
915 anchorRect.height = 0;
916 break;
917 case POPUPALIGNMENT_TOPCENTER:
918 pnt = nsPoint(anchorRect.x + anchorRect.width / 2, anchorRect.y);
919 anchorRect.x = pnt.x;
920 anchorRect.width = 0;
921 break;
922 case POPUPALIGNMENT_BOTTOMCENTER:
923 pnt = nsPoint(anchorRect.x + anchorRect.width / 2, anchorRect.YMost());
924 anchorRect.x = pnt.x;
925 anchorRect.width = 0;
926 break;
927 case POPUPALIGNMENT_TOPRIGHT:
928 pnt = anchorRect.TopRight();
929 break;
930 case POPUPALIGNMENT_BOTTOMLEFT:
931 pnt = anchorRect.BottomLeft();
932 break;
933 case POPUPALIGNMENT_BOTTOMRIGHT:
934 pnt = anchorRect.BottomRight();
935 break;
936 case POPUPALIGNMENT_TOPLEFT:
937 default:
938 pnt = anchorRect.TopLeft();
939 break;
940 }
942 // If the alignment is on the right edge of the popup, move the popup left
943 // by the width. Similarly, if the alignment is on the bottom edge of the
944 // popup, move the popup up by the height. In addition, account for the
945 // margins of the popup on the edge on which it is aligned.
946 nsMargin margin(0, 0, 0, 0);
947 StyleMargin()->GetMargin(margin);
948 switch (popupAlign) {
949 case POPUPALIGNMENT_TOPRIGHT:
950 pnt.MoveBy(-mRect.width - margin.right, margin.top);
951 break;
952 case POPUPALIGNMENT_BOTTOMLEFT:
953 pnt.MoveBy(margin.left, -mRect.height - margin.bottom);
954 break;
955 case POPUPALIGNMENT_BOTTOMRIGHT:
956 pnt.MoveBy(-mRect.width - margin.right, -mRect.height - margin.bottom);
957 break;
958 case POPUPALIGNMENT_TOPLEFT:
959 default:
960 pnt.MoveBy(margin.left, margin.top);
961 break;
962 }
964 // Flipping horizontally is allowed as long as the popup is above or below
965 // the anchor. This will happen if both the anchor and alignment are top or
966 // both are bottom, but different values. Similarly, flipping vertically is
967 // allowed if the popup is to the left or right of the anchor. In this case,
968 // the values of the constants are such that both must be positive or both
969 // must be negative. A special case, used for overlap, allows flipping
970 // vertically as well.
971 // If we are flipping in both directions, we want to set a flip style both
972 // horizontally and vertically. However, we want to flip on the inside edge
973 // of the anchor. Consider the example of a typical dropdown menu.
974 // Vertically, we flip the popup on the outside edges of the anchor menu,
975 // however horizontally, we want to to use the inside edges so the popup
976 // still appears underneath the anchor menu instead of floating off the
977 // side of the menu.
978 switch (popupAnchor) {
979 case POPUPALIGNMENT_LEFTCENTER:
980 case POPUPALIGNMENT_RIGHTCENTER:
981 aHFlip = FlipStyle_Outside;
982 aVFlip = FlipStyle_Inside;
983 break;
984 case POPUPALIGNMENT_TOPCENTER:
985 case POPUPALIGNMENT_BOTTOMCENTER:
986 aHFlip = FlipStyle_Inside;
987 aVFlip = FlipStyle_Outside;
988 break;
989 default:
990 {
991 FlipStyle anchorEdge = mFlip == FlipType_Both ? FlipStyle_Inside : FlipStyle_None;
992 aHFlip = (popupAnchor == -popupAlign) ? FlipStyle_Outside : anchorEdge;
993 if (((popupAnchor > 0) == (popupAlign > 0)) ||
994 (popupAnchor == POPUPALIGNMENT_TOPLEFT && popupAlign == POPUPALIGNMENT_TOPLEFT))
995 aVFlip = FlipStyle_Outside;
996 else
997 aVFlip = anchorEdge;
998 break;
999 }
1000 }
1002 return pnt;
1003 }
1005 nscoord
1006 nsMenuPopupFrame::SlideOrResize(nscoord& aScreenPoint, nscoord aSize,
1007 nscoord aScreenBegin, nscoord aScreenEnd,
1008 nscoord *aOffset)
1009 {
1010 // The popup may be positioned such that either the left/top or bottom/right
1011 // is outside the screen - but never both.
1012 nscoord newPos =
1013 std::max(aScreenBegin, std::min(aScreenEnd - aSize, aScreenPoint));
1014 *aOffset = newPos - aScreenPoint;
1015 aScreenPoint = newPos;
1016 return std::min(aSize, aScreenEnd - aScreenPoint);
1017 }
1019 nscoord
1020 nsMenuPopupFrame::FlipOrResize(nscoord& aScreenPoint, nscoord aSize,
1021 nscoord aScreenBegin, nscoord aScreenEnd,
1022 nscoord aAnchorBegin, nscoord aAnchorEnd,
1023 nscoord aMarginBegin, nscoord aMarginEnd,
1024 nscoord aOffsetForContextMenu, FlipStyle aFlip,
1025 bool* aFlipSide)
1026 {
1027 // all of the coordinates used here are in app units relative to the screen
1028 nscoord popupSize = aSize;
1029 if (aScreenPoint < aScreenBegin) {
1030 // at its current position, the popup would extend past the left or top
1031 // edge of the screen, so it will have to be moved or resized.
1032 if (aFlip) {
1033 // for inside flips, we flip on the opposite side of the anchor
1034 nscoord startpos = aFlip == FlipStyle_Outside ? aAnchorBegin : aAnchorEnd;
1035 nscoord endpos = aFlip == FlipStyle_Outside ? aAnchorEnd : aAnchorBegin;
1037 // check whether there is more room to the left and right (or top and
1038 // bottom) of the anchor and put the popup on the side with more room.
1039 if (startpos - aScreenBegin >= aScreenEnd - endpos) {
1040 aScreenPoint = aScreenBegin;
1041 popupSize = startpos - aScreenPoint - aMarginEnd;
1042 }
1043 else {
1044 // If the newly calculated position is different than the existing
1045 // position, flip such that the popup is to the right or bottom of the
1046 // anchor point instead . However, when flipping use the same margin
1047 // size.
1048 nscoord newScreenPoint = endpos + aMarginEnd;
1049 if (newScreenPoint != aScreenPoint) {
1050 *aFlipSide = true;
1051 aScreenPoint = newScreenPoint;
1052 // check if the new position is still off the right or bottom edge of
1053 // the screen. If so, resize the popup.
1054 if (aScreenPoint + aSize > aScreenEnd) {
1055 popupSize = aScreenEnd - aScreenPoint;
1056 }
1057 }
1058 }
1059 }
1060 else {
1061 aScreenPoint = aScreenBegin;
1062 }
1063 }
1064 else if (aScreenPoint + aSize > aScreenEnd) {
1065 // at its current position, the popup would extend past the right or
1066 // bottom edge of the screen, so it will have to be moved or resized.
1067 if (aFlip) {
1068 // for inside flips, we flip on the opposite side of the anchor
1069 nscoord startpos = aFlip == FlipStyle_Outside ? aAnchorBegin : aAnchorEnd;
1070 nscoord endpos = aFlip == FlipStyle_Outside ? aAnchorEnd : aAnchorBegin;
1072 // check whether there is more room to the left and right (or top and
1073 // bottom) of the anchor and put the popup on the side with more room.
1074 if (aScreenEnd - endpos >= startpos - aScreenBegin) {
1075 if (mIsContextMenu) {
1076 aScreenPoint = aScreenEnd - aSize;
1077 }
1078 else {
1079 aScreenPoint = endpos + aMarginBegin;
1080 popupSize = aScreenEnd - aScreenPoint;
1081 }
1082 }
1083 else {
1084 // if the newly calculated position is different than the existing
1085 // position, we flip such that the popup is to the left or top of the
1086 // anchor point instead.
1087 nscoord newScreenPoint = startpos - aSize - aMarginBegin - aOffsetForContextMenu;
1088 if (newScreenPoint != aScreenPoint) {
1089 *aFlipSide = true;
1090 aScreenPoint = newScreenPoint;
1092 // check if the new position is still off the left or top edge of the
1093 // screen. If so, resize the popup.
1094 if (aScreenPoint < aScreenBegin) {
1095 aScreenPoint = aScreenBegin;
1096 if (!mIsContextMenu) {
1097 popupSize = startpos - aScreenPoint - aMarginBegin;
1098 }
1099 }
1100 }
1101 }
1102 }
1103 else {
1104 aScreenPoint = aScreenEnd - aSize;
1105 }
1106 }
1108 // Make sure that the point is within the screen boundaries and that the
1109 // size isn't off the edge of the screen. This can happen when a large
1110 // positive or negative margin is used.
1111 if (aScreenPoint < aScreenBegin) {
1112 aScreenPoint = aScreenBegin;
1113 }
1114 if (aScreenPoint > aScreenEnd) {
1115 aScreenPoint = aScreenEnd - aSize;
1116 }
1118 // If popupSize ended up being negative, or the original size was actually
1119 // smaller than the calculated popup size, just use the original size instead.
1120 if (popupSize <= 0 || aSize < popupSize) {
1121 popupSize = aSize;
1122 }
1123 return std::min(popupSize, aScreenEnd - aScreenPoint);
1124 }
1126 nsresult
1127 nsMenuPopupFrame::SetPopupPosition(nsIFrame* aAnchorFrame, bool aIsMove, bool aSizedToPopup)
1128 {
1129 if (!mShouldAutoPosition)
1130 return NS_OK;
1132 // If this is due to a move, return early if the popup hasn't been laid out
1133 // yet. On Windows, this can happen when using a drag popup before it opens.
1134 if (aIsMove && (mPrefSize.width == -1 || mPrefSize.height == -1)) {
1135 return NS_OK;
1136 }
1138 nsPresContext* presContext = PresContext();
1139 nsIFrame* rootFrame = presContext->PresShell()->FrameManager()->GetRootFrame();
1140 NS_ASSERTION(rootFrame->GetView() && GetView() &&
1141 rootFrame->GetView() == GetView()->GetParent(),
1142 "rootFrame's view is not our view's parent???");
1144 // if the frame is not specified, use the anchor node passed to OpenPopup. If
1145 // that wasn't specified either, use the root frame. Note that mAnchorContent
1146 // might be a different document so its presshell must be used.
1147 if (!aAnchorFrame) {
1148 if (mAnchorContent) {
1149 aAnchorFrame = mAnchorContent->GetPrimaryFrame();
1150 }
1152 if (!aAnchorFrame) {
1153 aAnchorFrame = rootFrame;
1154 if (!aAnchorFrame)
1155 return NS_OK;
1156 }
1157 }
1159 // the dimensions of the anchor in its app units
1160 nsRect parentRect = aAnchorFrame->GetScreenRectInAppUnits();
1162 // the anchor may be in a different document with a different scale,
1163 // so adjust the size so that it is in the app units of the popup instead
1164 // of the anchor.
1165 parentRect = parentRect.ConvertAppUnitsRoundOut(
1166 aAnchorFrame->PresContext()->AppUnitsPerDevPixel(),
1167 presContext->AppUnitsPerDevPixel());
1169 // Set the popup's size to the preferred size. Below, this size will be
1170 // adjusted to fit on the screen or within the content area. If the anchor
1171 // is sized to the popup, use the anchor's width instead of the preferred
1172 // width. The preferred size should already be set by the parent frame.
1173 NS_ASSERTION(mPrefSize.width >= 0 || mPrefSize.height >= 0,
1174 "preferred size of popup not set");
1175 mRect.width = aSizedToPopup ? parentRect.width : mPrefSize.width;
1176 mRect.height = mPrefSize.height;
1178 // the screen position in app units where the popup should appear
1179 nsPoint screenPoint;
1181 // For anchored popups, the anchor rectangle. For non-anchored popups, the
1182 // size will be 0.
1183 nsRect anchorRect = parentRect;
1185 // indicators of whether the popup should be flipped or resized.
1186 FlipStyle hFlip = FlipStyle_None, vFlip = FlipStyle_None;
1188 nsMargin margin(0, 0, 0, 0);
1189 StyleMargin()->GetMargin(margin);
1191 // the screen rectangle of the root frame, in dev pixels.
1192 nsRect rootScreenRect = rootFrame->GetScreenRectInAppUnits();
1194 nsDeviceContext* devContext = presContext->DeviceContext();
1195 nscoord offsetForContextMenu = 0;
1197 bool isNoAutoHide = IsNoAutoHide();
1198 nsPopupLevel popupLevel = PopupLevel(isNoAutoHide);
1200 if (IsAnchored()) {
1201 // if we are anchored, there are certain things we don't want to do when
1202 // repositioning the popup to fit on the screen, such as end up positioned
1203 // over the anchor, for instance a popup appearing over the menu label.
1204 // When doing this reposition, we want to move the popup to the side with
1205 // the most room. The combination of anchor and alignment dictate if we
1206 // readjust above/below or to the left/right.
1207 if (mAnchorContent) {
1208 // move the popup according to the anchor and alignment. This will also
1209 // tell us which axis the popup is flush against in case we have to move
1210 // it around later. The AdjustPositionForAnchorAlign method accounts for
1211 // the popup's margin.
1212 screenPoint = AdjustPositionForAnchorAlign(anchorRect, hFlip, vFlip);
1213 }
1214 else {
1215 // with no anchor, the popup is positioned relative to the root frame
1216 anchorRect = rootScreenRect;
1217 screenPoint = anchorRect.TopLeft() + nsPoint(margin.left, margin.top);
1218 }
1220 // mXPos and mYPos specify an additonal offset passed to OpenPopup that
1221 // should be added to the position. We also add the offset to the anchor
1222 // pos so a later flip/resize takes the offset into account.
1223 nscoord anchorXOffset = presContext->CSSPixelsToAppUnits(mXPos);
1224 if (IsDirectionRTL()) {
1225 screenPoint.x -= anchorXOffset;
1226 anchorRect.x -= anchorXOffset;
1227 } else {
1228 screenPoint.x += anchorXOffset;
1229 anchorRect.x += anchorXOffset;
1230 }
1231 nscoord anchorYOffset = presContext->CSSPixelsToAppUnits(mYPos);
1232 screenPoint.y += anchorYOffset;
1233 anchorRect.y += anchorYOffset;
1235 // If this is a noautohide popup, set the screen coordinates of the popup.
1236 // This way, the popup stays at the location where it was opened even when
1237 // the window is moved. Popups at the parent level follow the parent
1238 // window as it is moved and remained anchored, so we want to maintain the
1239 // anchoring instead.
1240 if (isNoAutoHide && popupLevel != ePopupLevelParent) {
1241 // Account for the margin that will end up being added to the screen coordinate
1242 // the next time SetPopupPosition is called.
1243 mScreenXPos = presContext->AppUnitsToIntCSSPixels(screenPoint.x - margin.left);
1244 mScreenYPos = presContext->AppUnitsToIntCSSPixels(screenPoint.y - margin.top);
1245 }
1246 }
1247 else {
1248 // the popup is positioned at a screen coordinate.
1249 // first convert the screen position in mScreenXPos and mScreenYPos from
1250 // CSS pixels into device pixels, ignoring any scaling as mScreenXPos and
1251 // mScreenYPos are unscaled screen coordinates.
1252 int32_t factor = devContext->UnscaledAppUnitsPerDevPixel();
1254 // context menus should be offset by two pixels so that they don't appear
1255 // directly where the cursor is. Otherwise, it is too easy to have the
1256 // context menu close up again.
1257 if (mAdjustOffsetForContextMenu) {
1258 int32_t offsetForContextMenuDev =
1259 nsPresContext::CSSPixelsToAppUnits(CONTEXT_MENU_OFFSET_PIXELS) / factor;
1260 offsetForContextMenu = presContext->DevPixelsToAppUnits(offsetForContextMenuDev);
1261 }
1263 // next, convert into app units accounting for the scaling
1264 screenPoint.x = presContext->DevPixelsToAppUnits(
1265 nsPresContext::CSSPixelsToAppUnits(mScreenXPos) / factor);
1266 screenPoint.y = presContext->DevPixelsToAppUnits(
1267 nsPresContext::CSSPixelsToAppUnits(mScreenYPos) / factor);
1268 anchorRect = nsRect(screenPoint, nsSize(0, 0));
1270 // add the margins on the popup
1271 screenPoint.MoveBy(margin.left + offsetForContextMenu,
1272 margin.top + offsetForContextMenu);
1274 // screen positioned popups can be flipped vertically but never horizontally
1275 vFlip = FlipStyle_Outside;
1276 }
1278 // If a panel is being moved or has flip="none", don't constrain or flip it. But always do this for
1279 // content shells, so that the popup doesn't extend outside the containing frame.
1280 if (mInContentShell || (mFlip != FlipType_None && (!aIsMove || mPopupType != ePopupTypePanel))) {
1281 nsRect screenRect = GetConstraintRect(anchorRect, rootScreenRect, popupLevel);
1283 // Ensure that anchorRect is on screen.
1284 anchorRect = anchorRect.Intersect(screenRect);
1286 // shrink the the popup down if it is larger than the screen size
1287 if (mRect.width > screenRect.width)
1288 mRect.width = screenRect.width;
1289 if (mRect.height > screenRect.height)
1290 mRect.height = screenRect.height;
1292 // at this point the anchor (anchorRect) is within the available screen
1293 // area (screenRect) and the popup is known to be no larger than the screen.
1295 // We might want to "slide" an arrow if the panel is of the correct type -
1296 // but we can only slide on one axis - the other axis must be "flipped or
1297 // resized" as normal.
1298 bool slideHorizontal = false, slideVertical = false;
1299 if (mFlip == FlipType_Slide) {
1300 int8_t position = GetAlignmentPosition();
1301 slideHorizontal = position >= POPUPPOSITION_BEFORESTART &&
1302 position <= POPUPPOSITION_AFTEREND;
1303 slideVertical = position >= POPUPPOSITION_STARTBEFORE &&
1304 position <= POPUPPOSITION_ENDAFTER;
1305 }
1307 // Next, check if there is enough space to show the popup at full size when
1308 // positioned at screenPoint. If not, flip the popups to the opposite side
1309 // of their anchor point, or resize them as necessary.
1310 if (slideHorizontal) {
1311 mRect.width = SlideOrResize(screenPoint.x, mRect.width, screenRect.x,
1312 screenRect.XMost(), &mAlignmentOffset);
1313 } else {
1314 mRect.width = FlipOrResize(screenPoint.x, mRect.width, screenRect.x,
1315 screenRect.XMost(), anchorRect.x, anchorRect.XMost(),
1316 margin.left, margin.right, offsetForContextMenu, hFlip,
1317 &mHFlip);
1318 }
1319 if (slideVertical) {
1320 mRect.height = SlideOrResize(screenPoint.y, mRect.height, screenRect.y,
1321 screenRect.YMost(), &mAlignmentOffset);
1322 } else {
1323 mRect.height = FlipOrResize(screenPoint.y, mRect.height, screenRect.y,
1324 screenRect.YMost(), anchorRect.y, anchorRect.YMost(),
1325 margin.top, margin.bottom, offsetForContextMenu, vFlip,
1326 &mVFlip);
1327 }
1329 NS_ASSERTION(screenPoint.x >= screenRect.x && screenPoint.y >= screenRect.y &&
1330 screenPoint.x + mRect.width <= screenRect.XMost() &&
1331 screenPoint.y + mRect.height <= screenRect.YMost(),
1332 "Popup is offscreen");
1333 }
1335 // snap the popup's position in screen coordinates to device pixels,
1336 // see bug 622507, bug 961431
1337 screenPoint.x = presContext->RoundAppUnitsToNearestDevPixels(screenPoint.x);
1338 screenPoint.y = presContext->RoundAppUnitsToNearestDevPixels(screenPoint.y);
1340 // determine the x and y position of the view by subtracting the desired
1341 // screen position from the screen position of the root frame.
1342 nsPoint viewPoint = screenPoint - rootScreenRect.TopLeft();
1344 nsView* view = GetView();
1345 NS_ASSERTION(view, "popup with no view");
1347 // Offset the position by the width and height of the borders and titlebar.
1348 // Even though GetClientOffset should return (0, 0) when there is no
1349 // titlebar or borders, we skip these calculations anyway for non-panels
1350 // to save time since they will never have a titlebar.
1351 nsIWidget* widget = view->GetWidget();
1352 if (mPopupType == ePopupTypePanel && widget) {
1353 mLastClientOffset = widget->GetClientOffset();
1354 viewPoint.x += presContext->DevPixelsToAppUnits(mLastClientOffset.x);
1355 viewPoint.y += presContext->DevPixelsToAppUnits(mLastClientOffset.y);
1356 }
1358 presContext->GetPresShell()->GetViewManager()->
1359 MoveViewTo(view, viewPoint.x, viewPoint.y);
1361 // Now that we've positioned the view, sync up the frame's origin.
1362 nsBoxFrame::SetPosition(viewPoint - GetParent()->GetOffsetTo(rootFrame));
1364 if (aSizedToPopup) {
1365 nsBoxLayoutState state(PresContext());
1366 // XXXndeakin can parentSize.width still extend outside?
1367 SetBounds(state, nsRect(mRect.x, mRect.y, parentRect.width, mRect.height));
1368 }
1370 return NS_OK;
1371 }
1373 /* virtual */ nsMenuFrame*
1374 nsMenuPopupFrame::GetCurrentMenuItem()
1375 {
1376 return mCurrentMenu;
1377 }
1379 nsRect
1380 nsMenuPopupFrame::GetConstraintRect(const nsRect& aAnchorRect,
1381 const nsRect& aRootScreenRect,
1382 nsPopupLevel aPopupLevel)
1383 {
1384 nsIntRect screenRectPixels;
1385 nsPresContext* presContext = PresContext();
1387 // determine the available screen space. It will be reduced by the OS chrome
1388 // such as menubars. It addition, for content shells, it will be the area of
1389 // the content rather than the screen.
1390 nsCOMPtr<nsIScreen> screen;
1391 nsCOMPtr<nsIScreenManager> sm(do_GetService("@mozilla.org/gfx/screenmanager;1"));
1392 if (sm) {
1393 // for content shells, get the screen where the root frame is located.
1394 // This is because we need to constrain the content to this content area,
1395 // so we should use the same screen. Otherwise, use the screen where the
1396 // anchor is located.
1397 nsRect rect = mInContentShell ? aRootScreenRect : aAnchorRect;
1398 // nsIScreenManager::ScreenForRect wants the coordinates in CSS pixels
1399 int32_t width = std::max(1, nsPresContext::AppUnitsToIntCSSPixels(rect.width));
1400 int32_t height = std::max(1, nsPresContext::AppUnitsToIntCSSPixels(rect.height));
1401 sm->ScreenForRect(nsPresContext::AppUnitsToIntCSSPixels(rect.x),
1402 nsPresContext::AppUnitsToIntCSSPixels(rect.y),
1403 width, height, getter_AddRefs(screen));
1404 if (screen) {
1405 // Non-top-level popups (which will always be panels)
1406 // should never overlap the OS bar:
1407 bool dontOverlapOSBar = aPopupLevel != ePopupLevelTop;
1408 // get the total screen area if the popup is allowed to overlap it.
1409 if (!dontOverlapOSBar && mMenuCanOverlapOSBar && !mInContentShell)
1410 screen->GetRect(&screenRectPixels.x, &screenRectPixels.y,
1411 &screenRectPixels.width, &screenRectPixels.height);
1412 else
1413 screen->GetAvailRect(&screenRectPixels.x, &screenRectPixels.y,
1414 &screenRectPixels.width, &screenRectPixels.height);
1415 }
1416 }
1418 nsRect screenRect = screenRectPixels.ToAppUnits(presContext->AppUnitsPerDevPixel());
1419 if (mInContentShell) {
1420 // for content shells, clip to the client area rather than the screen area
1421 screenRect.IntersectRect(screenRect, aRootScreenRect);
1422 }
1424 return screenRect;
1425 }
1427 void nsMenuPopupFrame::CanAdjustEdges(int8_t aHorizontalSide, int8_t aVerticalSide, nsIntPoint& aChange)
1428 {
1429 int8_t popupAlign(mPopupAlignment);
1430 if (IsDirectionRTL()) {
1431 popupAlign = -popupAlign;
1432 }
1434 if (aHorizontalSide == (mHFlip ? NS_SIDE_RIGHT : NS_SIDE_LEFT)) {
1435 if (popupAlign == POPUPALIGNMENT_TOPLEFT || popupAlign == POPUPALIGNMENT_BOTTOMLEFT) {
1436 aChange.x = 0;
1437 }
1438 }
1439 else if (aHorizontalSide == (mHFlip ? NS_SIDE_LEFT : NS_SIDE_RIGHT)) {
1440 if (popupAlign == POPUPALIGNMENT_TOPRIGHT || popupAlign == POPUPALIGNMENT_BOTTOMRIGHT) {
1441 aChange.x = 0;
1442 }
1443 }
1445 if (aVerticalSide == (mVFlip ? NS_SIDE_BOTTOM : NS_SIDE_TOP)) {
1446 if (popupAlign == POPUPALIGNMENT_TOPLEFT || popupAlign == POPUPALIGNMENT_TOPRIGHT) {
1447 aChange.y = 0;
1448 }
1449 }
1450 else if (aVerticalSide == (mVFlip ? NS_SIDE_TOP : NS_SIDE_BOTTOM)) {
1451 if (popupAlign == POPUPALIGNMENT_BOTTOMLEFT || popupAlign == POPUPALIGNMENT_BOTTOMRIGHT) {
1452 aChange.y = 0;
1453 }
1454 }
1455 }
1457 bool nsMenuPopupFrame::ConsumeOutsideClicks()
1458 {
1459 // If the popup has explicitly set a consume mode, honor that.
1460 if (mConsumeRollupEvent != nsIPopupBoxObject::ROLLUP_DEFAULT)
1461 return (mConsumeRollupEvent == nsIPopupBoxObject::ROLLUP_CONSUME);
1463 if (mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::consumeoutsideclicks,
1464 nsGkAtoms::_true, eCaseMatters))
1465 return true;
1466 if (mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::consumeoutsideclicks,
1467 nsGkAtoms::_false, eCaseMatters))
1468 return false;
1470 nsCOMPtr<nsIContent> parentContent = mContent->GetParent();
1471 if (parentContent) {
1472 nsINodeInfo *ni = parentContent->NodeInfo();
1473 if (ni->Equals(nsGkAtoms::menulist, kNameSpaceID_XUL))
1474 return true; // Consume outside clicks for combo boxes on all platforms
1475 #if defined(XP_WIN)
1476 // Don't consume outside clicks for menus in Windows
1477 if (ni->Equals(nsGkAtoms::menu, kNameSpaceID_XUL) ||
1478 ni->Equals(nsGkAtoms::splitmenu, kNameSpaceID_XUL) ||
1479 ni->Equals(nsGkAtoms::popupset, kNameSpaceID_XUL) ||
1480 ((ni->Equals(nsGkAtoms::button, kNameSpaceID_XUL) ||
1481 ni->Equals(nsGkAtoms::toolbarbutton, kNameSpaceID_XUL)) &&
1482 (parentContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::type,
1483 nsGkAtoms::menu, eCaseMatters) ||
1484 parentContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::type,
1485 nsGkAtoms::menuButton, eCaseMatters)))) {
1486 return false;
1487 }
1488 #endif
1489 if (ni->Equals(nsGkAtoms::textbox, kNameSpaceID_XUL)) {
1490 // Don't consume outside clicks for autocomplete widget
1491 if (parentContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::type,
1492 nsGkAtoms::autocomplete, eCaseMatters))
1493 return false;
1494 }
1495 }
1497 return true;
1498 }
1500 // XXXroc this is megalame. Fossicking around for a frame of the right
1501 // type is a recipe for disaster in the long term.
1502 nsIScrollableFrame* nsMenuPopupFrame::GetScrollFrame(nsIFrame* aStart)
1503 {
1504 if (!aStart)
1505 return nullptr;
1507 // try start frame and siblings
1508 nsIFrame* currFrame = aStart;
1509 do {
1510 nsIScrollableFrame* sf = do_QueryFrame(currFrame);
1511 if (sf)
1512 return sf;
1513 currFrame = currFrame->GetNextSibling();
1514 } while (currFrame);
1516 // try children
1517 currFrame = aStart;
1518 do {
1519 nsIFrame* childFrame = currFrame->GetFirstPrincipalChild();
1520 nsIScrollableFrame* sf = GetScrollFrame(childFrame);
1521 if (sf)
1522 return sf;
1523 currFrame = currFrame->GetNextSibling();
1524 } while (currFrame);
1526 return nullptr;
1527 }
1529 void nsMenuPopupFrame::EnsureMenuItemIsVisible(nsMenuFrame* aMenuItem)
1530 {
1531 if (aMenuItem) {
1532 aMenuItem->PresContext()->PresShell()->ScrollFrameRectIntoView(
1533 aMenuItem,
1534 nsRect(nsPoint(0,0), aMenuItem->GetRect().Size()),
1535 nsIPresShell::ScrollAxis(),
1536 nsIPresShell::ScrollAxis(),
1537 nsIPresShell::SCROLL_OVERFLOW_HIDDEN |
1538 nsIPresShell::SCROLL_FIRST_ANCESTOR_ONLY);
1539 }
1540 }
1542 NS_IMETHODIMP nsMenuPopupFrame::SetCurrentMenuItem(nsMenuFrame* aMenuItem)
1543 {
1544 if (mCurrentMenu == aMenuItem)
1545 return NS_OK;
1547 if (mCurrentMenu) {
1548 mCurrentMenu->SelectMenu(false);
1549 }
1551 if (aMenuItem) {
1552 EnsureMenuItemIsVisible(aMenuItem);
1553 aMenuItem->SelectMenu(true);
1554 }
1556 mCurrentMenu = aMenuItem;
1558 return NS_OK;
1559 }
1561 void
1562 nsMenuPopupFrame::CurrentMenuIsBeingDestroyed()
1563 {
1564 mCurrentMenu = nullptr;
1565 }
1567 NS_IMETHODIMP
1568 nsMenuPopupFrame::ChangeMenuItem(nsMenuFrame* aMenuItem,
1569 bool aSelectFirstItem)
1570 {
1571 if (mCurrentMenu == aMenuItem)
1572 return NS_OK;
1574 // When a context menu is open, the current menu is locked, and no change
1575 // to the menu is allowed.
1576 nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
1577 if (!mIsContextMenu && pm && pm->HasContextMenu(this))
1578 return NS_OK;
1580 // Unset the current child.
1581 if (mCurrentMenu) {
1582 mCurrentMenu->SelectMenu(false);
1583 nsMenuPopupFrame* popup = mCurrentMenu->GetPopup();
1584 if (popup) {
1585 if (mCurrentMenu->IsOpen()) {
1586 if (pm)
1587 pm->HidePopupAfterDelay(popup);
1588 }
1589 }
1590 }
1592 // Set the new child.
1593 if (aMenuItem) {
1594 EnsureMenuItemIsVisible(aMenuItem);
1595 aMenuItem->SelectMenu(true);
1596 }
1598 mCurrentMenu = aMenuItem;
1600 return NS_OK;
1601 }
1603 nsMenuFrame*
1604 nsMenuPopupFrame::Enter(WidgetGUIEvent* aEvent)
1605 {
1606 mIncrementalString.Truncate();
1608 // Give it to the child.
1609 if (mCurrentMenu)
1610 return mCurrentMenu->Enter(aEvent);
1612 return nullptr;
1613 }
1615 nsMenuFrame*
1616 nsMenuPopupFrame::FindMenuWithShortcut(nsIDOMKeyEvent* aKeyEvent, bool& doAction)
1617 {
1618 uint32_t charCode, keyCode;
1619 aKeyEvent->GetCharCode(&charCode);
1620 aKeyEvent->GetKeyCode(&keyCode);
1622 doAction = false;
1624 // Enumerate over our list of frames.
1625 nsIFrame* immediateParent = PresContext()->PresShell()->
1626 FrameConstructor()->GetInsertionPoint(GetContent(), nullptr);
1627 if (!immediateParent)
1628 immediateParent = this;
1630 uint32_t matchCount = 0, matchShortcutCount = 0;
1631 bool foundActive = false;
1632 bool isShortcut;
1633 nsMenuFrame* frameBefore = nullptr;
1634 nsMenuFrame* frameAfter = nullptr;
1635 nsMenuFrame* frameShortcut = nullptr;
1637 nsIContent* parentContent = mContent->GetParent();
1639 bool isMenu = parentContent &&
1640 !parentContent->NodeInfo()->Equals(nsGkAtoms::menulist, kNameSpaceID_XUL);
1642 static DOMTimeStamp lastKeyTime = 0;
1643 DOMTimeStamp keyTime;
1644 aKeyEvent->GetTimeStamp(&keyTime);
1646 if (charCode == 0) {
1647 if (keyCode == nsIDOMKeyEvent::DOM_VK_BACK_SPACE) {
1648 if (!isMenu && !mIncrementalString.IsEmpty()) {
1649 mIncrementalString.SetLength(mIncrementalString.Length() - 1);
1650 return nullptr;
1651 }
1652 else {
1653 #ifdef XP_WIN
1654 nsCOMPtr<nsISound> soundInterface = do_CreateInstance("@mozilla.org/sound;1");
1655 if (soundInterface)
1656 soundInterface->Beep();
1657 #endif // #ifdef XP_WIN
1658 }
1659 }
1660 return nullptr;
1661 }
1662 else {
1663 char16_t uniChar = ToLowerCase(static_cast<char16_t>(charCode));
1664 if (isMenu || // Menu supports only first-letter navigation
1665 keyTime - lastKeyTime > INC_TYP_INTERVAL) // Interval too long, treat as new typing
1666 mIncrementalString = uniChar;
1667 else {
1668 mIncrementalString.Append(uniChar);
1669 }
1670 }
1672 // See bug 188199 & 192346, if all letters in incremental string are same, just try to match the first one
1673 nsAutoString incrementalString(mIncrementalString);
1674 uint32_t charIndex = 1, stringLength = incrementalString.Length();
1675 while (charIndex < stringLength && incrementalString[charIndex] == incrementalString[charIndex - 1]) {
1676 charIndex++;
1677 }
1678 if (charIndex == stringLength) {
1679 incrementalString.Truncate(1);
1680 stringLength = 1;
1681 }
1683 lastKeyTime = keyTime;
1685 nsIFrame* currFrame;
1686 // NOTE: If you crashed here due to a bogus |immediateParent| it is
1687 // possible that the menu whose shortcut is being looked up has
1688 // been destroyed already. One strategy would be to
1689 // setTimeout(<func>,0) as detailed in:
1690 // <http://bugzilla.mozilla.org/show_bug.cgi?id=126675#c32>
1691 currFrame = immediateParent->GetFirstPrincipalChild();
1693 int32_t menuAccessKey = -1;
1694 nsMenuBarListener::GetMenuAccessKey(&menuAccessKey);
1696 // We start searching from first child. This process is divided into two parts
1697 // -- before current and after current -- by the current item
1698 while (currFrame) {
1699 nsIContent* current = currFrame->GetContent();
1701 // See if it's a menu item.
1702 if (nsXULPopupManager::IsValidMenuItem(PresContext(), current, true)) {
1703 nsAutoString textKey;
1704 if (menuAccessKey >= 0) {
1705 // Get the shortcut attribute.
1706 current->GetAttr(kNameSpaceID_None, nsGkAtoms::accesskey, textKey);
1707 }
1708 if (textKey.IsEmpty()) { // No shortcut, try first letter
1709 isShortcut = false;
1710 current->GetAttr(kNameSpaceID_None, nsGkAtoms::label, textKey);
1711 if (textKey.IsEmpty()) // No label, try another attribute (value)
1712 current->GetAttr(kNameSpaceID_None, nsGkAtoms::value, textKey);
1713 }
1714 else
1715 isShortcut = true;
1717 if (StringBeginsWith(textKey, incrementalString,
1718 nsCaseInsensitiveStringComparator())) {
1719 // mIncrementalString is a prefix of textKey
1720 nsMenuFrame* menu = do_QueryFrame(currFrame);
1721 if (menu) {
1722 // There is one match
1723 matchCount++;
1724 if (isShortcut) {
1725 // There is one shortcut-key match
1726 matchShortcutCount++;
1727 // Record the matched item. If there is only one matched shortcut item, do it
1728 frameShortcut = menu;
1729 }
1730 if (!foundActive) {
1731 // It's a first candidate item located before/on the current item
1732 if (!frameBefore)
1733 frameBefore = menu;
1734 }
1735 else {
1736 // It's a first candidate item located after the current item
1737 if (!frameAfter)
1738 frameAfter = menu;
1739 }
1740 }
1741 else
1742 return nullptr;
1743 }
1745 // Get the active status
1746 if (current->AttrValueIs(kNameSpaceID_None, nsGkAtoms::menuactive,
1747 nsGkAtoms::_true, eCaseMatters)) {
1748 foundActive = true;
1749 if (stringLength > 1) {
1750 // If there is more than one char typed, the current item has highest priority,
1751 // otherwise the item next to current has highest priority
1752 if (currFrame == frameBefore)
1753 return frameBefore;
1754 }
1755 }
1756 }
1757 currFrame = currFrame->GetNextSibling();
1758 }
1760 doAction = (isMenu && (matchCount == 1 || matchShortcutCount == 1));
1762 if (matchShortcutCount == 1) // We have one matched shortcut item
1763 return frameShortcut;
1764 if (frameAfter) // If we have matched item after the current, use it
1765 return frameAfter;
1766 else if (frameBefore) // If we haven't, use the item before the current
1767 return frameBefore;
1769 // If we don't match anything, rollback the last typing
1770 mIncrementalString.SetLength(mIncrementalString.Length() - 1);
1772 // didn't find a matching menu item
1773 #ifdef XP_WIN
1774 // behavior on Windows - this item is in a menu popup off of the
1775 // menu bar, so beep and do nothing else
1776 if (isMenu) {
1777 nsCOMPtr<nsISound> soundInterface = do_CreateInstance("@mozilla.org/sound;1");
1778 if (soundInterface)
1779 soundInterface->Beep();
1780 }
1781 #endif // #ifdef XP_WIN
1783 return nullptr;
1784 }
1786 void
1787 nsMenuPopupFrame::LockMenuUntilClosed(bool aLock)
1788 {
1789 mIsMenuLocked = aLock;
1791 // Lock / unlock the parent, too.
1792 nsMenuFrame* menu = do_QueryFrame(GetParent());
1793 if (menu) {
1794 nsMenuParent* parentParent = menu->GetMenuParent();
1795 if (parentParent) {
1796 parentParent->LockMenuUntilClosed(aLock);
1797 }
1798 }
1799 }
1801 nsIWidget*
1802 nsMenuPopupFrame::GetWidget()
1803 {
1804 nsView * view = GetRootViewForPopup(this);
1805 if (!view)
1806 return nullptr;
1808 return view->GetWidget();
1809 }
1811 void
1812 nsMenuPopupFrame::AttachedDismissalListener()
1813 {
1814 mConsumeRollupEvent = nsIPopupBoxObject::ROLLUP_DEFAULT;
1815 }
1817 // helpers /////////////////////////////////////////////////////////////
1819 nsresult
1820 nsMenuPopupFrame::AttributeChanged(int32_t aNameSpaceID,
1821 nsIAtom* aAttribute,
1822 int32_t aModType)
1824 {
1825 nsresult rv = nsBoxFrame::AttributeChanged(aNameSpaceID, aAttribute,
1826 aModType);
1828 if (aAttribute == nsGkAtoms::left || aAttribute == nsGkAtoms::top)
1829 MoveToAttributePosition();
1831 if (aAttribute == nsGkAtoms::label) {
1832 // set the label for the titlebar
1833 nsView* view = GetView();
1834 if (view) {
1835 nsIWidget* widget = view->GetWidget();
1836 if (widget) {
1837 nsAutoString title;
1838 mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::label, title);
1839 if (!title.IsEmpty()) {
1840 widget->SetTitle(title);
1841 }
1842 }
1843 }
1844 }
1846 return rv;
1847 }
1849 void
1850 nsMenuPopupFrame::MoveToAttributePosition()
1851 {
1852 // Move the widget around when the user sets the |left| and |top| attributes.
1853 // Note that this is not the best way to move the widget, as it results in lots
1854 // of FE notifications and is likely to be slow as molasses. Use |moveTo| on
1855 // nsIPopupBoxObject if possible.
1856 nsAutoString left, top;
1857 mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::left, left);
1858 mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::top, top);
1859 nsresult err1, err2;
1860 int32_t xpos = left.ToInteger(&err1);
1861 int32_t ypos = top.ToInteger(&err2);
1863 if (NS_SUCCEEDED(err1) && NS_SUCCEEDED(err2))
1864 MoveTo(xpos, ypos, false);
1865 }
1867 void
1868 nsMenuPopupFrame::DestroyFrom(nsIFrame* aDestructRoot)
1869 {
1870 nsMenuFrame* menu = do_QueryFrame(GetParent());
1871 if (menu) {
1872 // clear the open attribute on the parent menu
1873 nsContentUtils::AddScriptRunner(
1874 new nsUnsetAttrRunnable(menu->GetContent(), nsGkAtoms::open));
1875 }
1877 nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
1878 if (pm)
1879 pm->PopupDestroyed(this);
1881 nsIRootBox* rootBox =
1882 nsIRootBox::GetRootBox(PresContext()->GetPresShell());
1883 if (rootBox && rootBox->GetDefaultTooltip() == mContent) {
1884 rootBox->SetDefaultTooltip(nullptr);
1885 }
1887 nsBoxFrame::DestroyFrom(aDestructRoot);
1888 }
1891 void
1892 nsMenuPopupFrame::MoveTo(int32_t aLeft, int32_t aTop, bool aUpdateAttrs)
1893 {
1894 nsIWidget* widget = GetWidget();
1895 if ((mScreenXPos == aLeft && mScreenYPos == aTop) &&
1896 (!widget || widget->GetClientOffset() == mLastClientOffset)) {
1897 return;
1898 }
1900 // reposition the popup at the specified coordinates. Don't clear the anchor
1901 // and position, because the popup can be reset to its anchor position by
1902 // using (-1, -1) as coordinates. Subtract off the margin as it will be
1903 // added to the position when SetPopupPosition is called.
1904 nsMargin margin(0, 0, 0, 0);
1905 StyleMargin()->GetMargin(margin);
1907 // Workaround for bug 788189. See also bug 708278 comment #25 and following.
1908 if (mAdjustOffsetForContextMenu) {
1909 nscoord offsetForContextMenu =
1910 nsPresContext::CSSPixelsToAppUnits(CONTEXT_MENU_OFFSET_PIXELS);
1911 margin.left += offsetForContextMenu;
1912 margin.top += offsetForContextMenu;
1913 }
1915 nsPresContext* presContext = PresContext();
1916 mScreenXPos = aLeft - presContext->AppUnitsToIntCSSPixels(margin.left);
1917 mScreenYPos = aTop - presContext->AppUnitsToIntCSSPixels(margin.top);
1919 SetPopupPosition(nullptr, true, false);
1921 nsCOMPtr<nsIContent> popup = mContent;
1922 if (aUpdateAttrs && (popup->HasAttr(kNameSpaceID_None, nsGkAtoms::left) ||
1923 popup->HasAttr(kNameSpaceID_None, nsGkAtoms::top)))
1924 {
1925 nsAutoString left, top;
1926 left.AppendInt(aLeft);
1927 top.AppendInt(aTop);
1928 popup->SetAttr(kNameSpaceID_None, nsGkAtoms::left, left, false);
1929 popup->SetAttr(kNameSpaceID_None, nsGkAtoms::top, top, false);
1930 }
1931 }
1933 void
1934 nsMenuPopupFrame::MoveToAnchor(nsIContent* aAnchorContent,
1935 const nsAString& aPosition,
1936 int32_t aXPos, int32_t aYPos,
1937 bool aAttributesOverride)
1938 {
1939 NS_ASSERTION(mPopupState == ePopupOpenAndVisible, "popup must be open to move it");
1941 InitializePopup(aAnchorContent, mTriggerContent, aPosition,
1942 aXPos, aYPos, aAttributesOverride);
1943 // InitializePopup changed the state so reset it.
1944 mPopupState = ePopupOpenAndVisible;
1946 // Pass false here so that flipping and adjusting to fit on the screen happen.
1947 SetPopupPosition(nullptr, false, false);
1948 }
1950 bool
1951 nsMenuPopupFrame::GetAutoPosition()
1952 {
1953 return mShouldAutoPosition;
1954 }
1956 void
1957 nsMenuPopupFrame::SetAutoPosition(bool aShouldAutoPosition)
1958 {
1959 mShouldAutoPosition = aShouldAutoPosition;
1960 }
1962 void
1963 nsMenuPopupFrame::SetConsumeRollupEvent(uint32_t aConsumeMode)
1964 {
1965 mConsumeRollupEvent = aConsumeMode;
1966 }
1968 int8_t
1969 nsMenuPopupFrame::GetAlignmentPosition() const
1970 {
1971 // The code below handles most cases of alignment, anchor and position values. Those that are
1972 // not handled just return POPUPPOSITION_UNKNOWN.
1974 if (mPosition == POPUPPOSITION_OVERLAP || mPosition == POPUPPOSITION_AFTERPOINTER)
1975 return mPosition;
1977 int8_t position = mPosition;
1979 if (position == POPUPPOSITION_UNKNOWN) {
1980 switch (mPopupAnchor) {
1981 case POPUPALIGNMENT_BOTTOMCENTER:
1982 position = mPopupAlignment == POPUPALIGNMENT_TOPRIGHT ?
1983 POPUPPOSITION_AFTEREND : POPUPPOSITION_AFTERSTART;
1984 break;
1985 case POPUPALIGNMENT_TOPCENTER:
1986 position = mPopupAlignment == POPUPALIGNMENT_BOTTOMRIGHT ?
1987 POPUPPOSITION_BEFOREEND : POPUPPOSITION_BEFORESTART;
1988 break;
1989 case POPUPALIGNMENT_LEFTCENTER:
1990 position = mPopupAlignment == POPUPALIGNMENT_BOTTOMRIGHT ?
1991 POPUPPOSITION_STARTAFTER : POPUPPOSITION_STARTBEFORE;
1992 break;
1993 case POPUPALIGNMENT_RIGHTCENTER:
1994 position = mPopupAlignment == POPUPALIGNMENT_BOTTOMLEFT ?
1995 POPUPPOSITION_ENDAFTER : POPUPPOSITION_ENDBEFORE;
1996 break;
1997 default:
1998 break;
1999 }
2000 }
2002 if (mHFlip) {
2003 position = POPUPPOSITION_HFLIP(position);
2004 }
2006 if (mVFlip) {
2007 position = POPUPPOSITION_VFLIP(position);
2008 }
2010 return position;
2011 }
2013 /**
2014 * KEEP THIS IN SYNC WITH nsContainerFrame::CreateViewForFrame
2015 * as much as possible. Until we get rid of views finally...
2016 */
2017 void
2018 nsMenuPopupFrame::CreatePopupView()
2019 {
2020 if (HasView()) {
2021 return;
2022 }
2024 nsViewManager* viewManager = PresContext()->GetPresShell()->GetViewManager();
2025 NS_ASSERTION(nullptr != viewManager, "null view manager");
2027 // Create a view
2028 nsView* parentView = viewManager->GetRootView();
2029 nsViewVisibility visibility = nsViewVisibility_kHide;
2030 int32_t zIndex = INT32_MAX;
2031 bool autoZIndex = false;
2033 NS_ASSERTION(parentView, "no parent view");
2035 // Create a view
2036 nsView *view = viewManager->CreateView(GetRect(), parentView, visibility);
2037 viewManager->SetViewZIndex(view, autoZIndex, zIndex);
2038 // XXX put view last in document order until we can do better
2039 viewManager->InsertChild(parentView, view, nullptr, true);
2041 // Remember our view
2042 SetView(view);
2044 NS_FRAME_LOG(NS_FRAME_TRACE_CALLS,
2045 ("nsMenuPopupFrame::CreatePopupView: frame=%p view=%p", this, view));
2046 }