Bug 941409, add support for an anchor attribute to menus so that the popup can be anchored to a different element than the menu itself, this is used to avoid repositioning the bookmarks toolbar button menu, also changes popups to default to vertical orientation [Australis] r=neil,mconley
authorNeil Deakin <neil@mozilla.com>
Tue, 28 Jan 2014 11:28:45 -0500
changeset 181590 71361416a124524525e820b3a82db53b87976a36
parent 181589 59684ec6f1518cd4b2ed5bb4f2bf7301cf696025
child 181591 4e6fcc7b5d13fddf23e5c07167e1d2f9919766ce
push id3343
push userffxbld
push dateMon, 17 Mar 2014 21:55:32 +0000
treeherdermozilla-beta@2f7d3415f79f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersneil, mconley
bugs941409
milestone29.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 941409, add support for an anchor attribute to menus so that the popup can be anchored to a different element than the menu itself, this is used to avoid repositioning the bookmarks toolbar button menu, also changes popups to default to vertical orientation [Australis] r=neil,mconley
browser/base/content/browser.xul
browser/components/places/content/menu.xml
content/base/src/nsGkAtomList.h
layout/xul/nsMenuFrame.cpp
layout/xul/nsMenuFrame.h
layout/xul/nsMenuPopupFrame.cpp
layout/xul/nsMenuPopupFrame.h
layout/xul/nsPopupSetFrame.cpp
layout/xul/nsXULPopupManager.cpp
toolkit/content/tests/chrome/chrome.ini
toolkit/content/tests/chrome/test_menu_anchored.xul
toolkit/content/widgets/popup.xml
toolkit/content/widgets/toolbarbutton.xml
--- a/browser/base/content/browser.xul
+++ b/browser/base/content/browser.xul
@@ -703,27 +703,27 @@
 
         <toolbarbutton id="bookmarks-menu-button"
                        class="toolbarbutton-1 chromeclass-toolbar-additional"
                        persist="class"
                        removable="true"
                        type="menu-button"
                        label="&bookmarksMenuButton.label;"
                        tooltiptext="&bookmarksMenuButton.tooltip;"
+                       anchor="dropmarker"
                        ondragenter="PlacesMenuDNDHandler.onDragEnter(event);"
                        ondragover="PlacesMenuDNDHandler.onDragOver(event);"
                        ondragleave="PlacesMenuDNDHandler.onDragLeave(event);"
                        ondrop="PlacesMenuDNDHandler.onDrop(event);"
                        cui-areatype="toolbar"
                        oncommand="BookmarkingUI.onCommand(event);">
           <menupopup id="BMB_bookmarksPopup"
                      placespopup="true"
                      context="placesContext"
                      openInTabs="children"
-                     anonanchorclass="toolbarbutton-menubutton-dropmarker"
                      oncommand="BookmarksEventHandler.onCommand(event, this.parentNode._placesView);"
                      onclick="BookmarksEventHandler.onClick(event, this.parentNode._placesView);"
                      onpopupshowing="BookmarkingUI.onPopupShowing(event);
                                      if (!this.parentNode._placesView)
                                        new PlacesMenu(event, 'place:folder=BOOKMARKS_MENU');"
                      tooltip="bhTooltip" popupsinherittooltip="true">
             <menuitem id="BMB_bookmarksShowAll"
                       label="&showAllBookmarks2.label;"
--- a/browser/components/places/content/menu.xml
+++ b/browser/components/places/content/menu.xml
@@ -485,121 +485,103 @@
 
     </handlers>
   </binding>
 
   <!-- Most of this is copied from the arrowpanel binding in popup.xml -->
   <binding id="places-popup-arrow"
            extends="chrome://browser/content/places/menu.xml#places-popup-base">
     <content flip="both" side="top" position="bottomcenter topleft">
-      <xul:box anonid="container" class="panel-arrowcontainer" flex="1"
+      <xul:vbox anonid="container" class="panel-arrowcontainer" flex="1"
                xbl:inherits="side,panelopen">
         <xul:box anonid="arrowbox" class="panel-arrowbox">
           <xul:image anonid="arrow" class="panel-arrow" xbl:inherits="side"/>
         </xul:box>
         <xul:box class="panel-arrowcontent" xbl:inherits="side,align,dir,orient,pack" flex="1">
           <xul:vbox class="menupopup-drop-indicator-bar" hidden="true">
             <xul:image class="menupopup-drop-indicator" mousethrough="always"/>
           </xul:vbox>
           <xul:arrowscrollbox class="popup-internal-box" flex="1" orient="vertical"
                               smoothscroll="false">
             <children/>
           </xul:arrowscrollbox>
         </xul:box>
