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/. */
7 #include "nsHTMLButtonControlFrame.h"
9 #include "mozilla/Baseline.h"
10 #include "mozilla/PresShell.h"
11 #include "nsContainerFrame.h"
12 #include "nsIFormControlFrame.h"
13 #include "nsIFrameInlines.h"
14 #include "nsPresContext.h"
15 #include "nsGkAtoms.h"
16 #include "nsButtonFrameRenderer.h"
17 #include "nsCSSAnonBoxes.h"
18 #include "nsNameSpaceManager.h"
19 #include "nsDisplayList.h"
22 using namespace mozilla
;
24 nsContainerFrame
* NS_NewHTMLButtonControlFrame(PresShell
* aPresShell
,
25 ComputedStyle
* aStyle
) {
26 return new (aPresShell
)
27 nsHTMLButtonControlFrame(aStyle
, aPresShell
->GetPresContext());
30 NS_IMPL_FRAMEARENA_HELPERS(nsHTMLButtonControlFrame
)
32 nsHTMLButtonControlFrame::nsHTMLButtonControlFrame(ComputedStyle
* aStyle
,
33 nsPresContext
* aPresContext
,
34 nsIFrame::ClassID aID
)
35 : nsContainerFrame(aStyle
, aPresContext
, aID
) {}
37 nsHTMLButtonControlFrame::~nsHTMLButtonControlFrame() = default;
39 void nsHTMLButtonControlFrame::Init(nsIContent
* aContent
,
40 nsContainerFrame
* aParent
,
41 nsIFrame
* aPrevInFlow
) {
42 nsContainerFrame::Init(aContent
, aParent
, aPrevInFlow
);
43 mRenderer
.SetFrame(this, PresContext());
46 NS_QUERYFRAME_HEAD(nsHTMLButtonControlFrame
)
47 NS_QUERYFRAME_ENTRY(nsIFormControlFrame
)
48 NS_QUERYFRAME_TAIL_INHERITING(nsContainerFrame
)
51 a11y::AccType
nsHTMLButtonControlFrame::AccessibleType() {
52 return a11y::eHTMLButtonType
;
56 void nsHTMLButtonControlFrame::SetFocus(bool aOn
, bool aRepaint
) {}
58 nsresult
nsHTMLButtonControlFrame::HandleEvent(nsPresContext
* aPresContext
,
59 WidgetGUIEvent
* aEvent
,
60 nsEventStatus
* aEventStatus
) {
61 // if disabled do nothing
62 if (mRenderer
.isDisabled()) {
66 // mouse clicks are handled by content
67 // we don't want our children to get any events. So just pass it to frame.
68 return nsIFrame::HandleEvent(aPresContext
, aEvent
, aEventStatus
);
71 bool nsHTMLButtonControlFrame::ShouldClipPaintingToBorderBox() {
72 return IsInput() || StyleDisplay()->mOverflowX
!= StyleOverflow::Visible
;
75 void nsHTMLButtonControlFrame::BuildDisplayList(
76 nsDisplayListBuilder
* aBuilder
, const nsDisplayListSet
& aLists
) {
77 nsDisplayList
onTop(aBuilder
);
78 if (IsVisibleForPainting()) {
79 // Clip the button itself to its border area for event hit testing.
80 Maybe
<DisplayListClipState::AutoSaveRestore
> eventClipState
;
81 if (aBuilder
->IsForEventDelivery()) {
82 eventClipState
.emplace(aBuilder
);
83 nsRect
rect(aBuilder
->ToReferenceFrame(this), GetSize());
85 bool hasRadii
= GetBorderRadii(radii
);
86 eventClipState
->ClipContainingBlockDescendants(
87 rect
, hasRadii
? radii
: nullptr);
90 mRenderer
.DisplayButton(aBuilder
, aLists
.BorderBackground(), &onTop
);
93 nsDisplayListCollection
set(aBuilder
);
96 DisplayListClipState::AutoSaveRestore
clipState(aBuilder
);
98 if (ShouldClipPaintingToBorderBox()) {
99 nsMargin border
= StyleBorder()->GetComputedBorder();
100 nsRect
rect(aBuilder
->ToReferenceFrame(this), GetSize());
101 rect
.Deflate(border
);
103 bool hasRadii
= GetPaddingBoxBorderRadii(radii
);
104 clipState
.ClipContainingBlockDescendants(rect
,
105 hasRadii
? radii
: nullptr);
108 BuildDisplayListForChild(aBuilder
, mFrames
.FirstChild(), set
,
109 DisplayChildFlag::ForcePseudoStackingContext
);
112 // Put the foreground outline and focus rects on top of the children
113 set
.Content()->AppendToTop(&onTop
);
116 DisplayOutline(aBuilder
, aLists
);
118 // to draw border when selected in editor
119 DisplaySelectionOverlay(aBuilder
, aLists
.Content());
122 nscoord
nsHTMLButtonControlFrame::GetMinISize(gfxContext
* aRenderingContext
) {
124 DISPLAY_MIN_INLINE_SIZE(this, result
);
125 if (Maybe
<nscoord
> containISize
= ContainIntrinsicISize()) {
126 result
= *containISize
;
128 nsIFrame
* kid
= mFrames
.FirstChild();
129 result
= nsLayoutUtils::IntrinsicForContainer(aRenderingContext
, kid
,
130 IntrinsicISizeType::MinISize
);
135 nscoord
nsHTMLButtonControlFrame::GetPrefISize(gfxContext
* aRenderingContext
) {
137 DISPLAY_PREF_INLINE_SIZE(this, result
);
138 if (Maybe
<nscoord
> containISize
= ContainIntrinsicISize()) {
139 result
= *containISize
;
141 nsIFrame
* kid
= mFrames
.FirstChild();
142 result
= nsLayoutUtils::IntrinsicForContainer(
143 aRenderingContext
, kid
, IntrinsicISizeType::PrefISize
);
148 void nsHTMLButtonControlFrame::Reflow(nsPresContext
* aPresContext
,
149 ReflowOutput
& aDesiredSize
,
150 const ReflowInput
& aReflowInput
,
151 nsReflowStatus
& aStatus
) {
153 DO_GLOBAL_REFLOW_COUNT("nsHTMLButtonControlFrame");
154 DISPLAY_REFLOW(aPresContext
, this, aReflowInput
, aDesiredSize
, aStatus
);
155 MOZ_ASSERT(aStatus
.IsEmpty(), "Caller should pass a fresh reflow status!");
158 nsIFrame
* firstKid
= mFrames
.FirstChild();
160 MOZ_ASSERT(firstKid
, "Button should have a child frame for its contents");
161 MOZ_ASSERT(!firstKid
->GetNextSibling(),
162 "Button should have exactly one child frame");
164 firstKid
->Style()->GetPseudoType() == PseudoStyleType::buttonContent
,
165 "Button's child frame has unexpected pseudo type!");
167 // XXXbz Eventually we may want to check-and-bail if
168 // !aReflowInput.ShouldReflowAllKids() &&
169 // !firstKid->IsSubtreeDirty().
170 // We'd need to cache our ascent for that, of course.
172 // Reflow the contents of the button.
173 // (This populates our aDesiredSize, too.)
174 ReflowButtonContents(aPresContext
, aDesiredSize
, aReflowInput
, firstKid
);
176 if (!ShouldClipPaintingToBorderBox()) {
177 ConsiderChildOverflow(aDesiredSize
.mOverflowAreas
, firstKid
);
179 // else, we ignore child overflow -- anything that overflows beyond our
180 // own border-box will get clipped when painting.
182 FinishReflowWithAbsoluteFrames(aPresContext
, aDesiredSize
, aReflowInput
,
185 // We're always complete and we don't support overflow containers
186 // so we shouldn't have a next-in-flow ever.
188 MOZ_ASSERT(!GetNextInFlow());
191 void nsHTMLButtonControlFrame::ReflowButtonContents(
192 nsPresContext
* aPresContext
, ReflowOutput
& aButtonDesiredSize
,
193 const ReflowInput
& aButtonReflowInput
, nsIFrame
* aFirstKid
) {
194 WritingMode wm
= GetWritingMode();
195 LogicalSize availSize
= aButtonReflowInput
.ComputedSize(wm
);
196 availSize
.BSize(wm
) = NS_UNCONSTRAINEDSIZE
;
198 // shorthand for a value we need to use in a bunch of places
199 const LogicalMargin
& clbp
=
200 aButtonReflowInput
.ComputedLogicalBorderPadding(wm
);
202 LogicalPoint
childPos(wm
);
203 childPos
.I(wm
) = clbp
.IStart(wm
);
204 availSize
.ISize(wm
) = std::max(availSize
.ISize(wm
), 0);
206 ReflowInput
contentsReflowInput(aPresContext
, aButtonReflowInput
, aFirstKid
,
209 nsReflowStatus contentsReflowStatus
;
210 ReflowOutput
contentsDesiredSize(aButtonReflowInput
);
211 childPos
.B(wm
) = 0; // This will be set properly later, after reflowing the
212 // child to determine its size.
214 if (aFirstKid
->IsFlexOrGridContainer()) {
215 // XXX: Should we use ResetResizeFlags::Yes?
216 contentsReflowInput
.SetComputedBSize(aButtonReflowInput
.ComputedBSize(),
217 ReflowInput::ResetResizeFlags::No
);
218 contentsReflowInput
.SetComputedMinBSize(
219 aButtonReflowInput
.ComputedMinBSize());
220 contentsReflowInput
.SetComputedMaxBSize(
221 aButtonReflowInput
.ComputedMaxBSize());
224 // We just pass a dummy containerSize here, as the child will be
225 // repositioned later by FinishReflowChild.
226 nsSize dummyContainerSize
;
227 ReflowChild(aFirstKid
, aPresContext
, contentsDesiredSize
, contentsReflowInput
,
228 wm
, childPos
, dummyContainerSize
, ReflowChildFlags::Default
,
229 contentsReflowStatus
);
230 MOZ_ASSERT(contentsReflowStatus
.IsComplete(),
231 "We gave button-contents frame unconstrained available height, "
232 "so it should be complete");
234 // Compute the button's content-box size:
235 LogicalSize
buttonContentBox(wm
);
236 if (aButtonReflowInput
.ComputedBSize() != NS_UNCONSTRAINEDSIZE
) {
237 // Button has a fixed block-size -- that's its content-box bSize.
238 buttonContentBox
.BSize(wm
) = aButtonReflowInput
.ComputedBSize();
240 // Button is intrinsically sized -- it should shrinkwrap the
241 // button-contents' bSize. But if it has size containment in block axis,
242 // ignore the contents and use contain-intrinsic-block-size.
243 nscoord bSize
= aButtonReflowInput
.mFrame
->ContainIntrinsicBSize().valueOr(
244 contentsDesiredSize
.BSize(wm
));
246 // Make sure we obey min/max-bSize in the case when we're doing intrinsic
247 // sizing (we get it for free when we have a non-intrinsic
248 // aButtonReflowInput.ComputedBSize()). Note that we do this before
249 // adjusting for borderpadding, since mComputedMaxBSize and
250 // mComputedMinBSize are content bSizes.
251 buttonContentBox
.BSize(wm
) =
252 NS_CSS_MINMAX(bSize
, aButtonReflowInput
.ComputedMinBSize(),
253 aButtonReflowInput
.ComputedMaxBSize());
255 if (aButtonReflowInput
.ComputedISize() != NS_UNCONSTRAINEDSIZE
) {
256 buttonContentBox
.ISize(wm
) = aButtonReflowInput
.ComputedISize();
258 nscoord iSize
= aButtonReflowInput
.mFrame
->ContainIntrinsicISize().valueOr(
259 contentsDesiredSize
.ISize(wm
));
260 buttonContentBox
.ISize(wm
) =
261 NS_CSS_MINMAX(iSize
, aButtonReflowInput
.ComputedMinISize(),
262 aButtonReflowInput
.ComputedMaxISize());
265 // Center child in the block-direction in the button
266 // (technically, inside of the button's focus-padding area)
268 buttonContentBox
.BSize(wm
) - contentsDesiredSize
.BSize(wm
);
270 childPos
.B(wm
) = std::max(0, extraSpace
/ 2);
272 // Adjust childPos.B() to be in terms of the button's frame-rect:
273 childPos
.B(wm
) += clbp
.BStart(wm
);
275 nsSize containerSize
= (buttonContentBox
+ clbp
.Size(wm
)).GetPhysicalSize(wm
);
278 FinishReflowChild(aFirstKid
, aPresContext
, contentsDesiredSize
,
279 &contentsReflowInput
, wm
, childPos
, containerSize
,
280 ReflowChildFlags::Default
);
282 // Make sure we have a useful 'ascent' value for the child
283 if (contentsDesiredSize
.BlockStartAscent() ==
284 ReflowOutput::ASK_FOR_BASELINE
) {
285 WritingMode wm
= aButtonReflowInput
.GetWritingMode();
286 contentsDesiredSize
.SetBlockStartAscent(aFirstKid
->GetLogicalBaseline(wm
));
289 // OK, we're done with the child frame.
290 // Use what we learned to populate the button frame's reflow metrics.
291 // * Button's height & width are content-box size + border-box contribution:
292 aButtonDesiredSize
.SetSize(
294 LogicalSize(wm
, aButtonReflowInput
.ComputedISize() + clbp
.IStartEnd(wm
),
295 buttonContentBox
.BSize(wm
) + clbp
.BStartEnd(wm
)));
297 // * Button's ascent is its child's ascent, plus the child's block-offset
298 // within our frame... unless it's orthogonal, in which case we'll use the
299 // contents inline-size as an approximation for now.
300 // XXX is there a better strategy? should we include border-padding?
301 if (!aButtonReflowInput
.mStyleDisplay
->IsContainLayout()) {
302 if (aButtonDesiredSize
.GetWritingMode().IsOrthogonalTo(wm
)) {
303 aButtonDesiredSize
.SetBlockStartAscent(
304 wm
.IsAlphabeticalBaseline() ? contentsDesiredSize
.ISize(wm
)
305 : contentsDesiredSize
.ISize(wm
) / 2);
307 aButtonDesiredSize
.SetBlockStartAscent(
308 contentsDesiredSize
.BlockStartAscent() + childPos
.B(wm
));
310 } // else: we're layout-contained, and so we have no baseline.
312 aButtonDesiredSize
.SetOverflowAreasToDesiredBounds();
315 Maybe
<nscoord
> nsHTMLButtonControlFrame::GetNaturalBaselineBOffset(
316 WritingMode aWM
, BaselineSharingGroup aBaselineGroup
,
317 BaselineExportContext aExportContext
) const {
318 if (StyleDisplay()->IsContainLayout()) {
322 nsIFrame
* inner
= mFrames
.FirstChild();
323 if (MOZ_UNLIKELY(inner
->GetWritingMode().IsOrthogonalTo(aWM
))) {
327 inner
->GetNaturalBaselineBOffset(aWM
, aBaselineGroup
, aExportContext
)
328 .valueOrFrom([inner
, aWM
, aBaselineGroup
]() {
329 return Baseline::SynthesizeBOffsetFromBorderBox(inner
, aWM
,
333 nscoord innerBStart
= inner
->BStart(aWM
, GetSize());
334 if (aBaselineGroup
== BaselineSharingGroup::First
) {
335 return Some(result
+ innerBStart
);
337 return Some(result
+ BSize(aWM
) - (innerBStart
+ inner
->BSize(aWM
)));
340 BaselineSharingGroup
nsHTMLButtonControlFrame::GetDefaultBaselineSharingGroup()
342 nsIFrame
* firstKid
= mFrames
.FirstChild();
344 MOZ_ASSERT(firstKid
, "Button should have a child frame for its contents");
345 MOZ_ASSERT(!firstKid
->GetNextSibling(),
346 "Button should have exactly one child frame");
347 return firstKid
->GetDefaultBaselineSharingGroup();
350 nscoord
nsHTMLButtonControlFrame::SynthesizeFallbackBaseline(
351 mozilla::WritingMode aWM
, BaselineSharingGroup aBaselineGroup
) const {
352 return Baseline::SynthesizeBOffsetFromMarginBox(this, aWM
, aBaselineGroup
);
355 nsresult
nsHTMLButtonControlFrame::SetFormProperty(nsAtom
* aName
,
356 const nsAString
& aValue
) {
357 if (nsGkAtoms::value
== aName
) {
358 return mContent
->AsElement()->SetAttr(kNameSpaceID_None
, nsGkAtoms::value
,
364 ComputedStyle
* nsHTMLButtonControlFrame::GetAdditionalComputedStyle(
365 int32_t aIndex
) const {
366 return mRenderer
.GetComputedStyle(aIndex
);
369 void nsHTMLButtonControlFrame::SetAdditionalComputedStyle(
370 int32_t aIndex
, ComputedStyle
* aComputedStyle
) {
371 mRenderer
.SetComputedStyle(aIndex
, aComputedStyle
);
374 void nsHTMLButtonControlFrame::AppendDirectlyOwnedAnonBoxes(
375 nsTArray
<OwnedAnonBox
>& aResult
) {
376 MOZ_ASSERT(mFrames
.FirstChild(), "Must have our button-content anon box");
377 MOZ_ASSERT(!mFrames
.FirstChild()->GetNextSibling(),
378 "Must only have our button-content anon box");
379 aResult
.AppendElement(OwnedAnonBox(mFrames
.FirstChild()));
383 void nsHTMLButtonControlFrame::AppendFrames(ChildListID aListID
,
384 nsFrameList
&& aFrameList
) {
385 MOZ_CRASH("unsupported operation");
388 void nsHTMLButtonControlFrame::InsertFrames(
389 ChildListID aListID
, nsIFrame
* aPrevFrame
,
390 const nsLineList::iterator
* aPrevFrameLine
, nsFrameList
&& aFrameList
) {
391 MOZ_CRASH("unsupported operation");
394 void nsHTMLButtonControlFrame::RemoveFrame(DestroyContext
&, ChildListID
,
396 MOZ_CRASH("unsupported operation");