1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4  * License, v. 2.0. If a copy of the MPL was not distributed with this
5  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 
7 /* utility functions for drawing borders and backgrounds */
8 
9 #include <ctime>
10 
11 #include "gfx2DGlue.h"
12 #include "gfxContext.h"
13 #include "mozilla/ArrayUtils.h"
14 #include "mozilla/ComputedStyle.h"
15 #include "mozilla/DebugOnly.h"
16 #include "mozilla/StaticPrefs_layout.h"
17 #include "mozilla/gfx/2D.h"
18 #include "mozilla/gfx/Helpers.h"
19 #include "mozilla/gfx/PathHelpers.h"
20 #include "mozilla/HashFunctions.h"
21 #include "mozilla/MathAlgorithms.h"
22 #include "mozilla/PresShell.h"
23 #include "mozilla/SVGImageContext.h"
24 #include "gfxFont.h"
25 #include "ScaledFontBase.h"
26 #include "skia/include/core/SkTextBlob.h"
27 
28 #include "BorderConsts.h"
29 #include "nsStyleConsts.h"
30 #include "nsPresContext.h"
31 #include "nsIFrame.h"
32 #include "nsIFrameInlines.h"
33 #include "nsPoint.h"
34 #include "nsRect.h"
35 #include "nsFrameManager.h"
36 #include "nsGkAtoms.h"
37 #include "nsCSSAnonBoxes.h"
38 #include "nsIContent.h"
39 #include "mozilla/dom/DocumentInlines.h"
40 #include "nsIScrollableFrame.h"
41 #include "imgIContainer.h"
42 #include "ImageOps.h"
43 #include "nsCSSRendering.h"
44 #include "nsCSSColorUtils.h"
45 #include "nsITheme.h"
46 #include "nsLayoutUtils.h"
47 #include "nsBlockFrame.h"
48 #include "nsStyleStructInlines.h"
49 #include "nsCSSFrameConstructor.h"
50 #include "nsCSSProps.h"
51 #include "nsContentUtils.h"
52 #include "gfxDrawable.h"
53 #include "nsCSSRenderingBorders.h"
54 #include "mozilla/css/ImageLoader.h"
55 #include "ImageContainer.h"
56 #include "mozilla/ProfilerLabels.h"
57 #include "mozilla/StaticPrefs_layout.h"
58 #include "mozilla/Telemetry.h"
59 #include "gfxUtils.h"
60 #include "gfxGradientCache.h"
61 #include "nsInlineFrame.h"
62 #include "nsRubyTextContainerFrame.h"
63 #include <algorithm>
64 #include "TextDrawTarget.h"
65 
66 using namespace mozilla;
67 using namespace mozilla::css;
68 using namespace mozilla::gfx;
69 using namespace mozilla::image;
70 using mozilla::CSSSizeOrRatio;
71 using mozilla::dom::Document;
72 
73 static int gFrameTreeLockCount = 0;
74 
75 // To avoid storing this data on nsInlineFrame (bloat) and to avoid
76 // recalculating this for each frame in a continuation (perf), hold
77 // a cache of various coordinate information that we need in order
78 // to paint inline backgrounds.
79 struct InlineBackgroundData {
InlineBackgroundDataInlineBackgroundData80   InlineBackgroundData()
81       : mFrame(nullptr),
82         mLineContainer(nullptr),
83         mContinuationPoint(0),
84         mUnbrokenMeasure(0),
85         mLineContinuationPoint(0),
86         mPIStartBorderData{},
87         mBidiEnabled(false),
88         mVertical(false) {}
89 
90   ~InlineBackgroundData() = default;
91 
ResetInlineBackgroundData92   void Reset() {
93     mBoundingBox.SetRect(0, 0, 0, 0);
94     mContinuationPoint = mLineContinuationPoint = mUnbrokenMeasure = 0;
95     mFrame = mLineContainer = nullptr;
96     mPIStartBorderData.Reset();
97   }
98 
99   /**
100    * Return a continuous rect for (an inline) aFrame relative to the
101    * continuation that draws the left-most part of the background.
102    * This is used when painting backgrounds.
103    */
GetContinuousRectInlineBackgroundData104   nsRect GetContinuousRect(nsIFrame* aFrame) {
105     MOZ_ASSERT(static_cast<nsInlineFrame*>(do_QueryFrame(aFrame)));
106 
107     SetFrame(aFrame);
108 
109     nscoord pos;  // an x coordinate if writing-mode is horizontal;
110                   // y coordinate if vertical
111     if (mBidiEnabled) {
112       pos = mLineContinuationPoint;
113 
114       // Scan continuations on the same line as aFrame and accumulate the widths
115       // of frames that are to the left (if this is an LTR block) or right
116       // (if it's RTL) of the current one.
117       bool isRtlBlock = (mLineContainer->StyleVisibility()->mDirection ==
118                          StyleDirection::Rtl);
119       nscoord curOffset = mVertical ? aFrame->GetOffsetTo(mLineContainer).y
120                                     : aFrame->GetOffsetTo(mLineContainer).x;
121 
122       // If the continuation is fluid we know inlineFrame is not on the same
123       // line. If it's not fluid, we need to test further to be sure.
124       nsIFrame* inlineFrame = aFrame->GetPrevContinuation();
125       while (inlineFrame && !inlineFrame->GetNextInFlow() &&
126              AreOnSameLine(aFrame, inlineFrame)) {
127         nscoord frameOffset = mVertical
128                                   ? inlineFrame->GetOffsetTo(mLineContainer).y
129                                   : inlineFrame->GetOffsetTo(mLineContainer).x;
130         if (isRtlBlock == (frameOffset >= curOffset)) {
131           pos += mVertical ? inlineFrame->GetSize().height
132                            : inlineFrame->GetSize().width;
133         }
134         inlineFrame = inlineFrame->GetPrevContinuation();
135       }
136 
137       inlineFrame = aFrame->GetNextContinuation();
138       while (inlineFrame && !inlineFrame->GetPrevInFlow() &&
139              AreOnSameLine(aFrame, inlineFrame)) {
140         nscoord frameOffset = mVertical
141                                   ? inlineFrame->GetOffsetTo(mLineContainer).y
142                                   : inlineFrame->GetOffsetTo(mLineContainer).x;
143         if (isRtlBlock == (frameOffset >= curOffset)) {
144           pos += mVertical ? inlineFrame->GetSize().height
145                            : inlineFrame->GetSize().width;
146         }
147         inlineFrame = inlineFrame->GetNextContinuation();
148       }
149       if (isRtlBlock) {
150         // aFrame itself is also to the right of its left edge, so add its
151         // width.
152         pos += mVertical ? aFrame->GetSize().height : aFrame->GetSize().width;
153         // pos is now the distance from the left [top] edge of aFrame to the
154         // right [bottom] edge of the unbroken content. Change it to indicate
155         // the distance from the left [top] edge of the unbroken content to the
156         // left [top] edge of aFrame.
157         pos = mUnbrokenMeasure - pos;
158       }
159     } else {
160       pos = mContinuationPoint;
161     }
162 
163     // Assume background-origin: border and return a rect with offsets
164     // relative to (0,0).  If we have a different background-origin,
165     // then our rect should be deflated appropriately by our caller.
166     return mVertical
167                ? nsRect(0, -pos, mFrame->GetSize().width, mUnbrokenMeasure)
168                : nsRect(-pos, 0, mUnbrokenMeasure, mFrame->GetSize().height);
169   }
170 
171   /**
172    * Return a continuous rect for (an inline) aFrame relative to the
173    * continuation that should draw the left[top]-border.  This is used when
174    * painting borders and clipping backgrounds.  This may NOT be the same
175    * continuous rect as for drawing backgrounds; the continuation with the
176    * left[top]-border might be somewhere in the middle of that rect (e.g. BIDI),
177    * in those cases we need the reverse background order starting at the
178    * left[top]-border continuation.
179    */
GetBorderContinuousRectInlineBackgroundData180   nsRect GetBorderContinuousRect(nsIFrame* aFrame, nsRect aBorderArea) {
181     // Calling GetContinuousRect(aFrame) here may lead to Reset/Init which
182     // resets our mPIStartBorderData so we save it ...
183     PhysicalInlineStartBorderData saved(mPIStartBorderData);
184     nsRect joinedBorderArea = GetContinuousRect(aFrame);
185     if (!saved.mIsValid || saved.mFrame != mPIStartBorderData.mFrame) {
186       if (aFrame == mPIStartBorderData.mFrame) {
187         if (mVertical) {
188           mPIStartBorderData.SetCoord(joinedBorderArea.y);
189         } else {
190           mPIStartBorderData.SetCoord(joinedBorderArea.x);
191         }
192       } else if (mPIStartBorderData.mFrame) {
193         // Copy data to a temporary object so that computing the
194         // continous rect here doesn't clobber our normal state.
195         InlineBackgroundData temp = *this;
196         if (mVertical) {
197           mPIStartBorderData.SetCoord(
198               temp.GetContinuousRect(mPIStartBorderData.mFrame).y);
199         } else {
200           mPIStartBorderData.SetCoord(
201               temp.GetContinuousRect(mPIStartBorderData.mFrame).x);
202         }
203       }
204     } else {
205       // ... and restore it when possible.
206       mPIStartBorderData.SetCoord(saved.mCoord);
207     }
208     if (mVertical) {
209       if (joinedBorderArea.y > mPIStartBorderData.mCoord) {
210         joinedBorderArea.y =
211             -(mUnbrokenMeasure + joinedBorderArea.y - aBorderArea.height);
212       } else {
213         joinedBorderArea.y -= mPIStartBorderData.mCoord;
214       }
215     } else {
216       if (joinedBorderArea.x > mPIStartBorderData.mCoord) {
217         joinedBorderArea.x =
218             -(mUnbrokenMeasure + joinedBorderArea.x - aBorderArea.width);
219       } else {
220         joinedBorderArea.x -= mPIStartBorderData.mCoord;
221       }
222     }
223     return joinedBorderArea;
224   }
225 
GetBoundingRectInlineBackgroundData226   nsRect GetBoundingRect(nsIFrame* aFrame) {
227     SetFrame(aFrame);
228 
229     // Move the offsets relative to (0,0) which puts the bounding box into
230     // our coordinate system rather than our parent's.  We do this by
231     // moving it the back distance from us to the bounding box.
232     // This also assumes background-origin: border, so our caller will
233     // need to deflate us if needed.
234     nsRect boundingBox(mBoundingBox);
235     nsPoint point = mFrame->GetPosition();
236     boundingBox.MoveBy(-point.x, -point.y);
237 
238     return boundingBox;
239   }
240 
241  protected:
242   // This is a coordinate on the inline axis, but is not a true logical inline-
243   // coord because it is always measured from left to right (if horizontal) or
244   // from top to bottom (if vertical), ignoring any bidi RTL directionality.
245   // We'll call this "physical inline start", or PIStart for short.
246   struct PhysicalInlineStartBorderData {
247     nsIFrame* mFrame;  // the continuation that may have a left-border
248     nscoord mCoord;    // cached GetContinuousRect(mFrame).x or .y
249     bool mIsValid;     // true if mCoord is valid
ResetInlineBackgroundData::PhysicalInlineStartBorderData250     void Reset() {
251       mFrame = nullptr;
252       mIsValid = false;
253     }
SetCoordInlineBackgroundData::PhysicalInlineStartBorderData254     void SetCoord(nscoord aCoord) {
255       mCoord = aCoord;
256       mIsValid = true;
257     }
258   };
259 
260   nsIFrame* mFrame;
261   nsIFrame* mLineContainer;
262   nsRect mBoundingBox;
263   nscoord mContinuationPoint;
264   nscoord mUnbrokenMeasure;
265   nscoord mLineContinuationPoint;
266   PhysicalInlineStartBorderData mPIStartBorderData;
267   bool mBidiEnabled;
268   bool mVertical;
269 
SetFrameInlineBackgroundData270   void SetFrame(nsIFrame* aFrame) {
271     MOZ_ASSERT(aFrame, "Need a frame");
272     NS_ASSERTION(gFrameTreeLockCount > 0,
273                  "Can't call this when frame tree is not locked");
274 
275     if (aFrame == mFrame) {
276       return;
277     }
278 
279     nsIFrame* prevContinuation = GetPrevContinuation(aFrame);
280 
281     if (!prevContinuation || mFrame != prevContinuation) {
282       // Ok, we've got the wrong frame.  We have to start from scratch.
283       Reset();
284       Init(aFrame);
285       return;
286     }
287 
288     // Get our last frame's size and add its width to our continuation
289     // point before we cache the new frame.
290     mContinuationPoint +=
291         mVertical ? mFrame->GetSize().height : mFrame->GetSize().width;
292 
293     // If this a new line, update mLineContinuationPoint.
294     if (mBidiEnabled &&
295         (aFrame->GetPrevInFlow() || !AreOnSameLine(mFrame, aFrame))) {
296       mLineContinuationPoint = mContinuationPoint;
297     }
298 
299     mFrame = aFrame;
300   }
301 
GetPrevContinuationInlineBackgroundData302   nsIFrame* GetPrevContinuation(nsIFrame* aFrame) {
303     nsIFrame* prevCont = aFrame->GetPrevContinuation();
304     if (!prevCont && aFrame->HasAnyStateBits(NS_FRAME_PART_OF_IBSPLIT)) {
305       nsIFrame* block = aFrame->GetProperty(nsIFrame::IBSplitPrevSibling());
306       if (block) {
307         // The {ib} properties are only stored on first continuations
308         NS_ASSERTION(!block->GetPrevContinuation(),
309                      "Incorrect value for IBSplitPrevSibling");
310         prevCont = block->GetProperty(nsIFrame::IBSplitPrevSibling());
311         NS_ASSERTION(prevCont, "How did that happen?");
312       }
313     }
314     return prevCont;
315   }
316 
GetNextContinuationInlineBackgroundData317   nsIFrame* GetNextContinuation(nsIFrame* aFrame) {
318     nsIFrame* nextCont = aFrame->GetNextContinuation();
319     if (!nextCont && aFrame->HasAnyStateBits(NS_FRAME_PART_OF_IBSPLIT)) {
320       // The {ib} properties are only stored on first continuations
321       aFrame = aFrame->FirstContinuation();
322       nsIFrame* block = aFrame->GetProperty(nsIFrame::IBSplitSibling());
323       if (block) {
324         nextCont = block->GetProperty(nsIFrame::IBSplitSibling());
325         NS_ASSERTION(nextCont, "How did that happen?");
326       }
327     }
328     return nextCont;
329   }
330 
InitInlineBackgroundData331   void Init(nsIFrame* aFrame) {
332     mPIStartBorderData.Reset();
333     mBidiEnabled = aFrame->PresContext()->BidiEnabled();
334     if (mBidiEnabled) {
335       // Find the line container frame
336       mLineContainer = aFrame;
337       while (mLineContainer &&
338              mLineContainer->IsFrameOfType(nsIFrame::eLineParticipant)) {
339         mLineContainer = mLineContainer->GetParent();
340       }
341 
342       MOZ_ASSERT(mLineContainer, "Cannot find line containing frame.");
343       MOZ_ASSERT(mLineContainer != aFrame,
344                  "line container frame "
345                  "should be an ancestor of the target frame.");
346     }
347 
348     mVertical = aFrame->GetWritingMode().IsVertical();
349 
350     // Start with the previous flow frame as our continuation point
351     // is the total of the widths of the previous frames.
352     nsIFrame* inlineFrame = GetPrevContinuation(aFrame);
353     bool changedLines = false;
354     while (inlineFrame) {
355       if (!mPIStartBorderData.mFrame &&
356           !(mVertical ? inlineFrame->GetSkipSides().Top()
357                       : inlineFrame->GetSkipSides().Left())) {
358         mPIStartBorderData.mFrame = inlineFrame;
359       }
360       nsRect rect = inlineFrame->GetRect();
361       mContinuationPoint += mVertical ? rect.height : rect.width;
362       if (mBidiEnabled &&
363           (changedLines || !AreOnSameLine(aFrame, inlineFrame))) {
364         mLineContinuationPoint += mVertical ? rect.height : rect.width;
365         changedLines = true;
366       }
367       mUnbrokenMeasure += mVertical ? rect.height : rect.width;
368       mBoundingBox.UnionRect(mBoundingBox, rect);
369       inlineFrame = GetPrevContinuation(inlineFrame);
370     }
371 
372     // Next add this frame and subsequent frames to the bounding box and
373     // unbroken width.
374     inlineFrame = aFrame;
375     while (inlineFrame) {
376       if (!mPIStartBorderData.mFrame &&
377           !(mVertical ? inlineFrame->GetSkipSides().Top()
378                       : inlineFrame->GetSkipSides().Left())) {
379         mPIStartBorderData.mFrame = inlineFrame;
380       }
381       nsRect rect = inlineFrame->GetRect();
382       mUnbrokenMeasure += mVertical ? rect.height : rect.width;
383       mBoundingBox.UnionRect(mBoundingBox, rect);
384       inlineFrame = GetNextContinuation(inlineFrame);
385     }
386 
387     mFrame = aFrame;
388   }
389 
AreOnSameLineInlineBackgroundData390   bool AreOnSameLine(nsIFrame* aFrame1, nsIFrame* aFrame2) {
391     if (nsBlockFrame* blockFrame = do_QueryFrame(mLineContainer)) {
392       bool isValid1, isValid2;
393       nsBlockInFlowLineIterator it1(blockFrame, aFrame1, &isValid1);
394       nsBlockInFlowLineIterator it2(blockFrame, aFrame2, &isValid2);
395       return isValid1 && isValid2 &&
396              // Make sure aFrame1 and aFrame2 are in the same continuation of
397              // blockFrame.
398              it1.GetContainer() == it2.GetContainer() &&
399              // And on the same line in it
400              it1.GetLine().get() == it2.GetLine().get();
401     }
402     if (nsRubyTextContainerFrame* rtcFrame = do_QueryFrame(mLineContainer)) {
403       nsBlockFrame* block = nsLayoutUtils::FindNearestBlockAncestor(rtcFrame);
404       // Ruby text container can only hold one line of text, so if they
405       // are in the same continuation, they are in the same line. Since
406       // ruby text containers are bidi isolate, they are never split for
407       // bidi reordering, which means being in different continuation
408       // indicates being in different lines.
409       for (nsIFrame* frame = rtcFrame->FirstContinuation(); frame;
410            frame = frame->GetNextContinuation()) {
411         bool isDescendant1 =
412             nsLayoutUtils::IsProperAncestorFrame(frame, aFrame1, block);
413         bool isDescendant2 =
414             nsLayoutUtils::IsProperAncestorFrame(frame, aFrame2, block);
415         if (isDescendant1 && isDescendant2) {
416           return true;
417         }
418         if (isDescendant1 || isDescendant2) {
419           return false;
420         }
421       }
422       MOZ_ASSERT_UNREACHABLE("None of the frames is a descendant of this rtc?");
423     }
424     MOZ_ASSERT_UNREACHABLE("Do we have any other type of line container?");
425     return false;
426   }
427 };
428 
429 static InlineBackgroundData* gInlineBGData = nullptr;
430 
431 // Initialize any static variables used by nsCSSRendering.
Init()432 void nsCSSRendering::Init() {
433   NS_ASSERTION(!gInlineBGData, "Init called twice");
434   gInlineBGData = new InlineBackgroundData();
435 }
436 
437 // Clean up any global variables used by nsCSSRendering.
Shutdown()438 void nsCSSRendering::Shutdown() {
439   delete gInlineBGData;
440   gInlineBGData = nullptr;
441 }
442 
443 /**
444  * Make a bevel color
445  */
MakeBevelColor(mozilla::Side whichSide,StyleBorderStyle style,nscolor aBorderColor)446 static nscolor MakeBevelColor(mozilla::Side whichSide, StyleBorderStyle style,
447                               nscolor aBorderColor) {
448   nscolor colors[2];
449   nscolor theColor;
450 
451   // Given a background color and a border color
452   // calculate the color used for the shading
453   NS_GetSpecial3DColors(colors, aBorderColor);
454 
455   if ((style == StyleBorderStyle::Outset) ||
456       (style == StyleBorderStyle::Ridge)) {
457     // Flip colors for these two border styles
458     switch (whichSide) {
459       case eSideBottom:
460         whichSide = eSideTop;
461         break;
462       case eSideRight:
463         whichSide = eSideLeft;
464         break;
465       case eSideTop:
466         whichSide = eSideBottom;
467         break;
468       case eSideLeft:
469         whichSide = eSideRight;
470         break;
471     }
472   }
473 
474   switch (whichSide) {
475     case eSideBottom:
476       theColor = colors[1];
477       break;
478     case eSideRight:
479       theColor = colors[1];
480       break;
481     case eSideTop:
482       theColor = colors[0];
483       break;
484     case eSideLeft:
485     default:
486       theColor = colors[0];
487       break;
488   }
489   return theColor;
490 }
491 
GetRadii(nsIFrame * aForFrame,const nsStyleBorder & aBorder,const nsRect & aOrigBorderArea,const nsRect & aBorderArea,nscoord aRadii[8])492 static bool GetRadii(nsIFrame* aForFrame, const nsStyleBorder& aBorder,
493                      const nsRect& aOrigBorderArea, const nsRect& aBorderArea,
494                      nscoord aRadii[8]) {
495   bool haveRoundedCorners;
496   nsSize sz = aBorderArea.Size();
497   nsSize frameSize = aForFrame->GetSize();
498   if (&aBorder == aForFrame->StyleBorder() &&
499       frameSize == aOrigBorderArea.Size()) {
500     haveRoundedCorners = aForFrame->GetBorderRadii(sz, sz, Sides(), aRadii);
501   } else {
502     haveRoundedCorners = nsIFrame::ComputeBorderRadii(
503         aBorder.mBorderRadius, frameSize, sz, Sides(), aRadii);
504   }
505 
506   return haveRoundedCorners;
507 }
508 
GetRadii(nsIFrame * aForFrame,const nsStyleBorder & aBorder,const nsRect & aOrigBorderArea,const nsRect & aBorderArea,RectCornerRadii * aBgRadii)509 static bool GetRadii(nsIFrame* aForFrame, const nsStyleBorder& aBorder,
510                      const nsRect& aOrigBorderArea, const nsRect& aBorderArea,
511                      RectCornerRadii* aBgRadii) {
512   nscoord radii[8];
513   bool haveRoundedCorners =
514       GetRadii(aForFrame, aBorder, aOrigBorderArea, aBorderArea, radii);
515 
516   if (haveRoundedCorners) {
517     auto d2a = aForFrame->PresContext()->AppUnitsPerDevPixel();
518     nsCSSRendering::ComputePixelRadii(radii, d2a, aBgRadii);
519   }
520   return haveRoundedCorners;
521 }
522 
JoinBoxesForBlockAxisSlice(nsIFrame * aFrame,const nsRect & aBorderArea)523 static nsRect JoinBoxesForBlockAxisSlice(nsIFrame* aFrame,
524                                          const nsRect& aBorderArea) {
525   // Inflate the block-axis size as if our continuations were laid out
526   // adjacent in that axis.  Note that we don't touch the inline size.
527   const auto wm = aFrame->GetWritingMode();
528   const nsSize dummyContainerSize;
529   LogicalRect borderArea(wm, aBorderArea, dummyContainerSize);
530   nscoord bSize = 0;
531   nsIFrame* f = aFrame->GetNextContinuation();
532   for (; f; f = f->GetNextContinuation()) {
533     bSize += f->BSize(wm);
534   }
535   borderArea.BSize(wm) += bSize;
536   bSize = 0;
537   f = aFrame->GetPrevContinuation();
538   for (; f; f = f->GetPrevContinuation()) {
539     bSize += f->BSize(wm);
540   }
541   borderArea.BStart(wm) -= bSize;
542   borderArea.BSize(wm) += bSize;
543   return borderArea.GetPhysicalRect(wm, dummyContainerSize);
544 }
545 
546 /**
547  * Inflate aBorderArea which is relative to aFrame's origin to calculate
548  * a hypothetical non-split frame area for all the continuations.
549  * See "Joining Boxes for 'slice'" in
550  * http://dev.w3.org/csswg/css-break/#break-decoration
551  */
552 enum InlineBoxOrder { eForBorder, eForBackground };
JoinBoxesForSlice(nsIFrame * aFrame,const nsRect & aBorderArea,InlineBoxOrder aOrder)553 static nsRect JoinBoxesForSlice(nsIFrame* aFrame, const nsRect& aBorderArea,
554                                 InlineBoxOrder aOrder) {
555   if (static_cast<nsInlineFrame*>(do_QueryFrame(aFrame))) {
556     return (aOrder == eForBorder
557                 ? gInlineBGData->GetBorderContinuousRect(aFrame, aBorderArea)
558                 : gInlineBGData->GetContinuousRect(aFrame)) +
559            aBorderArea.TopLeft();
560   }
561   return JoinBoxesForBlockAxisSlice(aFrame, aBorderArea);
562 }
563 
564 /* static */
IsBoxDecorationSlice(const nsStyleBorder & aStyleBorder)565 bool nsCSSRendering::IsBoxDecorationSlice(const nsStyleBorder& aStyleBorder) {
566   return aStyleBorder.mBoxDecorationBreak == StyleBoxDecorationBreak::Slice;
567 }
568 
569 /* static */
BoxDecorationRectForBorder(nsIFrame * aFrame,const nsRect & aBorderArea,Sides aSkipSides,const nsStyleBorder * aStyleBorder)570 nsRect nsCSSRendering::BoxDecorationRectForBorder(
571     nsIFrame* aFrame, const nsRect& aBorderArea, Sides aSkipSides,
572     const nsStyleBorder* aStyleBorder) {
573   if (!aStyleBorder) {
574     aStyleBorder = aFrame->StyleBorder();
575   }
576   // If aSkipSides.IsEmpty() then there are no continuations, or it's
577   // a ::first-letter that wants all border sides on the first continuation.
578   return IsBoxDecorationSlice(*aStyleBorder) && !aSkipSides.IsEmpty()
579              ? ::JoinBoxesForSlice(aFrame, aBorderArea, eForBorder)
580              : aBorderArea;
581 }
582 
583 /* static */
BoxDecorationRectForBackground(nsIFrame * aFrame,const nsRect & aBorderArea,Sides aSkipSides,const nsStyleBorder * aStyleBorder)584 nsRect nsCSSRendering::BoxDecorationRectForBackground(
585     nsIFrame* aFrame, const nsRect& aBorderArea, Sides aSkipSides,
586     const nsStyleBorder* aStyleBorder) {
587   if (!aStyleBorder) {
588     aStyleBorder = aFrame->StyleBorder();
589   }
590   // If aSkipSides.IsEmpty() then there are no continuations, or it's
591   // a ::first-letter that wants all border sides on the first continuation.
592   return IsBoxDecorationSlice(*aStyleBorder) && !aSkipSides.IsEmpty()
593              ? ::JoinBoxesForSlice(aFrame, aBorderArea, eForBackground)
594              : aBorderArea;
595 }
596 
597 //----------------------------------------------------------------------
598 // Thebes Border Rendering Code Start
599 
600 /*
601  * Compute the float-pixel radii that should be used for drawing
602  * this border/outline, given the various input bits.
603  */
604 /* static */
ComputePixelRadii(const nscoord * aAppUnitsRadii,nscoord aAppUnitsPerPixel,RectCornerRadii * oBorderRadii)605 void nsCSSRendering::ComputePixelRadii(const nscoord* aAppUnitsRadii,
606                                        nscoord aAppUnitsPerPixel,
607                                        RectCornerRadii* oBorderRadii) {
608   Float radii[8];
609   for (const auto corner : mozilla::AllPhysicalHalfCorners()) {
610     radii[corner] = Float(aAppUnitsRadii[corner]) / aAppUnitsPerPixel;
611   }
612 
613   (*oBorderRadii)[C_TL] = Size(radii[eCornerTopLeftX], radii[eCornerTopLeftY]);
614   (*oBorderRadii)[C_TR] =
615       Size(radii[eCornerTopRightX], radii[eCornerTopRightY]);
616   (*oBorderRadii)[C_BR] =
617       Size(radii[eCornerBottomRightX], radii[eCornerBottomRightY]);
618   (*oBorderRadii)[C_BL] =
619       Size(radii[eCornerBottomLeftX], radii[eCornerBottomLeftY]);
620 }
621 
GetBorderIfVisited(const ComputedStyle & aStyle)622 static Maybe<nsStyleBorder> GetBorderIfVisited(const ComputedStyle& aStyle) {
623   Maybe<nsStyleBorder> result;
624   // Don't check RelevantLinkVisited here, since we want to take the
625   // same amount of time whether or not it's true.
626   const ComputedStyle* styleIfVisited = aStyle.GetStyleIfVisited();
627   if (MOZ_LIKELY(!styleIfVisited)) {
628     return result;
629   }
630 
631   result.emplace(*aStyle.StyleBorder());
632   auto& newBorder = result.ref();
633   for (const auto side : mozilla::AllPhysicalSides()) {
634     nscolor color = aStyle.GetVisitedDependentColor(
635         nsStyleBorder::BorderColorFieldFor(side));
636     newBorder.BorderColorFor(side) = StyleColor::FromColor(color);
637   }
638 
639   return result;
640 }
641 
PaintBorder(nsPresContext * aPresContext,gfxContext & aRenderingContext,nsIFrame * aForFrame,const nsRect & aDirtyRect,const nsRect & aBorderArea,ComputedStyle * aStyle,PaintBorderFlags aFlags,Sides aSkipSides)642 ImgDrawResult nsCSSRendering::PaintBorder(
643     nsPresContext* aPresContext, gfxContext& aRenderingContext,
644     nsIFrame* aForFrame, const nsRect& aDirtyRect, const nsRect& aBorderArea,
645     ComputedStyle* aStyle, PaintBorderFlags aFlags, Sides aSkipSides) {
646   AUTO_PROFILER_LABEL("nsCSSRendering::PaintBorder", GRAPHICS);
647 
648   Maybe<nsStyleBorder> visitedBorder = GetBorderIfVisited(*aStyle);
649   return PaintBorderWithStyleBorder(
650       aPresContext, aRenderingContext, aForFrame, aDirtyRect, aBorderArea,
651       visitedBorder.refOr(*aStyle->StyleBorder()), aStyle, aFlags, aSkipSides);
652 }
653 
CreateBorderRenderer(nsPresContext * aPresContext,DrawTarget * aDrawTarget,nsIFrame * aForFrame,const nsRect & aDirtyRect,const nsRect & aBorderArea,ComputedStyle * aStyle,bool * aOutBorderIsEmpty,Sides aSkipSides)654 Maybe<nsCSSBorderRenderer> nsCSSRendering::CreateBorderRenderer(
655     nsPresContext* aPresContext, DrawTarget* aDrawTarget, nsIFrame* aForFrame,
656     const nsRect& aDirtyRect, const nsRect& aBorderArea, ComputedStyle* aStyle,
657     bool* aOutBorderIsEmpty, Sides aSkipSides) {
658   Maybe<nsStyleBorder> visitedBorder = GetBorderIfVisited(*aStyle);
659   return CreateBorderRendererWithStyleBorder(
660       aPresContext, aDrawTarget, aForFrame, aDirtyRect, aBorderArea,
661       visitedBorder.refOr(*aStyle->StyleBorder()), aStyle, aOutBorderIsEmpty,
662       aSkipSides);
663 }
664 
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)665 ImgDrawResult nsCSSRendering::CreateWebRenderCommandsForBorder(
666     nsDisplayItem* aItem, nsIFrame* aForFrame, const nsRect& aBorderArea,
667     mozilla::wr::DisplayListBuilder& aBuilder,
668     mozilla::wr::IpcResourceUpdateQueue& aResources,
669     const mozilla::layers::StackingContextHelper& aSc,
670     mozilla::layers::RenderRootStateManager* aManager,
671     nsDisplayListBuilder* aDisplayListBuilder) {
672   const auto* style = aForFrame->Style();
673   Maybe<nsStyleBorder> visitedBorder = GetBorderIfVisited(*style);
674   return nsCSSRendering::CreateWebRenderCommandsForBorderWithStyleBorder(
675       aItem, aForFrame, aBorderArea, aBuilder, aResources, aSc, aManager,
676       aDisplayListBuilder, visitedBorder.refOr(*style->StyleBorder()));
677 }
678 
CreateWebRenderCommandsForNullBorder(nsDisplayItem * aItem,nsIFrame * aForFrame,const nsRect & aBorderArea,mozilla::wr::DisplayListBuilder & aBuilder,mozilla::wr::IpcResourceUpdateQueue & aResources,const mozilla::layers::StackingContextHelper & aSc,const nsStyleBorder & aStyleBorder)679 void nsCSSRendering::CreateWebRenderCommandsForNullBorder(
680     nsDisplayItem* aItem, nsIFrame* aForFrame, const nsRect& aBorderArea,
681     mozilla::wr::DisplayListBuilder& aBuilder,
682     mozilla::wr::IpcResourceUpdateQueue& aResources,
683     const mozilla::layers::StackingContextHelper& aSc,
684     const nsStyleBorder& aStyleBorder) {
685   bool borderIsEmpty = false;
686   Maybe<nsCSSBorderRenderer> br =
687       nsCSSRendering::CreateNullBorderRendererWithStyleBorder(
688           aForFrame->PresContext(), nullptr, aForFrame, nsRect(), aBorderArea,
689           aStyleBorder, aForFrame->Style(), &borderIsEmpty,
690           aForFrame->GetSkipSides());
691   if (!borderIsEmpty && br) {
692     br->CreateWebRenderCommands(aItem, aBuilder, aResources, aSc);
693   }
694 }
695 
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)696 ImgDrawResult nsCSSRendering::CreateWebRenderCommandsForBorderWithStyleBorder(
697     nsDisplayItem* aItem, nsIFrame* aForFrame, const nsRect& aBorderArea,
698     mozilla::wr::DisplayListBuilder& aBuilder,
699     mozilla::wr::IpcResourceUpdateQueue& aResources,
700     const mozilla::layers::StackingContextHelper& aSc,
701     mozilla::layers::RenderRootStateManager* aManager,
702     nsDisplayListBuilder* aDisplayListBuilder,
703     const nsStyleBorder& aStyleBorder) {
704   auto& borderImage = aStyleBorder.mBorderImageSource;
705   // First try to create commands for simple borders.
706   if (borderImage.IsNone()) {
707     CreateWebRenderCommandsForNullBorder(
708         aItem, aForFrame, aBorderArea, aBuilder, aResources, aSc, aStyleBorder);
709     return ImgDrawResult::SUCCESS;
710   }
711 
712   // Next we try image and gradient borders. Gradients are not supported at
713   // this very moment.
714   if (!borderImage.IsImageRequestType()) {
715     return ImgDrawResult::NOT_SUPPORTED;
716   }
717 
718   if (aStyleBorder.mBorderImageRepeatH == StyleBorderImageRepeat::Space ||
719       aStyleBorder.mBorderImageRepeatV == StyleBorderImageRepeat::Space) {
720     return ImgDrawResult::NOT_SUPPORTED;
721   }
722 
723   uint32_t flags = 0;
724   if (aDisplayListBuilder->IsPaintingToWindow()) {
725     flags |= nsImageRenderer::FLAG_PAINTING_TO_WINDOW;
726   }
727   if (aDisplayListBuilder->ShouldSyncDecodeImages()) {
728     flags |= nsImageRenderer::FLAG_SYNC_DECODE_IMAGES;
729   }
730 
731   image::ImgDrawResult result;
732   Maybe<nsCSSBorderImageRenderer> bir =
733       nsCSSBorderImageRenderer::CreateBorderImageRenderer(
734           aForFrame->PresContext(), aForFrame, aBorderArea, aStyleBorder,
735           aItem->GetPaintRect(), aForFrame->GetSkipSides(), flags, &result);
736 
737   if (!bir) {
738     // We aren't ready. Try to fallback to the null border image if present but
739     // return the draw result for the border image renderer.
740     CreateWebRenderCommandsForNullBorder(
741         aItem, aForFrame, aBorderArea, aBuilder, aResources, aSc, aStyleBorder);
742     return result;
743   }
744 
745   return bir->CreateWebRenderCommands(aItem, aForFrame, aBuilder, aResources,
746                                       aSc, aManager, aDisplayListBuilder);
747 }
748 
ConstructBorderRenderer(nsPresContext * aPresContext,ComputedStyle * aStyle,DrawTarget * aDrawTarget,nsIFrame * aForFrame,const nsRect & aDirtyRect,const nsRect & aBorderArea,const nsStyleBorder & aStyleBorder,Sides aSkipSides,bool * aNeedsClip)749 static nsCSSBorderRenderer ConstructBorderRenderer(
750     nsPresContext* aPresContext, ComputedStyle* aStyle, DrawTarget* aDrawTarget,
751     nsIFrame* aForFrame, const nsRect& aDirtyRect, const nsRect& aBorderArea,
752     const nsStyleBorder& aStyleBorder, Sides aSkipSides, bool* aNeedsClip) {
753   nsMargin border = aStyleBorder.GetComputedBorder();
754 
755   // Compute the outermost boundary of the area that might be painted.
756   // Same coordinate space as aBorderArea & aBGClipRect.
757   nsRect joinedBorderArea = nsCSSRendering::BoxDecorationRectForBorder(
758       aForFrame, aBorderArea, aSkipSides, &aStyleBorder);
759   RectCornerRadii bgRadii;
760   ::GetRadii(aForFrame, aStyleBorder, aBorderArea, joinedBorderArea, &bgRadii);
761 
762   PrintAsFormatString(" joinedBorderArea: %d %d %d %d\n", joinedBorderArea.x,
763                       joinedBorderArea.y, joinedBorderArea.width,
764                       joinedBorderArea.height);
765 
766   // start drawing
767   if (nsCSSRendering::IsBoxDecorationSlice(aStyleBorder)) {
768     if (joinedBorderArea.IsEqualEdges(aBorderArea)) {
769       // No need for a clip, just skip the sides we don't want.
770       border.ApplySkipSides(aSkipSides);
771     } else {
772       // We're drawing borders around the joined continuation boxes so we need
773       // to clip that to the slice that we want for this frame.
774       *aNeedsClip = true;
775     }
776   } else {
777     MOZ_ASSERT(joinedBorderArea.IsEqualEdges(aBorderArea),
778                "Should use aBorderArea for box-decoration-break:clone");
779     MOZ_ASSERT(
780         aForFrame->GetSkipSides().IsEmpty() ||
781             aForFrame->IsTrueOverflowContainer() ||
782             aForFrame->IsColumnSetFrame(),  // a little broader than column-rule
783         "Should not skip sides for box-decoration-break:clone except "
784         "::first-letter/line continuations or other frame types that "
785         "don't have borders but those shouldn't reach this point. "
786         "Overflow containers do reach this point though, as does "
787         "column-rule drawing (which always involves a columnset).");
788     border.ApplySkipSides(aSkipSides);
789   }
790 
791   // Convert to dev pixels.
792   nscoord oneDevPixel = aPresContext->DevPixelsToAppUnits(1);
793   Rect joinedBorderAreaPx = NSRectToRect(joinedBorderArea, oneDevPixel);
794   Float borderWidths[4] = {
795       Float(border.top) / oneDevPixel, Float(border.right) / oneDevPixel,
796       Float(border.bottom) / oneDevPixel, Float(border.left) / oneDevPixel};
797   Rect dirtyRect = NSRectToRect(aDirtyRect, oneDevPixel);
798 
799   StyleBorderStyle borderStyles[4];
800   nscolor borderColors[4];
801 
802   // pull out styles, colors
803   for (const auto i : mozilla::AllPhysicalSides()) {
804     borderStyles[i] = aStyleBorder.GetBorderStyle(i);
805     borderColors[i] = aStyleBorder.BorderColorFor(i).CalcColor(*aStyle);
806   }
807 
808   PrintAsFormatString(
809       " borderStyles: %d %d %d %d\n", static_cast<int>(borderStyles[0]),
810       static_cast<int>(borderStyles[1]), static_cast<int>(borderStyles[2]),
811       static_cast<int>(borderStyles[3]));
812 
813   return nsCSSBorderRenderer(
814       aPresContext, aDrawTarget, dirtyRect, joinedBorderAreaPx, borderStyles,
815       borderWidths, bgRadii, borderColors, !aForFrame->BackfaceIsHidden(),
816       *aNeedsClip ? Some(NSRectToRect(aBorderArea, oneDevPixel)) : Nothing());
817 }
818 
PaintBorderWithStyleBorder(nsPresContext * aPresContext,gfxContext & aRenderingContext,nsIFrame * aForFrame,const nsRect & aDirtyRect,const nsRect & aBorderArea,const nsStyleBorder & aStyleBorder,ComputedStyle * aStyle,PaintBorderFlags aFlags,Sides aSkipSides)819 ImgDrawResult nsCSSRendering::PaintBorderWithStyleBorder(
820     nsPresContext* aPresContext, gfxContext& aRenderingContext,
821     nsIFrame* aForFrame, const nsRect& aDirtyRect, const nsRect& aBorderArea,
822     const nsStyleBorder& aStyleBorder, ComputedStyle* aStyle,
823     PaintBorderFlags aFlags, Sides aSkipSides) {
824   DrawTarget& aDrawTarget = *aRenderingContext.GetDrawTarget();
825 
826   PrintAsStringNewline("++ PaintBorder");
827 
828   // Check to see if we have an appearance defined.  If so, we let the theme
829   // renderer draw the border.  DO not get the data from aForFrame, since the
830   // passed in ComputedStyle may be different!  Always use |aStyle|!
831   StyleAppearance appearance = aStyle->StyleDisplay()->EffectiveAppearance();
832   if (appearance != StyleAppearance::None) {
833     nsITheme* theme = aPresContext->Theme();
834     if (theme->ThemeSupportsWidget(aPresContext, aForFrame, appearance)) {
835       return ImgDrawResult::SUCCESS;  // Let the theme handle it.
836     }
837   }
838 
839   if (!aStyleBorder.mBorderImageSource.IsNone()) {
840     ImgDrawResult result = ImgDrawResult::SUCCESS;
841 
842     uint32_t irFlags = 0;
843     if (aFlags & PaintBorderFlags::SyncDecodeImages) {
844       irFlags |= nsImageRenderer::FLAG_SYNC_DECODE_IMAGES;
845     }
846 
847     // Creating the border image renderer will request a decode, and we rely on
848     // that happening.
849     Maybe<nsCSSBorderImageRenderer> renderer =
850         nsCSSBorderImageRenderer::CreateBorderImageRenderer(
851             aPresContext, aForFrame, aBorderArea, aStyleBorder, aDirtyRect,
852             aSkipSides, irFlags, &result);
853     // renderer was created successfully, which means border image is ready to
854     // be used.
855     if (renderer) {
856       MOZ_ASSERT(result == ImgDrawResult::SUCCESS);
857       return renderer->DrawBorderImage(aPresContext, aRenderingContext,
858                                        aForFrame, aDirtyRect);
859     }
860   }
861 
862   ImgDrawResult result = ImgDrawResult::SUCCESS;
863 
864   // If we had a border-image, but it wasn't loaded, then we should return
865   // ImgDrawResult::NOT_READY; we'll want to try again if we do a paint with
866   // sync decoding enabled.
867   if (!aStyleBorder.mBorderImageSource.IsNone()) {
868     result = ImgDrawResult::NOT_READY;
869   }
870 
871   nsMargin border = aStyleBorder.GetComputedBorder();
872   if (0 == border.left && 0 == border.right && 0 == border.top &&
873       0 == border.bottom) {
874     // Empty border area
875     return result;
876   }
877 
878   bool needsClip = false;
879   nsCSSBorderRenderer br = ConstructBorderRenderer(
880       aPresContext, aStyle, &aDrawTarget, aForFrame, aDirtyRect, aBorderArea,
881       aStyleBorder, aSkipSides, &needsClip);
882   if (needsClip) {
883     aDrawTarget.PushClipRect(NSRectToSnappedRect(
884         aBorderArea, aForFrame->PresContext()->AppUnitsPerDevPixel(),
885         aDrawTarget));
886   }
887 
888   br.DrawBorders();
889 
890   if (needsClip) {
891     aDrawTarget.PopClip();
892   }
893 
894   PrintAsStringNewline();
895 
896   return result;
897 }
898 
CreateBorderRendererWithStyleBorder(nsPresContext * aPresContext,DrawTarget * aDrawTarget,nsIFrame * aForFrame,const nsRect & aDirtyRect,const nsRect & aBorderArea,const nsStyleBorder & aStyleBorder,ComputedStyle * aStyle,bool * aOutBorderIsEmpty,Sides aSkipSides)899 Maybe<nsCSSBorderRenderer> nsCSSRendering::CreateBorderRendererWithStyleBorder(
900     nsPresContext* aPresContext, DrawTarget* aDrawTarget, nsIFrame* aForFrame,
901     const nsRect& aDirtyRect, const nsRect& aBorderArea,
902     const nsStyleBorder& aStyleBorder, ComputedStyle* aStyle,
903     bool* aOutBorderIsEmpty, Sides aSkipSides) {
904   if (!aStyleBorder.mBorderImageSource.IsNone()) {
905     return Nothing();
906   }
907   return CreateNullBorderRendererWithStyleBorder(
908       aPresContext, aDrawTarget, aForFrame, aDirtyRect, aBorderArea,
909       aStyleBorder, aStyle, aOutBorderIsEmpty, aSkipSides);
910 }
911 
912 Maybe<nsCSSBorderRenderer>
CreateNullBorderRendererWithStyleBorder(nsPresContext * aPresContext,DrawTarget * aDrawTarget,nsIFrame * aForFrame,const nsRect & aDirtyRect,const nsRect & aBorderArea,const nsStyleBorder & aStyleBorder,ComputedStyle * aStyle,bool * aOutBorderIsEmpty,Sides aSkipSides)913 nsCSSRendering::CreateNullBorderRendererWithStyleBorder(
914     nsPresContext* aPresContext, DrawTarget* aDrawTarget, nsIFrame* aForFrame,
915     const nsRect& aDirtyRect, const nsRect& aBorderArea,
916     const nsStyleBorder& aStyleBorder, ComputedStyle* aStyle,
917     bool* aOutBorderIsEmpty, Sides aSkipSides) {
918   StyleAppearance appearance = aStyle->StyleDisplay()->EffectiveAppearance();
919   if (appearance != StyleAppearance::None) {
920     nsITheme* theme = aPresContext->Theme();
921     if (theme->ThemeSupportsWidget(aPresContext, aForFrame, appearance)) {
922       return Nothing();
923     }
924   }
925 
926   nsMargin border = aStyleBorder.GetComputedBorder();
927   if (0 == border.left && 0 == border.right && 0 == border.top &&
928       0 == border.bottom) {
929     // Empty border area
930     if (aOutBorderIsEmpty) {
931       *aOutBorderIsEmpty = true;
932     }
933     return Nothing();
934   }
935 
936   bool needsClip = false;
937   nsCSSBorderRenderer br = ConstructBorderRenderer(
938       aPresContext, aStyle, aDrawTarget, aForFrame, aDirtyRect, aBorderArea,
939       aStyleBorder, aSkipSides, &needsClip);
940   return Some(br);
941 }
942 
943 Maybe<nsCSSBorderRenderer>
CreateBorderRendererForNonThemedOutline(nsPresContext * aPresContext,DrawTarget * aDrawTarget,nsIFrame * aForFrame,const nsRect & aDirtyRect,const nsRect & aInnerRect,ComputedStyle * aStyle)944 nsCSSRendering::CreateBorderRendererForNonThemedOutline(
945     nsPresContext* aPresContext, DrawTarget* aDrawTarget, nsIFrame* aForFrame,
946     const nsRect& aDirtyRect, const nsRect& aInnerRect, ComputedStyle* aStyle) {
947   // Get our ComputedStyle's color struct.
948   const nsStyleOutline* ourOutline = aStyle->StyleOutline();
949   if (!ourOutline->ShouldPaintOutline()) {
950     // Empty outline
951     return Nothing();
952   }
953 
954   const nscoord offset = ourOutline->mOutlineOffset.ToAppUnits();
955   nsRect innerRect = aInnerRect;
956   innerRect.Inflate(offset);
957 
958   // If the dirty rect is completely inside the border area (e.g., only the
959   // content is being painted), then we can skip out now
960   // XXX this isn't exactly true for rounded borders, where the inside curves
961   // may encroach into the content area.  A safer calculation would be to
962   // shorten insideRect by the radius one each side before performing this test.
963   if (innerRect.Contains(aDirtyRect)) {
964     return Nothing();
965   }
966 
967   nscoord width = ourOutline->GetOutlineWidth();
968 
969   StyleBorderStyle outlineStyle;
970   // Themed outlines are handled by our callers, if supported.
971   if (ourOutline->mOutlineStyle.IsAuto()) {
972     if (width == 0) {
973       return Nothing();  // empty outline
974     }
975     // http://dev.w3.org/csswg/css-ui/#outline
976     // "User agents may treat 'auto' as 'solid'."
977     outlineStyle = StyleBorderStyle::Solid;
978   } else {
979     outlineStyle = ourOutline->mOutlineStyle.AsBorderStyle();
980   }
981 
982   RectCornerRadii outlineRadii;
983   nsRect outerRect = innerRect;
984   outerRect.Inflate(width);
985 
986   const nscoord oneDevPixel = aPresContext->AppUnitsPerDevPixel();
987   Rect oRect(NSRectToRect(outerRect, oneDevPixel));
988 
989   const Float outlineWidths[4] = {
990       Float(width) / oneDevPixel, Float(width) / oneDevPixel,
991       Float(width) / oneDevPixel, Float(width) / oneDevPixel};
992 
993   // convert the radii
994   nscoord twipsRadii[8];
995 
996   // get the radius for our outline
997   if (aForFrame->GetBorderRadii(twipsRadii)) {
998     RectCornerRadii innerRadii;
999     ComputePixelRadii(twipsRadii, oneDevPixel, &innerRadii);
1000 
1001     Float devPixelOffset = aPresContext->AppUnitsToFloatDevPixels(offset);
1002     const Float widths[4] = {
1003         outlineWidths[0] + devPixelOffset, outlineWidths[1] + devPixelOffset,
1004         outlineWidths[2] + devPixelOffset, outlineWidths[3] + devPixelOffset};
1005     nsCSSBorderRenderer::ComputeOuterRadii(innerRadii, widths, &outlineRadii);
1006   }
1007 
1008   StyleBorderStyle outlineStyles[4] = {outlineStyle, outlineStyle, outlineStyle,
1009                                        outlineStyle};
1010 
1011   // This handles treating the initial color as 'currentColor'; if we
1012   // ever want 'invert' back we'll need to do a bit of work here too.
1013   nscolor outlineColor =
1014       aStyle->GetVisitedDependentColor(&nsStyleOutline::mOutlineColor);
1015   nscolor outlineColors[4] = {outlineColor, outlineColor, outlineColor,
1016                               outlineColor};
1017 
1018   Rect dirtyRect = NSRectToRect(aDirtyRect, oneDevPixel);
1019 
1020   return Some(nsCSSBorderRenderer(
1021       aPresContext, aDrawTarget, dirtyRect, oRect, outlineStyles, outlineWidths,
1022       outlineRadii, outlineColors, !aForFrame->BackfaceIsHidden(), Nothing()));
1023 }
1024 
PaintNonThemedOutline(nsPresContext * aPresContext,gfxContext & aRenderingContext,nsIFrame * aForFrame,const nsRect & aDirtyRect,const nsRect & aInnerRect,ComputedStyle * aStyle)1025 void nsCSSRendering::PaintNonThemedOutline(nsPresContext* aPresContext,
1026                                            gfxContext& aRenderingContext,
1027                                            nsIFrame* aForFrame,
1028                                            const nsRect& aDirtyRect,
1029                                            const nsRect& aInnerRect,
1030                                            ComputedStyle* aStyle) {
1031   Maybe<nsCSSBorderRenderer> br = CreateBorderRendererForNonThemedOutline(
1032       aPresContext, aRenderingContext.GetDrawTarget(), aForFrame, aDirtyRect,
1033       aInnerRect, aStyle);
1034   if (!br) {
1035     return;
1036   }
1037 
1038   // start drawing
1039   br->DrawBorders();
1040 
1041   PrintAsStringNewline();
1042 }
1043 
PaintFocus(nsPresContext * aPresContext,DrawTarget * aDrawTarget,const nsRect & aFocusRect,nscolor aColor)1044 void nsCSSRendering::PaintFocus(nsPresContext* aPresContext,
1045                                 DrawTarget* aDrawTarget,
1046                                 const nsRect& aFocusRect, nscolor aColor) {
1047   nscoord oneCSSPixel = nsPresContext::CSSPixelsToAppUnits(1);
1048   nscoord oneDevPixel = aPresContext->DevPixelsToAppUnits(1);
1049 
1050   Rect focusRect(NSRectToRect(aFocusRect, oneDevPixel));
1051 
1052   RectCornerRadii focusRadii;
1053   {
1054     nscoord twipsRadii[8] = {0, 0, 0, 0, 0, 0, 0, 0};
1055     ComputePixelRadii(twipsRadii, oneDevPixel, &focusRadii);
1056   }
1057   Float focusWidths[4] = {
1058       Float(oneCSSPixel) / oneDevPixel, Float(oneCSSPixel) / oneDevPixel,
1059       Float(oneCSSPixel) / oneDevPixel, Float(oneCSSPixel) / oneDevPixel};
1060 
1061   StyleBorderStyle focusStyles[4] = {
1062       StyleBorderStyle::Dotted, StyleBorderStyle::Dotted,
1063       StyleBorderStyle::Dotted, StyleBorderStyle::Dotted};
1064   nscolor focusColors[4] = {aColor, aColor, aColor, aColor};
1065 
1066   // Because this renders a dotted border, the background color
1067   // should not be used.  Therefore, we provide a value that will
1068   // be blatantly wrong if it ever does get used.  (If this becomes
1069   // something that CSS can style, this function will then have access
1070   // to a ComputedStyle and can use the same logic that PaintBorder
1071   // and PaintOutline do.)
1072   //
1073   // WebRender layers-free mode don't use PaintFocus function. Just assign
1074   // the backface-visibility to true for this case.
1075   nsCSSBorderRenderer br(aPresContext, aDrawTarget, focusRect, focusRect,
1076                          focusStyles, focusWidths, focusRadii, focusColors,
1077                          true, Nothing());
1078   br.DrawBorders();
1079 
1080   PrintAsStringNewline();
1081 }
1082 
1083 // Thebes Border Rendering Code End
1084 //----------------------------------------------------------------------
1085 
1086 //----------------------------------------------------------------------
1087 
1088 /**
1089  * Helper for ComputeObjectAnchorPoint; parameters are the same as for
1090  * that function, except they're for a single coordinate / a single size
1091  * dimension. (so, x/width vs. y/height)
1092  */
ComputeObjectAnchorCoord(const LengthPercentage & aCoord,const nscoord aOriginBounds,const nscoord aImageSize,nscoord * aTopLeftCoord,nscoord * aAnchorPointCoord)1093 static void ComputeObjectAnchorCoord(const LengthPercentage& aCoord,
1094                                      const nscoord aOriginBounds,
1095                                      const nscoord aImageSize,
1096                                      nscoord* aTopLeftCoord,
1097                                      nscoord* aAnchorPointCoord) {
1098   nscoord extraSpace = aOriginBounds - aImageSize;
1099 
1100   // The anchor-point doesn't care about our image's size; just the size
1101   // of the region we're rendering into.
1102   *aAnchorPointCoord = aCoord.Resolve(aOriginBounds, NSToCoordRoundWithClamp);
1103   // Adjust aTopLeftCoord by the specified % of the extra space.
1104   *aTopLeftCoord = aCoord.Resolve(extraSpace, NSToCoordRoundWithClamp);
1105 }
1106 
ComputeObjectAnchorPoint(const Position & aPos,const nsSize & aOriginBounds,const nsSize & aImageSize,nsPoint * aTopLeft,nsPoint * aAnchorPoint)1107 void nsImageRenderer::ComputeObjectAnchorPoint(const Position& aPos,
1108                                                const nsSize& aOriginBounds,
1109                                                const nsSize& aImageSize,
1110                                                nsPoint* aTopLeft,
1111                                                nsPoint* aAnchorPoint) {
1112   ComputeObjectAnchorCoord(aPos.horizontal, aOriginBounds.width,
1113                            aImageSize.width, &aTopLeft->x, &aAnchorPoint->x);
1114 
1115   ComputeObjectAnchorCoord(aPos.vertical, aOriginBounds.height,
1116                            aImageSize.height, &aTopLeft->y, &aAnchorPoint->y);
1117 }
1118 
FindNonTransparentBackgroundFrame(nsIFrame * aFrame,bool aStopAtThemed)1119 auto nsCSSRendering::FindNonTransparentBackgroundFrame(nsIFrame* aFrame,
1120                                                        bool aStopAtThemed)
1121     -> NonTransparentBackgroundFrame {
1122   NS_ASSERTION(aFrame,
1123                "Cannot find NonTransparentBackgroundFrame in a null frame");
1124 
1125   for (nsIFrame* frame = aFrame; frame;
1126        frame = nsLayoutUtils::GetParentOrPlaceholderForCrossDoc(frame)) {
1127     // No need to call GetVisitedDependentColor because it always uses this
1128     // alpha component anyway.
1129     if (NS_GET_A(frame->StyleBackground()->BackgroundColor(frame))) {
1130       return {frame, false, false};
1131     }
1132 
1133     if (aStopAtThemed && frame->IsThemed()) {
1134       return {frame, true, false};
1135     }
1136 
1137     if (IsCanvasFrame(frame)) {
1138       nsIFrame* bgFrame = nullptr;
1139       if (FindBackgroundFrame(frame, &bgFrame) &&
1140           NS_GET_A(bgFrame->StyleBackground()->BackgroundColor(bgFrame))) {
1141         return {bgFrame, false, true};
1142       }
1143     }
1144   }
1145 
1146   return {};
1147 }
1148 
1149 // Returns true if aFrame is a canvas frame.
1150 // We need to treat the viewport as canvas because, even though
1151 // it does not actually paint a background, we need to get the right
1152 // background style so we correctly detect transparent documents.
IsCanvasFrame(const nsIFrame * aFrame)1153 bool nsCSSRendering::IsCanvasFrame(const nsIFrame* aFrame) {
1154   LayoutFrameType frameType = aFrame->Type();
1155   return frameType == LayoutFrameType::Canvas ||
1156          frameType == LayoutFrameType::XULRoot ||
1157          frameType == LayoutFrameType::PageContent ||
1158          frameType == LayoutFrameType::Viewport;
1159 }
1160 
FindBackgroundStyleFrame(nsIFrame * aForFrame)1161 nsIFrame* nsCSSRendering::FindBackgroundStyleFrame(nsIFrame* aForFrame) {
1162   const nsStyleBackground* result = aForFrame->StyleBackground();
1163 
1164   // Check if we need to do propagation from BODY rather than HTML.
1165   if (!result->IsTransparent(aForFrame)) {
1166     return aForFrame;
1167   }
1168 
1169   nsIContent* content = aForFrame->GetContent();
1170   // The root element content can't be null. We wouldn't know what
1171   // frame to create for aFrame.
1172   // Use |OwnerDoc| so it works during destruction.
1173   if (!content) {
1174     return aForFrame;
1175   }
1176 
1177   Document* document = content->OwnerDoc();
1178 
1179   dom::Element* bodyContent = document->GetBodyElement();
1180   // We need to null check the body node (bug 118829) since
1181   // there are cases, thanks to the fix for bug 5569, where we
1182   // will reflow a document with no body.  In particular, if a
1183   // SCRIPT element in the head blocks the parser and then has a
1184   // SCRIPT that does "document.location.href = 'foo'", then
1185   // nsParser::Terminate will call |DidBuildModel| methods
1186   // through to the content sink, which will call |StartLayout|
1187   // and thus |Initialize| on the pres shell.  See bug 119351
1188   // for the ugly details.
1189   if (!bodyContent) {
1190     return aForFrame;
1191   }
1192 
1193   nsIFrame* bodyFrame = bodyContent->GetPrimaryFrame();
1194   if (!bodyFrame) {
1195     return aForFrame;
1196   }
1197 
1198   return nsLayoutUtils::GetStyleFrame(bodyFrame);
1199 }
1200 
1201 /**
1202  * |FindBackground| finds the correct style data to use to paint the
1203  * background.  It is responsible for handling the following two
1204  * statements in section 14.2 of CSS2:
1205  *
1206  *   The background of the box generated by the root element covers the
1207  *   entire canvas.
1208  *
1209  *   For HTML documents, however, we recommend that authors specify the
1210  *   background for the BODY element rather than the HTML element. User
1211  *   agents should observe the following precedence rules to fill in the
1212  *   background: if the value of the 'background' property for the HTML
1213  *   element is different from 'transparent' then use it, else use the
1214  *   value of the 'background' property for the BODY element. If the
1215  *   resulting value is 'transparent', the rendering is undefined.
1216  *
1217  * Thus, in our implementation, it is responsible for ensuring that:
1218  *  + we paint the correct background on the |nsCanvasFrame|,
1219  *    |nsRootBoxFrame|, or |nsPageFrame|,
1220  *  + we don't paint the background on the root element, and
1221  *  + we don't paint the background on the BODY element in *some* cases,
1222  *    and for SGML-based HTML documents only.
1223  *
1224  * |FindBackground| returns true if a background should be painted, and
1225  * the resulting ComputedStyle to use for the background information
1226  * will be filled in to |aBackground|.
1227  */
FindRootFrameBackground(nsIFrame * aForFrame)1228 ComputedStyle* nsCSSRendering::FindRootFrameBackground(nsIFrame* aForFrame) {
1229   return FindBackgroundStyleFrame(aForFrame)->Style();
1230 }
1231 
FindElementBackground(const nsIFrame * aForFrame,nsIFrame * aRootElementFrame)1232 inline bool FindElementBackground(const nsIFrame* aForFrame,
1233                                   nsIFrame* aRootElementFrame) {
1234   if (aForFrame == aRootElementFrame) {
1235     // We must have propagated our background to the viewport or canvas. Abort.
1236     return false;
1237   }
1238 
1239   // Return true unless the frame is for a BODY element whose background
1240   // was propagated to the viewport.
1241 
1242   nsIContent* content = aForFrame->GetContent();
1243   if (!content || content->NodeInfo()->NameAtom() != nsGkAtoms::body)
1244     return true;  // not frame for a "body" element
1245   // It could be a non-HTML "body" element but that's OK, we'd fail the
1246   // bodyContent check below
1247 
1248   if (aForFrame->Style()->GetPseudoType() != PseudoStyleType::NotPseudo) {
1249     return true;  // A pseudo-element frame.
1250   }
1251 
1252   // We should only look at the <html> background if we're in an HTML document
1253   Document* document = content->OwnerDoc();
1254 
1255   dom::Element* bodyContent = document->GetBodyElement();
1256   if (bodyContent != content)
1257     return true;  // this wasn't the background that was propagated
1258 
1259   // This can be called even when there's no root element yet, during frame
1260   // construction, via nsLayoutUtils::FrameHasTransparency and
1261   // nsContainerFrame::SyncFrameViewProperties.
1262   if (!aRootElementFrame) {
1263     return true;
1264   }
1265 
1266   const nsStyleBackground* htmlBG = aRootElementFrame->StyleBackground();
1267   return !htmlBG->IsTransparent(aRootElementFrame);
1268 }
1269 
FindBackgroundFrame(const nsIFrame * aForFrame,nsIFrame ** aBackgroundFrame)1270 bool nsCSSRendering::FindBackgroundFrame(const nsIFrame* aForFrame,
1271                                          nsIFrame** aBackgroundFrame) {
1272   nsIFrame* rootElementFrame =
1273       aForFrame->PresShell()->FrameConstructor()->GetRootElementStyleFrame();
1274   if (IsCanvasFrame(aForFrame)) {
1275     *aBackgroundFrame = FindCanvasBackgroundFrame(aForFrame, rootElementFrame);
1276     return true;
1277   }
1278 
1279   *aBackgroundFrame = const_cast<nsIFrame*>(aForFrame);
1280   return FindElementBackground(aForFrame, rootElementFrame);
1281 }
1282 
FindBackground(const nsIFrame * aForFrame,ComputedStyle ** aBackgroundSC)1283 bool nsCSSRendering::FindBackground(const nsIFrame* aForFrame,
1284                                     ComputedStyle** aBackgroundSC) {
1285   nsIFrame* backgroundFrame = nullptr;
1286   if (FindBackgroundFrame(aForFrame, &backgroundFrame)) {
1287     *aBackgroundSC = backgroundFrame->Style();
1288     return true;
1289   }
1290   return false;
1291 }
1292 
BeginFrameTreesLocked()1293 void nsCSSRendering::BeginFrameTreesLocked() { ++gFrameTreeLockCount; }
1294 
EndFrameTreesLocked()1295 void nsCSSRendering::EndFrameTreesLocked() {
1296   NS_ASSERTION(gFrameTreeLockCount > 0, "Unbalanced EndFrameTreeLocked");
1297   --gFrameTreeLockCount;
1298   if (gFrameTreeLockCount == 0) {
1299     gInlineBGData->Reset();
1300   }
1301 }
1302 
HasBoxShadowNativeTheme(nsIFrame * aFrame,bool & aMaybeHasBorderRadius)1303 bool nsCSSRendering::HasBoxShadowNativeTheme(nsIFrame* aFrame,
1304                                              bool& aMaybeHasBorderRadius) {
1305   const nsStyleDisplay* styleDisplay = aFrame->StyleDisplay();
1306   nsITheme::Transparency transparency;
1307   if (aFrame->IsThemed(styleDisplay, &transparency)) {
1308     aMaybeHasBorderRadius = false;
1309     // For opaque (rectangular) theme widgets we can take the generic
1310     // border-box path with border-radius disabled.
1311     return transparency != nsITheme::eOpaque;
1312   }
1313 
1314   aMaybeHasBorderRadius = true;
1315   return false;
1316 }
1317 
GetShadowColor(const StyleSimpleShadow & aShadow,nsIFrame * aFrame,float aOpacity)1318 gfx::sRGBColor nsCSSRendering::GetShadowColor(const StyleSimpleShadow& aShadow,
1319                                               nsIFrame* aFrame,
1320                                               float aOpacity) {
1321   // Get the shadow color; if not specified, use the foreground color
1322   nscolor shadowColor = aShadow.color.CalcColor(aFrame);
1323   sRGBColor color = sRGBColor::FromABGR(shadowColor);
1324   color.a *= aOpacity;
1325   return color;
1326 }
1327 
GetShadowRect(const nsRect & aFrameArea,bool aNativeTheme,nsIFrame * aForFrame)1328 nsRect nsCSSRendering::GetShadowRect(const nsRect& aFrameArea,
1329                                      bool aNativeTheme, nsIFrame* aForFrame) {
1330   nsRect frameRect = aNativeTheme ? aForFrame->InkOverflowRectRelativeToSelf() +
1331                                         aFrameArea.TopLeft()
1332                                   : aFrameArea;
1333   Sides skipSides = aForFrame->GetSkipSides();
1334   frameRect = BoxDecorationRectForBorder(aForFrame, frameRect, skipSides);
1335 
1336   // Explicitly do not need to account for the spread radius here
1337   // Webrender does it for us or PaintBoxShadow will for non-WR
1338   return frameRect;
1339 }
1340 
GetBorderRadii(const nsRect & aFrameRect,const nsRect & aBorderRect,nsIFrame * aFrame,RectCornerRadii & aOutRadii)1341 bool nsCSSRendering::GetBorderRadii(const nsRect& aFrameRect,
1342                                     const nsRect& aBorderRect, nsIFrame* aFrame,
1343                                     RectCornerRadii& aOutRadii) {
1344   const nscoord oneDevPixel = aFrame->PresContext()->DevPixelsToAppUnits(1);
1345   nscoord twipsRadii[8];
1346   NS_ASSERTION(
1347       aBorderRect.Size() == aFrame->VisualBorderRectRelativeToSelf().Size(),
1348       "unexpected size");
1349   nsSize sz = aFrameRect.Size();
1350   bool hasBorderRadius = aFrame->GetBorderRadii(sz, sz, Sides(), twipsRadii);
1351   if (hasBorderRadius) {
1352     ComputePixelRadii(twipsRadii, oneDevPixel, &aOutRadii);
1353   }
1354 
1355   return hasBorderRadius;
1356 }
1357 
PaintBoxShadowOuter(nsPresContext * aPresContext,gfxContext & aRenderingContext,nsIFrame * aForFrame,const nsRect & aFrameArea,const nsRect & aDirtyRect,float aOpacity)1358 void nsCSSRendering::PaintBoxShadowOuter(nsPresContext* aPresContext,
1359                                          gfxContext& aRenderingContext,
1360                                          nsIFrame* aForFrame,
1361                                          const nsRect& aFrameArea,
1362                                          const nsRect& aDirtyRect,
1363                                          float aOpacity) {
1364   DrawTarget& aDrawTarget = *aRenderingContext.GetDrawTarget();
1365   auto shadows = aForFrame->StyleEffects()->mBoxShadow.AsSpan();
1366   if (shadows.IsEmpty()) {
1367     return;
1368   }
1369 
1370   bool hasBorderRadius;
1371   // mutually exclusive with hasBorderRadius
1372   bool nativeTheme = HasBoxShadowNativeTheme(aForFrame, hasBorderRadius);
1373   const nsStyleDisplay* styleDisplay = aForFrame->StyleDisplay();
1374 
1375   nsRect frameRect = GetShadowRect(aFrameArea, nativeTheme, aForFrame);
1376 
1377   // Get any border radius, since box-shadow must also have rounded corners if
1378   // the frame does.
1379   RectCornerRadii borderRadii;
1380   const nscoord oneDevPixel = aPresContext->DevPixelsToAppUnits(1);
1381   if (hasBorderRadius) {
1382     nscoord twipsRadii[8];
1383     NS_ASSERTION(
1384         aFrameArea.Size() == aForFrame->VisualBorderRectRelativeToSelf().Size(),
1385         "unexpected size");
1386     nsSize sz = frameRect.Size();
1387     hasBorderRadius = aForFrame->GetBorderRadii(sz, sz, Sides(), twipsRadii);
1388     if (hasBorderRadius) {
1389       ComputePixelRadii(twipsRadii, oneDevPixel, &borderRadii);
1390     }
1391   }
1392 
1393   // We don't show anything that intersects with the frame we're blurring on. So
1394   // tell the blurrer not to do unnecessary work there.
1395   gfxRect skipGfxRect = ThebesRect(NSRectToRect(frameRect, oneDevPixel));
1396   skipGfxRect.Round();
1397   bool useSkipGfxRect = true;
1398   if (nativeTheme) {
1399     // Optimize non-leaf native-themed frames by skipping computing pixels
1400     // in the padding-box. We assume the padding-box is going to be painted
1401     // opaquely for non-leaf frames.
1402     // XXX this may not be a safe assumption; we should make this go away
1403     // by optimizing box-shadow drawing more for the cases where we don't have a
1404     // skip-rect.
1405     useSkipGfxRect = !aForFrame->IsLeaf();
1406     nsRect paddingRect =
1407         aForFrame->GetPaddingRectRelativeToSelf() + aFrameArea.TopLeft();
1408     skipGfxRect = nsLayoutUtils::RectToGfxRect(paddingRect, oneDevPixel);
1409   } else if (hasBorderRadius) {
1410     skipGfxRect.Deflate(gfxMargin(
1411         std::max(borderRadii[C_TL].height, borderRadii[C_TR].height), 0,
1412         std::max(borderRadii[C_BL].height, borderRadii[C_BR].height), 0));
1413   }
1414 
1415   for (const StyleBoxShadow& shadow : Reversed(shadows)) {
1416     if (shadow.inset) {
1417       continue;
1418     }
1419 
1420     nsRect shadowRect = frameRect;
1421     nsPoint shadowOffset(shadow.base.horizontal.ToAppUnits(),
1422                          shadow.base.vertical.ToAppUnits());
1423     shadowRect.MoveBy(shadowOffset);
1424     nscoord shadowSpread = shadow.spread.ToAppUnits();
1425     if (!nativeTheme) {
1426       shadowRect.Inflate(shadowSpread);
1427     }
1428 
1429     // shadowRect won't include the blur, so make an extra rect here that
1430     // includes the blur for use in the even-odd rule below.
1431     nsRect shadowRectPlusBlur = shadowRect;
1432     nscoord blurRadius = shadow.base.blur.ToAppUnits();
1433     shadowRectPlusBlur.Inflate(
1434         nsContextBoxBlur::GetBlurRadiusMargin(blurRadius, oneDevPixel));
1435 
1436     Rect shadowGfxRectPlusBlur = NSRectToRect(shadowRectPlusBlur, oneDevPixel);
1437     shadowGfxRectPlusBlur.RoundOut();
1438     MaybeSnapToDevicePixels(shadowGfxRectPlusBlur, aDrawTarget, true);
1439 
1440     sRGBColor gfxShadowColor = GetShadowColor(shadow.base, aForFrame, aOpacity);
1441 
1442     if (nativeTheme) {
1443       nsContextBoxBlur blurringArea;
1444 
1445       // When getting the widget shape from the native theme, we're going
1446       // to draw the widget into the shadow surface to create a mask.
1447       // We need to ensure that there actually *is* a shadow surface
1448       // and that we're not going to draw directly into aRenderingContext.
1449       gfxContext* shadowContext = blurringArea.Init(
1450           shadowRect, shadowSpread, blurRadius, oneDevPixel, &aRenderingContext,
1451           aDirtyRect, useSkipGfxRect ? &skipGfxRect : nullptr,
1452           nsContextBoxBlur::FORCE_MASK);
1453       if (!shadowContext) continue;
1454 
1455       MOZ_ASSERT(shadowContext == blurringArea.GetContext());
1456 
1457       aRenderingContext.Save();
1458       aRenderingContext.SetColor(gfxShadowColor);
1459 
1460       // Draw the shape of the frame so it can be blurred. Recall how
1461       // nsContextBoxBlur doesn't make any temporary surfaces if blur is 0 and
1462       // it just returns the original surface? If we have no blur, we're
1463       // painting this fill on the actual content surface (aRenderingContext ==
1464       // shadowContext) which is why we set up the color and clip before doing
1465       // this.
1466 
1467       // We don't clip the border-box from the shadow, nor any other box.
1468       // We assume that the native theme is going to paint over the shadow.
1469 
1470       // Draw the widget shape
1471       gfxContextMatrixAutoSaveRestore save(shadowContext);
1472       gfxPoint devPixelOffset = nsLayoutUtils::PointToGfxPoint(
1473           shadowOffset, aPresContext->AppUnitsPerDevPixel());
1474       shadowContext->SetMatrixDouble(
1475           shadowContext->CurrentMatrixDouble().PreTranslate(devPixelOffset));
1476 
1477       nsRect nativeRect = aDirtyRect;
1478       nativeRect.MoveBy(-shadowOffset);
1479       nativeRect.IntersectRect(frameRect, nativeRect);
1480       aPresContext->Theme()->DrawWidgetBackground(
1481           shadowContext, aForFrame, styleDisplay->EffectiveAppearance(),
1482           aFrameArea, nativeRect, nsITheme::DrawOverflow::No);
1483 
1484       blurringArea.DoPaint();
1485       aRenderingContext.Restore();
1486     } else {
1487       aRenderingContext.Save();
1488 
1489       {
1490         Rect innerClipRect = NSRectToRect(frameRect, oneDevPixel);
1491         if (!MaybeSnapToDevicePixels(innerClipRect, aDrawTarget, true)) {
1492           innerClipRect.Round();
1493         }
1494 
1495         // Clip out the interior of the frame's border edge so that the shadow
1496         // is only painted outside that area.
1497         RefPtr<PathBuilder> builder =
1498             aDrawTarget.CreatePathBuilder(FillRule::FILL_EVEN_ODD);
1499         AppendRectToPath(builder, shadowGfxRectPlusBlur);
1500         if (hasBorderRadius) {
1501           AppendRoundedRectToPath(builder, innerClipRect, borderRadii);
1502         } else {
1503           AppendRectToPath(builder, innerClipRect);
1504         }
1505         RefPtr<Path> path = builder->Finish();
1506         aRenderingContext.Clip(path);
1507       }
1508 
1509       // Clip the shadow so that we only get the part that applies to aForFrame.
1510       nsRect fragmentClip = shadowRectPlusBlur;
1511       Sides skipSides = aForFrame->GetSkipSides();
1512       if (!skipSides.IsEmpty()) {
1513         if (skipSides.Left()) {
1514           nscoord xmost = fragmentClip.XMost();
1515           fragmentClip.x = aFrameArea.x;
1516           fragmentClip.width = xmost - fragmentClip.x;
1517         }
1518         if (skipSides.Right()) {
1519           nscoord xmost = fragmentClip.XMost();
1520           nscoord overflow = xmost - aFrameArea.XMost();
1521           if (overflow > 0) {
1522             fragmentClip.width -= overflow;
1523           }
1524         }
1525         if (skipSides.Top()) {
1526           nscoord ymost = fragmentClip.YMost();
1527           fragmentClip.y = aFrameArea.y;
1528           fragmentClip.height = ymost - fragmentClip.y;
1529         }
1530         if (skipSides.Bottom()) {
1531           nscoord ymost = fragmentClip.YMost();
1532           nscoord overflow = ymost - aFrameArea.YMost();
1533           if (overflow > 0) {
1534             fragmentClip.height -= overflow;
1535           }
1536         }
1537       }
1538       fragmentClip = fragmentClip.Intersect(aDirtyRect);
1539       aRenderingContext.Clip(NSRectToSnappedRect(
1540           fragmentClip, aForFrame->PresContext()->AppUnitsPerDevPixel(),
1541           aDrawTarget));
1542 
1543       RectCornerRadii clipRectRadii;
1544       if (hasBorderRadius) {
1545         Float spreadDistance = Float(shadowSpread / oneDevPixel);
1546 
1547         Float borderSizes[4];
1548 
1549         borderSizes[eSideLeft] = spreadDistance;
1550         borderSizes[eSideTop] = spreadDistance;
1551         borderSizes[eSideRight] = spreadDistance;
1552         borderSizes[eSideBottom] = spreadDistance;
1553 
1554         nsCSSBorderRenderer::ComputeOuterRadii(borderRadii, borderSizes,
1555                                                &clipRectRadii);
1556       }
1557       nsContextBoxBlur::BlurRectangle(
1558           &aRenderingContext, shadowRect, oneDevPixel,
1559           hasBorderRadius ? &clipRectRadii : nullptr, blurRadius,
1560           gfxShadowColor, aDirtyRect, skipGfxRect);
1561       aRenderingContext.Restore();
1562     }
1563   }
1564 }
1565 
GetBoxShadowInnerPaddingRect(nsIFrame * aFrame,const nsRect & aFrameArea)1566 nsRect nsCSSRendering::GetBoxShadowInnerPaddingRect(nsIFrame* aFrame,
1567                                                     const nsRect& aFrameArea) {
1568   Sides skipSides = aFrame->GetSkipSides();
1569   nsRect frameRect = BoxDecorationRectForBorder(aFrame, aFrameArea, skipSides);
1570 
1571   nsRect paddingRect = frameRect;
1572   nsMargin border = aFrame->GetUsedBorder();
1573   paddingRect.Deflate(border);
1574   return paddingRect;
1575 }
1576 
ShouldPaintBoxShadowInner(nsIFrame * aFrame)1577 bool nsCSSRendering::ShouldPaintBoxShadowInner(nsIFrame* aFrame) {
1578   const Span<const StyleBoxShadow> shadows =
1579       aFrame->StyleEffects()->mBoxShadow.AsSpan();
1580   if (shadows.IsEmpty()) {
1581     return false;
1582   }
1583 
1584   if (aFrame->IsThemed() && aFrame->GetContent() &&
1585       !nsContentUtils::IsChromeDoc(aFrame->GetContent()->GetComposedDoc())) {
1586     // There's no way of getting hold of a shape corresponding to a
1587     // "padding-box" for native-themed widgets, so just don't draw
1588     // inner box-shadows for them. But we allow chrome to paint inner
1589     // box shadows since chrome can be aware of the platform theme.
1590     return false;
1591   }
1592 
1593   return true;
1594 }
1595 
GetShadowInnerRadii(nsIFrame * aFrame,const nsRect & aFrameArea,RectCornerRadii & aOutInnerRadii)1596 bool nsCSSRendering::GetShadowInnerRadii(nsIFrame* aFrame,
1597                                          const nsRect& aFrameArea,
1598                                          RectCornerRadii& aOutInnerRadii) {
1599   // Get any border radius, since box-shadow must also have rounded corners
1600   // if the frame does.
1601   nscoord twipsRadii[8];
1602   nsRect frameRect =
1603       BoxDecorationRectForBorder(aFrame, aFrameArea, aFrame->GetSkipSides());
1604   nsSize sz = frameRect.Size();
1605   nsMargin border = aFrame->GetUsedBorder();
1606   aFrame->GetBorderRadii(sz, sz, Sides(), twipsRadii);
1607   const nscoord oneDevPixel = aFrame->PresContext()->DevPixelsToAppUnits(1);
1608 
1609   RectCornerRadii borderRadii;
1610 
1611   const bool hasBorderRadius =
1612       GetBorderRadii(frameRect, aFrameArea, aFrame, borderRadii);
1613 
1614   if (hasBorderRadius) {
1615     ComputePixelRadii(twipsRadii, oneDevPixel, &borderRadii);
1616 
1617     Float borderSizes[4] = {
1618         Float(border.top) / oneDevPixel, Float(border.right) / oneDevPixel,
1619         Float(border.bottom) / oneDevPixel, Float(border.left) / oneDevPixel};
1620     nsCSSBorderRenderer::ComputeInnerRadii(borderRadii, borderSizes,
1621                                            &aOutInnerRadii);
1622   }
1623 
1624   return hasBorderRadius;
1625 }
1626 
PaintBoxShadowInner(nsPresContext * aPresContext,gfxContext & aRenderingContext,nsIFrame * aForFrame,const nsRect & aFrameArea)1627 void nsCSSRendering::PaintBoxShadowInner(nsPresContext* aPresContext,
1628                                          gfxContext& aRenderingContext,
1629                                          nsIFrame* aForFrame,
1630                                          const nsRect& aFrameArea) {
1631   if (!ShouldPaintBoxShadowInner(aForFrame)) {
1632     return;
1633   }
1634 
1635   const Span<const StyleBoxShadow> shadows =
1636       aForFrame->StyleEffects()->mBoxShadow.AsSpan();
1637   NS_ASSERTION(
1638       aForFrame->IsFieldSetFrame() || aFrameArea.Size() == aForFrame->GetSize(),
1639       "unexpected size");
1640 
1641   nsRect paddingRect = GetBoxShadowInnerPaddingRect(aForFrame, aFrameArea);
1642 
1643   RectCornerRadii innerRadii;
1644   bool hasBorderRadius = GetShadowInnerRadii(aForFrame, aFrameArea, innerRadii);
1645 
1646   const nscoord oneDevPixel = aPresContext->DevPixelsToAppUnits(1);
1647 
1648   for (const StyleBoxShadow& shadow : Reversed(shadows)) {
1649     if (!shadow.inset) {
1650       continue;
1651     }
1652 
1653     // shadowPaintRect: the area to paint on the temp surface
1654     // shadowClipRect: the area on the temporary surface within shadowPaintRect
1655     //                 that we will NOT paint in
1656     nscoord blurRadius = shadow.base.blur.ToAppUnits();
1657     nsMargin blurMargin =
1658         nsContextBoxBlur::GetBlurRadiusMargin(blurRadius, oneDevPixel);
1659     nsRect shadowPaintRect = paddingRect;
1660     shadowPaintRect.Inflate(blurMargin);
1661 
1662     // Round the spread radius to device pixels (by truncation).
1663     // This mostly matches what we do for borders, except that we don't round
1664     // up values between zero and one device pixels to one device pixel.
1665     // This way of rounding is symmetric around zero, which makes sense for
1666     // the spread radius.
1667     int32_t spreadDistance = shadow.spread.ToAppUnits() / oneDevPixel;
1668     nscoord spreadDistanceAppUnits =
1669         aPresContext->DevPixelsToAppUnits(spreadDistance);
1670 
1671     nsRect shadowClipRect = paddingRect;
1672     shadowClipRect.MoveBy(shadow.base.horizontal.ToAppUnits(),
1673                           shadow.base.vertical.ToAppUnits());
1674     shadowClipRect.Deflate(spreadDistanceAppUnits, spreadDistanceAppUnits);
1675 
1676     Rect shadowClipGfxRect = NSRectToRect(shadowClipRect, oneDevPixel);
1677     shadowClipGfxRect.Round();
1678 
1679     RectCornerRadii clipRectRadii;
1680     if (hasBorderRadius) {
1681       // Calculate the radii the inner clipping rect will have
1682       Float borderSizes[4] = {0, 0, 0, 0};
1683 
1684       // See PaintBoxShadowOuter and bug 514670
1685       if (innerRadii[C_TL].width > 0 || innerRadii[C_BL].width > 0) {
1686         borderSizes[eSideLeft] = spreadDistance;
1687       }
1688 
1689       if (innerRadii[C_TL].height > 0 || innerRadii[C_TR].height > 0) {
1690         borderSizes[eSideTop] = spreadDistance;
1691       }
1692 
1693       if (innerRadii[C_TR].width > 0 || innerRadii[C_BR].width > 0) {
1694         borderSizes[eSideRight] = spreadDistance;
1695       }
1696 
1697       if (innerRadii[C_BL].height > 0 || innerRadii[C_BR].height > 0) {
1698         borderSizes[eSideBottom] = spreadDistance;
1699       }
1700 
1701       nsCSSBorderRenderer::ComputeInnerRadii(innerRadii, borderSizes,
1702                                              &clipRectRadii);
1703     }
1704 
1705     // Set the "skip rect" to the area within the frame that we don't paint in,
1706     // including after blurring.
1707     nsRect skipRect = shadowClipRect;
1708     skipRect.Deflate(blurMargin);
1709     gfxRect skipGfxRect = nsLayoutUtils::RectToGfxRect(skipRect, oneDevPixel);
1710     if (hasBorderRadius) {
1711       skipGfxRect.Deflate(gfxMargin(
1712           std::max(clipRectRadii[C_TL].height, clipRectRadii[C_TR].height), 0,
1713           std::max(clipRectRadii[C_BL].height, clipRectRadii[C_BR].height), 0));
1714     }
1715 
1716     // When there's a blur radius, gfxAlphaBoxBlur leaves the skiprect area
1717     // unchanged. And by construction the gfxSkipRect is not touched by the
1718     // rendered shadow (even after blurring), so those pixels must be completely
1719     // transparent in the shadow, so drawing them changes nothing.
1720     DrawTarget* drawTarget = aRenderingContext.GetDrawTarget();
1721 
1722     // Clip the context to the area of the frame's padding rect, so no part of
1723     // the shadow is painted outside. Also cut out anything beyond where the
1724     // inset shadow will be.
1725     Rect shadowGfxRect = NSRectToRect(paddingRect, oneDevPixel);
1726     shadowGfxRect.Round();
1727 
1728     sRGBColor shadowColor = GetShadowColor(shadow.base, aForFrame, 1.0);
1729     aRenderingContext.Save();
1730 
1731     // This clips the outside border radius.
1732     // clipRectRadii is the border radius inside the inset shadow.
1733     if (hasBorderRadius) {
1734       RefPtr<Path> roundedRect =
1735           MakePathForRoundedRect(*drawTarget, shadowGfxRect, innerRadii);
1736       aRenderingContext.Clip(roundedRect);
1737     } else {
1738       aRenderingContext.Clip(shadowGfxRect);
1739     }
1740 
1741     nsContextBoxBlur insetBoxBlur;
1742     gfxRect destRect =
1743         nsLayoutUtils::RectToGfxRect(shadowPaintRect, oneDevPixel);
1744     Point shadowOffset(shadow.base.horizontal.ToAppUnits() / oneDevPixel,
1745                        shadow.base.vertical.ToAppUnits() / oneDevPixel);
1746 
1747     insetBoxBlur.InsetBoxBlur(
1748         &aRenderingContext, ToRect(destRect), shadowClipGfxRect, shadowColor,
1749         blurRadius, spreadDistanceAppUnits, oneDevPixel, hasBorderRadius,
1750         clipRectRadii, ToRect(skipGfxRect), shadowOffset);
1751     aRenderingContext.Restore();
1752   }
1753 }
1754 
1755 /* static */
ForAllLayers(nsPresContext & aPresCtx,const nsRect & aDirtyRect,const nsRect & aBorderArea,nsIFrame * aFrame,uint32_t aPaintFlags,float aOpacity)1756 nsCSSRendering::PaintBGParams nsCSSRendering::PaintBGParams::ForAllLayers(
1757     nsPresContext& aPresCtx, const nsRect& aDirtyRect,
1758     const nsRect& aBorderArea, nsIFrame* aFrame, uint32_t aPaintFlags,
1759     float aOpacity) {
1760   MOZ_ASSERT(aFrame);
1761 
1762   PaintBGParams result(aPresCtx, aDirtyRect, aBorderArea, aFrame, aPaintFlags,
1763                        -1, CompositionOp::OP_OVER, aOpacity);
1764 
1765   return result;
1766 }
1767 
1768 /* static */
ForSingleLayer(nsPresContext & aPresCtx,const nsRect & aDirtyRect,const nsRect & aBorderArea,nsIFrame * aFrame,uint32_t aPaintFlags,int32_t aLayer,CompositionOp aCompositionOp,float aOpacity)1769 nsCSSRendering::PaintBGParams nsCSSRendering::PaintBGParams::ForSingleLayer(
1770     nsPresContext& aPresCtx, const nsRect& aDirtyRect,
1771     const nsRect& aBorderArea, nsIFrame* aFrame, uint32_t aPaintFlags,
1772     int32_t aLayer, CompositionOp aCompositionOp, float aOpacity) {
1773   MOZ_ASSERT(aFrame && (aLayer != -1));
1774 
1775   PaintBGParams result(aPresCtx, aDirtyRect, aBorderArea, aFrame, aPaintFlags,
1776                        aLayer, aCompositionOp, aOpacity);
1777 
1778   return result;
1779 }
1780 
PaintStyleImageLayer(const PaintBGParams & aParams,gfxContext & aRenderingCtx)1781 ImgDrawResult nsCSSRendering::PaintStyleImageLayer(const PaintBGParams& aParams,
1782                                                    gfxContext& aRenderingCtx) {
1783   AUTO_PROFILER_LABEL("nsCSSRendering::PaintStyleImageLayer", GRAPHICS);
1784 
1785   MOZ_ASSERT(aParams.frame,
1786              "Frame is expected to be provided to PaintStyleImageLayer");
1787 
1788   ComputedStyle* sc;
1789   if (!FindBackground(aParams.frame, &sc)) {
1790     // We don't want to bail out if moz-appearance is set on a root
1791     // node. If it has a parent content node, bail because it's not
1792     // a root, otherwise keep going in order to let the theme stuff
1793     // draw the background. The canvas really should be drawing the
1794     // bg, but there's no way to hook that up via css.
1795     if (!aParams.frame->StyleDisplay()->HasAppearance()) {
1796       return ImgDrawResult::SUCCESS;
1797     }
1798 
1799     nsIContent* content = aParams.frame->GetContent();
1800     if (!content || content->GetParent()) {
1801       return ImgDrawResult::SUCCESS;
1802     }
1803 
1804     sc = aParams.frame->Style();
1805   }
1806 
1807   return PaintStyleImageLayerWithSC(aParams, aRenderingCtx, sc,
1808                                     *aParams.frame->StyleBorder());
1809 }
1810 
CanBuildWebRenderDisplayItemsForStyleImageLayer(LayerManager * aManager,nsPresContext & aPresCtx,nsIFrame * aFrame,const nsStyleBackground * aBackgroundStyle,int32_t aLayer,uint32_t aPaintFlags)1811 bool nsCSSRendering::CanBuildWebRenderDisplayItemsForStyleImageLayer(
1812     LayerManager* aManager, nsPresContext& aPresCtx, nsIFrame* aFrame,
1813     const nsStyleBackground* aBackgroundStyle, int32_t aLayer,
1814     uint32_t aPaintFlags) {
1815   if (!aBackgroundStyle) {
1816     return false;
1817   }
1818 
1819   MOZ_ASSERT(aFrame && aLayer >= 0 &&
1820              (uint32_t)aLayer < aBackgroundStyle->mImage.mLayers.Length());
1821 
1822   // We cannot draw native themed backgrounds
1823   StyleAppearance appearance = aFrame->StyleDisplay()->EffectiveAppearance();
1824   if (appearance != StyleAppearance::None) {
1825     nsITheme* theme = aPresCtx.Theme();
1826     if (theme->ThemeSupportsWidget(&aPresCtx, aFrame, appearance)) {
1827       return false;
1828     }
1829   }
1830 
1831   // We only support painting gradients and image for a single style image
1832   // layer, and we don't support crop-rects.
1833   const auto& styleImage =
1834       aBackgroundStyle->mImage.mLayers[aLayer].mImage.FinalImage();
1835   if (styleImage.IsImageRequestType()) {
1836     if (styleImage.IsRect()) {
1837       return false;
1838     }
1839 
1840     imgRequestProxy* requestProxy = styleImage.GetImageRequest();
1841     if (!requestProxy) {
1842       return false;
1843     }
1844 
1845     uint32_t imageFlags = imgIContainer::FLAG_NONE;
1846     if (aPaintFlags & nsCSSRendering::PAINTBG_SYNC_DECODE_IMAGES) {
1847       imageFlags |= imgIContainer::FLAG_SYNC_DECODE;
1848     }
1849 
1850     nsCOMPtr<imgIContainer> srcImage;
1851     requestProxy->GetImage(getter_AddRefs(srcImage));
1852     if (!srcImage ||
1853         !srcImage->IsImageContainerAvailable(aManager, imageFlags)) {
1854       return false;
1855     }
1856 
1857     return true;
1858   }
1859 
1860   if (styleImage.IsGradient()) {
1861     return true;
1862   }
1863 
1864   return false;
1865 }
1866 
BuildWebRenderDisplayItemsForStyleImageLayer(const PaintBGParams & aParams,mozilla::wr::DisplayListBuilder & aBuilder,mozilla::wr::IpcResourceUpdateQueue & aResources,const mozilla::layers::StackingContextHelper & aSc,mozilla::layers::RenderRootStateManager * aManager,nsDisplayItem * aItem)1867 ImgDrawResult nsCSSRendering::BuildWebRenderDisplayItemsForStyleImageLayer(
1868     const PaintBGParams& aParams, mozilla::wr::DisplayListBuilder& aBuilder,
1869     mozilla::wr::IpcResourceUpdateQueue& aResources,
1870     const mozilla::layers::StackingContextHelper& aSc,
1871     mozilla::layers::RenderRootStateManager* aManager, nsDisplayItem* aItem) {
1872   MOZ_ASSERT(aParams.frame,
1873              "Frame is expected to be provided to "
1874              "BuildWebRenderDisplayItemsForStyleImageLayer");
1875 
1876   ComputedStyle* sc;
1877   if (!FindBackground(aParams.frame, &sc)) {
1878     // We don't want to bail out if moz-appearance is set on a root
1879     // node. If it has a parent content node, bail because it's not
1880     // a root, otherwise keep going in order to let the theme stuff
1881     // draw the background. The canvas really should be drawing the
1882     // bg, but there's no way to hook that up via css.
1883     if (!aParams.frame->StyleDisplay()->HasAppearance()) {
1884       return ImgDrawResult::SUCCESS;
1885     }
1886 
1887     nsIContent* content = aParams.frame->GetContent();
1888     if (!content || content->GetParent()) {
1889       return ImgDrawResult::SUCCESS;
1890     }
1891 
1892     sc = aParams.frame->Style();
1893   }
1894   return BuildWebRenderDisplayItemsForStyleImageLayerWithSC(
1895       aParams, aBuilder, aResources, aSc, aManager, aItem, sc,
1896       *aParams.frame->StyleBorder());
1897 }
1898 
IsOpaqueBorderEdge(const nsStyleBorder & aBorder,mozilla::Side aSide)1899 static bool IsOpaqueBorderEdge(const nsStyleBorder& aBorder,
1900                                mozilla::Side aSide) {
1901   if (aBorder.GetComputedBorder().Side(aSide) == 0) return true;
1902   switch (aBorder.GetBorderStyle(aSide)) {
1903     case StyleBorderStyle::Solid:
1904     case StyleBorderStyle::Groove:
1905     case StyleBorderStyle::Ridge:
1906     case StyleBorderStyle::Inset:
1907     case StyleBorderStyle::Outset:
1908       break;
1909     default:
1910       return false;
1911   }
1912 
1913   // If we're using a border image, assume it's not fully opaque,
1914   // because we may not even have the image loaded at this point, and
1915   // even if we did, checking whether the relevant tile is fully
1916   // opaque would be too much work.
1917   if (!aBorder.mBorderImageSource.IsNone()) {
1918     return false;
1919   }
1920 
1921   StyleColor color = aBorder.BorderColorFor(aSide);
1922   // We don't know the foreground color here, so if it's being used
1923   // we must assume it might be transparent.
1924   return !color.MaybeTransparent();
1925 }
1926 
1927 /**
1928  * Returns true if all border edges are either missing or opaque.
1929  */
IsOpaqueBorder(const nsStyleBorder & aBorder)1930 static bool IsOpaqueBorder(const nsStyleBorder& aBorder) {
1931   for (const auto i : mozilla::AllPhysicalSides()) {
1932     if (!IsOpaqueBorderEdge(aBorder, i)) {
1933       return false;
1934     }
1935   }
1936   return true;
1937 }
1938 
SetupDirtyRects(const nsRect & aBGClipArea,const nsRect & aCallerDirtyRect,nscoord aAppUnitsPerPixel,nsRect * aDirtyRect,gfxRect * aDirtyRectGfx)1939 static inline void SetupDirtyRects(const nsRect& aBGClipArea,
1940                                    const nsRect& aCallerDirtyRect,
1941                                    nscoord aAppUnitsPerPixel,
1942                                    /* OUT: */
1943                                    nsRect* aDirtyRect, gfxRect* aDirtyRectGfx) {
1944   aDirtyRect->IntersectRect(aBGClipArea, aCallerDirtyRect);
1945 
1946   // Compute the Thebes equivalent of the dirtyRect.
1947   *aDirtyRectGfx = nsLayoutUtils::RectToGfxRect(*aDirtyRect, aAppUnitsPerPixel);
1948   NS_WARNING_ASSERTION(aDirtyRect->IsEmpty() || !aDirtyRectGfx->IsEmpty(),
1949                        "converted dirty rect should not be empty");
1950   MOZ_ASSERT(!aDirtyRect->IsEmpty() || aDirtyRectGfx->IsEmpty(),
1951              "second should be empty if first is");
1952 }
1953 
IsSVGStyleGeometryBox(StyleGeometryBox aBox)1954 static bool IsSVGStyleGeometryBox(StyleGeometryBox aBox) {
1955   return (aBox == StyleGeometryBox::FillBox ||
1956           aBox == StyleGeometryBox::StrokeBox ||
1957           aBox == StyleGeometryBox::ViewBox);
1958 }
1959 
IsHTMLStyleGeometryBox(StyleGeometryBox aBox)1960 static bool IsHTMLStyleGeometryBox(StyleGeometryBox aBox) {
1961   return (aBox == StyleGeometryBox::ContentBox ||
1962           aBox == StyleGeometryBox::PaddingBox ||
1963           aBox == StyleGeometryBox::BorderBox ||
1964           aBox == StyleGeometryBox::MarginBox);
1965 }
1966 
ComputeBoxValue(nsIFrame * aForFrame,StyleGeometryBox aBox)1967 static StyleGeometryBox ComputeBoxValue(nsIFrame* aForFrame,
1968                                         StyleGeometryBox aBox) {
1969   if (!aForFrame->HasAnyStateBits(NS_FRAME_SVG_LAYOUT)) {
1970     // For elements with associated CSS layout box, the values fill-box,
1971     // stroke-box and view-box compute to the initial value of mask-clip.
1972     if (IsSVGStyleGeometryBox(aBox)) {
1973       return StyleGeometryBox::BorderBox;
1974     }
1975   } else {
1976     // For SVG elements without associated CSS layout box, the values
1977     // content-box, padding-box, border-box and margin-box compute to fill-box.
1978     if (IsHTMLStyleGeometryBox(aBox)) {
1979       return StyleGeometryBox::FillBox;
1980     }
1981   }
1982 
1983   return aBox;
1984 }
1985 
IsValid() const1986 bool nsCSSRendering::ImageLayerClipState::IsValid() const {
1987   // mDirtyRectInDevPx comes from mDirtyRectInAppUnits. mDirtyRectInAppUnits
1988   // can not be empty if mDirtyRectInDevPx is not.
1989   if (!mDirtyRectInDevPx.IsEmpty() && mDirtyRectInAppUnits.IsEmpty()) {
1990     return false;
1991   }
1992 
1993   if (mHasRoundedCorners == mClippedRadii.IsEmpty()) {
1994     return false;
1995   }
1996 
1997   return true;
1998 }
1999 
2000 /* static */
GetImageLayerClip(const nsStyleImageLayers::Layer & aLayer,nsIFrame * aForFrame,const nsStyleBorder & aBorder,const nsRect & aBorderArea,const nsRect & aCallerDirtyRect,bool aWillPaintBorder,nscoord aAppUnitsPerPixel,ImageLayerClipState * aClipState)2001 void nsCSSRendering::GetImageLayerClip(
2002     const nsStyleImageLayers::Layer& aLayer, nsIFrame* aForFrame,
2003     const nsStyleBorder& aBorder, const nsRect& aBorderArea,
2004     const nsRect& aCallerDirtyRect, bool aWillPaintBorder,
2005     nscoord aAppUnitsPerPixel,
2006     /* out */ ImageLayerClipState* aClipState) {
2007   StyleGeometryBox layerClip = ComputeBoxValue(aForFrame, aLayer.mClip);
2008   if (IsSVGStyleGeometryBox(layerClip)) {
2009     MOZ_ASSERT(aForFrame->IsFrameOfType(nsIFrame::eSVG) &&
2010                !aForFrame->IsSVGOuterSVGFrame());
2011 
2012     // The coordinate space of clipArea is svg user space.
2013     nsRect clipArea = nsLayoutUtils::ComputeGeometryBox(aForFrame, layerClip);
2014 
2015     nsRect strokeBox = (layerClip == StyleGeometryBox::StrokeBox)
2016                            ? clipArea
2017                            : nsLayoutUtils::ComputeGeometryBox(
2018                                  aForFrame, StyleGeometryBox::StrokeBox);
2019     nsRect clipAreaRelativeToStrokeBox = clipArea - strokeBox.TopLeft();
2020 
2021     // aBorderArea is the stroke-box area in a coordinate space defined by
2022     // the caller. This coordinate space can be svg user space of aForFrame,
2023     // the space of aForFrame's reference-frame, or anything else.
2024     //
2025     // Which coordinate space chosen for aBorderArea is not matter. What
2026     // matter is to ensure returning aClipState->mBGClipArea in the consistent
2027     // coordiante space with aBorderArea. So we evaluate the position of clip
2028     // area base on the position of aBorderArea here.
2029     aClipState->mBGClipArea =
2030         clipAreaRelativeToStrokeBox + aBorderArea.TopLeft();
2031 
2032     SetupDirtyRects(aClipState->mBGClipArea, aCallerDirtyRect,
2033                     aAppUnitsPerPixel, &aClipState->mDirtyRectInAppUnits,
2034                     &aClipState->mDirtyRectInDevPx);
2035     MOZ_ASSERT(aClipState->IsValid());
2036     return;
2037   }
2038 
2039   if (layerClip == StyleGeometryBox::NoClip) {
2040     aClipState->mBGClipArea = aCallerDirtyRect;
2041 
2042     SetupDirtyRects(aClipState->mBGClipArea, aCallerDirtyRect,
2043                     aAppUnitsPerPixel, &aClipState->mDirtyRectInAppUnits,
2044                     &aClipState->mDirtyRectInDevPx);
2045     MOZ_ASSERT(aClipState->IsValid());
2046     return;
2047   }
2048 
2049   MOZ_ASSERT(!aForFrame->IsFrameOfType(nsIFrame::eSVG) ||
2050              aForFrame->IsSVGOuterSVGFrame());
2051 
2052   // Compute the outermost boundary of the area that might be painted.
2053   // Same coordinate space as aBorderArea.
2054   Sides skipSides = aForFrame->GetSkipSides();
2055   nsRect clipBorderArea =
2056       BoxDecorationRectForBorder(aForFrame, aBorderArea, skipSides, &aBorder);
2057 
2058   bool haveRoundedCorners = false;
2059   LayoutFrameType fType = aForFrame->Type();
2060   if (fType != LayoutFrameType::TableColGroup &&
2061       fType != LayoutFrameType::TableCol &&
2062       fType != LayoutFrameType::TableRow &&
2063       fType != LayoutFrameType::TableRowGroup) {
2064     haveRoundedCorners = GetRadii(aForFrame, aBorder, aBorderArea,
2065                                   clipBorderArea, aClipState->mRadii);
2066   }
2067   bool isSolidBorder = aWillPaintBorder && IsOpaqueBorder(aBorder);
2068   if (isSolidBorder && layerClip == StyleGeometryBox::BorderBox) {
2069     // If we have rounded corners, we need to inflate the background
2070     // drawing area a bit to avoid seams between the border and
2071     // background.
2072     layerClip = haveRoundedCorners ? StyleGeometryBox::MozAlmostPadding
2073                                    : StyleGeometryBox::PaddingBox;
2074   }
2075 
2076   aClipState->mBGClipArea = clipBorderArea;
2077 
2078   if (aForFrame->IsScrollFrame() &&
2079       StyleImageLayerAttachment::Local == aLayer.mAttachment) {
2080     // As of this writing, this is still in discussion in the CSS Working Group
2081     // http://lists.w3.org/Archives/Public/www-style/2013Jul/0250.html
2082 
2083     // The rectangle for 'background-clip' scrolls with the content,
2084     // but the background is also clipped at a non-scrolling 'padding-box'
2085     // like the content. (See below.)
2086     // Therefore, only 'content-box' makes a difference here.
2087     if (layerClip == StyleGeometryBox::ContentBox) {
2088       nsIScrollableFrame* scrollableFrame = do_QueryFrame(aForFrame);
2089       // Clip at a rectangle attached to the scrolled content.
2090       aClipState->mHasAdditionalBGClipArea = true;
2091       aClipState->mAdditionalBGClipArea =
2092           nsRect(aClipState->mBGClipArea.TopLeft() +
2093                      scrollableFrame->GetScrolledFrame()->GetPosition()
2094                      // For the dir=rtl case:
2095                      + scrollableFrame->GetScrollRange().TopLeft(),
2096                  scrollableFrame->GetScrolledRect().Size());
2097       nsMargin padding = aForFrame->GetUsedPadding();
2098       // padding-bottom is ignored on scrollable frames:
2099       // https://bugzilla.mozilla.org/show_bug.cgi?id=748518
2100       padding.bottom = 0;
2101       padding.ApplySkipSides(skipSides);
2102       aClipState->mAdditionalBGClipArea.Deflate(padding);
2103     }
2104 
2105     // Also clip at a non-scrolling, rounded-corner 'padding-box',
2106     // same as the scrolled content because of the 'overflow' property.
2107     layerClip = StyleGeometryBox::PaddingBox;
2108   }
2109 
2110   // See the comment of StyleGeometryBox::Margin.
2111   // Hitting this assertion means we decide to turn on margin-box support for
2112   // positioned mask from CSS parser and style system. In this case, you
2113   // should *inflate* mBGClipArea by the margin returning from
2114   // aForFrame->GetUsedMargin() in the code chunk bellow.
2115   MOZ_ASSERT(layerClip != StyleGeometryBox::MarginBox,
2116              "StyleGeometryBox::MarginBox rendering is not supported yet.\n");
2117 
2118   if (layerClip != StyleGeometryBox::BorderBox &&
2119       layerClip != StyleGeometryBox::Text) {
2120     nsMargin border = aForFrame->GetUsedBorder();
2121     if (layerClip == StyleGeometryBox::MozAlmostPadding) {
2122       // Reduce |border| by 1px (device pixels) on all sides, if
2123       // possible, so that we don't get antialiasing seams between the
2124       // {background|mask} and border.
2125       border.top = std::max(0, border.top - aAppUnitsPerPixel);
2126       border.right = std::max(0, border.right - aAppUnitsPerPixel);
2127       border.bottom = std::max(0, border.bottom - aAppUnitsPerPixel);
2128       border.left = std::max(0, border.left - aAppUnitsPerPixel);
2129     } else if (layerClip != StyleGeometryBox::PaddingBox) {
2130       NS_ASSERTION(layerClip == StyleGeometryBox::ContentBox,
2131                    "unexpected background-clip");
2132       border += aForFrame->GetUsedPadding();
2133     }
2134     border.ApplySkipSides(skipSides);
2135     aClipState->mBGClipArea.Deflate(border);
2136 
2137     if (haveRoundedCorners) {
2138       nsIFrame::InsetBorderRadii(aClipState->mRadii, border);
2139     }
2140   }
2141 
2142   if (haveRoundedCorners) {
2143     auto d2a = aForFrame->PresContext()->AppUnitsPerDevPixel();
2144     nsCSSRendering::ComputePixelRadii(aClipState->mRadii, d2a,
2145                                       &aClipState->mClippedRadii);
2146     aClipState->mHasRoundedCorners = !aClipState->mClippedRadii.IsEmpty();
2147   }
2148 
2149   if (!haveRoundedCorners && aClipState->mHasAdditionalBGClipArea) {
2150     // Do the intersection here to account for the fast path(?) below.
2151     aClipState->mBGClipArea =
2152         aClipState->mBGClipArea.Intersect(aClipState->mAdditionalBGClipArea);
2153     aClipState->mHasAdditionalBGClipArea = false;
2154   }
2155 
2156   SetupDirtyRects(aClipState->mBGClipArea, aCallerDirtyRect, aAppUnitsPerPixel,
2157                   &aClipState->mDirtyRectInAppUnits,
2158                   &aClipState->mDirtyRectInDevPx);
2159 
2160   MOZ_ASSERT(aClipState->IsValid());
2161 }
2162 
SetupImageLayerClip(nsCSSRendering::ImageLayerClipState & aClipState,gfxContext * aCtx,nscoord aAppUnitsPerPixel,gfxContextAutoSaveRestore * aAutoSR)2163 static void SetupImageLayerClip(nsCSSRendering::ImageLayerClipState& aClipState,
2164                                 gfxContext* aCtx, nscoord aAppUnitsPerPixel,
2165                                 gfxContextAutoSaveRestore* aAutoSR) {
2166   if (aClipState.mDirtyRectInDevPx.IsEmpty()) {
2167     // Our caller won't draw anything under this condition, so no need
2168     // to set more up.
2169     return;
2170   }
2171 
2172   if (aClipState.mCustomClip) {
2173     // We don't support custom clips and rounded corners, arguably a bug, but
2174     // table painting seems to depend on it.
2175     return;
2176   }
2177 
2178   // If we have rounded corners, clip all subsequent drawing to the
2179   // rounded rectangle defined by bgArea and bgRadii (we don't know
2180   // whether the rounded corners intrude on the dirtyRect or not).
2181   // Do not do this if we have a caller-provided clip rect --
2182   // as above with bgArea, arguably a bug, but table painting seems
2183   // to depend on it.
2184 
2185   if (aClipState.mHasAdditionalBGClipArea) {
2186     gfxRect bgAreaGfx = nsLayoutUtils::RectToGfxRect(
2187         aClipState.mAdditionalBGClipArea, aAppUnitsPerPixel);
2188     bgAreaGfx.Round();
2189     gfxUtils::ConditionRect(bgAreaGfx);
2190 
2191     aAutoSR->EnsureSaved(aCtx);
2192     aCtx->SnappedClip(bgAreaGfx);
2193   }
2194 
2195   if (aClipState.mHasRoundedCorners) {
2196     Rect bgAreaGfx = NSRectToRect(aClipState.mBGClipArea, aAppUnitsPerPixel);
2197     bgAreaGfx.Round();
2198 
2199     if (bgAreaGfx.IsEmpty()) {
2200       // I think it's become possible to hit this since
2201       // https://hg.mozilla.org/mozilla-central/rev/50e934e4979b landed.
2202       NS_WARNING("converted background area should not be empty");
2203       // Make our caller not do anything.
2204       aClipState.mDirtyRectInDevPx.SizeTo(gfxSize(0.0, 0.0));
2205       return;
2206     }
2207 
2208     aAutoSR->EnsureSaved(aCtx);
2209 
2210     RefPtr<Path> roundedRect = MakePathForRoundedRect(
2211         *aCtx->GetDrawTarget(), bgAreaGfx, aClipState.mClippedRadii);
2212     aCtx->Clip(roundedRect);
2213   }
2214 }
2215 
DrawBackgroundColor(nsCSSRendering::ImageLayerClipState & aClipState,gfxContext * aCtx,nscoord aAppUnitsPerPixel)2216 static void DrawBackgroundColor(nsCSSRendering::ImageLayerClipState& aClipState,
2217                                 gfxContext* aCtx, nscoord aAppUnitsPerPixel) {
2218   if (aClipState.mDirtyRectInDevPx.IsEmpty()) {
2219     // Our caller won't draw anything under this condition, so no need
2220     // to set more up.
2221     return;
2222   }
2223 
2224   DrawTarget* drawTarget = aCtx->GetDrawTarget();
2225 
2226   // We don't support custom clips and rounded corners, arguably a bug, but
2227   // table painting seems to depend on it.
2228   if (!aClipState.mHasRoundedCorners || aClipState.mCustomClip) {
2229     aCtx->NewPath();
2230     aCtx->SnappedRectangle(aClipState.mDirtyRectInDevPx);
2231     aCtx->Fill();
2232     return;
2233   }
2234 
2235   Rect bgAreaGfx = NSRectToRect(aClipState.mBGClipArea, aAppUnitsPerPixel);
2236   bgAreaGfx.Round();
2237 
2238   if (bgAreaGfx.IsEmpty()) {
2239     // I think it's become possible to hit this since
2240     // https://hg.mozilla.org/mozilla-central/rev/50e934e4979b landed.
2241     NS_WARNING("converted background area should not be empty");
2242     // Make our caller not do anything.
2243     aClipState.mDirtyRectInDevPx.SizeTo(gfxSize(0.0, 0.0));
2244     return;
2245   }
2246 
2247   aCtx->Save();
2248   gfxRect dirty = ThebesRect(bgAreaGfx).Intersect(aClipState.mDirtyRectInDevPx);
2249 
2250   aCtx->SnappedClip(dirty);
2251 
2252   if (aClipState.mHasAdditionalBGClipArea) {
2253     gfxRect bgAdditionalAreaGfx = nsLayoutUtils::RectToGfxRect(
2254         aClipState.mAdditionalBGClipArea, aAppUnitsPerPixel);
2255     bgAdditionalAreaGfx.Round();
2256     gfxUtils::ConditionRect(bgAdditionalAreaGfx);
2257     aCtx->SnappedClip(bgAdditionalAreaGfx);
2258   }
2259 
2260   RefPtr<Path> roundedRect =
2261       MakePathForRoundedRect(*drawTarget, bgAreaGfx, aClipState.mClippedRadii);
2262   aCtx->SetPath(roundedRect);
2263   aCtx->Fill();
2264   aCtx->Restore();
2265 }
2266 
2267 enum class ScrollbarColorKind {
2268   Thumb,
2269   Track,
2270 };
2271 
CalcScrollbarColor(nsIFrame * aFrame,ScrollbarColorKind aKind)2272 static Maybe<nscolor> CalcScrollbarColor(nsIFrame* aFrame,
2273                                          ScrollbarColorKind aKind) {
2274   ComputedStyle* scrollbarStyle = nsLayoutUtils::StyleForScrollbar(aFrame);
2275   const auto& colors = scrollbarStyle->StyleUI()->mScrollbarColor;
2276   if (colors.IsAuto()) {
2277     return Nothing();
2278   }
2279   const auto& color = aKind == ScrollbarColorKind::Thumb
2280                           ? colors.AsColors().thumb
2281                           : colors.AsColors().track;
2282   return Some(color.CalcColor(*scrollbarStyle));
2283 }
2284 
GetBackgroundColor(nsIFrame * aFrame,ComputedStyle * aStyle)2285 static nscolor GetBackgroundColor(nsIFrame* aFrame, ComputedStyle* aStyle) {
2286   switch (aStyle->StyleDisplay()->EffectiveAppearance()) {
2287     case StyleAppearance::ScrollbarthumbVertical:
2288     case StyleAppearance::ScrollbarthumbHorizontal: {
2289       if (Maybe<nscolor> overrideColor =
2290               CalcScrollbarColor(aFrame, ScrollbarColorKind::Thumb)) {
2291         return *overrideColor;
2292       }
2293       break;
2294     }
2295     case StyleAppearance::ScrollbarVertical:
2296     case StyleAppearance::ScrollbarHorizontal:
2297     case StyleAppearance::Scrollcorner: {
2298       if (Maybe<nscolor> overrideColor =
2299               CalcScrollbarColor(aFrame, ScrollbarColorKind::Track)) {
2300         return *overrideColor;
2301       }
2302       break;
2303     }
2304     default:
2305       break;
2306   }
2307   return aStyle->GetVisitedDependentColor(&nsStyleBackground::mBackgroundColor);
2308 }
2309 
DetermineBackgroundColor(nsPresContext * aPresContext,ComputedStyle * aStyle,nsIFrame * aFrame,bool & aDrawBackgroundImage,bool & aDrawBackgroundColor)2310 nscolor nsCSSRendering::DetermineBackgroundColor(nsPresContext* aPresContext,
2311                                                  ComputedStyle* aStyle,
2312                                                  nsIFrame* aFrame,
2313                                                  bool& aDrawBackgroundImage,
2314                                                  bool& aDrawBackgroundColor) {
2315   auto shouldPaint = aFrame->ComputeShouldPaintBackground();
2316   aDrawBackgroundImage = shouldPaint.mImage;
2317   aDrawBackgroundColor = shouldPaint.mColor;
2318 
2319   const nsStyleBackground* bg = aStyle->StyleBackground();
2320   nscolor bgColor;
2321   if (aDrawBackgroundColor) {
2322     bgColor = GetBackgroundColor(aFrame, aStyle);
2323     if (NS_GET_A(bgColor) == 0) {
2324       aDrawBackgroundColor = false;
2325     }
2326   } else {
2327     // If GetBackgroundColorDraw() is false, we are still expected to
2328     // draw color in the background of any frame that's not completely
2329     // transparent, but we are expected to use white instead of whatever
2330     // color was specified.
2331     bgColor = NS_RGB(255, 255, 255);
2332     if (aDrawBackgroundImage || !bg->IsTransparent(aStyle)) {
2333       aDrawBackgroundColor = true;
2334     } else {
2335       bgColor = NS_RGBA(0, 0, 0, 0);
2336     }
2337   }
2338 
2339   // We can skip painting the background color if a background image is opaque.
2340   nsStyleImageLayers::Repeat repeat = bg->BottomLayer().mRepeat;
2341   bool xFullRepeat = repeat.mXRepeat == StyleImageLayerRepeat::Repeat ||
2342                      repeat.mXRepeat == StyleImageLayerRepeat::Round;
2343   bool yFullRepeat = repeat.mYRepeat == StyleImageLayerRepeat::Repeat ||
2344                      repeat.mYRepeat == StyleImageLayerRepeat::Round;
2345   if (aDrawBackgroundColor && xFullRepeat && yFullRepeat &&
2346       bg->BottomLayer().mImage.IsOpaque() &&
2347       bg->BottomLayer().mBlendMode == StyleBlend::Normal) {
2348     aDrawBackgroundColor = false;
2349   }
2350 
2351   return bgColor;
2352 }
2353 
DetermineCompositionOp(const nsCSSRendering::PaintBGParams & aParams,const nsStyleImageLayers & aLayers,uint32_t aLayerIndex)2354 static CompositionOp DetermineCompositionOp(
2355     const nsCSSRendering::PaintBGParams& aParams,
2356     const nsStyleImageLayers& aLayers, uint32_t aLayerIndex) {
2357   if (aParams.layer >= 0) {
2358     // When drawing a single layer, use the specified composition op.
2359     return aParams.compositionOp;
2360   }
2361 
2362   const nsStyleImageLayers::Layer& layer = aLayers.mLayers[aLayerIndex];
2363   // When drawing all layers, get the compositon op from each image layer.
2364   if (aParams.paintFlags & nsCSSRendering::PAINTBG_MASK_IMAGE) {
2365     // Always using OP_OVER mode while drawing the bottom mask layer.
2366     if (aLayerIndex == (aLayers.mImageCount - 1)) {
2367       return CompositionOp::OP_OVER;
2368     }
2369 
2370     return nsCSSRendering::GetGFXCompositeMode(layer.mComposite);
2371   }
2372 
2373   return nsCSSRendering::GetGFXBlendMode(layer.mBlendMode);
2374 }
2375 
PaintStyleImageLayerWithSC(const PaintBGParams & aParams,gfxContext & aRenderingCtx,ComputedStyle * aBackgroundSC,const nsStyleBorder & aBorder)2376 ImgDrawResult nsCSSRendering::PaintStyleImageLayerWithSC(
2377     const PaintBGParams& aParams, gfxContext& aRenderingCtx,
2378     ComputedStyle* aBackgroundSC, const nsStyleBorder& aBorder) {
2379   MOZ_ASSERT(aParams.frame,
2380              "Frame is expected to be provided to PaintStyleImageLayerWithSC");
2381 
2382   // If we're drawing all layers, aCompositonOp is ignored, so make sure that
2383   // it was left at its default value.
2384   MOZ_ASSERT(aParams.layer != -1 ||
2385              aParams.compositionOp == CompositionOp::OP_OVER);
2386 
2387   // Check to see if we have an appearance defined.  If so, we let the theme
2388   // renderer draw the background and bail out.
2389   // XXXzw this ignores aParams.bgClipRect.
2390   StyleAppearance appearance =
2391       aParams.frame->StyleDisplay()->EffectiveAppearance();
2392   if (appearance != StyleAppearance::None) {
2393     nsITheme* theme = aParams.presCtx.Theme();
2394     if (theme->ThemeSupportsWidget(&aParams.presCtx, aParams.frame,
2395                                    appearance)) {
2396       nsRect drawing(aParams.borderArea);
2397       theme->GetWidgetOverflow(aParams.presCtx.DeviceContext(), aParams.frame,
2398                                appearance, &drawing);
2399       drawing.IntersectRect(drawing, aParams.dirtyRect);
2400       theme->DrawWidgetBackground(&aRenderingCtx, aParams.frame, appearance,
2401                                   aParams.borderArea, drawing);
2402       return ImgDrawResult::SUCCESS;
2403     }
2404   }
2405 
2406   // For canvas frames (in the CSS sense) we draw the background color using
2407   // a solid color item that gets added in nsLayoutUtils::PaintFrame,
2408   // or nsSubDocumentFrame::BuildDisplayList (bug 488242). (The solid
2409   // color may be moved into nsDisplayCanvasBackground by
2410   // PresShell::AddCanvasBackgroundColorItem(), and painted by
2411   // nsDisplayCanvasBackground directly.) Either way we don't need to
2412   // paint the background color here.
2413   bool isCanvasFrame = IsCanvasFrame(aParams.frame);
2414   const bool paintMask = aParams.paintFlags & PAINTBG_MASK_IMAGE;
2415 
2416   // Determine whether we are drawing background images and/or
2417   // background colors.
2418   bool drawBackgroundImage = true;
2419   bool drawBackgroundColor = !paintMask;
2420   nscolor bgColor = NS_RGBA(0, 0, 0, 0);
2421   if (!paintMask) {
2422     bgColor =
2423         DetermineBackgroundColor(&aParams.presCtx, aBackgroundSC, aParams.frame,
2424                                  drawBackgroundImage, drawBackgroundColor);
2425   }
2426 
2427   // Masks shouldn't be suppressed for print.
2428   MOZ_ASSERT_IF(paintMask, drawBackgroundImage);
2429 
2430   const nsStyleImageLayers& layers =
2431       paintMask ? aBackgroundSC->StyleSVGReset()->mMask
2432                 : aBackgroundSC->StyleBackground()->mImage;
2433   // If we're drawing a specific layer, we don't want to draw the
2434   // background color.
2435   if (drawBackgroundColor && aParams.layer >= 0) {
2436     drawBackgroundColor = false;
2437   }
2438 
2439   // At this point, drawBackgroundImage and drawBackgroundColor are
2440   // true if and only if we are actually supposed to paint an image or
2441   // color into aDirtyRect, respectively.
2442   if (!drawBackgroundImage && !drawBackgroundColor) {
2443     return ImgDrawResult::SUCCESS;
2444   }
2445 
2446   // The 'bgClipArea' (used only by the image tiling logic, far below)
2447   // is the caller-provided aParams.bgClipRect if any, or else the area
2448   // determined by the value of 'background-clip' in
2449   // SetupCurrentBackgroundClip.  (Arguably it should be the
2450   // intersection, but that breaks the table painter -- in particular,
2451   // taking the intersection breaks reftests/bugs/403249-1[ab].)
2452   nscoord appUnitsPerPixel = aParams.presCtx.AppUnitsPerDevPixel();
2453   ImageLayerClipState clipState;
2454   if (aParams.bgClipRect) {
2455     clipState.mBGClipArea = *aParams.bgClipRect;
2456     clipState.mCustomClip = true;
2457     clipState.mHasRoundedCorners = false;
2458     SetupDirtyRects(clipState.mBGClipArea, aParams.dirtyRect, appUnitsPerPixel,
2459                     &clipState.mDirtyRectInAppUnits,
2460                     &clipState.mDirtyRectInDevPx);
2461   } else {
2462     GetImageLayerClip(layers.BottomLayer(), aParams.frame, aBorder,
2463                       aParams.borderArea, aParams.dirtyRect,
2464                       (aParams.paintFlags & PAINTBG_WILL_PAINT_BORDER),
2465                       appUnitsPerPixel, &clipState);
2466   }
2467 
2468   // If we might be using a background color, go ahead and set it now.
2469   if (drawBackgroundColor && !isCanvasFrame) {
2470     aRenderingCtx.SetColor(sRGBColor::FromABGR(bgColor));
2471   }
2472 
2473   // If there is no background image, draw a color.  (If there is
2474   // neither a background image nor a color, we wouldn't have gotten
2475   // this far.)
2476   if (!drawBackgroundImage) {
2477     if (!isCanvasFrame) {
2478       DrawBackgroundColor(clipState, &aRenderingCtx, appUnitsPerPixel);
2479     }
2480     return ImgDrawResult::SUCCESS;
2481   }
2482 
2483   if (layers.mImageCount < 1) {
2484     // Return if there are no background layers, all work from this point
2485     // onwards happens iteratively on these.
2486     return ImgDrawResult::SUCCESS;
2487   }
2488 
2489   MOZ_ASSERT((aParams.layer < 0) ||
2490              (layers.mImageCount > uint32_t(aParams.layer)));
2491 
2492   // The background color is rendered over the entire dirty area,
2493   // even if the image isn't.
2494   if (drawBackgroundColor && !isCanvasFrame) {
2495     DrawBackgroundColor(clipState, &aRenderingCtx, appUnitsPerPixel);
2496   }
2497 
2498   // Compute the outermost boundary of the area that might be painted.
2499   // Same coordinate space as aParams.borderArea & aParams.bgClipRect.
2500   Sides skipSides = aParams.frame->GetSkipSides();
2501   nsRect paintBorderArea = BoxDecorationRectForBackground(
2502       aParams.frame, aParams.borderArea, skipSides, &aBorder);
2503   nsRect clipBorderArea = BoxDecorationRectForBorder(
2504       aParams.frame, aParams.borderArea, skipSides, &aBorder);
2505 
2506   ImgDrawResult result = ImgDrawResult::SUCCESS;
2507   StyleGeometryBox currentBackgroundClip = StyleGeometryBox::BorderBox;
2508   const bool drawAllLayers = (aParams.layer < 0);
2509   uint32_t count = drawAllLayers
2510                        ? layers.mImageCount  // iterate all image layers.
2511                        : layers.mImageCount -
2512                              aParams.layer;  // iterate from the bottom layer to
2513                                              // the 'aParams.layer-th' layer.
2514   NS_FOR_VISIBLE_IMAGE_LAYERS_BACK_TO_FRONT_WITH_RANGE(
2515       i, layers, layers.mImageCount - 1, count) {
2516     // NOTE: no Save() yet, we do that later by calling autoSR.EnsureSaved(ctx)
2517     // in the cases we need it.
2518     gfxContextAutoSaveRestore autoSR;
2519     const nsStyleImageLayers::Layer& layer = layers.mLayers[i];
2520 
2521     ImageLayerClipState currentLayerClipState = clipState;
2522     if (!aParams.bgClipRect) {
2523       bool isBottomLayer = (i == layers.mImageCount - 1);
2524       if (currentBackgroundClip != layer.mClip || isBottomLayer) {
2525         currentBackgroundClip = layer.mClip;
2526         if (!isBottomLayer) {
2527           currentLayerClipState = {};
2528           // For the bottom layer, we already called GetImageLayerClip above
2529           // and it stored its results in clipState.
2530           GetImageLayerClip(layer, aParams.frame, aBorder, aParams.borderArea,
2531                             aParams.dirtyRect,
2532                             (aParams.paintFlags & PAINTBG_WILL_PAINT_BORDER),
2533                             appUnitsPerPixel, &currentLayerClipState);
2534         }
2535         SetupImageLayerClip(currentLayerClipState, &aRenderingCtx,
2536                             appUnitsPerPixel, &autoSR);
2537         if (!clipBorderArea.IsEqualEdges(aParams.borderArea)) {
2538           // We're drawing the background for the joined continuation boxes
2539           // so we need to clip that to the slice that we want for this
2540           // frame.
2541           gfxRect clip = nsLayoutUtils::RectToGfxRect(aParams.borderArea,
2542                                                       appUnitsPerPixel);
2543           autoSR.EnsureSaved(&aRenderingCtx);
2544           aRenderingCtx.SnappedClip(clip);
2545         }
2546       }
2547     }
2548 
2549     // Skip the following layer preparing and painting code if the current
2550     // layer is not selected for drawing.
2551     if (aParams.layer >= 0 && i != (uint32_t)aParams.layer) {
2552       continue;
2553     }
2554     nsBackgroundLayerState state = PrepareImageLayer(
2555         &aParams.presCtx, aParams.frame, aParams.paintFlags, paintBorderArea,
2556         currentLayerClipState.mBGClipArea, layer, nullptr);
2557     result &= state.mImageRenderer.PrepareResult();
2558 
2559     // Skip the layer painting code if we found the dirty region is empty.
2560     if (currentLayerClipState.mDirtyRectInDevPx.IsEmpty()) {
2561       continue;
2562     }
2563 
2564     if (!state.mFillArea.IsEmpty()) {
2565       CompositionOp co = DetermineCompositionOp(aParams, layers, i);
2566       if (co != CompositionOp::OP_OVER) {
2567         NS_ASSERTION(aRenderingCtx.CurrentOp() == CompositionOp::OP_OVER,
2568                      "It is assumed the initial op is OP_OVER, when it is "
2569                      "restored later");
2570         aRenderingCtx.SetOp(co);
2571       }
2572 
2573       result &= state.mImageRenderer.DrawLayer(
2574           &aParams.presCtx, aRenderingCtx, state.mDestArea, state.mFillArea,
2575           state.mAnchor + paintBorderArea.TopLeft(),
2576           currentLayerClipState.mDirtyRectInAppUnits, state.mRepeatSize,
2577           aParams.opacity);
2578 
2579       if (co != CompositionOp::OP_OVER) {
2580         aRenderingCtx.SetOp(CompositionOp::OP_OVER);
2581       }
2582     }
2583   }
2584 
2585   return result;
2586 }
2587 
2588 ImgDrawResult
BuildWebRenderDisplayItemsForStyleImageLayerWithSC(const PaintBGParams & aParams,mozilla::wr::DisplayListBuilder & aBuilder,mozilla::wr::IpcResourceUpdateQueue & aResources,const mozilla::layers::StackingContextHelper & aSc,mozilla::layers::RenderRootStateManager * aManager,nsDisplayItem * aItem,ComputedStyle * aBackgroundSC,const nsStyleBorder & aBorder)2589 nsCSSRendering::BuildWebRenderDisplayItemsForStyleImageLayerWithSC(
2590     const PaintBGParams& aParams, mozilla::wr::DisplayListBuilder& aBuilder,
2591     mozilla::wr::IpcResourceUpdateQueue& aResources,
2592     const mozilla::layers::StackingContextHelper& aSc,
2593     mozilla::layers::RenderRootStateManager* aManager, nsDisplayItem* aItem,
2594     ComputedStyle* aBackgroundSC, const nsStyleBorder& aBorder) {
2595   MOZ_ASSERT(!(aParams.paintFlags & PAINTBG_MASK_IMAGE));
2596 
2597   nscoord appUnitsPerPixel = aParams.presCtx.AppUnitsPerDevPixel();
2598   ImageLayerClipState clipState;
2599 
2600   clipState.mBGClipArea = *aParams.bgClipRect;
2601   clipState.mCustomClip = true;
2602   clipState.mHasRoundedCorners = false;
2603   SetupDirtyRects(clipState.mBGClipArea, aParams.dirtyRect, appUnitsPerPixel,
2604                   &clipState.mDirtyRectInAppUnits,
2605                   &clipState.mDirtyRectInDevPx);
2606 
2607   // Compute the outermost boundary of the area that might be painted.
2608   // Same coordinate space as aParams.borderArea & aParams.bgClipRect.
2609   Sides skipSides = aParams.frame->GetSkipSides();
2610   nsRect paintBorderArea = BoxDecorationRectForBackground(
2611       aParams.frame, aParams.borderArea, skipSides, &aBorder);
2612 
2613   const nsStyleImageLayers& layers = aBackgroundSC->StyleBackground()->mImage;
2614   const nsStyleImageLayers::Layer& layer = layers.mLayers[aParams.layer];
2615 
2616   // Skip the following layer painting code if we found the dirty region is
2617   // empty or the current layer is not selected for drawing.
2618   if (clipState.mDirtyRectInDevPx.IsEmpty()) {
2619     return ImgDrawResult::SUCCESS;
2620   }
2621 
2622   ImgDrawResult result = ImgDrawResult::SUCCESS;
2623   nsBackgroundLayerState state =
2624       PrepareImageLayer(&aParams.presCtx, aParams.frame, aParams.paintFlags,
2625                         paintBorderArea, clipState.mBGClipArea, layer, nullptr);
2626   result &= state.mImageRenderer.PrepareResult();
2627 
2628   if (!state.mFillArea.IsEmpty()) {
2629     return state.mImageRenderer.BuildWebRenderDisplayItemsForLayer(
2630         &aParams.presCtx, aBuilder, aResources, aSc, aManager, aItem,
2631         state.mDestArea, state.mFillArea,
2632         state.mAnchor + paintBorderArea.TopLeft(),
2633         clipState.mDirtyRectInAppUnits, state.mRepeatSize, aParams.opacity);
2634   }
2635 
2636   return result;
2637 }
2638 
ComputeImageLayerPositioningArea(nsPresContext * aPresContext,nsIFrame * aForFrame,const nsRect & aBorderArea,const nsStyleImageLayers::Layer & aLayer,nsIFrame ** aAttachedToFrame,bool * aOutIsTransformedFixed)2639 nsRect nsCSSRendering::ComputeImageLayerPositioningArea(
2640     nsPresContext* aPresContext, nsIFrame* aForFrame, const nsRect& aBorderArea,
2641     const nsStyleImageLayers::Layer& aLayer, nsIFrame** aAttachedToFrame,
2642     bool* aOutIsTransformedFixed) {
2643   // Compute {background|mask} origin area relative to aBorderArea now as we
2644   // may need  it to compute the effective image size for a CSS gradient.
2645   nsRect positionArea;
2646 
2647   StyleGeometryBox layerOrigin = ComputeBoxValue(aForFrame, aLayer.mOrigin);
2648 
2649   if (IsSVGStyleGeometryBox(layerOrigin)) {
2650     MOZ_ASSERT(aForFrame->IsFrameOfType(nsIFrame::eSVG) &&
2651                !aForFrame->IsSVGOuterSVGFrame());
2652     *aAttachedToFrame = aForFrame;
2653 
2654     positionArea = nsLayoutUtils::ComputeGeometryBox(aForFrame, layerOrigin);
2655 
2656     nsPoint toStrokeBoxOffset = nsPoint(0, 0);
2657     if (layerOrigin != StyleGeometryBox::StrokeBox) {
2658       nsRect strokeBox = nsLayoutUtils::ComputeGeometryBox(
2659           aForFrame, StyleGeometryBox::StrokeBox);
2660       toStrokeBoxOffset = positionArea.TopLeft() - strokeBox.TopLeft();
2661     }
2662 
2663     // For SVG frames, the return value is relative to the stroke box
2664     return nsRect(toStrokeBoxOffset, positionArea.Size());
2665   }
2666 
2667   MOZ_ASSERT(!aForFrame->IsFrameOfType(nsIFrame::eSVG) ||
2668              aForFrame->IsSVGOuterSVGFrame());
2669 
2670   LayoutFrameType frameType = aForFrame->Type();
2671   nsIFrame* geometryFrame = aForFrame;
2672   if (MOZ_UNLIKELY(frameType == LayoutFrameType::Scroll &&
2673                    StyleImageLayerAttachment::Local == aLayer.mAttachment)) {
2674     nsIScrollableFrame* scrollableFrame = do_QueryFrame(aForFrame);
2675     positionArea = nsRect(scrollableFrame->GetScrolledFrame()->GetPosition()
2676                               // For the dir=rtl case:
2677                               + scrollableFrame->GetScrollRange().TopLeft(),
2678                           scrollableFrame->GetScrolledRect().Size());
2679     // The ScrolledRect’s size does not include the borders or scrollbars,
2680     // reverse the handling of background-origin
2681     // compared to the common case below.
2682     if (layerOrigin == StyleGeometryBox::BorderBox) {
2683       nsMargin border = geometryFrame->GetUsedBorder();
2684       border.ApplySkipSides(geometryFrame->GetSkipSides());
2685       positionArea.Inflate(border);
2686       positionArea.Inflate(scrollableFrame->GetActualScrollbarSizes());
2687     } else if (layerOrigin != StyleGeometryBox::PaddingBox) {
2688       nsMargin padding = geometryFrame->GetUsedPadding();
2689       padding.ApplySkipSides(geometryFrame->GetSkipSides());
2690       positionArea.Deflate(padding);
2691       NS_ASSERTION(layerOrigin == StyleGeometryBox::ContentBox,
2692                    "unknown background-origin value");
2693     }
2694     *aAttachedToFrame = aForFrame;
2695     return positionArea;
2696   }
2697 
2698   if (MOZ_UNLIKELY(frameType == LayoutFrameType::Canvas)) {
2699     geometryFrame = aForFrame->PrincipalChildList().FirstChild();
2700     // geometryFrame might be null if this canvas is a page created
2701     // as an overflow container (e.g. the in-flow content has already
2702     // finished and this page only displays the continuations of
2703     // absolutely positioned content).
2704     if (geometryFrame) {
2705       positionArea =
2706           nsPlaceholderFrame::GetRealFrameFor(geometryFrame)->GetRect();
2707     }
2708   } else {
2709     positionArea = nsRect(nsPoint(0, 0), aBorderArea.Size());
2710   }
2711 
2712   // See the comment of StyleGeometryBox::MarginBox.
2713   // Hitting this assertion means we decide to turn on margin-box support for
2714   // positioned mask from CSS parser and style system. In this case, you
2715   // should *inflate* positionArea by the margin returning from
2716   // geometryFrame->GetUsedMargin() in the code chunk bellow.
2717   MOZ_ASSERT(aLayer.mOrigin != StyleGeometryBox::MarginBox,
2718              "StyleGeometryBox::MarginBox rendering is not supported yet.\n");
2719 
2720   // {background|mask} images are tiled over the '{background|mask}-clip' area
2721   // but the origin of the tiling is based on the '{background|mask}-origin'
2722   // area.
2723   if (layerOrigin != StyleGeometryBox::BorderBox && geometryFrame) {
2724     nsMargin border = geometryFrame->GetUsedBorder();
2725     if (layerOrigin != StyleGeometryBox::PaddingBox) {
2726       border += geometryFrame->GetUsedPadding();
2727       NS_ASSERTION(layerOrigin == StyleGeometryBox::ContentBox,
2728                    "unknown background-origin value");
2729     }
2730     positionArea.Deflate(border);
2731   }
2732 
2733   nsIFrame* attachedToFrame = aForFrame;
2734   if (StyleImageLayerAttachment::Fixed == aLayer.mAttachment) {
2735     // If it's a fixed background attachment, then the image is placed
2736     // relative to the viewport, which is the area of the root frame
2737     // in a screen context or the page content frame in a print context.
2738     attachedToFrame = aPresContext->PresShell()->GetRootFrame();
2739     NS_ASSERTION(attachedToFrame, "no root frame");
2740     nsIFrame* pageContentFrame = nullptr;
2741     if (aPresContext->IsPaginated()) {
2742       pageContentFrame = nsLayoutUtils::GetClosestFrameOfType(
2743           aForFrame, LayoutFrameType::PageContent);
2744       if (pageContentFrame) {
2745         attachedToFrame = pageContentFrame;
2746       }
2747       // else this is an embedded shell and its root frame is what we want
2748     }
2749 
2750     // If the background is affected by a transform, treat is as if it
2751     // wasn't fixed.
2752     if (nsLayoutUtils::IsTransformed(aForFrame, attachedToFrame)) {
2753       attachedToFrame = aForFrame;
2754       *aOutIsTransformedFixed = true;
2755     } else {
2756       // Set the background positioning area to the viewport's area
2757       // (relative to aForFrame)
2758       positionArea = nsRect(-aForFrame->GetOffsetTo(attachedToFrame),
2759                             attachedToFrame->GetSize());
2760 
2761       if (!pageContentFrame) {
2762         // Subtract the size of scrollbars.
2763         nsIScrollableFrame* scrollableFrame =
2764             aPresContext->PresShell()->GetRootScrollFrameAsScrollable();
2765         if (scrollableFrame) {
2766           nsMargin scrollbars = scrollableFrame->GetActualScrollbarSizes();
2767           positionArea.Deflate(scrollbars);
2768         }
2769       }
2770     }
2771   }
2772   *aAttachedToFrame = attachedToFrame;
2773 
2774   return positionArea;
2775 }
2776 
2777 /* static */
ComputeRoundedSize(nscoord aCurrentSize,nscoord aPositioningSize)2778 nscoord nsCSSRendering::ComputeRoundedSize(nscoord aCurrentSize,
2779                                            nscoord aPositioningSize) {
2780   float repeatCount = NS_roundf(float(aPositioningSize) / float(aCurrentSize));
2781   if (repeatCount < 1.0f) {
2782     return aPositioningSize;
2783   }
2784   return nscoord(NS_lround(float(aPositioningSize) / repeatCount));
2785 }
2786 
2787 // Apply the CSS image sizing algorithm as it applies to background images.
2788 // See http://www.w3.org/TR/css3-background/#the-background-size .
2789 // aIntrinsicSize is the size that the background image 'would like to be'.
2790 // It can be found by calling nsImageRenderer::ComputeIntrinsicSize.
ComputeDrawnSizeForBackground(const CSSSizeOrRatio & aIntrinsicSize,const nsSize & aBgPositioningArea,const StyleBackgroundSize & aLayerSize,StyleImageLayerRepeat aXRepeat,StyleImageLayerRepeat aYRepeat)2791 static nsSize ComputeDrawnSizeForBackground(
2792     const CSSSizeOrRatio& aIntrinsicSize, const nsSize& aBgPositioningArea,
2793     const StyleBackgroundSize& aLayerSize, StyleImageLayerRepeat aXRepeat,
2794     StyleImageLayerRepeat aYRepeat) {
2795   nsSize imageSize;
2796 
2797   // Size is dictated by cover or contain rules.
2798   if (aLayerSize.IsContain() || aLayerSize.IsCover()) {
2799     nsImageRenderer::FitType fitType = aLayerSize.IsCover()
2800                                            ? nsImageRenderer::COVER
2801                                            : nsImageRenderer::CONTAIN;
2802     imageSize = nsImageRenderer::ComputeConstrainedSize(
2803         aBgPositioningArea, aIntrinsicSize.mRatio, fitType);
2804   } else {
2805     MOZ_ASSERT(aLayerSize.IsExplicitSize());
2806     const auto& width = aLayerSize.explicit_size.width;
2807     const auto& height = aLayerSize.explicit_size.height;
2808     // No cover/contain constraint, use default algorithm.
2809     CSSSizeOrRatio specifiedSize;
2810     if (width.IsLengthPercentage()) {
2811       specifiedSize.SetWidth(
2812           width.AsLengthPercentage().Resolve(aBgPositioningArea.width));
2813     }
2814     if (height.IsLengthPercentage()) {
2815       specifiedSize.SetHeight(
2816           height.AsLengthPercentage().Resolve(aBgPositioningArea.height));
2817     }
2818 
2819     imageSize = nsImageRenderer::ComputeConcreteSize(
2820         specifiedSize, aIntrinsicSize, aBgPositioningArea);
2821   }
2822 
2823   // See https://www.w3.org/TR/css3-background/#background-size .
2824   // "If 'background-repeat' is 'round' for one (or both) dimensions, there is a
2825   // second
2826   //  step. The UA must scale the image in that dimension (or both dimensions)
2827   //  so that it fits a whole number of times in the background positioning
2828   //  area."
2829   // "If 'background-repeat' is 'round' for one dimension only and if
2830   // 'background-size'
2831   //  is 'auto' for the other dimension, then there is a third step: that other
2832   //  dimension is scaled so that the original aspect ratio is restored."
2833   bool isRepeatRoundInBothDimensions =
2834       aXRepeat == StyleImageLayerRepeat::Round &&
2835       aYRepeat == StyleImageLayerRepeat::Round;
2836 
2837   // Calculate the rounded size only if the background-size computation
2838   // returned a correct size for the image.
2839   if (imageSize.width && aXRepeat == StyleImageLayerRepeat::Round) {
2840     imageSize.width = nsCSSRendering::ComputeRoundedSize(
2841         imageSize.width, aBgPositioningArea.width);
2842     if (!isRepeatRoundInBothDimensions && aLayerSize.IsExplicitSize() &&
2843         aLayerSize.explicit_size.height.IsAuto()) {
2844       // Restore intrinsic ratio
2845       if (aIntrinsicSize.mRatio) {
2846         imageSize.height =
2847             aIntrinsicSize.mRatio.Inverted().ApplyTo(imageSize.width);
2848       }
2849     }
2850   }
2851 
2852   // Calculate the rounded size only if the background-size computation
2853   // returned a correct size for the image.
2854   if (imageSize.height && aYRepeat == StyleImageLayerRepeat::Round) {
2855     imageSize.height = nsCSSRendering::ComputeRoundedSize(
2856         imageSize.height, aBgPositioningArea.height);
2857     if (!isRepeatRoundInBothDimensions && aLayerSize.IsExplicitSize() &&
2858         aLayerSize.explicit_size.width.IsAuto()) {
2859       // Restore intrinsic ratio
2860       if (aIntrinsicSize.mRatio) {
2861         imageSize.width = aIntrinsicSize.mRatio.ApplyTo(imageSize.height);
2862       }
2863     }
2864   }
2865 
2866   return imageSize;
2867 }
2868 
2869 /* ComputeSpacedRepeatSize
2870  * aImageDimension: the image width/height
2871  * aAvailableSpace: the background positioning area width/height
2872  * aRepeat: determine whether the image is repeated
2873  * Returns the image size plus gap size of app units for use as spacing
2874  */
ComputeSpacedRepeatSize(nscoord aImageDimension,nscoord aAvailableSpace,bool & aRepeat)2875 static nscoord ComputeSpacedRepeatSize(nscoord aImageDimension,
2876                                        nscoord aAvailableSpace, bool& aRepeat) {
2877   float ratio = static_cast<float>(aAvailableSpace) / aImageDimension;
2878 
2879   if (ratio < 2.0f) {  // If you can't repeat at least twice, then don't repeat.
2880     aRepeat = false;
2881     return aImageDimension;
2882   }
2883 
2884   aRepeat = true;
2885   return (aAvailableSpace - aImageDimension) / (NSToIntFloor(ratio) - 1);
2886 }
2887 
2888 /* static */
ComputeBorderSpacedRepeatSize(nscoord aImageDimension,nscoord aAvailableSpace,nscoord & aSpace)2889 nscoord nsCSSRendering::ComputeBorderSpacedRepeatSize(nscoord aImageDimension,
2890                                                       nscoord aAvailableSpace,
2891                                                       nscoord& aSpace) {
2892   int32_t count = aImageDimension ? (aAvailableSpace / aImageDimension) : 0;
2893   aSpace = (aAvailableSpace - aImageDimension * count) / (count + 1);
2894   return aSpace + aImageDimension;
2895 }
2896 
PrepareImageLayer(nsPresContext * aPresContext,nsIFrame * aForFrame,uint32_t aFlags,const nsRect & aBorderArea,const nsRect & aBGClipRect,const nsStyleImageLayers::Layer & aLayer,bool * aOutIsTransformedFixed)2897 nsBackgroundLayerState nsCSSRendering::PrepareImageLayer(
2898     nsPresContext* aPresContext, nsIFrame* aForFrame, uint32_t aFlags,
2899     const nsRect& aBorderArea, const nsRect& aBGClipRect,
2900     const nsStyleImageLayers::Layer& aLayer, bool* aOutIsTransformedFixed) {
2901   /*
2902    * The properties we need to keep in mind when drawing style image
2903    * layers are:
2904    *
2905    *   background-image/ mask-image
2906    *   background-repeat/ mask-repeat
2907    *   background-attachment
2908    *   background-position/ mask-position
2909    *   background-clip/ mask-clip
2910    *   background-origin/ mask-origin
2911    *   background-size/ mask-size
2912    *   background-blend-mode
2913    *   box-decoration-break
2914    *   mask-mode
2915    *   mask-composite
2916    *
2917    * (background-color applies to the entire element and not to individual
2918    * layers, so it is irrelevant to this method.)
2919    *
2920    * These properties have the following dependencies upon each other when
2921    * determining rendering:
2922    *
2923    *   background-image/ mask-image
2924    *     no dependencies
2925    *   background-repeat/ mask-repeat
2926    *     no dependencies
2927    *   background-attachment
2928    *     no dependencies
2929    *   background-position/ mask-position
2930    *     depends upon background-size/mask-size (for the image's scaled size)
2931    *     and background-break (for the background positioning area)
2932    *   background-clip/ mask-clip
2933    *     no dependencies
2934    *   background-origin/ mask-origin
2935    *     depends upon background-attachment (only in the case where that value
2936    *     is 'fixed')
2937    *   background-size/ mask-size
2938    *     depends upon box-decoration-break (for the background positioning area
2939    *     for resolving percentages), background-image (for the image's intrinsic
2940    *     size), background-repeat (if that value is 'round'), and
2941    *     background-origin (for the background painting area, when
2942    *     background-repeat is 'round')
2943    *   background-blend-mode
2944    *     no dependencies
2945    *   mask-mode
2946    *     no dependencies
2947    *   mask-composite
2948    *     no dependencies
2949    *   box-decoration-break
2950    *     no dependencies
2951    *
2952    * As a result of only-if dependencies we don't strictly do a topological
2953    * sort of the above properties when processing, but it's pretty close to one:
2954    *
2955    *   background-clip/mask-clip (by caller)
2956    *   background-image/ mask-image
2957    *   box-decoration-break, background-origin/ mask origin
2958    *   background-attachment (postfix for background-origin if 'fixed')
2959    *   background-size/ mask-size
2960    *   background-position/ mask-position
2961    *   background-repeat/ mask-repeat
2962    */
2963 
2964   uint32_t irFlags = 0;
2965   if (aFlags & nsCSSRendering::PAINTBG_SYNC_DECODE_IMAGES) {
2966     irFlags |= nsImageRenderer::FLAG_SYNC_DECODE_IMAGES;
2967   }
2968   if (aFlags & nsCSSRendering::PAINTBG_TO_WINDOW) {
2969     irFlags |= nsImageRenderer::FLAG_PAINTING_TO_WINDOW;
2970   }
2971   if (aFlags & nsCSSRendering::PAINTBG_HIGH_QUALITY_SCALING) {
2972     irFlags |= nsImageRenderer::FLAG_HIGH_QUALITY_SCALING;
2973   }
2974 
2975   nsBackgroundLayerState state(aForFrame, &aLayer.mImage, irFlags);
2976   if (!state.mImageRenderer.PrepareImage()) {
2977     // There's no image or it's not ready to be painted.
2978     if (aOutIsTransformedFixed &&
2979         StyleImageLayerAttachment::Fixed == aLayer.mAttachment) {
2980       nsIFrame* attachedToFrame = aPresContext->PresShell()->GetRootFrame();
2981       NS_ASSERTION(attachedToFrame, "no root frame");
2982       nsIFrame* pageContentFrame = nullptr;
2983       if (aPresContext->IsPaginated()) {
2984         pageContentFrame = nsLayoutUtils::GetClosestFrameOfType(
2985             aForFrame, LayoutFrameType::PageContent);
2986         if (pageContentFrame) {
2987           attachedToFrame = pageContentFrame;
2988         }
2989         // else this is an embedded shell and its root frame is what we want
2990       }
2991 
2992       *aOutIsTransformedFixed =
2993           nsLayoutUtils::IsTransformed(aForFrame, attachedToFrame);
2994     }
2995     return state;
2996   }
2997 
2998   // The frame to which the background is attached
2999   nsIFrame* attachedToFrame = aForFrame;
3000   // Is the background marked 'fixed', but affected by a transform?
3001   bool transformedFixed = false;
3002   // Compute background origin area relative to aBorderArea now as we may need
3003   // it to compute the effective image size for a CSS gradient.
3004   nsRect positionArea = ComputeImageLayerPositioningArea(
3005       aPresContext, aForFrame, aBorderArea, aLayer, &attachedToFrame,
3006       &transformedFixed);
3007   if (aOutIsTransformedFixed) {
3008     *aOutIsTransformedFixed = transformedFixed;
3009   }
3010 
3011   // For background-attachment:fixed backgrounds, we'll override the area
3012   // where the background can be drawn to the viewport.
3013   nsRect bgClipRect = aBGClipRect;
3014 
3015   if (StyleImageLayerAttachment::Fixed == aLayer.mAttachment &&
3016       !transformedFixed && (aFlags & nsCSSRendering::PAINTBG_TO_WINDOW)) {
3017     bgClipRect = positionArea + aBorderArea.TopLeft();
3018   }
3019 
3020   StyleImageLayerRepeat repeatX = aLayer.mRepeat.mXRepeat;
3021   StyleImageLayerRepeat repeatY = aLayer.mRepeat.mYRepeat;
3022 
3023   // Scale the image as specified for background-size and background-repeat.
3024   // Also as required for proper background positioning when background-position
3025   // is defined with percentages.
3026   CSSSizeOrRatio intrinsicSize = state.mImageRenderer.ComputeIntrinsicSize();
3027   nsSize bgPositionSize = positionArea.Size();
3028   nsSize imageSize = ComputeDrawnSizeForBackground(
3029       intrinsicSize, bgPositionSize, aLayer.mSize, repeatX, repeatY);
3030 
3031   if (imageSize.width <= 0 || imageSize.height <= 0) return state;
3032 
3033   state.mImageRenderer.SetPreferredSize(intrinsicSize, imageSize);
3034 
3035   // Compute the anchor point.
3036   //
3037   // relative to aBorderArea.TopLeft() (which is where the top-left
3038   // of aForFrame's border-box will be rendered)
3039   nsPoint imageTopLeft;
3040 
3041   // Compute the position of the background now that the background's size is
3042   // determined.
3043   nsImageRenderer::ComputeObjectAnchorPoint(aLayer.mPosition, bgPositionSize,
3044                                             imageSize, &imageTopLeft,
3045                                             &state.mAnchor);
3046   state.mRepeatSize = imageSize;
3047   if (repeatX == StyleImageLayerRepeat::Space) {
3048     bool isRepeat;
3049     state.mRepeatSize.width = ComputeSpacedRepeatSize(
3050         imageSize.width, bgPositionSize.width, isRepeat);
3051     if (isRepeat) {
3052       imageTopLeft.x = 0;
3053       state.mAnchor.x = 0;
3054     } else {
3055       repeatX = StyleImageLayerRepeat::NoRepeat;
3056     }
3057   }
3058 
3059   if (repeatY == StyleImageLayerRepeat::Space) {
3060     bool isRepeat;
3061     state.mRepeatSize.height = ComputeSpacedRepeatSize(
3062         imageSize.height, bgPositionSize.height, isRepeat);
3063     if (isRepeat) {
3064       imageTopLeft.y = 0;
3065       state.mAnchor.y = 0;
3066     } else {
3067       repeatY = StyleImageLayerRepeat::NoRepeat;
3068     }
3069   }
3070 
3071   imageTopLeft += positionArea.TopLeft();
3072   state.mAnchor += positionArea.TopLeft();
3073   state.mDestArea = nsRect(imageTopLeft + aBorderArea.TopLeft(), imageSize);
3074   state.mFillArea = state.mDestArea;
3075 
3076   ExtendMode repeatMode = ExtendMode::CLAMP;
3077   if (repeatX == StyleImageLayerRepeat::Repeat ||
3078       repeatX == StyleImageLayerRepeat::Round ||
3079       repeatX == StyleImageLayerRepeat::Space) {
3080     state.mFillArea.x = bgClipRect.x;
3081     state.mFillArea.width = bgClipRect.width;
3082     repeatMode = ExtendMode::REPEAT_X;
3083   }
3084   if (repeatY == StyleImageLayerRepeat::Repeat ||
3085       repeatY == StyleImageLayerRepeat::Round ||
3086       repeatY == StyleImageLayerRepeat::Space) {
3087     state.mFillArea.y = bgClipRect.y;
3088     state.mFillArea.height = bgClipRect.height;
3089 
3090     /***
3091      * We're repeating on the X axis already,
3092      * so if we have to repeat in the Y axis,
3093      * we really need to repeat in both directions.
3094      */
3095     if (repeatMode == ExtendMode::REPEAT_X) {
3096       repeatMode = ExtendMode::REPEAT;
3097     } else {
3098       repeatMode = ExtendMode::REPEAT_Y;
3099     }
3100   }
3101   state.mImageRenderer.SetExtendMode(repeatMode);
3102   state.mImageRenderer.SetMaskOp(aLayer.mMaskMode);
3103 
3104   state.mFillArea.IntersectRect(state.mFillArea, bgClipRect);
3105 
3106   return state;
3107 }
3108 
GetBackgroundLayerRect(nsPresContext * aPresContext,nsIFrame * aForFrame,const nsRect & aBorderArea,const nsRect & aClipRect,const nsStyleImageLayers::Layer & aLayer,uint32_t aFlags)3109 nsRect nsCSSRendering::GetBackgroundLayerRect(
3110     nsPresContext* aPresContext, nsIFrame* aForFrame, const nsRect& aBorderArea,
3111     const nsRect& aClipRect, const nsStyleImageLayers::Layer& aLayer,
3112     uint32_t aFlags) {
3113   Sides skipSides = aForFrame->GetSkipSides();
3114   nsRect borderArea =
3115       BoxDecorationRectForBackground(aForFrame, aBorderArea, skipSides);
3116   nsBackgroundLayerState state = PrepareImageLayer(
3117       aPresContext, aForFrame, aFlags, borderArea, aClipRect, aLayer);
3118   return state.mFillArea;
3119 }
3120 
3121 // Begin table border-collapsing section
3122 // These functions were written to not disrupt the normal ones and yet satisfy
3123 // some additional requirements At some point, all functions should be unified
3124 // to include the additional functionality that these provide
3125 
RoundIntToPixel(nscoord aValue,nscoord aOneDevPixel,bool aRoundDown=false)3126 static nscoord RoundIntToPixel(nscoord aValue, nscoord aOneDevPixel,
3127                                bool aRoundDown = false) {
3128   if (aOneDevPixel <= 0) {
3129     // We must be rendering to a device that has a resolution greater than
3130     // one device pixel!
3131     // In that case, aValue is as accurate as it's going to get.
3132     return aValue;
3133   }
3134 
3135   nscoord halfPixel = NSToCoordRound(aOneDevPixel / 2.0f);
3136   nscoord extra = aValue % aOneDevPixel;
3137   nscoord finalValue = (!aRoundDown && (extra >= halfPixel))
3138                            ? aValue + (aOneDevPixel - extra)
3139                            : aValue - extra;
3140   return finalValue;
3141 }
3142 
RoundFloatToPixel(float aValue,nscoord aOneDevPixel,bool aRoundDown=false)3143 static nscoord RoundFloatToPixel(float aValue, nscoord aOneDevPixel,
3144                                  bool aRoundDown = false) {
3145   return RoundIntToPixel(NSToCoordRound(aValue), aOneDevPixel, aRoundDown);
3146 }
3147 
SetPoly(const Rect & aRect,Point * poly)3148 static void SetPoly(const Rect& aRect, Point* poly) {
3149   poly[0].x = aRect.x;
3150   poly[0].y = aRect.y;
3151   poly[1].x = aRect.x + aRect.width;
3152   poly[1].y = aRect.y;
3153   poly[2].x = aRect.x + aRect.width;
3154   poly[2].y = aRect.y + aRect.height;
3155   poly[3].x = aRect.x;
3156   poly[3].y = aRect.y + aRect.height;
3157 }
3158 
DrawDashedSegment(DrawTarget & aDrawTarget,nsRect aRect,nscoord aDashLength,nscolor aColor,int32_t aAppUnitsPerDevPixel,bool aHorizontal)3159 static void DrawDashedSegment(DrawTarget& aDrawTarget, nsRect aRect,
3160                               nscoord aDashLength, nscolor aColor,
3161                               int32_t aAppUnitsPerDevPixel, bool aHorizontal) {
3162   ColorPattern color(ToDeviceColor(aColor));
3163   DrawOptions drawOptions(1.f, CompositionOp::OP_OVER, AntialiasMode::NONE);
3164   StrokeOptions strokeOptions;
3165 
3166   Float dash[2];
3167   dash[0] = Float(aDashLength) / aAppUnitsPerDevPixel;
3168   dash[1] = dash[0];
3169 
3170   strokeOptions.mDashPattern = dash;
3171   strokeOptions.mDashLength = MOZ_ARRAY_LENGTH(dash);
3172 
3173   if (aHorizontal) {
3174     nsPoint left = (aRect.TopLeft() + aRect.BottomLeft()) / 2;
3175     nsPoint right = (aRect.TopRight() + aRect.BottomRight()) / 2;
3176     strokeOptions.mLineWidth = Float(aRect.height) / aAppUnitsPerDevPixel;
3177     StrokeLineWithSnapping(left, right, aAppUnitsPerDevPixel, aDrawTarget,
3178                            color, strokeOptions, drawOptions);
3179   } else {
3180     nsPoint top = (aRect.TopLeft() + aRect.TopRight()) / 2;
3181     nsPoint bottom = (aRect.BottomLeft() + aRect.BottomRight()) / 2;
3182     strokeOptions.mLineWidth = Float(aRect.width) / aAppUnitsPerDevPixel;
3183     StrokeLineWithSnapping(top, bottom, aAppUnitsPerDevPixel, aDrawTarget,
3184                            color, strokeOptions, drawOptions);
3185   }
3186 }
3187 
DrawSolidBorderSegment(DrawTarget & aDrawTarget,nsRect aRect,nscolor aColor,int32_t aAppUnitsPerDevPixel,mozilla::Side aStartBevelSide=mozilla::eSideTop,nscoord aStartBevelOffset=0,mozilla::Side aEndBevelSide=mozilla::eSideTop,nscoord aEndBevelOffset=0)3188 static void DrawSolidBorderSegment(
3189     DrawTarget& aDrawTarget, nsRect aRect, nscolor aColor,
3190     int32_t aAppUnitsPerDevPixel,
3191     mozilla::Side aStartBevelSide = mozilla::eSideTop,
3192     nscoord aStartBevelOffset = 0,
3193     mozilla::Side aEndBevelSide = mozilla::eSideTop,
3194     nscoord aEndBevelOffset = 0) {
3195   ColorPattern color(ToDeviceColor(aColor));
3196   DrawOptions drawOptions(1.f, CompositionOp::OP_OVER, AntialiasMode::NONE);
3197 
3198   nscoord oneDevPixel = NSIntPixelsToAppUnits(1, aAppUnitsPerDevPixel);
3199   // We don't need to bevel single pixel borders
3200   if ((aRect.width == oneDevPixel) || (aRect.height == oneDevPixel) ||
3201       ((0 == aStartBevelOffset) && (0 == aEndBevelOffset))) {
3202     // simple rectangle
3203     aDrawTarget.FillRect(
3204         NSRectToSnappedRect(aRect, aAppUnitsPerDevPixel, aDrawTarget), color,
3205         drawOptions);
3206   } else {
3207     // polygon with beveling
3208     Point poly[4];
3209     SetPoly(NSRectToSnappedRect(aRect, aAppUnitsPerDevPixel, aDrawTarget),
3210             poly);
3211 
3212     Float startBevelOffset =
3213         NSAppUnitsToFloatPixels(aStartBevelOffset, aAppUnitsPerDevPixel);
3214     switch (aStartBevelSide) {
3215       case eSideTop:
3216         poly[0].x += startBevelOffset;
3217         break;
3218       case eSideBottom:
3219         poly[3].x += startBevelOffset;
3220         break;
3221       case eSideRight:
3222         poly[1].y += startBevelOffset;
3223         break;
3224       case eSideLeft:
3225         poly[0].y += startBevelOffset;
3226     }
3227 
3228     Float endBevelOffset =
3229         NSAppUnitsToFloatPixels(aEndBevelOffset, aAppUnitsPerDevPixel);
3230     switch (aEndBevelSide) {
3231       case eSideTop:
3232         poly[1].x -= endBevelOffset;
3233         break;
3234       case eSideBottom:
3235         poly[2].x -= endBevelOffset;
3236         break;
3237       case eSideRight:
3238         poly[2].y -= endBevelOffset;
3239         break;
3240       case eSideLeft:
3241         poly[3].y -= endBevelOffset;
3242     }
3243 
3244     RefPtr<PathBuilder> builder = aDrawTarget.CreatePathBuilder();
3245     builder->MoveTo(poly[0]);
3246     builder->LineTo(poly[1]);
3247     builder->LineTo(poly[2]);
3248     builder->LineTo(poly[3]);
3249     builder->Close();
3250     RefPtr<Path> path = builder->Finish();
3251     aDrawTarget.Fill(path, color, drawOptions);
3252   }
3253 }
3254 
GetDashInfo(nscoord aBorderLength,nscoord aDashLength,nscoord aOneDevPixel,int32_t & aNumDashSpaces,nscoord & aStartDashLength,nscoord & aEndDashLength)3255 static void GetDashInfo(nscoord aBorderLength, nscoord aDashLength,
3256                         nscoord aOneDevPixel, int32_t& aNumDashSpaces,
3257                         nscoord& aStartDashLength, nscoord& aEndDashLength) {
3258   aNumDashSpaces = 0;
3259   if (aStartDashLength + aDashLength + aEndDashLength >= aBorderLength) {
3260     aStartDashLength = aBorderLength;
3261     aEndDashLength = 0;
3262   } else {
3263     aNumDashSpaces =
3264         (aBorderLength - aDashLength) / (2 * aDashLength);  // round down
3265     nscoord extra = aBorderLength - aStartDashLength - aEndDashLength -
3266                     (((2 * aNumDashSpaces) - 1) * aDashLength);
3267     if (extra > 0) {
3268       nscoord half = RoundIntToPixel(extra / 2, aOneDevPixel);
3269       aStartDashLength += half;
3270       aEndDashLength += (extra - half);
3271     }
3272   }
3273 }
3274 
DrawTableBorderSegment(DrawTarget & aDrawTarget,StyleBorderStyle aBorderStyle,nscolor aBorderColor,const nsRect & aBorder,int32_t aAppUnitsPerDevPixel,mozilla::Side aStartBevelSide,nscoord aStartBevelOffset,mozilla::Side aEndBevelSide,nscoord aEndBevelOffset)3275 void nsCSSRendering::DrawTableBorderSegment(
3276     DrawTarget& aDrawTarget, StyleBorderStyle aBorderStyle,
3277     nscolor aBorderColor, const nsRect& aBorder, int32_t aAppUnitsPerDevPixel,
3278     mozilla::Side aStartBevelSide, nscoord aStartBevelOffset,
3279     mozilla::Side aEndBevelSide, nscoord aEndBevelOffset) {
3280   bool horizontal =
3281       ((eSideTop == aStartBevelSide) || (eSideBottom == aStartBevelSide));
3282   nscoord oneDevPixel = NSIntPixelsToAppUnits(1, aAppUnitsPerDevPixel);
3283 
3284   if ((oneDevPixel >= aBorder.width) || (oneDevPixel >= aBorder.height) ||
3285       (StyleBorderStyle::Dashed == aBorderStyle) ||
3286       (StyleBorderStyle::Dotted == aBorderStyle)) {
3287     // no beveling for 1 pixel border, dash or dot
3288     aStartBevelOffset = 0;
3289     aEndBevelOffset = 0;
3290   }
3291 
3292   switch (aBorderStyle) {
3293     case StyleBorderStyle::None:
3294     case StyleBorderStyle::Hidden:
3295       // NS_ASSERTION(false, "style of none or hidden");
3296       break;
3297     case StyleBorderStyle::Dotted:
3298     case StyleBorderStyle::Dashed: {
3299       nscoord dashLength =
3300           (StyleBorderStyle::Dashed == aBorderStyle) ? DASH_LENGTH : DOT_LENGTH;
3301       // make the dash length proportional to the border thickness
3302       dashLength *= (horizontal) ? aBorder.height : aBorder.width;
3303       // make the min dash length for the ends 1/2 the dash length
3304       nscoord minDashLength =
3305           (StyleBorderStyle::Dashed == aBorderStyle)
3306               ? RoundFloatToPixel(((float)dashLength) / 2.0f,
3307                                   aAppUnitsPerDevPixel)
3308               : dashLength;
3309       minDashLength = std::max(minDashLength, oneDevPixel);
3310       nscoord numDashSpaces = 0;
3311       nscoord startDashLength = minDashLength;
3312       nscoord endDashLength = minDashLength;
3313       if (horizontal) {
3314         GetDashInfo(aBorder.width, dashLength, aAppUnitsPerDevPixel,
3315                     numDashSpaces, startDashLength, endDashLength);
3316         nsRect rect(aBorder.x, aBorder.y, startDashLength, aBorder.height);
3317         DrawSolidBorderSegment(aDrawTarget, rect, aBorderColor,
3318                                aAppUnitsPerDevPixel);
3319 
3320         rect.x += startDashLength + dashLength;
3321         rect.width =
3322             aBorder.width - (startDashLength + endDashLength + dashLength);
3323         DrawDashedSegment(aDrawTarget, rect, dashLength, aBorderColor,
3324                           aAppUnitsPerDevPixel, horizontal);
3325 
3326         rect.x += rect.width;
3327         rect.width = endDashLength;
3328         DrawSolidBorderSegment(aDrawTarget, rect, aBorderColor,
3329                                aAppUnitsPerDevPixel);
3330       } else {
3331         GetDashInfo(aBorder.height, dashLength, aAppUnitsPerDevPixel,
3332                     numDashSpaces, startDashLength, endDashLength);
3333         nsRect rect(aBorder.x, aBorder.y, aBorder.width, startDashLength);
3334         DrawSolidBorderSegment(aDrawTarget, rect, aBorderColor,
3335                                aAppUnitsPerDevPixel);
3336 
3337         rect.y += rect.height + dashLength;
3338         rect.height =
3339             aBorder.height - (startDashLength + endDashLength + dashLength);
3340         DrawDashedSegment(aDrawTarget, rect, dashLength, aBorderColor,
3341                           aAppUnitsPerDevPixel, horizontal);
3342 
3343         rect.y += rect.height;
3344         rect.height = endDashLength;
3345         DrawSolidBorderSegment(aDrawTarget, rect, aBorderColor,
3346                                aAppUnitsPerDevPixel);
3347       }
3348     } break;
3349     default:
3350       AutoTArray<SolidBeveledBorderSegment, 3> segments;
3351       GetTableBorderSolidSegments(
3352           segments, aBorderStyle, aBorderColor, aBorder, aAppUnitsPerDevPixel,
3353           aStartBevelSide, aStartBevelOffset, aEndBevelSide, aEndBevelOffset);
3354       for (const auto& segment : segments) {
3355         DrawSolidBorderSegment(
3356             aDrawTarget, segment.mRect, segment.mColor, aAppUnitsPerDevPixel,
3357             segment.mStartBevel.mSide, segment.mStartBevel.mOffset,
3358             segment.mEndBevel.mSide, segment.mEndBevel.mOffset);
3359       }
3360       break;
3361   }
3362 }
3363 
GetTableBorderSolidSegments(nsTArray<SolidBeveledBorderSegment> & aSegments,StyleBorderStyle aBorderStyle,nscolor aBorderColor,const nsRect & aBorder,int32_t aAppUnitsPerDevPixel,mozilla::Side aStartBevelSide,nscoord aStartBevelOffset,mozilla::Side aEndBevelSide,nscoord aEndBevelOffset)3364 void nsCSSRendering::GetTableBorderSolidSegments(
3365     nsTArray<SolidBeveledBorderSegment>& aSegments,
3366     StyleBorderStyle aBorderStyle, nscolor aBorderColor, const nsRect& aBorder,
3367     int32_t aAppUnitsPerDevPixel, mozilla::Side aStartBevelSide,
3368     nscoord aStartBevelOffset, mozilla::Side aEndBevelSide,
3369     nscoord aEndBevelOffset) {
3370   const bool horizontal =
3371       eSideTop == aStartBevelSide || eSideBottom == aStartBevelSide;
3372   const nscoord oneDevPixel = NSIntPixelsToAppUnits(1, aAppUnitsPerDevPixel);
3373 
3374   switch (aBorderStyle) {
3375     case StyleBorderStyle::None:
3376     case StyleBorderStyle::Hidden:
3377       return;
3378     case StyleBorderStyle::Dotted:
3379     case StyleBorderStyle::Dashed:
3380       MOZ_ASSERT_UNREACHABLE("Caller should have checked");
3381       return;
3382     case StyleBorderStyle::Groove:
3383     case StyleBorderStyle::Ridge:
3384       if ((horizontal && (oneDevPixel >= aBorder.height)) ||
3385           (!horizontal && (oneDevPixel >= aBorder.width))) {
3386         aSegments.AppendElement(
3387             SolidBeveledBorderSegment{aBorder,
3388                                       aBorderColor,
3389                                       {aStartBevelSide, aStartBevelOffset},
3390                                       {aEndBevelSide, aEndBevelOffset}});
3391       } else {
3392         nscoord startBevel =
3393             (aStartBevelOffset > 0)
3394                 ? RoundFloatToPixel(0.5f * (float)aStartBevelOffset,
3395                                     aAppUnitsPerDevPixel, true)
3396                 : 0;
3397         nscoord endBevel =
3398             (aEndBevelOffset > 0)
3399                 ? RoundFloatToPixel(0.5f * (float)aEndBevelOffset,
3400                                     aAppUnitsPerDevPixel, true)
3401                 : 0;
3402         mozilla::Side ridgeGrooveSide = (horizontal) ? eSideTop : eSideLeft;
3403         // FIXME: In theory, this should use the visited-dependent
3404         // background color, but I don't care.
3405         nscolor bevelColor =
3406             MakeBevelColor(ridgeGrooveSide, aBorderStyle, aBorderColor);
3407         nsRect rect(aBorder);
3408         nscoord half;
3409         if (horizontal) {  // top, bottom
3410           half = RoundFloatToPixel(0.5f * (float)aBorder.height,
3411                                    aAppUnitsPerDevPixel);
3412           rect.height = half;
3413           if (eSideTop == aStartBevelSide) {
3414             rect.x += startBevel;
3415             rect.width -= startBevel;
3416           }
3417           if (eSideTop == aEndBevelSide) {
3418             rect.width -= endBevel;
3419           }
3420           aSegments.AppendElement(
3421               SolidBeveledBorderSegment{rect,
3422                                         bevelColor,
3423                                         {aStartBevelSide, startBevel},
3424                                         {aEndBevelSide, endBevel}});
3425         } else {  // left, right
3426           half = RoundFloatToPixel(0.5f * (float)aBorder.width,
3427                                    aAppUnitsPerDevPixel);
3428           rect.width = half;
3429           if (eSideLeft == aStartBevelSide) {
3430             rect.y += startBevel;
3431             rect.height -= startBevel;
3432           }
3433           if (eSideLeft == aEndBevelSide) {
3434             rect.height -= endBevel;
3435           }
3436           aSegments.AppendElement(
3437               SolidBeveledBorderSegment{rect,
3438                                         bevelColor,
3439                                         {aStartBevelSide, startBevel},
3440                                         {aEndBevelSide, endBevel}});
3441         }
3442 
3443         rect = aBorder;
3444         ridgeGrooveSide =
3445             (eSideTop == ridgeGrooveSide) ? eSideBottom : eSideRight;
3446         // FIXME: In theory, this should use the visited-dependent
3447         // background color, but I don't care.
3448         bevelColor =
3449             MakeBevelColor(ridgeGrooveSide, aBorderStyle, aBorderColor);
3450         if (horizontal) {
3451           rect.y = rect.y + half;
3452           rect.height = aBorder.height - half;
3453           if (eSideBottom == aStartBevelSide) {
3454             rect.x += startBevel;
3455             rect.width -= startBevel;
3456           }
3457           if (eSideBottom == aEndBevelSide) {
3458             rect.width -= endBevel;
3459           }
3460           aSegments.AppendElement(
3461               SolidBeveledBorderSegment{rect,
3462                                         bevelColor,
3463                                         {aStartBevelSide, startBevel},
3464                                         {aEndBevelSide, endBevel}});
3465         } else {
3466           rect.x = rect.x + half;
3467           rect.width = aBorder.width - half;
3468           if (eSideRight == aStartBevelSide) {
3469             rect.y += aStartBevelOffset - startBevel;
3470             rect.height -= startBevel;
3471           }
3472           if (eSideRight == aEndBevelSide) {
3473             rect.height -= endBevel;
3474           }
3475           aSegments.AppendElement(
3476               SolidBeveledBorderSegment{rect,
3477                                         bevelColor,
3478                                         {aStartBevelSide, startBevel},
3479                                         {aEndBevelSide, endBevel}});
3480         }
3481       }
3482       break;
3483     case StyleBorderStyle::Double:
3484       // We can only do "double" borders if the thickness of the border
3485       // is more than 2px.  Otherwise, we fall through to painting a
3486       // solid border.
3487       if ((aBorder.width > 2 * oneDevPixel || horizontal) &&
3488           (aBorder.height > 2 * oneDevPixel || !horizontal)) {
3489         nscoord startBevel =
3490             (aStartBevelOffset > 0)
3491                 ? RoundFloatToPixel(0.333333f * (float)aStartBevelOffset,
3492                                     aAppUnitsPerDevPixel)
3493                 : 0;
3494         nscoord endBevel =
3495             (aEndBevelOffset > 0)
3496                 ? RoundFloatToPixel(0.333333f * (float)aEndBevelOffset,
3497                                     aAppUnitsPerDevPixel)
3498                 : 0;
3499         if (horizontal) {  // top, bottom
3500           nscoord thirdHeight = RoundFloatToPixel(
3501               0.333333f * (float)aBorder.height, aAppUnitsPerDevPixel);
3502 
3503           // draw the top line or rect
3504           nsRect topRect(aBorder.x, aBorder.y, aBorder.width, thirdHeight);
3505           if (eSideTop == aStartBevelSide) {
3506             topRect.x += aStartBevelOffset - startBevel;
3507             topRect.width -= aStartBevelOffset - startBevel;
3508           }
3509           if (eSideTop == aEndBevelSide) {
3510             topRect.width -= aEndBevelOffset - endBevel;
3511           }
3512 
3513           aSegments.AppendElement(
3514               SolidBeveledBorderSegment{topRect,
3515                                         aBorderColor,
3516                                         {aStartBevelSide, startBevel},
3517                                         {aEndBevelSide, endBevel}});
3518 
3519           // draw the botom line or rect
3520           nscoord heightOffset = aBorder.height - thirdHeight;
3521           nsRect bottomRect(aBorder.x, aBorder.y + heightOffset, aBorder.width,
3522                             aBorder.height - heightOffset);
3523           if (eSideBottom == aStartBevelSide) {
3524             bottomRect.x += aStartBevelOffset - startBevel;
3525             bottomRect.width -= aStartBevelOffset - startBevel;
3526           }
3527           if (eSideBottom == aEndBevelSide) {
3528             bottomRect.width -= aEndBevelOffset - endBevel;
3529           }
3530           aSegments.AppendElement(
3531               SolidBeveledBorderSegment{bottomRect,
3532                                         aBorderColor,
3533                                         {aStartBevelSide, startBevel},
3534                                         {aEndBevelSide, endBevel}});
3535         } else {  // left, right
3536           nscoord thirdWidth = RoundFloatToPixel(
3537               0.333333f * (float)aBorder.width, aAppUnitsPerDevPixel);
3538 
3539           nsRect leftRect(aBorder.x, aBorder.y, thirdWidth, aBorder.height);
3540           if (eSideLeft == aStartBevelSide) {
3541             leftRect.y += aStartBevelOffset - startBevel;
3542             leftRect.height -= aStartBevelOffset - startBevel;
3543           }
3544           if (eSideLeft == aEndBevelSide) {
3545             leftRect.height -= aEndBevelOffset - endBevel;
3546           }
3547 
3548           aSegments.AppendElement(
3549               SolidBeveledBorderSegment{leftRect,
3550                                         aBorderColor,
3551                                         {aStartBevelSide, startBevel},
3552                                         {aEndBevelSide, endBevel}});
3553 
3554           nscoord widthOffset = aBorder.width - thirdWidth;
3555           nsRect rightRect(aBorder.x + widthOffset, aBorder.y,
3556                            aBorder.width - widthOffset, aBorder.height);
3557           if (eSideRight == aStartBevelSide) {
3558             rightRect.y += aStartBevelOffset - startBevel;
3559             rightRect.height -= aStartBevelOffset - startBevel;
3560           }
3561           if (eSideRight == aEndBevelSide) {
3562             rightRect.height -= aEndBevelOffset - endBevel;
3563           }
3564           aSegments.AppendElement(
3565               SolidBeveledBorderSegment{rightRect,
3566                                         aBorderColor,
3567                                         {aStartBevelSide, startBevel},
3568                                         {aEndBevelSide, endBevel}});
3569         }
3570         break;
3571       }
3572       // else fall through to solid
3573       [[fallthrough]];
3574     case StyleBorderStyle::Solid:
3575       aSegments.AppendElement(
3576           SolidBeveledBorderSegment{aBorder,
3577                                     aBorderColor,
3578                                     {aStartBevelSide, aStartBevelOffset},
3579                                     {aEndBevelSide, aEndBevelOffset}});
3580       break;
3581     case StyleBorderStyle::Outset:
3582     case StyleBorderStyle::Inset:
3583       MOZ_ASSERT_UNREACHABLE(
3584           "inset, outset should have been converted to groove, ridge");
3585       break;
3586   }
3587 }
3588 
3589 // End table border-collapsing section
3590 
ExpandPaintingRectForDecorationLine(nsIFrame * aFrame,const uint8_t aStyle,const Rect & aClippedRect,const Float aICoordInFrame,const Float aCycleLength,bool aVertical)3591 Rect nsCSSRendering::ExpandPaintingRectForDecorationLine(
3592     nsIFrame* aFrame, const uint8_t aStyle, const Rect& aClippedRect,
3593     const Float aICoordInFrame, const Float aCycleLength, bool aVertical) {
3594   switch (aStyle) {
3595     case NS_STYLE_TEXT_DECORATION_STYLE_DOTTED:
3596     case NS_STYLE_TEXT_DECORATION_STYLE_DASHED:
3597     case NS_STYLE_TEXT_DECORATION_STYLE_WAVY:
3598       break;
3599     default:
3600       NS_ERROR("Invalid style was specified");
3601       return aClippedRect;
3602   }
3603 
3604   nsBlockFrame* block = nullptr;
3605   // Note that when we paint the decoration lines in relative positioned
3606   // box, we should paint them like all of the boxes are positioned as static.
3607   nscoord framePosInBlockAppUnits = 0;
3608   for (nsIFrame* f = aFrame; f; f = f->GetParent()) {
3609     block = do_QueryFrame(f);
3610     if (block) {
3611       break;
3612     }
3613     framePosInBlockAppUnits +=
3614         aVertical ? f->GetNormalPosition().y : f->GetNormalPosition().x;
3615   }
3616 
3617   NS_ENSURE_TRUE(block, aClippedRect);
3618 
3619   nsPresContext* pc = aFrame->PresContext();
3620   Float framePosInBlock =
3621       Float(pc->AppUnitsToGfxUnits(framePosInBlockAppUnits));
3622   int32_t rectPosInBlock = int32_t(NS_round(framePosInBlock + aICoordInFrame));
3623   int32_t extraStartEdge =
3624       rectPosInBlock - (rectPosInBlock / int32_t(aCycleLength) * aCycleLength);
3625   Rect rect(aClippedRect);
3626   if (aVertical) {
3627     rect.y -= extraStartEdge;
3628     rect.height += extraStartEdge;
3629   } else {
3630     rect.x -= extraStartEdge;
3631     rect.width += extraStartEdge;
3632   }
3633   return rect;
3634 }
3635 
3636 // Converts a GfxFont to an SkFont
3637 // Either returns true if it was successful, or false if something went wrong
GetSkFontFromGfxFont(DrawTarget & aDrawTarget,gfxFont * aFont,SkFont & aSkFont)3638 static bool GetSkFontFromGfxFont(DrawTarget& aDrawTarget, gfxFont* aFont,
3639                                  SkFont& aSkFont) {
3640   RefPtr<ScaledFont> scaledFont = aFont->GetScaledFont(&aDrawTarget);
3641   if (!scaledFont) {
3642     return false;
3643   }
3644 
3645   ScaledFontBase* fontBase = static_cast<ScaledFontBase*>(scaledFont.get());
3646 
3647   SkTypeface* typeface = fontBase->GetSkTypeface();
3648   if (!typeface) {
3649     return false;
3650   }
3651 
3652   aSkFont = SkFont(sk_ref_sp(typeface), SkFloatToScalar(fontBase->GetSize()));
3653   return true;
3654 }
3655 
3656 // Computes data used to position the decoration line within a
3657 // SkTextBlob, data is returned through aBounds
GetPositioning(const nsCSSRendering::PaintDecorationLineParams & aParams,const Rect & aRect,Float aOneCSSPixel,Float aCenterBaselineOffset,SkScalar aBounds[])3658 static void GetPositioning(
3659     const nsCSSRendering::PaintDecorationLineParams& aParams, const Rect& aRect,
3660     Float aOneCSSPixel, Float aCenterBaselineOffset, SkScalar aBounds[]) {
3661   /**
3662    * How Positioning in Skia Works
3663    *  Take the letter "n" for example
3664    *  We set textPos as 0, 0
3665    *  This is represented in Skia like so (not to scale)
3666    *        ^
3667    *  -10px |  _ __
3668    *        | | '_ \
3669    *   -5px | | | | |
3670    * y-axis | |_| |_|
3671    *  (0,0) ----------------------->
3672    *        |     5px        10px
3673    *    5px |
3674    *        |
3675    *   10px |
3676    *        v
3677    *  0 on the x axis is a line that touches the bottom of the n
3678    *  (0,0) is the bottom left-hand corner of the n character
3679    *  Moving "up" from the n is going in a negative y direction
3680    *  Moving "down" from the n is going in a positive y direction
3681    *
3682    *  The intercepts that are returned in this arrangement will be
3683    *  offset by the original point it starts at. (This happens in
3684    *  the SkipInk function below).
3685    *
3686    *  In Skia, text MUST be laid out such that the next character
3687    *  in the RunBuffer is further along the x-axis than the previous
3688    *  character, otherwise there is undefined/strange behavior.
3689    */
3690 
3691   Float rectThickness = aParams.vertical ? aRect.Width() : aRect.Height();
3692 
3693   // the upper and lower lines/edges of the under or over line
3694   SkScalar upperLine, lowerLine;
3695   if (aParams.decoration == mozilla::StyleTextDecorationLine::OVERLINE) {
3696     lowerLine =
3697         -aParams.offset + aParams.defaultLineThickness - aCenterBaselineOffset;
3698     upperLine = lowerLine - rectThickness;
3699   } else {
3700     // underlines in vertical text are offset from the center of
3701     // the text, and not the baseline
3702     // Skia sets the text at it's baseline so we have to offset it
3703     // for text in vertical-* writing modes
3704     upperLine = -aParams.offset - aCenterBaselineOffset;
3705     lowerLine = upperLine + rectThickness;
3706   }
3707 
3708   // set up the bounds, add in a little padding to the thickness of the line
3709   // (unless the line is <= 1 CSS pixel thick)
3710   Float lineThicknessPadding = aParams.lineSize.height > aOneCSSPixel
3711                                    ? 0.25f * aParams.lineSize.height
3712                                    : 0;
3713   // don't allow padding greater than 0.75 CSS pixel
3714   lineThicknessPadding = std::min(lineThicknessPadding, 0.75f * aOneCSSPixel);
3715   aBounds[0] = upperLine - lineThicknessPadding;
3716   aBounds[1] = lowerLine + lineThicknessPadding;
3717 }
3718 
3719 // positions an individual glyph according to the given offset
GlyphPosition(const gfxTextRun::DetailedGlyph & aGlyph,const SkPoint & aTextPos,int32_t aAppUnitsPerDevPixel)3720 static SkPoint GlyphPosition(const gfxTextRun::DetailedGlyph& aGlyph,
3721                              const SkPoint& aTextPos,
3722                              int32_t aAppUnitsPerDevPixel) {
3723   SkPoint point = {aGlyph.mOffset.x, aGlyph.mOffset.y};
3724 
3725   // convert to device pixels
3726   point.fX /= (float)aAppUnitsPerDevPixel;
3727   point.fY /= (float)aAppUnitsPerDevPixel;
3728 
3729   // add offsets
3730   point.fX += aTextPos.fX;
3731   point.fY += aTextPos.fY;
3732   return point;
3733 }
3734 
3735 // returns a count of all the glyphs that will be rendered
3736 // excludes ligature continuations, includes the number of individual
3737 // glyph records. This includes the number of DetailedGlyphs that a single
3738 // CompressedGlyph record points to. This function is necessary because Skia
3739 // needs the total length of glyphs to add to it's run buffer before it creates
3740 // the RunBuffer object, and this cannot be resized later.
CountAllGlyphs(const gfxTextRun * aTextRun,const gfxTextRun::CompressedGlyph * aCompressedGlyph,uint32_t aStringStart,uint32_t aStringEnd)3741 static uint32_t CountAllGlyphs(
3742     const gfxTextRun* aTextRun,
3743     const gfxTextRun::CompressedGlyph* aCompressedGlyph, uint32_t aStringStart,
3744     uint32_t aStringEnd) {
3745   uint32_t totalGlyphCount = 0;
3746 
3747   for (const gfxTextRun::CompressedGlyph* cg = aCompressedGlyph + aStringStart;
3748        cg < aCompressedGlyph + aStringEnd; ++cg) {
3749     totalGlyphCount += cg->IsSimpleGlyph() ? 1 : cg->GetGlyphCount();
3750   }
3751 
3752   return totalGlyphCount;
3753 }
3754 
AddDetailedGlyph(const SkTextBlobBuilder::RunBuffer & aRunBuffer,const gfxTextRun::DetailedGlyph & aGlyph,int aIndex,float aAppUnitsPerDevPixel,SkPoint & aTextPos)3755 static void AddDetailedGlyph(const SkTextBlobBuilder::RunBuffer& aRunBuffer,
3756                              const gfxTextRun::DetailedGlyph& aGlyph,
3757                              int aIndex, float aAppUnitsPerDevPixel,
3758                              SkPoint& aTextPos) {
3759   // add glyph ID to the run buffer at i
3760   aRunBuffer.glyphs[aIndex] = aGlyph.mGlyphID;
3761 
3762   // position the glyph correctly using the detailed offsets
3763   SkPoint position = GlyphPosition(aGlyph, aTextPos, aAppUnitsPerDevPixel);
3764   aRunBuffer.pos[2 * aIndex] = position.fX;
3765   aRunBuffer.pos[(2 * aIndex) + 1] = position.fY;
3766 
3767   // increase aTextPos.fx by the advance
3768   aTextPos.fX += ((float)aGlyph.mAdvance / aAppUnitsPerDevPixel);
3769 }
3770 
AddSimpleGlyph(const SkTextBlobBuilder::RunBuffer & aRunBuffer,const gfxTextRun::CompressedGlyph & aGlyph,int aIndex,float aAppUnitsPerDevPixel,SkPoint & aTextPos)3771 static void AddSimpleGlyph(const SkTextBlobBuilder::RunBuffer& aRunBuffer,
3772                            const gfxTextRun::CompressedGlyph& aGlyph,
3773                            int aIndex, float aAppUnitsPerDevPixel,
3774                            SkPoint& aTextPos) {
3775   aRunBuffer.glyphs[aIndex] = aGlyph.GetSimpleGlyph();
3776 
3777   // simple glyphs are offset from 0, so we'll just use textPos
3778   aRunBuffer.pos[2 * aIndex] = aTextPos.fX;
3779   aRunBuffer.pos[(2 * aIndex) + 1] = aTextPos.fY;
3780 
3781   // increase aTextPos.fX by the advance
3782   aTextPos.fX += ((float)aGlyph.GetSimpleAdvance() / aAppUnitsPerDevPixel);
3783 }
3784 
3785 // Sets up a Skia TextBlob of the specified font, text position, and made up of
3786 // the glyphs between aStringStart and aStringEnd. Handles RTL and LTR text
3787 // and positions each glyph within the text blob
CreateTextBlob(const gfxTextRun * aTextRun,const gfxTextRun::CompressedGlyph * aCompressedGlyph,const SkFont & aFont,const gfxTextRun::PropertyProvider::Spacing * aSpacing,uint32_t aStringStart,uint32_t aStringEnd,float aAppUnitsPerDevPixel,SkPoint & aTextPos,int32_t & aSpacingOffset)3788 static sk_sp<const SkTextBlob> CreateTextBlob(
3789     const gfxTextRun* aTextRun,
3790     const gfxTextRun::CompressedGlyph* aCompressedGlyph, const SkFont& aFont,
3791     const gfxTextRun::PropertyProvider::Spacing* aSpacing,
3792     uint32_t aStringStart, uint32_t aStringEnd, float aAppUnitsPerDevPixel,
3793     SkPoint& aTextPos, int32_t& aSpacingOffset) {
3794   // allocate space for the run buffer, then fill it with the glyphs
3795   uint32_t len =
3796       CountAllGlyphs(aTextRun, aCompressedGlyph, aStringStart, aStringEnd);
3797   if (len <= 0) {
3798     return nullptr;
3799   }
3800 
3801   SkTextBlobBuilder builder;
3802   const SkTextBlobBuilder::RunBuffer& run = builder.allocRunPos(aFont, len);
3803 
3804   // RTL text should be read in by glyph starting at aStringEnd - 1 down until
3805   // aStringStart.
3806   bool isRTL = aTextRun->IsRightToLeft();
3807   uint32_t currIndex = isRTL ? aStringEnd - 1 : aStringStart;  // textRun index
3808   // currIndex will be advanced by |step| until it reaches |limit|, which is the
3809   // final index to be handled (NOT one beyond the final index)
3810   int step = isRTL ? -1 : 1;
3811   uint32_t limit = isRTL ? aStringStart : aStringEnd - 1;
3812 
3813   uint32_t i = 0;  // index into the SkTextBlob we're building
3814   while (true) {
3815     // Loop exit test is below, just before we update currIndex.
3816     aTextPos.fX +=
3817         isRTL ? aSpacing[aSpacingOffset].mAfter / aAppUnitsPerDevPixel
3818               : aSpacing[aSpacingOffset].mBefore / aAppUnitsPerDevPixel;
3819 
3820     if (aCompressedGlyph[currIndex].IsSimpleGlyph()) {
3821       MOZ_ASSERT(i < len, "glyph count error!");
3822       AddSimpleGlyph(run, aCompressedGlyph[currIndex], i, aAppUnitsPerDevPixel,
3823                      aTextPos);
3824       i++;
3825     } else {
3826       // if it's detailed, potentially add multiple into run.glyphs
3827       uint32_t count = aCompressedGlyph[currIndex].GetGlyphCount();
3828       if (count > 0) {
3829         gfxTextRun::DetailedGlyph* detailGlyph =
3830             aTextRun->GetDetailedGlyphs(currIndex);
3831         for (uint32_t d = isRTL ? count - 1 : 0; count; count--, d += step) {
3832           MOZ_ASSERT(i < len, "glyph count error!");
3833           AddDetailedGlyph(run, detailGlyph[d], i, aAppUnitsPerDevPixel,
3834                            aTextPos);
3835           i++;
3836         }
3837       }
3838     }
3839     aTextPos.fX += isRTL
3840                        ? aSpacing[aSpacingOffset].mBefore / aAppUnitsPerDevPixel
3841                        : aSpacing[aSpacingOffset].mAfter / aAppUnitsPerDevPixel;
3842     aSpacingOffset += step;
3843 
3844     if (currIndex == limit) {
3845       break;
3846     }
3847     currIndex += step;
3848   }
3849 
3850   MOZ_ASSERT(i == len, "glyph count error!");
3851 
3852   return builder.make();
3853 }
3854 
3855 // Given a TextBlob, the bounding lines, and the set of current intercepts this
3856 // function adds the intercepts for the current TextBlob into the given set of
3857 // previoulsy calculated intercepts. This set is either of length 0, or a
3858 // multiple of 2 (since every intersection with a piece of text results in two
3859 // intercepts: entering/exiting)
GetTextIntercepts(const sk_sp<const SkTextBlob> & aBlob,const SkScalar aBounds[],nsTArray<SkScalar> & aIntercepts)3860 static void GetTextIntercepts(const sk_sp<const SkTextBlob>& aBlob,
3861                               const SkScalar aBounds[],
3862                               nsTArray<SkScalar>& aIntercepts) {
3863   // https://skia.org/user/api/SkTextBlob_Reference#Text_Blob_Text_Intercepts
3864   int count = aBlob->getIntercepts(aBounds, nullptr);
3865   if (count < 2) {
3866     return;
3867   }
3868   aBlob->getIntercepts(aBounds, aIntercepts.AppendElements(count));
3869 }
3870 
3871 // This function, given a set of intercepts that represent each intersection
3872 // between an under/overline and text, makes a series of calls to
3873 // PaintDecorationLineInternal that paints a series of clip rects which
3874 // implement the text-decoration-skip-ink property
3875 // Logic for where to place each clipped rect, and the length of each rect is
3876 // included here
SkipInk(nsIFrame * aFrame,DrawTarget & aDrawTarget,const nsCSSRendering::PaintDecorationLineParams & aParams,const nsTArray<SkScalar> & aIntercepts,Float aPadding,Rect & aRect)3877 static void SkipInk(nsIFrame* aFrame, DrawTarget& aDrawTarget,
3878                     const nsCSSRendering::PaintDecorationLineParams& aParams,
3879                     const nsTArray<SkScalar>& aIntercepts, Float aPadding,
3880                     Rect& aRect) {
3881   nsCSSRendering::PaintDecorationLineParams clipParams = aParams;
3882   int length = aIntercepts.Length();
3883 
3884   SkScalar startIntercept = 0;
3885   SkScalar endIntercept = 0;
3886 
3887   // keep track of the direction we are drawing the clipped rects in
3888   // for sideways text, our intercepts from the first glyph are actually
3889   // decreasing (towards the top edge of the page), so we use a negative
3890   // direction
3891   Float dir = 1.0f;
3892   Float lineStart = aParams.vertical ? aParams.pt.y : aParams.pt.x;
3893   Float lineEnd = lineStart + aParams.lineSize.width;
3894   if (aParams.sidewaysLeft) {
3895     dir = -1.0f;
3896     std::swap(lineStart, lineEnd);
3897   }
3898 
3899   for (int i = 0; i <= length; i += 2) {
3900     // handle start/end edge cases and set up general case
3901     startIntercept = (i > 0) ? (dir * aIntercepts[i - 1]) + lineStart
3902                              : lineStart - (dir * aPadding);
3903     endIntercept = (i < length) ? (dir * aIntercepts[i]) + lineStart
3904                                 : lineEnd + (dir * aPadding);
3905 
3906     // remove padding at both ends for width
3907     // the start of the line is calculated so the padding removes just
3908     // enough so that the line starts at its normal position
3909     clipParams.lineSize.width =
3910         (dir * (endIntercept - startIntercept)) - (2.0 * aPadding);
3911 
3912     // Don't draw decoration lines that have a smaller width than 1, or half
3913     // the line-end padding dimension.
3914     if (clipParams.lineSize.width < std::max(aPadding * 0.5, 1.0)) {
3915       continue;
3916     }
3917 
3918     // Start the line right after the intercept's location plus room for
3919     // padding; snap the rect edges to device pixels for consistent rendering
3920     // of dots across separate fragments of a dotted line.
3921     if (aParams.vertical) {
3922       clipParams.pt.y = aParams.sidewaysLeft ? endIntercept + aPadding
3923                                              : startIntercept + aPadding;
3924       aRect.y = std::floor(clipParams.pt.y + 0.5);
3925       aRect.SetBottomEdge(
3926           std::floor(clipParams.pt.y + clipParams.lineSize.width + 0.5));
3927     } else {
3928       clipParams.pt.x = startIntercept + aPadding;
3929       aRect.x = std::floor(clipParams.pt.x + 0.5);
3930       aRect.SetRightEdge(
3931           std::floor(clipParams.pt.x + clipParams.lineSize.width + 0.5));
3932     }
3933 
3934     nsCSSRendering::PaintDecorationLineInternal(aFrame, aDrawTarget, clipParams,
3935                                                 aRect);
3936   }
3937 }
3938 
PaintDecorationLine(nsIFrame * aFrame,DrawTarget & aDrawTarget,const PaintDecorationLineParams & aParams)3939 void nsCSSRendering::PaintDecorationLine(
3940     nsIFrame* aFrame, DrawTarget& aDrawTarget,
3941     const PaintDecorationLineParams& aParams) {
3942   NS_ASSERTION(aParams.style != NS_STYLE_TEXT_DECORATION_STYLE_NONE,
3943                "aStyle is none");
3944 
3945   Rect rect = ToRect(GetTextDecorationRectInternal(aParams.pt, aParams));
3946   if (rect.IsEmpty() || !rect.Intersects(aParams.dirtyRect)) {
3947     return;
3948   }
3949 
3950   if (aParams.decoration != StyleTextDecorationLine::UNDERLINE &&
3951       aParams.decoration != StyleTextDecorationLine::OVERLINE &&
3952       aParams.decoration != StyleTextDecorationLine::LINE_THROUGH) {
3953     MOZ_ASSERT_UNREACHABLE("Invalid text decoration value");
3954     return;
3955   }
3956 
3957   // Check if decoration line will skip past ascenders/descenders
3958   // text-decoration-skip-ink only applies to overlines/underlines
3959   mozilla::StyleTextDecorationSkipInk skipInk =
3960       aFrame->StyleText()->mTextDecorationSkipInk;
3961   bool skipInkEnabled =
3962       skipInk != mozilla::StyleTextDecorationSkipInk::None &&
3963       aParams.decoration != StyleTextDecorationLine::LINE_THROUGH;
3964 
3965   if (!skipInkEnabled || aParams.glyphRange.Length() == 0) {
3966     PaintDecorationLineInternal(aFrame, aDrawTarget, aParams, rect);
3967     return;
3968   }
3969 
3970   // check if the frame is a text frame or not
3971   nsTextFrame* textFrame = nullptr;
3972   if (aFrame->IsTextFrame()) {
3973     textFrame = static_cast<nsTextFrame*>(aFrame);
3974   } else {
3975     PaintDecorationLineInternal(aFrame, aDrawTarget, aParams, rect);
3976     return;
3977   }
3978 
3979   // get text run and current text offset (for line wrapping)
3980   gfxTextRun* textRun =
3981       textFrame->GetTextRun(nsTextFrame::TextRunType::eInflated);
3982 
3983   // used for conversions from app units to device pixels
3984   int32_t appUnitsPerDevPixel = aFrame->PresContext()->AppUnitsPerDevPixel();
3985 
3986   // pointer to the array of glyphs for this TextRun
3987   gfxTextRun::CompressedGlyph* characterGlyphs = textRun->GetCharacterGlyphs();
3988 
3989   // get positioning info
3990   SkPoint textPos = {0, aParams.baselineOffset};
3991   SkScalar bounds[] = {0, 0};
3992   Float oneCSSPixel = aFrame->PresContext()->CSSPixelsToDevPixels(1.0f);
3993   if (!textRun->UseCenterBaseline()) {
3994     GetPositioning(aParams, rect, oneCSSPixel, 0, bounds);
3995   }
3996 
3997   // array for the text intercepts
3998   AutoTArray<SkScalar, 256> intercepts;
3999 
4000   // array for spacing data
4001   AutoTArray<gfxTextRun::PropertyProvider::Spacing, 64> spacing;
4002   spacing.SetLength(aParams.glyphRange.Length());
4003   if (aParams.provider != nullptr) {
4004     aParams.provider->GetSpacing(aParams.glyphRange, spacing.Elements());
4005   }
4006 
4007   // loop through each glyph run
4008   // in most cases there will only be one
4009   bool isRTL = textRun->IsRightToLeft();
4010   int32_t spacingOffset = isRTL ? aParams.glyphRange.Length() - 1 : 0;
4011   gfxTextRun::GlyphRunIterator iter(textRun, aParams.glyphRange, isRTL);
4012 
4013   // For any glyph run where we don't actually do skipping, we'll need to
4014   // advance the current position by its width.
4015   // (For runs we do process, CreateTextBlob will update the position.)
4016   auto currentGlyphRunAdvance = [&]() {
4017     return textRun->GetAdvanceWidth(
4018                gfxTextRun::Range(iter.GetStringStart(), iter.GetStringEnd()),
4019                aParams.provider) /
4020            appUnitsPerDevPixel;
4021   };
4022 
4023   while (iter.NextRun()) {
4024     if (iter.GetGlyphRun()->mOrientation ==
4025             mozilla::gfx::ShapedTextFlags::TEXT_ORIENT_VERTICAL_UPRIGHT ||
4026         (iter.GetGlyphRun()->mIsCJK &&
4027          skipInk == mozilla::StyleTextDecorationSkipInk::Auto)) {
4028       // We don't support upright text in vertical modes currently
4029       // (see https://bugzilla.mozilla.org/show_bug.cgi?id=1572294),
4030       // but we do need to update textPos so that following runs will be
4031       // correctly positioned.
4032       // We also don't apply skip-ink to CJK text runs because many fonts
4033       // have an underline that looks really bad if this is done
4034       // (see https://bugzilla.mozilla.org/show_bug.cgi?id=1573249),
4035       // when skip-ink is set to 'auto'.
4036       textPos.fX += currentGlyphRunAdvance();
4037       continue;
4038     }
4039 
4040     gfxFont* font = iter.GetGlyphRun()->mFont;
4041     // Don't try to apply skip-ink to 'sbix' fonts like Apple Color Emoji,
4042     // because old macOS (10.9) may crash trying to retrieve glyph paths
4043     // that don't exist.
4044     if (font->GetFontEntry()->HasFontTable(TRUETYPE_TAG('s', 'b', 'i', 'x'))) {
4045       textPos.fX += currentGlyphRunAdvance();
4046       continue;
4047     }
4048 
4049     // get a Skia version of the glyph run's font
4050     SkFont skiafont;
4051     if (!GetSkFontFromGfxFont(aDrawTarget, font, skiafont)) {
4052       PaintDecorationLineInternal(aFrame, aDrawTarget, aParams, rect);
4053       return;
4054     }
4055 
4056     // Create a text blob with correctly positioned glyphs. This also updates
4057     // textPos.fX with the advance of the glyphs.
4058     sk_sp<const SkTextBlob> textBlob =
4059         CreateTextBlob(textRun, characterGlyphs, skiafont, spacing.Elements(),
4060                        iter.GetStringStart(), iter.GetStringEnd(),
4061                        (float)appUnitsPerDevPixel, textPos, spacingOffset);
4062 
4063     if (!textBlob) {
4064       textPos.fX += currentGlyphRunAdvance();
4065       continue;
4066     }
4067 
4068     if (textRun->UseCenterBaseline()) {
4069       // writing modes that use a center baseline need to be adjusted on a
4070       // font-by-font basis since Skia lines up the text on a alphabetic
4071       // baseline, but for some vertical-* writing modes the offset is from the
4072       // center.
4073       gfxFont::Metrics metrics = font->GetMetrics(nsFontMetrics::eHorizontal);
4074       Float centerToBaseline = (metrics.emAscent - metrics.emDescent) / 2.0f;
4075       GetPositioning(aParams, rect, oneCSSPixel, centerToBaseline, bounds);
4076     }
4077 
4078     // compute the text intercepts that need to be skipped
4079     GetTextIntercepts(textBlob, bounds, intercepts);
4080   }
4081   bool needsSkipInk = intercepts.Length() > 0;
4082 
4083   if (needsSkipInk) {
4084     // Padding between glyph intercepts and the decoration line: we use the
4085     // decoration line thickness, clamped to a minimum of 1px and a maximum
4086     // of 0.2em.
4087     Float padding =
4088         std::min(std::max(aParams.lineSize.height, oneCSSPixel),
4089                  Float(textRun->GetFontGroup()->GetStyle()->size / 5.0));
4090     SkipInk(aFrame, aDrawTarget, aParams, intercepts, padding, rect);
4091   } else {
4092     PaintDecorationLineInternal(aFrame, aDrawTarget, aParams, rect);
4093   }
4094 }
4095 
PaintDecorationLineInternal(nsIFrame * aFrame,DrawTarget & aDrawTarget,const PaintDecorationLineParams & aParams,Rect aRect)4096 void nsCSSRendering::PaintDecorationLineInternal(
4097     nsIFrame* aFrame, DrawTarget& aDrawTarget,
4098     const PaintDecorationLineParams& aParams, Rect aRect) {
4099   Float lineThickness = std::max(NS_round(aParams.lineSize.height), 1.0);
4100 
4101   DeviceColor color = ToDeviceColor(aParams.color);
4102   ColorPattern colorPat(color);
4103   StrokeOptions strokeOptions(lineThickness);
4104   DrawOptions drawOptions;
4105 
4106   Float dash[2];
4107 
4108   AutoPopClips autoPopClips(&aDrawTarget);
4109 
4110   mozilla::layout::TextDrawTarget* textDrawer = nullptr;
4111   if (aDrawTarget.GetBackendType() == BackendType::WEBRENDER_TEXT) {
4112     textDrawer = static_cast<mozilla::layout::TextDrawTarget*>(&aDrawTarget);
4113   }
4114 
4115   switch (aParams.style) {
4116     case NS_STYLE_TEXT_DECORATION_STYLE_SOLID:
4117     case NS_STYLE_TEXT_DECORATION_STYLE_DOUBLE:
4118       break;
4119     case NS_STYLE_TEXT_DECORATION_STYLE_DASHED: {
4120       autoPopClips.PushClipRect(aRect);
4121       Float dashWidth = lineThickness * DOT_LENGTH * DASH_LENGTH;
4122       dash[0] = dashWidth;
4123       dash[1] = dashWidth;
4124       strokeOptions.mDashPattern = dash;
4125       strokeOptions.mDashLength = MOZ_ARRAY_LENGTH(dash);
4126       strokeOptions.mLineCap = CapStyle::BUTT;
4127       aRect = ExpandPaintingRectForDecorationLine(
4128           aFrame, aParams.style, aRect, aParams.icoordInFrame, dashWidth * 2,
4129           aParams.vertical);
4130       // We should continue to draw the last dash even if it is not in the rect.
4131       aRect.width += dashWidth;
4132       break;
4133     }
4134     case NS_STYLE_TEXT_DECORATION_STYLE_DOTTED: {
4135       autoPopClips.PushClipRect(aRect);
4136       Float dashWidth = lineThickness * DOT_LENGTH;
4137       if (lineThickness > 2.0) {
4138         dash[0] = 0.f;
4139         dash[1] = dashWidth * 2.f;
4140         strokeOptions.mLineCap = CapStyle::ROUND;
4141       } else {
4142         dash[0] = dashWidth;
4143         dash[1] = dashWidth;
4144       }
4145       strokeOptions.mDashPattern = dash;
4146       strokeOptions.mDashLength = MOZ_ARRAY_LENGTH(dash);
4147       aRect = ExpandPaintingRectForDecorationLine(
4148           aFrame, aParams.style, aRect, aParams.icoordInFrame, dashWidth * 2,
4149           aParams.vertical);
4150       // We should continue to draw the last dot even if it is not in the rect.
4151       aRect.width += dashWidth;
4152       break;
4153     }
4154     case NS_STYLE_TEXT_DECORATION_STYLE_WAVY:
4155       autoPopClips.PushClipRect(aRect);
4156       if (lineThickness > 2.0) {
4157         drawOptions.mAntialiasMode = AntialiasMode::SUBPIXEL;
4158       } else {
4159         // Don't use anti-aliasing here.  Because looks like lighter color wavy
4160         // line at this case.  And probably, users don't think the
4161         // non-anti-aliased wavy line is not pretty.
4162         drawOptions.mAntialiasMode = AntialiasMode::NONE;
4163       }
4164       break;
4165     default:
4166       NS_ERROR("Invalid style value!");
4167       return;
4168   }
4169 
4170   // The block-direction position should be set to the middle of the line.
4171   if (aParams.vertical) {
4172     aRect.x += lineThickness / 2;
4173   } else {
4174     aRect.y += lineThickness / 2;
4175   }
4176 
4177   switch (aParams.style) {
4178     case NS_STYLE_TEXT_DECORATION_STYLE_SOLID:
4179     case NS_STYLE_TEXT_DECORATION_STYLE_DOTTED:
4180     case NS_STYLE_TEXT_DECORATION_STYLE_DASHED: {
4181       Point p1 = aRect.TopLeft();
4182       Point p2 = aParams.vertical ? aRect.BottomLeft() : aRect.TopRight();
4183       if (textDrawer) {
4184         textDrawer->AppendDecoration(p1, p2, lineThickness, aParams.vertical,
4185                                      color, aParams.style);
4186       } else {
4187         aDrawTarget.StrokeLine(p1, p2, colorPat, strokeOptions, drawOptions);
4188       }
4189       return;
4190     }
4191     case NS_STYLE_TEXT_DECORATION_STYLE_DOUBLE: {
4192       /**
4193        *  We are drawing double line as:
4194        *
4195        * +-------------------------------------------+
4196        * |XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX| ^
4197        * |XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX| | lineThickness
4198        * |XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX| v
4199        * |                                           |
4200        * |                                           |
4201        * |XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX| ^
4202        * |XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX| | lineThickness
4203        * |XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX| v
4204        * +-------------------------------------------+
4205        */
4206       Point p1a = aRect.TopLeft();
4207       Point p2a = aParams.vertical ? aRect.BottomLeft() : aRect.TopRight();
4208 
4209       if (aParams.vertical) {
4210         aRect.width -= lineThickness;
4211       } else {
4212         aRect.height -= lineThickness;
4213       }
4214 
4215       Point p1b = aParams.vertical ? aRect.TopRight() : aRect.BottomLeft();
4216       Point p2b = aRect.BottomRight();
4217 
4218       if (textDrawer) {
4219         textDrawer->AppendDecoration(p1a, p2a, lineThickness, aParams.vertical,
4220                                      color,
4221                                      NS_STYLE_TEXT_DECORATION_STYLE_SOLID);
4222         textDrawer->AppendDecoration(p1b, p2b, lineThickness, aParams.vertical,
4223                                      color,
4224                                      NS_STYLE_TEXT_DECORATION_STYLE_SOLID);
4225       } else {
4226         aDrawTarget.StrokeLine(p1a, p2a, colorPat, strokeOptions, drawOptions);
4227         aDrawTarget.StrokeLine(p1b, p2b, colorPat, strokeOptions, drawOptions);
4228       }
4229       return;
4230     }
4231     case NS_STYLE_TEXT_DECORATION_STYLE_WAVY: {
4232       /**
4233        *  We are drawing wavy line as:
4234        *
4235        *  P: Path, X: Painted pixel
4236        *
4237        *     +---------------------------------------+
4238        *   XX|X            XXXXXX            XXXXXX  |
4239        *   PP|PX          XPPPPPPX          XPPPPPPX |    ^
4240        *   XX|XPX        XPXXXXXXPX        XPXXXXXXPX|    |
4241        *     | XPX      XPX      XPX      XPX      XP|X   |adv
4242        *     |  XPXXXXXXPX        XPXXXXXXPX        X|PX  |
4243        *     |   XPPPPPPX          XPPPPPPX          |XPX v
4244        *     |    XXXXXX            XXXXXX           | XX
4245        *     +---------------------------------------+
4246        *      <---><--->                                ^
4247        *      adv  flatLengthAtVertex                   rightMost
4248        *
4249        *  1. Always starts from top-left of the drawing area, however, we need
4250        *     to draw  the line from outside of the rect.  Because the start
4251        *     point of the line is not good style if we draw from inside it.
4252        *  2. First, draw horizontal line from outside the rect to top-left of
4253        *     the rect;
4254        *  3. Goes down to bottom of the area at 45 degrees.
4255        *  4. Slides to right horizontaly, see |flatLengthAtVertex|.
4256        *  5. Goes up to top of the area at 45 degrees.
4257        *  6. Slides to right horizontaly.
4258        *  7. Repeat from 2 until reached to right-most edge of the area.
4259        *
4260        * In the vertical case, swap horizontal and vertical coordinates and
4261        * directions in the above description.
4262        */
4263 
4264       Float& rectICoord = aParams.vertical ? aRect.y : aRect.x;
4265       Float& rectISize = aParams.vertical ? aRect.height : aRect.width;
4266       const Float rectBSize = aParams.vertical ? aRect.width : aRect.height;
4267 
4268       const Float adv = rectBSize - lineThickness;
4269       const Float flatLengthAtVertex =
4270           std::max((lineThickness - 1.0) * 2.0, 1.0);
4271 
4272       // Align the start of wavy lines to the nearest ancestor block.
4273       const Float cycleLength = 2 * (adv + flatLengthAtVertex);
4274       aRect = ExpandPaintingRectForDecorationLine(
4275           aFrame, aParams.style, aRect, aParams.icoordInFrame, cycleLength,
4276           aParams.vertical);
4277 
4278       if (textDrawer) {
4279         // Undo attempted centering
4280         Float& rectBCoord = aParams.vertical ? aRect.x : aRect.y;
4281         rectBCoord -= lineThickness / 2;
4282 
4283         textDrawer->AppendWavyDecoration(aRect, lineThickness, aParams.vertical,
4284                                          color);
4285         return;
4286       }
4287 
4288       // figure out if we can trim whole cycles from the left and right edges
4289       // of the line, to try and avoid creating an unnecessarily long and
4290       // complex path (but don't do this for webrender, )
4291       const Float dirtyRectICoord =
4292           aParams.vertical ? aParams.dirtyRect.y : aParams.dirtyRect.x;
4293       int32_t skipCycles = floor((dirtyRectICoord - rectICoord) / cycleLength);
4294       if (skipCycles > 0) {
4295         rectICoord += skipCycles * cycleLength;
4296         rectISize -= skipCycles * cycleLength;
4297       }
4298 
4299       rectICoord += lineThickness / 2.0;
4300 
4301       Point pt(aRect.TopLeft());
4302       Float& ptICoord = aParams.vertical ? pt.y : pt.x;
4303       Float& ptBCoord = aParams.vertical ? pt.x : pt.y;
4304       if (aParams.vertical) {
4305         ptBCoord += adv;
4306       }
4307       Float iCoordLimit = ptICoord + rectISize + lineThickness;
4308 
4309       const Float dirtyRectIMost = aParams.vertical ? aParams.dirtyRect.YMost()
4310                                                     : aParams.dirtyRect.XMost();
4311       skipCycles = floor((iCoordLimit - dirtyRectIMost) / cycleLength);
4312       if (skipCycles > 0) {
4313         iCoordLimit -= skipCycles * cycleLength;
4314       }
4315 
4316       RefPtr<PathBuilder> builder = aDrawTarget.CreatePathBuilder();
4317       RefPtr<Path> path;
4318 
4319       ptICoord -= lineThickness;
4320       builder->MoveTo(pt);  // 1
4321 
4322       ptICoord = rectICoord;
4323       builder->LineTo(pt);  // 2
4324 
4325       // In vertical mode, to go "down" relative to the text we need to
4326       // decrease the block coordinate, whereas in horizontal we increase
4327       // it. So the sense of this flag is effectively inverted.
4328       bool goDown = !aParams.vertical;
4329       uint32_t iter = 0;
4330       while (ptICoord < iCoordLimit) {
4331         if (++iter > 1000) {
4332           // stroke the current path and start again, to avoid pathological
4333           // behavior in cairo with huge numbers of path segments
4334           path = builder->Finish();
4335           aDrawTarget.Stroke(path, colorPat, strokeOptions, drawOptions);
4336           builder = aDrawTarget.CreatePathBuilder();
4337           builder->MoveTo(pt);
4338           iter = 0;
4339         }
4340         ptICoord += adv;
4341         ptBCoord += goDown ? adv : -adv;
4342 
4343         builder->LineTo(pt);  // 3 and 5
4344 
4345         ptICoord += flatLengthAtVertex;
4346         builder->LineTo(pt);  // 4 and 6
4347 
4348         goDown = !goDown;
4349       }
4350       path = builder->Finish();
4351       aDrawTarget.Stroke(path, colorPat, strokeOptions, drawOptions);
4352       return;
4353     }
4354     default:
4355       NS_ERROR("Invalid style value!");
4356   }
4357 }
4358 
DecorationLineToPath(const PaintDecorationLineParams & aParams)4359 Rect nsCSSRendering::DecorationLineToPath(
4360     const PaintDecorationLineParams& aParams) {
4361   NS_ASSERTION(aParams.style != NS_STYLE_TEXT_DECORATION_STYLE_NONE,
4362                "aStyle is none");
4363 
4364   Rect path;  // To benefit from RVO, we return this from all return points
4365 
4366   Rect rect = ToRect(GetTextDecorationRectInternal(aParams.pt, aParams));
4367   if (rect.IsEmpty() || !rect.Intersects(aParams.dirtyRect)) {
4368     return path;
4369   }
4370 
4371   if (aParams.decoration != StyleTextDecorationLine::UNDERLINE &&
4372       aParams.decoration != StyleTextDecorationLine::OVERLINE &&
4373       aParams.decoration != StyleTextDecorationLine::LINE_THROUGH) {
4374     MOZ_ASSERT_UNREACHABLE("Invalid text decoration value");
4375     return path;
4376   }
4377 
4378   if (aParams.style != NS_STYLE_TEXT_DECORATION_STYLE_SOLID) {
4379     // For the moment, we support only solid text decorations.
4380     return path;
4381   }
4382 
4383   Float lineThickness = std::max(NS_round(aParams.lineSize.height), 1.0);
4384 
4385   // The block-direction position should be set to the middle of the line.
4386   if (aParams.vertical) {
4387     rect.x += lineThickness / 2;
4388     path = Rect(rect.TopLeft() - Point(lineThickness / 2, 0.0),
4389                 Size(lineThickness, rect.Height()));
4390   } else {
4391     rect.y += lineThickness / 2;
4392     path = Rect(rect.TopLeft() - Point(0.0, lineThickness / 2),
4393                 Size(rect.Width(), lineThickness));
4394   }
4395 
4396   return path;
4397 }
4398 
GetTextDecorationRect(nsPresContext * aPresContext,const DecorationRectParams & aParams)4399 nsRect nsCSSRendering::GetTextDecorationRect(
4400     nsPresContext* aPresContext, const DecorationRectParams& aParams) {
4401   NS_ASSERTION(aPresContext, "aPresContext is null");
4402   NS_ASSERTION(aParams.style != NS_STYLE_TEXT_DECORATION_STYLE_NONE,
4403                "aStyle is none");
4404 
4405   gfxRect rect = GetTextDecorationRectInternal(Point(0, 0), aParams);
4406   // The rect values are already rounded to nearest device pixels.
4407   nsRect r;
4408   r.x = aPresContext->GfxUnitsToAppUnits(rect.X());
4409   r.y = aPresContext->GfxUnitsToAppUnits(rect.Y());
4410   r.width = aPresContext->GfxUnitsToAppUnits(rect.Width());
4411   r.height = aPresContext->GfxUnitsToAppUnits(rect.Height());
4412   return r;
4413 }
4414 
GetTextDecorationRectInternal(const Point & aPt,const DecorationRectParams & aParams)4415 gfxRect nsCSSRendering::GetTextDecorationRectInternal(
4416     const Point& aPt, const DecorationRectParams& aParams) {
4417   NS_ASSERTION(aParams.style <= NS_STYLE_TEXT_DECORATION_STYLE_WAVY,
4418                "Invalid aStyle value");
4419 
4420   if (aParams.style == NS_STYLE_TEXT_DECORATION_STYLE_NONE)
4421     return gfxRect(0, 0, 0, 0);
4422 
4423   bool canLiftUnderline = aParams.descentLimit >= 0.0;
4424 
4425   gfxFloat iCoord = aParams.vertical ? aPt.y : aPt.x;
4426   gfxFloat bCoord = aParams.vertical ? aPt.x : aPt.y;
4427 
4428   // 'left' and 'right' are relative to the line, so for vertical writing modes
4429   // they will actually become top and bottom of the rendered line.
4430   // Similarly, aLineSize.width and .height are actually length and thickness
4431   // of the line, which runs horizontally or vertically according to aVertical.
4432   const gfxFloat left = floor(iCoord + 0.5),
4433                  right = floor(iCoord + aParams.lineSize.width + 0.5);
4434 
4435   // We compute |r| as if for a horizontal text run, and then swap vertical
4436   // and horizontal coordinates at the end if vertical was requested.
4437   gfxRect r(left, 0, right - left, 0);
4438 
4439   gfxFloat lineThickness = NS_round(aParams.lineSize.height);
4440   lineThickness = std::max(lineThickness, 1.0);
4441   gfxFloat defaultLineThickness = NS_round(aParams.defaultLineThickness);
4442   defaultLineThickness = std::max(defaultLineThickness, 1.0);
4443 
4444   gfxFloat ascent = NS_round(aParams.ascent);
4445   gfxFloat descentLimit = floor(aParams.descentLimit);
4446 
4447   gfxFloat suggestedMaxRectHeight =
4448       std::max(std::min(ascent, descentLimit), 1.0);
4449   r.height = lineThickness;
4450   if (aParams.style == NS_STYLE_TEXT_DECORATION_STYLE_DOUBLE) {
4451     /**
4452      *  We will draw double line as:
4453      *
4454      * +-------------------------------------------+
4455      * |XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX| ^
4456      * |XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX| | lineThickness
4457      * |XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX| v
4458      * |                                           | ^
4459      * |                                           | | gap
4460      * |                                           | v
4461      * |XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX| ^
4462      * |XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX| | lineThickness
4463      * |XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX| v
4464      * +-------------------------------------------+
4465      */
4466     gfxFloat gap = NS_round(lineThickness / 2.0);
4467     gap = std::max(gap, 1.0);
4468     r.height = lineThickness * 2.0 + gap;
4469     if (canLiftUnderline) {
4470       if (r.Height() > suggestedMaxRectHeight) {
4471         // Don't shrink the line height, because the thickness has some meaning.
4472         // We can just shrink the gap at this time.
4473         r.height = std::max(suggestedMaxRectHeight, lineThickness * 2.0 + 1.0);
4474       }
4475     }
4476   } else if (aParams.style == NS_STYLE_TEXT_DECORATION_STYLE_WAVY) {
4477     /**
4478      *  We will draw wavy line as:
4479      *
4480      * +-------------------------------------------+
4481      * |XXXXX            XXXXXX            XXXXXX  | ^
4482      * |XXXXXX          XXXXXXXX          XXXXXXXX | | lineThickness
4483      * |XXXXXXX        XXXXXXXXXX        XXXXXXXXXX| v
4484      * |     XXX      XXX      XXX      XXX      XX|
4485      * |      XXXXXXXXXX        XXXXXXXXXX        X|
4486      * |       XXXXXXXX          XXXXXXXX          |
4487      * |        XXXXXX            XXXXXX           |
4488      * +-------------------------------------------+
4489      */
4490     r.height = lineThickness > 2.0 ? lineThickness * 4.0 : lineThickness * 3.0;
4491     if (canLiftUnderline) {
4492       if (r.Height() > suggestedMaxRectHeight) {
4493         // Don't shrink the line height even if there is not enough space,
4494         // because the thickness has some meaning.  E.g., the 1px wavy line and
4495         // 2px wavy line can be used for different meaning in IME selections
4496         // at same time.
4497         r.height = std::max(suggestedMaxRectHeight, lineThickness * 2.0);
4498       }
4499     }
4500   }
4501 
4502   gfxFloat baseline = floor(bCoord + aParams.ascent + 0.5);
4503 
4504   // Calculate adjusted offset based on writing-mode/orientation and thickness
4505   // of decoration line. The input value aParams.offset is the nominal position
4506   // (offset from baseline) where we would draw a single, infinitely-thin line;
4507   // but for a wavy or double line, we'll need to move the bounding rect of the
4508   // decoration outwards from the baseline so that an underline remains below
4509   // the glyphs, and an overline above them, despite the increased block-dir
4510   // extent of the decoration.
4511   //
4512   // So adjustments by r.Height() are used to make the wider line styles (wavy
4513   // and double) "grow" in the appropriate direction compared to the basic
4514   // single line.
4515   //
4516   // Note that at this point, the decoration rect is being calculated in line-
4517   // relative coordinates, where 'x' is line-rightwards, and 'y' is line-
4518   // upwards. We'll swap them to be physical coords at the end.
4519   gfxFloat offset = 0.0;
4520 
4521   if (aParams.decoration == StyleTextDecorationLine::UNDERLINE) {
4522     offset = aParams.offset;
4523     if (canLiftUnderline) {
4524       if (descentLimit < -offset + r.Height()) {
4525         // If we can ignore the offset and the decoration line is overflowing,
4526         // we should align the bottom edge of the decoration line rect if it's
4527         // possible.  Otherwise, we should lift up the top edge of the rect as
4528         // far as possible.
4529         gfxFloat offsetBottomAligned = -descentLimit + r.Height();
4530         gfxFloat offsetTopAligned = 0.0;
4531         offset = std::min(offsetBottomAligned, offsetTopAligned);
4532       }
4533     }
4534   } else if (aParams.decoration == StyleTextDecorationLine::OVERLINE) {
4535     // For overline, we adjust the offset by defaultlineThickness (the default
4536     // thickness of a single decoration line) because empirically it looks
4537     // better to draw the overline just inside rather than outside the font's
4538     // ascent, which is what nsTextFrame passes as aParams.offset (as fonts
4539     // don't provide an explicit overline-offset).
4540     offset = aParams.offset - defaultLineThickness + r.Height();
4541   } else if (aParams.decoration == StyleTextDecorationLine::LINE_THROUGH) {
4542     // To maintain a consistent mid-point for line-through decorations,
4543     // we adjust the offset by half of the decoration rect's height.
4544     gfxFloat extra = floor(r.Height() / 2.0 + 0.5);
4545     extra = std::max(extra, lineThickness);
4546     // computes offset for when user specifies a decoration width since
4547     // aParams.offset is derived from the font metric's line height
4548     gfxFloat decorationThicknessOffset =
4549         (lineThickness - defaultLineThickness) / 2.0;
4550     offset = aParams.offset - lineThickness + extra + decorationThicknessOffset;
4551   } else {
4552     MOZ_ASSERT_UNREACHABLE("Invalid text decoration value");
4553   }
4554 
4555   // Convert line-relative coordinate system (x = line-right, y = line-up)
4556   // to physical coords, and move the decoration rect to the calculated
4557   // offset from baseline.
4558   if (aParams.vertical) {
4559     std::swap(r.x, r.y);
4560     std::swap(r.width, r.height);
4561     // line-upwards in vertical mode = physical-right, so we /add/ offset
4562     // to baseline. Except in sideways-lr mode, where line-upwards will be
4563     // physical leftwards.
4564     if (aParams.sidewaysLeft) {
4565       r.x = baseline - floor(offset + 0.5);
4566     } else {
4567       r.x = baseline + floor(offset - r.Width() + 0.5);
4568     }
4569   } else {
4570     // line-upwards in horizontal mode = physical-up, but our physical coord
4571     // system works downwards, so we /subtract/ offset from baseline.
4572     r.y = baseline - floor(offset + 0.5);
4573   }
4574 
4575   return r;
4576 }
4577 
4578 #define MAX_BLUR_RADIUS 300
4579 #define MAX_SPREAD_RADIUS 50
4580 
ComputeBlurStdDev(nscoord aBlurRadius,int32_t aAppUnitsPerDevPixel,gfxFloat aScaleX,gfxFloat aScaleY)4581 static inline gfxPoint ComputeBlurStdDev(nscoord aBlurRadius,
4582                                          int32_t aAppUnitsPerDevPixel,
4583                                          gfxFloat aScaleX, gfxFloat aScaleY) {
4584   // http://dev.w3.org/csswg/css3-background/#box-shadow says that the
4585   // standard deviation of the blur should be half the given blur value.
4586   gfxFloat blurStdDev = gfxFloat(aBlurRadius) / gfxFloat(aAppUnitsPerDevPixel);
4587 
4588   return gfxPoint(
4589       std::min((blurStdDev * aScaleX), gfxFloat(MAX_BLUR_RADIUS)) / 2.0,
4590       std::min((blurStdDev * aScaleY), gfxFloat(MAX_BLUR_RADIUS)) / 2.0);
4591 }
4592 
ComputeBlurRadius(nscoord aBlurRadius,int32_t aAppUnitsPerDevPixel,gfxFloat aScaleX=1.0,gfxFloat aScaleY=1.0)4593 static inline IntSize ComputeBlurRadius(nscoord aBlurRadius,
4594                                         int32_t aAppUnitsPerDevPixel,
4595                                         gfxFloat aScaleX = 1.0,
4596                                         gfxFloat aScaleY = 1.0) {
4597   gfxPoint scaledBlurStdDev =
4598       ComputeBlurStdDev(aBlurRadius, aAppUnitsPerDevPixel, aScaleX, aScaleY);
4599   return gfxAlphaBoxBlur::CalculateBlurRadius(scaledBlurStdDev);
4600 }
4601 
4602 // -----
4603 // nsContextBoxBlur
4604 // -----
Init(const nsRect & aRect,nscoord aSpreadRadius,nscoord aBlurRadius,int32_t aAppUnitsPerDevPixel,gfxContext * aDestinationCtx,const nsRect & aDirtyRect,const gfxRect * aSkipRect,uint32_t aFlags)4605 gfxContext* nsContextBoxBlur::Init(const nsRect& aRect, nscoord aSpreadRadius,
4606                                    nscoord aBlurRadius,
4607                                    int32_t aAppUnitsPerDevPixel,
4608                                    gfxContext* aDestinationCtx,
4609                                    const nsRect& aDirtyRect,
4610                                    const gfxRect* aSkipRect, uint32_t aFlags) {
4611   if (aRect.IsEmpty()) {
4612     mContext = nullptr;
4613     return nullptr;
4614   }
4615 
4616   IntSize blurRadius;
4617   IntSize spreadRadius;
4618   GetBlurAndSpreadRadius(aDestinationCtx->GetDrawTarget(), aAppUnitsPerDevPixel,
4619                          aBlurRadius, aSpreadRadius, blurRadius, spreadRadius);
4620 
4621   mDestinationCtx = aDestinationCtx;
4622 
4623   // If not blurring, draw directly onto the destination device
4624   if (blurRadius.width <= 0 && blurRadius.height <= 0 &&
4625       spreadRadius.width <= 0 && spreadRadius.height <= 0 &&
4626       !(aFlags & FORCE_MASK)) {
4627     mContext = aDestinationCtx;
4628     return mContext;
4629   }
4630 
4631   // Convert from app units to device pixels
4632   gfxRect rect = nsLayoutUtils::RectToGfxRect(aRect, aAppUnitsPerDevPixel);
4633 
4634   gfxRect dirtyRect =
4635       nsLayoutUtils::RectToGfxRect(aDirtyRect, aAppUnitsPerDevPixel);
4636   dirtyRect.RoundOut();
4637 
4638   gfxMatrix transform = aDestinationCtx->CurrentMatrixDouble();
4639   rect = transform.TransformBounds(rect);
4640 
4641   mPreTransformed = !transform.IsIdentity();
4642 
4643   // Create the temporary surface for blurring
4644   dirtyRect = transform.TransformBounds(dirtyRect);
4645   bool useHardwareAccel = !(aFlags & DISABLE_HARDWARE_ACCELERATION_BLUR);
4646   if (aSkipRect) {
4647     gfxRect skipRect = transform.TransformBounds(*aSkipRect);
4648     mContext =
4649         mAlphaBoxBlur.Init(aDestinationCtx, rect, spreadRadius, blurRadius,
4650                            &dirtyRect, &skipRect, useHardwareAccel);
4651   } else {
4652     mContext =
4653         mAlphaBoxBlur.Init(aDestinationCtx, rect, spreadRadius, blurRadius,
4654                            &dirtyRect, nullptr, useHardwareAccel);
4655   }
4656 
4657   if (mContext) {
4658     // we don't need to blur if skipRect is equal to rect
4659     // and mContext will be nullptr
4660     mContext->Multiply(transform);
4661   }
4662   return mContext;
4663 }
4664 
DoPaint()4665 void nsContextBoxBlur::DoPaint() {
4666   if (mContext == mDestinationCtx) {
4667     return;
4668   }
4669 
4670   gfxContextMatrixAutoSaveRestore saveMatrix(mDestinationCtx);
4671 
4672   if (mPreTransformed) {
4673     mDestinationCtx->SetMatrix(Matrix());
4674   }
4675 
4676   mAlphaBoxBlur.Paint(mDestinationCtx);
4677 }
4678 
GetContext()4679 gfxContext* nsContextBoxBlur::GetContext() { return mContext; }
4680 
4681 /* static */
GetBlurRadiusMargin(nscoord aBlurRadius,int32_t aAppUnitsPerDevPixel)4682 nsMargin nsContextBoxBlur::GetBlurRadiusMargin(nscoord aBlurRadius,
4683                                                int32_t aAppUnitsPerDevPixel) {
4684   IntSize blurRadius = ComputeBlurRadius(aBlurRadius, aAppUnitsPerDevPixel);
4685 
4686   nsMargin result;
4687   result.top = result.bottom = blurRadius.height * aAppUnitsPerDevPixel;
4688   result.left = result.right = blurRadius.width * aAppUnitsPerDevPixel;
4689   return result;
4690 }
4691 
4692 /* static */
BlurRectangle(gfxContext * aDestinationCtx,const nsRect & aRect,int32_t aAppUnitsPerDevPixel,RectCornerRadii * aCornerRadii,nscoord aBlurRadius,const sRGBColor & aShadowColor,const nsRect & aDirtyRect,const gfxRect & aSkipRect)4693 void nsContextBoxBlur::BlurRectangle(
4694     gfxContext* aDestinationCtx, const nsRect& aRect,
4695     int32_t aAppUnitsPerDevPixel, RectCornerRadii* aCornerRadii,
4696     nscoord aBlurRadius, const sRGBColor& aShadowColor,
4697     const nsRect& aDirtyRect, const gfxRect& aSkipRect) {
4698   DrawTarget& aDestDrawTarget = *aDestinationCtx->GetDrawTarget();
4699 
4700   if (aRect.IsEmpty()) {
4701     return;
4702   }
4703 
4704   Rect shadowGfxRect = NSRectToRect(aRect, aAppUnitsPerDevPixel);
4705 
4706   if (aBlurRadius <= 0) {
4707     ColorPattern color(ToDeviceColor(aShadowColor));
4708     if (aCornerRadii) {
4709       RefPtr<Path> roundedRect =
4710           MakePathForRoundedRect(aDestDrawTarget, shadowGfxRect, *aCornerRadii);
4711       aDestDrawTarget.Fill(roundedRect, color);
4712     } else {
4713       aDestDrawTarget.FillRect(shadowGfxRect, color);
4714     }
4715     return;
4716   }
4717 
4718   gfxFloat scaleX = 1;
4719   gfxFloat scaleY = 1;
4720 
4721   // Do blurs in device space when possible.
4722   // Chrome/Skia always does the blurs in device space
4723   // and will sometimes get incorrect results (e.g. rotated blurs)
4724   gfxMatrix transform = aDestinationCtx->CurrentMatrixDouble();
4725   // XXX: we could probably handle negative scales but for now it's easier just
4726   // to fallback
4727   if (!transform.HasNonAxisAlignedTransform() && transform._11 > 0.0 &&
4728       transform._22 > 0.0) {
4729     scaleX = transform._11;
4730     scaleY = transform._22;
4731     aDestinationCtx->SetMatrix(Matrix());
4732   } else {
4733     transform = gfxMatrix();
4734   }
4735 
4736   gfxPoint blurStdDev =
4737       ComputeBlurStdDev(aBlurRadius, aAppUnitsPerDevPixel, scaleX, scaleY);
4738 
4739   gfxRect dirtyRect =
4740       nsLayoutUtils::RectToGfxRect(aDirtyRect, aAppUnitsPerDevPixel);
4741   dirtyRect.RoundOut();
4742 
4743   gfxRect shadowThebesRect =
4744       transform.TransformBounds(ThebesRect(shadowGfxRect));
4745   dirtyRect = transform.TransformBounds(dirtyRect);
4746   gfxRect skipRect = transform.TransformBounds(aSkipRect);
4747 
4748   if (aCornerRadii) {
4749     aCornerRadii->Scale(scaleX, scaleY);
4750   }
4751 
4752   gfxAlphaBoxBlur::BlurRectangle(aDestinationCtx, shadowThebesRect,
4753                                  aCornerRadii, blurStdDev, aShadowColor,
4754                                  dirtyRect, skipRect);
4755 }
4756 
4757 /* static */
GetBlurAndSpreadRadius(DrawTarget * aDestDrawTarget,int32_t aAppUnitsPerDevPixel,nscoord aBlurRadius,nscoord aSpreadRadius,IntSize & aOutBlurRadius,IntSize & aOutSpreadRadius,bool aConstrainSpreadRadius)4758 void nsContextBoxBlur::GetBlurAndSpreadRadius(
4759     DrawTarget* aDestDrawTarget, int32_t aAppUnitsPerDevPixel,
4760     nscoord aBlurRadius, nscoord aSpreadRadius, IntSize& aOutBlurRadius,
4761     IntSize& aOutSpreadRadius, bool aConstrainSpreadRadius) {
4762   // Do blurs in device space when possible.
4763   // Chrome/Skia always does the blurs in device space
4764   // and will sometimes get incorrect results (e.g. rotated blurs)
4765   Matrix transform = aDestDrawTarget->GetTransform();
4766   // XXX: we could probably handle negative scales but for now it's easier just
4767   // to fallback
4768   gfxFloat scaleX, scaleY;
4769   if (transform.HasNonAxisAlignedTransform() || transform._11 <= 0.0 ||
4770       transform._22 <= 0.0) {
4771     scaleX = 1;
4772     scaleY = 1;
4773   } else {
4774     scaleX = transform._11;
4775     scaleY = transform._22;
4776   }
4777 
4778   // compute a large or smaller blur radius
4779   aOutBlurRadius =
4780       ComputeBlurRadius(aBlurRadius, aAppUnitsPerDevPixel, scaleX, scaleY);
4781   aOutSpreadRadius =
4782       IntSize(int32_t(aSpreadRadius * scaleX / aAppUnitsPerDevPixel),
4783               int32_t(aSpreadRadius * scaleY / aAppUnitsPerDevPixel));
4784 
4785   if (aConstrainSpreadRadius) {
4786     aOutSpreadRadius.width =
4787         std::min(aOutSpreadRadius.width, int32_t(MAX_SPREAD_RADIUS));
4788     aOutSpreadRadius.height =
4789         std::min(aOutSpreadRadius.height, int32_t(MAX_SPREAD_RADIUS));
4790   }
4791 }
4792 
4793 /* static */
InsetBoxBlur(gfxContext * aDestinationCtx,Rect aDestinationRect,Rect aShadowClipRect,sRGBColor & aShadowColor,nscoord aBlurRadiusAppUnits,nscoord aSpreadDistanceAppUnits,int32_t aAppUnitsPerDevPixel,bool aHasBorderRadius,RectCornerRadii & aInnerClipRectRadii,Rect aSkipRect,Point aShadowOffset)4794 bool nsContextBoxBlur::InsetBoxBlur(
4795     gfxContext* aDestinationCtx, Rect aDestinationRect, Rect aShadowClipRect,
4796     sRGBColor& aShadowColor, nscoord aBlurRadiusAppUnits,
4797     nscoord aSpreadDistanceAppUnits, int32_t aAppUnitsPerDevPixel,
4798     bool aHasBorderRadius, RectCornerRadii& aInnerClipRectRadii, Rect aSkipRect,
4799     Point aShadowOffset) {
4800   if (aDestinationRect.IsEmpty()) {
4801     mContext = nullptr;
4802     return false;
4803   }
4804 
4805   gfxContextAutoSaveRestore autoRestore(aDestinationCtx);
4806 
4807   IntSize blurRadius;
4808   IntSize spreadRadius;
4809   // Convert the blur and spread radius to device pixels
4810   bool constrainSpreadRadius = false;
4811   GetBlurAndSpreadRadius(aDestinationCtx->GetDrawTarget(), aAppUnitsPerDevPixel,
4812                          aBlurRadiusAppUnits, aSpreadDistanceAppUnits,
4813                          blurRadius, spreadRadius, constrainSpreadRadius);
4814 
4815   // The blur and spread radius are scaled already, so scale all
4816   // input data to the blur. This way, we don't have to scale the min
4817   // inset blur to the invert of the dest context, then rescale it back
4818   // when we draw to the destination surface.
4819   gfx::Size scale = aDestinationCtx->CurrentMatrix().ScaleFactors();
4820   Matrix transform = aDestinationCtx->CurrentMatrix();
4821 
4822   // XXX: we could probably handle negative scales but for now it's easier just
4823   // to fallback
4824   if (!transform.HasNonAxisAlignedTransform() && transform._11 > 0.0 &&
4825       transform._22 > 0.0) {
4826     // If we don't have a rotation, we're pre-transforming all the rects.
4827     aDestinationCtx->SetMatrix(Matrix());
4828   } else {
4829     // Don't touch anything, we have a rotation.
4830     transform = Matrix();
4831   }
4832 
4833   Rect transformedDestRect = transform.TransformBounds(aDestinationRect);
4834   Rect transformedShadowClipRect = transform.TransformBounds(aShadowClipRect);
4835   Rect transformedSkipRect = transform.TransformBounds(aSkipRect);
4836 
4837   transformedDestRect.Round();
4838   transformedShadowClipRect.Round();
4839   transformedSkipRect.RoundIn();
4840 
4841   for (size_t i = 0; i < 4; i++) {
4842     aInnerClipRectRadii[i].width =
4843         std::floor(scale.width * aInnerClipRectRadii[i].width);
4844     aInnerClipRectRadii[i].height =
4845         std::floor(scale.height * aInnerClipRectRadii[i].height);
4846   }
4847 
4848   mAlphaBoxBlur.BlurInsetBox(aDestinationCtx, transformedDestRect,
4849                              transformedShadowClipRect, blurRadius,
4850                              aShadowColor,
4851                              aHasBorderRadius ? &aInnerClipRectRadii : nullptr,
4852                              transformedSkipRect, aShadowOffset);
4853   return true;
4854 }
4855