Coverage Report

Created: 2018-09-25 14:53

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