michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "nsHTMLButtonControlFrame.h" michael@0: michael@0: #include "nsContainerFrame.h" michael@0: #include "nsIFormControlFrame.h" michael@0: #include "nsPresContext.h" michael@0: #include "nsGkAtoms.h" michael@0: #include "nsButtonFrameRenderer.h" michael@0: #include "nsCSSAnonBoxes.h" michael@0: #include "nsFormControlFrame.h" michael@0: #include "nsNameSpaceManager.h" michael@0: #include "nsStyleSet.h" michael@0: #include "nsDisplayList.h" michael@0: #include michael@0: michael@0: using namespace mozilla; michael@0: michael@0: nsIFrame* michael@0: NS_NewHTMLButtonControlFrame(nsIPresShell* aPresShell, nsStyleContext* aContext) michael@0: { michael@0: return new (aPresShell) nsHTMLButtonControlFrame(aContext); michael@0: } michael@0: michael@0: NS_IMPL_FRAMEARENA_HELPERS(nsHTMLButtonControlFrame) michael@0: michael@0: nsHTMLButtonControlFrame::nsHTMLButtonControlFrame(nsStyleContext* aContext) michael@0: : nsContainerFrame(aContext) michael@0: { michael@0: } michael@0: michael@0: nsHTMLButtonControlFrame::~nsHTMLButtonControlFrame() michael@0: { michael@0: } michael@0: michael@0: void michael@0: nsHTMLButtonControlFrame::DestroyFrom(nsIFrame* aDestructRoot) michael@0: { michael@0: nsFormControlFrame::RegUnRegAccessKey(static_cast(this), false); michael@0: nsContainerFrame::DestroyFrom(aDestructRoot); michael@0: } michael@0: michael@0: void michael@0: nsHTMLButtonControlFrame::Init( michael@0: nsIContent* aContent, michael@0: nsIFrame* aParent, michael@0: nsIFrame* aPrevInFlow) michael@0: { michael@0: nsContainerFrame::Init(aContent, aParent, aPrevInFlow); michael@0: mRenderer.SetFrame(this, PresContext()); michael@0: } michael@0: michael@0: NS_QUERYFRAME_HEAD(nsHTMLButtonControlFrame) michael@0: NS_QUERYFRAME_ENTRY(nsIFormControlFrame) michael@0: NS_QUERYFRAME_TAIL_INHERITING(nsContainerFrame) michael@0: michael@0: #ifdef ACCESSIBILITY michael@0: a11y::AccType michael@0: nsHTMLButtonControlFrame::AccessibleType() michael@0: { michael@0: return a11y::eHTMLButtonType; michael@0: } michael@0: #endif michael@0: michael@0: nsIAtom* michael@0: nsHTMLButtonControlFrame::GetType() const michael@0: { michael@0: return nsGkAtoms::HTMLButtonControlFrame; michael@0: } michael@0: michael@0: void michael@0: nsHTMLButtonControlFrame::SetFocus(bool aOn, bool aRepaint) michael@0: { michael@0: } michael@0: michael@0: nsresult michael@0: nsHTMLButtonControlFrame::HandleEvent(nsPresContext* aPresContext, michael@0: WidgetGUIEvent* aEvent, michael@0: nsEventStatus* aEventStatus) michael@0: { michael@0: // if disabled do nothing michael@0: if (mRenderer.isDisabled()) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: // mouse clicks are handled by content michael@0: // we don't want our children to get any events. So just pass it to frame. michael@0: return nsFrame::HandleEvent(aPresContext, aEvent, aEventStatus); michael@0: } michael@0: michael@0: michael@0: void michael@0: nsHTMLButtonControlFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder, michael@0: const nsRect& aDirtyRect, michael@0: const nsDisplayListSet& aLists) michael@0: { michael@0: nsDisplayList onTop; michael@0: if (IsVisibleForPainting(aBuilder)) { michael@0: mRenderer.DisplayButton(aBuilder, aLists.BorderBackground(), &onTop); michael@0: } michael@0: michael@0: nsDisplayListCollection set; michael@0: michael@0: // Do not allow the child subtree to receive events. michael@0: if (!aBuilder->IsForEventDelivery()) { michael@0: DisplayListClipState::AutoSaveRestore clipState(aBuilder); michael@0: michael@0: if (IsInput() || StyleDisplay()->mOverflowX != NS_STYLE_OVERFLOW_VISIBLE) { michael@0: nsMargin border = StyleBorder()->GetComputedBorder(); michael@0: nsRect rect(aBuilder->ToReferenceFrame(this), GetSize()); michael@0: rect.Deflate(border); michael@0: nscoord radii[8]; michael@0: bool hasRadii = GetPaddingBoxBorderRadii(radii); michael@0: clipState.ClipContainingBlockDescendants(rect, hasRadii ? radii : nullptr); michael@0: } michael@0: michael@0: BuildDisplayListForChild(aBuilder, mFrames.FirstChild(), aDirtyRect, set, michael@0: DISPLAY_CHILD_FORCE_PSEUDO_STACKING_CONTEXT); michael@0: // That should put the display items in set.Content() michael@0: } michael@0: michael@0: // Put the foreground outline and focus rects on top of the children michael@0: set.Content()->AppendToTop(&onTop); michael@0: set.MoveTo(aLists); michael@0: michael@0: DisplayOutline(aBuilder, aLists); michael@0: michael@0: // to draw border when selected in editor michael@0: DisplaySelectionOverlay(aBuilder, aLists.Content()); michael@0: } michael@0: michael@0: nscoord michael@0: nsHTMLButtonControlFrame::GetMinWidth(nsRenderingContext* aRenderingContext) michael@0: { michael@0: nscoord result; michael@0: DISPLAY_MIN_WIDTH(this, result); michael@0: michael@0: nsIFrame* kid = mFrames.FirstChild(); michael@0: result = nsLayoutUtils::IntrinsicForContainer(aRenderingContext, michael@0: kid, michael@0: nsLayoutUtils::MIN_WIDTH); michael@0: michael@0: result += mRenderer.GetAddedButtonBorderAndPadding().LeftRight(); michael@0: michael@0: return result; michael@0: } michael@0: michael@0: nscoord michael@0: nsHTMLButtonControlFrame::GetPrefWidth(nsRenderingContext* aRenderingContext) michael@0: { michael@0: nscoord result; michael@0: DISPLAY_PREF_WIDTH(this, result); michael@0: michael@0: nsIFrame* kid = mFrames.FirstChild(); michael@0: result = nsLayoutUtils::IntrinsicForContainer(aRenderingContext, michael@0: kid, michael@0: nsLayoutUtils::PREF_WIDTH); michael@0: result += mRenderer.GetAddedButtonBorderAndPadding().LeftRight(); michael@0: return result; michael@0: } michael@0: michael@0: nsresult michael@0: nsHTMLButtonControlFrame::Reflow(nsPresContext* aPresContext, michael@0: nsHTMLReflowMetrics& aDesiredSize, michael@0: const nsHTMLReflowState& aReflowState, michael@0: nsReflowStatus& aStatus) michael@0: { michael@0: DO_GLOBAL_REFLOW_COUNT("nsHTMLButtonControlFrame"); michael@0: DISPLAY_REFLOW(aPresContext, this, aReflowState, aDesiredSize, aStatus); michael@0: michael@0: NS_PRECONDITION(aReflowState.ComputedWidth() != NS_INTRINSICSIZE, michael@0: "Should have real computed width by now"); michael@0: michael@0: if (mState & NS_FRAME_FIRST_REFLOW) { michael@0: nsFormControlFrame::RegUnRegAccessKey(static_cast(this), true); michael@0: } michael@0: michael@0: // Reflow the child michael@0: nsIFrame* firstKid = mFrames.FirstChild(); michael@0: michael@0: MOZ_ASSERT(firstKid, "Button should have a child frame for its contents"); michael@0: MOZ_ASSERT(!firstKid->GetNextSibling(), michael@0: "Button should have exactly one child frame"); michael@0: MOZ_ASSERT(firstKid->StyleContext()->GetPseudo() == michael@0: nsCSSAnonBoxes::buttonContent, michael@0: "Button's child frame has unexpected pseudo type!"); michael@0: michael@0: // XXXbz Eventually we may want to check-and-bail if michael@0: // !aReflowState.ShouldReflowAllKids() && michael@0: // !NS_SUBTREE_DIRTY(firstKid). michael@0: // We'd need to cache our ascent for that, of course. michael@0: michael@0: // Reflow the contents of the button. michael@0: // (This populates our aDesiredSize, too.) michael@0: ReflowButtonContents(aPresContext, aDesiredSize, michael@0: aReflowState, firstKid); michael@0: michael@0: ConsiderChildOverflow(aDesiredSize.mOverflowAreas, firstKid); michael@0: michael@0: aStatus = NS_FRAME_COMPLETE; michael@0: FinishReflowWithAbsoluteFrames(aPresContext, aDesiredSize, michael@0: aReflowState, aStatus); michael@0: michael@0: NS_FRAME_SET_TRUNCATION(aStatus, aReflowState, aDesiredSize); michael@0: return NS_OK; michael@0: } michael@0: michael@0: // Helper-function that lets us clone the button's reflow state, but with its michael@0: // ComputedWidth and ComputedHeight reduced by the amount of renderer-specific michael@0: // focus border and padding that we're using. (This lets us provide a more michael@0: // appropriate content-box size for descendents' percent sizes to resolve michael@0: // against.) michael@0: static nsHTMLReflowState michael@0: CloneReflowStateWithReducedContentBox( michael@0: const nsHTMLReflowState& aButtonReflowState, michael@0: const nsMargin& aFocusPadding) michael@0: { michael@0: nscoord adjustedWidth = michael@0: aButtonReflowState.ComputedWidth() - aFocusPadding.LeftRight(); michael@0: adjustedWidth = std::max(0, adjustedWidth); michael@0: michael@0: // (Only adjust height if it's an actual length.) michael@0: nscoord adjustedHeight = aButtonReflowState.ComputedHeight(); michael@0: if (adjustedHeight != NS_INTRINSICSIZE) { michael@0: adjustedHeight -= aFocusPadding.TopBottom(); michael@0: adjustedHeight = std::max(0, adjustedHeight); michael@0: } michael@0: michael@0: nsHTMLReflowState clone(aButtonReflowState); michael@0: clone.SetComputedWidth(adjustedWidth); michael@0: clone.SetComputedHeight(adjustedHeight); michael@0: michael@0: return clone; michael@0: } michael@0: michael@0: void michael@0: nsHTMLButtonControlFrame::ReflowButtonContents(nsPresContext* aPresContext, michael@0: nsHTMLReflowMetrics& aButtonDesiredSize, michael@0: const nsHTMLReflowState& aButtonReflowState, michael@0: nsIFrame* aFirstKid) michael@0: { michael@0: // Buttons have some bonus renderer-determined border/padding, michael@0: // which occupies part of the button's content-box area: michael@0: const nsMargin focusPadding = mRenderer.GetAddedButtonBorderAndPadding(); michael@0: michael@0: nsSize availSize(aButtonReflowState.ComputedWidth(), NS_INTRINSICSIZE); michael@0: michael@0: // Indent the child inside us by the focus border. We must do this separate michael@0: // from the regular border. michael@0: availSize.width -= focusPadding.LeftRight(); michael@0: michael@0: // See whether out availSize's width is big enough. If it's smaller than our michael@0: // intrinsic min width, that means that the kid wouldn't really fit; for a michael@0: // better look in such cases we adjust the available width and our left michael@0: // offset to allow the kid to spill left into our padding. michael@0: nscoord xoffset = focusPadding.left + michael@0: aButtonReflowState.ComputedPhysicalBorderPadding().left; michael@0: nscoord extrawidth = GetMinWidth(aButtonReflowState.rendContext) - michael@0: aButtonReflowState.ComputedWidth(); michael@0: if (extrawidth > 0) { michael@0: nscoord extraleft = extrawidth / 2; michael@0: nscoord extraright = extrawidth - extraleft; michael@0: NS_ASSERTION(extraright >=0, "How'd that happen?"); michael@0: michael@0: // Do not allow the extras to be bigger than the relevant padding michael@0: extraleft = std::min(extraleft, aButtonReflowState.ComputedPhysicalPadding().left); michael@0: extraright = std::min(extraright, aButtonReflowState.ComputedPhysicalPadding().right); michael@0: xoffset -= extraleft; michael@0: availSize.width += extraleft + extraright; michael@0: } michael@0: availSize.width = std::max(availSize.width,0); michael@0: michael@0: // Give child a clone of the button's reflow state, with height/width reduced michael@0: // by focusPadding, so that descendants with height:100% don't protrude. michael@0: nsHTMLReflowState adjustedButtonReflowState = michael@0: CloneReflowStateWithReducedContentBox(aButtonReflowState, focusPadding); michael@0: michael@0: nsHTMLReflowState contentsReflowState(aPresContext, michael@0: adjustedButtonReflowState, michael@0: aFirstKid, availSize); michael@0: michael@0: nsReflowStatus contentsReflowStatus; michael@0: nsHTMLReflowMetrics contentsDesiredSize(aButtonReflowState); michael@0: ReflowChild(aFirstKid, aPresContext, michael@0: contentsDesiredSize, contentsReflowState, michael@0: xoffset, michael@0: focusPadding.top + aButtonReflowState.ComputedPhysicalBorderPadding().top, michael@0: 0, contentsReflowStatus); michael@0: MOZ_ASSERT(NS_FRAME_IS_COMPLETE(contentsReflowStatus), michael@0: "We gave button-contents frame unconstrained available height, " michael@0: "so it should be complete"); michael@0: michael@0: // Compute the button's content-box height: michael@0: nscoord buttonContentBoxHeight = 0; michael@0: if (aButtonReflowState.ComputedHeight() != NS_INTRINSICSIZE) { michael@0: // Button has a fixed height -- that's its content-box height. michael@0: buttonContentBoxHeight = aButtonReflowState.ComputedHeight(); michael@0: } else { michael@0: // Button is intrinsically sized -- it should shrinkwrap the michael@0: // button-contents' height, plus any focus-padding space: michael@0: buttonContentBoxHeight = michael@0: contentsDesiredSize.Height() + focusPadding.TopBottom(); michael@0: michael@0: // Make sure we obey min/max-height in the case when we're doing intrinsic michael@0: // sizing (we get it for free when we have a non-intrinsic michael@0: // aButtonReflowState.ComputedHeight()). Note that we do this before michael@0: // adjusting for borderpadding, since mComputedMaxHeight and michael@0: // mComputedMinHeight are content heights. michael@0: buttonContentBoxHeight = michael@0: NS_CSS_MINMAX(buttonContentBoxHeight, michael@0: aButtonReflowState.ComputedMinHeight(), michael@0: aButtonReflowState.ComputedMaxHeight()); michael@0: } michael@0: michael@0: // Center child vertically in the button michael@0: // (technically, inside of the button's focus-padding area) michael@0: nscoord extraSpace = michael@0: buttonContentBoxHeight - focusPadding.TopBottom() - michael@0: contentsDesiredSize.Height(); michael@0: michael@0: nscoord yoffset = std::max(0, extraSpace / 2); michael@0: michael@0: // Adjust yoffset to be in terms of the button's frame-rect, instead of michael@0: // its focus-padding rect: michael@0: yoffset += focusPadding.top + aButtonReflowState.ComputedPhysicalBorderPadding().top; michael@0: michael@0: // Place the child michael@0: FinishReflowChild(aFirstKid, aPresContext, michael@0: contentsDesiredSize, &contentsReflowState, michael@0: xoffset, yoffset, 0); michael@0: michael@0: // Make sure we have a useful 'ascent' value for the child michael@0: if (contentsDesiredSize.TopAscent() == nsHTMLReflowMetrics::ASK_FOR_BASELINE) { michael@0: contentsDesiredSize.SetTopAscent(aFirstKid->GetBaseline()); michael@0: } michael@0: michael@0: // OK, we're done with the child frame. michael@0: // Use what we learned to populate the button frame's reflow metrics. michael@0: // * Button's height & width are content-box size + border-box contribution: michael@0: aButtonDesiredSize.Width() = aButtonReflowState.ComputedWidth() + michael@0: aButtonReflowState.ComputedPhysicalBorderPadding().LeftRight(); michael@0: michael@0: aButtonDesiredSize.Height() = buttonContentBoxHeight + michael@0: aButtonReflowState.ComputedPhysicalBorderPadding().TopBottom(); michael@0: michael@0: // * Button's ascent is its child's ascent, plus the child's y-offset michael@0: // within our frame: michael@0: aButtonDesiredSize.SetTopAscent(contentsDesiredSize.TopAscent() + yoffset); michael@0: michael@0: aButtonDesiredSize.SetOverflowAreasToDesiredBounds(); michael@0: } michael@0: michael@0: nsresult nsHTMLButtonControlFrame::SetFormProperty(nsIAtom* aName, const nsAString& aValue) michael@0: { michael@0: if (nsGkAtoms::value == aName) { michael@0: return mContent->SetAttr(kNameSpaceID_None, nsGkAtoms::value, michael@0: aValue, true); michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsStyleContext* michael@0: nsHTMLButtonControlFrame::GetAdditionalStyleContext(int32_t aIndex) const michael@0: { michael@0: return mRenderer.GetStyleContext(aIndex); michael@0: } michael@0: michael@0: void michael@0: nsHTMLButtonControlFrame::SetAdditionalStyleContext(int32_t aIndex, michael@0: nsStyleContext* aStyleContext) michael@0: { michael@0: mRenderer.SetStyleContext(aIndex, aStyleContext); michael@0: } michael@0: michael@0: nsresult michael@0: nsHTMLButtonControlFrame::AppendFrames(ChildListID aListID, michael@0: nsFrameList& aFrameList) michael@0: { michael@0: NS_NOTREACHED("unsupported operation"); michael@0: return NS_ERROR_UNEXPECTED; michael@0: } michael@0: michael@0: nsresult michael@0: nsHTMLButtonControlFrame::InsertFrames(ChildListID aListID, michael@0: nsIFrame* aPrevFrame, michael@0: nsFrameList& aFrameList) michael@0: { michael@0: NS_NOTREACHED("unsupported operation"); michael@0: return NS_ERROR_UNEXPECTED; michael@0: } michael@0: michael@0: nsresult michael@0: nsHTMLButtonControlFrame::RemoveFrame(ChildListID aListID, michael@0: nsIFrame* aOldFrame) michael@0: { michael@0: NS_NOTREACHED("unsupported operation"); michael@0: return NS_ERROR_UNEXPECTED; michael@0: }