-      </xul:box>
+      </xul:vbox>
     </content>
 
     <implementation>
       <method name="adjustArrowPosition">
         <body><![CDATA[
           var arrow = document.getAnonymousElementByAttribute(this, "anonid", "arrow");
 
           var anchor = this.anchorNode;
           if (!anchor) {
             arrow.hidden = true;
             return;
           }
 
           var container = document.getAnonymousElementByAttribute(this, "anonid", "container");
           var arrowbox = document.getAnonymousElementByAttribute(this, "anonid", "arrowbox");
 
+          var position = this.alignmentPosition;
+          var offset = this.alignmentOffset;
           // if this panel has a "sliding" arrow, we may have previously set margins...
-          arrowbox.style.removeProperty("margin");
-
-          var position = this.alignmentPosition;
+          arrowbox.style.removeProperty("transform");
           if (position.indexOf("start_") == 0 || position.indexOf("end_") == 0) {
-            container.orient = "";
+            container.orient = "horizontal";
             arrowbox.orient = "vertical";
             if (position.indexOf("_after") > 0) {
               arrowbox.pack = "end";
-              arrowbox.style.marginBottom = this.alignmentOffset + "px";
             } else {
               arrowbox.pack = "start";
-              arrowbox.style.marginTop = this.alignmentOffset + "px";
             }
+            arrowbox.style.transform = "translate(0, " + -offset + "px)";
 
             // The assigned side stays the same regardless of direction.
             var isRTL = (window.getComputedStyle(this).direction == "rtl");
 
             if (position.indexOf("start_") == 0) {
               container.dir = "reverse";
               this.setAttribute("side", isRTL ? "left" : "right");
             }
             else {
               container.dir = "";
               this.setAttribute("side", isRTL ? "right" : "left");
             }
           }
           else if (position.indexOf("before_") == 0 || position.indexOf("after_") == 0) {
-            container.orient = "vertical";
+            container.orient = "";
             arrowbox.orient = "";
             if (position.indexOf("_end") > 0) {
               arrowbox.pack = "end";
-              arrowbox.style.marginRight = this.alignmentOffset + "px";
             } else {
               arrowbox.pack = "start";
-              arrowbox.style.marginLeft = this.alignmentOffset + "px";
             }
+            arrowbox.style.transform = "translate(" + -offset + "px, 0)";
 
             if (position.indexOf("before_") == 0) {
               container.dir = "reverse";
               this.setAttribute("side", "bottom");
             }
             else {
               container.dir = "";
               this.setAttribute("side", "top");
             }
           }
+
           arrow.hidden = false;
         ]]></body>
       </method>
     </implementation>
 
     <handlers>
       <handler event="popupshowing" phase="target"><![CDATA[
         this.adjustArrowPosition();
       ]]></handler>
       <handler event="popupshown" phase="target"><![CDATA[
         this.setAttribute("panelopen", "true");
-
-        // Allow anchoring to a specified element inside the anchor.
-        var anchorClass = this.getAttribute("anonanchorclass");
-        if (anchorClass && this.anchorNode) {
-          let anchor =
-            document.getAnonymousElementByAttribute(this.anchorNode, "class",
-                                                    anchorClass);
-          if (anchor) {
-            let offsetX = anchor.boxObject.width / 2;
-            if (this.alignmentPosition.endsWith("_end"))
-              offsetX *= -1;
-            this.popupBoxObject.moveToAnchor(anchor, this.alignmentPosition,
-                                             offsetX, 0,
-                                             false);
-            this.adjustArrowPosition();
-          }
-        }
       ]]></handler>
       <handler event="popuphidden" phase="target"><![CDATA[
         this.removeAttribute("panelopen");
       ]]></handler>
     </handlers>
   </binding>
 </bindings>
