123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205 |
- /* -*- 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->GetType() == nsGkAtoms::listControlFrame)
- 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) 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,
- nsRenderingContext* 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(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(new (aBuilder)
- nsDisplayListFocus(aBuilder, this));
- }
- }
- void
- nsSelectsAreaFrame::Reflow(nsPresContext* aPresContext,
- ReflowOutput& aDesiredSize,
- const ReflowInput& aReflowInput,
- nsReflowStatus& aStatus)
- {
- 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);
- }
- }
- }
|