|
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 } |