From ad4b55af1ff3ccb7e380779122ddb2ef2b62a7a9 Mon Sep 17 00:00:00 2001 From: Kirk Steuber Date: Tue, 16 Aug 2016 15:33:05 -0700 Subject: [PATCH] Bug 1206133 - Add popuppositioning state and popuppositioned event to improve arrow panel position handling. r=enndeakin MozReview-Commit-ID: Dh1npORCQ6J --HG-- extra : rebase_source : 5df6076561a746791c44d249afa31009d0e1b30a --- browser/base/content/browser-places.js | 24 ++++- .../places/content/editBookmarkOverlay.js | 12 +++ dom/base/nsGkAtomList.h | 1 + dom/events/EventNameList.h | 4 + layout/xul/PopupBoxObject.cpp | 1 + layout/xul/nsMenuPopupFrame.cpp | 32 +++++-- layout/xul/nsMenuPopupFrame.h | 16 +++- layout/xul/nsXULPopupManager.cpp | 94 ++++++++++++++++++- layout/xul/nsXULPopupManager.h | 29 ++++++ .../tests/widgets/test_popupanchor.xul | 55 +++++++---- toolkit/content/widgets/popup.xml | 30 +++--- toolkit/modules/PopupNotifications.jsm | 34 +++++-- widget/EventMessageList.h | 1 + 13 files changed, 272 insertions(+), 61 deletions(-) diff --git a/browser/base/content/browser-places.js b/browser/base/content/browser-places.js index c9cba5bc0c75..d4463e320b9b 100644 --- a/browser/base/content/browser-places.js +++ b/browser/base/content/browser-places.js @@ -260,12 +260,26 @@ var StarUI = { parent.setAttribute("open", "true"); } } - this.panel.openPopup(aAnchorElement, aPosition); + let panel = this.panel; + let target = panel; + if (target.parentNode) { + // By targeting the panel's parent and using a capturing listener, we + // can have our listener called before others waiting for the panel to + // be shown (which probably expect the panel to be fully initialized) + target = target.parentNode; + } + target.addEventListener("popupshown", function shownListener(event) { + if (event.target == panel) { + target.removeEventListener("popupshown", shownListener, true); - gEditItemOverlay.initPanel({ node: aNode - , hiddenRows: ["description", "location", - "loadInSidebar", "keyword"] - , focusedElement: "preferred" }); + gEditItemOverlay.initPanel({ node: aNode + , hiddenRows: ["description", "location", + "loadInSidebar", "keyword"] + , focusedElement: "preferred"}); + } + }, true); + + this.panel.openPopup(aAnchorElement, aPosition); }), panelShown: diff --git a/browser/components/places/content/editBookmarkOverlay.js b/browser/components/places/content/editBookmarkOverlay.js index 105ab9b54d83..4a0497dccf68 100644 --- a/browser/components/places/content/editBookmarkOverlay.js +++ b/browser/components/places/content/editBookmarkOverlay.js @@ -177,6 +177,18 @@ var gEditItemOverlay = { initPanel(aInfo) { if (typeof(aInfo) != "object" || aInfo === null) throw new Error("aInfo must be an object."); + if ("node" in aInfo) { + try { + aInfo.node.type; + } catch (e) { + // If the lazy loader for |type| generates an exception, it means that + // this bookmark could not be loaded. This sometimes happens when tests + // create a bookmark by clicking the bookmark star, then try to cleanup + // before the bookmark panel has finished opening. Either way, if we + // cannot retrieve the bookmark information, we cannot open the panel. + return; + } + } // For sanity ensure that the implementer has uninited the panel before // trying to init it again, or we could end up leaking due to observers. diff --git a/dom/base/nsGkAtomList.h b/dom/base/nsGkAtomList.h index 144e18a6f358..bf37e954a0d1 100644 --- a/dom/base/nsGkAtomList.h +++ b/dom/base/nsGkAtomList.h @@ -884,6 +884,7 @@ GK_ATOM(onpointerlockchange, "onpointerlockchange") GK_ATOM(onpointerlockerror, "onpointerlockerror") GK_ATOM(onpopuphidden, "onpopuphidden") GK_ATOM(onpopuphiding, "onpopuphiding") +GK_ATOM(onpopuppositioned, "onpopuppositioned") GK_ATOM(onpopupshowing, "onpopupshowing") GK_ATOM(onpopupshown, "onpopupshown") GK_ATOM(onposter, "onposter") diff --git a/dom/events/EventNameList.h b/dom/events/EventNameList.h index 3d13068675df..3126c4ffff26 100644 --- a/dom/events/EventNameList.h +++ b/dom/events/EventNameList.h @@ -781,6 +781,10 @@ NON_IDL_EVENT(popupshown, eXULPopupShown, EventNameType_XUL, eBasicEventClass) +NON_IDL_EVENT(popuppositioned, + eXULPopupPositioned, + EventNameType_XUL, + eBasicEventClass) NON_IDL_EVENT(popuphiding, eXULPopupHiding, EventNameType_XUL, diff --git a/layout/xul/PopupBoxObject.cpp b/layout/xul/PopupBoxObject.cpp index 523dbb4e459f..00ecc943d8fc 100644 --- a/layout/xul/PopupBoxObject.cpp +++ b/layout/xul/PopupBoxObject.cpp @@ -232,6 +232,7 @@ PopupBoxObject::GetPopupState(nsString& aState) aState.AssignLiteral("open"); break; case ePopupShowing: + case ePopupPositioning: case ePopupOpening: case ePopupVisible: aState.AssignLiteral("showing"); diff --git a/layout/xul/nsMenuPopupFrame.cpp b/layout/xul/nsMenuPopupFrame.cpp index 894513fcb1cd..b6bbdb6e8feb 100644 --- a/layout/xul/nsMenuPopupFrame.cpp +++ b/layout/xul/nsMenuPopupFrame.cpp @@ -436,7 +436,7 @@ nsMenuPopupFrame::LayoutPopup(nsBoxLayoutState& aState, nsIFrame* aParentMenu, if (!isOpen) { // if the popup is not open, only do layout while showing or if the menu // is sized to the popup - shouldPosition = (mPopupState == ePopupShowing); + shouldPosition = (mPopupState == ePopupShowing || mPopupState == ePopupPositioning); if (!shouldPosition && !aSizedToPopup) { RemoveStateBits(NS_FRAME_FIRST_REFLOW); return; @@ -476,7 +476,7 @@ nsMenuPopupFrame::LayoutPopup(nsBoxLayoutState& aState, nsIFrame* aParentMenu, bool needCallback = false; if (shouldPosition) { - SetPopupPosition(aAnchor, false, aSizedToPopup); + SetPopupPosition(aAnchor, false, aSizedToPopup, mPopupState == ePopupPositioning); needCallback = true; } @@ -502,7 +502,7 @@ nsMenuPopupFrame::LayoutPopup(nsBoxLayoutState& aState, nsIFrame* aParentMenu, } if (rePosition) { - SetPopupPosition(aAnchor, false, aSizedToPopup); + SetPopupPosition(aAnchor, false, aSizedToPopup, false); } nsPresContext* pc = PresContext(); @@ -561,7 +561,7 @@ nsMenuPopupFrame::LayoutPopup(nsBoxLayoutState& aState, nsIFrame* aParentMenu, bool nsMenuPopupFrame::ReflowFinished() { - SetPopupPosition(mReflowCallbackData.mAnchor, false, mReflowCallbackData.mSizedToPopup); + SetPopupPosition(mReflowCallbackData.mAnchor, false, mReflowCallbackData.mSizedToPopup, false); mReflowCallbackData.Clear(); @@ -865,7 +865,7 @@ nsMenuPopupFrame::ShowPopup(bool aIsContextMenu) InvalidateFrameSubtree(); - if (mPopupState == ePopupShowing) { + if (mPopupState == ePopupShowing || mPopupState == ePopupPositioning) { mPopupState = ePopupOpening; mIsOpenChanged = true; @@ -906,7 +906,8 @@ nsMenuPopupFrame::HidePopup(bool aDeselectMenu, nsPopupState aNewState) ClearPopupShownDispatcher(); // don't hide the popup when it isn't open - if (mPopupState == ePopupClosed || mPopupState == ePopupShowing) + if (mPopupState == ePopupClosed || mPopupState == ePopupShowing || + mPopupState == ePopupPositioning) return; // clear the trigger content if the popup is being closed. But don't clear @@ -1184,6 +1185,8 @@ nsMenuPopupFrame::FlipOrResize(nscoord& aScreenPoint, nscoord aSize, nscoord aOffsetForContextMenu, FlipStyle aFlip, bool* aFlipSide) { + *aFlipSide = false; + // all of the coordinates used here are in app units relative to the screen nscoord popupSize = aSize; if (aScreenPoint < aScreenBegin) { @@ -1284,7 +1287,7 @@ nsMenuPopupFrame::FlipOrResize(nscoord& aScreenPoint, nscoord aSize, } nsresult -nsMenuPopupFrame::SetPopupPosition(nsIFrame* aAnchorFrame, bool aIsMove, bool aSizedToPopup) +nsMenuPopupFrame::SetPopupPosition(nsIFrame* aAnchorFrame, bool aIsMove, bool aSizedToPopup, bool aNotify) { if (!mShouldAutoPosition) return NS_OK; @@ -1589,6 +1592,17 @@ nsMenuPopupFrame::SetPopupPosition(nsIFrame* aAnchorFrame, bool aIsMove, bool aS SetXULBounds(state, mRect); } + // If the popup is in the positioned state or if it is shown and the position + // or size changed, dispatch a popuppositioned event if the popup wants it. + nsIntRect newRect(screenPoint.x, screenPoint.y, mRect.width, mRect.height); + if (mPopupState == ePopupPositioning || + (mPopupState == ePopupShown && !newRect.IsEqualEdges(mUsedScreenRect))) { + mUsedScreenRect = newRect; + if (aNotify) { + nsXULPopupPositionedEvent::DispatchIfNeeded(mContent, false, false); + } + } + return NS_OK; } @@ -2270,7 +2284,7 @@ nsMenuPopupFrame::MoveTo(const CSSIntPoint& aPos, bool aUpdateAttrs) mScreenRect.x = aPos.x - presContext->AppUnitsToIntCSSPixels(margin.left); mScreenRect.y = aPos.y - presContext->AppUnitsToIntCSSPixels(margin.top); - SetPopupPosition(nullptr, true, false); + SetPopupPosition(nullptr, true, false, true); nsCOMPtr popup = mContent; if (aUpdateAttrs && (popup->HasAttr(kNameSpaceID_None, nsGkAtoms::left) || @@ -2299,7 +2313,7 @@ nsMenuPopupFrame::MoveToAnchor(nsIContent* aAnchorContent, mPopupState = oldstate; // Pass false here so that flipping and adjusting to fit on the screen happen. - SetPopupPosition(nullptr, false, false); + SetPopupPosition(nullptr, false, false, true); } bool diff --git a/layout/xul/nsMenuPopupFrame.h b/layout/xul/nsMenuPopupFrame.h index 72593e53e0e7..6b509b69085f 100644 --- a/layout/xul/nsMenuPopupFrame.h +++ b/layout/xul/nsMenuPopupFrame.h @@ -48,6 +48,8 @@ enum nsPopupState { // state from when a popup is requested to be shown to after the // popupshowing event has been fired. ePopupShowing, + // state while a popup is waiting to be laid out and positioned + ePopupPositioning, // state while a popup is open but the widget is not yet visible ePopupOpening, // state while a popup is visible and waiting for the popupshown event @@ -251,8 +253,10 @@ public: // (or the frame for mAnchorContent if aAnchorFrame is null), anchored at a // rectangle, or at a specific point if a screen position is 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, bool aSizedToPopup); + // popup is being moved, and should not be flipped. If aNotify is true, then + // a popuppositioned event is sent. + nsresult SetPopupPosition(nsIFrame* aAnchorFrame, bool aIsMove, + bool aSizedToPopup, bool aNotify); bool HasGeneratedChildren() { return mGeneratedChildren; } void SetGeneratedChildren() { mGeneratedChildren = true; } @@ -425,6 +429,11 @@ public: return false; } + void ShowWithPositionedEvent() { + mPopupState = ePopupPositioning; + mShouldAutoPosition = true; + } + // nsIReflowCallback virtual bool ReflowFinished() override; virtual void ReflowCallbackCanceled() override; @@ -524,6 +533,9 @@ protected: RefPtr mPopupShownDispatcher; + // The popup's screen rectangle in app units. + nsIntRect mUsedScreenRect; + // A popup's preferred size may be different than its actual size stored in // mRect in the case where the popup was resized because it was too large // for the screen. The preferred size mPrefSize holds the full size the popup diff --git a/layout/xul/nsXULPopupManager.cpp b/layout/xul/nsXULPopupManager.cpp index cf60814cc3c7..7b546431f153 100644 --- a/layout/xul/nsXULPopupManager.cpp +++ b/layout/xul/nsXULPopupManager.cpp @@ -444,7 +444,7 @@ nsXULPopupManager::AdjustPopupsOnWindowChange(nsPIDOMWindowOuter* aWindow) } for (int32_t l = list.Length() - 1; l >= 0; l--) { - list[l]->SetPopupPosition(nullptr, true, false); + list[l]->SetPopupPosition(nullptr, true, false, true); } } @@ -500,7 +500,7 @@ nsXULPopupManager::PopupMoved(nsIFrame* aFrame, nsIntPoint aPnt) // the specified screen coordinates. if (menuPopupFrame->IsAnchored() && menuPopupFrame->PopupLevel() == ePopupLevelParent) { - menuPopupFrame->SetPopupPosition(nullptr, true, false); + menuPopupFrame->SetPopupPosition(nullptr, true, false, true); } else { CSSPoint cssPos = LayoutDeviceIntPoint::FromUnknownPoint(aPnt) @@ -1038,6 +1038,24 @@ nsXULPopupManager::HidePopup(nsIContent* aPopup, } else if (foundPanel) { popupToHide = aPopup; + } else { + // When the popup is in the popuppositioning state, it will not be in the + // mPopups list. We need another way to find it and make sure it does not + // continue the popup showing process. + popupFrame = do_QueryFrame(aPopup->GetPrimaryFrame()); + if (popupFrame) { + if (popupFrame->PopupState() == ePopupPositioning) { + // Do basically the same thing we would have done if we had found the + // popup in the mPopups list. + deselectMenu = aDeselectMenu; + popupToHide = aPopup; + type = popupFrame->PopupType(); + } else { + // The popup is not positioning. If we were supposed to have handled + // closing it, it should have been in mPopups or mNoHidePanels + popupFrame = nullptr; + } + } } if (popupFrame) { @@ -1494,7 +1512,19 @@ nsXULPopupManager::FirePopupShowingEvent(nsIContent* aPopup, popupFrame->ClearTriggerContent(); } else { - ShowPopupCallback(aPopup, popupFrame, aIsContextMenu, aSelectFirstItem); + // Now check if we need to fire the popuppositioned event. If not, call + // ShowPopupCallback directly. + + // The popuppositioned event only fires on arrow panels for now. + if (popup->AttrValueIs(kNameSpaceID_None, nsGkAtoms::type, + nsGkAtoms::arrow, eCaseMatters)) { + popupFrame->ShowWithPositionedEvent(); + presShell->FrameNeedsReflow(popupFrame, nsIPresShell::eTreeChange, + NS_FRAME_HAS_DIRTY_CHILDREN); + } + else { + ShowPopupCallback(popup, popupFrame, aIsContextMenu, aSelectFirstItem); + } } } } @@ -1724,7 +1754,8 @@ nsXULPopupManager::MayShowPopup(nsMenuPopupFrame* aPopup) // if the popup is not in the open popup chain, then it must have a state that // is either closed, in the process of being shown, or invisible. NS_ASSERTION(IsPopupOpen(aPopup->GetContent()) || state == ePopupClosed || - state == ePopupShowing || state == ePopupInvisible, + state == ePopupShowing || state == ePopupPositioning || + state == ePopupInvisible, "popup not in XULPopupManager open list is open"); // don't show popups unless they are closed or invisible @@ -2705,6 +2736,61 @@ nsXULPopupHidingEvent::Run() return NS_OK; } +bool +nsXULPopupPositionedEvent::DispatchIfNeeded(nsIContent *aPopup, + bool aIsContextMenu, + bool aSelectFirstItem) +{ + // The popuppositioned event only fires on arrow panels for now. + if (aPopup->AttrValueIs(kNameSpaceID_None, nsGkAtoms::type, + nsGkAtoms::arrow, eCaseMatters)) { + nsCOMPtr event = + new nsXULPopupPositionedEvent(aPopup, aIsContextMenu, aSelectFirstItem); + NS_DispatchToCurrentThread(event); + + return true; + } + + return false; +} + +NS_IMETHODIMP +nsXULPopupPositionedEvent::Run() +{ + nsXULPopupManager* pm = nsXULPopupManager::GetInstance(); + if (pm) { + nsMenuPopupFrame* popupFrame = do_QueryFrame(mPopup->GetPrimaryFrame()); + if (popupFrame) { + // At this point, hidePopup may have been called but it currently has no + // way to stop this event. However, if hidePopup was called, the popup + // will now be in the hiding or closed state. If we are in the shown or + // positioning state instead, we can assume that we are still clear to + // open/move the popup + nsPopupState state = popupFrame->PopupState(); + if (state != ePopupPositioning && state != ePopupShown) { + return NS_OK; + } + nsEventStatus status = nsEventStatus_eIgnore; + WidgetMouseEvent event(true, eXULPopupPositioned, nullptr, + WidgetMouseEvent::eReal); + EventDispatcher::Dispatch(mPopup, popupFrame->PresContext(), + &event, nullptr, &status); + + // Get the popup frame and make sure it is still in the positioning + // state. If it isn't, someone may have tried to reshow or hide it + // during the popuppositioned event. + // Alternately, this event may have been fired in reponse to moving the + // popup rather than opening it. In that case, we are done. + nsMenuPopupFrame* popupFrame = do_QueryFrame(mPopup->GetPrimaryFrame()); + if (popupFrame && popupFrame->PopupState() == ePopupPositioning) { + pm->ShowPopupCallback(mPopup, popupFrame, mIsContextMenu, mSelectFirstItem); + } + } + } + + return NS_OK; +} + NS_IMETHODIMP nsXULMenuCommandEvent::Run() { diff --git a/layout/xul/nsXULPopupManager.h b/layout/xul/nsXULPopupManager.h index a6cf53a4bcc8..14d5bfef6025 100644 --- a/layout/xul/nsXULPopupManager.h +++ b/layout/xul/nsXULPopupManager.h @@ -237,6 +237,34 @@ private: bool mIsRollup; }; +// this class is used for dispatching popuppositioned events asynchronously. +class nsXULPopupPositionedEvent : public mozilla::Runnable +{ +public: + explicit nsXULPopupPositionedEvent(nsIContent *aPopup, + bool aIsContextMenu, + bool aSelectFirstItem) + : mPopup(aPopup) + , mIsContextMenu(aIsContextMenu) + , mSelectFirstItem(aSelectFirstItem) + { + NS_ASSERTION(aPopup, "null popup supplied to nsXULPopupShowingEvent constructor"); + } + + NS_IMETHOD Run() override; + + // Asynchronously dispatch a popuppositioned event at aPopup if this is a + // panel that should receieve such events. Return true if the event was sent. + static bool DispatchIfNeeded(nsIContent *aPopup, + bool aIsContextMenu, + bool aSelectFirstItem); + +private: + nsCOMPtr mPopup; + bool mIsContextMenu; + bool mSelectFirstItem; +}; + // this class is used for dispatching menu command events asynchronously. class nsXULMenuCommandEvent : public mozilla::Runnable { @@ -287,6 +315,7 @@ class nsXULPopupManager final : public nsIDOMEventListener, public: friend class nsXULPopupShowingEvent; friend class nsXULPopupHidingEvent; + friend class nsXULPopupPositionedEvent; friend class nsXULMenuCommandEvent; friend class TransitionEnder; diff --git a/toolkit/content/tests/widgets/test_popupanchor.xul b/toolkit/content/tests/widgets/test_popupanchor.xul index 34d163716e83..814d9272f389 100644 --- a/toolkit/content/tests/widgets/test_popupanchor.xul +++ b/toolkit/content/tests/widgets/test_popupanchor.xul @@ -88,6 +88,15 @@ function openPopup(position, callback) { _openPopup(position, callback); } +function waitForPopupPositioned(actionFn, callback) +{ + panel.addEventListener("popuppositioned", function listener() { + panel.removeEventListener("popuppositioned", listener, false); + callback(); + }, false); + actionFn(); +} + 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 @@ -200,13 +209,21 @@ var tests = [ // and the anchor right - it can't fit with the panel on the left/arrow // on the right, so it must flip (arrow on the left, panel on the right) var offset = Math.floor(-anchorRight / 2); - panel.moveToAnchor(anchor, "after_end", offset, 0); - isArrowPositionedOn("left", offset); // should have flipped and have the offset. - // resize back to original and move to a zero offset - it should flip back. - panel.sizeTo(anchorRight - 10, 100); - panel.moveToAnchor(anchor, "after_end", 0, 0); - isArrowPositionedOn("right"); // should have flipped back and no offset - next(); + + waitForPopupPositioned( + () => panel.moveToAnchor(anchor, "after_end", offset, 0), + () => { + isArrowPositionedOn("left", offset); // should have flipped and have the offset. + // resize back to original and move to a zero offset - it should flip back. + + panel.sizeTo(anchorRight - 10, 100); + waitForPopupPositioned( + () => panel.moveToAnchor(anchor, "after_end", 0, 0), + () => { + isArrowPositionedOn("right"); // should have flipped back and no offset + next(); + }); + }); }); }], @@ -219,12 +236,20 @@ var tests = [ openPopup("start_after", function() { isArrowPositionedOn("bottom"); var offset = Math.floor(-anchorBottom / 2); - panel.moveToAnchor(anchor, "start_after", 0, offset); - isArrowPositionedOn("top", offset); - panel.sizeTo(100, anchorBottom - 10); - panel.moveToAnchor(anchor, "start_after", 0, 0); - isArrowPositionedOn("bottom"); - next(); + + waitForPopupPositioned( + () => panel.moveToAnchor(anchor, "start_after", 0, offset), + () => { + isArrowPositionedOn("top", offset); + panel.sizeTo(100, anchorBottom - 10); + + waitForPopupPositioned( + () => panel.moveToAnchor(anchor, "start_after", 0, 0), + () => { + isArrowPositionedOn("bottom"); + next(); + }); + }); }); }], @@ -379,10 +404,8 @@ SimpleTest.waitForExplicitFinish(); addEventListener("load", function() { // anchor is set by the test runner above panel = document.getElementById("testPanel"); + arrow = SpecialPowers.wrap(document).getAnonymousElementByAttribute(panel, "anonid", "arrow"); - // Cancel the arrow panel slide-in transition (bug 767133) so the size and - // position are "stable" enough to test without jumping through hoops... - arrow.style.transition = "none"; runTests(); }); diff --git a/toolkit/content/widgets/popup.xml b/toolkit/content/widgets/popup.xml index 590a39c79e54..cd8b00b0c63e 100644 --- a/toolkit/content/widgets/popup.xml +++ b/toolkit/content/widgets/popup.xml @@ -374,19 +374,9 @@ - - - - - - - @@ -399,8 +389,6 @@ @@ -411,7 +399,6 @@ var anchor = this.anchorNode; if (!anchor) { - arrow.hidden = true; return; } @@ -423,8 +410,6 @@ this.setAttribute("arrowposition", position); - // 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 = "horizontal"; arrowbox.orient = "vertical"; @@ -466,8 +451,6 @@ this.setAttribute("side", "top"); } } - - arrow.hidden = false; ]]> @@ -475,7 +458,13 @@ + + this.adjustArrowPosition(); + diff --git a/toolkit/modules/PopupNotifications.jsm b/toolkit/modules/PopupNotifications.jsm index af4fb654cdea..20e3d1de8892 100644 --- a/toolkit/modules/PopupNotifications.jsm +++ b/toolkit/modules/PopupNotifications.jsm @@ -848,13 +848,35 @@ PopupNotifications.prototype = { // the next reason will be that the user clicked elsewhere. this.nextDismissReason = TELEMETRY_STAT_DISMISSAL_CLICK_ELSEWHERE; + let target = this.panel; + if (target.parentNode) { + // NOTIFICATION_EVENT_SHOWN should be fired for the panel before + // anyone listening for popupshown on the panel gets run. Otherwise, + // the panel will not be initialized when the popupshown event + // listeners run. + // By targeting the panel's parent and using a capturing listener, we + // can have our listener called before others waiting for the panel to + // be shown (which probably expect the panel to be fully initialized) + target = target.parentNode; + } + if (this._popupshownListener) { + target.removeEventListener("popupshown", this._popupshownListener, true); + } + this._popupshownListener = function (e) { + target.removeEventListener("popupshown", this._popupshownListener, true); + this._popupshownListener = null; + + notificationsToShow.forEach(function (n) { + this._fireCallback(n, NOTIFICATION_EVENT_SHOWN); + }, this); + // This notification is used by tests to know when all the processing + // required to display the panel has happened. + this.panel.dispatchEvent(new this.window.CustomEvent("Shown")); + }; + this._popupshownListener = this._popupshownListener.bind(this); + target.addEventListener("popupshown", this._popupshownListener, true); + this.panel.openPopup(anchorElement, "bottomcenter topleft"); - notificationsToShow.forEach(function (n) { - this._fireCallback(n, NOTIFICATION_EVENT_SHOWN); - }, this); - // This notification is used by tests to know when all the processing - // required to display the panel has happened. - this.panel.dispatchEvent(new this.window.CustomEvent("Shown")); }); }, diff --git a/widget/EventMessageList.h b/widget/EventMessageList.h index 858efc59ddde..cbda1fb0f605 100644 --- a/widget/EventMessageList.h +++ b/widget/EventMessageList.h @@ -142,6 +142,7 @@ NS_EVENT_MESSAGE_FIRST_LAST(eDragDropEvent, eDragEnter, eDragLeave) // XUL specific events NS_EVENT_MESSAGE(eXULPopupShowing) NS_EVENT_MESSAGE(eXULPopupShown) +NS_EVENT_MESSAGE(eXULPopupPositioned) NS_EVENT_MESSAGE(eXULPopupHiding) NS_EVENT_MESSAGE(eXULPopupHidden) NS_EVENT_MESSAGE(eXULBroadcast)