nsSelectsAreaFrame.cpp 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205
  1. /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
  2. /* This Source Code Form is subject to the terms of the Mozilla Public
  3. * License, v. 2.0. If a copy of the MPL was not distributed with this
  4. * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
  5. #include "nsSelectsAreaFrame.h"
  6. #include "nsIContent.h"
  7. #include "nsListControlFrame.h"
  8. #include "nsDisplayList.h"
  9. #include "WritingModes.h"
  10. using namespace mozilla;
  11. nsContainerFrame*
  12. NS_NewSelectsAreaFrame(nsIPresShell* aShell, nsStyleContext* aContext, nsFrameState aFlags)
  13. {
  14. nsSelectsAreaFrame* it = new (aShell) nsSelectsAreaFrame(aContext);
  15. // We need NS_BLOCK_FLOAT_MGR to ensure that the options inside the select
  16. // aren't expanded by right floats outside the select.
  17. it->AddStateBits(aFlags | NS_BLOCK_FLOAT_MGR);
  18. return it;
  19. }
  20. NS_IMPL_FRAMEARENA_HELPERS(nsSelectsAreaFrame)
  21. //---------------------------------------------------------
  22. /**
  23. * This wrapper class lets us redirect mouse hits from the child frame of
  24. * an option element to the element's own frame.
  25. * REVIEW: This is what nsSelectsAreaFrame::GetFrameForPoint used to do
  26. */
  27. class nsDisplayOptionEventGrabber : public nsDisplayWrapList {
  28. public:
  29. nsDisplayOptionEventGrabber(nsDisplayListBuilder* aBuilder,
  30. nsIFrame* aFrame, nsDisplayItem* aItem)
  31. : nsDisplayWrapList(aBuilder, aFrame, aItem) {}
  32. nsDisplayOptionEventGrabber(nsDisplayListBuilder* aBuilder,
  33. nsIFrame* aFrame, nsDisplayList* aList)
  34. : nsDisplayWrapList(aBuilder, aFrame, aList) {}
  35. virtual void HitTest(nsDisplayListBuilder* aBuilder, const nsRect& aRect,
  36. HitTestState* aState, nsTArray<nsIFrame*> *aOutFrames) override;
  37. virtual bool ShouldFlattenAway(nsDisplayListBuilder* aBuilder) override {
  38. return false;
  39. }
  40. NS_DISPLAY_DECL_NAME("OptionEventGrabber", TYPE_OPTION_EVENT_GRABBER)
  41. };
  42. void nsDisplayOptionEventGrabber::HitTest(nsDisplayListBuilder* aBuilder,
  43. const nsRect& aRect, HitTestState* aState, nsTArray<nsIFrame*> *aOutFrames)
  44. {
  45. nsTArray<nsIFrame*> outFrames;
  46. mList.HitTest(aBuilder, aRect, aState, &outFrames);
  47. for (uint32_t i = 0; i < outFrames.Length(); i++) {
  48. nsIFrame* selectedFrame = outFrames.ElementAt(i);
  49. while (selectedFrame &&
  50. !(selectedFrame->GetContent() &&
  51. selectedFrame->GetContent()->IsHTMLElement(nsGkAtoms::option))) {
  52. selectedFrame = selectedFrame->GetParent();
  53. }
  54. if (selectedFrame) {
  55. aOutFrames->AppendElement(selectedFrame);
  56. } else {
  57. // keep the original result, which could be this frame
  58. aOutFrames->AppendElement(outFrames.ElementAt(i));
  59. }
  60. }
  61. }
  62. class nsOptionEventGrabberWrapper : public nsDisplayWrapper
  63. {
  64. public:
  65. nsOptionEventGrabberWrapper() {}
  66. virtual nsDisplayItem* WrapList(nsDisplayListBuilder* aBuilder,
  67. nsIFrame* aFrame, nsDisplayList* aList) {
  68. return new (aBuilder) nsDisplayOptionEventGrabber(aBuilder, aFrame, aList);
  69. }
  70. virtual nsDisplayItem* WrapItem(nsDisplayListBuilder* aBuilder,
  71. nsDisplayItem* aItem) {
  72. return new (aBuilder) nsDisplayOptionEventGrabber(aBuilder, aItem->Frame(), aItem);
  73. }
  74. };
  75. static nsListControlFrame* GetEnclosingListFrame(nsIFrame* aSelectsAreaFrame)
  76. {
  77. nsIFrame* frame = aSelectsAreaFrame->GetParent();
  78. while (frame) {
  79. if (frame->GetType() == nsGkAtoms::listControlFrame)
  80. return static_cast<nsListControlFrame*>(frame);
  81. frame = frame->GetParent();
  82. }
  83. return nullptr;
  84. }
  85. class nsDisplayListFocus : public nsDisplayItem {
  86. public:
  87. nsDisplayListFocus(nsDisplayListBuilder* aBuilder,
  88. nsSelectsAreaFrame* aFrame) :
  89. nsDisplayItem(aBuilder, aFrame) {
  90. MOZ_COUNT_CTOR(nsDisplayListFocus);
  91. }
  92. #ifdef NS_BUILD_REFCNT_LOGGING
  93. virtual ~nsDisplayListFocus() {
  94. MOZ_COUNT_DTOR(nsDisplayListFocus);
  95. }
  96. #endif
  97. virtual nsRect GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap) override {
  98. *aSnap = false;
  99. // override bounds because the list item focus ring may extend outside
  100. // the nsSelectsAreaFrame
  101. nsListControlFrame* listFrame = GetEnclosingListFrame(Frame());
  102. return listFrame->GetVisualOverflowRectRelativeToSelf() +
  103. listFrame->GetOffsetToCrossDoc(ReferenceFrame());
  104. }
  105. virtual void Paint(nsDisplayListBuilder* aBuilder,
  106. nsRenderingContext* aCtx) override {
  107. nsListControlFrame* listFrame = GetEnclosingListFrame(Frame());
  108. // listFrame must be non-null or we wouldn't get called.
  109. listFrame->PaintFocus(aCtx->GetDrawTarget(),
  110. aBuilder->ToReferenceFrame(listFrame));
  111. }
  112. NS_DISPLAY_DECL_NAME("ListFocus", TYPE_LIST_FOCUS)
  113. };
  114. void
  115. nsSelectsAreaFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
  116. const nsDisplayListSet& aLists)
  117. {
  118. if (!aBuilder->IsForEventDelivery()) {
  119. BuildDisplayListInternal(aBuilder, aLists);
  120. return;
  121. }
  122. nsDisplayListCollection set(aBuilder);
  123. BuildDisplayListInternal(aBuilder, set);
  124. nsOptionEventGrabberWrapper wrapper;
  125. wrapper.WrapLists(aBuilder, this, set, aLists);
  126. }
  127. void
  128. nsSelectsAreaFrame::BuildDisplayListInternal(nsDisplayListBuilder* aBuilder,
  129. const nsDisplayListSet& aLists)
  130. {
  131. nsBlockFrame::BuildDisplayList(aBuilder, aLists);
  132. nsListControlFrame* listFrame = GetEnclosingListFrame(this);
  133. if (listFrame && listFrame->IsFocused()) {
  134. // we can't just associate the display item with the list frame,
  135. // because then the list's scrollframe won't clip it (the scrollframe
  136. // only clips contained descendants).
  137. aLists.Outlines()->AppendNewToTop(new (aBuilder)
  138. nsDisplayListFocus(aBuilder, this));
  139. }
  140. }
  141. void
  142. nsSelectsAreaFrame::Reflow(nsPresContext* aPresContext,
  143. ReflowOutput& aDesiredSize,
  144. const ReflowInput& aReflowInput,
  145. nsReflowStatus& aStatus)
  146. {
  147. nsListControlFrame* list = GetEnclosingListFrame(this);
  148. NS_ASSERTION(list,
  149. "Must have an nsListControlFrame! Frame constructor is "
  150. "broken");
  151. bool isInDropdownMode = list->IsInDropDownMode();
  152. // See similar logic in nsListControlFrame::Reflow and
  153. // nsListControlFrame::ReflowAsDropdown. We need to match it here.
  154. WritingMode wm = aReflowInput.GetWritingMode();
  155. nscoord oldBSize;
  156. if (isInDropdownMode) {
  157. // Store the block size now in case it changes during
  158. // nsBlockFrame::Reflow for some odd reason.
  159. if (!(GetStateBits() & NS_FRAME_FIRST_REFLOW)) {
  160. oldBSize = BSize(wm);
  161. } else {
  162. oldBSize = NS_UNCONSTRAINEDSIZE;
  163. }
  164. }
  165. nsBlockFrame::Reflow(aPresContext, aDesiredSize, aReflowInput, aStatus);
  166. // Check whether we need to suppress scrollbar updates. We want to do
  167. // that if we're in a possible first pass and our block size of a row
  168. // has changed.
  169. if (list->MightNeedSecondPass()) {
  170. nscoord newBSizeOfARow = list->CalcBSizeOfARow();
  171. // We'll need a second pass if our block size of a row changed. For
  172. // comboboxes, we'll also need it if our block size changed. If
  173. // we're going to do a second pass, suppress scrollbar updates for
  174. // this pass.
  175. if (newBSizeOfARow != mBSizeOfARow ||
  176. (isInDropdownMode && (oldBSize != aDesiredSize.BSize(wm) ||
  177. oldBSize != BSize(wm)))) {
  178. mBSizeOfARow = newBSizeOfARow;
  179. list->SetSuppressScrollbarUpdate(true);
  180. }
  181. }
  182. }