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 #include "nsButtonFrameRenderer.h"
7 #include "nsCSSRendering.h"
8 #include "nsPresContext.h"
9 #include "nsPresContextInlines.h"
10 #include "nsGkAtoms.h"
11 #include "nsCSSPseudoElements.h"
12 #include "nsNameSpaceManager.h"
13 #include "mozilla/ServoStyleSet.h"
14 #include "mozilla/Unused.h"
15 #include "nsDisplayList.h"
18 #include "mozilla/dom/Element.h"
21 #include "mozilla/layers/RenderRootStateManager.h"
23 using namespace mozilla
;
24 using namespace mozilla::image
;
25 using namespace mozilla::layers
;
28 class nsDisplayButtonBoxShadowOuter
;
29 class nsDisplayButtonBorder
;
30 class nsDisplayButtonForeground
;
31 } // namespace mozilla
33 nsButtonFrameRenderer::nsButtonFrameRenderer() : mFrame(nullptr) {
34 MOZ_COUNT_CTOR(nsButtonFrameRenderer
);
37 nsButtonFrameRenderer::~nsButtonFrameRenderer() {
38 MOZ_COUNT_DTOR(nsButtonFrameRenderer
);
41 void nsButtonFrameRenderer::SetFrame(nsIFrame
* aFrame
,
42 nsPresContext
* aPresContext
) {
44 ReResolveStyles(aPresContext
);
47 nsIFrame
* nsButtonFrameRenderer::GetFrame() { return mFrame
; }
49 void nsButtonFrameRenderer::SetDisabled(bool aDisabled
, bool aNotify
) {
50 dom::Element
* element
= mFrame
->GetContent()->AsElement();
52 element
->SetAttr(kNameSpaceID_None
, nsGkAtoms::disabled
, u
""_ns
, aNotify
);
54 element
->UnsetAttr(kNameSpaceID_None
, nsGkAtoms::disabled
, aNotify
);
57 bool nsButtonFrameRenderer::isDisabled() {
58 return mFrame
->GetContent()->AsElement()->IsDisabled();
61 nsresult
nsButtonFrameRenderer::DisplayButton(nsDisplayListBuilder
* aBuilder
,
62 nsDisplayList
* aBackground
,
63 nsDisplayList
* aForeground
) {
64 if (!mFrame
->StyleEffects()->mBoxShadow
.IsEmpty()) {
65 aBackground
->AppendNewToTop
<nsDisplayButtonBoxShadowOuter
>(aBuilder
,
70 mFrame
->GetRectRelativeToSelf() + aBuilder
->ToReferenceFrame(mFrame
);
72 const AppendedBackgroundType result
=
73 nsDisplayBackgroundImage::AppendBackgroundItemsToTop(
74 aBuilder
, mFrame
, buttonRect
, aBackground
);
75 if (result
== AppendedBackgroundType::None
) {
76 aBuilder
->BuildCompositorHitTestInfoIfNeeded(GetFrame(), aBackground
);
79 aBackground
->AppendNewToTop
<nsDisplayButtonBorder
>(aBuilder
, GetFrame(),
82 // Only display focus rings if we actually have them. Since at most one
83 // button would normally display a focus ring, most buttons won't have them.
84 if (mInnerFocusStyle
&& mInnerFocusStyle
->StyleBorder()->HasBorder() &&
86 mFrame
->PresContext()->Theme()->ThemeWantsButtonInnerFocusRing()) {
87 aForeground
->AppendNewToTop
<nsDisplayButtonForeground
>(aBuilder
, GetFrame(),
93 void nsButtonFrameRenderer::GetButtonInnerFocusRect(const nsRect
& aRect
,
96 aResult
.Deflate(mFrame
->GetUsedBorderAndPadding());
98 if (mInnerFocusStyle
) {
99 nsMargin
innerFocusPadding(0, 0, 0, 0);
100 mInnerFocusStyle
->StylePadding()->GetPadding(innerFocusPadding
);
102 nsMargin framePadding
= mFrame
->GetUsedPadding();
104 innerFocusPadding
.top
= std::min(innerFocusPadding
.top
, framePadding
.top
);
105 innerFocusPadding
.right
=
106 std::min(innerFocusPadding
.right
, framePadding
.right
);
107 innerFocusPadding
.bottom
=
108 std::min(innerFocusPadding
.bottom
, framePadding
.bottom
);
109 innerFocusPadding
.left
=
110 std::min(innerFocusPadding
.left
, framePadding
.left
);
112 aResult
.Inflate(innerFocusPadding
);
116 ImgDrawResult
nsButtonFrameRenderer::PaintInnerFocusBorder(
117 nsDisplayListBuilder
* aBuilder
, nsPresContext
* aPresContext
,
118 gfxContext
& aRenderingContext
, const nsRect
& aDirtyRect
,
119 const nsRect
& aRect
) {
120 // we draw the -moz-focus-inner border just inside the button's
121 // normal border and padding, to match Windows themes.
125 PaintBorderFlags flags
= aBuilder
->ShouldSyncDecodeImages()
126 ? PaintBorderFlags::SyncDecodeImages
127 : PaintBorderFlags();
129 ImgDrawResult result
= ImgDrawResult::SUCCESS
;
131 if (mInnerFocusStyle
) {
132 GetButtonInnerFocusRect(aRect
, rect
);
135 nsCSSRendering::PaintBorder(aPresContext
, aRenderingContext
, mFrame
,
136 aDirtyRect
, rect
, mInnerFocusStyle
, flags
);
142 Maybe
<nsCSSBorderRenderer
>
143 nsButtonFrameRenderer::CreateInnerFocusBorderRenderer(
144 nsDisplayListBuilder
* aBuilder
, nsPresContext
* aPresContext
,
145 gfxContext
* aRenderingContext
, const nsRect
& aDirtyRect
,
146 const nsRect
& aRect
, bool* aBorderIsEmpty
) {
147 if (mInnerFocusStyle
) {
149 GetButtonInnerFocusRect(aRect
, rect
);
151 gfx::DrawTarget
* dt
=
152 aRenderingContext
? aRenderingContext
->GetDrawTarget() : nullptr;
153 return nsCSSRendering::CreateBorderRenderer(
154 aPresContext
, dt
, mFrame
, aDirtyRect
, rect
, mInnerFocusStyle
,
161 ImgDrawResult
nsButtonFrameRenderer::PaintBorder(nsDisplayListBuilder
* aBuilder
,
162 nsPresContext
* aPresContext
,
163 gfxContext
& aRenderingContext
,
164 const nsRect
& aDirtyRect
,
165 const nsRect
& aRect
) {
166 // get the button rect this is inside the focus and outline rects
167 nsRect buttonRect
= aRect
;
168 ComputedStyle
* context
= mFrame
->Style();
170 PaintBorderFlags borderFlags
= aBuilder
->ShouldSyncDecodeImages()
171 ? PaintBorderFlags::SyncDecodeImages
172 : PaintBorderFlags();
174 nsCSSRendering::PaintBoxShadowInner(aPresContext
, aRenderingContext
, mFrame
,
177 ImgDrawResult result
=
178 nsCSSRendering::PaintBorder(aPresContext
, aRenderingContext
, mFrame
,
179 aDirtyRect
, buttonRect
, context
, borderFlags
);
185 * Call this when styles change
187 void nsButtonFrameRenderer::ReResolveStyles(nsPresContext
* aPresContext
) {
188 // get all the styles
189 ServoStyleSet
* styleSet
= aPresContext
->StyleSet();
191 // get styles assigned to -moz-focus-inner (ie dotted border on Windows)
192 mInnerFocusStyle
= styleSet
->ProbePseudoElementStyle(
193 *mFrame
->GetContent()->AsElement(), PseudoStyleType::mozFocusInner
,
194 nullptr, mFrame
->Style());
197 ComputedStyle
* nsButtonFrameRenderer::GetComputedStyle(int32_t aIndex
) const {
199 case NS_BUTTON_RENDERER_FOCUS_INNER_CONTEXT_INDEX
:
200 return mInnerFocusStyle
;
206 void nsButtonFrameRenderer::SetComputedStyle(int32_t aIndex
,
207 ComputedStyle
* aComputedStyle
) {
209 case NS_BUTTON_RENDERER_FOCUS_INNER_CONTEXT_INDEX
:
210 mInnerFocusStyle
= aComputedStyle
;
217 class nsDisplayButtonBoxShadowOuter
: public nsPaintedDisplayItem
{
219 nsDisplayButtonBoxShadowOuter(nsDisplayListBuilder
* aBuilder
,
221 : nsPaintedDisplayItem(aBuilder
, aFrame
) {
222 MOZ_COUNT_CTOR(nsDisplayButtonBoxShadowOuter
);
224 MOZ_COUNTED_DTOR_OVERRIDE(nsDisplayButtonBoxShadowOuter
)
226 virtual bool CreateWebRenderCommands(
227 mozilla::wr::DisplayListBuilder
& aBuilder
,
228 mozilla::wr::IpcResourceUpdateQueue
& aResources
,
229 const StackingContextHelper
& aSc
,
230 mozilla::layers::RenderRootStateManager
* aManager
,
231 nsDisplayListBuilder
* aDisplayListBuilder
) override
;
233 bool CanBuildWebRenderDisplayItems();
235 virtual void Paint(nsDisplayListBuilder
* aBuilder
, gfxContext
* aCtx
) override
;
236 virtual nsRect
GetBounds(nsDisplayListBuilder
* aBuilder
,
237 bool* aSnap
) const override
;
238 NS_DISPLAY_DECL_NAME("ButtonBoxShadowOuter", TYPE_BUTTON_BOX_SHADOW_OUTER
)
241 nsRect
nsDisplayButtonBoxShadowOuter::GetBounds(nsDisplayListBuilder
* aBuilder
,
244 return mFrame
->InkOverflowRectRelativeToSelf() + ToReferenceFrame();
247 void nsDisplayButtonBoxShadowOuter::Paint(nsDisplayListBuilder
* aBuilder
,
249 nsRect frameRect
= nsRect(ToReferenceFrame(), mFrame
->GetSize());
251 nsCSSRendering::PaintBoxShadowOuter(mFrame
->PresContext(), *aCtx
, mFrame
,
252 frameRect
, GetPaintRect(aBuilder
, aCtx
));
255 bool nsDisplayButtonBoxShadowOuter::CanBuildWebRenderDisplayItems() {
256 // FIXME(emilio): Is this right? That doesn't make much sense.
257 if (mFrame
->StyleEffects()->mBoxShadow
.IsEmpty()) {
261 bool hasBorderRadius
;
263 nsCSSRendering::HasBoxShadowNativeTheme(mFrame
, hasBorderRadius
);
265 // We don't support native themed things yet like box shadows around
270 bool nsDisplayButtonBoxShadowOuter::CreateWebRenderCommands(
271 mozilla::wr::DisplayListBuilder
& aBuilder
,
272 mozilla::wr::IpcResourceUpdateQueue
& aResources
,
273 const StackingContextHelper
& aSc
,
274 mozilla::layers::RenderRootStateManager
* aManager
,
275 nsDisplayListBuilder
* aDisplayListBuilder
) {
276 if (!CanBuildWebRenderDisplayItems()) {
279 int32_t appUnitsPerDevPixel
= mFrame
->PresContext()->AppUnitsPerDevPixel();
280 nsRect shadowRect
= nsRect(ToReferenceFrame(), mFrame
->GetSize());
281 LayoutDeviceRect deviceBox
=
282 LayoutDeviceRect::FromAppUnits(shadowRect
, appUnitsPerDevPixel
);
283 wr::LayoutRect deviceBoxRect
= wr::ToLayoutRect(deviceBox
);
286 LayoutDeviceRect clipRect
= LayoutDeviceRect::FromAppUnits(
287 GetBounds(aDisplayListBuilder
, &dummy
), appUnitsPerDevPixel
);
288 wr::LayoutRect deviceClipRect
= wr::ToLayoutRect(clipRect
);
290 bool hasBorderRadius
;
291 Unused
<< nsCSSRendering::HasBoxShadowNativeTheme(mFrame
, hasBorderRadius
);
293 LayoutDeviceSize zeroSize
;
294 wr::BorderRadius borderRadius
=
295 wr::ToBorderRadius(zeroSize
, zeroSize
, zeroSize
, zeroSize
);
296 if (hasBorderRadius
) {
297 gfx::RectCornerRadii borderRadii
;
298 hasBorderRadius
= nsCSSRendering::GetBorderRadii(shadowRect
, shadowRect
,
299 mFrame
, borderRadii
);
300 if (hasBorderRadius
) {
301 borderRadius
= wr::ToBorderRadius(borderRadii
);
305 const Span
<const StyleBoxShadow
> shadows
=
306 mFrame
->StyleEffects()->mBoxShadow
.AsSpan();
307 MOZ_ASSERT(!shadows
.IsEmpty());
309 for (const StyleBoxShadow
& shadow
: Reversed(shadows
)) {
314 float(shadow
.base
.blur
.ToAppUnits()) / float(appUnitsPerDevPixel
);
315 gfx::DeviceColor shadowColor
=
316 ToDeviceColor(nsCSSRendering::GetShadowColor(shadow
.base
, mFrame
, 1.0));
318 LayoutDevicePoint shadowOffset
= LayoutDevicePoint::FromAppUnits(
319 nsPoint(shadow
.base
.horizontal
.ToAppUnits(),
320 shadow
.base
.vertical
.ToAppUnits()),
321 appUnitsPerDevPixel
);
324 float(shadow
.spread
.ToAppUnits()) / float(appUnitsPerDevPixel
);
326 aBuilder
.PushBoxShadow(deviceBoxRect
, deviceClipRect
, !BackfaceIsHidden(),
327 deviceBoxRect
, wr::ToLayoutVector2D(shadowOffset
),
328 wr::ToColorF(shadowColor
), blurRadius
, spreadRadius
,
329 borderRadius
, wr::BoxShadowClipMode::Outset
);
334 class nsDisplayButtonBorder final
: public nsPaintedDisplayItem
{
336 nsDisplayButtonBorder(nsDisplayListBuilder
* aBuilder
, nsIFrame
* aFrame
,
337 nsButtonFrameRenderer
* aRenderer
)
338 : nsPaintedDisplayItem(aBuilder
, aFrame
), mBFR(aRenderer
) {
339 MOZ_COUNT_CTOR(nsDisplayButtonBorder
);
341 MOZ_COUNTED_DTOR_OVERRIDE(nsDisplayButtonBorder
)
343 virtual void HitTest(nsDisplayListBuilder
* aBuilder
, const nsRect
& aRect
,
344 HitTestState
* aState
,
345 nsTArray
<nsIFrame
*>* aOutFrames
) override
{
346 aOutFrames
->AppendElement(mFrame
);
348 virtual void Paint(nsDisplayListBuilder
* aBuilder
, gfxContext
* aCtx
) override
;
349 virtual nsRect
GetBounds(nsDisplayListBuilder
* aBuilder
,
350 bool* aSnap
) const override
;
351 virtual bool CreateWebRenderCommands(
352 mozilla::wr::DisplayListBuilder
& aBuilder
,
353 mozilla::wr::IpcResourceUpdateQueue
& aResources
,
354 const StackingContextHelper
& aSc
,
355 mozilla::layers::RenderRootStateManager
* aManager
,
356 nsDisplayListBuilder
* aDisplayListBuilder
) override
;
357 NS_DISPLAY_DECL_NAME("ButtonBorderBackground", TYPE_BUTTON_BORDER_BACKGROUND
)
359 nsButtonFrameRenderer
* mBFR
;
362 bool nsDisplayButtonBorder::CreateWebRenderCommands(
363 mozilla::wr::DisplayListBuilder
& aBuilder
,
364 mozilla::wr::IpcResourceUpdateQueue
& aResources
,
365 const StackingContextHelper
& aSc
,
366 mozilla::layers::RenderRootStateManager
* aManager
,
367 nsDisplayListBuilder
* aDisplayListBuilder
) {
368 // This is really a combination of paint box shadow inner +
370 aBuilder
.StartGroup(this);
371 const nsRect buttonRect
= nsRect(ToReferenceFrame(), mFrame
->GetSize());
373 nsRect visible
= GetBounds(aDisplayListBuilder
, &snap
);
374 nsDisplayBoxShadowInner::CreateInsetBoxShadowWebRenderCommands(
375 aBuilder
, aSc
, visible
, mFrame
, buttonRect
);
377 bool borderIsEmpty
= false;
378 Maybe
<nsCSSBorderRenderer
> br
= nsCSSRendering::CreateBorderRenderer(
379 mFrame
->PresContext(), nullptr, mFrame
, nsRect(),
380 nsRect(ToReferenceFrame(), mFrame
->GetSize()), mFrame
->Style(),
381 &borderIsEmpty
, mFrame
->GetSkipSides());
384 aBuilder
.FinishGroup();
386 aBuilder
.CancelGroup(true);
389 return borderIsEmpty
;
392 br
->CreateWebRenderCommands(this, aBuilder
, aResources
, aSc
);
393 aBuilder
.FinishGroup();
397 void nsDisplayButtonBorder::Paint(nsDisplayListBuilder
* aBuilder
,
399 NS_ASSERTION(mFrame
, "No frame?");
400 nsPresContext
* pc
= mFrame
->PresContext();
401 nsRect r
= nsRect(ToReferenceFrame(), mFrame
->GetSize());
403 // draw the border and background inside the focus and outline borders
404 Unused
<< mBFR
->PaintBorder(aBuilder
, pc
, *aCtx
, GetPaintRect(aBuilder
, aCtx
),
408 nsRect
nsDisplayButtonBorder::GetBounds(nsDisplayListBuilder
* aBuilder
,
411 return aBuilder
->IsForEventDelivery()
412 ? nsRect(ToReferenceFrame(), mFrame
->GetSize())
413 : mFrame
->InkOverflowRectRelativeToSelf() + ToReferenceFrame();
416 class nsDisplayButtonForeground final
: public nsPaintedDisplayItem
{
418 nsDisplayButtonForeground(nsDisplayListBuilder
* aBuilder
, nsIFrame
* aFrame
,
419 nsButtonFrameRenderer
* aRenderer
)
420 : nsPaintedDisplayItem(aBuilder
, aFrame
), mBFR(aRenderer
) {
421 MOZ_COUNT_CTOR(nsDisplayButtonForeground
);
423 MOZ_COUNTED_DTOR_OVERRIDE(nsDisplayButtonForeground
)
425 void Paint(nsDisplayListBuilder
* aBuilder
, gfxContext
* aCtx
) override
;
426 bool CreateWebRenderCommands(
427 mozilla::wr::DisplayListBuilder
& aBuilder
,
428 mozilla::wr::IpcResourceUpdateQueue
& aResources
,
429 const StackingContextHelper
& aSc
,
430 mozilla::layers::RenderRootStateManager
* aManager
,
431 nsDisplayListBuilder
* aDisplayListBuilder
) override
;
432 NS_DISPLAY_DECL_NAME("ButtonForeground", TYPE_BUTTON_FOREGROUND
)
434 nsButtonFrameRenderer
* mBFR
;
437 void nsDisplayButtonForeground::Paint(nsDisplayListBuilder
* aBuilder
,
439 nsRect r
= nsRect(ToReferenceFrame(), mFrame
->GetSize());
441 // Draw the -moz-focus-inner border
442 Unused
<< mBFR
->PaintInnerFocusBorder(aBuilder
, mFrame
->PresContext(), *aCtx
,
443 GetPaintRect(aBuilder
, aCtx
), r
);
446 bool nsDisplayButtonForeground::CreateWebRenderCommands(
447 mozilla::wr::DisplayListBuilder
& aBuilder
,
448 mozilla::wr::IpcResourceUpdateQueue
& aResources
,
449 const StackingContextHelper
& aSc
,
450 mozilla::layers::RenderRootStateManager
* aManager
,
451 nsDisplayListBuilder
* aDisplayListBuilder
) {
452 Maybe
<nsCSSBorderRenderer
> br
;
453 bool borderIsEmpty
= false;
455 nsRect r
= nsRect(ToReferenceFrame(), mFrame
->GetSize());
456 br
= mBFR
->CreateInnerFocusBorderRenderer(
457 aDisplayListBuilder
, mFrame
->PresContext(), nullptr,
458 GetBounds(aDisplayListBuilder
, &dummy
), r
, &borderIsEmpty
);
461 return borderIsEmpty
;
464 aBuilder
.StartGroup(this);
465 br
->CreateWebRenderCommands(this, aBuilder
, aResources
, aSc
);
466 aBuilder
.FinishGroup();
471 } // namespace mozilla