Bug 1429691 constrain both above and below dropdown frame, changes to AbsolutelyPositionDropdown
MozReview-Commit-ID: 9GYAwAdLep9
--- 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.
*/