Bug 1109868, panels should watch their anchors for position and visibility changes and update accordingly, r=tn
authorNeil Deakin <neil@mozilla.com>
Thu, 16 Feb 2017 08:53:59 -0500
changeset 343387 d280a7a149bba92ecdb59b9de0a0336c336de889
parent 343386 b834e90881333f2f3ad4c900afce5b7657859daf
child 343388 98eeda9744e6c2f7d4fc3e97562c250c685cc30b
push id31378
push usercbook@mozilla.com
push dateFri, 17 Feb 2017 12:25:19 +0000
treeherdermozilla-central@03fc0a686d3f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerstn
bugs1109868
milestone54.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 1109868, panels should watch their anchors for position and visibility changes and update accordingly, r=tn
browser/base/content/popup-notifications.inc
dom/base/nsGkAtomList.h
layout/base/nsRefreshDriver.cpp
layout/xul/nsMenuPopupFrame.cpp
layout/xul/nsMenuPopupFrame.h
layout/xul/nsXULPopupManager.cpp
layout/xul/nsXULPopupManager.h
toolkit/content/tests/chrome/chrome.ini
toolkit/content/tests/chrome/test_panel_anchoradjust.xul
toolkit/content/tests/chrome/window_panel_anchoradjust.xul
--- a/browser/base/content/popup-notifications.inc
+++ b/browser/base/content/popup-notifications.inc
@@ -1,16 +1,17 @@
 # to be included inside a popupset element
 
     <panel id="notification-popup"
            type="arrow"
            position="after_start"
            hidden="true"
            orient="vertical"
            noautofocus="true"
+           followanchor="false"
            role="alert"/>
 
     <popupnotification id="webRTC-shareDevices-notification" hidden="true">
       <popupnotificationcontent id="webRTC-selectCamera" orient="vertical">
         <label value="&getUserMedia.selectCamera.label;"
                accesskey="&getUserMedia.selectCamera.accesskey;"
                control="webRTC-selectCamera-menulist"/>
         <menulist id="webRTC-selectCamera-menulist">
--- a/dom/base/nsGkAtomList.h
+++ b/dom/base/nsGkAtomList.h
@@ -390,16 +390,17 @@ GK_ATOM(flags, "flags")
 GK_ATOM(flex, "flex")
 GK_ATOM(flexgroup, "flexgroup")
 GK_ATOM(flip, "flip")
 GK_ATOM(floating, "floating")
 GK_ATOM(floor, "floor")
 GK_ATOM(flowlength, "flowlength")
 GK_ATOM(focus, "focus")
 GK_ATOM(focused, "focused")
+GK_ATOM(followanchor, "followanchor")
 GK_ATOM(following, "following")
 GK_ATOM(followingSibling, "following-sibling")
 GK_ATOM(font, "font")
 GK_ATOM(fontWeight, "font-weight")
 GK_ATOM(fontpicker, "fontpicker")
 GK_ATOM(footer, "footer")
 GK_ATOM(_for, "for")
 GK_ATOM(forEach, "for-each")
--- a/layout/base/nsRefreshDriver.cpp
+++ b/layout/base/nsRefreshDriver.cpp
@@ -65,16 +65,20 @@
 #include "mozilla/VsyncDispatcher.h"
 #include "nsThreadUtils.h"
 #include "mozilla/Unused.h"
 #include "mozilla/TimelineConsumers.h"
 #include "nsAnimationManager.h"
 #include "nsIDOMEvent.h"
 #include "nsDisplayList.h"
 
+#ifdef MOZ_XUL
+#include "nsXULPopupManager.h"
+#endif
+
 using namespace mozilla;
 using namespace mozilla::widget;
 using namespace mozilla::ipc;
 using namespace mozilla::layout;
 
 static mozilla::LazyLogModule sRefreshDriverLog("nsRefreshDriver");
 #define LOG(...) MOZ_LOG(sRefreshDriverLog, mozilla::LogLevel::Debug, (__VA_ARGS__))
 
@@ -1936,16 +1940,25 @@ nsRefreshDriver::Tick(int64_t aNowEpoch,
       aNowTime >= mNextRecomputeVisibilityTick &&
       !presShell->IsPaintingSuppressed()) {
     mNextRecomputeVisibilityTick = aNowTime + mMinRecomputeVisibilityInterval;
     mNeedToRecomputeVisibility = false;
 
     presShell->ScheduleApproximateFrameVisibilityUpdateNow();
   }
 
