Line data Source code
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 "nsDisplayList.h"
17 : #include <algorithm>
18 :
19 : using namespace mozilla;
20 :
21 : nsContainerFrame*
22 0 : NS_NewHTMLButtonControlFrame(nsIPresShell* aPresShell, nsStyleContext* aContext)
23 : {
24 0 : return new (aPresShell) nsHTMLButtonControlFrame(aContext);
25 : }
26 :
27 0 : NS_IMPL_FRAMEARENA_HELPERS(nsHTMLButtonControlFrame)
28 :
29 0 : nsHTMLButtonControlFrame::nsHTMLButtonControlFrame(nsStyleContext* aContext,
30 0 : nsIFrame::ClassID aID)
31 0 : : nsContainerFrame(aContext, aID)
32 : {
33 0 : }
34 :
35 0 : nsHTMLButtonControlFrame::~nsHTMLButtonControlFrame()
36 : {
37 0 : }
38 :
39 : void
40 0 : nsHTMLButtonControlFrame::DestroyFrom(nsIFrame* aDestructRoot)
41 : {
42 0 : nsFormControlFrame::RegUnRegAccessKey(static_cast<nsIFrame*>(this), false);
43 0 : nsContainerFrame::DestroyFrom(aDestructRoot);
44 0 : }
45 :
46 : void
47 0 : nsHTMLButtonControlFrame::Init(nsIContent* aContent,
48 : nsContainerFrame* aParent,
49 : nsIFrame* aPrevInFlow)
50 : {
51 0 : nsContainerFrame::Init(aContent, aParent, aPrevInFlow);
52 0 : mRenderer.SetFrame(this, PresContext());
53 0 : }
54 :
55 0 : NS_QUERYFRAME_HEAD(nsHTMLButtonControlFrame)
56 0 : NS_QUERYFRAME_ENTRY(nsIFormControlFrame)
57 0 : NS_QUERYFRAME_TAIL_INHERITING(nsContainerFrame)
58 :
59 : #ifdef ACCESSIBILITY
60 : a11y::AccType
61 0 : nsHTMLButtonControlFrame::AccessibleType()
62 : {
63 0 : return a11y::eHTMLButtonType;
64 : }
65 : #endif
66 :
67 : void
68 0 : nsHTMLButtonControlFrame::SetFocus(bool aOn, bool aRepaint)
69 : {
70 0 : }
71 :
72 : nsresult
73 0 : nsHTMLButtonControlFrame::HandleEvent(nsPresContext* aPresContext,
74 : WidgetGUIEvent* aEvent,
75 : nsEventStatus* aEventStatus)
76 : {
77 : // if disabled do nothing
78 0 : if (mRenderer.isDisabled()) {
79 0 : return NS_OK;
80 : }
81 :
82 : // mouse clicks are handled by content
83 : // we don't want our children to get any events. So just pass it to frame.
84 0 : return nsFrame::HandleEvent(aPresContext, aEvent, aEventStatus);
85 : }
86 :
87 : bool
88 0 : nsHTMLButtonControlFrame::ShouldClipPaintingToBorderBox()
89 : {
90 0 : return IsInput() || StyleDisplay()->mOverflowX != NS_STYLE_OVERFLOW_VISIBLE;
91 : }
92 :
93 : void
94 0 : nsHTMLButtonControlFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
95 : const nsRect& aDirtyRect,
96 : const nsDisplayListSet& aLists)
97 : {
98 : // Clip to our border area for event hit testing.
99 0 : Maybe<DisplayListClipState::AutoSaveRestore> eventClipState;
100 0 : const bool isForEventDelivery = aBuilder->IsForEventDelivery();
101 0 : if (isForEventDelivery) {
102 0 : eventClipState.emplace(aBuilder);
103 0 : nsRect rect(aBuilder->ToReferenceFrame(this), GetSize());
104 : nscoord radii[8];
105 0 : bool hasRadii = GetBorderRadii(radii);
106 0 : eventClipState->ClipContainingBlockDescendants(rect, hasRadii ? radii : nullptr);
107 : }
108 :
109 0 : nsDisplayList onTop;
110 0 : if (IsVisibleForPainting(aBuilder)) {
111 0 : mRenderer.DisplayButton(aBuilder, aLists.BorderBackground(), &onTop);
112 : }
113 :
114 0 : nsDisplayListCollection set;
115 :
116 : // Do not allow the child subtree to receive events.
117 0 : if (!isForEventDelivery) {
118 0 : DisplayListClipState::AutoSaveRestore clipState(aBuilder);
119 :
120 0 : if (ShouldClipPaintingToBorderBox()) {
121 0 : nsMargin border = StyleBorder()->GetComputedBorder();
122 0 : nsRect rect(aBuilder->ToReferenceFrame(this), GetSize());
123 0 : rect.Deflate(border);
124 : nscoord radii[8];
125 0 : bool hasRadii = GetPaddingBoxBorderRadii(radii);
126 0 : clipState.ClipContainingBlockDescendants(rect, hasRadii ? radii : nullptr);
127 : }
128 :
129 0 : BuildDisplayListForChild(aBuilder, mFrames.FirstChild(), aDirtyRect, set,
130 0 : DISPLAY_CHILD_FORCE_PSEUDO_STACKING_CONTEXT);
131 : // That should put the display items in set.Content()
132 : }
133 :
134 : // Put the foreground outline and focus rects on top of the children
135 0 : set.Content()->AppendToTop(&onTop);
136 0 : set.MoveTo(aLists);
137 :
138 0 : DisplayOutline(aBuilder, aLists);
139 :
140 : // to draw border when selected in editor
141 0 : DisplaySelectionOverlay(aBuilder, aLists.Content());
142 0 : }
143 :
144 : nscoord
145 0 : nsHTMLButtonControlFrame::GetMinISize(gfxContext* aRenderingContext)
146 : {
147 : nscoord result;
148 0 : DISPLAY_MIN_WIDTH(this, result);
149 :
150 0 : nsIFrame* kid = mFrames.FirstChild();
151 0 : result = nsLayoutUtils::IntrinsicForContainer(aRenderingContext,
152 : kid,
153 : nsLayoutUtils::MIN_ISIZE);
154 :
155 0 : return result;
156 : }
157 :
158 : nscoord
159 0 : nsHTMLButtonControlFrame::GetPrefISize(gfxContext* aRenderingContext)
160 : {
161 : nscoord result;
162 0 : DISPLAY_PREF_WIDTH(this, result);
163 :
164 0 : nsIFrame* kid = mFrames.FirstChild();
165 0 : result = nsLayoutUtils::IntrinsicForContainer(aRenderingContext,
166 : kid,
167 : nsLayoutUtils::PREF_ISIZE);
168 :
169 0 : return result;
170 : }
171 :
172 : void
173 0 : nsHTMLButtonControlFrame::Reflow(nsPresContext* aPresContext,
174 : ReflowOutput& aDesiredSize,
175 : const ReflowInput& aReflowInput,
176 : nsReflowStatus& aStatus)
177 : {
178 0 : MarkInReflow();
179 0 : DO_GLOBAL_REFLOW_COUNT("nsHTMLButtonControlFrame");
180 0 : DISPLAY_REFLOW(aPresContext, this, aReflowInput, aDesiredSize, aStatus);
181 :
182 0 : if (mState & NS_FRAME_FIRST_REFLOW) {
183 0 : nsFormControlFrame::RegUnRegAccessKey(static_cast<nsIFrame*>(this), true);
184 : }
185 :
186 : // Reflow the child
187 0 : nsIFrame* firstKid = mFrames.FirstChild();
188 :
189 0 : MOZ_ASSERT(firstKid, "Button should have a child frame for its contents");
190 0 : MOZ_ASSERT(!firstKid->GetNextSibling(),
191 : "Button should have exactly one child frame");
192 0 : MOZ_ASSERT(firstKid->StyleContext()->GetPseudo() ==
193 : nsCSSAnonBoxes::buttonContent,
194 : "Button's child frame has unexpected pseudo type!");
195 :
196 : // XXXbz Eventually we may want to check-and-bail if
197 : // !aReflowInput.ShouldReflowAllKids() &&
198 : // !NS_SUBTREE_DIRTY(firstKid).
199 : // We'd need to cache our ascent for that, of course.
200 :
201 : // Reflow the contents of the button.
202 : // (This populates our aDesiredSize, too.)
203 : ReflowButtonContents(aPresContext, aDesiredSize,
204 0 : aReflowInput, firstKid);
205 :
206 0 : if (!ShouldClipPaintingToBorderBox()) {
207 0 : ConsiderChildOverflow(aDesiredSize.mOverflowAreas, firstKid);
208 : }
209 : // else, we ignore child overflow -- anything that overflows beyond our
210 : // own border-box will get clipped when painting.
211 :
212 0 : aStatus.Reset();
213 0 : FinishReflowWithAbsoluteFrames(aPresContext, aDesiredSize,
214 0 : aReflowInput, aStatus);
215 :
216 : // We're always complete and we don't support overflow containers
217 : // so we shouldn't have a next-in-flow ever.
218 0 : aStatus.Reset();
219 0 : MOZ_ASSERT(!GetNextInFlow());
220 :
221 0 : NS_FRAME_SET_TRUNCATION(aStatus, aReflowInput, aDesiredSize);
222 0 : }
223 :
224 : void
225 0 : nsHTMLButtonControlFrame::ReflowButtonContents(nsPresContext* aPresContext,
226 : ReflowOutput& aButtonDesiredSize,
227 : const ReflowInput& aButtonReflowInput,
228 : nsIFrame* aFirstKid)
229 : {
230 0 : WritingMode wm = GetWritingMode();
231 0 : LogicalSize availSize = aButtonReflowInput.ComputedSize(wm);
232 0 : availSize.BSize(wm) = NS_INTRINSICSIZE;
233 :
234 : // shorthand for a value we need to use in a bunch of places
235 0 : const LogicalMargin& clbp = aButtonReflowInput.ComputedLogicalBorderPadding();
236 :
237 0 : LogicalPoint childPos(wm);
238 0 : childPos.I(wm) = clbp.IStart(wm);
239 0 : availSize.ISize(wm) = std::max(availSize.ISize(wm), 0);
240 :
241 : ReflowInput contentsReflowInput(aPresContext, aButtonReflowInput,
242 0 : aFirstKid, availSize);
243 :
244 0 : nsReflowStatus contentsReflowStatus;
245 0 : ReflowOutput contentsDesiredSize(aButtonReflowInput);
246 0 : childPos.B(wm) = 0; // This will be set properly later, after reflowing the
247 : // child to determine its size.
248 :
249 : // We just pass a dummy containerSize here, as the child will be
250 : // repositioned later by FinishReflowChild.
251 0 : nsSize dummyContainerSize;
252 0 : ReflowChild(aFirstKid, aPresContext,
253 : contentsDesiredSize, contentsReflowInput,
254 0 : wm, childPos, dummyContainerSize, 0, contentsReflowStatus);
255 0 : MOZ_ASSERT(contentsReflowStatus.IsComplete(),
256 : "We gave button-contents frame unconstrained available height, "
257 : "so it should be complete");
258 :
259 : // Compute the button's content-box size:
260 0 : LogicalSize buttonContentBox(wm);
261 0 : if (aButtonReflowInput.ComputedBSize() != NS_INTRINSICSIZE) {
262 : // Button has a fixed block-size -- that's its content-box bSize.
263 0 : buttonContentBox.BSize(wm) = aButtonReflowInput.ComputedBSize();
264 : } else {
265 : // Button is intrinsically sized -- it should shrinkwrap the
266 : // button-contents' bSize:
267 0 : buttonContentBox.BSize(wm) = contentsDesiredSize.BSize(wm);
268 :
269 : // Make sure we obey min/max-bSize in the case when we're doing intrinsic
270 : // sizing (we get it for free when we have a non-intrinsic
271 : // aButtonReflowInput.ComputedBSize()). Note that we do this before
272 : // adjusting for borderpadding, since mComputedMaxBSize and
273 : // mComputedMinBSize are content bSizes.
274 0 : buttonContentBox.BSize(wm) =
275 0 : NS_CSS_MINMAX(buttonContentBox.BSize(wm),
276 : aButtonReflowInput.ComputedMinBSize(),
277 : aButtonReflowInput.ComputedMaxBSize());
278 : }
279 0 : if (aButtonReflowInput.ComputedISize() != NS_INTRINSICSIZE) {
280 0 : buttonContentBox.ISize(wm) = aButtonReflowInput.ComputedISize();
281 : } else {
282 0 : buttonContentBox.ISize(wm) = contentsDesiredSize.ISize(wm);
283 0 : buttonContentBox.ISize(wm) =
284 0 : NS_CSS_MINMAX(buttonContentBox.ISize(wm),
285 : aButtonReflowInput.ComputedMinISize(),
286 : aButtonReflowInput.ComputedMaxISize());
287 : }
288 :
289 : // Center child in the block-direction in the button
290 : // (technically, inside of the button's focus-padding area)
291 0 : nscoord extraSpace = buttonContentBox.BSize(wm) -
292 0 : contentsDesiredSize.BSize(wm);
293 :
294 0 : childPos.B(wm) = std::max(0, extraSpace / 2);
295 :
296 : // Adjust childPos.B() to be in terms of the button's frame-rect:
297 0 : childPos.B(wm) += clbp.BStart(wm);
298 :
299 : nsSize containerSize =
300 0 : (buttonContentBox + clbp.Size(wm)).GetPhysicalSize(wm);
301 :
302 : // Place the child
303 : FinishReflowChild(aFirstKid, aPresContext,
304 : contentsDesiredSize, &contentsReflowInput,
305 0 : wm, childPos, containerSize, 0);
306 :
307 : // Make sure we have a useful 'ascent' value for the child
308 0 : if (contentsDesiredSize.BlockStartAscent() ==
309 : ReflowOutput::ASK_FOR_BASELINE) {
310 0 : WritingMode wm = aButtonReflowInput.GetWritingMode();
311 0 : contentsDesiredSize.SetBlockStartAscent(aFirstKid->GetLogicalBaseline(wm));
312 : }
313 :
314 : // OK, we're done with the child frame.
315 : // Use what we learned to populate the button frame's reflow metrics.
316 : // * Button's height & width are content-box size + border-box contribution:
317 0 : aButtonDesiredSize.SetSize(wm,
318 0 : LogicalSize(wm, aButtonReflowInput.ComputedISize() + clbp.IStartEnd(wm),
319 0 : buttonContentBox.BSize(wm) + clbp.BStartEnd(wm)));
320 :
321 : // * Button's ascent is its child's ascent, plus the child's block-offset
322 : // within our frame... unless it's orthogonal, in which case we'll use the
323 : // contents inline-size as an approximation for now.
324 : // XXX is there a better strategy? should we include border-padding?
325 0 : if (aButtonDesiredSize.GetWritingMode().IsOrthogonalTo(wm)) {
326 0 : aButtonDesiredSize.SetBlockStartAscent(contentsDesiredSize.ISize(wm));
327 : } else {
328 0 : aButtonDesiredSize.SetBlockStartAscent(contentsDesiredSize.BlockStartAscent() +
329 0 : childPos.B(wm));
330 : }
331 :
332 0 : aButtonDesiredSize.SetOverflowAreasToDesiredBounds();
333 0 : }
334 :
335 : bool
336 0 : nsHTMLButtonControlFrame::GetVerticalAlignBaseline(mozilla::WritingMode aWM,
337 : nscoord* aBaseline) const
338 : {
339 0 : nsIFrame* inner = mFrames.FirstChild();
340 0 : if (MOZ_UNLIKELY(inner->GetWritingMode().IsOrthogonalTo(aWM))) {
341 0 : return false;
342 : }
343 0 : if (!inner->GetVerticalAlignBaseline(aWM, aBaseline)) {
344 : // <input type=color> has an empty block frame as inner frame
345 0 : *aBaseline = inner->
346 0 : SynthesizeBaselineBOffsetFromBorderBox(aWM, BaselineSharingGroup::eFirst);
347 : }
348 0 : nscoord innerBStart = inner->BStart(aWM, GetSize());
349 0 : *aBaseline += innerBStart;
350 0 : return true;
351 : }
352 :
353 : bool
354 0 : nsHTMLButtonControlFrame::GetNaturalBaselineBOffset(mozilla::WritingMode aWM,
355 : BaselineSharingGroup aBaselineGroup,
356 : nscoord* aBaseline) const
357 : {
358 0 : nsIFrame* inner = mFrames.FirstChild();
359 0 : if (MOZ_UNLIKELY(inner->GetWritingMode().IsOrthogonalTo(aWM))) {
360 0 : return false;
361 : }
362 0 : if (!inner->GetNaturalBaselineBOffset(aWM, aBaselineGroup, aBaseline)) {
363 : // <input type=color> has an empty block frame as inner frame
364 0 : *aBaseline = inner->
365 0 : SynthesizeBaselineBOffsetFromBorderBox(aWM, aBaselineGroup);
366 : }
367 0 : nscoord innerBStart = inner->BStart(aWM, GetSize());
368 0 : if (aBaselineGroup == BaselineSharingGroup::eFirst) {
369 0 : *aBaseline += innerBStart;
370 : } else {
371 0 : *aBaseline += BSize(aWM) - (innerBStart + inner->BSize(aWM));
372 : }
373 0 : return true;
374 : }
375 :
376 0 : nsresult nsHTMLButtonControlFrame::SetFormProperty(nsIAtom* aName, const nsAString& aValue)
377 : {
378 0 : if (nsGkAtoms::value == aName) {
379 0 : return mContent->SetAttr(kNameSpaceID_None, nsGkAtoms::value,
380 0 : aValue, true);
381 : }
382 0 : return NS_OK;
383 : }
384 :
385 : nsStyleContext*
386 0 : nsHTMLButtonControlFrame::GetAdditionalStyleContext(int32_t aIndex) const
387 : {
388 0 : return mRenderer.GetStyleContext(aIndex);
389 : }
390 :
391 : void
392 0 : nsHTMLButtonControlFrame::SetAdditionalStyleContext(int32_t aIndex,
393 : nsStyleContext* aStyleContext)
394 : {
395 0 : mRenderer.SetStyleContext(aIndex, aStyleContext);
396 0 : }
397 :
398 : void
399 0 : nsHTMLButtonControlFrame::AppendDirectlyOwnedAnonBoxes(nsTArray<OwnedAnonBox>& aResult)
400 : {
401 0 : MOZ_ASSERT(mFrames.FirstChild(), "Must have our button-content anon box");
402 0 : MOZ_ASSERT(!mFrames.FirstChild()->GetNextSibling(),
403 : "Must only have our button-content anon box");
404 0 : aResult.AppendElement(OwnedAnonBox(mFrames.FirstChild()));
405 0 : }
406 :
407 : #ifdef DEBUG
408 : void
409 0 : nsHTMLButtonControlFrame::AppendFrames(ChildListID aListID,
410 : nsFrameList& aFrameList)
411 : {
412 0 : MOZ_CRASH("unsupported operation");
413 : }
414 :
415 : void
416 0 : nsHTMLButtonControlFrame::InsertFrames(ChildListID aListID,
417 : nsIFrame* aPrevFrame,
418 : nsFrameList& aFrameList)
419 : {
420 0 : MOZ_CRASH("unsupported operation");
421 : }
422 :
423 : void
424 0 : nsHTMLButtonControlFrame::RemoveFrame(ChildListID aListID,
425 : nsIFrame* aOldFrame)
426 : {
427 0 : MOZ_CRASH("unsupported operation");
428 : }
429 : #endif
|