diff options
Diffstat (limited to 'layout/forms/nsSelectsAreaFrame.cpp')
-rw-r--r-- | layout/forms/nsSelectsAreaFrame.cpp | 191 |
1 files changed, 191 insertions, 0 deletions
diff --git a/layout/forms/nsSelectsAreaFrame.cpp b/layout/forms/nsSelectsAreaFrame.cpp new file mode 100644 index 0000000000..64fcdbd029 --- /dev/null +++ b/layout/forms/nsSelectsAreaFrame.cpp @@ -0,0 +1,191 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 "mozilla/PresShell.h" +#include "nsIContent.h" +#include "nsListControlFrame.h" +#include "nsDisplayList.h" +#include "WritingModes.h" + +using namespace mozilla; + +nsContainerFrame* NS_NewSelectsAreaFrame(PresShell* aShell, + ComputedStyle* aStyle, + nsFrameState aFlags) { + nsSelectsAreaFrame* it = + new (aShell) nsSelectsAreaFrame(aStyle, aShell->GetPresContext()); + + // 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) + +static nsListControlFrame* GetEnclosingListFrame(nsIFrame* aSelectsAreaFrame) { + nsIFrame* frame = aSelectsAreaFrame->GetParent(); + while (frame) { + if (frame->IsListControlFrame()) + return static_cast<nsListControlFrame*>(frame); + frame = frame->GetParent(); + } + return nullptr; +} + +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"); + + 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) { + mBSizeOfARow = newBSizeOfARow; + list->SetSuppressScrollbarUpdate(true); + } + } +} + +namespace mozilla { +/** + * 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; + } + void Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) override { + GetChildren()->Paint(aBuilder, aCtx, + mFrame->PresContext()->AppUnitsPerDevPixel()); + } + 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 nsDisplayItemWrapper { + public: + nsOptionEventGrabberWrapper() = default; + virtual nsDisplayItem* WrapList(nsDisplayListBuilder* aBuilder, + nsIFrame* aFrame, + nsDisplayList* aList) override { + return MakeDisplayItem<nsDisplayOptionEventGrabber>(aBuilder, aFrame, + aList); + } + virtual nsDisplayItem* WrapItem(nsDisplayListBuilder* aBuilder, + nsDisplayItem* aItem) override { + return MakeDisplayItem<nsDisplayOptionEventGrabber>(aBuilder, + aItem->Frame(), aItem); + } +}; + +class nsDisplayListFocus : public nsPaintedDisplayItem { + public: + nsDisplayListFocus(nsDisplayListBuilder* aBuilder, nsSelectsAreaFrame* aFrame) + : nsPaintedDisplayItem(aBuilder, aFrame) { + MOZ_COUNT_CTOR(nsDisplayListFocus); + } + MOZ_COUNTED_DTOR_OVERRIDE(nsDisplayListFocus) + + 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->InkOverflowRectRelativeToSelf() + + listFrame->GetOffsetToCrossDoc(Frame()) + ToReferenceFrame(); + } + 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(), + listFrame->GetOffsetToCrossDoc(Frame()) + ToReferenceFrame()); + } + NS_DISPLAY_DECL_NAME("ListFocus", TYPE_LIST_FOCUS) +}; + +} // namespace mozilla + +void nsSelectsAreaFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder, + const nsDisplayListSet& aLists) { + if (!aBuilder->IsForEventDelivery()) { + BuildDisplayListInternal(aBuilder, aLists); + return; + } + + nsDisplayListCollection set(aBuilder); + 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<nsDisplayListFocus>(aBuilder, this); + } +} |