Bug 1429691 constrain both above and below dropdown frame, changes to AbsolutelyPositionDropdown draft
authorEmma Malysz <emalysz@mozilla.com>
Fri, 03 Aug 2018 14:22:13 -0700
changeset 828346 683a5b6b1c90a1b4ea089f18c309c3a950158ec7
parent 826308 3d107866825433ab1168c7d23e94eac727e0f673
push id118674
push userbmo:emalysz@mozilla.com
push dateFri, 10 Aug 2018 19:24:13 +0000
bugs1429691
milestone63.0a1
Bug 1429691 constrain both above and below dropdown frame, changes to AbsolutelyPositionDropdown MozReview-Commit-ID: 9GYAwAdLep9
layout/forms/nsComboboxControlFrame.cpp
layout/forms/nsListControlFrame.cpp
layout/generic/ViewportFrame.cpp
layout/generic/nsCanvasFrame.cpp
layout/generic/nsCanvasFrame.h
--- a/layout/forms/nsComboboxControlFrame.cpp
+++ b/layout/forms/nsComboboxControlFrame.cpp
@@ -19,16 +19,17 @@
 #include "nsIFormControl.h"
 #include "nsNameSpaceManager.h"
 #include "nsIListControlFrame.h"
 #include "nsPIDOMWindow.h"
 #include "nsIPresShell.h"
 #include "mozilla/PresState.h"
 #include "nsView.h"
 #include "nsViewManager.h"
+#include "nsCanvasFrame.h"
 #include "nsIContentInlines.h"
 #include "nsIDOMEventListener.h"
 #include "nsISelectControlFrame.h"
 #include "nsContentUtils.h"
 #include "mozilla/dom/Event.h"
 #include "mozilla/dom/HTMLSelectElement.h"
 #include "nsIDocument.h"
 #include "nsIScrollableFrame.h"