+#ifdef MOZ_XUL
+  // Update any popups that may need to be moved or hidden due to their
+  // anchor changing.
+  nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
+  if (pm) {
+    pm->UpdatePopupPositions(this);
+  }
+#endif
+
   nsCOMArray<nsIDocument> documents;
   CollectDocuments(mPresContext->Document(), &documents);
   for (int32_t i = 0; i < documents.Count(); ++i) {
     nsIDocument* doc = documents[i];
     doc->UpdateIntersectionObservations();
     doc->ScheduleIntersectionObserverNotification();
   }
 
--- a/layout/xul/nsMenuPopupFrame.cpp
+++ b/layout/xul/nsMenuPopupFrame.cpp
@@ -1303,16 +1303,37 @@ nsMenuPopupFrame::FlipOrResize(nscoord& 
   // If popupSize ended up being negative, or the original size was actually
   // 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);
 }
 
+nsRect
+nsMenuPopupFrame::ComputeAnchorRect(nsPresContext* aRootPresContext, nsIFrame* aAnchorFrame)
+{
+  // Get the root frame for a reference
+  nsIFrame* rootFrame = aRootPresContext->FrameManager()->GetRootFrame();
+
+  // The dimensions of the anchor
+  nsRect anchorRect = aAnchorFrame->GetRectRelativeToSelf();
+
+  // Relative to the root
+  anchorRect = nsLayoutUtils::TransformFrameRectToAncestor(aAnchorFrame,
+                                                           anchorRect,
+                                                           rootFrame);
+  // Relative to the screen
+  anchorRect.MoveBy(rootFrame->GetScreenRectInAppUnits().TopLeft());
+
+  // In its own app units
+  return anchorRect.ScaleToOtherAppUnitsRoundOut(aRootPresContext->AppUnitsPerDevPixel(),
+                                                 PresContext()->AppUnitsPerDevPixel());
+}
+
 nsresult
 nsMenuPopupFrame::SetPopupPosition(nsIFrame* aAnchorFrame, bool aIsMove, bool aSizedToPopup, bool aNotify)
 {
   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.
@@ -1359,32 +1380,17 @@ nsMenuPopupFrame::SetPopupPosition(nsIFr
 
         if (!aAnchorFrame) {
           aAnchorFrame = rootFrame;
           if (!aAnchorFrame)
             return NS_OK;
         }
       }
 
-      // And then we need its root frame for a reference
-      nsIFrame* referenceFrame = rootPresContext->FrameManager()->GetRootFrame();
-
-      // the dimensions of the anchor
-      nsRect parentRect = aAnchorFrame->GetRectRelativeToSelf();
-      // Relative to the root
-      anchorRect = nsLayoutUtils::TransformFrameRectToAncestor(aAnchorFrame,
-                                                               parentRect,
-                                                               referenceFrame);
-      // Relative to the screen
-      anchorRect.MoveBy(referenceFrame->GetScreenRectInAppUnits().TopLeft());
-
-      // In its own app units
-      anchorRect =
-        anchorRect.ScaleToOtherAppUnitsRoundOut(rootPresContext->AppUnitsPerDevPixel(),
-                                                presContext->AppUnitsPerDevPixel());
+      anchorRect = ComputeAnchorRect(rootPresContext, aAnchorFrame);
     }
 
     // The width is needed when aSizedToPopup is true
     parentWidth = anchorRect.width;
   }
 
   // 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
