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/. */
6 #include "nsHTMLButtonControlFrame.h"
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 "nsINameSpaceManager.h"
16 #include "nsStyleSet.h"
17 #include "nsDisplayList.h"
20 using namespace mozilla
;
23 NS_NewHTMLButtonControlFrame(nsIPresShell
* aPresShell
, nsStyleContext
* aContext
)
25 return new (aPresShell
) nsHTMLButtonControlFrame(aContext
);
28 NS_IMPL_FRAMEARENA_HELPERS(nsHTMLButtonControlFrame
)
30 nsHTMLButtonControlFrame::nsHTMLButtonControlFrame(nsStyleContext
* aContext
)
31 : nsContainerFrame(aContext
)
35 nsHTMLButtonControlFrame::~nsHTMLButtonControlFrame()
40 nsHTMLButtonControlFrame::DestroyFrom(nsIFrame
* aDestructRoot
)
42 nsFormControlFrame::RegUnRegAccessKey(static_cast<nsIFrame
*>(this), false);
43 nsContainerFrame::DestroyFrom(aDestructRoot
);
47 nsHTMLButtonControlFrame::Init(
50 nsIFrame
* aPrevInFlow
)
52 nsContainerFrame::Init(aContent
, aParent
, aPrevInFlow
);
53 mRenderer
.SetFrame(this, PresContext());
56 NS_QUERYFRAME_HEAD(nsHTMLButtonControlFrame
)
57 NS_QUERYFRAME_ENTRY(nsIFormControlFrame
)
58 NS_QUERYFRAME_TAIL_INHERITING(nsContainerFrame
)
62 nsHTMLButtonControlFrame::AccessibleType()
64 return a11y::eHTMLButtonType
;
69 nsHTMLButtonControlFrame::GetType() const
71 return nsGkAtoms::HTMLButtonControlFrame
;
75 nsHTMLButtonControlFrame::SetFocus(bool aOn
, bool aRepaint
)
80 nsHTMLButtonControlFrame::HandleEvent(nsPresContext
* aPresContext
,
81 WidgetGUIEvent
* aEvent
,
82 nsEventStatus
* aEventStatus
)
84 // if disabled do nothing
85 if (mRenderer
.isDisabled()) {
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
);
96 nsHTMLButtonControlFrame::BuildDisplayList(nsDisplayListBuilder
* aBuilder
,
97 const nsRect
& aDirtyRect
,
98 const nsDisplayListSet
& aLists
)
101 if (IsVisibleForPainting(aBuilder
)) {
102 mRenderer
.DisplayButton(aBuilder
, aLists
.BorderBackground(), &onTop
);
105 nsDisplayListCollection set
;
107 // Do not allow the child subtree to receive events.
108 if (!aBuilder
->IsForEventDelivery()) {
109 DisplayListClipState::AutoSaveRestore
clipState(aBuilder
);
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
);
116 bool hasRadii
= GetPaddingBoxBorderRadii(radii
);
117 clipState
.ClipContainingBlockDescendants(rect
, hasRadii
? radii
: nullptr);
120 BuildDisplayListForChild(aBuilder
, mFrames
.FirstChild(), aDirtyRect
, set
,
121 DISPLAY_CHILD_FORCE_PSEUDO_STACKING_CONTEXT
);
122 // That should put the display items in set.Content()
125 // Put the foreground outline and focus rects on top of the children
126 set
.Content()->AppendToTop(&onTop
);
129 DisplayOutline(aBuilder
, aLists
);
131 // to draw border when selected in editor
132 DisplaySelectionOverlay(aBuilder
, aLists
.Content());
136 nsHTMLButtonControlFrame::GetMinWidth(nsRenderingContext
* aRenderingContext
)
139 DISPLAY_MIN_WIDTH(this, result
);
141 nsIFrame
* kid
= mFrames
.FirstChild();
142 result
= nsLayoutUtils::IntrinsicForContainer(aRenderingContext
,
144 nsLayoutUtils::MIN_WIDTH
);
146 result
+= mRenderer
.GetAddedButtonBorderAndPadding().LeftRight();
152 nsHTMLButtonControlFrame::GetPrefWidth(nsRenderingContext
* aRenderingContext
)
155 DISPLAY_PREF_WIDTH(this, result
);
157 nsIFrame
* kid
= mFrames
.FirstChild();
158 result
= nsLayoutUtils::IntrinsicForContainer(aRenderingContext
,
160 nsLayoutUtils::PREF_WIDTH
);
161 result
+= mRenderer
.GetAddedButtonBorderAndPadding().LeftRight();
166 nsHTMLButtonControlFrame::Reflow(nsPresContext
* aPresContext
,
167 nsHTMLReflowMetrics
& aDesiredSize
,
168 const nsHTMLReflowState
& aReflowState
,
169 nsReflowStatus
& aStatus
)
171 DO_GLOBAL_REFLOW_COUNT("nsHTMLButtonControlFrame");
172 DISPLAY_REFLOW(aPresContext
, this, aReflowState
, aDesiredSize
, aStatus
);
174 NS_PRECONDITION(aReflowState
.ComputedWidth() != NS_INTRINSICSIZE
,
175 "Should have real computed width by now");
177 if (mState
& NS_FRAME_FIRST_REFLOW
) {
178 nsFormControlFrame::RegUnRegAccessKey(static_cast<nsIFrame
*>(this), true);
182 nsIFrame
* firstKid
= mFrames
.FirstChild();
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!");
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.
196 // Reflow the contents of the button.
197 // (This populates our aDesiredSize, too.)
198 ReflowButtonContents(aPresContext
, aDesiredSize
,
199 aReflowState
, firstKid
);
201 ConsiderChildOverflow(aDesiredSize
.mOverflowAreas
, firstKid
);
203 aStatus
= NS_FRAME_COMPLETE
;
204 FinishReflowWithAbsoluteFrames(aPresContext
, aDesiredSize
,
205 aReflowState
, aStatus
);
207 NS_FRAME_SET_TRUNCATION(aStatus
, aReflowState
, aDesiredSize
);
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
216 static nsHTMLReflowState
217 CloneReflowStateWithReducedContentBox(
218 const nsHTMLReflowState
& aButtonReflowState
,
219 const nsMargin
& aFocusPadding
)
221 nscoord adjustedWidth
=
222 aButtonReflowState
.ComputedWidth() - aFocusPadding
.LeftRight();
223 adjustedWidth
= std::max(0, adjustedWidth
);
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
);
232 nsHTMLReflowState
clone(aButtonReflowState
);
233 clone
.SetComputedWidth(adjustedWidth
);
234 clone
.SetComputedHeight(adjustedHeight
);
240 nsHTMLButtonControlFrame::ReflowButtonContents(nsPresContext
* aPresContext
,
241 nsHTMLReflowMetrics
& aButtonDesiredSize
,
242 const nsHTMLReflowState
& aButtonReflowState
,
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();
249 nsSize
availSize(aButtonReflowState
.ComputedWidth(), NS_INTRINSICSIZE
);
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();
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?");
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
;
274 availSize
.width
= std::max(availSize
.width
,0);
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
);
281 nsHTMLReflowState
contentsReflowState(aPresContext
,
282 adjustedButtonReflowState
,
283 aFirstKid
, availSize
);
285 nsReflowStatus contentsReflowStatus
;
286 nsHTMLReflowMetrics
contentsDesiredSize(aButtonReflowState
.GetWritingMode());
287 ReflowChild(aFirstKid
, aPresContext
,
288 contentsDesiredSize
, contentsReflowState
,
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");
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();
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();
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());
318 // Center child vertically in the button
319 // (technically, inside of the button's focus-padding area)
321 buttonContentBoxHeight
- focusPadding
.TopBottom() -
322 contentsDesiredSize
.Height();
324 nscoord yoffset
= std::max(0, extraSpace
/ 2);
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
;
331 FinishReflowChild(aFirstKid
, aPresContext
,
332 &contentsReflowState
, contentsDesiredSize
,
333 xoffset
, yoffset
, 0);
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());
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();
346 aButtonDesiredSize
.Height() = buttonContentBoxHeight
+
347 aButtonReflowState
.ComputedPhysicalBorderPadding().TopBottom();
349 // * Button's ascent is its child's ascent, plus the child's y-offset
351 aButtonDesiredSize
.SetTopAscent(contentsDesiredSize
.TopAscent() + yoffset
);
353 aButtonDesiredSize
.SetOverflowAreasToDesiredBounds();
356 nsresult
nsHTMLButtonControlFrame::SetFormProperty(nsIAtom
* aName
, const nsAString
& aValue
)
358 if (nsGkAtoms::value
== aName
) {
359 return mContent
->SetAttr(kNameSpaceID_None
, nsGkAtoms::value
,
366 nsHTMLButtonControlFrame::GetAdditionalStyleContext(int32_t aIndex
) const
368 return mRenderer
.GetStyleContext(aIndex
);
372 nsHTMLButtonControlFrame::SetAdditionalStyleContext(int32_t aIndex
,
373 nsStyleContext
* aStyleContext
)
375 mRenderer
.SetStyleContext(aIndex
, aStyleContext
);
379 nsHTMLButtonControlFrame::AppendFrames(ChildListID aListID
,
380 nsFrameList
& aFrameList
)
382 NS_NOTREACHED("unsupported operation");
383 return NS_ERROR_UNEXPECTED
;
387 nsHTMLButtonControlFrame::InsertFrames(ChildListID aListID
,
388 nsIFrame
* aPrevFrame
,
389 nsFrameList
& aFrameList
)
391 NS_NOTREACHED("unsupported operation");
392 return NS_ERROR_UNEXPECTED
;
396 nsHTMLButtonControlFrame::RemoveFrame(ChildListID aListID
,
399 NS_NOTREACHED("unsupported operation");
400 return NS_ERROR_UNEXPECTED
;