@@ -303,16 +304,31 @@ nsComboboxControlFrame::SetFocus(bool aO
   InvalidateFrame();
 }
 
 void
 nsComboboxControlFrame::ShowPopup(bool aShowPopup)
 {
   // TODO(kuoe0) Remove this function when content-select is enabled.
 
+  if (nsLayoutUtils::IsContentSelectEnabled()) {
+    nsCanvasFrame* canvasFrame = do_QueryFrame(mDropdownFrame->GetParent());
+    if (aShowPopup){
+      this->mDroppedDown = true;
+      canvasFrame->SetDropdownFrame(mDropdownFrame);
+    } else {
+      PresShell()->FrameNeedsReflow(mDisplayFrame,
+                                                 nsIPresShell::eStyleChange,
+                                                 NS_FRAME_IS_DIRTY);
+      canvasFrame->SetDropdownFrame(nullptr);
+      mDroppedDown = false;
+    }
+    return;
+  }
+
   nsView* view = mDropdownFrame->GetView();
   nsViewManager* viewManager = view->GetViewManager();
 
   if (aShowPopup) {
     nsRect rect = mDropdownFrame->GetRect();
     rect.x = rect.y = 0;
     viewManager->ResizeView(view, rect);
     viewManager->SetViewVisibility(view, nsViewVisibility_kShow);
@@ -338,16 +354,17 @@ nsComboboxControlFrame::ShowList(bool aS
 {
 
   // TODO(kuoe0) Remove this function when content-select is enabled.
   //
   // This function is used to handle the widget/view stuff, so we just return
   // when content-select is enabled. And the following callee, ShowPopup(), will
   // also be ignored, it is only used to show and hide the widget.
   if (nsLayoutUtils::IsContentSelectEnabled()) {
+    ShowPopup(aShowList);
     return true;
   }
 
   nsView* view = mDropdownFrame->GetView();
   if (aShowList) {
     NS_ASSERTION(!view->HasWidget(),
                  "We shouldn't have a widget before we need to display the popup");
 
@@ -584,17 +601,20 @@ public:
 };
 
 void
 nsComboboxControlFrame::GetAvailableDropdownSpace(WritingMode aWM,
                                                   nscoord* aBefore,
                                                   nscoord* aAfter,
                                                   LogicalPoint* aTranslation)
 {
-  MOZ_ASSERT(!XRE_IsContentProcess());
+  if (!nsLayoutUtils::IsContentSelectEnabled()) {
+    // TODO(kuoe0) remove this assertion after content-select is enabled
+    MOZ_ASSERT(!XRE_IsContentProcess());
+  }
   // Note: At first glance, it appears that you could simply get the
   // absolute bounding box for the dropdown list by first getting its
   // view, then getting the view's nsIWidget, then asking the nsIWidget
   // for its AbsoluteBounds.
   // The problem with this approach, is that the dropdown list's bcoord
   // location can change based on whether the dropdown is placed after
   // or before the display frame.  The approach taken here is to get the
   // absolute position of the display frame and use its location to
@@ -632,17 +652,22 @@ nsComboboxControlFrame::GetAvailableDrop
     if (mLastDropDownAfterScreenBCoord < minBCoord) {
       // Don't allow the drop-down to be placed before the content area.
       return;
     }
   } else {
     minBCoord = logicalScreen.BStart(aWM);
   }
 
-  nscoord after = logicalScreen.BEnd(aWM) - mLastDropDownAfterScreenBCoord;
+  nscoord maxBCoord = LogicalRect(aWM, root->GetScreenRectInAppUnits(), containerSize).BEnd(aWM);
+  if (maxBCoord > logicalScreen.BEnd(aWM)) {
+    maxBCoord = logicalScreen.BEnd(aWM);
+  }
+
+  nscoord after = maxBCoord - mLastDropDownAfterScreenBCoord;
   nscoord before = mLastDropDownBeforeScreenBCoord - minBCoord;
 
   // If the difference between the space before and after is less
   // than a row-block-size, then we favor the space after.
   if (before >= after) {
     nsListControlFrame* lcf = static_cast<nsListControlFrame*>(mDropdownFrame);
     nscoord rowBSize = lcf->GetBSizeOfARow();
     if (before < after + rowBSize) {
@@ -652,17 +677,18 @@ nsComboboxControlFrame::GetAvailableDrop
 
   *aAfter = after;
   *aBefore = before;
 }
 
 nsComboboxControlFrame::DropDownPositionState
 nsComboboxControlFrame::AbsolutelyPositionDropDown()
 {
-  if (XRE_IsContentProcess()) {
+  if (!nsLayoutUtils::IsContentSelectEnabled() && XRE_IsContentProcess()) {
+    // TODO(kuoe0) remove this after content-select is enabled
     return eDropDownPositionSuppressed;
   }
 
   WritingMode wm = GetWritingMode();
   LogicalPoint translation(wm);
   nscoord before, after;
   mLastDropDownAfterScreenBCoord = nscoord_MIN;
   GetAvailableDropdownSpace(wm, &before, &after, &translation);
@@ -697,22 +723,29 @@ nsComboboxControlFrame::AbsolutelyPositi
   }
 
   // Position the drop-down after if there is room, otherwise place it before
   // if there is room.  If there is no room for it on either side then place
   // it after (to avoid overlapping UI like the URL bar).
   bool b = dropdownSize.BSize(wm)<= after || dropdownSize.BSize(wm) > before;
   LogicalPoint dropdownPosition(wm, 0, b ? BSize(wm) : -dropdownSize.BSize(wm));
 
+  nsSize containerSize = GetSize();
+  //TODO (emalysz): make sure this works when select is in a fixed position
+  nsRect mainRect(dropdownPosition.GetPhysicalPoint(wm, containerSize), containerSize);
+  nsRect convertedRect = nsLayoutUtils::TransformFrameRectToAncestor(this, mainRect, mDropdownFrame->GetParent());
+
+  nsPoint pos(convertedRect.x, convertedRect.y);
+  LogicalPoint dropdownPositionConverted(wm, pos, containerSize);
+
   // Don't position the view unless the position changed since it might cause
   // a call to NotifyGeometryChange() and an infinite loop here.
-  nsSize containerSize = GetSize();
   const LogicalPoint currentPos =
     mDropdownFrame->GetLogicalPosition(containerSize);
-  const LogicalPoint newPos = dropdownPosition + translation;
+  const LogicalPoint newPos = dropdownPositionConverted + translation;
   if (currentPos != newPos) {
     mDropdownFrame->SetPosition(wm, newPos, containerSize);
     nsContainerFrame::PositionFrameView(mDropdownFrame);
   }
   return eDropDownPositionFinal;
 }
 
 void
@@ -1434,16 +1467,19 @@ nsComboboxControlFrame::DestroyFrom(nsIF
     nsView* view = mDropdownFrame->GetView();
     MOZ_ASSERT(view);
     nsIWidget* widget = view->GetWidget();
     if (widget) {
       widget->CaptureRollupEvents(this, false);
     }
   }
 
+  nsCanvasFrame* canvasFrame = do_QueryFrame(mDropdownFrame->GetParent());
+  canvasFrame->SetDropdownFrame(nullptr);
+
   // Cleanup frames in popup child list
   mPopupFrames.DestroyFramesFrom(aDestructRoot, aPostDestroyData);
   aPostDestroyData.AddAnonymousContent(mDisplayContent.forget());
   aPostDestroyData.AddAnonymousContent(mButtonContent.forget());
   nsBlockFrame::DestroyFrom(aDestructRoot, aPostDestroyData);
 }
 
 const nsFrameList&
--- a/layout/forms/nsListControlFrame.cpp
+++ b/layout/forms/nsListControlFrame.cpp
@@ -177,16 +177,22 @@ nsListControlFrame::BuildDisplayList(nsD
   // XXX why do we need this here? we should never reach this. Maybe
   // because these can have widgets? Hmm
   if (aBuilder->IsBackgroundOnly())
     return;
 
   DO_GLOBAL_REFLOW_COUNT_DSP("nsListControlFrame");
 
   if (IsInDropDownMode()) {
+    if (nsLayoutUtils::IsContentSelectEnabled() &&
+      !mComboboxFrame->IsDroppedDown()) {
+      // Don't build the display list when the list is not dropped down.
+      return;
+    }
+
     NS_ASSERTION(NS_GET_A(mLastDropdownBackstopColor) == 255,
                  "need an opaque backstop color");
     // XXX Because we have an opaque widget and we get called to paint with
     // this frame as the root of a stacking context we need make sure to draw
     // some opaque color over the whole widget. (Bug 511323)
     aLists.BorderBackground()->AppendToBottom(
       MakeDisplayItem<nsDisplaySolidColor>(aBuilder,
         this, nsRect(aBuilder->ToReferenceFrame(this), GetSize()),
@@ -568,17 +574,17 @@ nsListControlFrame::ReflowAsDropdown(nsP
   // we're depending on?
   nsHTMLScrollFrame::DidReflow(aPresContext, &state);
 
   // Now compute the block size we want to have.
   // Note: no need to apply min/max constraints, since we have no such
   // rules applied to the combobox dropdown.
 
   mDropdownCanGrow = false;
-  if (visibleBSize <= 0 || blockSizeOfARow <= 0 || XRE_IsContentProcess()) {
+  if (visibleBSize <= 0 || blockSizeOfARow <= 0) {
     // Looks like we have no options.  Just size us to a single row
     // block size.
     state.SetComputedBSize(blockSizeOfARow);
     mNumDisplayRows = 1;
   } else {
     nsComboboxControlFrame* combobox =
       static_cast<nsComboboxControlFrame*>(mComboboxFrame);
     LogicalPoint translation(wm);
--- a/layout/generic/ViewportFrame.cpp
+++ b/layout/generic/ViewportFrame.cpp
@@ -177,16 +177,21 @@ ViewportFrame::BuildDisplayListForTopLay
         BuildDisplayListForTopLayerFrame(aBuilder, backdropFrame, aList);
       }
       BuildDisplayListForTopLayerFrame(aBuilder, frame, aList);
     }
   }
 
   nsIPresShell* shell = PresShell();
   if (nsCanvasFrame* canvasFrame = shell->GetCanvasFrame()) {
+    // Build display items for the dropped-down menu
+    if (nsIFrame* dropdownFrame = canvasFrame->GetDropdownFrame()) {
+      BuildDisplayListForTopLayerFrame(aBuilder, dropdownFrame, aList);
+      MOZ_ASSERT(dropdownFrame->StyleDisplay()->mTopLayer == NS_STYLE_TOP_LAYER_TOP);
+    }
     if (Element* container = canvasFrame->GetCustomContentContainer()) {
       if (nsIFrame* frame = container->GetPrimaryFrame()) {
         MOZ_ASSERT(frame->StyleDisplay()->mTopLayer != NS_STYLE_TOP_LAYER_NONE,
                    "ua.css should ensure this");
         MOZ_ASSERT(frame->GetStateBits() & NS_FRAME_OUT_OF_FLOW);
         BuildDisplayListForTopLayerFrame(aBuilder, frame, aList);
       }
     }
--- a/layout/generic/nsCanvasFrame.cpp
+++ b/layout/generic/nsCanvasFrame.cpp
@@ -338,16 +338,25 @@ void
 nsCanvasFrame::SetDefaultTooltip(Element* aTooltip)
 {
   MOZ_ASSERT(!aTooltip || aTooltip == mTooltipContent,
              "Default tooltip should be anonymous content tooltip.");
   mTooltipContent = aTooltip;
 }
 
 void
+nsCanvasFrame::SetDropdownFrame(nsIFrame* aDropdownFrame) {
+  if (aDropdownFrame) {
+    MOZ_ASSERT(aDropdownFrame->IsListControlFrame(),
+               "Only nsListControlFrame can be dropped down.");
+  }
+  mDropdownFrame = aDropdownFrame;
+}
+
+void
 nsDisplayCanvasBackgroundColor::Paint(nsDisplayListBuilder* aBuilder,
                                       gfxContext* aCtx)
 {
   nsCanvasFrame* frame = static_cast<nsCanvasFrame*>(mFrame);
   nsPoint offset = ToReferenceFrame();
   nsRect bgClipRect = frame->CanvasArea() + offset;
   if (NS_GET_A(mColor) > 0) {
     DrawTarget* drawTarget = aCtx->GetDrawTarget();
--- a/layout/generic/nsCanvasFrame.h
+++ b/layout/generic/nsCanvasFrame.h
@@ -37,16 +37,17 @@ class nsCanvasFrame final : public nsCon
                             public nsIPopupContainer
 {
 public:
   explicit nsCanvasFrame(ComputedStyle* aStyle)
     : nsContainerFrame(aStyle, kClassID)
     , mDoPaintFocus(false)
     , mAddedScrollPositionListener(false)
     , mPopupSetFrame(nullptr)
+    , mDropdownFrame(nullptr)
   {}
 
   NS_DECL_QUERYFRAME
   NS_DECL_FRAMEARENA_HELPERS(nsCanvasFrame)
 
   nsPopupSetFrame* GetPopupSetFrame() override;
   void SetPopupSetFrame(nsPopupSetFrame* aPopupSet) override;
   Element* GetDefaultTooltip() override;
@@ -116,31 +117,37 @@ public:
 #ifdef DEBUG_FRAME_DUMP
   virtual nsresult GetFrameName(nsAString& aResult) const override;
 #endif
   virtual nsresult GetContentForEvent(mozilla::WidgetEvent* aEvent,
                                       nsIContent** aContent) override;
 
   nsRect CanvasArea() const;
 
+  // The frame should be dropped down
+  nsIFrame* GetDropdownFrame() const { return mDropdownFrame; }
+  void SetDropdownFrame(nsIFrame* aDropDownFrame);
+
+
 protected:
   // Utility function to propagate the WritingMode from our first child to
   // 'this' and all its ancestors.
   void MaybePropagateRootElementWritingMode();
 
   // Data members
   bool                      mDoPaintFocus;
   bool                      mAddedScrollPositionListener;
 
   nsCOMPtr<mozilla::dom::Element> mCustomContentContainer;
 
 private:
   nsPopupSetFrame* mPopupSetFrame;
   nsCOMPtr<mozilla::dom::Element> mPopupgroupContent;
   nsCOMPtr<mozilla::dom::Element> mTooltipContent;
+  nsIFrame* mDropdownFrame;
 };
 
 /**
  * Override nsDisplayBackground methods so that we pass aBGClipRect to
  * PaintBackground, covering the whole overflow area.
  * We can also paint an "extra background color" behind the normal
  * background.
  */