Source code

Revision control

Other Tools

/* -*- 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/. */
/* utility functions for drawing borders and backgrounds */
#include <ctime>
#include "gfx2DGlue.h"
#include "gfxContext.h"
#include "mozilla/ArrayUtils.h"
#include "mozilla/ComputedStyle.h"
#include "mozilla/DebugOnly.h"
#include "mozilla/StaticPrefs_layout.h"
#include "mozilla/gfx/2D.h"
#include "mozilla/gfx/Helpers.h"
#include "mozilla/gfx/PathHelpers.h"
#include "mozilla/HashFunctions.h"
#include "mozilla/MathAlgorithms.h"
#include "mozilla/PresShell.h"
#include "mozilla/SVGImageContext.h"
#include "gfxFont.h"
#include "ScaledFontBase.h"
#include "skia/include/core/SkTextBlob.h"
#include "BorderConsts.h"
#include "nsStyleConsts.h"
#include "nsPresContext.h"
#include "nsIFrame.h"
#include "nsIFrameInlines.h"
#include "nsPoint.h"
#include "nsRect.h"
#include "nsFrameManager.h"
#include "nsGkAtoms.h"
#include "nsCSSAnonBoxes.h"
#include "nsIContent.h"
#include "mozilla/dom/DocumentInlines.h"
#include "nsIScrollableFrame.h"
#include "imgIContainer.h"
#include "ImageOps.h"
#include "nsCSSRendering.h"
#include "nsCSSColorUtils.h"
#include "nsITheme.h"
#include "nsLayoutUtils.h"
#include "nsBlockFrame.h"
#include "nsStyleStructInlines.h"
#include "nsCSSFrameConstructor.h"
#include "nsCSSProps.h"
#include "nsContentUtils.h"
#include "gfxDrawable.h"
#include "nsCSSRenderingBorders.h"
#include "mozilla/css/ImageLoader.h"
#include "ImageContainer.h"
#include "mozilla/ProfilerLabels.h"
#include "mozilla/StaticPrefs_layout.h"
#include "mozilla/Telemetry.h"
#include "gfxUtils.h"
#include "gfxGradientCache.h"
#include "nsInlineFrame.h"
#include "nsRubyTextContainerFrame.h"
#include <algorithm>
#include "TextDrawTarget.h"
using namespace mozilla;
using namespace mozilla::css;
using namespace mozilla::gfx;
using namespace mozilla::image;
using mozilla::CSSSizeOrRatio;
using mozilla::dom::Document;
static int gFrameTreeLockCount = 0;
// To avoid storing this data on nsInlineFrame (bloat) and to avoid
// recalculating this for each frame in a continuation (perf), hold
// a cache of various coordinate information that we need in order
// to paint inline backgrounds.
struct InlineBackgroundData {
InlineBackgroundData()
: mFrame(nullptr),
mLineContainer(nullptr),
mContinuationPoint(0),
mUnbrokenMeasure(0),
mLineContinuationPoint(0),
mPIStartBorderData{},
mBidiEnabled(false),
mVertical(false) {}
~InlineBackgroundData() = default;
void Reset() {
mBoundingBox.SetRect(0, 0, 0, 0);
mContinuationPoint = mLineContinuationPoint = mUnbrokenMeasure = 0;
mFrame = mLineContainer = nullptr;
mPIStartBorderData.Reset();
}
/**
* Return a continuous rect for (an inline) aFrame relative to the
* continuation that draws the left-most part of the background.
* This is used when painting backgrounds.
*/
nsRect GetContinuousRect(nsIFrame* aFrame) {
MOZ_ASSERT(static_cast<nsInlineFrame*>(do_QueryFrame(aFrame)));
SetFrame(aFrame);
nscoord pos; // an x coordinate if writing-mode is horizontal;
// y coordinate if vertical
if (mBidiEnabled) {
pos = mLineContinuationPoint;
// Scan continuations on the same line as aFrame and accumulate the widths
// of frames that are to the left (if this is an LTR block) or right
// (if it's RTL) of the current one.
bool isRtlBlock = (mLineContainer->StyleVisibility()->mDirection ==
StyleDirection::Rtl);
nscoord curOffset = mVertical ? aFrame->GetOffsetTo(mLineContainer).y
: aFrame->GetOffsetTo(mLineContainer).x;
// If the continuation is fluid we know inlineFrame is not on the same
// line. If it's not fluid, we need to test further to be sure.
nsIFrame* inlineFrame = aFrame->GetPrevContinuation();
while (inlineFrame && !inlineFrame->GetNextInFlow() &&
AreOnSameLine(aFrame, inlineFrame)) {
nscoord frameOffset = mVertical
? inlineFrame->GetOffsetTo(mLineContainer).y
: inlineFrame->GetOffsetTo(mLineContainer).x;
if (isRtlBlock == (frameOffset >= curOffset)) {
pos += mVertical ? inlineFrame->GetSize().height
: inlineFrame->GetSize().width;
}
inlineFrame = inlineFrame->GetPrevContinuation();
}
inlineFrame = aFrame->GetNextContinuation();
while (inlineFrame && !inlineFrame->GetPrevInFlow() &&
AreOnSameLine(aFrame, inlineFrame)) {
nscoord frameOffset = mVertical
? inlineFrame->GetOffsetTo(mLineContainer).y
: inlineFrame->GetOffsetTo(mLineContainer).x;
if (isRtlBlock == (frameOffset >= curOffset)) {
pos += mVertical ? inlineFrame->GetSize().height
: inlineFrame->GetSize().width;
}
inlineFrame = inlineFrame->GetNextContinuation();
}
if (isRtlBlock) {
// aFrame itself is also to the right of its left edge, so add its
// width.
pos += mVertical ? aFrame->GetSize().height : aFrame->GetSize().width;
// pos is now the distance from the left [top] edge of aFrame to the
// right [bottom] edge of the unbroken content. Change it to indicate
// the distance from the left [top] edge of the unbroken content to the
// left [top] edge of aFrame.
pos = mUnbrokenMeasure - pos;
}
} else {
pos = mContinuationPoint;
}
// Assume background-origin: border and return a rect with offsets
// relative to (0,0). If we have a different background-origin,
// then our rect should be deflated appropriately by our caller.
return mVertical
? nsRect(0, -pos, mFrame->GetSize().width, mUnbrokenMeasure)
: nsRect(-pos, 0, mUnbrokenMeasure, mFrame->GetSize().height);
}
/**
* Return a continuous rect for (an inline) aFrame relative to the
* continuation that should draw the left[top]-border. This is used when
* painting borders and clipping backgrounds. This may NOT be the same
* continuous rect as for drawing backgrounds; the continuation with the
* left[top]-border might be somewhere in the middle of that rect (e.g. BIDI),
* in those cases we need the reverse background order starting at the
* left[top]-border continuation.
*/
nsRect GetBorderContinuousRect(nsIFrame* aFrame, nsRect aBorderArea) {
// Calling GetContinuousRect(aFrame) here may lead to Reset/Init which
// resets our mPIStartBorderData so we save it ...
PhysicalInlineStartBorderData saved(mPIStartBorderData);
nsRect joinedBorderArea = GetContinuousRect(aFrame);
if (!saved.mIsValid || saved.mFrame != mPIStartBorderData.mFrame) {
if (aFrame == mPIStartBorderData.mFrame) {
if (mVertical) {
mPIStartBorderData.SetCoord(joinedBorderArea.y);
} else {
mPIStartBorderData.SetCoord(joinedBorderArea.x);
}
} else if (mPIStartBorderData.mFrame) {
// Copy data to a temporary object so that computing the
// continous rect here doesn't clobber our normal state.
InlineBackgroundData temp = *this;
if (mVertical) {
mPIStartBorderData.SetCoord(
temp.GetContinuousRect(mPIStartBorderData.mFrame).y);
} else {
mPIStartBorderData.SetCoord(
temp.GetContinuousRect(mPIStartBorderData.mFrame).x);
}
}
} else {
// ... and restore it when possible.
mPIStartBorderData.SetCoord(saved.mCoord);
}
if (mVertical) {
if (joinedBorderArea.y > mPIStartBorderData.mCoord) {
joinedBorderArea.y =
-(mUnbrokenMeasure + joinedBorderArea.y - aBorderArea.height);
} else {
joinedBorderArea.y -= mPIStartBorderData.mCoord;
}
} else {
if (joinedBorderArea.x > mPIStartBorderData.mCoord) {
joinedBorderArea.x =
-(mUnbrokenMeasure + joinedBorderArea.x - aBorderArea.width);
} else {
joinedBorderArea.x -= mPIStartBorderData.mCoord;
}
}
return joinedBorderArea;
}
nsRect GetBoundingRect(nsIFrame* aFrame) {
SetFrame(aFrame);
// Move the offsets relative to (0,0) which puts the bounding box into
// our coordinate system rather than our parent's. We do this by
// moving it the back distance from us to the bounding box.
// This also assumes background-origin: border, so our caller will
// need to deflate us if needed.
nsRect boundingBox(mBoundingBox);
nsPoint point = mFrame->GetPosition();
boundingBox.MoveBy(-point.x, -point.y);
return boundingBox;
}
protected:
// This is a coordinate on the inline axis, but is not a true logical inline-
// coord because it is always measured from left to right (if horizontal) or
// from top to bottom (if vertical), ignoring any bidi RTL directionality.
// We'll call this "physical inline start", or PIStart for short.
struct PhysicalInlineStartBorderData {
nsIFrame* mFrame; // the continuation that may have a left-border
nscoord mCoord; // cached GetContinuousRect(mFrame).x or .y
bool mIsValid; // true if mCoord is valid
void Reset() {
mFrame = nullptr;
mIsValid = false;
}
void SetCoord(nscoord aCoord) {
mCoord = aCoord;
mIsValid = true;
}
};
nsIFrame* mFrame;
nsIFrame* mLineContainer;
nsRect mBoundingBox;
nscoord mContinuationPoint;
nscoord mUnbrokenMeasure;
nscoord mLineContinuationPoint;
PhysicalInlineStartBorderData mPIStartBorderData;
bool mBidiEnabled;
bool mVertical;
void SetFrame(nsIFrame* aFrame) {
MOZ_ASSERT(aFrame, "Need a frame");
NS_ASSERTION(gFrameTreeLockCount > 0,
"Can't call this when frame tree is not locked");
if (aFrame == mFrame) {
return;
}
nsIFrame* prevContinuation = GetPrevContinuation(aFrame);
if (!prevContinuation || mFrame != prevContinuation) {
// Ok, we've got the wrong frame. We have to start from scratch.
Reset();
Init(aFrame);
return;
}
// Get our last frame's size and add its width to our continuation
// point before we cache the new frame.
mContinuationPoint +=
mVertical ? mFrame->GetSize().height : mFrame->GetSize().width;
// If this a new line, update mLineContinuationPoint.
if (mBidiEnabled &&
(aFrame->GetPrevInFlow() || !AreOnSameLine(mFrame, aFrame))) {
mLineContinuationPoint = mContinuationPoint;
}
mFrame = aFrame;
}
nsIFrame* GetPrevContinuation(nsIFrame* aFrame) {
nsIFrame* prevCont = aFrame->GetPrevContinuation();
if (!prevCont && aFrame->HasAnyStateBits(NS_FRAME_PART_OF_IBSPLIT)) {
nsIFrame* block = aFrame->GetProperty(nsIFrame::IBSplitPrevSibling());
if (block) {
// The {ib} properties are only stored on first continuations
NS_ASSERTION(!block->GetPrevContinuation(),
"Incorrect value for IBSplitPrevSibling");
prevCont = block->GetProperty(nsIFrame::IBSplitPrevSibling());
NS_ASSERTION(prevCont, "How did that happen?");
}
}
return prevCont;
}
nsIFrame* GetNextContinuation(nsIFrame* aFrame) {
nsIFrame* nextCont = aFrame->GetNextContinuation();
if (!nextCont && aFrame->HasAnyStateBits(NS_FRAME_PART_OF_IBSPLIT)) {
// The {ib} properties are only stored on first continuations
aFrame = aFrame->FirstContinuation();
nsIFrame* block = aFrame->GetProperty(nsIFrame::IBSplitSibling());
if (block) {
nextCont = block->GetProperty(nsIFrame::IBSplitSibling());
NS_ASSERTION(nextCont, "How did that happen?");
}
}
return nextCont;
}
void Init(nsIFrame* aFrame) {
mPIStartBorderData.Reset();
mBidiEnabled = aFrame->PresContext()->BidiEnabled();
if (mBidiEnabled) {
// Find the line container frame
mLineContainer = aFrame;
while (mLineContainer &&
mLineContainer->IsFrameOfType(nsIFrame::eLineParticipant)) {
mLineContainer = mLineContainer->GetParent();
}
MOZ_ASSERT(mLineContainer, "Cannot find line containing frame.");
MOZ_ASSERT(mLineContainer != aFrame,
"line container frame "
"should be an ancestor of the target frame.");
}
mVertical = aFrame->GetWritingMode().IsVertical();
// Start with the previous flow frame as our continuation point
// is the total of the widths of the previous frames.
nsIFrame* inlineFrame = GetPrevContinuation(aFrame);
bool changedLines = false;
while (inlineFrame) {
if (!mPIStartBorderData.mFrame &&
!(mVertical ? inlineFrame->GetSkipSides().Top()
: inlineFrame->GetSkipSides().Left())) {
mPIStartBorderData.mFrame = inlineFrame;
}
nsRect rect = inlineFrame->GetRect();
mContinuationPoint += mVertical ? rect.height : rect.width;
if (mBidiEnabled &&
(changedLines || !AreOnSameLine(aFrame, inlineFrame))) {
mLineContinuationPoint += mVertical ? rect.height : rect.width;
changedLines = true;
}
mUnbrokenMeasure += mVertical ? rect.height : rect.width;
mBoundingBox.UnionRect(mBoundingBox, rect);
inlineFrame = GetPrevContinuation(inlineFrame);
}
// Next add this frame and subsequent frames to the bounding box and
// unbroken width.
inlineFrame = aFrame;
while (inlineFrame) {
if (!mPIStartBorderData.mFrame &&
!(mVertical ? inlineFrame->GetSkipSides().Top()
: inlineFrame->GetSkipSides().Left())) {
mPIStartBorderData.mFrame = inlineFrame;
}
nsRect rect = inlineFrame->GetRect();
mUnbrokenMeasure += mVertical ? rect.height : rect.width;
mBoundingBox.UnionRect(mBoundingBox, rect);
inlineFrame = GetNextContinuation(inlineFrame);
}
mFrame = aFrame;
}
bool AreOnSameLine(nsIFrame* aFrame1, nsIFrame* aFrame2) {
if (nsBlockFrame* blockFrame = do_QueryFrame(mLineContainer)) {
bool isValid1, isValid2;
nsBlockInFlowLineIterator it1(blockFrame, aFrame1, &isValid1);
nsBlockInFlowLineIterator it2(blockFrame, aFrame2, &isValid2);
return isValid1 && isValid2 &&
// Make sure aFrame1 and aFrame2 are in the same continuation of
// blockFrame.
it1.GetContainer() == it2.GetContainer() &&
// And on the same line in it
it1.GetLine().get() == it2.GetLine().get();
}
if (nsRubyTextContainerFrame* rtcFrame = do_QueryFrame(mLineContainer)) {
nsBlockFrame* block = nsLayoutUtils::FindNearestBlockAncestor(rtcFrame);
// Ruby text container can only hold one line of text, so if they
// are in the same continuation, they are in the same line. Since
// ruby text containers are bidi isolate, they are never split for
// bidi reordering, which means being in different continuation
// indicates being in different lines.
for (nsIFrame* frame = rtcFrame->FirstContinuation(); frame;
frame = frame->GetNextContinuation()) {
bool isDescendant1 =
nsLayoutUtils::IsProperAncestorFrame(frame, aFrame1, block);
bool isDescendant2 =
nsLayoutUtils::IsProperAncestorFrame(frame, aFrame2, block);
if (isDescendant1 && isDescendant2) {
return true;
}
if (isDescendant1 || isDescendant2) {
return false;
}
}
MOZ_ASSERT_UNREACHABLE("None of the frames is a descendant of this rtc?");
}
MOZ_ASSERT_UNREACHABLE("Do we have any other type of line container?");
return false;
}
};
static InlineBackgroundData* gInlineBGData = nullptr;
// Initialize any static variables used by nsCSSRendering.
void nsCSSRendering::Init() {
NS_ASSERTION(!gInlineBGData, "Init called twice");
gInlineBGData = new InlineBackgroundData();
}
// Clean up any global variables used by nsCSSRendering.
void nsCSSRendering::Shutdown() {
delete gInlineBGData;
gInlineBGData = nullptr;
}
/**
* Make a bevel color
*/
static nscolor MakeBevelColor(mozilla::Side whichSide, StyleBorderStyle style,
nscolor aBorderColor) {
nscolor colors[2];
nscolor theColor;
// Given a background color and a border color
// calculate the color used for the shading
NS_GetSpecial3DColors(colors, aBorderColor);
if ((style == StyleBorderStyle::Outset) ||
(style == StyleBorderStyle::Ridge)) {
// Flip colors for these two border styles
switch (whichSide) {
case eSideBottom:
whichSide = eSideTop;
break;
case eSideRight:
whichSide = eSideLeft;
break;
case eSideTop:
whichSide = eSideBottom;
break;
case eSideLeft:
whichSide = eSideRight;
break;
}
}
switch (whichSide) {
case eSideBottom:
theColor = colors[1];
break;
case eSideRight:
theColor = colors[1];
break;
case eSideTop:
theColor = colors[0];
break;
case eSideLeft:
default:
theColor = colors[0];
break;
}
return theColor;
}
static bool GetRadii(nsIFrame* aForFrame, const nsStyleBorder& aBorder,
const nsRect& aOrigBorderArea, const nsRect& aBorderArea,
nscoord aRadii[8]) {
bool haveRoundedCorners;
nsSize sz = aBorderArea.Size();
nsSize frameSize = aForFrame->GetSize();
if (&aBorder == aForFrame->StyleBorder() &&
frameSize == aOrigBorderArea.Size()) {
haveRoundedCorners = aForFrame->GetBorderRadii(sz, sz, Sides(), aRadii);
} else {
haveRoundedCorners = nsIFrame::ComputeBorderRadii(
aBorder.mBorderRadius, frameSize, sz, Sides(), aRadii);
}
return haveRoundedCorners;
}
static bool GetRadii(nsIFrame* aForFrame, const nsStyleBorder& aBorder,
const nsRect& aOrigBorderArea, const nsRect& aBorderArea,
RectCornerRadii* aBgRadii) {
nscoord radii[8];
bool haveRoundedCorners =
GetRadii(aForFrame, aBorder, aOrigBorderArea, aBorderArea, radii);
if (haveRoundedCorners) {
auto d2a = aForFrame->PresContext()->AppUnitsPerDevPixel();
nsCSSRendering::ComputePixelRadii(radii, d2a, aBgRadii);
}
return haveRoundedCorners;
}
static nsRect JoinBoxesForBlockAxisSlice(nsIFrame* aFrame,
const nsRect& aBorderArea) {
// Inflate the block-axis size as if our continuations were laid out
// adjacent in that axis. Note that we don't touch the inline size.
const auto wm = aFrame->GetWritingMode();
const nsSize dummyContainerSize;
LogicalRect borderArea(wm, aBorderArea, dummyContainerSize);
nscoord bSize = 0;
nsIFrame* f = aFrame->GetNextContinuation();
for (; f; f = f->GetNextContinuation()) {
bSize += f->BSize(wm);
}
borderArea.BSize(wm) += bSize;
bSize = 0;
f = aFrame->GetPrevContinuation();
for (; f; f = f->GetPrevContinuation()) {
bSize += f->BSize(wm);
}
borderArea.BStart(wm) -= bSize;
borderArea.BSize(wm) += bSize;
return borderArea.GetPhysicalRect(wm, dummyContainerSize);
}
/**
* Inflate aBorderArea which is relative to aFrame's origin to calculate
* a hypothetical non-split frame area for all the continuations.
* See "Joining Boxes for 'slice'" in
*/
enum InlineBoxOrder { eForBorder, eForBackground };
static nsRect JoinBoxesForSlice(nsIFrame* aFrame, const nsRect& aBorderArea,
InlineBoxOrder aOrder) {
if (static_cast<nsInlineFrame*>(do_QueryFrame(aFrame))) {
return (aOrder == eForBorder
? gInlineBGData->GetBorderContinuousRect(aFrame, aBorderArea)
: gInlineBGData->GetContinuousRect(aFrame)) +
aBorderArea.TopLeft();
}
return JoinBoxesForBlockAxisSlice(aFrame, aBorderArea);
}
/* static */
bool nsCSSRendering::IsBoxDecorationSlice(const nsStyleBorder& aStyleBorder) {
return aStyleBorder.mBoxDecorationBreak == StyleBoxDecorationBreak::Slice;
}
/* static */
nsRect nsCSSRendering::BoxDecorationRectForBorder(
nsIFrame* aFrame, const nsRect& aBorderArea, Sides aSkipSides,
const nsStyleBorder* aStyleBorder) {
if (!aStyleBorder) {
aStyleBorder = aFrame->StyleBorder();
}
// If aSkipSides.IsEmpty() then there are no continuations, or it's
// a ::first-letter that wants all border sides on the first continuation.
return IsBoxDecorationSlice(*aStyleBorder) && !aSkipSides.IsEmpty()
? ::JoinBoxesForSlice(aFrame, aBorderArea, eForBorder)
: aBorderArea;
}
/* static */
nsRect nsCSSRendering::BoxDecorationRectForBackground(
nsIFrame* aFrame, const nsRect& aBorderArea, Sides aSkipSides,
const nsStyleBorder* aStyleBorder) {
if (!aStyleBorder) {
aStyleBorder = aFrame->StyleBorder();
}
// If aSkipSides.IsEmpty() then there are no continuations, or it's
// a ::first-letter that wants all border sides on the first continuation.
return IsBoxDecorationSlice(*aStyleBorder) && !aSkipSides.IsEmpty()
? ::JoinBoxesForSlice(aFrame, aBorderArea, eForBackground)
: aBorderArea;
}
//----------------------------------------------------------------------
// Thebes Border Rendering Code Start
/*
* Compute the float-pixel radii that should be used for drawing
* this border/outline, given the various input bits.
*/
/* static */
void nsCSSRendering::ComputePixelRadii(const nscoord* aAppUnitsRadii,
nscoord aAppUnitsPerPixel,
RectCornerRadii* oBorderRadii) {
Float radii[8];
for (const auto corner : mozilla::AllPhysicalHalfCorners()) {
radii[corner] = Float(aAppUnitsRadii[corner]) / aAppUnitsPerPixel;
}
(*oBorderRadii)[C_TL] = Size(radii[eCornerTopLeftX], radii[eCornerTopLeftY]);
(*oBorderRadii)[C_TR] =
Size(radii[eCornerTopRightX], radii[eCornerTopRightY]);
(*oBorderRadii)[C_BR] =
Size(radii[eCornerBottomRightX], radii[eCornerBottomRightY]);
(*oBorderRadii)[C_BL] =
Size(radii[eCornerBottomLeftX], radii[eCornerBottomLeftY]);
}
static Maybe<nsStyleBorder> GetBorderIfVisited(const ComputedStyle& aStyle) {
Maybe<nsStyleBorder> result;
// Don't check RelevantLinkVisited here, since we want to take the
// same amount of time whether or not it's true.
const ComputedStyle* styleIfVisited = aStyle.GetStyleIfVisited();
if (MOZ_LIKELY(!styleIfVisited)) {
return result;
}
result.emplace(*aStyle.StyleBorder());
auto& newBorder = result.ref();
for (const auto side : mozilla::AllPhysicalSides()) {
nscolor color = aStyle.GetVisitedDependentColor(
nsStyleBorder::BorderColorFieldFor(side));
newBorder.BorderColorFor(side) = StyleColor::FromColor(color);
}
return result;
}
ImgDrawResult nsCSSRendering::PaintBorder(
nsPresContext* aPresContext, gfxContext& aRenderingContext,
nsIFrame* aForFrame, const nsRect& aDirtyRect, const nsRect& aBorderArea,
ComputedStyle* aStyle, PaintBorderFlags aFlags, Sides aSkipSides) {
AUTO_PROFILER_LABEL("nsCSSRendering::PaintBorder", GRAPHICS);
Maybe<nsStyleBorder> visitedBorder = GetBorderIfVisited(*aStyle);
return PaintBorderWithStyleBorder(
aPresContext, aRenderingContext, aForFrame, aDirtyRect, aBorderArea,
visitedBorder.refOr(*aStyle->StyleBorder()), aStyle, aFlags, aSkipSides);
}
Maybe<nsCSSBorderRenderer> nsCSSRendering::CreateBorderRenderer(
nsPresContext* aPresContext, DrawTarget* aDrawTarget, nsIFrame* aForFrame,
const nsRect& aDirtyRect, const nsRect& aBorderArea, ComputedStyle* aStyle,
bool* aOutBorderIsEmpty, Sides aSkipSides) {
Maybe<nsStyleBorder> visitedBorder = GetBorderIfVisited(*aStyle);
return CreateBorderRendererWithStyleBorder(
aPresContext, aDrawTarget, aForFrame, aDirtyRect, aBorderArea,
visitedBorder.refOr(*aStyle->StyleBorder()), aStyle, aOutBorderIsEmpty,
aSkipSides);
}
ImgDrawResult nsCSSRendering::CreateWebRenderCommandsForBorder(
nsDisplayItem* aItem, nsIFrame* aForFrame, const nsRect& aBorderArea,
mozilla::wr::DisplayListBuilder& aBuilder,
mozilla::wr::IpcResourceUpdateQueue& aResources,
const mozilla::layers::StackingContextHelper& aSc,
mozilla::layers::RenderRootStateManager* aManager,
nsDisplayListBuilder* aDisplayListBuilder) {
const auto* style = aForFrame->Style();
Maybe<nsStyleBorder> visitedBorder = GetBorderIfVisited(*style);
return nsCSSRendering::CreateWebRenderCommandsForBorderWithStyleBorder(
aItem, aForFrame, aBorderArea, aBuilder, aResources, aSc, aManager,
aDisplayListBuilder, visitedBorder.refOr(*style->StyleBorder()));
}
void nsCSSRendering::CreateWebRenderCommandsForNullBorder(
nsDisplayItem* aItem, nsIFrame* aForFrame, const nsRect& aBorderArea,
mozilla::wr::DisplayListBuilder& aBuilder,
mozilla::wr::IpcResourceUpdateQueue& aResources,
const mozilla::layers::StackingContextHelper& aSc,
const nsStyleBorder& aStyleBorder) {
bool borderIsEmpty = false;
Maybe<nsCSSBorderRenderer> br =
nsCSSRendering::CreateNullBorderRendererWithStyleBorder(
aForFrame->PresContext(), nullptr, aForFrame, nsRect(), aBorderArea,
aStyleBorder, aForFrame->Style(), &borderIsEmpty,
aForFrame->GetSkipSides());
if (!borderIsEmpty && br) {
br->CreateWebRenderCommands(aItem, aBuilder, aResources, aSc);
}
}
ImgDrawResult nsCSSRendering::CreateWebRenderCommandsForBorderWithStyleBorder(
nsDisplayItem* aItem, nsIFrame* aForFrame, const nsRect& aBorderArea,
mozilla::wr::DisplayListBuilder& aBuilder,
mozilla::wr::IpcResourceUpdateQueue& aResources,
const mozilla::layers::StackingContextHelper& aSc,
mozilla::layers::RenderRootStateManager* aManager,
nsDisplayListBuilder* aDisplayListBuilder,
const nsStyleBorder& aStyleBorder) {
auto& borderImage = aStyleBorder.mBorderImageSource;
// First try to create commands for simple borders.
if (borderImage.IsNone()) {
CreateWebRenderCommandsForNullBorder(
aItem, aForFrame, aBorderArea, aBuilder, aResources, aSc, aStyleBorder);
return ImgDrawResult::SUCCESS;
}
// Next we try image and gradient borders. Gradients are not supported at
// this very moment.
if (!borderImage.IsImageRequestType()) {
return ImgDrawResult::NOT_SUPPORTED;
}
if (aStyleBorder.mBorderImageRepeatH == StyleBorderImageRepeat::Space ||
aStyleBorder.mBorderImageRepeatV == StyleBorderImageRepeat::Space) {
return ImgDrawResult::NOT_SUPPORTED;
}
uint32_t flags = 0;
if (aDisplayListBuilder->IsPaintingToWindow()) {
flags |= nsImageRenderer::FLAG_PAINTING_TO_WINDOW;
}
if (aDisplayListBuilder->ShouldSyncDecodeImages()) {
flags |= nsImageRenderer::FLAG_SYNC_DECODE_IMAGES;
}
image::ImgDrawResult result;
Maybe<nsCSSBorderImageRenderer> bir =
nsCSSBorderImageRenderer::CreateBorderImageRenderer(
aForFrame->PresContext(), aForFrame, aBorderArea, aStyleBorder,
aItem->GetPaintRect(), aForFrame->GetSkipSides(), flags, &result);
if (!bir) {
// We aren't ready. Try to fallback to the null border image if present but
// return the draw result for the border image renderer.
CreateWebRenderCommandsForNullBorder(
aItem, aForFrame, aBorderArea, aBuilder, aResources, aSc, aStyleBorder);
return result;
}
return bir->CreateWebRenderCommands(aItem, aForFrame, aBuilder, aResources,
aSc, aManager, aDisplayListBuilder);
}
static nsCSSBorderRenderer ConstructBorderRenderer(
nsPresContext* aPresContext, ComputedStyle* aStyle, DrawTarget* aDrawTarget,
nsIFrame* aForFrame, const nsRect& aDirtyRect, const nsRect& aBorderArea,
const nsStyleBorder& aStyleBorder, Sides aSkipSides, bool* aNeedsClip) {
nsMargin border = aStyleBorder.GetComputedBorder();
// Compute the outermost boundary of the area that might be painted.
// Same coordinate space as aBorderArea & aBGClipRect.
nsRect joinedBorderArea = nsCSSRendering::BoxDecorationRectForBorder(
aForFrame, aBorderArea, aSkipSides, &aStyleBorder);
RectCornerRadii bgRadii;
::GetRadii(aForFrame, aStyleBorder, aBorderArea, joinedBorderArea, &bgRadii);
PrintAsFormatString(" joinedBorderArea: %d %d %d %d\n", joinedBorderArea.x,
joinedBorderArea.y, joinedBorderArea.width,
joinedBorderArea.height);
// start drawing
if (nsCSSRendering::IsBoxDecorationSlice(aStyleBorder)) {
if (joinedBorderArea.IsEqualEdges(aBorderArea)) {
// No need for a clip, just skip the sides we don't want.
border.ApplySkipSides(aSkipSides);
} else {
// We're drawing borders around the joined continuation boxes so we need
// to clip that to the slice that we want for this frame.
*aNeedsClip = true;
}
} else {
MOZ_ASSERT(joinedBorderArea.IsEqualEdges(aBorderArea),
"Should use aBorderArea for box-decoration-break:clone");
MOZ_ASSERT(
aForFrame->GetSkipSides().IsEmpty() ||
aForFrame->IsTrueOverflowContainer() ||
aForFrame->IsColumnSetFrame(), // a little broader than column-rule
"Should not skip sides for box-decoration-break:clone except "
"::first-letter/line continuations or other frame types that "
"don't have borders but those shouldn't reach this point. "
"Overflow containers do reach this point though, as does "
"column-rule drawing (which always involves a columnset).");
border.ApplySkipSides(aSkipSides);
}
// Convert to dev pixels.
nscoord oneDevPixel = aPresContext->DevPixelsToAppUnits(1);
Rect joinedBorderAreaPx = NSRectToRect(joinedBorderArea, oneDevPixel);
Float borderWidths[4] = {
Float(border.top) / oneDevPixel, Float(border.right) / oneDevPixel,
Float(border.bottom) / oneDevPixel, Float(border.left) / oneDevPixel};
Rect dirtyRect = NSRectToRect(aDirtyRect, oneDevPixel);
StyleBorderStyle borderStyles[4];
nscolor borderColors[4];
// pull out styles, colors
for (const auto i : mozilla::AllPhysicalSides()) {
borderStyles[i] = aStyleBorder.GetBorderStyle(i);
borderColors[i] = aStyleBorder.BorderColorFor(i).CalcColor(*aStyle);
}
PrintAsFormatString(
" borderStyles: %d %d %d %d\n", static_cast<int>(borderStyles[0]),
static_cast<int>(borderStyles[1]), static_cast<int>(borderStyles[2]),
static_cast<int>(borderStyles[3]));
return nsCSSBorderRenderer(
aPresContext, aDrawTarget, dirtyRect, joinedBorderAreaPx, borderStyles,
borderWidths, bgRadii, borderColors, !aForFrame->BackfaceIsHidden(),
*aNeedsClip ? Some(NSRectToRect(aBorderArea, oneDevPixel)) : Nothing());
}
ImgDrawResult nsCSSRendering::PaintBorderWithStyleBorder(
nsPresContext* aPresContext, gfxContext& aRenderingContext,
nsIFrame* aForFrame, const nsRect& aDirtyRect, const nsRect& aBorderArea,
const nsStyleBorder& aStyleBorder, ComputedStyle* aStyle,
PaintBorderFlags aFlags, Sides aSkipSides) {
DrawTarget& aDrawTarget = *aRenderingContext.GetDrawTarget();
PrintAsStringNewline("++ PaintBorder");
// Check to see if we have an appearance defined. If so, we let the theme
// renderer draw the border. DO not get the data from aForFrame, since the
// passed in ComputedStyle may be different! Always use |aStyle|!
StyleAppearance appearance = aStyle->StyleDisplay()->EffectiveAppearance();
if (appearance != StyleAppearance::None) {
nsITheme* theme = aPresContext->Theme();
if (theme->ThemeSupportsWidget(aPresContext, aForFrame, appearance)) {
return ImgDrawResult::SUCCESS; // Let the theme handle it.
}
}
if (!aStyleBorder.mBorderImageSource.IsNone()) {
ImgDrawResult result = ImgDrawResult::SUCCESS;
uint32_t irFlags = 0;
if (aFlags & PaintBorderFlags::SyncDecodeImages) {
irFlags |= nsImageRenderer::FLAG_SYNC_DECODE_IMAGES;
}
// Creating the border image renderer will request a decode, and we rely on
// that happening.
Maybe<nsCSSBorderImageRenderer> renderer =
nsCSSBorderImageRenderer::CreateBorderImageRenderer(
aPresContext, aForFrame, aBorderArea, aStyleBorder, aDirtyRect,
aSkipSides, irFlags, &result);
// renderer was created successfully, which means border image is ready to
// be used.
if (renderer) {
MOZ_ASSERT(result == ImgDrawResult::SUCCESS);
return renderer->DrawBorderImage(aPresContext, aRenderingContext,
aForFrame, aDirtyRect);
}
}
ImgDrawResult result = ImgDrawResult::SUCCESS;
// If we had a border-image, but it wasn't loaded, then we should return
// ImgDrawResult::NOT_READY; we'll want to try again if we do a paint with
// sync decoding enabled.
if (!aStyleBorder.mBorderImageSource.IsNone()) {
result = ImgDrawResult::NOT_READY;
}
nsMargin border = aStyleBorder.GetComputedBorder();
if (0 == border.left && 0 == border.right && 0 == border.top &&
0 == border.bottom) {
// Empty border area
return result;
}
bool needsClip = false;
nsCSSBorderRenderer br = ConstructBorderRenderer(
aPresContext, aStyle, &aDrawTarget, aForFrame, aDirtyRect, aBorderArea,
aStyleBorder, aSkipSides, &needsClip);
if (needsClip) {
aDrawTarget.PushClipRect(NSRectToSnappedRect(
aBorderArea, aForFrame->PresContext()->AppUnitsPerDevPixel(),
aDrawTarget));
}
br.DrawBorders();
if (needsClip) {
aDrawTarget.PopClip();
}
PrintAsStringNewline();
return result;
}
Maybe<nsCSSBorderRenderer> nsCSSRendering::CreateBorderRendererWithStyleBorder(
nsPresContext* aPresContext, DrawTarget* aDrawTarget, nsIFrame* aForFrame,
const nsRect& aDirtyRect, const nsRect& aBorderArea,
const nsStyleBorder& aStyleBorder, ComputedStyle* aStyle,
bool* aOutBorderIsEmpty, Sides aSkipSides) {
if (!aStyleBorder.mBorderImageSource.IsNone()) {
return Nothing();
}
return CreateNullBorderRendererWithStyleBorder(
aPresContext, aDrawTarget, aForFrame, aDirtyRect, aBorderArea,
aStyleBorder, aStyle, aOutBorderIsEmpty, aSkipSides);
}
Maybe<nsCSSBorderRenderer>
nsCSSRendering::CreateNullBorderRendererWithStyleBorder(
nsPresContext* aPresContext, DrawTarget* aDrawTarget, nsIFrame* aForFrame,
const nsRect& aDirtyRect, const nsRect& aBorderArea,
const nsStyleBorder& aStyleBorder, ComputedStyle* aStyle,
bool* aOutBorderIsEmpty, Sides aSkipSides) {
StyleAppearance appearance = aStyle->StyleDisplay()->EffectiveAppearance();
if (appearance != StyleAppearance::None) {
nsITheme* theme = aPresContext->Theme();
if (theme->ThemeSupportsWidget(aPresContext, aForFrame, appearance)) {
return Nothing();
}
}
nsMargin border = aStyleBorder.GetComputedBorder();
if (0 == border.left && 0 == border.right && 0 == border.top &&
0 == border.bottom) {
// Empty border area
if (aOutBorderIsEmpty) {
*aOutBorderIsEmpty = true;
}
return Nothing();
}
bool needsClip = false;
nsCSSBorderRenderer br = ConstructBorderRenderer(
aPresContext, aStyle, aDrawTarget, aForFrame, aDirtyRect, aBorderArea,
aStyleBorder, aSkipSides, &needsClip);
return Some(br);
}
Maybe<nsCSSBorderRenderer>
nsCSSRendering::CreateBorderRendererForNonThemedOutline(
nsPresContext* aPresContext, DrawTarget* aDrawTarget, nsIFrame* aForFrame,
const nsRect& aDirtyRect, const nsRect& aInnerRect, ComputedStyle* aStyle) {
// Get our ComputedStyle's color struct.
const nsStyleOutline* ourOutline = aStyle->StyleOutline();
if (!ourOutline->ShouldPaintOutline()) {
// Empty outline
return Nothing();
}
const nscoord offset = ourOutline->mOutlineOffset.ToAppUnits();
nsRect innerRect = aInnerRect;
innerRect.Inflate(offset);
// If the dirty rect is completely inside the border area (e.g., only the
// content is being painted), then we can skip out now
// XXX this isn't exactly true for rounded borders, where the inside curves
// may encroach into the content area. A safer calculation would be to
// shorten insideRect by the radius one each side before performing this test.
if (innerRect.Contains(aDirtyRect)) {
return Nothing();
}
nscoord width = ourOutline->GetOutlineWidth();
StyleBorderStyle outlineStyle;
// Themed outlines are handled by our callers, if supported.
if (ourOutline->mOutlineStyle.IsAuto()) {
if (width == 0) {
return Nothing(); // empty outline
}
// "User agents may treat 'auto' as 'solid'."
outlineStyle = StyleBorderStyle::Solid;
} else {
outlineStyle = ourOutline->mOutlineStyle.AsBorderStyle();
}
RectCornerRadii outlineRadii;
nsRect outerRect = innerRect;
outerRect.Inflate(width);
const nscoord oneDevPixel = aPresContext->AppUnitsPerDevPixel();
Rect oRect(NSRectToRect(outerRect, oneDevPixel));
const Float outlineWidths[4] = {
Float(width) / oneDevPixel, Float(width) / oneDevPixel,
Float(width) / oneDevPixel, Float(width) / oneDevPixel};
// convert the radii
nscoord twipsRadii[8];
// get the radius for our outline
if (StaticPrefs::layout_css_outline_follows_border_radius_enabled()) {
if (aForFrame->GetBorderRadii(twipsRadii)) {
RectCornerRadii innerRadii;
ComputePixelRadii(twipsRadii, oneDevPixel, &innerRadii);
Float devPixelOffset = aPresContext->AppUnitsToFloatDevPixels(offset);
const Float widths[4] = {
outlineWidths[0] + devPixelOffset, outlineWidths[1] + devPixelOffset,
outlineWidths[2] + devPixelOffset, outlineWidths[3] + devPixelOffset};
nsCSSBorderRenderer::ComputeOuterRadii(innerRadii, widths, &outlineRadii);
}
} else {
nsIFrame::ComputeBorderRadii(ourOutline->mOutlineRadius,
aForFrame->GetSize(), outerRect.Size(),
Sides(), twipsRadii);
ComputePixelRadii(twipsRadii, oneDevPixel, &outlineRadii);
}
StyleBorderStyle outlineStyles[4] = {outlineStyle, outlineStyle, outlineStyle,
outlineStyle};
// This handles treating the initial color as 'currentColor'; if we
// ever want 'invert' back we'll need to do a bit of work here too.
nscolor outlineColor =
aStyle->GetVisitedDependentColor(&nsStyleOutline::mOutlineColor);
nscolor outlineColors[4] = {outlineColor, outlineColor, outlineColor,
outlineColor};
Rect dirtyRect = NSRectToRect(aDirtyRect, oneDevPixel);
return Some(nsCSSBorderRenderer(
aPresContext, aDrawTarget, dirtyRect, oRect, outlineStyles, outlineWidths,
outlineRadii, outlineColors, !aForFrame->BackfaceIsHidden(), Nothing()));
}
void nsCSSRendering::PaintNonThemedOutline(nsPresContext* aPresContext,
gfxContext& aRenderingContext,
nsIFrame* aForFrame,
const nsRect& aDirtyRect,
const nsRect& aInnerRect,
ComputedStyle* aStyle) {
Maybe<nsCSSBorderRenderer> br = CreateBorderRendererForNonThemedOutline(
aPresContext, aRenderingContext.GetDrawTarget(), aForFrame, aDirtyRect,
aInnerRect, aStyle);
if (!br) {
return;
}
// start drawing
br->DrawBorders();
PrintAsStringNewline();
}
void nsCSSRendering::PaintFocus(nsPresContext* aPresContext,
DrawTarget* aDrawTarget,
const nsRect& aFocusRect, nscolor aColor) {
nscoord oneCSSPixel = nsPresContext::CSSPixelsToAppUnits(1);
nscoord oneDevPixel = aPresContext->DevPixelsToAppUnits(1);
Rect focusRect(NSRectToRect(aFocusRect, oneDevPixel));
RectCornerRadii focusRadii;
{
nscoord twipsRadii[8] = {0, 0, 0, 0, 0, 0, 0, 0};
ComputePixelRadii(twipsRadii, oneDevPixel, &focusRadii);
}
Float focusWidths[4] = {
Float(oneCSSPixel) / oneDevPixel, Float(oneCSSPixel) / oneDevPixel,
Float(oneCSSPixel) / oneDevPixel, Float(oneCSSPixel) / oneDevPixel};
StyleBorderStyle focusStyles[4] = {
StyleBorderStyle::Dotted, StyleBorderStyle::Dotted,
StyleBorderStyle::Dotted, StyleBorderStyle::Dotted};
nscolor focusColors[4] = {aColor, aColor, aColor, aColor};
// Because this renders a dotted border, the background color
// should not be used. Therefore, we provide a value that will
// be blatantly wrong if it ever does get used. (If this becomes
// something that CSS can style, this function will then have access
// to a ComputedStyle and can use the same logic that PaintBorder
// and PaintOutline do.)
//
// WebRender layers-free mode don't use PaintFocus function. Just assign
// the backface-visibility to true for this case.
nsCSSBorderRenderer br(aPresContext, aDrawTarget, focusRect, focusRect,
focusStyles, focusWidths, focusRadii, focusColors,
true, Nothing());
br.DrawBorders();
PrintAsStringNewline();
}
// Thebes Border Rendering Code End
//----------------------------------------------------------------------
//----------------------------------------------------------------------
/**
* Helper for ComputeObjectAnchorPoint; parameters are the same as for
* that function, except they're for a single coordinate / a single size
* dimension. (so, x/width vs. y/height)
*/
static void ComputeObjectAnchorCoord(const LengthPercentage& aCoord,
const nscoord aOriginBounds,
const nscoord aImageSize,
nscoord* aTopLeftCoord,
nscoord* aAnchorPointCoord) {
nscoord extraSpace = aOriginBounds - aImageSize;
// The anchor-point doesn't care about our image's size; just the size
// of the region we're rendering into.
*aAnchorPointCoord = aCoord.Resolve(aOriginBounds, NSToCoordRoundWithClamp);
// Adjust aTopLeftCoord by the specified % of the extra space.
*aTopLeftCoord = aCoord.Resolve(extraSpace, NSToCoordRoundWithClamp);
}
void nsImageRenderer::ComputeObjectAnchorPoint(const Position& aPos,
const nsSize& aOriginBounds,
const nsSize& aImageSize,
nsPoint* aTopLeft,
nsPoint* aAnchorPoint) {
ComputeObjectAnchorCoord(aPos.horizontal, aOriginBounds.width,
aImageSize.width, &aTopLeft->x, &aAnchorPoint->x);
ComputeObjectAnchorCoord(aPos.vertical, aOriginBounds.height,
aImageSize.height, &aTopLeft->y, &aAnchorPoint->y);
}
auto nsCSSRendering::FindNonTransparentBackgroundFrame(nsIFrame* aFrame,
bool aStopAtThemed)
-> NonTransparentBackgroundFrame {
NS_ASSERTION(aFrame,
"Cannot find NonTransparentBackgroundFrame in a null frame");
for (nsIFrame* frame = aFrame; frame;
frame = nsLayoutUtils::GetParentOrPlaceholderForCrossDoc(frame)) {
// No need to call GetVisitedDependentColor because it always uses this
// alpha component anyway.
if (NS_GET_A(frame->StyleBackground()->BackgroundColor(frame))) {
return {frame, false, false};
}
if (aStopAtThemed && frame->IsThemed()) {
return {frame, true, false};
}
if (IsCanvasFrame(frame)) {
nsIFrame* bgFrame = nullptr;
if (FindBackgroundFrame(frame, &bgFrame) &&
NS_GET_A(bgFrame->StyleBackground()->BackgroundColor(bgFrame))) {
return {bgFrame, false, true};
}
}
}
return {};
}
// Returns true if aFrame is a canvas frame.
// We need to treat the viewport as canvas because, even though
// it does not actually paint a background, we need to get the right
// background style so we correctly detect transparent documents.
bool nsCSSRendering::IsCanvasFrame(const nsIFrame* aFrame) {
LayoutFrameType frameType = aFrame->Type();
return frameType == LayoutFrameType::Canvas ||
frameType == LayoutFrameType::XULRoot ||
frameType == LayoutFrameType::PageContent ||
frameType == LayoutFrameType::Viewport;
}
nsIFrame* nsCSSRendering::FindBackgroundStyleFrame(nsIFrame* aForFrame) {
const nsStyleBackground* result = aForFrame->StyleBackground();
// Check if we need to do propagation from BODY rather than HTML.
if (!result->IsTransparent(aForFrame)) {
return aForFrame;
}
nsIContent* content = aForFrame->GetContent();
// The root element content can't be null. We wouldn't know what
// frame to create for aFrame.
// Use |OwnerDoc| so it works during destruction.
if (!content) {
return aForFrame;
}
Document* document = content->OwnerDoc();
dom::Element* bodyContent = document->GetBodyElement();
// We need to null check the body node (bug 118829) since
// there are cases, thanks to the fix for bug 5569, where we
// will reflow a document with no body. In particular, if a
// SCRIPT element in the head blocks the parser and then has a
// SCRIPT that does "document.location.href = 'foo'", then
// nsParser::Terminate will call |DidBuildModel| methods
// through to the content sink, which will call |StartLayout|
// and thus |Initialize| on the pres shell. See bug 119351
// for the ugly details.
if (!bodyContent) {
return aForFrame;
}
nsIFrame* bodyFrame = bodyContent->GetPrimaryFrame();
if (!bodyFrame) {
return aForFrame;
}
return nsLayoutUtils::GetStyleFrame(bodyFrame);
}
/**
* |FindBackground| finds the correct style data to use to paint the
* background. It is responsible for handling the following two
* statements in section 14.2 of CSS2:
*
* The background of the box generated by the root element covers the
* entire canvas.
*
* For HTML documents, however, we recommend that authors specify the
* background for the BODY element rather than the HTML element. User
* agents should observe the following precedence rules to fill in the
* background: if the value of the 'background' property for the HTML
* element is different from 'transparent' then use it, else use the
* value of the 'background' property for the BODY element. If the
* resulting value is 'transparent', the rendering is undefined.
*
* Thus, in our implementation, it is responsible for ensuring that:
* + we paint the correct background on the |nsCanvasFrame|,
* |nsRootBoxFrame|, or |nsPageFrame|,
* + we don't paint the background on the root element, and
* + we don't paint the background on the BODY element in *some* cases,
* and for SGML-based HTML documents only.
*
* |FindBackground| returns true if a background should be painted, and
* the resulting ComputedStyle to use for the background information
* will be filled in to |aBackground|.
*/
ComputedStyle* nsCSSRendering::FindRootFrameBackground(nsIFrame* aForFrame) {
return FindBackgroundStyleFrame(aForFrame)->Style();
}
inline bool FindElementBackground(const nsIFrame* aForFrame,
nsIFrame* aRootElementFrame) {
if (aForFrame == aRootElementFrame) {
// We must have propagated our background to the viewport or canvas. Abort.
return false;
}
// Return true unless the frame is for a BODY element whose background
// was propagated to the viewport.
nsIContent* content = aForFrame->GetContent();
if (!content || content->NodeInfo()->NameAtom() != nsGkAtoms::body)
return true; // not frame for a "body" element
// It could be a non-HTML "body" element but that's OK, we'd fail the
// bodyContent check below
if (aForFrame->Style()->GetPseudoType() != PseudoStyleType::NotPseudo) {
return true; // A pseudo-element frame.
}
// We should only look at the <html> background if we're in an HTML document
Document* document = content->OwnerDoc();
dom::Element* bodyContent = document->GetBodyElement();
if (bodyContent != content)
return true; // this wasn't the background that was propagated
// This can be called even when there's no root element yet, during frame
// construction, via nsLayoutUtils::FrameHasTransparency and
// nsContainerFrame::SyncFrameViewProperties.
if (!aRootElementFrame) {
return true;
}
const nsStyleBackground* htmlBG = aRootElementFrame->StyleBackground();
return !htmlBG->IsTransparent(aRootElementFrame);
}
bool nsCSSRendering::FindBackgroundFrame(const nsIFrame* aForFrame,
nsIFrame** aBackgroundFrame) {
nsIFrame* rootElementFrame =
aForFrame->PresShell()->FrameConstructor()->GetRootElementStyleFrame();
if (IsCanvasFrame(aForFrame)) {
*aBackgroundFrame = FindCanvasBackgroundFrame(aForFrame, rootElementFrame);
return true;
}
*aBackgroundFrame = const_cast<nsIFrame*>(aForFrame);
return FindElementBackground(aForFrame, rootElementFrame);
}
bool nsCSSRendering::FindBackground(const nsIFrame* aForFrame,
ComputedStyle** aBackgroundSC) {
nsIFrame* backgroundFrame = nullptr;
if (FindBackgroundFrame(aForFrame, &backgroundFrame)) {
*aBackgroundSC = backgroundFrame->Style();
return true;
}
return false;
}
void nsCSSRendering::BeginFrameTreesLocked() { ++gFrameTreeLockCount; }
void nsCSSRendering::EndFrameTreesLocked() {
NS_ASSERTION(gFrameTreeLockCount > 0, "Unbalanced EndFrameTreeLocked");
--gFrameTreeLockCount;
if (gFrameTreeLockCount == 0) {
gInlineBGData->Reset();
}
}
bool nsCSSRendering::HasBoxShadowNativeTheme(nsIFrame* aFrame,
bool& aMaybeHasBorderRadius) {
const nsStyleDisplay* styleDisplay = aFrame->StyleDisplay();
nsITheme::Transparency transparency;
if (aFrame->IsThemed(styleDisplay, &transparency)) {
aMaybeHasBorderRadius = false;
// For opaque (rectangular) theme widgets we can take the generic
// border-box path with border-radius disabled.
return transparency != nsITheme::eOpaque;
}
aMaybeHasBorderRadius = true;
return false;
}
gfx::sRGBColor nsCSSRendering::GetShadowColor(const StyleSimpleShadow& aShadow,
nsIFrame* aFrame,
float aOpacity) {
// Get the shadow color; if not specified, use the foreground color
nscolor shadowColor = aShadow.color.CalcColor(aFrame);
sRGBColor color = sRGBColor::FromABGR(shadowColor);
color.a *= aOpacity;
return color;
}
nsRect nsCSSRendering::GetShadowRect(const nsRect& aFrameArea,
bool aNativeTheme, nsIFrame* aForFrame) {
nsRect frameRect = aNativeTheme ? aForFrame->InkOverflowRectRelativeToSelf() +
aFrameArea.TopLeft()
: aFrameArea;
Sides skipSides = aForFrame->GetSkipSides();
frameRect = BoxDecorationRectForBorder(aForFrame, frameRect, skipSides);
// Explicitly do not need to account for the spread radius here
// Webrender does it for us or PaintBoxShadow will for non-WR
return frameRect;
}
bool nsCSSRendering::GetBorderRadii(const nsRect& aFrameRect,
const nsRect& aBorderRect, nsIFrame* aFrame,
RectCornerRadii& aOutRadii) {
const nscoord oneDevPixel = aFrame->PresContext()->DevPixelsToAppUnits(1);
nscoord twipsRadii[8];
NS_ASSERTION(
aBorderRect.Size() == aFrame->VisualBorderRectRelativeToSelf().Size(),
"unexpected size");
nsSize sz = aFrameRect.Size();
bool hasBorderRadius = aFrame->GetBorderRadii(sz, sz, Sides(), twipsRadii);
if (hasBorderRadius) {
ComputePixelRadii(twipsRadii, oneDevPixel, &aOutRadii);
}
return hasBorderRadius;
}
void nsCSSRendering::PaintBoxShadowOuter(nsPresContext* aPresContext,
gfxContext& aRenderingContext,
nsIFrame* aForFrame,
const nsRect& aFrameArea,
const nsRect& aDirtyRect,
float aOpacity) {
DrawTarget& aDrawTarget = *aRenderingContext.GetDrawTarget();
auto shadows = aForFrame->StyleEffects()->mBoxShadow.AsSpan();
if (shadows.IsEmpty()) {
return;
}
bool hasBorderRadius;
// mutually exclusive with hasBorderRadius
bool nativeTheme = HasBoxShadowNativeTheme(aForFrame, hasBorderRadius);
const nsStyleDisplay* styleDisplay = aForFrame->StyleDisplay();
nsRect frameRect = GetShadowRect(aFrameArea, nativeTheme, aForFrame);
// Get any border radius, since box-shadow must also have rounded corners if
// the frame does.
RectCornerRadii borderRadii;
const nscoord oneDevPixel = aPresContext->DevPixelsToAppUnits(1);
if (hasBorderRadius) {
nscoord twipsRadii[8];
NS_ASSERTION(
aFrameArea.Size() == aForFrame->VisualBorderRectRelativeToSelf().Size(),
"unexpected size");
nsSize sz = frameRect.Size();
hasBorderRadius = aForFrame->GetBorderRadii(sz, sz, Sides(), twipsRadii);
if (hasBorderRadius) {
ComputePixelRadii(twipsRadii, oneDevPixel, &borderRadii);
}
}
// We don't show anything that intersects with the frame we're blurring on. So
// tell the blurrer not to do unnecessary work there.
gfxRect skipGfxRect = ThebesRect(NSRectToRect(frameRect, oneDevPixel));
skipGfxRect.Round();
bool useSkipGfxRect = true;
if (nativeTheme) {
// Optimize non-leaf native-themed frames by skipping computing pixels
// in the padding-box. We assume the padding-box is going to be painted
// opaquely for non-leaf frames.
// XXX this may not be a safe assumption; we should make this go away
// by optimizing box-shadow drawing more for the cases where we don't have a
// skip-rect.
useSkipGfxRect = !aForFrame->IsLeaf();
nsRect paddingRect =
aForFrame->GetPaddingRectRelativeToSelf() + aFrameArea.TopLeft();
skipGfxRect = nsLayoutUtils::RectToGfxRect(paddingRect, oneDevPixel);
} else if (hasBorderRadius) {
skipGfxRect.Deflate(gfxMargin(
std::max(borderRadii[C_TL].height, borderRadii[C_TR].height), 0,
std::max(borderRadii[C_BL].height, borderRadii[C_BR].height), 0));
}
for (const StyleBoxShadow& shadow : Reversed(shadows)) {
if (shadow.inset) {
continue;
}
nsRect shadowRect = frameRect;
nsPoint shadowOffset(shadow.base.horizontal.ToAppUnits(),
shadow.base.vertical.ToAppUnits());
shadowRect.MoveBy(shadowOffset);
nscoord shadowSpread = shadow.spread.ToAppUnits();
if (!nativeTheme) {
shadowRect.Inflate(shadowSpread);
}
// shadowRect won't include the blur, so make an extra rect here that
// includes the blur for use in the even-odd rule below.
nsRect shadowRectPlusBlur = shadowRect;
nscoord blurRadius = shadow.base.blur.ToAppUnits();
shadowRectPlusBlur.Inflate(
nsContextBoxBlur::GetBlurRadiusMargin(blurRadius, oneDevPixel));
Rect shadowGfxRectPlusBlur = NSRectToRect(shadowRectPlusBlur, oneDevPixel);
shadowGfxRectPlusBlur.RoundOut();
MaybeSnapToDevicePixels(shadowGfxRectPlusBlur, aDrawTarget, true);
sRGBColor gfxShadowColor = GetShadowColor(shadow.base, aForFrame, aOpacity);
if (nativeTheme) {
nsContextBoxBlur blurringArea;
// When getting the widget shape from the native theme, we're going
// to draw the widget into the shadow surface to create a mask.
// We need to ensure that there actually *is* a shadow surface
// and that we're not going to draw directly into aRenderingContext.
gfxContext* shadowContext = blurringArea.Init(
shadowRect, shadowSpread, blurRadius, oneDevPixel, &aRenderingContext,
aDirtyRect, useSkipGfxRect ? &skipGfxRect : nullptr,
nsContextBoxBlur::FORCE_MASK);
if (!shadowContext) continue;
MOZ_ASSERT(shadowContext == blurringArea.GetContext());
aRenderingContext.Save();
aRenderingContext.SetColor(gfxShadowColor);
// Draw the shape of the frame so it can be blurred. Recall how
// nsContextBoxBlur doesn't make any temporary surfaces if blur is 0 and
// it just returns the original surface? If we have no blur, we're
// painting this fill on the actual content surface (aRenderingContext ==