@@ -2209,16 +2215,23 @@ nsMenuPopupFrame::AttributeChanged(int32
 #ifndef MOZ_GTK2
   if (aAttribute == nsGkAtoms::noautohide) {
     nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
     if (pm)
       pm->EnableRollup(mContent, !IsNoAutoHide());
   }
 #endif
 
+  if (aAttribute == nsGkAtoms::followanchor) {
+    nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
+    if (pm) {
+      pm->UpdateFollowAnchor(this);
+    }
+  }
+
   if (aAttribute == nsGkAtoms::label) {
     // set the label for the titlebar
     nsView* view = GetView();
     if (view) {
       nsIWidget* widget = view->GetWidget();
       if (widget) {
         nsAutoString title;
         mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::label, title);
@@ -2353,16 +2366,20 @@ nsMenuPopupFrame::GetAutoPosition()
 {
   return mShouldAutoPosition;
 }
 
 void
 nsMenuPopupFrame::SetAutoPosition(bool aShouldAutoPosition)
 {
   mShouldAutoPosition = aShouldAutoPosition;
+  nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
+  if (pm) {
+    pm->UpdateFollowAnchor(this);
+  }
 }
 
 void
 nsMenuPopupFrame::SetConsumeRollupEvent(uint32_t aConsumeMode)
 {
   mConsumeRollupEvent = aConsumeMode;
 }
 
@@ -2441,8 +2458,102 @@ nsMenuPopupFrame::CreatePopupView()
   viewManager->InsertChild(parentView, view, nullptr, true);
 
   // Remember our view
   SetView(view);
 
   NS_FRAME_LOG(NS_FRAME_TRACE_CALLS,
     ("nsMenuPopupFrame::CreatePopupView: frame=%p view=%p", this, view));
 }
+
+bool
+nsMenuPopupFrame::ShouldFollowAnchor()
+{
+  if (!mShouldAutoPosition ||
+      mAnchorType != MenuPopupAnchorType_Node || !mAnchorContent) {
+    return false;
+  }
+
+  // Follow anchor mode is used when followanchor="true" is set or for arrow panels.
+  if (mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::followanchor,
+                            nsGkAtoms::_true, eCaseMatters)) {
+    return true;
+  }
+
+  if (mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::followanchor,
+                            nsGkAtoms::_false, eCaseMatters)) {
+    return false;
+  }
+
+  return (mPopupType == ePopupTypePanel &&
+          mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::type,
+                                nsGkAtoms::arrow, eCaseMatters));
+}
+
+bool
+nsMenuPopupFrame::ShouldFollowAnchor(nsRect& aRect)
+{
+  if (!ShouldFollowAnchor()) {
+    return false;
+  }
+
+  nsIFrame* anchorFrame = mAnchorContent->GetPrimaryFrame();
+  if (anchorFrame) {
+    nsPresContext* rootPresContext = PresContext()->GetRootPresContext();
+    if (rootPresContext) {
+      aRect = ComputeAnchorRect(rootPresContext, anchorFrame);
+    }
+  }
+
+  return true;
+}
+
+void
+nsMenuPopupFrame::CheckForAnchorChange(nsRect& aRect)
+{
+  // Don't update if the popup isn't visible or we shouldn't be following the anchor.
+  if (!IsVisible() || !ShouldFollowAnchor()) {
+    return;
+  }
+
+  bool shouldHide = false;
+
+  nsPresContext* rootPresContext = PresContext()->GetRootPresContext();
+
+  // If the frame for the anchor has gone away, hide the popup.
+  nsIFrame* anchor = mAnchorContent->GetPrimaryFrame();
+  if (!anchor || !rootPresContext) {
+    shouldHide = true;
+  } else if (!anchor->IsVisibleConsideringAncestors(VISIBILITY_CROSS_CHROME_CONTENT_BOUNDARY)) {
+    // If the anchor is now inside something that is invisible, hide the popup.
+    shouldHide = true;
+  } else {
+    // If the anchor is now inside a hidden parent popup, hide the popup.
+    nsIFrame* frame = anchor;
+    while (frame) {
+      nsMenuPopupFrame* popup = do_QueryFrame(frame);
+      if (popup && popup->PopupState() != ePopupShown) {
+        shouldHide = true;
+        break;
+      }
+
+      frame = frame->GetParent();
+    }
+  }
+
+  if (shouldHide) {
+    nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
+    if (pm) {
+      // As the caller will be iterating over the open popups, hide asyncronously.
+      pm->HidePopup(mContent, false, true, true, false);
+    }
+
+    return;
+  }
+
+  nsRect anchorRect = ComputeAnchorRect(rootPresContext, anchor);
+
+  // If the rectangles are different, move the popup.
+  if (!anchorRect.IsEqualEdges(aRect)) {
+    aRect = anchorRect;
+    SetPopupPosition(nullptr, true, false, true);
+  }
+}
--- a/layout/xul/nsMenuPopupFrame.h
+++ b/layout/xul/nsMenuPopupFrame.h
@@ -432,16 +432,23 @@ public:
     return false;
   }
 
   void ShowWithPositionedEvent() {
     mPopupState = ePopupPositioning;
     mShouldAutoPosition = true;
   }
 
