/* -*- 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 */
usingnamespace mozilla; usingnamespace mozilla::css; usingnamespace mozilla::gfx; usingnamespace mozilla::image; using mozilla::CSSSizeOrRatio; using mozilla::dom::Document;
staticint 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) {}
/** * 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);
}
} elseif (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;
}
// 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;
}
};
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 (!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->IsLineParticipant()) {
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.");
}
// 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) { returntrue;
} if (isDescendant1 || isDescendant2) { returnfalse;
}
}
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?"); returnfalse;
}
};
// 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() { 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 */
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;
}
/* * 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 (constauto corner : mozilla::AllPhysicalHalfCorners()) {
radii[corner] = Float(aAppUnitsRadii[corner]) / aAppUnitsPerPixel;
}
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 (constauto side : mozilla::AllPhysicalSides()) {
nscolor color = aStyle.GetVisitedDependentColor(
nsStyleBorder::BorderColorFieldFor(side));
newBorder.BorderColorFor(side) = StyleColor::FromColor(color);
}
// Next we try image and gradient borders. Gradients are not supported at // this very moment. if (!borderImage.IsImageRequestType()) { return ImgDrawResult::NOT_SUPPORTED;
}
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;
}
// 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);
// 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);
// 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;
// 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;
}
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)) { // The border will be draw as part of the themed background item created // for this same frame. If no themed background item was created then not // drawing also matches that we do without webrender and what // nsDisplayBorder does for themed borders. if (aOutBorderIsEmpty) {
*aOutBorderIsEmpty = true;
} return Nothing();
}
}
// 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();
}
// get the radius for our outline if (aForFrame->GetBorderRadii(twipsRadii)) {
RectCornerRadii innerRadii;
ComputePixelRadii(twipsRadii, oneDevPixel, &innerRadii);
// 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);
// 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)
*/ staticvoid 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, static_cast<nscoord (*)(float)>(NSToCoordRoundWithClamp)); // Adjust aTopLeftCoord by the specified % of the extra space.
*aTopLeftCoord = aCoord.Resolve(
extraSpace, static_cast<nscoord (*)(float)>(NSToCoordRoundWithClamp));
}
// In print / print preview we have multiple canvas frames (one for each page, // and one for the document as a whole). For the topmost one, we really want the // page sequence page background, not the root or body's background. static nsIFrame* GetPageSequenceForCanvas(const nsIFrame* aCanvasFrame) {
MOZ_ASSERT(aCanvasFrame->IsCanvasFrame(), "not a canvas frame");
nsPresContext* pc = aCanvasFrame->PresContext(); if (!pc->IsRootPaginatedDocument()) { return nullptr;
} auto* ps = pc->PresShell()->GetPageSequenceFrame(); if (NS_WARN_IF(!ps)) { return nullptr;
} if (ps->GetParent() != aCanvasFrame) { return nullptr;
} return ps;
}
auto nsCSSRendering::FindEffectiveBackgroundColor(
nsIFrame* aFrame, bool aStopAtThemed, bool aPreferBodyToCanvas) -> EffectiveBackgroundColor {
MOZ_ASSERT(aFrame);
nsPresContext* pc = aFrame->PresContext(); auto BgColorIfNotTransparent = [&](nsIFrame* aFrame) -> Maybe<nscolor> {
nscolor c =
aFrame->GetVisitedDependentColor(&nsStyleBackground::mBackgroundColor); if (NS_GET_A(c) == 255) { return Some(c);
} if (NS_GET_A(c)) { // TODO(emilio): We should maybe just blend with ancestor bg colors and // such, but this is probably good enough for now, matches pre-existing // behavior. const nscolor defaultBg = pc->DefaultBackgroundColor();
MOZ_ASSERT(NS_GET_A(defaultBg) == 255, "PreferenceSheet guarantees this"); return Some(NS_ComposeColors(defaultBg, c));
} return Nothing();
};
for (nsIFrame* frame = aFrame; frame;
frame = nsLayoutUtils::GetParentOrPlaceholderForCrossDoc(frame)) { if (auto bg = BgColorIfNotTransparent(frame)) { return {*bg};
}
if (aStopAtThemed && frame->IsThemed()) { return {NS_TRANSPARENT, true};
}
if (frame->IsCanvasFrame()) { if (aPreferBodyToCanvas && !GetPageSequenceForCanvas(frame)) { if (auto* body = pc->Document()->GetBodyElement()) { if (nsIFrame* f = body->GetPrimaryFrame()) { if (auto bg = BgColorIfNotTransparent(f)) { return {*bg};
}
}
}
} if (nsIFrame* bgFrame = FindBackgroundFrame(frame)) { if (auto bg = BgColorIfNotTransparent(bgFrame)) { return {*bg};
}
}
}
}
return {pc->DefaultBackgroundColor()};
}
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 || aForFrame->StyleDisplay()->IsContainAny()) { return aForFrame;
}
/** * |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| 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| checks whether a background should be painted. If yes, it * returns the resulting ComputedStyle to use for the background information; * Otherwise, it returns nullptr.
*/
ComputedStyle* nsCSSRendering::FindRootFrameBackground(nsIFrame* aForFrame) { return FindBackgroundStyleFrame(aForFrame)->Style();
}
static nsIFrame* FindCanvasBackgroundFrame(const nsIFrame* aForFrame,
nsIFrame* aRootElementFrame) {
MOZ_ASSERT(aForFrame->IsCanvasFrame(), "not a canvas frame"); if (auto* ps = GetPageSequenceForCanvas(aForFrame)) { return ps;
} if (aRootElementFrame) { return nsCSSRendering::FindBackgroundStyleFrame(aRootElementFrame);
} // This should always give transparent, so we'll fill it in with the default // color if needed. This seems to happen a bit while a page is being loaded. returnconst_cast<nsIFrame*>(aForFrame);
}
// Helper for FindBackgroundFrame. Returns true if aForFrame has a meaningful // background that it should draw (i.e. that it hasn't propagated to another // frame). See documentation for FindBackground. inlinebool FrameHasMeaningfulBackground(const nsIFrame* aForFrame,
nsIFrame* aRootElementFrame) {
MOZ_ASSERT(!aForFrame->IsCanvasFrame(), "FindBackgroundFrame handles canvas frames before calling us, " "so we don't need to consider them here");
if (aForFrame == aRootElementFrame) { // We must have propagated our background to the viewport or canvas. Abort. returnfalse;
}
// 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) { returntrue; // 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 ||
aForFrame->StyleDisplay()->IsContainAny()) { returntrue; // A pseudo-element frame, or contained.
}
// 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) { returntrue; // 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 || aRootElementFrame->StyleDisplay()->IsContainAny()) { returntrue;
}
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; returnfalse;
}
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;
}
// 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);
} elseif (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;
}
// 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);
// 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;
}
// 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 == // shadowContext) which is why we set up the color and clip before doing // this.
// We don't clip the border-box from the shadow, nor any other box. // We assume that the native theme is going to paint over the shadow.
{
Rect innerClipRect = NSRectToRect(frameRect, oneDevPixel); if (!MaybeSnapToDevicePixels(innerClipRect, aDrawTarget, true)) {
innerClipRect.Round();
}
// Clip out the interior of the frame's border edge so that the shadow // is only painted outside that area.
RefPtr<PathBuilder> builder =
aDrawTarget.CreatePathBuilder(FillRule::FILL_EVEN_ODD);
AppendRectToPath(builder, shadowGfxRectPlusBlur); if (hasBorderRadius) {
AppendRoundedRectToPath(builder, innerClipRect, borderRadii);
} else {
AppendRectToPath(builder, innerClipRect);
}
RefPtr<Path> path = builder->Finish();
aRenderingContext.Clip(path);
}
// Clip the shadow so that we only get the part that applies to aForFrame.
nsRect fragmentClip = shadowRectPlusBlur;
Sides skipSides = aForFrame->GetSkipSides(); if (!skipSides.IsEmpty()) { if (skipSides.Left()) {
nscoord xmost = fragmentClip.XMost();
fragmentClip.x = aFrameArea.x;
fragmentClip.width = xmost - fragmentClip.x;
} if (skipSides.Right()) {
nscoord xmost = fragmentClip.XMost();
nscoord overflow = xmost - aFrameArea.XMost(); if (overflow > 0) {
fragmentClip.width -= overflow;
}
} if (skipSides.Top()) {
nscoord ymost = fragmentClip.YMost();
fragmentClip.y = aFrameArea.y;
fragmentClip.height = ymost - fragmentClip.y;
} if (skipSides.Bottom()) {
nscoord ymost = fragmentClip.YMost();
nscoord overflow = ymost - aFrameArea.YMost(); if (overflow > 0) {
fragmentClip.height -= overflow;
}
}
}
fragmentClip = fragmentClip.Intersect(aDirtyRect);
aRenderingContext.Clip(NSRectToSnappedRect(
fragmentClip, aForFrame->PresContext()->AppUnitsPerDevPixel(),
aDrawTarget));
if (aFrame->IsThemed() && aFrame->GetContent() &&
!nsContentUtils::IsChromeDoc(aFrame->GetContent()->GetComposedDoc())) { // There's no way of getting hold of a shape corresponding to a // "padding-box" for native-themed widgets, so just don't draw // inner box-shadows for them. But we allow chrome to paint inner // box shadows since chrome can be aware of the platform theme. returnfalse;
}
returntrue;
}
bool nsCSSRendering::GetShadowInnerRadii(nsIFrame* aFrame, const nsRect& aFrameArea,
RectCornerRadii& aOutInnerRadii) { // Get any border radius, since box-shadow must also have rounded corners // if the frame does.
nscoord twipsRadii[8];
nsRect frameRect =
BoxDecorationRectForBorder(aFrame, aFrameArea, aFrame->GetSkipSides());
nsSize sz = frameRect.Size();
nsMargin border = aFrame->GetUsedBorder();
aFrame->GetBorderRadii(sz, sz, Sides(), twipsRadii); const nscoord oneDevPixel = aFrame->PresContext()->DevPixelsToAppUnits(1);
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung ist noch experimentell.