Bug 812943 - allow panel anchor arrow to 'slide' on one axis rather than being resized. r=Neil
authorMark Hammond <mhammond@skippinet.com.au>
Thu, 09 May 2013 08:59:03 +1000
changeset 131726 89ae725c012559b4712d94a5e88fb8d44ee47f04
parent 131725 b2f16d5e0ebde723a425c30d1576013c5cdd62ce
child 131727 5359a2b9768c6bdad6eec07e7b3420b1ee36b21a
push id24671
push userryanvm@gmail.com
push dateMon, 13 May 2013 20:32:09 +0000
treeherdermozilla-central@81dd97739fa1 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersNeil
bugs812943
milestone23.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 812943 - allow panel anchor arrow to 'slide' on one axis rather than being resized. r=Neil
layout/xul/base/public/nsIPopupBoxObject.idl
layout/xul/base/src/nsMenuPopupFrame.cpp
layout/xul/base/src/nsMenuPopupFrame.h
layout/xul/base/src/nsPopupBoxObject.cpp
toolkit/content/tests/widgets/test_popupanchor.xul
toolkit/content/widgets/popup.xml
--- a/layout/xul/base/public/nsIPopupBoxObject.idl
+++ b/layout/xul/base/public/nsIPopupBoxObject.idl
@@ -5,17 +5,17 @@
 
 #include "nsIBoxObject.idl"
 
 interface nsIDOMElement;
 interface nsIDOMNode;
 interface nsIDOMEvent;
 interface nsIDOMClientRect;
 
-[scriptable, uuid(DF60BA02-005B-4F61-AB1C-5632D0779DB4)]
+[scriptable, uuid(b1192cac-467b-42ca-88d6-fcdec5bb4ef7)]
 interface nsIPopupBoxObject : nsISupports
 {
   /**
    *  This method is deprecated. Use openPopup or openPopupAtScreen instead.
    */
   void showPopup(in nsIDOMElement srcContent, in nsIDOMElement popupContent,
                  in long xpos, in long ypos,
                  in wstring popupType, in wstring anchorAlignment, 
@@ -166,15 +166,16 @@ interface nsIPopupBoxObject : nsISupport
                     in AString position,
                     in long x, in long y,
                     in boolean attributesOverride);
 
   /** Returns the alignment position where the popup has appeared relative to its
    *  anchor node or point, accounting for any flipping that occurred.
    */
   readonly attribute AString alignmentPosition;
+  readonly attribute long alignmentOffset;
 };
 
 %{C++
 nsresult
 NS_NewPopupBoxObject(nsIBoxObject** aResult);
 
 %}
--- a/layout/xul/base/src/nsMenuPopupFrame.cpp
+++ b/layout/xul/base/src/nsMenuPopupFrame.cpp
@@ -548,16 +548,17 @@ nsMenuPopupFrame::InitializePopup(nsICon
   mPopupState = ePopupShowing;
   mAnchorContent = aAnchorContent;
   mTriggerContent = aTriggerContent;
   mXPos = aXPos;
   mYPos = aYPos;
   mAdjustOffsetForContextMenu = false;
   mVFlip = false;
   mHFlip = false;
+  mAlignmentOffset = 0;
 
   // if aAttributesOverride is true, then the popupanchor, popupalign and
   // position attributes on the <popup> override those values passed in.
   // If false, those attributes are only used if the values passed in are empty
   if (aAnchorContent) {
     nsAutoString anchor, align, position, flip;
     mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::popupanchor, anchor);
     mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::popupalign, align);
@@ -572,16 +573,17 @@ nsMenuPopupFrame::InitializePopup(nsICon
       else
         mXPos = mYPos = 0;
     }
     else if (!aPosition.IsEmpty()) {
       position.Assign(aPosition);
     }
 
     mFlipBoth = flip.EqualsLiteral("both");
+    mSlide = flip.EqualsLiteral("slide");
 
     position.CompressWhitespace();
     int32_t spaceIdx = position.FindChar(' ');
     // if there is a space in the position, assume it is the anchor and
     // alignment as two separate tokens.
     if (spaceIdx >= 0) {
       InitPositionFromAnchorAlign(Substring(position, 0, spaceIdx), Substring(position, spaceIdx + 1));
     }
