Coverage Report

Created: 2018-09-25 14:53

/src/mozilla-central/layout/forms/nsHTMLButtonControlFrame.cpp
Line
Count
Source (jump to first uncovered line)
1
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
3
/* This Source Code Form is subject to the terms of the Mozilla Public
4
 * License, v. 2.0. If a copy of the MPL was not distributed with this
5
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6
7
#include "nsHTMLButtonControlFrame.h"
8
9
#include "nsContainerFrame.h"
10
#include "nsIFormControlFrame.h"
11
#include "nsPresContext.h"
12
#include "nsGkAtoms.h"
13
#include "nsButtonFrameRenderer.h"
14
#include "nsCSSAnonBoxes.h"
15
#include "nsCheckboxRadioFrame.h"
16
#include "nsNameSpaceManager.h"
17
#include "nsDisplayList.h"
18
#include <algorithm>
19
20
using namespace mozilla;
21
22
nsContainerFrame*
23
NS_NewHTMLButtonControlFrame(nsIPresShell* aPresShell, ComputedStyle* aStyle)
24
0
{
25
0
  return new (aPresShell) nsHTMLButtonControlFrame(aStyle);
26
0
}
27
28
NS_IMPL_FRAMEARENA_HELPERS(nsHTMLButtonControlFrame)
29
30
nsHTMLButtonControlFrame::nsHTMLButtonControlFrame(ComputedStyle* aStyle,
31
                                                   nsIFrame::ClassID aID)
32
  : nsContainerFrame(aStyle, aID)
33
0
{
34
0
}
35
36
nsHTMLButtonControlFrame::~nsHTMLButtonControlFrame()
37
0
{
38
0
}
39
40
void
41
nsHTMLButtonControlFrame::DestroyFrom(nsIFrame* aDestructRoot, PostDestroyData& aPostDestroyData)
42
0
{
43
0
  nsCheckboxRadioFrame::RegUnRegAccessKey(static_cast<nsIFrame*>(this), false);
44
0
  nsContainerFrame::DestroyFrom(aDestructRoot, aPostDestroyData);
45
0
}
46
47
void
48
nsHTMLButtonControlFrame::Init(nsIContent*       aContent,
49
                               nsContainerFrame* aParent,
50
                               nsIFrame*         aPrevInFlow)
51
0
{
52
0
  nsContainerFrame::Init(aContent, aParent, aPrevInFlow);
53
0
  mRenderer.SetFrame(this, PresContext());
54
0
}
55
56
0
NS_QUERYFRAME_HEAD(nsHTMLButtonControlFrame)
57
0
  NS_QUERYFRAME_ENTRY(nsIFormControlFrame)
58
0
NS_QUERYFRAME_TAIL_INHERITING(nsContainerFrame)
59
60
#ifdef ACCESSIBILITY
61
a11y::AccType
62
nsHTMLButtonControlFrame::AccessibleType()
63
0
{
64
0
  return a11y::eHTMLButtonType;
65
0
}
66
#endif
67
68
void
69
nsHTMLButtonControlFrame::SetFocus(bool aOn, bool aRepaint)
70
0
{
71
0
}
72
73
nsresult
74
nsHTMLButtonControlFrame::HandleEvent(nsPresContext* aPresContext,
75
                                      WidgetGUIEvent* aEvent,
76
                                      nsEventStatus* aEventStatus)
77
0
{
78
0
  // if disabled do nothing
79
0
  if (mRenderer.isDisabled()) {
80
0
    return NS_OK;
81
0
  }
82
0
83
0
  // mouse clicks are handled by content
84
0
  // we don't want our children to get any events. So just pass it to frame.
85
0
  return nsFrame::HandleEvent(aPresContext, aEvent, aEventStatus);
86
0
}
87
88
bool
89
nsHTMLButtonControlFrame::ShouldClipPaintingToBorderBox()
90
0
{
91
0
  return IsInput() || StyleDisplay()->mOverflowX != NS_STYLE_OVERFLOW_VISIBLE;
92
0
}
93
94
void
95
nsHTMLButtonControlFrame::BuildDisplayList(nsDisplayListBuilder*   aBuilder,
96
                                           const nsDisplayListSet& aLists)
97
0
{
98
0
  // Clip to our border area for event hit testing.
99
0
  Maybe<DisplayListClipState::AutoSaveRestore> eventClipState;
100
0
  const bool isForEventDelivery = aBuilder->IsForEventDelivery();
101
0
  if (isForEventDelivery) {
102
0
    eventClipState.emplace(aBuilder);
103
0
    nsRect rect(aBuilder->ToReferenceFrame(this), GetSize());
104
0
    nscoord radii[8];
105
0
    bool hasRadii = GetBorderRadii(radii);
106
0
    eventClipState->ClipContainingBlockDescendants(rect, hasRadii ? radii : nullptr);
107
0
  }
108
0
109
0
  nsDisplayList onTop;
110
0
  if (IsVisibleForPainting(aBuilder)) {
111
0
    mRenderer.DisplayButton(aBuilder, aLists.BorderBackground(), &onTop);
112
0
  }
113
0
114
0
  nsDisplayListCollection set(aBuilder);
115
0
116
0
  // Do not allow the child subtree to receive events.
117
0
  if (!isForEventDelivery || aBuilder->HitTestIsForVisibility()) {
118
0
    DisplayListClipState::AutoSaveRestore clipState(aBuilder);
119
0
120
0
    if (ShouldClipPaintingToBorderBox()) {
121
0
      nsMargin border = StyleBorder()->GetComputedBorder();
122
0
      nsRect rect(aBuilder->ToReferenceFrame(this), GetSize());
123
0
      rect.Deflate(border);
124
0
      nscoord radii[8];
125
0
      bool hasRadii = GetPaddingBoxBorderRadii(radii);
126
0
      clipState.ClipContainingBlockDescendants(rect, hasRadii ? radii : nullptr);
127
0
    }
128
0
129
0
    BuildDisplayListForChild(aBuilder, mFrames.FirstChild(), set,
130
0
                             DISPLAY_CHILD_FORCE_PSEUDO_STACKING_CONTEXT);
131
0
    // That should put the display items in set.Content()
132
0
  }
133
0
134
0
  // Put the foreground outline and focus rects on top of the children
135
0
  set.Content()->AppendToTop(&onTop);
136
0
  set.MoveTo(aLists);
137
0
138
0
  DisplayOutline(aBuilder, aLists);
139
0
140
0
  // to draw border when selected in editor
141
0
  DisplaySelectionOverlay(aBuilder, aLists.Content());
142
0
}
143
144
nscoord
145
nsHTMLButtonControlFrame::GetMinISize(gfxContext* aRenderingContext)
146
0
{
147
0
  nscoord result;
148
0
  DISPLAY_MIN_INLINE_SIZE(this, result);
149
0
  if (StyleDisplay()->IsContainSize()) {
150
0
    result = 0;
151
0
  } else {
152
0
    nsIFrame* kid = mFrames.FirstChild();
153
0
    result = nsLayoutUtils::IntrinsicForContainer(aRenderingContext,
154
0
                                                  kid,
155
0
                                                  nsLayoutUtils::MIN_ISIZE);
156
0
  }
157
0
  return result;
158
0
}
159
160
nscoord
161
nsHTMLButtonControlFrame::GetPrefISize(gfxContext* aRenderingContext)
162
0
{
163
0
  nscoord result;
164
0
  DISPLAY_PREF_INLINE_SIZE(this, result);
165
0
  if (StyleDisplay()->IsContainSize()) {
166
0
    result = 0;
167
0
  } else {
168
0
    nsIFrame* kid = mFrames.FirstChild();
169
0
    result = nsLayoutUtils::IntrinsicForContainer(aRenderingContext,
170
0
                                                  kid,
171
0
                                                  nsLayoutUtils::PREF_ISIZE);
172
0
  }
173
0
  return result;
174
0
}
175
176
void
177
nsHTMLButtonControlFrame::Reflow(nsPresContext* aPresContext,
178
                                 ReflowOutput& aDesiredSize,
179
                                 const ReflowInput& aReflowInput,
180
                                 nsReflowStatus& aStatus)
181
0
{
182
0
  MarkInReflow();
183
0
  DO_GLOBAL_REFLOW_COUNT("nsHTMLButtonControlFrame");
184
0
  DISPLAY_REFLOW(aPresContext, this, aReflowInput, aDesiredSize, aStatus);
185
0
  MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!");
186
0
187
0
  if (mState & NS_FRAME_FIRST_REFLOW) {
188
0
    nsCheckboxRadioFrame::RegUnRegAccessKey(static_cast<nsIFrame*>(this), true);
189
0
  }
190
0
191
0
  // Reflow the child
192
0
  nsIFrame* firstKid = mFrames.FirstChild();
193
0
194
0
  MOZ_ASSERT(firstKid, "Button should have a child frame for its contents");
195
0
  MOZ_ASSERT(!firstKid->GetNextSibling(),
196
0
             "Button should have exactly one child frame");
197
0
  MOZ_ASSERT(firstKid->Style()->GetPseudo() ==
198
0
             nsCSSAnonBoxes::buttonContent(),
199
0
             "Button's child frame has unexpected pseudo type!");
200
0
201
0
  // XXXbz Eventually we may want to check-and-bail if
202
0
  // !aReflowInput.ShouldReflowAllKids() &&
203
0
  // !NS_SUBTREE_DIRTY(firstKid).
204
0
  // We'd need to cache our ascent for that, of course.
205
0
206
0
  // Reflow the contents of the button.
207
0
  // (This populates our aDesiredSize, too.)
208
0
  ReflowButtonContents(aPresContext, aDesiredSize,
209
0
                       aReflowInput, firstKid);
210
0
211
0
  if (!ShouldClipPaintingToBorderBox()) {
212
0
    ConsiderChildOverflow(aDesiredSize.mOverflowAreas, firstKid);
213
0
  }
214
0
  // else, we ignore child overflow -- anything that overflows beyond our
215
0
  // own border-box will get clipped when painting.
216
0
217
0
  FinishReflowWithAbsoluteFrames(aPresContext, aDesiredSize,
218
0
                                 aReflowInput, aStatus);
219
0
220
0
  // We're always complete and we don't support overflow containers
221
0
  // so we shouldn't have a next-in-flow ever.
222
0
  aStatus.Reset();
223
0
  MOZ_ASSERT(!GetNextInFlow());
224
0
225
0
  NS_FRAME_SET_TRUNCATION(aStatus, aReflowInput, aDesiredSize);
226
0
}
227
228
void
229
nsHTMLButtonControlFrame::ReflowButtonContents(nsPresContext* aPresContext,
230
                                               ReflowOutput& aButtonDesiredSize,
231
                                               const ReflowInput& aButtonReflowInput,
232
                                               nsIFrame* aFirstKid)
233
0
{
234
0
  WritingMode wm = GetWritingMode();
235
0
  LogicalSize availSize = aButtonReflowInput.ComputedSize(wm);
236
0
  availSize.BSize(wm) = NS_INTRINSICSIZE;
237
0
238
0
  // shorthand for a value we need to use in a bunch of places
239
0
  const LogicalMargin& clbp = aButtonReflowInput.ComputedLogicalBorderPadding();
240
0
241
0
  LogicalPoint childPos(wm);
242
0
  childPos.I(wm) = clbp.IStart(wm);
243
0
  availSize.ISize(wm) = std::max(availSize.ISize(wm), 0);
244
0
245
0
  ReflowInput contentsReflowInput(aPresContext, aButtonReflowInput,
246
0
                                  aFirstKid, availSize);
247
0
248
0
  nsReflowStatus contentsReflowStatus;
249
0
  ReflowOutput contentsDesiredSize(aButtonReflowInput);
250
0
  childPos.B(wm) = 0; // This will be set properly later, after reflowing the
251
0
                      // child to determine its size.
252
0
253
0
  const LayoutFrameType frameType = aFirstKid->Type();
254
0
  if (frameType == LayoutFrameType::FlexContainer ||
255
0
      frameType == LayoutFrameType::GridContainer) {
256
0
    contentsReflowInput.ComputedBSize() = aButtonReflowInput.ComputedBSize();
257
0
    contentsReflowInput.ComputedMinBSize() =
258
0
      aButtonReflowInput.ComputedMinBSize();
259
0
    contentsReflowInput.ComputedMaxBSize() =
260
0
      aButtonReflowInput.ComputedMaxBSize();
261
0
  }
262
0
263
0
  // We just pass a dummy containerSize here, as the child will be
264
0
  // repositioned later by FinishReflowChild.
265
0
  nsSize dummyContainerSize;
266
0
  ReflowChild(aFirstKid, aPresContext,
267
0
              contentsDesiredSize, contentsReflowInput,
268
0
              wm, childPos, dummyContainerSize, 0, contentsReflowStatus);
269
0
  MOZ_ASSERT(contentsReflowStatus.IsComplete(),
270
0
             "We gave button-contents frame unconstrained available height, "
271
0
             "so it should be complete");
272
0
273
0
  // Compute the button's content-box size:
274
0
  LogicalSize buttonContentBox(wm);
275
0
  if (aButtonReflowInput.ComputedBSize() != NS_INTRINSICSIZE) {
276
0
    // Button has a fixed block-size -- that's its content-box bSize.
277
0
    buttonContentBox.BSize(wm) = aButtonReflowInput.ComputedBSize();
278
0
  } else if (aButtonReflowInput.mStyleDisplay->IsContainSize()) {
279
0
    // Button is intrinsically sized and has size containment.
280
0
    // It should have a BSize that is either zero or the minimum
281
0
    // specified BSize.
282
0
    buttonContentBox.BSize(wm) = aButtonReflowInput.ComputedMinBSize();
283
0
  } else {
284
0
    // Button is intrinsically sized -- it should shrinkwrap the
285
0
    // button-contents' bSize:
286
0
    buttonContentBox.BSize(wm) = contentsDesiredSize.BSize(wm);
287
0
288
0
    // Make sure we obey min/max-bSize in the case when we're doing intrinsic
289
0
    // sizing (we get it for free when we have a non-intrinsic
290
0
    // aButtonReflowInput.ComputedBSize()).  Note that we do this before
291
0
    // adjusting for borderpadding, since mComputedMaxBSize and
292
0
    // mComputedMinBSize are content bSizes.
293
0
    buttonContentBox.BSize(wm) =
294
0
      NS_CSS_MINMAX(buttonContentBox.BSize(wm),
295
0
                    aButtonReflowInput.ComputedMinBSize(),
296
0
                    aButtonReflowInput.ComputedMaxBSize());
297
0
  }
298
0
  if (aButtonReflowInput.ComputedISize() != NS_INTRINSICSIZE) {
299
0
    buttonContentBox.ISize(wm) = aButtonReflowInput.ComputedISize();
300
0
  } else if (aButtonReflowInput.mStyleDisplay->IsContainSize()) {
301
0
    buttonContentBox.ISize(wm) = aButtonReflowInput.ComputedMinISize();
302
0
  } else {
303
0
    buttonContentBox.ISize(wm) = contentsDesiredSize.ISize(wm);
304
0
    buttonContentBox.ISize(wm) =
305
0
      NS_CSS_MINMAX(buttonContentBox.ISize(wm),
306
0
                    aButtonReflowInput.ComputedMinISize(),
307
0
                    aButtonReflowInput.ComputedMaxISize());
308
0
  }
309
0
310
0
  // Center child in the block-direction in the button
311
0
  // (technically, inside of the button's focus-padding area)
312
0
  nscoord extraSpace = buttonContentBox.BSize(wm) -
313
0
                       contentsDesiredSize.BSize(wm);
314
0
315
0
  childPos.B(wm) = std::max(0, extraSpace / 2);
316
0
317
0
  // Adjust childPos.B() to be in terms of the button's frame-rect:
318
0
  childPos.B(wm) += clbp.BStart(wm);
319
0
320
0
  nsSize containerSize =
321
0
    (buttonContentBox + clbp.Size(wm)).GetPhysicalSize(wm);
322
0
323
0
  // Place the child
324
0
  FinishReflowChild(aFirstKid, aPresContext,
325
0
                    contentsDesiredSize, &contentsReflowInput,
326
0
                    wm, childPos, containerSize, 0);
327
0
328
0
  // Make sure we have a useful 'ascent' value for the child
329
0
  if (contentsDesiredSize.BlockStartAscent() ==
330
0
      ReflowOutput::ASK_FOR_BASELINE) {
331
0
    WritingMode wm = aButtonReflowInput.GetWritingMode();
332
0
    contentsDesiredSize.SetBlockStartAscent(aFirstKid->GetLogicalBaseline(wm));
333
0
  }
334
0
335
0
  // OK, we're done with the child frame.
336
0
  // Use what we learned to populate the button frame's reflow metrics.
337
0
  //  * Button's height & width are content-box size + border-box contribution:
338
0
  aButtonDesiredSize.SetSize(wm,
339
0
    LogicalSize(wm, aButtonReflowInput.ComputedISize() + clbp.IStartEnd(wm),
340
0
                    buttonContentBox.BSize(wm) + clbp.BStartEnd(wm)));
341
0
342
0
  //  * Button's ascent is its child's ascent, plus the child's block-offset
343
0
  // within our frame... unless it's orthogonal, in which case we'll use the
344
0
  // contents inline-size as an approximation for now.
345
0
  // XXX is there a better strategy? should we include border-padding?
346
0
  if (aButtonReflowInput.mStyleDisplay->IsContainSize()) {
347
0
    // If we're size-contained, we should pretend our contents had 0 height
348
0
    // (as they would, if we had no children). This case is identical to the
349
0
    // final else case, but uses only our specified button height for ascent
350
0
    // (ie. it ignores the height returned in contentsDesiredSize).
351
0
    nscoord containAscent = (buttonContentBox.BSize(wm) / 2) + clbp.BStart(wm);
352
0
    aButtonDesiredSize.SetBlockStartAscent(containAscent);
353
0
  } else if (aButtonDesiredSize.GetWritingMode().IsOrthogonalTo(wm)) {
354
0
    aButtonDesiredSize.SetBlockStartAscent(contentsDesiredSize.ISize(wm));
355
0
  } else {
356
0
    aButtonDesiredSize.SetBlockStartAscent(contentsDesiredSize.BlockStartAscent() +
357
0
                                           childPos.B(wm));
358
0
  }
359
0
360
0
  aButtonDesiredSize.SetOverflowAreasToDesiredBounds();
361
0
}
362
363
bool
364
nsHTMLButtonControlFrame::GetVerticalAlignBaseline(mozilla::WritingMode aWM,
365
                                                   nscoord* aBaseline) const
366
0
{
367
0
  nsIFrame* inner = mFrames.FirstChild();
368
0
  if (MOZ_UNLIKELY(inner->GetWritingMode().IsOrthogonalTo(aWM))) {
369
0
    return false;
370
0
  }
371
0
  if (!inner->GetVerticalAlignBaseline(aWM, aBaseline)) {
372
0
    // <input type=color> has an empty block frame as inner frame
373
0
    *aBaseline = inner->
374
0
      SynthesizeBaselineBOffsetFromBorderBox(aWM, BaselineSharingGroup::eFirst);
375
0
  }
376
0
  nscoord innerBStart = inner->BStart(aWM, GetSize());
377
0
  *aBaseline += innerBStart;
378
0
  return true;
379
0
}
380
381
bool
382
nsHTMLButtonControlFrame::GetNaturalBaselineBOffset(mozilla::WritingMode aWM,
383
                                         BaselineSharingGroup aBaselineGroup,
384
                                         nscoord* aBaseline) const
385
0
{
386
0
  nsIFrame* inner = mFrames.FirstChild();
387
0
  if (MOZ_UNLIKELY(inner->GetWritingMode().IsOrthogonalTo(aWM))) {
388
0
    return false;
389
0
  }
390
0
  if (!inner->GetNaturalBaselineBOffset(aWM, aBaselineGroup, aBaseline)) {
391
0
    // <input type=color> has an empty block frame as inner frame
392
0
    *aBaseline = inner->
393
0
      SynthesizeBaselineBOffsetFromBorderBox(aWM, aBaselineGroup);
394
0
  }
395
0
  nscoord innerBStart = inner->BStart(aWM, GetSize());
396
0
  if (aBaselineGroup == BaselineSharingGroup::eFirst) {
397
0
    *aBaseline += innerBStart;
398
0
  } else {
399
0
    *aBaseline += BSize(aWM) - (innerBStart + inner->BSize(aWM));
400
0
  }
401
0
  return true;
402
0
}
403
404
nsresult nsHTMLButtonControlFrame::SetFormProperty(nsAtom* aName, const nsAString& aValue)
405
0
{
406
0
  if (nsGkAtoms::value == aName) {
407
0
    return mContent->AsElement()->SetAttr(kNameSpaceID_None, nsGkAtoms::value,
408
0
                                          aValue, true);
409
0
  }
410
0
  return NS_OK;
411
0
}
412
413
ComputedStyle*
414
nsHTMLButtonControlFrame::GetAdditionalComputedStyle(int32_t aIndex) const
415
0
{
416
0
  return mRenderer.GetComputedStyle(aIndex);
417
0
}
418
419
void
420
nsHTMLButtonControlFrame::SetAdditionalComputedStyle(int32_t aIndex,
421
                                                    ComputedStyle* aComputedStyle)
422
0
{
423
0
  mRenderer.SetComputedStyle(aIndex, aComputedStyle);
424
0
}
425
426
void
427
nsHTMLButtonControlFrame::AppendDirectlyOwnedAnonBoxes(nsTArray<OwnedAnonBox>& aResult)
428
0
{
429
0
  MOZ_ASSERT(mFrames.FirstChild(), "Must have our button-content anon box");
430
0
  MOZ_ASSERT(!mFrames.FirstChild()->GetNextSibling(),
431
0
             "Must only have our button-content anon box");
432
0
  aResult.AppendElement(OwnedAnonBox(mFrames.FirstChild()));
433
0
}
434
435
#ifdef DEBUG
436
void
437
nsHTMLButtonControlFrame::AppendFrames(ChildListID     aListID,
438
                                       nsFrameList&    aFrameList)
439
{
440
  MOZ_CRASH("unsupported operation");
441
}
442
443
void
444
nsHTMLButtonControlFrame::InsertFrames(ChildListID     aListID,
445
                                       nsIFrame*       aPrevFrame,
446
                                       nsFrameList&    aFrameList)
447
{
448
  MOZ_CRASH("unsupported operation");
449
}
450
451
void
452
nsHTMLButtonControlFrame::RemoveFrame(ChildListID     aListID,
453
                                      nsIFrame*       aOldFrame)
454
{
455
  MOZ_CRASH("unsupported operation");
456
}
457
#endif