fune/layout/forms/nsSelectsAreaFrame.cpp
Ting-Yu Lin 2e869d8f3d Bug 1341009 - Add nsReflowStatus::IsEmpty() assertions to all nsIFrame::Reflow() methods and some reflow helpers, and remove unneeded Reset(). r=dholbert
nsReflowStatus::IsEmpty() assertions are added after DISPLAY_REFLOW in the
beginning of the Reflow().

A few Reflow() implementations have Reset() calls at the end which are left
in place by this patch (with an explanatory comment added to each). These
ending Reset()s are only needed for cases where a non-splittable frame
passes its own nsReflowStatus to a child's reflow method. Just in case the
child leaves a "not fully complete" value in the nsReflowStatus, the
non-splittable parent frame must clear out the nsReflowStatus before
returning, so that its own parent doesn't then try to split it.

MozReview-Commit-ID: 6Jj3jfMAqj4

--HG--
extra : rebase_source : e1fe6a775ad97e16a6d7cc224634ff962ccb0fbf
2017-09-13 18:00:25 +08:00

208 lines
7.5 KiB
C++

/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "nsSelectsAreaFrame.h"
#include "nsIContent.h"
#include "nsListControlFrame.h"
#include "nsDisplayList.h"
#include "WritingModes.h"
using namespace mozilla;
nsContainerFrame*
NS_NewSelectsAreaFrame(nsIPresShell* aShell, nsStyleContext* aContext, nsFrameState aFlags)
{
nsSelectsAreaFrame* it = new (aShell) nsSelectsAreaFrame(aContext);
// We need NS_BLOCK_FLOAT_MGR to ensure that the options inside the select
// aren't expanded by right floats outside the select.
it->AddStateBits(aFlags | NS_BLOCK_FLOAT_MGR);
return it;
}
NS_IMPL_FRAMEARENA_HELPERS(nsSelectsAreaFrame)
//---------------------------------------------------------
/**
* This wrapper class lets us redirect mouse hits from the child frame of
* an option element to the element's own frame.
* REVIEW: This is what nsSelectsAreaFrame::GetFrameForPoint used to do
*/
class nsDisplayOptionEventGrabber : public nsDisplayWrapList {
public:
nsDisplayOptionEventGrabber(nsDisplayListBuilder* aBuilder,
nsIFrame* aFrame, nsDisplayItem* aItem)
: nsDisplayWrapList(aBuilder, aFrame, aItem) {}
nsDisplayOptionEventGrabber(nsDisplayListBuilder* aBuilder,
nsIFrame* aFrame, nsDisplayList* aList)
: nsDisplayWrapList(aBuilder, aFrame, aList) {}
virtual void HitTest(nsDisplayListBuilder* aBuilder, const nsRect& aRect,
HitTestState* aState, nsTArray<nsIFrame*> *aOutFrames) override;
virtual bool ShouldFlattenAway(nsDisplayListBuilder* aBuilder) override {
return false;
}
NS_DISPLAY_DECL_NAME("OptionEventGrabber", TYPE_OPTION_EVENT_GRABBER)
};
void nsDisplayOptionEventGrabber::HitTest(nsDisplayListBuilder* aBuilder,
const nsRect& aRect, HitTestState* aState, nsTArray<nsIFrame*> *aOutFrames)
{
nsTArray<nsIFrame*> outFrames;
mList.HitTest(aBuilder, aRect, aState, &outFrames);
for (uint32_t i = 0; i < outFrames.Length(); i++) {
nsIFrame* selectedFrame = outFrames.ElementAt(i);
while (selectedFrame &&
!(selectedFrame->GetContent() &&
selectedFrame->GetContent()->IsHTMLElement(nsGkAtoms::option))) {
selectedFrame = selectedFrame->GetParent();
}
if (selectedFrame) {
aOutFrames->AppendElement(selectedFrame);
} else {
// keep the original result, which could be this frame
aOutFrames->AppendElement(outFrames.ElementAt(i));
}
}
}
class nsOptionEventGrabberWrapper : public nsDisplayWrapper
{
public:
nsOptionEventGrabberWrapper() {}
virtual nsDisplayItem* WrapList(nsDisplayListBuilder* aBuilder,
nsIFrame* aFrame, nsDisplayList* aList) {
return new (aBuilder) nsDisplayOptionEventGrabber(aBuilder, aFrame, aList);
}
virtual nsDisplayItem* WrapItem(nsDisplayListBuilder* aBuilder,
nsDisplayItem* aItem) {
return new (aBuilder) nsDisplayOptionEventGrabber(aBuilder, aItem->Frame(), aItem);
}
};
static nsListControlFrame* GetEnclosingListFrame(nsIFrame* aSelectsAreaFrame)
{
nsIFrame* frame = aSelectsAreaFrame->GetParent();
while (frame) {
if (frame->IsListControlFrame())
return static_cast<nsListControlFrame*>(frame);
frame = frame->GetParent();
}
return nullptr;
}
class nsDisplayListFocus : public nsDisplayItem {
public:
nsDisplayListFocus(nsDisplayListBuilder* aBuilder,
nsSelectsAreaFrame* aFrame) :
nsDisplayItem(aBuilder, aFrame) {
MOZ_COUNT_CTOR(nsDisplayListFocus);
}
#ifdef NS_BUILD_REFCNT_LOGGING
virtual ~nsDisplayListFocus() {
MOZ_COUNT_DTOR(nsDisplayListFocus);
}
#endif
virtual nsRect GetBounds(nsDisplayListBuilder* aBuilder,
bool* aSnap) const override
{
*aSnap = false;
// override bounds because the list item focus ring may extend outside
// the nsSelectsAreaFrame
nsListControlFrame* listFrame = GetEnclosingListFrame(Frame());
return listFrame->GetVisualOverflowRectRelativeToSelf() +
listFrame->GetOffsetToCrossDoc(ReferenceFrame());
}
virtual void Paint(nsDisplayListBuilder* aBuilder,
gfxContext* aCtx) override {
nsListControlFrame* listFrame = GetEnclosingListFrame(Frame());
// listFrame must be non-null or we wouldn't get called.
listFrame->PaintFocus(aCtx->GetDrawTarget(),
aBuilder->ToReferenceFrame(listFrame));
}
NS_DISPLAY_DECL_NAME("ListFocus", TYPE_LIST_FOCUS)
};
void
nsSelectsAreaFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
const nsDisplayListSet& aLists)
{
if (!aBuilder->IsForEventDelivery()) {
BuildDisplayListInternal(aBuilder, aLists);
return;
}
nsDisplayListCollection set;
BuildDisplayListInternal(aBuilder, set);
nsOptionEventGrabberWrapper wrapper;
wrapper.WrapLists(aBuilder, this, set, aLists);
}
void
nsSelectsAreaFrame::BuildDisplayListInternal(nsDisplayListBuilder* aBuilder,
const nsDisplayListSet& aLists)
{
nsBlockFrame::BuildDisplayList(aBuilder, aLists);
nsListControlFrame* listFrame = GetEnclosingListFrame(this);
if (listFrame && listFrame->IsFocused()) {
// we can't just associate the display item with the list frame,
// because then the list's scrollframe won't clip it (the scrollframe
// only clips contained descendants).
aLists.Outlines()->AppendNewToTop(new (aBuilder)
nsDisplayListFocus(aBuilder, this));
}
}
void
nsSelectsAreaFrame::Reflow(nsPresContext* aPresContext,
ReflowOutput& aDesiredSize,
const ReflowInput& aReflowInput,
nsReflowStatus& aStatus)
{
MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!");
nsListControlFrame* list = GetEnclosingListFrame(this);
NS_ASSERTION(list,
"Must have an nsListControlFrame! Frame constructor is "
"broken");
bool isInDropdownMode = list->IsInDropDownMode();
// See similar logic in nsListControlFrame::Reflow and
// nsListControlFrame::ReflowAsDropdown. We need to match it here.
WritingMode wm = aReflowInput.GetWritingMode();
nscoord oldBSize;
if (isInDropdownMode) {
// Store the block size now in case it changes during
// nsBlockFrame::Reflow for some odd reason.
if (!(GetStateBits() & NS_FRAME_FIRST_REFLOW)) {
oldBSize = BSize(wm);
} else {
oldBSize = NS_UNCONSTRAINEDSIZE;
}
}
nsBlockFrame::Reflow(aPresContext, aDesiredSize, aReflowInput, aStatus);
// Check whether we need to suppress scrollbar updates. We want to do
// that if we're in a possible first pass and our block size of a row
// has changed.
if (list->MightNeedSecondPass()) {
nscoord newBSizeOfARow = list->CalcBSizeOfARow();
// We'll need a second pass if our block size of a row changed. For
// comboboxes, we'll also need it if our block size changed. If
// we're going to do a second pass, suppress scrollbar updates for
// this pass.
if (newBSizeOfARow != mBSizeOfARow ||
(isInDropdownMode && (oldBSize != aDesiredSize.BSize(wm) ||
oldBSize != BSize(wm)))) {
mBSizeOfARow = newBSizeOfARow;
list->SetSuppressScrollbarUpdate(true);
}
}
}