diff options
Diffstat (limited to 'layout/forms/nsButtonFrameRenderer.cpp')
-rw-r--r-- | layout/forms/nsButtonFrameRenderer.cpp | 528 |
1 files changed, 528 insertions, 0 deletions
diff --git a/layout/forms/nsButtonFrameRenderer.cpp b/layout/forms/nsButtonFrameRenderer.cpp new file mode 100644 index 0000000000..647c2c0dd9 --- /dev/null +++ b/layout/forms/nsButtonFrameRenderer.cpp @@ -0,0 +1,528 @@ +/* -*- 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/EventStates.h" +#include "mozilla/dom/Element.h" +#include "Layers.h" + +#include "gfxUtils.h" +#include "mozilla/layers/RenderRootStateManager.h" + +#define ACTIVE "active" +#define HOVER "hover" +#define FOCUS "focus" + +using namespace mozilla; +using namespace mozilla::image; +using namespace mozilla::layers; + +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(); +} + +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()); +} + +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. + if (nativeTheme) { + return false; + } + + return true; +} + +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); + + LayoutDeviceRect clipRect = + LayoutDeviceRect::FromAppUnits(GetPaintRect(), 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) { + mozilla::gfx::RectCornerRadii borderRadii; + hasBorderRadius = nsCSSRendering::GetBorderRadii(shadowRect, shadowRect, + mFrame, borderRadii); + if (hasBorderRadius) { + borderRadius = wr::ToBorderRadius( + LayoutDeviceSize::FromUnknownSize(borderRadii.TopLeft()), + LayoutDeviceSize::FromUnknownSize(borderRadii.TopRight()), + LayoutDeviceSize::FromUnknownSize(borderRadii.BottomLeft()), + LayoutDeviceSize::FromUnknownSize(borderRadii.BottomRight())); + } + } + + 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 bool MustPaintOnContentSide() const override { return true; } + + 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 nsDisplayItemGeometry* AllocateGeometry( + nsDisplayListBuilder* aBuilder) override; + virtual void ComputeInvalidationRegion( + nsDisplayListBuilder* aBuilder, const nsDisplayItemGeometry* aGeometry, + nsRegion* aInvalidRegion) 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; +}; + +nsDisplayItemGeometry* nsDisplayButtonBorder::AllocateGeometry( + nsDisplayListBuilder* aBuilder) { + return new nsDisplayItemGenericImageGeometry(this, aBuilder); +} + +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; + nsRegion 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::ComputeInvalidationRegion( + nsDisplayListBuilder* aBuilder, const nsDisplayItemGeometry* aGeometry, + nsRegion* aInvalidRegion) const { + auto geometry = + static_cast<const nsDisplayItemGenericImageGeometry*>(aGeometry); + + if (aBuilder->ShouldSyncDecodeImages() && + geometry->ShouldInvalidateToSyncDecodeImages()) { + bool snap; + aInvalidRegion->Or(*aInvalidRegion, GetBounds(aBuilder, &snap)); + } + + nsDisplayItem::ComputeInvalidationRegion(aBuilder, aGeometry, aInvalidRegion); +} + +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 + ImgDrawResult result = + mBFR->PaintBorder(aBuilder, pc, *aCtx, GetPaintRect(), r); + + nsDisplayItemGenericImageGeometry::UpdateDrawResult(this, result); +} + +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) + + nsDisplayItemGeometry* AllocateGeometry( + nsDisplayListBuilder* aBuilder) override; + void ComputeInvalidationRegion(nsDisplayListBuilder* aBuilder, + const nsDisplayItemGeometry* aGeometry, + nsRegion* aInvalidRegion) const override; + 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; +}; + +nsDisplayItemGeometry* nsDisplayButtonForeground::AllocateGeometry( + nsDisplayListBuilder* aBuilder) { + return new nsDisplayItemGenericImageGeometry(this, aBuilder); +} + +void nsDisplayButtonForeground::ComputeInvalidationRegion( + nsDisplayListBuilder* aBuilder, const nsDisplayItemGeometry* aGeometry, + nsRegion* aInvalidRegion) const { + auto geometry = + static_cast<const nsDisplayItemGenericImageGeometry*>(aGeometry); + + if (aBuilder->ShouldSyncDecodeImages() && + geometry->ShouldInvalidateToSyncDecodeImages()) { + bool snap; + aInvalidRegion->Or(*aInvalidRegion, GetBounds(aBuilder, &snap)); + } + + nsDisplayItem::ComputeInvalidationRegion(aBuilder, aGeometry, aInvalidRegion); +} + +void nsDisplayButtonForeground::Paint(nsDisplayListBuilder* aBuilder, + gfxContext* aCtx) { + nsRect r = nsRect(ToReferenceFrame(), mFrame->GetSize()); + + // Draw the -moz-focus-inner border + ImgDrawResult result = mBFR->PaintInnerFocusBorder( + aBuilder, mFrame->PresContext(), *aCtx, GetPaintRect(), r); + + nsDisplayItemGenericImageGeometry::UpdateDrawResult(this, result); +} + +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; + nsRect r = nsRect(ToReferenceFrame(), mFrame->GetSize()); + br = mBFR->CreateInnerFocusBorderRenderer(aDisplayListBuilder, + mFrame->PresContext(), nullptr, + GetPaintRect(), r, &borderIsEmpty); + + if (!br) { + return borderIsEmpty; + } + + aBuilder.StartGroup(this); + br->CreateWebRenderCommands(this, aBuilder, aResources, aSc); + aBuilder.FinishGroup(); + + return true; +} + +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); + + nsDisplayBackgroundImage::AppendBackgroundItemsToTop(aBuilder, mFrame, + buttonRect, 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. + const auto* disp = mFrame->StyleDisplay(); + nsPresContext* pc = mFrame->PresContext(); + if (mInnerFocusStyle && mInnerFocusStyle->StyleBorder()->HasBorder() && + mFrame->IsThemed(disp) && + pc->Theme()->ThemeWantsButtonInnerFocusRing( + disp->EffectiveAppearance())) { + 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, + 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; + } +} |