123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507 |
- /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
- /* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
- #include "nsHTMLButtonControlFrame.h"
- #include "nsContainerFrame.h"
- #include "nsIFormControlFrame.h"
- #include "nsIFrameInlines.h"
- #include "nsPresContext.h"
- #include "nsGkAtoms.h"
- #include "nsButtonFrameRenderer.h"
- #include "nsCSSAnonBoxes.h"
- #include "nsFormControlFrame.h"
- #include "nsNameSpaceManager.h"
- #include "nsDisplayList.h"
- #include <algorithm>
- using namespace mozilla;
- nsContainerFrame*
- NS_NewHTMLButtonControlFrame(nsIPresShell* aPresShell, nsStyleContext* aContext)
- {
- return new (aPresShell) nsHTMLButtonControlFrame(aContext);
- }
- NS_IMPL_FRAMEARENA_HELPERS(nsHTMLButtonControlFrame)
- nsHTMLButtonControlFrame::nsHTMLButtonControlFrame(nsStyleContext* aContext)
- : nsContainerFrame(aContext)
- {
- }
- nsHTMLButtonControlFrame::~nsHTMLButtonControlFrame()
- {
- }
- void
- nsHTMLButtonControlFrame::DestroyFrom(nsIFrame* aDestructRoot)
- {
- nsFormControlFrame::RegUnRegAccessKey(static_cast<nsIFrame*>(this), false);
- nsContainerFrame::DestroyFrom(aDestructRoot);
- }
- void
- nsHTMLButtonControlFrame::Init(nsIContent* aContent,
- nsContainerFrame* aParent,
- nsIFrame* aPrevInFlow)
- {
- nsContainerFrame::Init(aContent, aParent, aPrevInFlow);
- mRenderer.SetFrame(this, PresContext());
- }
- NS_QUERYFRAME_HEAD(nsHTMLButtonControlFrame)
- NS_QUERYFRAME_ENTRY(nsIFormControlFrame)
- NS_QUERYFRAME_TAIL_INHERITING(nsContainerFrame)
- #ifdef ACCESSIBILITY
- a11y::AccType
- nsHTMLButtonControlFrame::AccessibleType()
- {
- return a11y::eHTMLButtonType;
- }
- #endif
- nsIAtom*
- nsHTMLButtonControlFrame::GetType() const
- {
- return nsGkAtoms::HTMLButtonControlFrame;
- }
- void
- nsHTMLButtonControlFrame::SetFocus(bool aOn, bool aRepaint)
- {
- }
- nsresult
- nsHTMLButtonControlFrame::HandleEvent(nsPresContext* aPresContext,
- WidgetGUIEvent* aEvent,
- nsEventStatus* aEventStatus)
- {
- // if disabled do nothing
- if (mRenderer.isDisabled()) {
- return NS_OK;
- }
- // mouse clicks are handled by content
- // we don't want our children to get any events. So just pass it to frame.
- return nsFrame::HandleEvent(aPresContext, aEvent, aEventStatus);
- }
- bool
- nsHTMLButtonControlFrame::ShouldClipPaintingToBorderBox()
- {
- return IsInput() || StyleDisplay()->mOverflowX != NS_STYLE_OVERFLOW_VISIBLE;
- }
- void
- nsHTMLButtonControlFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
- const nsDisplayListSet& aLists)
- {
- // Clip to our border area for event hit testing.
- Maybe<DisplayListClipState::AutoSaveRestore> eventClipState;
- const bool isForEventDelivery = aBuilder->IsForEventDelivery();
- if (isForEventDelivery) {
- eventClipState.emplace(aBuilder);
- nsRect rect(aBuilder->ToReferenceFrame(this), GetSize());
- nscoord radii[8];
- bool hasRadii = GetBorderRadii(radii);
- eventClipState->ClipContainingBlockDescendants(rect, hasRadii ? radii : nullptr);
- }
- nsDisplayList onTop;
- if (IsVisibleForPainting(aBuilder)) {
- mRenderer.DisplayButton(aBuilder, aLists.BorderBackground(), &onTop);
- }
- nsDisplayListCollection set(aBuilder);
- // Do not allow the child subtree to receive events.
- if (!isForEventDelivery) {
- DisplayListClipState::AutoSaveRestore clipState(aBuilder);
- if (ShouldClipPaintingToBorderBox()) {
- nsMargin border = StyleBorder()->GetComputedBorder();
- nsRect rect(aBuilder->ToReferenceFrame(this), GetSize());
- rect.Deflate(border);
- nscoord radii[8];
- bool hasRadii = GetPaddingBoxBorderRadii(radii);
- clipState.ClipContainingBlockDescendants(rect, hasRadii ? radii : nullptr);
- }
- BuildDisplayListForChild(aBuilder, mFrames.FirstChild(), set,
- DISPLAY_CHILD_FORCE_PSEUDO_STACKING_CONTEXT);
- // That should put the display items in set.Content()
- }
-
- // Put the foreground outline and focus rects on top of the children
- set.Content()->AppendToTop(&onTop);
- set.MoveTo(aLists);
-
- DisplayOutline(aBuilder, aLists);
- // to draw border when selected in editor
- DisplaySelectionOverlay(aBuilder, aLists.Content());
- }
- nscoord
- nsHTMLButtonControlFrame::GetMinISize(nsRenderingContext* aRenderingContext)
- {
- nscoord result;
- DISPLAY_MIN_WIDTH(this, result);
- nsIFrame* kid = mFrames.FirstChild();
- result = nsLayoutUtils::IntrinsicForContainer(aRenderingContext,
- kid,
- nsLayoutUtils::MIN_ISIZE);
- result += GetWritingMode().IsVertical()
- ? mRenderer.GetAddedButtonBorderAndPadding().TopBottom()
- : mRenderer.GetAddedButtonBorderAndPadding().LeftRight();
- return result;
- }
- nscoord
- nsHTMLButtonControlFrame::GetPrefISize(nsRenderingContext* aRenderingContext)
- {
- nscoord result;
- DISPLAY_PREF_WIDTH(this, result);
-
- nsIFrame* kid = mFrames.FirstChild();
- result = nsLayoutUtils::IntrinsicForContainer(aRenderingContext,
- kid,
- nsLayoutUtils::PREF_ISIZE);
- result += GetWritingMode().IsVertical()
- ? mRenderer.GetAddedButtonBorderAndPadding().TopBottom()
- : mRenderer.GetAddedButtonBorderAndPadding().LeftRight();
- return result;
- }
- void
- nsHTMLButtonControlFrame::Reflow(nsPresContext* aPresContext,
- ReflowOutput& aDesiredSize,
- const ReflowInput& aReflowInput,
- nsReflowStatus& aStatus)
- {
- MarkInReflow();
- DO_GLOBAL_REFLOW_COUNT("nsHTMLButtonControlFrame");
- DISPLAY_REFLOW(aPresContext, this, aReflowInput, aDesiredSize, aStatus);
- NS_PRECONDITION(aReflowInput.ComputedISize() != NS_INTRINSICSIZE,
- "Should have real computed inline-size by now");
- if (mState & NS_FRAME_FIRST_REFLOW) {
- nsFormControlFrame::RegUnRegAccessKey(static_cast<nsIFrame*>(this), true);
- }
- // Reflow the child
- nsIFrame* firstKid = mFrames.FirstChild();
- MOZ_ASSERT(firstKid, "Button should have a child frame for its contents");
- MOZ_ASSERT(!firstKid->GetNextSibling(),
- "Button should have exactly one child frame");
- MOZ_ASSERT(firstKid->StyleContext()->GetPseudo() ==
- nsCSSAnonBoxes::buttonContent,
- "Button's child frame has unexpected pseudo type!");
- // XXXbz Eventually we may want to check-and-bail if
- // !aReflowInput.ShouldReflowAllKids() &&
- // !NS_SUBTREE_DIRTY(firstKid).
- // We'd need to cache our ascent for that, of course.
- // Reflow the contents of the button.
- // (This populates our aDesiredSize, too.)
- ReflowButtonContents(aPresContext, aDesiredSize,
- aReflowInput, firstKid);
- if (!ShouldClipPaintingToBorderBox()) {
- ConsiderChildOverflow(aDesiredSize.mOverflowAreas, firstKid);
- }
- // else, we ignore child overflow -- anything that overflows beyond our
- // own border-box will get clipped when painting.
- aStatus = NS_FRAME_COMPLETE;
- FinishReflowWithAbsoluteFrames(aPresContext, aDesiredSize,
- aReflowInput, aStatus);
- // We're always complete and we don't support overflow containers
- // so we shouldn't have a next-in-flow ever.
- aStatus = NS_FRAME_COMPLETE;
- MOZ_ASSERT(!GetNextInFlow());
- NS_FRAME_SET_TRUNCATION(aStatus, aReflowInput, aDesiredSize);
- }
- // Helper-function that lets us clone the button's reflow state, but with its
- // ComputedWidth and ComputedHeight reduced by the amount of renderer-specific
- // focus border and padding that we're using. (This lets us provide a more
- // appropriate content-box size for descendents' percent sizes to resolve
- // against.)
- static ReflowInput
- CloneReflowInputWithReducedContentBox(
- const ReflowInput& aButtonReflowInput,
- const nsMargin& aFocusPadding)
- {
- nscoord adjustedWidth =
- aButtonReflowInput.ComputedWidth() - aFocusPadding.LeftRight();
- adjustedWidth = std::max(0, adjustedWidth);
- // (Only adjust height if it's an actual length.)
- nscoord adjustedHeight = aButtonReflowInput.ComputedHeight();
- if (adjustedHeight != NS_INTRINSICSIZE) {
- adjustedHeight -= aFocusPadding.TopBottom();
- adjustedHeight = std::max(0, adjustedHeight);
- }
- ReflowInput clone(aButtonReflowInput);
- clone.SetComputedWidth(adjustedWidth);
- clone.SetComputedHeight(adjustedHeight);
- return clone;
- }
- void
- nsHTMLButtonControlFrame::ReflowButtonContents(nsPresContext* aPresContext,
- ReflowOutput& aButtonDesiredSize,
- const ReflowInput& aButtonReflowInput,
- nsIFrame* aFirstKid)
- {
- WritingMode wm = GetWritingMode();
- LogicalSize availSize = aButtonReflowInput.ComputedSize(wm);
- availSize.BSize(wm) = NS_INTRINSICSIZE;
- // Buttons have some bonus renderer-determined border/padding,
- // which occupies part of the button's content-box area:
- LogicalMargin focusPadding =
- LogicalMargin(wm, mRenderer.GetAddedButtonBorderAndPadding());
- // See whether out availSize's inline-size is big enough. If it's
- // smaller than our intrinsic min iSize, that means that the kid
- // wouldn't really fit. In that case, we overflow into our internal
- // focuspadding (which other browsers don't have) so that there's a
- // little more space for it.
- // Note that GetMinISize includes the focusPadding.
- nscoord IOverflow = GetMinISize(aButtonReflowInput.mRenderingContext) -
- aButtonReflowInput.ComputedISize();
- nscoord IFocusPadding = focusPadding.IStartEnd(wm);
- nscoord focusPaddingReduction = std::min(IFocusPadding,
- std::max(IOverflow, 0));
- if (focusPaddingReduction > 0) {
- nscoord startReduction = focusPadding.IStart(wm);
- if (focusPaddingReduction != IFocusPadding) {
- startReduction = NSToCoordRound(startReduction *
- (float(focusPaddingReduction) /
- float(IFocusPadding)));
- }
- focusPadding.IStart(wm) -= startReduction;
- focusPadding.IEnd(wm) -= focusPaddingReduction - startReduction;
- }
- // shorthand for a value we need to use in a bunch of places
- const LogicalMargin& clbp = aButtonReflowInput.ComputedLogicalBorderPadding();
- // Indent the child inside us by the focus border. We must do this separate
- // from the regular border.
- availSize.ISize(wm) -= focusPadding.IStartEnd(wm);
- LogicalPoint childPos(wm);
- childPos.I(wm) = focusPadding.IStart(wm) + clbp.IStart(wm);
- availSize.ISize(wm) = std::max(availSize.ISize(wm), 0);
- // Give child a clone of the button's reflow state, with height/width reduced
- // by focusPadding, so that descendants with height:100% don't protrude.
- ReflowInput adjustedButtonReflowInput =
- CloneReflowInputWithReducedContentBox(aButtonReflowInput,
- focusPadding.GetPhysicalMargin(wm));
- ReflowInput contentsReflowInput(aPresContext,
- adjustedButtonReflowInput,
- aFirstKid, availSize);
- nsReflowStatus contentsReflowStatus;
- ReflowOutput contentsDesiredSize(aButtonReflowInput);
- childPos.B(wm) = 0; // This will be set properly later, after reflowing the
- // child to determine its size.
- // We just pass a dummy containerSize here, as the child will be
- // repositioned later by FinishReflowChild.
- nsSize dummyContainerSize;
- ReflowChild(aFirstKid, aPresContext,
- contentsDesiredSize, contentsReflowInput,
- wm, childPos, dummyContainerSize, 0, contentsReflowStatus);
- MOZ_ASSERT(NS_FRAME_IS_COMPLETE(contentsReflowStatus),
- "We gave button-contents frame unconstrained available height, "
- "so it should be complete");
- // Compute the button's content-box size:
- LogicalSize buttonContentBox(wm);
- if (aButtonReflowInput.ComputedBSize() != NS_INTRINSICSIZE) {
- // Button has a fixed block-size -- that's its content-box bSize.
- buttonContentBox.BSize(wm) = aButtonReflowInput.ComputedBSize();
- } else {
- // Button is intrinsically sized -- it should shrinkwrap the
- // button-contents' bSize, plus any focus-padding space:
- buttonContentBox.BSize(wm) =
- contentsDesiredSize.BSize(wm) + focusPadding.BStartEnd(wm);
- // Make sure we obey min/max-bSize in the case when we're doing intrinsic
- // sizing (we get it for free when we have a non-intrinsic
- // aButtonReflowInput.ComputedBSize()). Note that we do this before
- // adjusting for borderpadding, since mComputedMaxBSize and
- // mComputedMinBSize are content bSizes.
- buttonContentBox.BSize(wm) =
- NS_CSS_MINMAX(buttonContentBox.BSize(wm),
- aButtonReflowInput.ComputedMinBSize(),
- aButtonReflowInput.ComputedMaxBSize());
- }
- if (aButtonReflowInput.ComputedISize() != NS_INTRINSICSIZE) {
- buttonContentBox.ISize(wm) = aButtonReflowInput.ComputedISize();
- } else {
- buttonContentBox.ISize(wm) =
- contentsDesiredSize.ISize(wm) + focusPadding.IStartEnd(wm);
- buttonContentBox.ISize(wm) =
- NS_CSS_MINMAX(buttonContentBox.ISize(wm),
- aButtonReflowInput.ComputedMinISize(),
- aButtonReflowInput.ComputedMaxISize());
- }
- // Center child in the block-direction in the button
- // (technically, inside of the button's focus-padding area)
- nscoord extraSpace =
- buttonContentBox.BSize(wm) - focusPadding.BStartEnd(wm) -
- contentsDesiredSize.BSize(wm);
- childPos.B(wm) = std::max(0, extraSpace / 2);
- // Adjust childPos.B() to be in terms of the button's frame-rect, instead of
- // its focus-padding rect:
- childPos.B(wm) += focusPadding.BStart(wm) + clbp.BStart(wm);
- nsSize containerSize =
- (buttonContentBox + clbp.Size(wm)).GetPhysicalSize(wm);
- // Place the child
- FinishReflowChild(aFirstKid, aPresContext,
- contentsDesiredSize, &contentsReflowInput,
- wm, childPos, containerSize, 0);
- // Make sure we have a useful 'ascent' value for the child
- if (contentsDesiredSize.BlockStartAscent() ==
- ReflowOutput::ASK_FOR_BASELINE) {
- WritingMode wm = aButtonReflowInput.GetWritingMode();
- contentsDesiredSize.SetBlockStartAscent(aFirstKid->GetLogicalBaseline(wm));
- }
- // OK, we're done with the child frame.
- // Use what we learned to populate the button frame's reflow metrics.
- // * Button's height & width are content-box size + border-box contribution:
- aButtonDesiredSize.SetSize(wm,
- LogicalSize(wm, aButtonReflowInput.ComputedISize() + clbp.IStartEnd(wm),
- buttonContentBox.BSize(wm) + clbp.BStartEnd(wm)));
- // * Button's ascent is its child's ascent, plus the child's block-offset
- // within our frame... unless it's orthogonal, in which case we'll use the
- // contents inline-size as an approximation for now.
- // XXX is there a better strategy? should we include border-padding?
- if (aButtonDesiredSize.GetWritingMode().IsOrthogonalTo(wm)) {
- aButtonDesiredSize.SetBlockStartAscent(contentsDesiredSize.ISize(wm));
- } else {
- aButtonDesiredSize.SetBlockStartAscent(contentsDesiredSize.BlockStartAscent() +
- childPos.B(wm));
- }
- aButtonDesiredSize.SetOverflowAreasToDesiredBounds();
- }
- bool
- nsHTMLButtonControlFrame::GetVerticalAlignBaseline(mozilla::WritingMode aWM,
- nscoord* aBaseline) const
- {
- nsIFrame* inner = mFrames.FirstChild();
- if (MOZ_UNLIKELY(inner->GetWritingMode().IsOrthogonalTo(aWM))) {
- return false;
- }
- if (!inner->GetVerticalAlignBaseline(aWM, aBaseline)) {
- // <input type=color> has an empty block frame as inner frame
- *aBaseline = inner->
- SynthesizeBaselineBOffsetFromBorderBox(aWM, BaselineSharingGroup::eFirst);
- }
- nscoord innerBStart = inner->BStart(aWM, GetSize());
- *aBaseline += innerBStart;
- return true;
- }
- bool
- nsHTMLButtonControlFrame::GetNaturalBaselineBOffset(mozilla::WritingMode aWM,
- BaselineSharingGroup aBaselineGroup,
- nscoord* aBaseline) const
- {
- nsIFrame* inner = mFrames.FirstChild();
- if (MOZ_UNLIKELY(inner->GetWritingMode().IsOrthogonalTo(aWM))) {
- return false;
- }
- if (!inner->GetNaturalBaselineBOffset(aWM, aBaselineGroup, aBaseline)) {
- // <input type=color> has an empty block frame as inner frame
- *aBaseline = inner->
- SynthesizeBaselineBOffsetFromBorderBox(aWM, aBaselineGroup);
- }
- nscoord innerBStart = inner->BStart(aWM, GetSize());
- if (aBaselineGroup == BaselineSharingGroup::eFirst) {
- *aBaseline += innerBStart;
- } else {
- *aBaseline += BSize(aWM) - (innerBStart + inner->BSize(aWM));
- }
- return true;
- }
- nsresult nsHTMLButtonControlFrame::SetFormProperty(nsIAtom* aName, const nsAString& aValue)
- {
- if (nsGkAtoms::value == aName) {
- return mContent->SetAttr(kNameSpaceID_None, nsGkAtoms::value,
- aValue, true);
- }
- return NS_OK;
- }
- nsStyleContext*
- nsHTMLButtonControlFrame::GetAdditionalStyleContext(int32_t aIndex) const
- {
- return mRenderer.GetStyleContext(aIndex);
- }
- void
- nsHTMLButtonControlFrame::SetAdditionalStyleContext(int32_t aIndex,
- nsStyleContext* aStyleContext)
- {
- mRenderer.SetStyleContext(aIndex, aStyleContext);
- }
- #ifdef DEBUG
- void
- nsHTMLButtonControlFrame::AppendFrames(ChildListID aListID,
- nsFrameList& aFrameList)
- {
- MOZ_CRASH("unsupported operation");
- }
- void
- nsHTMLButtonControlFrame::InsertFrames(ChildListID aListID,
- nsIFrame* aPrevFrame,
- nsFrameList& aFrameList)
- {
- MOZ_CRASH("unsupported operation");
- }
- void
- nsHTMLButtonControlFrame::RemoveFrame(ChildListID aListID,
- nsIFrame* aOldFrame)
- {
- MOZ_CRASH("unsupported operation");
- }
- #endif
|