@@ -675,16 +677,17 @@ nsMenuPopupFrame::InitializePopupAtScree
   EnsureWidget();
 
   mPopupState = ePopupShowing;
   mAnchorContent = nullptr;
   mTriggerContent = aTriggerContent;
   mScreenXPos = aXPos;
   mScreenYPos = aYPos;
   mFlipBoth = false;
+  mSlide = false;
   mPopupAnchor = POPUPALIGNMENT_NONE;
   mPopupAlignment = POPUPALIGNMENT_NONE;
   mIsContextMenu = aIsContextMenu;
   mAdjustOffsetForContextMenu = aIsContextMenu;
 }
 
 void
 nsMenuPopupFrame::InitializePopupWithAnchorAlign(nsIContent* aAnchorContent,
@@ -692,16 +695,17 @@ nsMenuPopupFrame::InitializePopupWithAnc
                                                  nsAString& aAlign,
                                                  int32_t aXPos, int32_t aYPos)
 {
   EnsureWidget();
 
   mPopupState = ePopupShowing;
   mAdjustOffsetForContextMenu = false;
   mFlipBoth = false;
+  mSlide = false;
 
   // this popup opening function is provided for backwards compatibility
   // only. It accepts either coordinates or an anchor and alignment value
   // but doesn't use both together.
   if (aXPos == -1 && aYPos == -1) {
     mAnchorContent = aAnchorContent;
     mScreenXPos = -1;
     mScreenYPos = -1;
@@ -983,16 +987,33 @@ nsMenuPopupFrame::AdjustPositionForAncho
       break;
     }
   }
 
   return pnt;
 }
 
 nscoord
+nsMenuPopupFrame::SlideOrResize(nscoord& aScreenPoint, nscoord aSize,
+                               nscoord aScreenBegin, nscoord aScreenEnd,
+                               nscoord *aOffset)
+{
+  // The popup may be positioned such that either the left/top or bottom/right
+  // is outside the screen - but never both.
+  if (aScreenPoint < aScreenBegin) {
+    *aOffset = aScreenBegin - aScreenPoint;
+    aScreenPoint = aScreenBegin;
+  } else if (aScreenPoint + aSize > aScreenEnd) {
+    *aOffset = aScreenPoint + aSize - aScreenEnd;
+    aScreenPoint = std::max(aScreenEnd - aSize, 0);
+  }
+  return std::min(aSize, aScreenEnd - aScreenPoint);
+}
+
+nscoord
 nsMenuPopupFrame::FlipOrResize(nscoord& aScreenPoint, nscoord aSize, 
                                nscoord aScreenBegin, nscoord aScreenEnd,
                                nscoord aAnchorBegin, nscoord aAnchorEnd,
                                nscoord aMarginBegin, nscoord aMarginEnd,
                                nscoord aOffsetForContextMenu, FlipStyle aFlip,
                                bool* aFlipSide)
 {
   // all of the coordinates used here are in app units relative to the screen
@@ -1271,26 +1292,44 @@ nsMenuPopupFrame::SetPopupPosition(nsIFr
     // shrink the the popup down if it is larger than the screen size
     if (mRect.width > screenRect.width)
       mRect.width = screenRect.width;
     if (mRect.height > screenRect.height)
       mRect.height = screenRect.height;
 
     // at this point the anchor (anchorRect) is within the available screen
     // area (screenRect) and the popup is known to be no larger than the screen.
+
+    // We might want to "slide" an arrow if the panel is of the correct type -
+    // but we can only slide on one axis - the other axis must be "flipped or
+    // resized" as normal.
+    bool slideHorizontal = mSlide && mPosition <= POPUPPOSITION_AFTEREND;
+    bool slideVertical = mSlide && mPosition >= POPUPPOSITION_STARTBEFORE;
+
     // Next, check if there is enough space to show the popup at full size when
     // positioned at screenPoint. If not, flip the popups to the opposite side
     // of their anchor point, or resize them as necessary.
-    mRect.width = FlipOrResize(screenPoint.x, mRect.width, screenRect.x,
-                               screenRect.XMost(), anchorRect.x, anchorRect.XMost(),
-                               margin.left, margin.right, offsetForContextMenu, hFlip, &mHFlip);
-
-    mRect.height = FlipOrResize(screenPoint.y, mRect.height, screenRect.y,
-                                screenRect.YMost(), anchorRect.y, anchorRect.YMost(),
-                                margin.top, margin.bottom, offsetForContextMenu, vFlip, &mVFlip);
+    if (slideHorizontal) {
+      mRect.width = SlideOrResize(screenPoint.x, mRect.width, screenRect.x,
+                                  screenRect.XMost(), &mAlignmentOffset);
+    } else {
+      mRect.width = FlipOrResize(screenPoint.x, mRect.width, screenRect.x,
+                                 screenRect.XMost(), anchorRect.x, anchorRect.XMost(),
+                                 margin.left, margin.right, offsetForContextMenu, hFlip,
+                                 &mHFlip);
+    }
+    if (slideVertical) {
+      mRect.height = SlideOrResize(screenPoint.y, mRect.height, screenRect.y,
+                                  screenRect.YMost(), &mAlignmentOffset);
+    } else {
+      mRect.height = FlipOrResize(screenPoint.y, mRect.height, screenRect.y,
+                                  screenRect.YMost(), anchorRect.y, anchorRect.YMost(),
+                                  margin.top, margin.bottom, offsetForContextMenu, vFlip,
+                                  &mVFlip);
+    }
 
     NS_ASSERTION(screenPoint.x >= screenRect.x && screenPoint.y >= screenRect.y &&
                  screenPoint.x + mRect.width <= screenRect.XMost() &&
                  screenPoint.y + mRect.height <= screenRect.YMost(),
                  "Popup is offscreen");
   }
 
   // determine the x and y position of the view by subtracting the desired
--- a/layout/xul/base/src/nsMenuPopupFrame.h
+++ b/layout/xul/base/src/nsMenuPopupFrame.h
@@ -329,16 +329,18 @@ public:
                                 const nsRect&           aDirtyRect,
                                 const nsDisplayListSet& aLists) MOZ_OVERRIDE;
 
   nsIntPoint GetLastClientOffset() const { return mLastClientOffset; }
 
   // Return the alignment of the popup
   int8_t GetAlignmentPosition() const;
 