+  // Checks for the anchor to change and either moves or hides the popup
+  // accordingly. The original position of the anchor should be supplied as
+  // the argument. If the popup needs to be hidden, HidePopup will be called by
+  // CheckForAnchorChange. If the popup needs to be moved, aRect will be updated
+  // with the new rectangle.
+  void CheckForAnchorChange(nsRect& aRect);
+
   // nsIReflowCallback
   virtual bool ReflowFinished() override;
   virtual void ReflowCallbackCanceled() override;
 
 protected:
 
   // returns the popup's level.
   nsPopupLevel PopupLevel(bool aIsNoAutoHide) const;
@@ -497,16 +504,20 @@ protected:
   //   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);
 
+  // Given an anchor frame, compute the anchor rectangle relative to the screen,
+  // using the popup frame's app units, and taking into account transforms.
+  nsRect ComputeAnchorRect(nsPresContext* aRootPresContext, nsIFrame* aAnchorFrame);
+
   // 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.
    *
@@ -517,16 +528,27 @@ protected:
       ? mAnchorContent->GetPrimaryFrame()->StyleVisibility()->mDirection == NS_STYLE_DIRECTION_RTL
       : StyleVisibility()->mDirection == NS_STYLE_DIRECTION_RTL;
   }
 
   // Create a popup view for this frame. The view is added a child of the root
   // view, and is initially hidden.
   void CreatePopupView();
 
+  // Returns true if the popup should try to remain at the same relative
+  // location as the anchor while it is open. If the anchor becomes hidden
+  // either directly or indirectly because a parent popup or other element
+  // is no longer visible, or a parent deck page is changed, the popup hides
+  // as well. The second variation also sets the anchor rectangle, relative to
+  // the popup frame.
+  bool ShouldFollowAnchor();
+public:
+  bool ShouldFollowAnchor(nsRect& aRect);
+
+protected:
   nsString     mIncrementalString;  // for incremental typing navigation
 
   // the content that the popup is anchored to, if any, which may be in a
   // different document than the popup.
   nsCOMPtr<nsIContent> mAnchorContent;
 
   // the content that triggered the popup, typically the node where the mouse
   // was clicked. It will be cleared when the popup is hidden.
--- a/layout/xul/nsXULPopupManager.cpp
+++ b/layout/xul/nsXULPopupManager.cpp
@@ -105,16 +105,30 @@ void nsMenuChainItem::Detach(nsMenuChain
     // An item without a child should be the first item in the chain, so set
     // the first item pointer, pointed to by aRoot, to the parent.
     NS_ASSERTION(this == *aRoot, "Unexpected - popup with no child not at end of chain");
     *aRoot = mParent;
     SetParent(nullptr);
   }
 }
 
+void
+nsMenuChainItem::UpdateFollowAnchor()
+{
+  mFollowAnchor = mFrame->ShouldFollowAnchor(mCurrentRect);
+}
+
+void
+nsMenuChainItem::CheckForAnchorChange()
+{
+  if (mFollowAnchor) {
+    mFrame->CheckForAnchorChange(mCurrentRect);
+  }
+}
+
 bool nsXULPopupManager::sDevtoolsDisableAutoHide = false;
 
 const char* kPrefDevtoolsDisableAutoHide =
   "ui.popup.disable_autohide";
 
 NS_IMPL_ISUPPORTS(nsXULPopupManager,
                   nsIDOMEventListener,
                   nsITimerCallback,
@@ -938,16 +952,18 @@ nsXULPopupManager::ShowPopupCallback(nsI
     nsIContent* oldmenu = nullptr;
     if (mPopups)
       oldmenu = mPopups->Content();
     item->SetParent(mPopups);
     mPopups = item;
     SetCaptureState(oldmenu);
   }
 
+  item->UpdateFollowAnchor();
+
   if (aSelectFirstItem) {
     nsMenuFrame* next = GetNextMenuItem(aPopupFrame, nullptr, true, false);
     aPopupFrame->SetCurrentMenuItem(next);
   }
 
   if (ismenu)
     UpdateMenuItems(aPopup);
 
@@ -1220,24 +1236,16 @@ nsXULPopupManager::HidePopupCallback(nsI
       FirePopupHidingEvent(popupToHide, nextPopup, aLastPopup,
                            popupFrame->PresContext(),
                            foundMenu->PopupType(), aDeselectMenu, false);
     }
   }
 }
 
 void
