layout/forms/nsHTMLButtonControlFrame.cpp

branch
TOR_BUG_9701
changeset 8
97036ab72558
equal deleted inserted replaced
-1:000000000000 0:e1b9cc5f2412
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
6 #include "nsHTMLButtonControlFrame.h"
7
8 #include "nsContainerFrame.h"
9 #include "nsIFormControlFrame.h"
10 #include "nsPresContext.h"
11 #include "nsGkAtoms.h"
12 #include "nsButtonFrameRenderer.h"
13 #include "nsCSSAnonBoxes.h"
14 #include "nsFormControlFrame.h"
15 #include "nsNameSpaceManager.h"
16 #include "nsStyleSet.h"
17 #include "nsDisplayList.h"
18 #include <algorithm>
19
20 using namespace mozilla;
21
22 nsIFrame*
23 NS_NewHTMLButtonControlFrame(nsIPresShell* aPresShell, nsStyleContext* aContext)
24 {
25 return new (aPresShell) nsHTMLButtonControlFrame(aContext);
26 }
27
28 NS_IMPL_FRAMEARENA_HELPERS(nsHTMLButtonControlFrame)
29
30 nsHTMLButtonControlFrame::nsHTMLButtonControlFrame(nsStyleContext* aContext)
31 : nsContainerFrame(aContext)
32 {
33 }
34
35 nsHTMLButtonControlFrame::~nsHTMLButtonControlFrame()
36 {
37 }
38
39 void
40 nsHTMLButtonControlFrame::DestroyFrom(nsIFrame* aDestructRoot)
41 {
42 nsFormControlFrame::RegUnRegAccessKey(static_cast<nsIFrame*>(this), false);
43 nsContainerFrame::DestroyFrom(aDestructRoot);
44 }
45
46 void
47 nsHTMLButtonControlFrame::Init(
48 nsIContent* aContent,
49 nsIFrame* aParent,
50 nsIFrame* aPrevInFlow)
51 {
52 nsContainerFrame::Init(aContent, aParent, aPrevInFlow);
53 mRenderer.SetFrame(this, PresContext());
54 }
55
56 NS_QUERYFRAME_HEAD(nsHTMLButtonControlFrame)
57 NS_QUERYFRAME_ENTRY(nsIFormControlFrame)
58 NS_QUERYFRAME_TAIL_INHERITING(nsContainerFrame)
59
60 #ifdef ACCESSIBILITY
61 a11y::AccType
62 nsHTMLButtonControlFrame::AccessibleType()
63 {
64 return a11y::eHTMLButtonType;
65 }
66 #endif
67
68 nsIAtom*
69 nsHTMLButtonControlFrame::GetType() const
70 {
71 return nsGkAtoms::HTMLButtonControlFrame;
72 }
73
74 void
75 nsHTMLButtonControlFrame::SetFocus(bool aOn, bool aRepaint)
76 {
77 }
78
79 nsresult
80 nsHTMLButtonControlFrame::HandleEvent(nsPresContext* aPresContext,
81 WidgetGUIEvent* aEvent,
82 nsEventStatus* aEventStatus)
83 {
84 // if disabled do nothing
85 if (mRenderer.isDisabled()) {
86 return NS_OK;
87 }
88
89 // mouse clicks are handled by content
90 // we don't want our children to get any events. So just pass it to frame.
91 return nsFrame::HandleEvent(aPresContext, aEvent, aEventStatus);
92 }
93
94
95 void
96 nsHTMLButtonControlFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
97 const nsRect& aDirtyRect,
98 const nsDisplayListSet& aLists)
99 {
100 nsDisplayList onTop;
101 if (IsVisibleForPainting(aBuilder)) {
102 mRenderer.DisplayButton(aBuilder, aLists.BorderBackground(), &onTop);
103 }
104
105 nsDisplayListCollection set;
106
107 // Do not allow the child subtree to receive events.
108 if (!aBuilder->IsForEventDelivery()) {
109 DisplayListClipState::AutoSaveRestore clipState(aBuilder);
110
111 if (IsInput() || StyleDisplay()->mOverflowX != NS_STYLE_OVERFLOW_VISIBLE) {
112 nsMargin border = StyleBorder()->GetComputedBorder();
113 nsRect rect(aBuilder->ToReferenceFrame(this), GetSize());
114 rect.Deflate(border);
115 nscoord radii[8];
116 bool hasRadii = GetPaddingBoxBorderRadii(radii);
117 clipState.ClipContainingBlockDescendants(rect, hasRadii ? radii : nullptr);
118 }
119
120 BuildDisplayListForChild(aBuilder, mFrames.FirstChild(), aDirtyRect, set,
121 DISPLAY_CHILD_FORCE_PSEUDO_STACKING_CONTEXT);
122 // That should put the display items in set.Content()
123 }
124
125 // Put the foreground outline and focus rects on top of the children
126 set.Content()->AppendToTop(&onTop);
127 set.MoveTo(aLists);
128
129 DisplayOutline(aBuilder, aLists);
130
131 // to draw border when selected in editor
132 DisplaySelectionOverlay(aBuilder, aLists.Content());
133 }
134
135 nscoord
136 nsHTMLButtonControlFrame::GetMinWidth(nsRenderingContext* aRenderingContext)
137 {
138 nscoord result;
139 DISPLAY_MIN_WIDTH(this, result);
140
141 nsIFrame* kid = mFrames.FirstChild();
142 result = nsLayoutUtils::IntrinsicForContainer(aRenderingContext,
143 kid,
144 nsLayoutUtils::MIN_WIDTH);
145
146 result += mRenderer.GetAddedButtonBorderAndPadding().LeftRight();
147
148 return result;
149 }
150
151 nscoord
152 nsHTMLButtonControlFrame::GetPrefWidth(nsRenderingContext* aRenderingContext)
153 {
154 nscoord result;
155 DISPLAY_PREF_WIDTH(this, result);
156
157 nsIFrame* kid = mFrames.FirstChild();
158 result = nsLayoutUtils::IntrinsicForContainer(aRenderingContext,
159 kid,
160 nsLayoutUtils::PREF_WIDTH);
161 result += mRenderer.GetAddedButtonBorderAndPadding().LeftRight();
162 return result;
163 }
164
165 nsresult
166 nsHTMLButtonControlFrame::Reflow(nsPresContext* aPresContext,
167 nsHTMLReflowMetrics& aDesiredSize,
168 const nsHTMLReflowState& aReflowState,
169 nsReflowStatus& aStatus)
170 {
171 DO_GLOBAL_REFLOW_COUNT("nsHTMLButtonControlFrame");
172 DISPLAY_REFLOW(aPresContext, this, aReflowState, aDesiredSize, aStatus);
173
174 NS_PRECONDITION(aReflowState.ComputedWidth() != NS_INTRINSICSIZE,
175 "Should have real computed width by now");
176
177 if (mState & NS_FRAME_FIRST_REFLOW) {
178 nsFormControlFrame::RegUnRegAccessKey(static_cast<nsIFrame*>(this), true);
179 }
180
181 // Reflow the child
182 nsIFrame* firstKid = mFrames.FirstChild();
183
184 MOZ_ASSERT(firstKid, "Button should have a child frame for its contents");
185 MOZ_ASSERT(!firstKid->GetNextSibling(),
186 "Button should have exactly one child frame");
187 MOZ_ASSERT(firstKid->StyleContext()->GetPseudo() ==
188 nsCSSAnonBoxes::buttonContent,
189 "Button's child frame has unexpected pseudo type!");
190
191 // XXXbz Eventually we may want to check-and-bail if
192 // !aReflowState.ShouldReflowAllKids() &&
193 // !NS_SUBTREE_DIRTY(firstKid).
194 // We'd need to cache our ascent for that, of course.
195
196 // Reflow the contents of the button.
197 // (This populates our aDesiredSize, too.)
198 ReflowButtonContents(aPresContext, aDesiredSize,
199 aReflowState, firstKid);
200
201 ConsiderChildOverflow(aDesiredSize.mOverflowAreas, firstKid);
202
203 aStatus = NS_FRAME_COMPLETE;
204 FinishReflowWithAbsoluteFrames(aPresContext, aDesiredSize,
205 aReflowState, aStatus);
206
207 NS_FRAME_SET_TRUNCATION(aStatus, aReflowState, aDesiredSize);
208 return NS_OK;
209 }
210
211 // Helper-function that lets us clone the button's reflow state, but with its
212 // ComputedWidth and ComputedHeight reduced by the amount of renderer-specific
213 // focus border and padding that we're using. (This lets us provide a more
214 // appropriate content-box size for descendents' percent sizes to resolve
215 // against.)
216 static nsHTMLReflowState
217 CloneReflowStateWithReducedContentBox(
218 const nsHTMLReflowState& aButtonReflowState,
219 const nsMargin& aFocusPadding)
220 {
221 nscoord adjustedWidth =
222 aButtonReflowState.ComputedWidth() - aFocusPadding.LeftRight();
223 adjustedWidth = std::max(0, adjustedWidth);
224
225 // (Only adjust height if it's an actual length.)
226 nscoord adjustedHeight = aButtonReflowState.ComputedHeight();
227 if (adjustedHeight != NS_INTRINSICSIZE) {
228 adjustedHeight -= aFocusPadding.TopBottom();
229 adjustedHeight = std::max(0, adjustedHeight);
230 }
231
232 nsHTMLReflowState clone(aButtonReflowState);
233 clone.SetComputedWidth(adjustedWidth);
234 clone.SetComputedHeight(adjustedHeight);
235
236 return clone;
237 }
238
239 void
240 nsHTMLButtonControlFrame::ReflowButtonContents(nsPresContext* aPresContext,
241 nsHTMLReflowMetrics& aButtonDesiredSize,
242 const nsHTMLReflowState& aButtonReflowState,
243 nsIFrame* aFirstKid)
244 {
245 // Buttons have some bonus renderer-determined border/padding,
246 // which occupies part of the button's content-box area:
247 const nsMargin focusPadding = mRenderer.GetAddedButtonBorderAndPadding();
248
249 nsSize availSize(aButtonReflowState.ComputedWidth(), NS_INTRINSICSIZE);
250
251 // Indent the child inside us by the focus border. We must do this separate
252 // from the regular border.
253 availSize.width -= focusPadding.LeftRight();
254
255 // See whether out availSize's width is big enough. If it's smaller than our
256 // intrinsic min width, that means that the kid wouldn't really fit; for a
257 // better look in such cases we adjust the available width and our left
258 // offset to allow the kid to spill left into our padding.
259 nscoord xoffset = focusPadding.left +
260 aButtonReflowState.ComputedPhysicalBorderPadding().left;
261 nscoord extrawidth = GetMinWidth(aButtonReflowState.rendContext) -
262 aButtonReflowState.ComputedWidth();
263 if (extrawidth > 0) {
264 nscoord extraleft = extrawidth / 2;
265 nscoord extraright = extrawidth - extraleft;
266 NS_ASSERTION(extraright >=0, "How'd that happen?");
267
268 // Do not allow the extras to be bigger than the relevant padding
269 extraleft = std::min(extraleft, aButtonReflowState.ComputedPhysicalPadding().left);
270 extraright = std::min(extraright, aButtonReflowState.ComputedPhysicalPadding().right);
271 xoffset -= extraleft;
272 availSize.width += extraleft + extraright;
273 }
274 availSize.width = std::max(availSize.width,0);
275
276 // Give child a clone of the button's reflow state, with height/width reduced
277 // by focusPadding, so that descendants with height:100% don't protrude.
278 nsHTMLReflowState adjustedButtonReflowState =
279 CloneReflowStateWithReducedContentBox(aButtonReflowState, focusPadding);
280
281 nsHTMLReflowState contentsReflowState(aPresContext,
282 adjustedButtonReflowState,
283 aFirstKid, availSize);
284
285 nsReflowStatus contentsReflowStatus;
286 nsHTMLReflowMetrics contentsDesiredSize(aButtonReflowState);
287 ReflowChild(aFirstKid, aPresContext,
288 contentsDesiredSize, contentsReflowState,
289 xoffset,
290 focusPadding.top + aButtonReflowState.ComputedPhysicalBorderPadding().top,
291 0, contentsReflowStatus);
292 MOZ_ASSERT(NS_FRAME_IS_COMPLETE(contentsReflowStatus),
293 "We gave button-contents frame unconstrained available height, "
294 "so it should be complete");
295
296 // Compute the button's content-box height:
297 nscoord buttonContentBoxHeight = 0;
298 if (aButtonReflowState.ComputedHeight() != NS_INTRINSICSIZE) {
299 // Button has a fixed height -- that's its content-box height.
300 buttonContentBoxHeight = aButtonReflowState.ComputedHeight();
301 } else {
302 // Button is intrinsically sized -- it should shrinkwrap the
303 // button-contents' height, plus any focus-padding space:
304 buttonContentBoxHeight =
305 contentsDesiredSize.Height() + focusPadding.TopBottom();
306
307 // Make sure we obey min/max-height in the case when we're doing intrinsic
308 // sizing (we get it for free when we have a non-intrinsic
309 // aButtonReflowState.ComputedHeight()). Note that we do this before
310 // adjusting for borderpadding, since mComputedMaxHeight and
311 // mComputedMinHeight are content heights.
312 buttonContentBoxHeight =
313 NS_CSS_MINMAX(buttonContentBoxHeight,
314 aButtonReflowState.ComputedMinHeight(),
315 aButtonReflowState.ComputedMaxHeight());
316 }
317
318 // Center child vertically in the button
319 // (technically, inside of the button's focus-padding area)
320 nscoord extraSpace =
321 buttonContentBoxHeight - focusPadding.TopBottom() -
322 contentsDesiredSize.Height();
323
324 nscoord yoffset = std::max(0, extraSpace / 2);
325
326 // Adjust yoffset to be in terms of the button's frame-rect, instead of
327 // its focus-padding rect:
328 yoffset += focusPadding.top + aButtonReflowState.ComputedPhysicalBorderPadding().top;
329
330 // Place the child
331 FinishReflowChild(aFirstKid, aPresContext,
332 contentsDesiredSize, &contentsReflowState,
333 xoffset, yoffset, 0);
334
335 // Make sure we have a useful 'ascent' value for the child
336 if (contentsDesiredSize.TopAscent() == nsHTMLReflowMetrics::ASK_FOR_BASELINE) {
337 contentsDesiredSize.SetTopAscent(aFirstKid->GetBaseline());
338 }
339
340 // OK, we're done with the child frame.
341 // Use what we learned to populate the button frame's reflow metrics.
342 // * Button's height & width are content-box size + border-box contribution:
343 aButtonDesiredSize.Width() = aButtonReflowState.ComputedWidth() +
344 aButtonReflowState.ComputedPhysicalBorderPadding().LeftRight();
345
346 aButtonDesiredSize.Height() = buttonContentBoxHeight +
347 aButtonReflowState.ComputedPhysicalBorderPadding().TopBottom();
348
349 // * Button's ascent is its child's ascent, plus the child's y-offset
350 // within our frame:
351 aButtonDesiredSize.SetTopAscent(contentsDesiredSize.TopAscent() + yoffset);
352
353 aButtonDesiredSize.SetOverflowAreasToDesiredBounds();
354 }
355
356 nsresult nsHTMLButtonControlFrame::SetFormProperty(nsIAtom* aName, const nsAString& aValue)
357 {
358 if (nsGkAtoms::value == aName) {
359 return mContent->SetAttr(kNameSpaceID_None, nsGkAtoms::value,
360 aValue, true);
361 }
362 return NS_OK;
363 }
364
365 nsStyleContext*
366 nsHTMLButtonControlFrame::GetAdditionalStyleContext(int32_t aIndex) const
367 {
368 return mRenderer.GetStyleContext(aIndex);
369 }
370
371 void
372 nsHTMLButtonControlFrame::SetAdditionalStyleContext(int32_t aIndex,
373 nsStyleContext* aStyleContext)
374 {
375 mRenderer.SetStyleContext(aIndex, aStyleContext);
376 }
377
378 nsresult
379 nsHTMLButtonControlFrame::AppendFrames(ChildListID aListID,
380 nsFrameList& aFrameList)
381 {
382 NS_NOTREACHED("unsupported operation");
383 return NS_ERROR_UNEXPECTED;
384 }
385
386 nsresult
387 nsHTMLButtonControlFrame::InsertFrames(ChildListID aListID,
388 nsIFrame* aPrevFrame,
389 nsFrameList& aFrameList)
390 {
391 NS_NOTREACHED("unsupported operation");
392 return NS_ERROR_UNEXPECTED;
393 }
394
395 nsresult
396 nsHTMLButtonControlFrame::RemoveFrame(ChildListID aListID,
397 nsIFrame* aOldFrame)
398 {
399 NS_NOTREACHED("unsupported operation");
400 return NS_ERROR_UNEXPECTED;
401 }

mercurial