1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/layout/forms/nsHTMLButtonControlFrame.cpp Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,401 @@ 1.4 +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 1.5 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.6 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.7 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.8 + 1.9 +#include "nsHTMLButtonControlFrame.h" 1.10 + 1.11 +#include "nsContainerFrame.h" 1.12 +#include "nsIFormControlFrame.h" 1.13 +#include "nsPresContext.h" 1.14 +#include "nsGkAtoms.h" 1.15 +#include "nsButtonFrameRenderer.h" 1.16 +#include "nsCSSAnonBoxes.h" 1.17 +#include "nsFormControlFrame.h" 1.18 +#include "nsNameSpaceManager.h" 1.19 +#include "nsStyleSet.h" 1.20 +#include "nsDisplayList.h" 1.21 +#include <algorithm> 1.22 + 1.23 +using namespace mozilla; 1.24 + 1.25 +nsIFrame* 1.26 +NS_NewHTMLButtonControlFrame(nsIPresShell* aPresShell, nsStyleContext* aContext) 1.27 +{ 1.28 + return new (aPresShell) nsHTMLButtonControlFrame(aContext); 1.29 +} 1.30 + 1.31 +NS_IMPL_FRAMEARENA_HELPERS(nsHTMLButtonControlFrame) 1.32 + 1.33 +nsHTMLButtonControlFrame::nsHTMLButtonControlFrame(nsStyleContext* aContext) 1.34 + : nsContainerFrame(aContext) 1.35 +{ 1.36 +} 1.37 + 1.38 +nsHTMLButtonControlFrame::~nsHTMLButtonControlFrame() 1.39 +{ 1.40 +} 1.41 + 1.42 +void 1.43 +nsHTMLButtonControlFrame::DestroyFrom(nsIFrame* aDestructRoot) 1.44 +{ 1.45 + nsFormControlFrame::RegUnRegAccessKey(static_cast<nsIFrame*>(this), false); 1.46 + nsContainerFrame::DestroyFrom(aDestructRoot); 1.47 +} 1.48 + 1.49 +void 1.50 +nsHTMLButtonControlFrame::Init( 1.51 + nsIContent* aContent, 1.52 + nsIFrame* aParent, 1.53 + nsIFrame* aPrevInFlow) 1.54 +{ 1.55 + nsContainerFrame::Init(aContent, aParent, aPrevInFlow); 1.56 + mRenderer.SetFrame(this, PresContext()); 1.57 +} 1.58 + 1.59 +NS_QUERYFRAME_HEAD(nsHTMLButtonControlFrame) 1.60 + NS_QUERYFRAME_ENTRY(nsIFormControlFrame) 1.61 +NS_QUERYFRAME_TAIL_INHERITING(nsContainerFrame) 1.62 + 1.63 +#ifdef ACCESSIBILITY 1.64 +a11y::AccType 1.65 +nsHTMLButtonControlFrame::AccessibleType() 1.66 +{ 1.67 + return a11y::eHTMLButtonType; 1.68 +} 1.69 +#endif 1.70 + 1.71 +nsIAtom* 1.72 +nsHTMLButtonControlFrame::GetType() const 1.73 +{ 1.74 + return nsGkAtoms::HTMLButtonControlFrame; 1.75 +} 1.76 + 1.77 +void 1.78 +nsHTMLButtonControlFrame::SetFocus(bool aOn, bool aRepaint) 1.79 +{ 1.80 +} 1.81 + 1.82 +nsresult 1.83 +nsHTMLButtonControlFrame::HandleEvent(nsPresContext* aPresContext, 1.84 + WidgetGUIEvent* aEvent, 1.85 + nsEventStatus* aEventStatus) 1.86 +{ 1.87 + // if disabled do nothing 1.88 + if (mRenderer.isDisabled()) { 1.89 + return NS_OK; 1.90 + } 1.91 + 1.92 + // mouse clicks are handled by content 1.93 + // we don't want our children to get any events. So just pass it to frame. 1.94 + return nsFrame::HandleEvent(aPresContext, aEvent, aEventStatus); 1.95 +} 1.96 + 1.97 + 1.98 +void 1.99 +nsHTMLButtonControlFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder, 1.100 + const nsRect& aDirtyRect, 1.101 + const nsDisplayListSet& aLists) 1.102 +{ 1.103 + nsDisplayList onTop; 1.104 + if (IsVisibleForPainting(aBuilder)) { 1.105 + mRenderer.DisplayButton(aBuilder, aLists.BorderBackground(), &onTop); 1.106 + } 1.107 + 1.108 + nsDisplayListCollection set; 1.109 + 1.110 + // Do not allow the child subtree to receive events. 1.111 + if (!aBuilder->IsForEventDelivery()) { 1.112 + DisplayListClipState::AutoSaveRestore clipState(aBuilder); 1.113 + 1.114 + if (IsInput() || StyleDisplay()->mOverflowX != NS_STYLE_OVERFLOW_VISIBLE) { 1.115 + nsMargin border = StyleBorder()->GetComputedBorder(); 1.116 + nsRect rect(aBuilder->ToReferenceFrame(this), GetSize()); 1.117 + rect.Deflate(border); 1.118 + nscoord radii[8]; 1.119 + bool hasRadii = GetPaddingBoxBorderRadii(radii); 1.120 + clipState.ClipContainingBlockDescendants(rect, hasRadii ? radii : nullptr); 1.121 + } 1.122 + 1.123 + BuildDisplayListForChild(aBuilder, mFrames.FirstChild(), aDirtyRect, set, 1.124 + DISPLAY_CHILD_FORCE_PSEUDO_STACKING_CONTEXT); 1.125 + // That should put the display items in set.Content() 1.126 + } 1.127 + 1.128 + // Put the foreground outline and focus rects on top of the children 1.129 + set.Content()->AppendToTop(&onTop); 1.130 + set.MoveTo(aLists); 1.131 + 1.132 + DisplayOutline(aBuilder, aLists); 1.133 + 1.134 + // to draw border when selected in editor 1.135 + DisplaySelectionOverlay(aBuilder, aLists.Content()); 1.136 +} 1.137 + 1.138 +nscoord 1.139 +nsHTMLButtonControlFrame::GetMinWidth(nsRenderingContext* aRenderingContext) 1.140 +{ 1.141 + nscoord result; 1.142 + DISPLAY_MIN_WIDTH(this, result); 1.143 + 1.144 + nsIFrame* kid = mFrames.FirstChild(); 1.145 + result = nsLayoutUtils::IntrinsicForContainer(aRenderingContext, 1.146 + kid, 1.147 + nsLayoutUtils::MIN_WIDTH); 1.148 + 1.149 + result += mRenderer.GetAddedButtonBorderAndPadding().LeftRight(); 1.150 + 1.151 + return result; 1.152 +} 1.153 + 1.154 +nscoord 1.155 +nsHTMLButtonControlFrame::GetPrefWidth(nsRenderingContext* aRenderingContext) 1.156 +{ 1.157 + nscoord result; 1.158 + DISPLAY_PREF_WIDTH(this, result); 1.159 + 1.160 + nsIFrame* kid = mFrames.FirstChild(); 1.161 + result = nsLayoutUtils::IntrinsicForContainer(aRenderingContext, 1.162 + kid, 1.163 + nsLayoutUtils::PREF_WIDTH); 1.164 + result += mRenderer.GetAddedButtonBorderAndPadding().LeftRight(); 1.165 + return result; 1.166 +} 1.167 + 1.168 +nsresult 1.169 +nsHTMLButtonControlFrame::Reflow(nsPresContext* aPresContext, 1.170 + nsHTMLReflowMetrics& aDesiredSize, 1.171 + const nsHTMLReflowState& aReflowState, 1.172 + nsReflowStatus& aStatus) 1.173 +{ 1.174 + DO_GLOBAL_REFLOW_COUNT("nsHTMLButtonControlFrame"); 1.175 + DISPLAY_REFLOW(aPresContext, this, aReflowState, aDesiredSize, aStatus); 1.176 + 1.177 + NS_PRECONDITION(aReflowState.ComputedWidth() != NS_INTRINSICSIZE, 1.178 + "Should have real computed width by now"); 1.179 + 1.180 + if (mState & NS_FRAME_FIRST_REFLOW) { 1.181 + nsFormControlFrame::RegUnRegAccessKey(static_cast<nsIFrame*>(this), true); 1.182 + } 1.183 + 1.184 + // Reflow the child 1.185 + nsIFrame* firstKid = mFrames.FirstChild(); 1.186 + 1.187 + MOZ_ASSERT(firstKid, "Button should have a child frame for its contents"); 1.188 + MOZ_ASSERT(!firstKid->GetNextSibling(), 1.189 + "Button should have exactly one child frame"); 1.190 + MOZ_ASSERT(firstKid->StyleContext()->GetPseudo() == 1.191 + nsCSSAnonBoxes::buttonContent, 1.192 + "Button's child frame has unexpected pseudo type!"); 1.193 + 1.194 + // XXXbz Eventually we may want to check-and-bail if 1.195 + // !aReflowState.ShouldReflowAllKids() && 1.196 + // !NS_SUBTREE_DIRTY(firstKid). 1.197 + // We'd need to cache our ascent for that, of course. 1.198 + 1.199 + // Reflow the contents of the button. 1.200 + // (This populates our aDesiredSize, too.) 1.201 + ReflowButtonContents(aPresContext, aDesiredSize, 1.202 + aReflowState, firstKid); 1.203 + 1.204 + ConsiderChildOverflow(aDesiredSize.mOverflowAreas, firstKid); 1.205 + 1.206 + aStatus = NS_FRAME_COMPLETE; 1.207 + FinishReflowWithAbsoluteFrames(aPresContext, aDesiredSize, 1.208 + aReflowState, aStatus); 1.209 + 1.210 + NS_FRAME_SET_TRUNCATION(aStatus, aReflowState, aDesiredSize); 1.211 + return NS_OK; 1.212 +} 1.213 + 1.214 +// Helper-function that lets us clone the button's reflow state, but with its 1.215 +// ComputedWidth and ComputedHeight reduced by the amount of renderer-specific 1.216 +// focus border and padding that we're using. (This lets us provide a more 1.217 +// appropriate content-box size for descendents' percent sizes to resolve 1.218 +// against.) 1.219 +static nsHTMLReflowState 1.220 +CloneReflowStateWithReducedContentBox( 1.221 + const nsHTMLReflowState& aButtonReflowState, 1.222 + const nsMargin& aFocusPadding) 1.223 +{ 1.224 + nscoord adjustedWidth = 1.225 + aButtonReflowState.ComputedWidth() - aFocusPadding.LeftRight(); 1.226 + adjustedWidth = std::max(0, adjustedWidth); 1.227 + 1.228 + // (Only adjust height if it's an actual length.) 1.229 + nscoord adjustedHeight = aButtonReflowState.ComputedHeight(); 1.230 + if (adjustedHeight != NS_INTRINSICSIZE) { 1.231 + adjustedHeight -= aFocusPadding.TopBottom(); 1.232 + adjustedHeight = std::max(0, adjustedHeight); 1.233 + } 1.234 + 1.235 + nsHTMLReflowState clone(aButtonReflowState); 1.236 + clone.SetComputedWidth(adjustedWidth); 1.237 + clone.SetComputedHeight(adjustedHeight); 1.238 + 1.239 + return clone; 1.240 +} 1.241 + 1.242 +void 1.243 +nsHTMLButtonControlFrame::ReflowButtonContents(nsPresContext* aPresContext, 1.244 + nsHTMLReflowMetrics& aButtonDesiredSize, 1.245 + const nsHTMLReflowState& aButtonReflowState, 1.246 + nsIFrame* aFirstKid) 1.247 +{ 1.248 + // Buttons have some bonus renderer-determined border/padding, 1.249 + // which occupies part of the button's content-box area: 1.250 + const nsMargin focusPadding = mRenderer.GetAddedButtonBorderAndPadding(); 1.251 + 1.252 + nsSize availSize(aButtonReflowState.ComputedWidth(), NS_INTRINSICSIZE); 1.253 + 1.254 + // Indent the child inside us by the focus border. We must do this separate 1.255 + // from the regular border. 1.256 + availSize.width -= focusPadding.LeftRight(); 1.257 + 1.258 + // See whether out availSize's width is big enough. If it's smaller than our 1.259 + // intrinsic min width, that means that the kid wouldn't really fit; for a 1.260 + // better look in such cases we adjust the available width and our left 1.261 + // offset to allow the kid to spill left into our padding. 1.262 + nscoord xoffset = focusPadding.left + 1.263 + aButtonReflowState.ComputedPhysicalBorderPadding().left; 1.264 + nscoord extrawidth = GetMinWidth(aButtonReflowState.rendContext) - 1.265 + aButtonReflowState.ComputedWidth(); 1.266 + if (extrawidth > 0) { 1.267 + nscoord extraleft = extrawidth / 2; 1.268 + nscoord extraright = extrawidth - extraleft; 1.269 + NS_ASSERTION(extraright >=0, "How'd that happen?"); 1.270 + 1.271 + // Do not allow the extras to be bigger than the relevant padding 1.272 + extraleft = std::min(extraleft, aButtonReflowState.ComputedPhysicalPadding().left); 1.273 + extraright = std::min(extraright, aButtonReflowState.ComputedPhysicalPadding().right); 1.274 + xoffset -= extraleft; 1.275 + availSize.width += extraleft + extraright; 1.276 + } 1.277 + availSize.width = std::max(availSize.width,0); 1.278 + 1.279 + // Give child a clone of the button's reflow state, with height/width reduced 1.280 + // by focusPadding, so that descendants with height:100% don't protrude. 1.281 + nsHTMLReflowState adjustedButtonReflowState = 1.282 + CloneReflowStateWithReducedContentBox(aButtonReflowState, focusPadding); 1.283 + 1.284 + nsHTMLReflowState contentsReflowState(aPresContext, 1.285 + adjustedButtonReflowState, 1.286 + aFirstKid, availSize); 1.287 + 1.288 + nsReflowStatus contentsReflowStatus; 1.289 + nsHTMLReflowMetrics contentsDesiredSize(aButtonReflowState); 1.290 + ReflowChild(aFirstKid, aPresContext, 1.291 + contentsDesiredSize, contentsReflowState, 1.292 + xoffset, 1.293 + focusPadding.top + aButtonReflowState.ComputedPhysicalBorderPadding().top, 1.294 + 0, contentsReflowStatus); 1.295 + MOZ_ASSERT(NS_FRAME_IS_COMPLETE(contentsReflowStatus), 1.296 + "We gave button-contents frame unconstrained available height, " 1.297 + "so it should be complete"); 1.298 + 1.299 + // Compute the button's content-box height: 1.300 + nscoord buttonContentBoxHeight = 0; 1.301 + if (aButtonReflowState.ComputedHeight() != NS_INTRINSICSIZE) { 1.302 + // Button has a fixed height -- that's its content-box height. 1.303 + buttonContentBoxHeight = aButtonReflowState.ComputedHeight(); 1.304 + } else { 1.305 + // Button is intrinsically sized -- it should shrinkwrap the 1.306 + // button-contents' height, plus any focus-padding space: 1.307 + buttonContentBoxHeight = 1.308 + contentsDesiredSize.Height() + focusPadding.TopBottom(); 1.309 + 1.310 + // Make sure we obey min/max-height in the case when we're doing intrinsic 1.311 + // sizing (we get it for free when we have a non-intrinsic 1.312 + // aButtonReflowState.ComputedHeight()). Note that we do this before 1.313 + // adjusting for borderpadding, since mComputedMaxHeight and 1.314 + // mComputedMinHeight are content heights. 1.315 + buttonContentBoxHeight = 1.316 + NS_CSS_MINMAX(buttonContentBoxHeight, 1.317 + aButtonReflowState.ComputedMinHeight(), 1.318 + aButtonReflowState.ComputedMaxHeight()); 1.319 + } 1.320 + 1.321 + // Center child vertically in the button 1.322 + // (technically, inside of the button's focus-padding area) 1.323 + nscoord extraSpace = 1.324 + buttonContentBoxHeight - focusPadding.TopBottom() - 1.325 + contentsDesiredSize.Height(); 1.326 + 1.327 + nscoord yoffset = std::max(0, extraSpace / 2); 1.328 + 1.329 + // Adjust yoffset to be in terms of the button's frame-rect, instead of 1.330 + // its focus-padding rect: 1.331 + yoffset += focusPadding.top + aButtonReflowState.ComputedPhysicalBorderPadding().top; 1.332 + 1.333 + // Place the child 1.334 + FinishReflowChild(aFirstKid, aPresContext, 1.335 + contentsDesiredSize, &contentsReflowState, 1.336 + xoffset, yoffset, 0); 1.337 + 1.338 + // Make sure we have a useful 'ascent' value for the child 1.339 + if (contentsDesiredSize.TopAscent() == nsHTMLReflowMetrics::ASK_FOR_BASELINE) { 1.340 + contentsDesiredSize.SetTopAscent(aFirstKid->GetBaseline()); 1.341 + } 1.342 + 1.343 + // OK, we're done with the child frame. 1.344 + // Use what we learned to populate the button frame's reflow metrics. 1.345 + // * Button's height & width are content-box size + border-box contribution: 1.346 + aButtonDesiredSize.Width() = aButtonReflowState.ComputedWidth() + 1.347 + aButtonReflowState.ComputedPhysicalBorderPadding().LeftRight(); 1.348 + 1.349 + aButtonDesiredSize.Height() = buttonContentBoxHeight + 1.350 + aButtonReflowState.ComputedPhysicalBorderPadding().TopBottom(); 1.351 + 1.352 + // * Button's ascent is its child's ascent, plus the child's y-offset 1.353 + // within our frame: 1.354 + aButtonDesiredSize.SetTopAscent(contentsDesiredSize.TopAscent() + yoffset); 1.355 + 1.356 + aButtonDesiredSize.SetOverflowAreasToDesiredBounds(); 1.357 +} 1.358 + 1.359 +nsresult nsHTMLButtonControlFrame::SetFormProperty(nsIAtom* aName, const nsAString& aValue) 1.360 +{ 1.361 + if (nsGkAtoms::value == aName) { 1.362 + return mContent->SetAttr(kNameSpaceID_None, nsGkAtoms::value, 1.363 + aValue, true); 1.364 + } 1.365 + return NS_OK; 1.366 +} 1.367 + 1.368 +nsStyleContext* 1.369 +nsHTMLButtonControlFrame::GetAdditionalStyleContext(int32_t aIndex) const 1.370 +{ 1.371 + return mRenderer.GetStyleContext(aIndex); 1.372 +} 1.373 + 1.374 +void 1.375 +nsHTMLButtonControlFrame::SetAdditionalStyleContext(int32_t aIndex, 1.376 + nsStyleContext* aStyleContext) 1.377 +{ 1.378 + mRenderer.SetStyleContext(aIndex, aStyleContext); 1.379 +} 1.380 + 1.381 +nsresult 1.382 +nsHTMLButtonControlFrame::AppendFrames(ChildListID aListID, 1.383 + nsFrameList& aFrameList) 1.384 +{ 1.385 + NS_NOTREACHED("unsupported operation"); 1.386 + return NS_ERROR_UNEXPECTED; 1.387 +} 1.388 + 1.389 +nsresult 1.390 +nsHTMLButtonControlFrame::InsertFrames(ChildListID aListID, 1.391 + nsIFrame* aPrevFrame, 1.392 + nsFrameList& aFrameList) 1.393 +{ 1.394 + NS_NOTREACHED("unsupported operation"); 1.395 + return NS_ERROR_UNEXPECTED; 1.396 +} 1.397 + 1.398 +nsresult 1.399 +nsHTMLButtonControlFrame::RemoveFrame(ChildListID aListID, 1.400 + nsIFrame* aOldFrame) 1.401 +{ 1.402 + NS_NOTREACHED("unsupported operation"); 1.403 + return NS_ERROR_UNEXPECTED; 1.404 +}