-nsXULPopupManager::HidePopup(nsIFrame* aFrame)
-{
-  nsMenuPopupFrame* popup = do_QueryFrame(aFrame);
-  if (popup)
-    HidePopup(aFrame->GetContent(), false, true, false, false);
-}
-
-void
 nsXULPopupManager::HidePopupAfterDelay(nsMenuPopupFrame* aPopup)
 {
   // Don't close up immediately.
   // Kick off a close timer.
   KillMenuTimer();
 
   int32_t menuDelay =
     LookAndFeel::GetInt(LookAndFeel::eIntID_SubmenuDelay, 300); // ms
@@ -1360,16 +1368,51 @@ nsXULPopupManager::HidePopupsInDocShell(
     }
     item = parent;
   }
 
   HidePopupsInList(popupsToHide);
 }
 
 void
+nsXULPopupManager::UpdatePopupPositions(nsRefreshDriver* aRefreshDriver)
+{
+  if (!mPopups && !mNoHidePanels) {
+    return;
+  }
+
+  for (int32_t i = 0; i < 2; i++) {
+    nsMenuChainItem* item = i == 0 ? mPopups : mNoHidePanels;
+    while (item) {
+      if (item->Frame()->PresContext()->RefreshDriver() == aRefreshDriver) {
+        item->CheckForAnchorChange();
+      }
+
+      item = item->GetParent();
+    }
+  }
+}
+
+void
+nsXULPopupManager::UpdateFollowAnchor(nsMenuPopupFrame* aPopup)
+{
+  for (int32_t i = 0; i < 2; i++) {
+    nsMenuChainItem* item = i == 0 ? mPopups : mNoHidePanels;
+    while (item) {
+      if (item->Frame() == aPopup) {
+        item->UpdateFollowAnchor();
+        break;
+      }
+
+      item = item->GetParent();
+    }
+  }
+}
+
+void
 nsXULPopupManager::ExecuteMenu(nsIContent* aMenu, nsXULMenuCommandEvent* aEvent)
 {
   CloseMenuMode cmm = CloseMenuMode_Auto;
 
   static nsIContent::AttrValuesArray strings[] =
     {&nsGkAtoms::none, &nsGkAtoms::single, nullptr};
 
   switch (aMenu->FindAttrValueIn(kNameSpaceID_None, nsGkAtoms::closemenu,
--- a/layout/xul/nsXULPopupManager.h
+++ b/layout/xul/nsXULPopupManager.h
@@ -50,16 +50,17 @@
 class nsContainerFrame;
 class nsMenuFrame;
 class nsMenuPopupFrame;
 class nsMenuBarFrame;
 class nsMenuParent;
 class nsIDOMKeyEvent;
 class nsIDocShellTreeItem;
 class nsPIDOMWindowOuter;
+class nsRefreshDriver;
 
 // when a menu command is executed, the closemenu attribute may be used
 // to define how the menu should be closed up
 enum CloseMenuMode {
   CloseMenuMode_Auto, // close up the chain of menus, default value
   CloseMenuMode_None, // don't close up any menus
   CloseMenuMode_Single // close up only the menu the command is inside
 };
@@ -133,26 +134,34 @@ class nsMenuChainItem
 {
 private:
   nsMenuPopupFrame* mFrame; // the popup frame
   nsPopupType mPopupType; // the popup type of the frame
   bool mIsContext; // true for context menus
   bool mOnMenuBar; // true if the menu is on a menu bar
   nsIgnoreKeys mIgnoreKeys; // indicates how keyboard listeners should be used
 
+  // True if the popup should maintain its position relative to the anchor when
+  // the anchor moves.
+  bool mFollowAnchor;
+
+  // The last seen position of the anchor, relative to the screen.
+  nsRect mCurrentRect;
+
   nsMenuChainItem* mParent;
   nsMenuChainItem* mChild;
 
 public:
   nsMenuChainItem(nsMenuPopupFrame* aFrame, bool aIsContext, nsPopupType aPopupType)
     : mFrame(aFrame),
       mPopupType(aPopupType),
       mIsContext(aIsContext),
       mOnMenuBar(false),
       mIgnoreKeys(eIgnoreKeys_False),
+      mFollowAnchor(false),
       mParent(nullptr),
       mChild(nullptr)
   {
     NS_ASSERTION(aFrame, "null frame passed to nsMenuChainItem constructor");
     MOZ_COUNT_CTOR(nsMenuChainItem);
   }
 
   ~nsMenuChainItem()
@@ -166,22 +175,25 @@ public:
   bool IsMenu() { return mPopupType == ePopupTypeMenu; }
   bool IsContextMenu() { return mIsContext; }
   nsIgnoreKeys IgnoreKeys() { return mIgnoreKeys; }
   void SetIgnoreKeys(nsIgnoreKeys aIgnoreKeys) { mIgnoreKeys = aIgnoreKeys; }
   bool IsOnMenuBar() { return mOnMenuBar; }
   void SetOnMenuBar(bool aOnMenuBar) { mOnMenuBar = aOnMenuBar; }
   nsMenuChainItem* GetParent() { return mParent; }
   nsMenuChainItem* GetChild() { return mChild; }
+  bool FollowsAnchor() { return mFollowAnchor; }
+  void UpdateFollowAnchor();
+  void CheckForAnchorChange();
 
   // set the parent of this item to aParent, also changing the parent
   // to have this as a child.
   void SetParent(nsMenuChainItem* aParent);
 
-  // removes an item from the chain. The root pointer must be supplied in case
+  // Removes an item from the chain. The root pointer must be supplied in case
   // the item is the first item in the chain in which case the pointer will be
   // set to the next item, or null if there isn't another item. After detaching,
   // this item will not have a parent or a child.
   void Detach(nsMenuChainItem** aRoot);
 };
 
 // this class is used for dispatching popupshowing events asynchronously.
 class nsXULPopupShowingEvent : public mozilla::Runnable
@@ -495,22 +507,16 @@ public:
   void HidePopup(nsIContent* aPopup,
                  bool aHideChain,
                  bool aDeselectMenu,
                  bool aAsynchronous,
                  bool aIsCancel,
                  nsIContent* aLastPopup = nullptr);
 
   /**
-   * Hide the popup aFrame. This method is called by the view manager when the
-   * close button is pressed.
-   */
-  void HidePopup(nsIFrame* aFrame);
-
-  /**
    * Hide a popup after a short delay. This is used when rolling over menu items.
    * This timer is stored in mCloseTimer. The timer may be cancelled and the popup
    * closed by calling KillMenuTimer.
    */
   void HidePopupAfterDelay(nsMenuPopupFrame* aPopup);
 
   /**
    * Hide all of the popups from a given docshell. This should be called when the
@@ -522,16 +528,29 @@ public:
    * Enable or disable the dynamic noautohide state of a panel.
    *
    * aPanel - the panel whose state is to change
    * aShouldRollup - whether the panel is no longer noautohide
    */
   void EnableRollup(nsIContent* aPopup, bool aShouldRollup);
 
   /**
+   * Check if any popups need to be repositioned or hidden after a style or
+   * layout change. This will update, for example, any arrow type panels when
+   * the anchor that is is pointing to has moved, resized or gone away.
+   * Only those popups that pertain to the supplied aRefreshDriver are updated.
+   */
+  void UpdatePopupPositions(nsRefreshDriver* aRefreshDriver);
+
+  /**
+   * Enable or disable anchor following on the popup if needed.
+   */
+  void UpdateFollowAnchor(nsMenuPopupFrame* aPopup);
+
+  /**
    * Execute a menu command from the triggering event aEvent.
    *
    * aMenu - a menuitem to execute
    * aEvent - an nsXULMenuCommandEvent that contains all the info from the mouse
    *          event which triggered the menu to be executed, may not be null
    */
   void ExecuteMenu(nsIContent* aMenu, nsXULMenuCommandEvent* aEvent);
 
--- a/toolkit/content/tests/chrome/chrome.ini
+++ b/toolkit/content/tests/chrome/chrome.ini
@@ -24,16 +24,17 @@ support-files =
   frame_subframe_origin_subframe2.xul
   popup_childframe_node.xul
   popup_trigger.js
   sample_entireword_latin1.html
   window_browser_drop.xul
   window_keys.xul
   window_largemenu.xul
   window_panel.xul
+  window_panel_anchoradjust.xul
   window_popup_anchor.xul
   window_popup_anchoratrect.xul
   window_popup_attribute.xul
   window_popup_button.xul
   window_popup_preventdefault_chrome.xul
   window_preferences.xul
   window_preferences2.xul
   window_preferences3.xul
@@ -120,16 +121,17 @@ skip-if = os == 'linux' && !debug #Bug 1
 [test_menulist.xul]
 [test_menulist_keynav.xul]
 [test_menulist_null_value.xul]
 [test_menulist_paging.xul]
 [test_menulist_position.xul]
 [test_mousescroll.xul]
 [test_notificationbox.xul]
 [test_panel.xul]
+[test_panel_anchoradjust.xul]
 [test_panelfrommenu.xul]
 [test_popup_anchor.xul]
 [test_popup_anchoratrect.xul]
 skip-if = os == 'linux' # 1167694
 [test_popup_attribute.xul]
 skip-if = os == 'linux' && asan #Bug 1131634
 [test_popup_button.xul]
 skip-if = os == 'linux' && asan # Bug 1281360
new file mode 100644
--- /dev/null
+++ b/toolkit/content/tests/chrome/test_panel_anchoradjust.xul
@@ -0,0 +1,31 @@
+<?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"?>
+
+<window title="Test Panel Position When Anchor Changes"
+        onload="runTest()"
+        xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+  <script type="application/javascript"
+          src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+  <script type="application/javascript"
+          src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/>
+
+<script>
+SimpleTest.waitForExplicitFinish();
+function runTest()
+{
+  window.open("window_panel_anchoradjust.xul", "_blank", "chrome,left=200,top=200,width=200,height=200");
+}
+</script>
+
+<body xmlns="http://www.w3.org/1999/xhtml">
+<p id="display">
+</p>
+<div id="content" style="display: none">
+</div>
+<pre id="test">
+</pre>
+</body>
+
+</window>
new file mode 100644
--- /dev/null
+++ b/toolkit/content/tests/chrome/window_panel_anchoradjust.xul
@@ -0,0 +1,183 @@
+<?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"?>
+
+<window width="200" height="200"
+        xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+  <script type="application/javascript"
+          src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+  <script type="application/javascript"
+          src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/>
+
+<deck id="deck">
+  <hbox id="container">
+    <button id="anchor" label="Anchor"/>
+  </hbox>
+  <button id="anchor3" label="Anchor3"/>
+</deck>
+
+<hbox id="container2">
+  <button id="anchor2" label="Anchor2"/>
+</hbox>
+
+<button id="anchor4" label="Anchor4"/>
+
+<panel id="panel" type="arrow">
+  <button label="OK"/>
+</panel>
+
+<menupopup id="menupopup">
+  <menuitem label="One"/>
+  <menuitem id="menuanchor" label="Two"/>
+  <menuitem label="Three"/>
+</menupopup>
+
+<script type="application/javascript"><![CDATA[
+
+Components.utils.import("resource://gre/modules/Task.jsm");
+
+SimpleTest.waitForExplicitFinish();
+
+function next()
+{
+  return new Promise(r => SimpleTest.executeSoon(r));
+}
+
+function waitForPanel(panel, event)  
+{
+  return new Promise(resolve => {
+    panel.addEventListener(event, () => { resolve(); }, { once: true });
+  });
+}
+
+function isWithinHalfPixel(a, b)
+{
+  return Math.abs(a - b) <= 0.5;
+}
+
+function runTests() {
+  Task.spawn(function*() {
+    let panel = document.getElementById("panel");
+    let anchor = document.getElementById("anchor");
+
+    let popupshown = waitForPanel(panel, "popupshown");
+    panel.openPopup(anchor, "after_start");
+    yield popupshown;
+
+    let anchorrect = anchor.getBoundingClientRect();
+    let panelrect = panel.getBoundingClientRect();
+    let xarrowdiff = panelrect.left - anchorrect.left;
+
+    // When the anchor is moved in some manner, the panel should be adjusted
+    let popuppositioned = waitForPanel(panel, "popuppositioned");
+    document.getElementById("anchor").style.marginLeft = "50px"
+    yield popuppositioned;
+
+    anchorrect = anchor.getBoundingClientRect();
+    panelrect = panel.getBoundingClientRect();
+    ok(isWithinHalfPixel(anchorrect.left, panelrect.left - xarrowdiff), "anchor moved x");
+    ok(isWithinHalfPixel(anchorrect.bottom, panelrect.top), "anchor moved y");
+
+    // moveToAnchor is used to change the anchor
+    let anchor2 = document.getElementById("anchor2");
+    popuppositioned = waitForPanel(panel, "popuppositioned");
+    panel.moveToAnchor(anchor2, "after_end");
+    yield popuppositioned;
+
+    let anchor2rect = anchor2.getBoundingClientRect();
+    panelrect = panel.getBoundingClientRect();
+    ok(isWithinHalfPixel(anchor2rect.right, panelrect.right + xarrowdiff), "new anchor x");
+    ok(isWithinHalfPixel(anchor2rect.bottom, panelrect.top), "new anchor y");
+
+    // moveToAnchor is used to change the anchor with an x and y offset
+    popuppositioned = waitForPanel(panel, "popuppositioned");
+    panel.moveToAnchor(anchor2, "after_end", 7, 9);
+    yield popuppositioned;
+
+    anchor2rect = anchor2.getBoundingClientRect();
+    panelrect = panel.getBoundingClientRect();
+    ok(isWithinHalfPixel(anchor2rect.right + 7, panelrect.right + xarrowdiff), "new anchor with offset x");
+    ok(isWithinHalfPixel(anchor2rect.bottom + 9, panelrect.top), "new anchor with offset y");
+
+    // When the container of the anchor is collapsed, the panel should be hidden.
+    let popuphidden = waitForPanel(panel, "popuphidden");
+    anchor2.parentNode.collapsed = true;
+    yield popuphidden;
+
+    popupshown = waitForPanel(panel, "popupshown");
+    panel.openPopup(anchor, "after_start");
+    yield popupshown;
+
+    // When the deck containing the anchor changes to a different page, the panel should be hidden.
+    popuphidden = waitForPanel(panel, "popuphidden");
+    document.getElementById("deck").selectedIndex = 1;
+    yield popuphidden;
+
+    let anchor3 = document.getElementById("anchor3");
+    popupshown = waitForPanel(panel, "popupshown");
+    panel.openPopup(anchor3, "after_start");
+    yield popupshown;
+
+    // When the anchor is hidden; the panel should be hidden.
+    popuphidden = waitForPanel(panel, "popuphidden");
+    anchor3.parentNode.hidden = true;
+    yield popuphidden;
+
+    // When the panel is anchored to an element in a popup, the panel should
+    // also be hidden when that popup is hidden.
+    let menupopup = document.getElementById("menupopup");
+    popupshown = waitForPanel(menupopup, "popupshown");
+    menupopup.openPopupAtScreen(200, 200);
+    yield popupshown;
+
+    popupshown = waitForPanel(panel, "popupshown");
+    panel.openPopup(document.getElementById("menuanchor"), "after_start");
+    yield popupshown;
+
+    popuphidden = waitForPanel(panel, "popuphidden");
+    menupopuphidden = waitForPanel(menupopup, "popuphidden");
+    menupopup.hidePopup();
+    yield popuphidden;
+    yield menupopuphidden;
+
+    // The panel should no longer follow anchors.
+    panel.setAttribute("followanchor", "false");
+
+    let anchor4 = document.getElementById("anchor4");
+    popupshown = waitForPanel(panel, "popupshown");
+    panel.openPopup(anchor4, "after_start");
+    yield popupshown;
+
+    let anchor4rect = anchor4.getBoundingClientRect();
+    panelrect = panel.getBoundingClientRect();
+
+    document.getElementById("anchor4").style.marginLeft = "50px"
+    yield next();
+
+    panelrect = panel.getBoundingClientRect();
+    ok(isWithinHalfPixel(anchor4rect.left, panelrect.left - xarrowdiff), "no follow anchor x");
+    ok(isWithinHalfPixel(anchor4rect.bottom, panelrect.top), "no follow anchor y");
+
+    popuphidden = waitForPanel(panel, "popuphidden");
+    panel.hidePopup();
+    yield popuphidden;
+
+    window.close();
+    window.opener.wrappedJSObject.SimpleTest.finish();
+  });
+}
+
+function ok(condition, message) {
+  window.opener.wrappedJSObject.SimpleTest.ok(condition, message);
+}
+
+function is(left, right, message) {
+  window.opener.wrappedJSObject.SimpleTest.is(left, right, message);
+}
+
+window.opener.wrappedJSObject.SimpleTest.waitForFocus(runTests, window);
+
+]]>
+</script>
+
+</window>