summaryrefslogtreecommitdiffstats
path: root/layout/forms/nsButtonFrameRenderer.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'layout/forms/nsButtonFrameRenderer.cpp')
-rw-r--r--layout/forms/nsButtonFrameRenderer.cpp471
1 files changed, 471 insertions, 0 deletions
diff --git a/layout/forms/nsButtonFrameRenderer.cpp b/layout/forms/nsButtonFrameRenderer.cpp
new file mode 100644
index 0000000000..598610cf72
--- /dev/null
+++ b/layout/forms/nsButtonFrameRenderer.cpp
@@ -0,0 +1,471 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 "nsButtonFrameRenderer.h"
+#include "nsCSSRendering.h"
+#include "nsPresContext.h"
+#include "nsPresContextInlines.h"
+#include "nsGkAtoms.h"
+#include "nsCSSPseudoElements.h"
+#include "nsNameSpaceManager.h"
+#include "mozilla/ServoStyleSet.h"
+#include "mozilla/Unused.h"
+#include "nsDisplayList.h"
+#include "nsITheme.h"
+#include "nsIFrame.h"
+#include "mozilla/dom/Element.h"
+
+#include "gfxUtils.h"
+#include "mozilla/layers/RenderRootStateManager.h"
+
+using namespace mozilla;
+using namespace mozilla::image;
+using namespace mozilla::layers;
+
+namespace mozilla {
+class nsDisplayButtonBoxShadowOuter;
+class nsDisplayButtonBorder;
+class nsDisplayButtonForeground;
+} // namespace mozilla
+
+nsButtonFrameRenderer::nsButtonFrameRenderer() : mFrame(nullptr) {
+ MOZ_COUNT_CTOR(nsButtonFrameRenderer);
+}
+
+nsButtonFrameRenderer::~nsButtonFrameRenderer() {
+ MOZ_COUNT_DTOR(nsButtonFrameRenderer);
+}
+
+void nsButtonFrameRenderer::SetFrame(nsIFrame* aFrame,
+ nsPresContext* aPresContext) {
+ mFrame = aFrame;
+ ReResolveStyles(aPresContext);
+}
+
+nsIFrame* nsButtonFrameRenderer::GetFrame() { return mFrame; }
+
+void nsButtonFrameRenderer::SetDisabled(bool aDisabled, bool aNotify) {
+ dom::Element* element = mFrame->GetContent()->AsElement();
+ if (aDisabled)
+ element->SetAttr(kNameSpaceID_None, nsGkAtoms::disabled, u""_ns, aNotify);
+ else
+ element->UnsetAttr(kNameSpaceID_None, nsGkAtoms::disabled, aNotify);
+}
+
+bool nsButtonFrameRenderer::isDisabled() {
+ return mFrame->GetContent()->AsElement()->IsDisabled();
+}
+
+nsresult nsButtonFrameRenderer::DisplayButton(nsDisplayListBuilder* aBuilder,
+ nsDisplayList* aBackground,
+ nsDisplayList* aForeground) {
+ if (!mFrame->StyleEffects()->mBoxShadow.IsEmpty()) {
+ aBackground->AppendNewToTop<nsDisplayButtonBoxShadowOuter>(aBuilder,
+ GetFrame());
+ }
+
+ nsRect buttonRect =
+ mFrame->GetRectRelativeToSelf() + aBuilder->ToReferenceFrame(mFrame);
+
+ const AppendedBackgroundType result =
+ nsDisplayBackgroundImage::AppendBackgroundItemsToTop(
+ aBuilder, mFrame, buttonRect, aBackground);
+ if (result == AppendedBackgroundType::None) {
+ aBuilder->BuildCompositorHitTestInfoIfNeeded(GetFrame(), aBackground);
+ }
+
+ aBackground->AppendNewToTop<nsDisplayButtonBorder>(aBuilder, GetFrame(),
+ this);
+
+ // Only display focus rings if we actually have them. Since at most one
+ // button would normally display a focus ring, most buttons won't have them.
+ if (mInnerFocusStyle && mInnerFocusStyle->StyleBorder()->HasBorder() &&
+ mFrame->IsThemed() &&
+ mFrame->PresContext()->Theme()->ThemeWantsButtonInnerFocusRing()) {
+ aForeground->AppendNewToTop<nsDisplayButtonForeground>(aBuilder, GetFrame(),
+ this);
+ }
+ return NS_OK;
+}
+
+void nsButtonFrameRenderer::GetButtonInnerFocusRect(const nsRect& aRect,
+ nsRect& aResult) {
+ aResult = aRect;
+ aResult.Deflate(mFrame->GetUsedBorderAndPadding());
+
+ if (mInnerFocusStyle) {
+ nsMargin innerFocusPadding(0, 0, 0, 0);
+ mInnerFocusStyle->StylePadding()->GetPadding(innerFocusPadding);
+
+ nsMargin framePadding = mFrame->GetUsedPadding();
+
+ innerFocusPadding.top = std::min(innerFocusPadding.top, framePadding.top);
+ innerFocusPadding.right =
+ std::min(innerFocusPadding.right, framePadding.right);
+ innerFocusPadding.bottom =
+ std::min(innerFocusPadding.bottom, framePadding.bottom);
+ innerFocusPadding.left =
+ std::min(innerFocusPadding.left, framePadding.left);
+
+ aResult.Inflate(innerFocusPadding);
+ }
+}
+
+ImgDrawResult nsButtonFrameRenderer::PaintInnerFocusBorder(
+ nsDisplayListBuilder* aBuilder, nsPresContext* aPresContext,
+ gfxContext& aRenderingContext, const nsRect& aDirtyRect,
+ const nsRect& aRect) {
+ // we draw the -moz-focus-inner border just inside the button's
+ // normal border and padding, to match Windows themes.
+
+ nsRect rect;
+
+ PaintBorderFlags flags = aBuilder->ShouldSyncDecodeImages()
+ ? PaintBorderFlags::SyncDecodeImages
+ : PaintBorderFlags();
+
+ ImgDrawResult result = ImgDrawResult::SUCCESS;
+
+ if (mInnerFocusStyle) {
+ GetButtonInnerFocusRect(aRect, rect);
+
+ result &=
+ nsCSSRendering::PaintBorder(aPresContext, aRenderingContext, mFrame,
+ aDirtyRect, rect, mInnerFocusStyle, flags);
+ }
+
+ return result;
+}
+
+Maybe<nsCSSBorderRenderer>
+nsButtonFrameRenderer::CreateInnerFocusBorderRenderer(
+ nsDisplayListBuilder* aBuilder, nsPresContext* aPresContext,
+ gfxContext* aRenderingContext, const nsRect& aDirtyRect,
+ const nsRect& aRect, bool* aBorderIsEmpty) {
+ if (mInnerFocusStyle) {
+ nsRect rect;
+ GetButtonInnerFocusRect(aRect, rect);
+
+ gfx::DrawTarget* dt =
+ aRenderingContext ? aRenderingContext->GetDrawTarget() : nullptr;
+ return nsCSSRendering::CreateBorderRenderer(
+ aPresContext, dt, mFrame, aDirtyRect, rect, mInnerFocusStyle,
+ aBorderIsEmpty);
+ }
+
+ return Nothing();
+}
+
+ImgDrawResult nsButtonFrameRenderer::PaintBorder(nsDisplayListBuilder* aBuilder,
+ nsPresContext* aPresContext,
+ gfxContext& aRenderingContext,
+ const nsRect& aDirtyRect,
+ const nsRect& aRect) {
+ // get the button rect this is inside the focus and outline rects
+ nsRect buttonRect = aRect;
+ ComputedStyle* context = mFrame->Style();
+
+ PaintBorderFlags borderFlags = aBuilder->ShouldSyncDecodeImages()
+ ? PaintBorderFlags::SyncDecodeImages
+ : PaintBorderFlags();
+
+ nsCSSRendering::PaintBoxShadowInner(aPresContext, aRenderingContext, mFrame,
+ buttonRect);
+
+ ImgDrawResult result =
+ nsCSSRendering::PaintBorder(aPresContext, aRenderingContext, mFrame,
+ aDirtyRect, buttonRect, context, borderFlags);
+
+ return result;
+}
+
+/**
+ * Call this when styles change
+ */
+void nsButtonFrameRenderer::ReResolveStyles(nsPresContext* aPresContext) {
+ // get all the styles
+ ServoStyleSet* styleSet = aPresContext->StyleSet();
+
+ // get styles assigned to -moz-focus-inner (ie dotted border on Windows)
+ mInnerFocusStyle = styleSet->ProbePseudoElementStyle(
+ *mFrame->GetContent()->AsElement(), PseudoStyleType::mozFocusInner,
+ nullptr, mFrame->Style());
+}
+
+ComputedStyle* nsButtonFrameRenderer::GetComputedStyle(int32_t aIndex) const {
+ switch (aIndex) {
+ case NS_BUTTON_RENDERER_FOCUS_INNER_CONTEXT_INDEX:
+ return mInnerFocusStyle;
+ default:
+ return nullptr;
+ }
+}
+
+void nsButtonFrameRenderer::SetComputedStyle(int32_t aIndex,
+ ComputedStyle* aComputedStyle) {
+ switch (aIndex) {
+ case NS_BUTTON_RENDERER_FOCUS_INNER_CONTEXT_INDEX:
+ mInnerFocusStyle = aComputedStyle;
+ break;
+ }
+}
+
+namespace mozilla {
+
+class nsDisplayButtonBoxShadowOuter : public nsPaintedDisplayItem {
+ public:
+ nsDisplayButtonBoxShadowOuter(nsDisplayListBuilder* aBuilder,
+ nsIFrame* aFrame)
+ : nsPaintedDisplayItem(aBuilder, aFrame) {
+ MOZ_COUNT_CTOR(nsDisplayButtonBoxShadowOuter);
+ }
+ MOZ_COUNTED_DTOR_OVERRIDE(nsDisplayButtonBoxShadowOuter)
+
+ virtual bool CreateWebRenderCommands(
+ mozilla::wr::DisplayListBuilder& aBuilder,
+ mozilla::wr::IpcResourceUpdateQueue& aResources,
+ const StackingContextHelper& aSc,
+ mozilla::layers::RenderRootStateManager* aManager,
+ nsDisplayListBuilder* aDisplayListBuilder) override;
+
+ bool CanBuildWebRenderDisplayItems();
+
+ virtual void Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) override;
+ virtual nsRect GetBounds(nsDisplayListBuilder* aBuilder,
+ bool* aSnap) const override;
+ NS_DISPLAY_DECL_NAME("ButtonBoxShadowOuter", TYPE_BUTTON_BOX_SHADOW_OUTER)
+};
+
+nsRect nsDisplayButtonBoxShadowOuter::GetBounds(nsDisplayListBuilder* aBuilder,
+ bool* aSnap) const {
+ *aSnap = false;
+ return mFrame->InkOverflowRectRelativeToSelf() + ToReferenceFrame();
+}
+
+void nsDisplayButtonBoxShadowOuter::Paint(nsDisplayListBuilder* aBuilder,
+ gfxContext* aCtx) {
+ nsRect frameRect = nsRect(ToReferenceFrame(), mFrame->GetSize());
+
+ nsCSSRendering::PaintBoxShadowOuter(mFrame->PresContext(), *aCtx, mFrame,
+ frameRect, GetPaintRect(aBuilder, aCtx));
+}
+
+bool nsDisplayButtonBoxShadowOuter::CanBuildWebRenderDisplayItems() {
+ // FIXME(emilio): Is this right? That doesn't make much sense.
+ if (mFrame->StyleEffects()->mBoxShadow.IsEmpty()) {
+ return false;
+ }
+
+ bool hasBorderRadius;
+ bool nativeTheme =
+ nsCSSRendering::HasBoxShadowNativeTheme(mFrame, hasBorderRadius);
+
+ // We don't support native themed things yet like box shadows around
+ // input buttons.
+ return !nativeTheme;
+}
+
+bool nsDisplayButtonBoxShadowOuter::CreateWebRenderCommands(
+ mozilla::wr::DisplayListBuilder& aBuilder,
+ mozilla::wr::IpcResourceUpdateQueue& aResources,
+ const StackingContextHelper& aSc,
+ mozilla::layers::RenderRootStateManager* aManager,
+ nsDisplayListBuilder* aDisplayListBuilder) {
+ if (!CanBuildWebRenderDisplayItems()) {
+ return false;
+ }
+ int32_t appUnitsPerDevPixel = mFrame->PresContext()->AppUnitsPerDevPixel();
+ nsRect shadowRect = nsRect(ToReferenceFrame(), mFrame->GetSize());
+ LayoutDeviceRect deviceBox =
+ LayoutDeviceRect::FromAppUnits(shadowRect, appUnitsPerDevPixel);
+ wr::LayoutRect deviceBoxRect = wr::ToLayoutRect(deviceBox);
+
+ bool dummy;
+ LayoutDeviceRect clipRect = LayoutDeviceRect::FromAppUnits(
+ GetBounds(aDisplayListBuilder, &dummy), appUnitsPerDevPixel);
+ wr::LayoutRect deviceClipRect = wr::ToLayoutRect(clipRect);
+
+ bool hasBorderRadius;
+ Unused << nsCSSRendering::HasBoxShadowNativeTheme(mFrame, hasBorderRadius);
+
+ LayoutDeviceSize zeroSize;
+ wr::BorderRadius borderRadius =
+ wr::ToBorderRadius(zeroSize, zeroSize, zeroSize, zeroSize);
+ if (hasBorderRadius) {
+ gfx::RectCornerRadii borderRadii;
+ hasBorderRadius = nsCSSRendering::GetBorderRadii(shadowRect, shadowRect,
+ mFrame, borderRadii);
+ if (hasBorderRadius) {
+ borderRadius = wr::ToBorderRadius(borderRadii);
+ }
+ }
+
+ const Span<const StyleBoxShadow> shadows =
+ mFrame->StyleEffects()->mBoxShadow.AsSpan();
+ MOZ_ASSERT(!shadows.IsEmpty());
+
+ for (const StyleBoxShadow& shadow : Reversed(shadows)) {
+ if (shadow.inset) {
+ continue;
+ }
+ float blurRadius =
+ float(shadow.base.blur.ToAppUnits()) / float(appUnitsPerDevPixel);
+ gfx::DeviceColor shadowColor =
+ ToDeviceColor(nsCSSRendering::GetShadowColor(shadow.base, mFrame, 1.0));
+
+ LayoutDevicePoint shadowOffset = LayoutDevicePoint::FromAppUnits(
+ nsPoint(shadow.base.horizontal.ToAppUnits(),
+ shadow.base.vertical.ToAppUnits()),
+ appUnitsPerDevPixel);
+
+ float spreadRadius =
+ float(shadow.spread.ToAppUnits()) / float(appUnitsPerDevPixel);
+
+ aBuilder.PushBoxShadow(deviceBoxRect, deviceClipRect, !BackfaceIsHidden(),
+ deviceBoxRect, wr::ToLayoutVector2D(shadowOffset),
+ wr::ToColorF(shadowColor), blurRadius, spreadRadius,
+ borderRadius, wr::BoxShadowClipMode::Outset);
+ }
+ return true;
+}
+
+class nsDisplayButtonBorder final : public nsPaintedDisplayItem {
+ public:
+ nsDisplayButtonBorder(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
+ nsButtonFrameRenderer* aRenderer)
+ : nsPaintedDisplayItem(aBuilder, aFrame), mBFR(aRenderer) {
+ MOZ_COUNT_CTOR(nsDisplayButtonBorder);
+ }
+ MOZ_COUNTED_DTOR_OVERRIDE(nsDisplayButtonBorder)
+
+ virtual void HitTest(nsDisplayListBuilder* aBuilder, const nsRect& aRect,
+ HitTestState* aState,
+ nsTArray<nsIFrame*>* aOutFrames) override {
+ aOutFrames->AppendElement(mFrame);
+ }
+ virtual void Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) override;
+ virtual nsRect GetBounds(nsDisplayListBuilder* aBuilder,
+ bool* aSnap) const override;
+ virtual bool CreateWebRenderCommands(
+ mozilla::wr::DisplayListBuilder& aBuilder,
+ mozilla::wr::IpcResourceUpdateQueue& aResources,
+ const StackingContextHelper& aSc,
+ mozilla::layers::RenderRootStateManager* aManager,
+ nsDisplayListBuilder* aDisplayListBuilder) override;
+ NS_DISPLAY_DECL_NAME("ButtonBorderBackground", TYPE_BUTTON_BORDER_BACKGROUND)
+ private:
+ nsButtonFrameRenderer* mBFR;
+};
+
+bool nsDisplayButtonBorder::CreateWebRenderCommands(
+ mozilla::wr::DisplayListBuilder& aBuilder,
+ mozilla::wr::IpcResourceUpdateQueue& aResources,
+ const StackingContextHelper& aSc,
+ mozilla::layers::RenderRootStateManager* aManager,
+ nsDisplayListBuilder* aDisplayListBuilder) {
+ // This is really a combination of paint box shadow inner +
+ // paint border.
+ aBuilder.StartGroup(this);
+ const nsRect buttonRect = nsRect(ToReferenceFrame(), mFrame->GetSize());
+ bool snap;
+ nsRect visible = GetBounds(aDisplayListBuilder, &snap);
+ nsDisplayBoxShadowInner::CreateInsetBoxShadowWebRenderCommands(
+ aBuilder, aSc, visible, mFrame, buttonRect);
+
+ bool borderIsEmpty = false;
+ Maybe<nsCSSBorderRenderer> br = nsCSSRendering::CreateBorderRenderer(
+ mFrame->PresContext(), nullptr, mFrame, nsRect(),
+ nsRect(ToReferenceFrame(), mFrame->GetSize()), mFrame->Style(),
+ &borderIsEmpty, mFrame->GetSkipSides());
+ if (!br) {
+ if (borderIsEmpty) {
+ aBuilder.FinishGroup();
+ } else {
+ aBuilder.CancelGroup(true);
+ }
+
+ return borderIsEmpty;
+ }
+
+ br->CreateWebRenderCommands(this, aBuilder, aResources, aSc);
+ aBuilder.FinishGroup();
+ return true;
+}
+
+void nsDisplayButtonBorder::Paint(nsDisplayListBuilder* aBuilder,
+ gfxContext* aCtx) {
+ NS_ASSERTION(mFrame, "No frame?");
+ nsPresContext* pc = mFrame->PresContext();
+ nsRect r = nsRect(ToReferenceFrame(), mFrame->GetSize());
+
+ // draw the border and background inside the focus and outline borders
+ Unused << mBFR->PaintBorder(aBuilder, pc, *aCtx, GetPaintRect(aBuilder, aCtx),
+ r);
+}
+
+nsRect nsDisplayButtonBorder::GetBounds(nsDisplayListBuilder* aBuilder,
+ bool* aSnap) const {
+ *aSnap = false;
+ return aBuilder->IsForEventDelivery()
+ ? nsRect(ToReferenceFrame(), mFrame->GetSize())
+ : mFrame->InkOverflowRectRelativeToSelf() + ToReferenceFrame();
+}
+
+class nsDisplayButtonForeground final : public nsPaintedDisplayItem {
+ public:
+ nsDisplayButtonForeground(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
+ nsButtonFrameRenderer* aRenderer)
+ : nsPaintedDisplayItem(aBuilder, aFrame), mBFR(aRenderer) {
+ MOZ_COUNT_CTOR(nsDisplayButtonForeground);
+ }
+ MOZ_COUNTED_DTOR_OVERRIDE(nsDisplayButtonForeground)
+
+ void Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) override;
+ bool CreateWebRenderCommands(
+ mozilla::wr::DisplayListBuilder& aBuilder,
+ mozilla::wr::IpcResourceUpdateQueue& aResources,
+ const StackingContextHelper& aSc,
+ mozilla::layers::RenderRootStateManager* aManager,
+ nsDisplayListBuilder* aDisplayListBuilder) override;
+ NS_DISPLAY_DECL_NAME("ButtonForeground", TYPE_BUTTON_FOREGROUND)
+ private:
+ nsButtonFrameRenderer* mBFR;
+};
+
+void nsDisplayButtonForeground::Paint(nsDisplayListBuilder* aBuilder,
+ gfxContext* aCtx) {
+ nsRect r = nsRect(ToReferenceFrame(), mFrame->GetSize());
+
+ // Draw the -moz-focus-inner border
+ Unused << mBFR->PaintInnerFocusBorder(aBuilder, mFrame->PresContext(), *aCtx,
+ GetPaintRect(aBuilder, aCtx), r);
+}
+
+bool nsDisplayButtonForeground::CreateWebRenderCommands(
+ mozilla::wr::DisplayListBuilder& aBuilder,
+ mozilla::wr::IpcResourceUpdateQueue& aResources,
+ const StackingContextHelper& aSc,
+ mozilla::layers::RenderRootStateManager* aManager,
+ nsDisplayListBuilder* aDisplayListBuilder) {
+ Maybe<nsCSSBorderRenderer> br;
+ bool borderIsEmpty = false;
+ bool dummy;
+ nsRect r = nsRect(ToReferenceFrame(), mFrame->GetSize());
+ br = mBFR->CreateInnerFocusBorderRenderer(
+ aDisplayListBuilder, mFrame->PresContext(), nullptr,
+ GetBounds(aDisplayListBuilder, &dummy), r, &borderIsEmpty);
+
+ if (!br) {
+ return borderIsEmpty;
+ }
+
+ aBuilder.StartGroup(this);
+ br->CreateWebRenderCommands(this, aBuilder, aResources, aSc);
+ aBuilder.FinishGroup();
+
+ return true;
+}
+
+} // namespace mozilla