--- a/content/base/src/nsGkAtomList.h
+++ b/content/base/src/nsGkAtomList.h
@@ -81,16 +81,17 @@ GK_ATOM(allowsameorigin,"allow-same-orig
 GK_ATOM(allowscripts,"allow-scripts")
 GK_ATOM(allowtopnavigation,"allow-top-navigation")
 GK_ATOM(allowuntrusted, "allowuntrusted")
 GK_ATOM(alt, "alt")
 GK_ATOM(alternate, "alternate")
 GK_ATOM(always, "always")
 GK_ATOM(ancestor, "ancestor")
 GK_ATOM(ancestorOrSelf, "ancestor-or-self")
+GK_ATOM(anchor, "anchor")
 GK_ATOM(_and, "and")
 GK_ATOM(any, "any")
 GK_ATOM(mozapp, "mozapp")
 GK_ATOM(applet, "applet")
 GK_ATOM(applyImports, "apply-imports")
 GK_ATOM(applyTemplates, "apply-templates")
 GK_ATOM(mozapptype, "mozapptype")
 GK_ATOM(apz, "apz")
--- a/layout/xul/nsMenuFrame.cpp
+++ b/layout/xul/nsMenuFrame.cpp
@@ -663,16 +663,37 @@ nsMenuFrame::AttributeChanged(int32_t aN
       aAttribute == nsGkAtoms::name) {
     nsCOMPtr<nsIRunnable> event =
       new nsMenuAttributeChangedEvent(this, aAttribute);
     nsContentUtils::AddScriptRunner(event);
   }
   return NS_OK;
 }
 
+nsIContent*
+nsMenuFrame::GetAnchor()
+{
+  mozilla::dom::Element* anchor = nullptr;
+
+  nsAutoString id;
+  mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::anchor, id);
+  if (!id.IsEmpty()) {
+    nsIDocument* doc = mContent->OwnerDoc();
+
+    anchor =
+      doc->GetAnonymousElementByAttribute(mContent, nsGkAtoms::anonid, id);
+    if (!anchor) {
+      anchor = doc->GetElementById(id);
+    }
+  }
+
+  // Always return the menu's content if the anchor wasn't set or wasn't found.
+  return anchor && anchor->GetPrimaryFrame() ? anchor : mContent;
+}
+
 void
 nsMenuFrame::OpenMenu(bool aSelectFirstItem)
 {
   if (!mContent)
     return;
 
   gEatMouseMove = true;
 
@@ -720,17 +741,17 @@ NS_IMETHODIMP
 nsMenuFrame::DoLayout(nsBoxLayoutState& aState)
 {
   // lay us out
   nsresult rv = nsBoxFrame::DoLayout(aState);
 
   nsMenuPopupFrame* popupFrame = GetPopup();
   if (popupFrame) {
     bool sizeToPopup = IsSizedToPopup(mContent, false);
-    popupFrame->LayoutPopup(aState, this, sizeToPopup);
+    popupFrame->LayoutPopup(aState, this, GetAnchor()->GetPrimaryFrame(), sizeToPopup);
   }
 
   return rv;
 }
 
 #ifdef DEBUG_LAYOUT
 NS_IMETHODIMP
 nsMenuFrame::SetDebug(nsBoxLayoutState& aState, bool aDebug)
--- a/layout/xul/nsMenuFrame.h
+++ b/layout/xul/nsMenuFrame.h
@@ -123,16 +123,22 @@ public:
                           nsIFrame*       aOldFrame) MOZ_OVERRIDE;
 
   virtual nsIAtom* GetType() const MOZ_OVERRIDE { return nsGkAtoms::menuFrame; }
 
   NS_IMETHOD SelectMenu(bool aActivateFlag);
 
   virtual nsIScrollableFrame* GetScrollTargetFrame() MOZ_OVERRIDE;
 
+  // Retrieve the element that the menu should be anchored to. By default this is
+  // the menu itself. However, the anchor attribute may refer to the value of an
+  // anonid within the menu's binding, or, if not found, the id of an element in
+  // the document.
+  nsIContent* GetAnchor();
+
   /**
    * NOTE: OpenMenu will open the menu asynchronously.
    */
   void OpenMenu(bool aSelectFirstItem);
   // CloseMenu closes the menu asynchronously
   void CloseMenu(bool aDeselectMenu);
 
   bool IsChecked() { return mChecked; }
--- a/layout/xul/nsMenuPopupFrame.cpp
+++ b/layout/xul/nsMenuPopupFrame.cpp
@@ -374,17 +374,18 @@ nsMenuPopupFrame::IsLeaf() const
   // the parent menu is dependent on the size of the popup, so the frames
   // need to exist in order to calculate this size.
   nsIContent* parentContent = mContent->GetParent();
   return (parentContent &&
           !parentContent->HasAttr(kNameSpaceID_None, nsGkAtoms::sizetopopup));
 }
 
 void