+  // Return the offset applied to the alignment of the popup
+  nscoord GetAlignmentOffset() const { return mAlignmentOffset; }
 protected:
 
   // returns the popup's level.
   nsPopupLevel PopupLevel(bool aIsNoAutoHide) const;
 
   // redefine to tell the box system not to move the views.
   virtual void GetLayoutFlags(uint32_t& aFlags);
 
@@ -368,16 +370,34 @@ protected:
   //   aFlipSide - pointer to where current flip mode is stored
   nscoord FlipOrResize(nscoord& aScreenPoint, nscoord aSize, 
                        nscoord aScreenBegin, nscoord aScreenEnd,
                        nscoord aAnchorBegin, nscoord aAnchorEnd,
                        nscoord aMarginBegin, nscoord aMarginEnd,
                        nscoord aOffsetForContextMenu, FlipStyle aFlip,
                        bool* aFlipSide);
 
+  // check if the popup can fit into the available space by "sliding" (i.e.,
+  // by having the anchor arrow slide along one axis and only resizing if that
+  // can't provide the requested size). Only one axis can be slid - the other
+  // axis is "flipped" as normal. This method can handle either axis, but is
+  // only called for the sliding axis. All coordinates are in app units
+  // relative to the screen.
+  //   aScreenPoint - the point where the popup should appear
+  //   aSize - the size of the popup
+  //   aScreenBegin - the left or top edge of the screen
+  //   aScreenEnd - the right or bottom edge of the screen
+  //   aOffset - the amount by which the arrow must be slid such that it is
+  //             still aligned with the anchor.
+  // Result is the new size of the popup, which will typically be the same
+  // as aSize, unless aSize is greater than the screen width/height.
+  nscoord SlideOrResize(nscoord& aScreenPoint, nscoord aSize,
+                        nscoord aScreenBegin, nscoord aScreenEnd,
+                        nscoord *aOffset);
+
   // Move the popup to the position specified in its |left| and |top| attributes.
   void MoveToAttributePosition();
 
   /**
    * Return whether the popup direction should be RTL.
    * If the popup has an anchor, its direction is the anchor direction.
    * Otherwise, its the general direction of the UI.
    *
@@ -413,32 +433,39 @@ protected:
 
   // The position of the popup, in CSS pixels.
   // The screen coordinates, if set to values other than -1,
   // override mXPos and mYPos.
   int32_t mXPos;
   int32_t mYPos;
   int32_t mScreenXPos;
   int32_t mScreenYPos;
+
+  // If the panel prefers to "slide" rather than resize, then the arrow gets
+  // positioned at this offset (along either the x or y axis, depending on
+  // mPosition)
+  nscoord mAlignmentOffset;
+
   // The value of the client offset of our widget the last time we positioned
   // ourselves. We store this so that we can detect when it changes but the
   // position of our widget didn't change.
   nsIntPoint mLastClientOffset;
 
   nsPopupType mPopupType; // type of popup
   nsPopupState mPopupState; // open state of the popup
 
   // popup alignment relative to the anchor node
   int8_t mPopupAlignment;
   int8_t mPopupAnchor;
   int8_t mPosition;
 
   // One of nsIPopupBoxObject::ROLLUP_DEFAULT/ROLLUP_CONSUME/ROLLUP_NO_CONSUME
   int8_t mConsumeRollupEvent;
   bool mFlipBoth; // flip in both directions
+  bool mSlide; // allow the arrow to "slide" instead of resizing
 
   bool mIsOpenChanged; // true if the open state changed since the last layout
   bool mIsContextMenu; // true for context menus
   // true if we need to offset the popup to ensure it's not under the mouse
   bool mAdjustOffsetForContextMenu;
   bool mGeneratedChildren; // true if the contents have been created
 
   bool mMenuCanOverlapOSBar;    // can we appear over the taskbar/menubar?
--- a/layout/xul/base/src/nsPopupBoxObject.cpp
+++ b/layout/xul/base/src/nsPopupBoxObject.cpp
@@ -350,16 +350,33 @@ nsPopupBoxObject::GetAlignmentPosition(n
     default:
       // Leave as an empty string.
       break;
   }
 
   return NS_OK;
 }
 
+NS_IMETHODIMP
+nsPopupBoxObject::GetAlignmentOffset(int32_t *aAlignmentOffset)
+{
+  nsMenuPopupFrame *menuPopupFrame = do_QueryFrame(GetFrame(true));
+  if (!menuPopupFrame)
+    return NS_OK;
+
+  int32_t pp = nsDeviceContext::AppUnitsPerCSSPixel();
+  // Note that the offset might be along either the X or Y axis, but for the
+  // sake of simplicity we use a point with only the X axis set so we can
+  // use ToNearestPixels().
+  nsPoint appOffset(menuPopupFrame->GetAlignmentOffset(), 0);
+  nsIntPoint popupOffset = appOffset.ToNearestPixels(pp);
+  *aAlignmentOffset = popupOffset.x;
+  return NS_OK;
+}
+
 // Creation Routine ///////////////////////////////////////////////////////////////////////
 
 nsresult
 NS_NewPopupBoxObject(nsIBoxObject** aResult)
 {
   *aResult = new nsPopupBoxObject;
   if (!*aResult)
     return NS_ERROR_OUT_OF_MEMORY;
--- a/toolkit/content/tests/widgets/test_popupanchor.xul
+++ b/toolkit/content/tests/widgets/test_popupanchor.xul
@@ -14,16 +14,22 @@
   </panel>
 
   <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>      
 
 <script>
 <![CDATA[
 var anchor, panel, arrow;
 
+function is_close(got, exp, msg) {
+  // on some platforms we see differences of a fraction of a pixel - so
+  // allow any difference of < 1 pixels as being OK.
+  ok(Math.abs(got - exp) < 1, msg + ": " + got + " should be equal(-ish) to " + exp);
+}
+
 function isArrowPositionedOn(side, offset) {
   var arrowRect = arrow.getBoundingClientRect();
   var arrowMidX = (arrowRect.left + arrowRect.right) / 2;
   var arrowMidY = (arrowRect.top + arrowRect.bottom) / 2;
   var panelRect = panel.getBoundingClientRect();
   var panelMidX = (panelRect.left + panelRect.right) / 2;
   var panelMidY = (panelRect.top + panelRect.bottom) / 2;
   // First check the "flip" of the panel is correct.  If we are expecting the
@@ -43,21 +49,16 @@ function isArrowPositionedOn(side, offse
       break;
     case "bottom":
       ok(arrowMidY >= panelMidY, "arrow should be on the bottom of the panel");
       break;
     default:
       ok(false, "invalid position " + where);
       break;
   }
-  function is_close(got, exp, msg) {
-    // on some platforms we see differences of a fraction of a pixel - so
-    // allow any difference of < 1 pixels as being OK.
-    ok(Math.abs(got - exp) < 1, msg + ": " + got + " should be equal to " + exp);
-  }
   // Now check the arrow really is pointing where we expect.  The middle of
   // the arrow should be pointing exactly to the left (or right) side of the
   // anchor rect, +- any offsets.
   if (offset === null) // special case - explicit 'null' means 'don't check offset'
     return;
   offset = offset || 0; // no param means no offset expected.
   var anchorRect = anchor.getBoundingClientRect();
   var anchorPos = anchorRect[side];
@@ -73,17 +74,27 @@ function isArrowPositionedOn(side, offse
       is_close(panelRect.right, anchorRect.left, "right of panel should be left of anchor");
       break;
     default:
       ok(false, "unknown side " + side);
       break;
   }
 }
 
+function openSlidingPopup(position, callback) {
+  panel.setAttribute("flip", "slide");
+  _openPopup(position, callback);
+}
+
 function openPopup(position, callback) {
+  panel.setAttribute("flip", "both");
+  _openPopup(position, callback);
+}
+
+function _openPopup(position, callback) {
   // this is very ugly: the panel CSS sets the arrow's list-style-image based
   // on the 'side' attribute.  If the setting of the 'side' attribute causes
   // the image to change, we may get the popupshown event before the new
   // image has loaded - which causes the size of the arrow to be incorrect
   // for a brief moment - right when we are measuring it!
   // So we work around this in 2 steps:
   // * Force the 'side' attribute to a value which causes the CSS to not
   //   specify an image - then when the popup gets shown, the correct image
@@ -213,29 +224,74 @@ var tests = [
       isArrowPositionedOn("top", offset);
       panel.sizeTo(100, anchorBottom - 10);
       panel.moveToAnchor(anchor, "start_after", 0, 0);
       isArrowPositionedOn("bottom");
       next();
     });
   }],
 
-  ['veryWidePanel', 'middle', function(next) {
-    openPopup("after_end", function() {
+  ['veryWidePanel-after_end', 'middle', function(next) {
+    openSlidingPopup("after_end", function() {
       var origArrowRect = arrow.getBoundingClientRect();
       // Now move it such that the arrow can't be at either end of the panel but
+      // instead somewhere in the middle as that is the only way things fit,
+      // meaning the arrow should "slide" down the panel.
+      panel.sizeTo(window.innerWidth - 10, 60);
+      is(panel.getBoundingClientRect().width, window.innerWidth - 10, "width is what we requested.")
+      // the arrow should not have moved.
+      var curArrowRect = arrow.getBoundingClientRect();
+      is_close(curArrowRect.left, origArrowRect.left, "arrow should not have moved");
+      is_close(curArrowRect.top, origArrowRect.top, "arrow should not have moved up or down");
+      next();
+    });
+  }],
+
+  ['veryWidePanel-before_start', 'middle', function(next) {
+    openSlidingPopup("before_start", function() {
+      var origArrowRect = arrow.getBoundingClientRect();
+      // Now size it such that the arrow can't be at either end of the panel but
       // instead somewhere in the middle as that is the only way things fit.
-      // XXX - these tests might not be quite correct even when bug 812943
-      // is fixed.
       panel.sizeTo(window.innerWidth - 10, 60);
-      todo_is(panel.getBoundingClientRect().width, window.innerWidth - 10, "Bug 812943 - width is what we requested.")
+      is(panel.getBoundingClientRect().width, window.innerWidth - 10, "width is what we requested")
       // the arrow should not have moved.
       var curArrowRect = arrow.getBoundingClientRect();
-      todo_is(curArrowRect.left, origArrowRect.left, "Bug 812943 - arrow should not have moved");
-      is(curArrowRect.top, origArrowRect.top, "arrow should not have moved up or down");
+      is_close(curArrowRect.left, origArrowRect.left, "arrow should not have moved");
+      is_close(curArrowRect.top, origArrowRect.top, "arrow should not have moved up or down");
+      next();
+    });
+  }],
+
+  ['veryTallPanel-start_after', 'middle', function(next) {
+    openSlidingPopup("start_after", function() {
+      var origArrowRect = arrow.getBoundingClientRect();
+      // Now move it such that the arrow can't be at either end of the panel but
+      // instead somewhere in the middle as that is the only way things fit,
+      // meaning the arrow should "slide" down the panel.
+      panel.sizeTo(100, window.innerHeight - 10);
+      is(panel.getBoundingClientRect().height, window.innerHeight - 10, "height is what we requested.")
+      // the arrow should not have moved.
+      var curArrowRect = arrow.getBoundingClientRect();
+      is_close(curArrowRect.left, origArrowRect.left, "arrow should not have moved");
+      is_close(curArrowRect.top, origArrowRect.top, "arrow should not have moved up or down");
+      next();
+    });
+  }],
+
+  ['veryTallPanel-start_before', 'middle', function(next) {
+    openSlidingPopup("start_before", function() {
+      var origArrowRect = arrow.getBoundingClientRect();
+      // Now size it such that the arrow can't be at either end of the panel but
+      // instead somewhere in the middle as that is the only way things fit.
+      panel.sizeTo(100, window.innerHeight - 10);
+      is(panel.getBoundingClientRect().height, window.innerHeight - 10, "height is what we requested")
+      // the arrow should not have moved.
+      var curArrowRect = arrow.getBoundingClientRect();
+      is_close(curArrowRect.left, origArrowRect.left, "arrow should not have moved");
+      is_close(curArrowRect.top, origArrowRect.top, "arrow should not have moved up or down");
       next();
     });
   }],
 
   // Tests against the anchor at the right-hand side of the window
   ['afterend', 'right', function(next) {
     openPopup("after_end", function() {
       // when we request too far to the right/bottom, the panel gets shrunk
--- a/toolkit/content/widgets/popup.xml
+++ b/toolkit/content/widgets/popup.xml
@@ -129,16 +129,24 @@
 
       <property name="alignmentPosition" readonly="true">
         <getter>
         <![CDATA[
           return this.popupBoxObject.alignmentPosition;
         ]]>
         </getter>
       </property>
+
+      <property name="alignmentOffset" readonly="true">
+        <getter>
+        <![CDATA[
+          return this.popupBoxObject.alignmentOffset;
+        ]]>
+        </getter>
+      </property>
       
       <method name="enableKeyboardNavigator">
         <parameter name="aEnableKeyboardNavigator"/>
         <body>
         <![CDATA[
           this.popupBoxObject.enableKeyboardNavigator(aEnableKeyboardNavigator);
         ]]>
         </body>
@@ -385,38 +393,53 @@
         if (!anchor) {
           arrow.hidden = true;
           return;
         }
 
         var container = document.getAnonymousElementByAttribute(this, "anonid", "container");
         var arrowbox = document.getAnonymousElementByAttribute(this, "anonid", "arrowbox");
 
+        // if this panel has a "sliding" arrow, we may have previously set margins...
+        arrowbox.style.removeProperty("margin");
+
         var position = this.alignmentPosition;
         if (position.indexOf("start_") == 0 || position.indexOf("end_") == 0) {
           container.orient = "";
           arrowbox.orient = "vertical";
-          arrowbox.pack = (position.indexOf("_after") > 0) ? "end" : "start";
+          if (position.indexOf("_after") > 0) {
+            arrowbox.pack = "end";
+            arrowbox.style.marginBottom = this.alignmentOffset + "px";
+          } else {
+            arrowbox.pack = "start";
+            arrowbox.style.marginTop = this.alignmentOffset + "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";
           arrowbox.orient = "";
-          arrowbox.pack = (position.indexOf("_end") > 0) ? "end" : "start";
+          if (position.indexOf("_end") > 0) {
+            arrowbox.pack = "end";
+            arrowbox.style.marginRight = this.alignmentOffset + "px";
+          } else {
+            arrowbox.pack = "start";
+            arrowbox.style.marginLeft = this.alignmentOffset + "px";
+          }
 
           if (position.indexOf("before_") == 0) {
             container.dir = "reverse";
             this.setAttribute("side", "bottom");
           }
           else {
             container.dir = "";
             this.setAttribute("side", "top");