nsHTMLButtonControlFrame.cpp 18 KB

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