-nsMenuPopupFrame::LayoutPopup(nsBoxLayoutState& aState, nsIFrame* aParentMenu, bool aSizedToPopup)
+nsMenuPopupFrame::LayoutPopup(nsBoxLayoutState& aState, nsIFrame* aParentMenu,
+                              nsIFrame* aAnchor, bool aSizedToPopup)
 {
   if (!mGeneratedChildren)
     return;
 
   SchedulePaint();
 
   bool shouldPosition = true;
   bool isOpen = IsOpen();
@@ -422,34 +423,34 @@ nsMenuPopupFrame::LayoutPopup(nsBoxLayou
   // if the size changed then set the bounds to be the preferred size
   bool sizeChanged = (mPrefSize != prefSize);
   if (sizeChanged) {
     SetBounds(aState, nsRect(0, 0, prefSize.width, prefSize.height), false);
     mPrefSize = prefSize;
   }
 
   if (shouldPosition) {
-    SetPopupPosition(aParentMenu, false);
+    SetPopupPosition(aAnchor, false, aSizedToPopup);
   }
 
   nsRect bounds(GetRect());
   Layout(aState);
 
   // if the width or height changed, readjust the popup position. This is a
   // special case for tooltips where the preferred height doesn't include the
   // real height for its inline element, but does once it is laid out.
   // This is bug 228673 which doesn't have a simple fix.
   if (!aParentMenu) {
     nsSize newsize = GetSize();
     if (newsize.width > bounds.width || newsize.height > bounds.height) {
       // the size after layout was larger than the preferred size,
       // so set the preferred size accordingly
       mPrefSize = newsize;
       if (isOpen) {
-        SetPopupPosition(nullptr, false);
+        SetPopupPosition(nullptr, false, aSizedToPopup);
       }
     }
   }
 
   nsPresContext* pc = PresContext();
   nsView* view = GetView();
 
   if (sizeChanged) {
@@ -1115,17 +1116,17 @@ nsMenuPopupFrame::FlipOrResize(nscoord& 
   // smaller than the calculated popup size, just use the original size instead.
   if (popupSize <= 0 || aSize < popupSize) {
     popupSize = aSize;
   }
   return std::min(popupSize, aScreenEnd - aScreenPoint);
 }
 
 nsresult
-nsMenuPopupFrame::SetPopupPosition(nsIFrame* aAnchorFrame, bool aIsMove)
+nsMenuPopupFrame::SetPopupPosition(nsIFrame* aAnchorFrame, bool aIsMove, bool aSizedToPopup)
 {
   if (!mShouldAutoPosition)
     return NS_OK;
 
   // If this is due to a move, return early if the popup hasn't been laid out
   // yet. On Windows, this can happen when using a drag popup before it opens.
   if (aIsMove && (mPrefSize.width == -1 || mPrefSize.height == -1)) {
     return NS_OK;
@@ -1147,39 +1148,33 @@ nsMenuPopupFrame::SetPopupPosition(nsIFr
 
     if (!aAnchorFrame) {
       aAnchorFrame = rootFrame;
       if (!aAnchorFrame)
         return NS_OK;
     }
   }
 
-  bool sizedToPopup = false;
-  if (aAnchorFrame->GetContent()) {
-    // the popup should be the same size as the anchor menu, for example, a menulist.
-    sizedToPopup = nsMenuFrame::IsSizedToPopup(aAnchorFrame->GetContent(), false);
-  }
-
   // the dimensions of the anchor in its app units
   nsRect parentRect = aAnchorFrame->GetScreenRectInAppUnits();
 
   // the anchor may be in a different document with a different scale,
   // so adjust the size so that it is in the app units of the popup instead
   // of the anchor.
   parentRect = parentRect.ConvertAppUnitsRoundOut(
     aAnchorFrame->PresContext()->AppUnitsPerDevPixel(),
     presContext->AppUnitsPerDevPixel());
 
   // Set the popup's size to the preferred size. Below, this size will be
   // adjusted to fit on the screen or within the content area. If the anchor
   // is sized to the popup, use the anchor's width instead of the preferred
   // width. The preferred size should already be set by the parent frame.
   NS_ASSERTION(mPrefSize.width >= 0 || mPrefSize.height >= 0,
                "preferred size of popup not set");
-  mRect.width = sizedToPopup ? parentRect.width : mPrefSize.width;
+  mRect.width = aSizedToPopup ? parentRect.width : mPrefSize.width;
   mRect.height = mPrefSize.height;
 
   // the screen position in app units where the popup should appear
   nsPoint screenPoint;
 
   // For anchored popups, the anchor rectangle. For non-anchored popups, the
   // size will be 0.
   nsRect anchorRect = parentRect;
@@ -1365,17 +1360,17 @@ nsMenuPopupFrame::SetPopupPosition(nsIFr
   }
 
   presContext->GetPresShell()->GetViewManager()->
     MoveViewTo(view, viewPoint.x, viewPoint.y);
 
   // Now that we've positioned the view, sync up the frame's origin.
   nsBoxFrame::SetPosition(viewPoint - GetParent()->GetOffsetTo(rootFrame));
 
-  if (sizedToPopup) {
+  if (aSizedToPopup) {
     nsBoxLayoutState state(PresContext());
     // XXXndeakin can parentSize.width still extend outside?
     SetBounds(state, nsRect(mRect.x, mRect.y, parentRect.width, mRect.height));
   }
 
   return NS_OK;
 }
 
@@ -1929,17 +1924,17 @@ nsMenuPopupFrame::MoveTo(int32_t aLeft, 
     margin.left += offsetForContextMenu;
     margin.top += offsetForContextMenu;
   }
 
   nsPresContext* presContext = PresContext();
   mScreenXPos = aLeft - presContext->AppUnitsToIntCSSPixels(margin.left);
   mScreenYPos = aTop - presContext->AppUnitsToIntCSSPixels(margin.top);
 
-  SetPopupPosition(nullptr, true);
+  SetPopupPosition(nullptr, true, false);
 
   nsCOMPtr<nsIContent> popup = mContent;
   if (aUpdateAttrs && (popup->HasAttr(kNameSpaceID_None, nsGkAtoms::left) ||
                        popup->HasAttr(kNameSpaceID_None, nsGkAtoms::top)))
   {
     nsAutoString left, top;
     left.AppendInt(aLeft);
     top.AppendInt(aTop);
@@ -1957,17 +1952,17 @@ nsMenuPopupFrame::MoveToAnchor(nsIConten
   NS_ASSERTION(mPopupState == ePopupOpenAndVisible, "popup must be open to move it");
 
   InitializePopup(aAnchorContent, mTriggerContent, aPosition,
                   aXPos, aYPos, aAttributesOverride);
   // InitializePopup changed the state so reset it.
   mPopupState = ePopupOpenAndVisible;
 
   // Pass false here so that flipping and adjusting to fit on the screen happen.
-  SetPopupPosition(nullptr, false);
+  SetPopupPosition(nullptr, false, false);
 }
 
 bool
 nsMenuPopupFrame::GetAutoPosition()
 {
   return mShouldAutoPosition;
 }
 
--- a/layout/xul/nsMenuPopupFrame.h
+++ b/layout/xul/nsMenuPopupFrame.h
@@ -198,26 +198,27 @@ public:
   uint8_t GetShadowStyle();
 
   NS_IMETHOD SetInitialChildList(ChildListID     aListID,
                                  nsFrameList&    aChildList) MOZ_OVERRIDE;
 
   virtual bool IsLeaf() const MOZ_OVERRIDE;
 
   // layout, position and display the popup as needed
-  void LayoutPopup(nsBoxLayoutState& aState, nsIFrame* aParentMenu, bool aSizedToPopup);
+  void LayoutPopup(nsBoxLayoutState& aState, nsIFrame* aParentMenu,
+                   nsIFrame* aAnchor, bool aSizedToPopup);
 
   nsView* GetRootViewForPopup(nsIFrame* aStartFrame);
 
   // set the position of the popup either relative to the anchor aAnchorFrame
   // (or the frame for mAnchorContent if aAnchorFrame is null) or at a specific
   // point if a screen position (mScreenXPos and mScreenYPos) are set. The popup
   // will be adjusted so that it is on screen. If aIsMove is true, then the popup
   // is being moved, and should not be flipped.
-  nsresult SetPopupPosition(nsIFrame* aAnchorFrame, bool aIsMove);
+  nsresult SetPopupPosition(nsIFrame* aAnchorFrame, bool aIsMove, bool aSizedToPopup);
 
   bool HasGeneratedChildren() { return mGeneratedChildren; }
   void SetGeneratedChildren() { mGeneratedChildren = true; }
 
   // called when the Enter key is pressed while the popup is open. This will
   // just pass the call down to the current menu, if any. If a current menu
   // should be opened as a result, this method should return the frame for
   // that menu, or null if no menu should be opened. Also, calling Enter will
--- a/layout/xul/nsPopupSetFrame.cpp
+++ b/layout/xul/nsPopupSetFrame.cpp
@@ -125,17 +125,17 @@ NS_IMETHODIMP
 nsPopupSetFrame::DoLayout(nsBoxLayoutState& aState)
 {
   // lay us out
   nsresult rv = nsBoxFrame::DoLayout(aState);
 
   // lay out all of our currently open popups.
   for (nsFrameList::Enumerator e(mPopupList); !e.AtEnd(); e.Next()) {
     nsMenuPopupFrame* popupChild = static_cast<nsMenuPopupFrame*>(e.get());
-    popupChild->LayoutPopup(aState, nullptr, false);
+    popupChild->LayoutPopup(aState, nullptr, nullptr, false);
   }
 
   return rv;
 }
 
 void
 nsPopupSetFrame::RemovePopupFrame(nsIFrame* aPopup)
 {
--- a/layout/xul/nsXULPopupManager.cpp
+++ b/layout/xul/nsXULPopupManager.cpp
@@ -337,17 +337,17 @@ nsXULPopupManager::AdjustPopupsOnWindowC
       nsIContent* popup = frame->GetContent();
       if (popup) {
         nsIDocument* document = popup->GetCurrentDoc();
         if (document) {
           nsPIDOMWindow* window = document->GetWindow();
           if (window) {
             window = window->GetPrivateRoot();
             if (window == aWindow) {
-              frame->SetPopupPosition(nullptr, true);
+              frame->SetPopupPosition(nullptr, true, false);
             }
           }
         }
       }
     }
 
     item = item->GetParent();
   }
@@ -390,17 +390,17 @@ nsXULPopupManager::PopupMoved(nsIFrame* 
   if ((aPnt.x != currentPnt.x || aPnt.y != currentPnt.y) || (widget &&
       widget->GetClientOffset() != menuPopupFrame->GetLastClientOffset())) {
     // Update the popup's position using SetPopupPosition if the popup is
     // anchored and at the parent level as these maintain their position
     // relative to the parent window. Otherwise, just update the popup to
     // the specified screen coordinates.
     if (menuPopupFrame->IsAnchored() &&
         menuPopupFrame->PopupLevel() == ePopupLevelParent) {
-      menuPopupFrame->SetPopupPosition(nullptr, true);
+      menuPopupFrame->SetPopupPosition(nullptr, true, false);
     }
     else {
       menuPopupFrame->MoveTo(aPnt.x, aPnt.y, false);
     }
   }
 }
 
 void
@@ -600,17 +600,17 @@ nsXULPopupManager::ShowMenu(nsIContent *
   nsAutoString position;
   if (onMenuBar || !onmenu)
     position.AssignLiteral("after_start");
   else
     position.AssignLiteral("end_before");
 
   // there is no trigger event for menus
   InitTriggerEvent(nullptr, nullptr, nullptr);
-  popupFrame->InitializePopup(aMenu, nullptr, position, 0, 0, true);
+  popupFrame->InitializePopup(menuFrame->GetAnchor(), nullptr, position, 0, 0, true);
 
   if (aAsynchronous) {
     nsCOMPtr<nsIRunnable> event =
       new nsXULPopupShowingEvent(popupFrame->GetContent(),
                                  parentIsContextMenu, aSelectFirstItem);
     NS_DispatchToCurrentThread(event);
   }
   else {
--- a/toolkit/content/tests/chrome/chrome.ini
+++ b/toolkit/content/tests/chrome/chrome.ini
@@ -92,16 +92,17 @@ skip-if = os == "win" # Intermittent fai
 [test_findbar.xul]
 [test_findbar_events.xul]
 [test_focus_anons.xul]
 [test_hiddenitems.xul]
 [test_hiddenpaging.xul]
 [test_keys.xul]
 [test_largemenu.xul]
 [test_menu.xul]
+[test_menu_anchored.xul]
 [test_menu_hide.xul]
 [test_menuchecks.xul]
 [test_menuitem_blink.xul]
 [test_menuitem_commands.xul]
 [test_menulist.xul]
 [test_menulist_keynav.xul]
 [test_menulist_null_value.xul]
 [test_mousecapture.xul]
new file mode 100644
--- /dev/null
+++ b/toolkit/content/tests/chrome/test_menu_anchored.xul
@@ -0,0 +1,77 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?>
+<!--
+  Test for menus with the anchor attribute set
+  -->
+<window title="Anchored Menus Test"
+        align="start"
+        onload="setTimeout(runTest, 0,'tb1');"
+        xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+  <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+  <script type="application/javascript" src="xul_selectcontrol.js"/>
+
+<hbox>
+
+<toolbarbutton id="tb1" type="menu-button" label="Open" anchor="dropmarker">
+  <menupopup id="popup1"
+             onpopupshown="checkPopup(this, document.getAnonymousElementByAttribute(this.parentNode, 'anonid', 'dropmarker'))"
+             onpopuphidden="runTest('tb2')">
+    <menuitem label="Item"/>
+  </menupopup>
+</toolbarbutton>
+
+<toolbarbutton id="tb2" type="menu-button" label="Open" anchor="someanchor">
+  <menupopup id="popup2" onpopupshown="checkPopup(this, $('someanchor'))" onpopuphidden="runTest('tb3')">
+    <menuitem label="Item"/>
+  </menupopup>
+</toolbarbutton>
+
+<toolbarbutton id="tb3" type="menu-button" label="Open" anchor="noexist">
+  <menupopup id="popup3" onpopupshown="checkPopup(this, this.parentNode)" onpopuphidden="SimpleTest.finish()">
+    <menuitem label="Item"/>
+  </menupopup>
+</toolbarbutton>
+
+</hbox>
+
+<hbox pack="end" width="180">
+  <button id="someanchor" label="Anchor"/>
+</hbox>
+
+<!-- test results are displayed in the html:body -->
+<body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
+
+<script type="application/javascript"><![CDATA[
+
+function runTest(menuid)
+{
+  let menu = $(menuid);
+  let dropmarker = document.getAnonymousElementByAttribute(menu, "anonid", "dropmarker");
+
+  synthesizeMouseAtCenter(dropmarker, { });
+}
+
+function isWithinHalfPixel(a, b)
+{
+  return Math.abs(a - b) <= 0.5;
+}
+
+function checkPopup(popup, anchor)
+{
+  let popupRect = popup.getBoundingClientRect();
+  let anchorRect = anchor.getBoundingClientRect();
+
+  ok(isWithinHalfPixel(popupRect.left, anchorRect.left), popup.id + " left");
+  ok(isWithinHalfPixel(popupRect.top, anchorRect.bottom), popup.id + " top");
+
+  popup.hidePopup();
+}
+
+SimpleTest.waitForExplicitFinish();
+
+]]>
+</script>
+
+</window>
--- a/toolkit/content/widgets/popup.xml
+++ b/toolkit/content/widgets/popup.xml
@@ -309,26 +309,26 @@
           }
         }
       ]]></handler>
     </handlers>
   </binding>
 
   <binding id="arrowpanel" extends="chrome://global/content/bindings/popup.xml#panel">
     <content flip="both" side="top" position="bottomcenter topleft" consumeoutsideclicks="false">
-      <xul:box anonid="container" class="panel-arrowcontainer" flex="1"
+      <xul:vbox anonid="container" class="panel-arrowcontainer" flex="1"
                xbl:inherits="side,panelopen">
         <xul:box anonid="arrowbox" class="panel-arrowbox">
           <xul:image anonid="arrow" class="panel-arrow" xbl:inherits="side"/>
         </xul:box>
         <xul:box class="panel-arrowcontent" xbl:inherits="side,align,dir,orient,pack" flex="1">
           <children/>
           <xul:box class="panel-inner-arrowcontentfooter" xbl:inherits="footertype" hidden="true"/>
         </xul:box>
-      </xul:box>
+      </xul:vbox>
     </content>
     <implementation>
       <field name="_fadeTimer">null</field>
       <method name="sizeTo">
         <parameter name="aWidth"/>
         <parameter name="aHeight"/>
         <body>
         <![CDATA[
@@ -377,17 +377,17 @@
         var container = document.getAnonymousElementByAttribute(this, "anonid", "container");
         var arrowbox = document.getAnonymousElementByAttribute(this, "anonid", "arrowbox");
 
         var position = this.alignmentPosition;
         var offset = this.alignmentOffset;
         // if this panel has a "sliding" arrow, we may have previously set margins...
         arrowbox.style.removeProperty("transform");
         if (position.indexOf("start_") == 0 || position.indexOf("end_") == 0) {
-          container.orient = "";
+          container.orient = "horizontal";
           arrowbox.orient = "vertical";
           if (position.indexOf("_after") > 0) {
             arrowbox.pack = "end";
           } else {
             arrowbox.pack = "start";
           }
           arrowbox.style.transform = "translate(0, " + -offset + "px)";
 
@@ -399,17 +399,17 @@
             this.setAttribute("side", isRTL ? "left" : "right");
           }
           else {
             container.dir = "";
             this.setAttribute("side", isRTL ? "right" : "left");
           }
         }
         else if (position.indexOf("before_") == 0 || position.indexOf("after_") == 0) {
-          container.orient = "vertical";
+          container.orient = "";
           arrowbox.orient = "";
           if (position.indexOf("_end") > 0) {
             arrowbox.pack = "end";
           } else {
             arrowbox.pack = "start";
           }
           arrowbox.style.transform = "translate(" + -offset + "px, 0)";
 
--- a/toolkit/content/widgets/toolbarbutton.xml
+++ b/toolkit/content/widgets/toolbarbutton.xml
@@ -29,33 +29,35 @@
            extends="chrome://global/content/bindings/toolbarbutton.xml#toolbarbutton">
     <content>
       <children includes="observes|template|menupopup|panel|tooltip"/>
       <xul:image class="toolbarbutton-icon" xbl:inherits="validate,src=image,label,type"/>
       <xul:label class="toolbarbutton-text" crop="right" flex="1"
                  xbl:inherits="value=label,accesskey,crop,dragover-top"/>
       <xul:label class="toolbarbutton-multiline-text" flex="1"
                  xbl:inherits="xbl:text=label,accesskey"/>
-      <xul:dropmarker type="menu" class="toolbarbutton-menu-dropmarker" xbl:inherits="disabled,label"/>
+      <xul:dropmarker anonid="dropmarker" type="menu"
+                      class="toolbarbutton-menu-dropmarker" xbl:inherits="disabled,label"/>
     </content>
   </binding>
   
   <binding id="menu-vertical" display="xul:menu"
            extends="chrome://global/content/bindings/toolbarbutton.xml#toolbarbutton">
     <content>
       <children includes="observes|template|menupopup|panel|tooltip"/>
       <xul:hbox flex="1" align="center">
         <xul:vbox flex="1" align="center">
           <xul:image class="toolbarbutton-icon" xbl:inherits="validate,src=image,label"/>
           <xul:label class="toolbarbutton-text" crop="right" flex="1"
                      xbl:inherits="value=label,accesskey,crop,dragover-top"/>
           <xul:label class="toolbarbutton-multiline-text" flex="1"
                      xbl:inherits="xbl:text=label,accesskey"/>
         </xul:vbox>
-        <xul:dropmarker type="menu" class="toolbarbutton-menu-dropmarker" xbl:inherits="disabled,label"/>
+        <xul:dropmarker anonid="dropmarker" type="menu"
+                        class="toolbarbutton-menu-dropmarker" xbl:inherits="disabled,label"/>
       </xul:hbox>
     </content>
   </binding>
   
   <binding id="menu-button" display="xul:menu" 
            extends="chrome://global/content/bindings/button.xml#menu-button-base">
     <resources>
       <stylesheet src="chrome://global/skin/toolbarbutton.css"/>
@@ -63,17 +65,17 @@
 
     <content>
       <children includes="observes|template|menupopup|panel|tooltip"/>
       <xul:toolbarbutton class="box-inherit toolbarbutton-menubutton-button"
                          anonid="button" flex="1" allowevents="true"
                          xbl:inherits="disabled,crop,image,label,accesskey,command,wrap,
                                        align,dir,pack,orient,tooltiptext=buttontooltiptext"/>
       <xul:dropmarker type="menu-button" class="toolbarbutton-menubutton-dropmarker"
-                      xbl:inherits="align,dir,pack,orient,disabled,label,open"/>
+                      anonid="dropmarker" xbl:inherits="align,dir,pack,orient,disabled,label,open"/>
     </content>
   </binding>
 
   <binding id="toolbarbutton-image"
            extends="chrome://global/content/bindings/toolbarbutton.xml#toolbarbutton">
     <content>
       <xul:image class="toolbarbutton-icon" xbl:inherits="src=image"/>
     </content>