1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 // vim:cindent:ts=2:et:sw=2:
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 "mozilla/ArrayUtils.h"
13 #include "mozilla/DebugOnly.h"
14 #include "mozilla/gfx/2D.h"
15 #include "mozilla/gfx/Helpers.h"
16 #include "mozilla/gfx/PathHelpers.h"
17 #include "mozilla/HashFunctions.h"
18 #include "mozilla/MathAlgorithms.h"
19 
20 #include "BorderConsts.h"
21 #include "nsISVGChildFrame.h"
22 #include "nsStyleConsts.h"
23 #include "nsPresContext.h"
24 #include "nsIFrame.h"
25 #include "nsPoint.h"
26 #include "nsRect.h"
27 #include "nsIPresShell.h"
28 #include "nsFrameManager.h"
29 #include "nsStyleContext.h"
30 #include "nsGkAtoms.h"
31 #include "nsCSSAnonBoxes.h"
32 #include "nsIContent.h"
33 #include "nsIDocumentInlines.h"
34 #include "nsIScrollableFrame.h"
35 #include "imgIRequest.h"
36 #include "imgIContainer.h"
37 #include "ImageOps.h"
38 #include "nsCSSRendering.h"
39 #include "nsCSSColorUtils.h"
40 #include "nsITheme.h"
41 #include "nsThemeConstants.h"
42 #include "nsLayoutUtils.h"
43 #include "nsBlockFrame.h"
44 #include "gfxContext.h"
45 #include "nsRenderingContext.h"
46 #include "nsStyleStructInlines.h"
47 #include "nsCSSFrameConstructor.h"
48 #include "nsCSSProps.h"
49 #include "nsContentUtils.h"
50 #include "nsSVGEffects.h"
51 #include "nsSVGIntegrationUtils.h"
52 #include "gfxDrawable.h"
53 #include "GeckoProfiler.h"
54 #include "nsCSSRenderingBorders.h"
55 #include "mozilla/css/ImageLoader.h"
56 #include "ImageContainer.h"
57 #include "mozilla/Telemetry.h"
58 #include "gfxUtils.h"
59 #include "gfxGradientCache.h"
60 #include "nsInlineFrame.h"
61 #include "nsRubyTextContainerFrame.h"
62 #include <algorithm>
63 
64 using namespace mozilla;
65 using namespace mozilla::css;
66 using namespace mozilla::gfx;
67 using namespace mozilla::image;
68 using mozilla::CSSSizeOrRatio;
69 
70 static int gFrameTreeLockCount = 0;
71 
72 // To avoid storing this data on nsInlineFrame (bloat) and to avoid
73 // recalculating this for each frame in a continuation (perf), hold
74 // a cache of various coordinate information that we need in order
75 // to paint inline backgrounds.
76 struct InlineBackgroundData
77 {
InlineBackgroundDataInlineBackgroundData78   InlineBackgroundData()
79       : mFrame(nullptr), mLineContainer(nullptr)
80   {
81   }
82 
~InlineBackgroundDataInlineBackgroundData83   ~InlineBackgroundData()
84   {
85   }
86 
ResetInlineBackgroundData87   void Reset()
88   {
89     mBoundingBox.SetRect(0,0,0,0);
90     mContinuationPoint = mLineContinuationPoint = mUnbrokenMeasure = 0;
91     mFrame = mLineContainer = nullptr;
92     mPIStartBorderData.Reset();
93   }
94 
95   /**
96    * Return a continuous rect for (an inline) aFrame relative to the
97    * continuation that draws the left-most part of the background.
98    * This is used when painting backgrounds.
99    */
GetContinuousRectInlineBackgroundData100   nsRect GetContinuousRect(nsIFrame* aFrame)
101   {
102     MOZ_ASSERT(static_cast<nsInlineFrame*>(do_QueryFrame(aFrame)));
103 
104     SetFrame(aFrame);
105 
106     nscoord pos; // an x coordinate if writing-mode is horizontal;
107                  // y coordinate if vertical
108     if (mBidiEnabled) {
109       pos = mLineContinuationPoint;
110 
111       // Scan continuations on the same line as aFrame and accumulate the widths
112       // of frames that are to the left (if this is an LTR block) or right
113       // (if it's RTL) of the current one.
114       bool isRtlBlock = (mLineContainer->StyleVisibility()->mDirection ==
115                            NS_STYLE_DIRECTION_RTL);
116       nscoord curOffset = mVertical ? aFrame->GetOffsetTo(mLineContainer).y
117                                     : aFrame->GetOffsetTo(mLineContainer).x;
118 
119       // If the continuation is fluid we know inlineFrame is not on the same line.
120       // If it's not fluid, we need to test further to be sure.
121       nsIFrame* inlineFrame = aFrame->GetPrevContinuation();
122       while (inlineFrame && !inlineFrame->GetNextInFlow() &&
123              AreOnSameLine(aFrame, inlineFrame)) {
124         nscoord frameOffset = mVertical
125           ? inlineFrame->GetOffsetTo(mLineContainer).y
126           : inlineFrame->GetOffsetTo(mLineContainer).x;
127         if (isRtlBlock == (frameOffset >= curOffset)) {
128           pos += mVertical
129                ? inlineFrame->GetSize().height
130                : inlineFrame->GetSize().width;
131         }
132         inlineFrame = inlineFrame->GetPrevContinuation();
133       }
134 
135       inlineFrame = aFrame->GetNextContinuation();
136       while (inlineFrame && !inlineFrame->GetPrevInFlow() &&
137              AreOnSameLine(aFrame, inlineFrame)) {
138         nscoord frameOffset = mVertical
139           ? inlineFrame->GetOffsetTo(mLineContainer).y
140           : inlineFrame->GetOffsetTo(mLineContainer).x;
141         if (isRtlBlock == (frameOffset >= curOffset)) {
142           pos += mVertical
143                  ? inlineFrame->GetSize().height
144                  : inlineFrame->GetSize().width;
145         }
146         inlineFrame = inlineFrame->GetNextContinuation();
147       }
148       if (isRtlBlock) {
149         // aFrame itself is also to the right of its left edge, so add its width.
150         pos += mVertical ? aFrame->GetSize().height : aFrame->GetSize().width;
151         // pos is now the distance from the left [top] edge of aFrame to the right [bottom] edge
152         // of the unbroken content. Change it to indicate the distance from the
153         // left [top] edge of the unbroken content to the left [top] edge of aFrame.
154         pos = mUnbrokenMeasure - pos;
155       }
156     } else {
157       pos = mContinuationPoint;
158     }
159 
160     // Assume background-origin: border and return a rect with offsets
161     // relative to (0,0).  If we have a different background-origin,
162     // then our rect should be deflated appropriately by our caller.
163     return mVertical
164       ? nsRect(0, -pos, mFrame->GetSize().width, mUnbrokenMeasure)
165       : nsRect(-pos, 0, mUnbrokenMeasure, mFrame->GetSize().height);
166   }
167 
168   /**
169    * Return a continuous rect for (an inline) aFrame relative to the
170    * continuation that should draw the left[top]-border.  This is used when painting
171    * borders and clipping backgrounds.  This may NOT be the same continuous rect
172    * as for drawing backgrounds; the continuation with the left[top]-border might be
173    * somewhere in the middle of that rect (e.g. BIDI), in those cases we need
174    * the reverse background order starting at the left[top]-border continuation.
175    */
GetBorderContinuousRectInlineBackgroundData176   nsRect GetBorderContinuousRect(nsIFrame* aFrame, nsRect aBorderArea)
177   {
178     // Calling GetContinuousRect(aFrame) here may lead to Reset/Init which
179     // resets our mPIStartBorderData so we save it ...
180     PhysicalInlineStartBorderData saved(mPIStartBorderData);
181     nsRect joinedBorderArea = GetContinuousRect(aFrame);
182     if (!saved.mIsValid || saved.mFrame != mPIStartBorderData.mFrame) {
183       if (aFrame == mPIStartBorderData.mFrame) {
184         if (mVertical) {
185           mPIStartBorderData.SetCoord(joinedBorderArea.y);
186         } else {
187           mPIStartBorderData.SetCoord(joinedBorderArea.x);
188         }
189       } else if (mPIStartBorderData.mFrame) {
190         if (mVertical) {
191           mPIStartBorderData.SetCoord(GetContinuousRect(mPIStartBorderData.mFrame).y);
192         } else {
193           mPIStartBorderData.SetCoord(GetContinuousRect(mPIStartBorderData.mFrame).x);
194         }
195       }
196     } else {
197       // ... and restore it when possible.
198       mPIStartBorderData.mCoord = saved.mCoord;
199     }
200     if (mVertical) {
201       if (joinedBorderArea.y > mPIStartBorderData.mCoord) {
202         joinedBorderArea.y =
203           -(mUnbrokenMeasure + joinedBorderArea.y - aBorderArea.height);
204       } else {
205         joinedBorderArea.y -= mPIStartBorderData.mCoord;
206       }
207     } else {
208       if (joinedBorderArea.x > mPIStartBorderData.mCoord) {
209         joinedBorderArea.x =
210           -(mUnbrokenMeasure + joinedBorderArea.x - aBorderArea.width);
211       } else {
212         joinedBorderArea.x -= mPIStartBorderData.mCoord;
213       }
214     }
215     return joinedBorderArea;
216   }
217 
GetBoundingRectInlineBackgroundData218   nsRect GetBoundingRect(nsIFrame* aFrame)
219   {
220     SetFrame(aFrame);
221 
222     // Move the offsets relative to (0,0) which puts the bounding box into
223     // our coordinate system rather than our parent's.  We do this by
224     // moving it the back distance from us to the bounding box.
225     // This also assumes background-origin: border, so our caller will
226     // need to deflate us if needed.
227     nsRect boundingBox(mBoundingBox);
228     nsPoint point = mFrame->GetPosition();
229     boundingBox.MoveBy(-point.x, -point.y);
230 
231     return boundingBox;
232   }
233 
234 protected:
235   // This is a coordinate on the inline axis, but is not a true logical inline-
236   // coord because it is always measured from left to right (if horizontal) or
237   // from top to bottom (if vertical), ignoring any bidi RTL directionality.
238   // We'll call this "physical inline start", or PIStart for short.
239   struct PhysicalInlineStartBorderData {
240     nsIFrame* mFrame;   // the continuation that may have a left-border
241     nscoord   mCoord;   // cached GetContinuousRect(mFrame).x or .y
242     bool      mIsValid; // true if mCoord is valid
ResetInlineBackgroundData::PhysicalInlineStartBorderData243     void Reset() { mFrame = nullptr; mIsValid = false; }
SetCoordInlineBackgroundData::PhysicalInlineStartBorderData244     void SetCoord(nscoord aCoord) { mCoord = aCoord; mIsValid = true; }
245   };
246 
247   nsIFrame*      mFrame;
248   nsIFrame*      mLineContainer;
249   nsRect         mBoundingBox;
250   nscoord        mContinuationPoint;
251   nscoord        mUnbrokenMeasure;
252   nscoord        mLineContinuationPoint;
253   PhysicalInlineStartBorderData mPIStartBorderData;
254   bool           mBidiEnabled;
255   bool           mVertical;
256 
SetFrameInlineBackgroundData257   void SetFrame(nsIFrame* aFrame)
258   {
259     NS_PRECONDITION(aFrame, "Need a frame");
260     NS_ASSERTION(gFrameTreeLockCount > 0,
261                  "Can't call this when frame tree is not locked");
262 
263     if (aFrame == mFrame) {
264       return;
265     }
266 
267     nsIFrame *prevContinuation = GetPrevContinuation(aFrame);
268 
269     if (!prevContinuation || mFrame != prevContinuation) {
270       // Ok, we've got the wrong frame.  We have to start from scratch.
271       Reset();
272       Init(aFrame);
273       return;
274     }
275 
276     // Get our last frame's size and add its width to our continuation
277     // point before we cache the new frame.
278     mContinuationPoint += mVertical ? mFrame->GetSize().height
279                                     : mFrame->GetSize().width;
280 
281     // If this a new line, update mLineContinuationPoint.
282     if (mBidiEnabled &&
283         (aFrame->GetPrevInFlow() || !AreOnSameLine(mFrame, aFrame))) {
284        mLineContinuationPoint = mContinuationPoint;
285     }
286 
287     mFrame = aFrame;
288   }
289 
GetPrevContinuationInlineBackgroundData290   nsIFrame* GetPrevContinuation(nsIFrame* aFrame)
291   {
292     nsIFrame* prevCont = aFrame->GetPrevContinuation();
293     if (!prevCont &&
294         (aFrame->GetStateBits() & NS_FRAME_PART_OF_IBSPLIT)) {
295       nsIFrame* block =
296         aFrame->Properties().Get(nsIFrame::IBSplitPrevSibling());
297       if (block) {
298         // The {ib} properties are only stored on first continuations
299         NS_ASSERTION(!block->GetPrevContinuation(),
300                      "Incorrect value for IBSplitPrevSibling");
301         prevCont =
302           block->Properties().Get(nsIFrame::IBSplitPrevSibling());
303         NS_ASSERTION(prevCont, "How did that happen?");
304       }
305     }
306     return prevCont;
307   }
308 
GetNextContinuationInlineBackgroundData309   nsIFrame* GetNextContinuation(nsIFrame* aFrame)
310   {
311     nsIFrame* nextCont = aFrame->GetNextContinuation();
312     if (!nextCont &&
313         (aFrame->GetStateBits() & NS_FRAME_PART_OF_IBSPLIT)) {
314       // The {ib} properties are only stored on first continuations
315       aFrame = aFrame->FirstContinuation();
316       nsIFrame* block = aFrame->Properties().Get(nsIFrame::IBSplitSibling());
317       if (block) {
318         nextCont = block->Properties().Get(nsIFrame::IBSplitSibling());
319         NS_ASSERTION(nextCont, "How did that happen?");
320       }
321     }
322     return nextCont;
323   }
324 
InitInlineBackgroundData325   void Init(nsIFrame* aFrame)
326   {
327     mPIStartBorderData.Reset();
328     mBidiEnabled = aFrame->PresContext()->BidiEnabled();
329     if (mBidiEnabled) {
330       // Find the line container frame
331       mLineContainer = aFrame;
332       while (mLineContainer &&
333              mLineContainer->IsFrameOfType(nsIFrame::eLineParticipant)) {
334         mLineContainer = mLineContainer->GetParent();
335       }
336 
337       MOZ_ASSERT(mLineContainer, "Cannot find line containing frame.");
338       MOZ_ASSERT(mLineContainer != aFrame, "line container frame "
339                  "should be an ancestor of the target frame.");
340     }
341 
342     mVertical = aFrame->GetWritingMode().IsVertical();
343 
344     // Start with the previous flow frame as our continuation point
345     // is the total of the widths of the previous frames.
346     nsIFrame* inlineFrame = GetPrevContinuation(aFrame);
347     while (inlineFrame) {
348       if (!mPIStartBorderData.mFrame &&
349           !(mVertical ? inlineFrame->GetSkipSides().Top()
350                       : inlineFrame->GetSkipSides().Left())) {
351         mPIStartBorderData.mFrame = inlineFrame;
352       }
353       nsRect rect = inlineFrame->GetRect();
354       mContinuationPoint += mVertical ? rect.height : rect.width;
355       if (mBidiEnabled && !AreOnSameLine(aFrame, inlineFrame)) {
356         mLineContinuationPoint += mVertical ? rect.height : rect.width;
357       }
358       mUnbrokenMeasure += mVertical ? rect.height : rect.width;
359       mBoundingBox.UnionRect(mBoundingBox, rect);
360       inlineFrame = GetPrevContinuation(inlineFrame);
361     }
362 
363     // Next add this frame and subsequent frames to the bounding box and
364     // unbroken width.
365     inlineFrame = aFrame;
366     while (inlineFrame) {
367       if (!mPIStartBorderData.mFrame &&
368           !(mVertical ? inlineFrame->GetSkipSides().Top()
369                       : inlineFrame->GetSkipSides().Left())) {
370         mPIStartBorderData.mFrame = inlineFrame;
371       }
372       nsRect rect = inlineFrame->GetRect();
373       mUnbrokenMeasure += mVertical ? rect.height : rect.width;
374       mBoundingBox.UnionRect(mBoundingBox, rect);
375       inlineFrame = GetNextContinuation(inlineFrame);
376     }
377 
378     mFrame = aFrame;
379   }
380 
AreOnSameLineInlineBackgroundData381   bool AreOnSameLine(nsIFrame* aFrame1, nsIFrame* aFrame2) {
382     if (nsBlockFrame* blockFrame = do_QueryFrame(mLineContainer)) {
383       bool isValid1, isValid2;
384       nsBlockInFlowLineIterator it1(blockFrame, aFrame1, &isValid1);
385       nsBlockInFlowLineIterator it2(blockFrame, aFrame2, &isValid2);
386       return isValid1 && isValid2 &&
387         // Make sure aFrame1 and aFrame2 are in the same continuation of
388         // blockFrame.
389         it1.GetContainer() == it2.GetContainer() &&
390         // And on the same line in it
391         it1.GetLine() == it2.GetLine();
392     }
393     if (nsRubyTextContainerFrame* rtcFrame = do_QueryFrame(mLineContainer)) {
394       nsBlockFrame* block = nsLayoutUtils::FindNearestBlockAncestor(rtcFrame);
395       // Ruby text container can only hold one line of text, so if they
396       // are in the same continuation, they are in the same line. Since
397       // ruby text containers are bidi isolate, they are never split for
398       // bidi reordering, which means being in different continuation
399       // indicates being in different lines.
400       for (nsIFrame* frame = rtcFrame->FirstContinuation();
401            frame; frame = frame->GetNextContinuation()) {
402         bool isDescendant1 =
403           nsLayoutUtils::IsProperAncestorFrame(frame, aFrame1, block);
404         bool isDescendant2 =
405           nsLayoutUtils::IsProperAncestorFrame(frame, aFrame2, block);
406         if (isDescendant1 && isDescendant2) {
407           return true;
408         }
409         if (isDescendant1 || isDescendant2) {
410           return false;
411         }
412       }
413       MOZ_ASSERT_UNREACHABLE("None of the frames is a descendant of this rtc?");
414     }
415     MOZ_ASSERT_UNREACHABLE("Do we have any other type of line container?");
416     return false;
417   }
418 };
419 
420 // A resolved color stop, with a specific position along the gradient line and
421 // a color.
422 struct ColorStop {
ColorStopColorStop423   ColorStop(): mPosition(0), mIsMidpoint(false) {}
ColorStopColorStop424   ColorStop(double aPosition, bool aIsMidPoint, const Color& aColor) :
425     mPosition(aPosition), mIsMidpoint(aIsMidPoint), mColor(aColor) {}
426   double mPosition; // along the gradient line; 0=start, 1=end
427   bool mIsMidpoint;
428   Color mColor;
429 };
430 
431 /* Local functions */
432 static DrawResult DrawBorderImage(nsPresContext* aPresContext,
433                                   nsRenderingContext& aRenderingContext,
434                                   nsIFrame* aForFrame,
435                                   const nsRect& aBorderArea,
436                                   const nsStyleBorder& aStyleBorder,
437                                   const nsRect& aDirtyRect,
438                                   Sides aSkipSides,
439                                   PaintBorderFlags aFlags);
440 
441 static nscolor MakeBevelColor(mozilla::css::Side whichSide, uint8_t style,
442                               nscolor aBackgroundColor,
443                               nscolor aBorderColor);
444 
445 static InlineBackgroundData* gInlineBGData = nullptr;
446 
447 // Initialize any static variables used by nsCSSRendering.
Init()448 void nsCSSRendering::Init()
449 {
450   NS_ASSERTION(!gInlineBGData, "Init called twice");
451   gInlineBGData = new InlineBackgroundData();
452 }
453 
454 // Clean up any global variables used by nsCSSRendering.
Shutdown()455 void nsCSSRendering::Shutdown()
456 {
457   delete gInlineBGData;
458   gInlineBGData = nullptr;
459 }
460 
461 /**
462  * Make a bevel color
463  */
464 static nscolor
MakeBevelColor(mozilla::css::Side whichSide,uint8_t style,nscolor aBackgroundColor,nscolor aBorderColor)465 MakeBevelColor(mozilla::css::Side whichSide, uint8_t style,
466                nscolor aBackgroundColor, nscolor aBorderColor)
467 {
468 
469   nscolor colors[2];
470   nscolor theColor;
471 
472   // Given a background color and a border color
473   // calculate the color used for the shading
474   NS_GetSpecial3DColors(colors, aBackgroundColor, aBorderColor);
475 
476   if ((style == NS_STYLE_BORDER_STYLE_OUTSET) ||
477       (style == NS_STYLE_BORDER_STYLE_RIDGE)) {
478     // Flip colors for these two border styles
479     switch (whichSide) {
480     case NS_SIDE_BOTTOM: whichSide = NS_SIDE_TOP;    break;
481     case NS_SIDE_RIGHT:  whichSide = NS_SIDE_LEFT;   break;
482     case NS_SIDE_TOP:    whichSide = NS_SIDE_BOTTOM; break;
483     case NS_SIDE_LEFT:   whichSide = NS_SIDE_RIGHT;  break;
484     }
485   }
486 
487   switch (whichSide) {
488   case NS_SIDE_BOTTOM:
489     theColor = colors[1];
490     break;
491   case NS_SIDE_RIGHT:
492     theColor = colors[1];
493     break;
494   case NS_SIDE_TOP:
495     theColor = colors[0];
496     break;
497   case NS_SIDE_LEFT:
498   default:
499     theColor = colors[0];
500     break;
501   }
502   return theColor;
503 }
504 
505 static bool
GetRadii(nsIFrame * aForFrame,const nsStyleBorder & aBorder,const nsRect & aOrigBorderArea,const nsRect & aBorderArea,nscoord aRadii[8])506 GetRadii(nsIFrame* aForFrame, const nsStyleBorder& aBorder,
507          const nsRect& aOrigBorderArea, const nsRect& aBorderArea,
508          nscoord aRadii[8])
509 {
510   bool haveRoundedCorners;
511   nsSize sz = aBorderArea.Size();
512   nsSize frameSize = aForFrame->GetSize();
513   if (&aBorder == aForFrame->StyleBorder() &&
514       frameSize == aOrigBorderArea.Size()) {
515     haveRoundedCorners = aForFrame->GetBorderRadii(sz, sz, Sides(), aRadii);
516    } else {
517     haveRoundedCorners =
518       nsIFrame::ComputeBorderRadii(aBorder.mBorderRadius, frameSize, sz, Sides(), aRadii);
519   }
520 
521   return haveRoundedCorners;
522 }
523 
524 static bool
GetRadii(nsIFrame * aForFrame,const nsStyleBorder & aBorder,const nsRect & aOrigBorderArea,const nsRect & aBorderArea,RectCornerRadii * aBgRadii)525 GetRadii(nsIFrame* aForFrame, const nsStyleBorder& aBorder,
526          const nsRect& aOrigBorderArea, const nsRect& aBorderArea,
527          RectCornerRadii* aBgRadii)
528 {
529   nscoord radii[8];
530   bool haveRoundedCorners = GetRadii(aForFrame, aBorder, aOrigBorderArea, aBorderArea, radii);
531 
532   if (haveRoundedCorners) {
533     auto d2a = aForFrame->PresContext()->AppUnitsPerDevPixel();
534     nsCSSRendering::ComputePixelRadii(radii, d2a, aBgRadii);
535   }
536   return haveRoundedCorners;
537 }
538 
539 static nsRect
JoinBoxesForVerticalSlice(nsIFrame * aFrame,const nsRect & aBorderArea)540 JoinBoxesForVerticalSlice(nsIFrame* aFrame, const nsRect& aBorderArea)
541 {
542   // Inflate vertically as if our continuations were laid out vertically
543   // adjacent. Note that we don't touch the width.
544   nsRect borderArea = aBorderArea;
545   nscoord h = 0;
546   nsIFrame* f = aFrame->GetNextContinuation();
547   for (; f; f = f->GetNextContinuation()) {
548     MOZ_ASSERT(!(f->GetStateBits() & NS_FRAME_PART_OF_IBSPLIT),
549                "anonymous ib-split block shouldn't have border/background");
550     h += f->GetRect().height;
551   }
552   borderArea.height += h;
553   h = 0;
554   f = aFrame->GetPrevContinuation();
555   for (; f; f = f->GetPrevContinuation()) {
556     MOZ_ASSERT(!(f->GetStateBits() & NS_FRAME_PART_OF_IBSPLIT),
557                "anonymous ib-split block shouldn't have border/background");
558     h += f->GetRect().height;
559   }
560   borderArea.y -= h;
561   borderArea.height += h;
562   return borderArea;
563 }
564 
565 /**
566  * Inflate aBorderArea which is relative to aFrame's origin to calculate
567  * a hypothetical non-split frame area for all the continuations.
568  * See "Joining Boxes for 'slice'" in
569  * http://dev.w3.org/csswg/css-break/#break-decoration
570  */
571 enum InlineBoxOrder { eForBorder, eForBackground };
572 static nsRect
JoinBoxesForSlice(nsIFrame * aFrame,const nsRect & aBorderArea,InlineBoxOrder aOrder)573 JoinBoxesForSlice(nsIFrame* aFrame, const nsRect& aBorderArea,
574                   InlineBoxOrder aOrder)
575 {
576   if (static_cast<nsInlineFrame*>(do_QueryFrame(aFrame))) {
577     return (aOrder == eForBorder
578             ? gInlineBGData->GetBorderContinuousRect(aFrame, aBorderArea)
579             : gInlineBGData->GetContinuousRect(aFrame)) +
580       aBorderArea.TopLeft();
581   }
582   return JoinBoxesForVerticalSlice(aFrame, aBorderArea);
583 }
584 
585 static bool
IsBoxDecorationSlice(const nsStyleBorder & aStyleBorder)586 IsBoxDecorationSlice(const nsStyleBorder& aStyleBorder)
587 {
588   return aStyleBorder.mBoxDecorationBreak == StyleBoxDecorationBreak::Slice;
589 }
590 
591 static nsRect
BoxDecorationRectForBorder(nsIFrame * aFrame,const nsRect & aBorderArea,Sides aSkipSides,const nsStyleBorder * aStyleBorder=nullptr)592 BoxDecorationRectForBorder(nsIFrame* aFrame, const nsRect& aBorderArea,
593                            Sides aSkipSides,
594                            const nsStyleBorder* aStyleBorder = nullptr)
595 {
596   if (!aStyleBorder) {
597     aStyleBorder = aFrame->StyleBorder();
598   }
599   // If aSkipSides.IsEmpty() then there are no continuations, or it's
600   // a ::first-letter that wants all border sides on the first continuation.
601   return ::IsBoxDecorationSlice(*aStyleBorder) && !aSkipSides.IsEmpty()
602            ? ::JoinBoxesForSlice(aFrame, aBorderArea, eForBorder)
603            : aBorderArea;
604 }
605 
606 static nsRect
BoxDecorationRectForBackground(nsIFrame * aFrame,const nsRect & aBorderArea,Sides aSkipSides,const nsStyleBorder * aStyleBorder=nullptr)607 BoxDecorationRectForBackground(nsIFrame* aFrame, const nsRect& aBorderArea,
608                                Sides aSkipSides,
609                                const nsStyleBorder* aStyleBorder = nullptr)
610 {
611   if (!aStyleBorder) {
612     aStyleBorder = aFrame->StyleBorder();
613   }
614   // If aSkipSides.IsEmpty() then there are no continuations, or it's
615   // a ::first-letter that wants all border sides on the first continuation.
616   return ::IsBoxDecorationSlice(*aStyleBorder) && !aSkipSides.IsEmpty()
617            ? ::JoinBoxesForSlice(aFrame, aBorderArea, eForBackground)
618            : aBorderArea;
619 }
620 
621 //----------------------------------------------------------------------
622 // Thebes Border Rendering Code Start
623 
624 /*
625  * Compute the float-pixel radii that should be used for drawing
626  * this border/outline, given the various input bits.
627  */
628 /* static */ void
ComputePixelRadii(const nscoord * aAppUnitsRadii,nscoord aAppUnitsPerPixel,RectCornerRadii * oBorderRadii)629 nsCSSRendering::ComputePixelRadii(const nscoord *aAppUnitsRadii,
630                                   nscoord aAppUnitsPerPixel,
631                                   RectCornerRadii *oBorderRadii)
632 {
633   Float radii[8];
634   NS_FOR_CSS_HALF_CORNERS(corner)
635     radii[corner] = Float(aAppUnitsRadii[corner]) / aAppUnitsPerPixel;
636 
637   (*oBorderRadii)[C_TL] = Size(radii[NS_CORNER_TOP_LEFT_X],
638                                radii[NS_CORNER_TOP_LEFT_Y]);
639   (*oBorderRadii)[C_TR] = Size(radii[NS_CORNER_TOP_RIGHT_X],
640                                radii[NS_CORNER_TOP_RIGHT_Y]);
641   (*oBorderRadii)[C_BR] = Size(radii[NS_CORNER_BOTTOM_RIGHT_X],
642                                radii[NS_CORNER_BOTTOM_RIGHT_Y]);
643   (*oBorderRadii)[C_BL] = Size(radii[NS_CORNER_BOTTOM_LEFT_X],
644                                radii[NS_CORNER_BOTTOM_LEFT_Y]);
645 }
646 
647 DrawResult
PaintBorder(nsPresContext * aPresContext,nsRenderingContext & aRenderingContext,nsIFrame * aForFrame,const nsRect & aDirtyRect,const nsRect & aBorderArea,nsStyleContext * aStyleContext,PaintBorderFlags aFlags,Sides aSkipSides)648 nsCSSRendering::PaintBorder(nsPresContext* aPresContext,
649                             nsRenderingContext& aRenderingContext,
650                             nsIFrame* aForFrame,
651                             const nsRect& aDirtyRect,
652                             const nsRect& aBorderArea,
653                             nsStyleContext* aStyleContext,
654                             PaintBorderFlags aFlags,
655                             Sides aSkipSides)
656 {
657   PROFILER_LABEL("nsCSSRendering", "PaintBorder",
658     js::ProfileEntry::Category::GRAPHICS);
659 
660   nsStyleContext *styleIfVisited = aStyleContext->GetStyleIfVisited();
661   const nsStyleBorder *styleBorder = aStyleContext->StyleBorder();
662   // Don't check RelevantLinkVisited here, since we want to take the
663   // same amount of time whether or not it's true.
664   if (!styleIfVisited) {
665     return PaintBorderWithStyleBorder(aPresContext, aRenderingContext, aForFrame,
666                                       aDirtyRect, aBorderArea, *styleBorder,
667                                       aStyleContext, aFlags, aSkipSides);
668   }
669 
670   nsStyleBorder newStyleBorder(*styleBorder);
671 
672   NS_FOR_CSS_SIDES(side) {
673     nscolor color = aStyleContext->GetVisitedDependentColor(
674       nsCSSProps::SubpropertyEntryFor(eCSSProperty_border_color)[side]);
675     newStyleBorder.mBorderColor[side] = StyleComplexColor::FromColor(color);
676   }
677   DrawResult result =
678     PaintBorderWithStyleBorder(aPresContext, aRenderingContext, aForFrame,
679                                aDirtyRect, aBorderArea, newStyleBorder,
680                                aStyleContext, aFlags, aSkipSides);
681 
682   return result;
683 }
684 
685 DrawResult
PaintBorderWithStyleBorder(nsPresContext * aPresContext,nsRenderingContext & aRenderingContext,nsIFrame * aForFrame,const nsRect & aDirtyRect,const nsRect & aBorderArea,const nsStyleBorder & aStyleBorder,nsStyleContext * aStyleContext,PaintBorderFlags aFlags,Sides aSkipSides)686 nsCSSRendering::PaintBorderWithStyleBorder(nsPresContext* aPresContext,
687                                            nsRenderingContext& aRenderingContext,
688                                            nsIFrame* aForFrame,
689                                            const nsRect& aDirtyRect,
690                                            const nsRect& aBorderArea,
691                                            const nsStyleBorder& aStyleBorder,
692                                            nsStyleContext* aStyleContext,
693                                            PaintBorderFlags aFlags,
694                                            Sides aSkipSides)
695 {
696   DrawTarget& aDrawTarget = *aRenderingContext.GetDrawTarget();
697 
698   PrintAsStringNewline("++ PaintBorder");
699 
700   // Check to see if we have an appearance defined.  If so, we let the theme
701   // renderer draw the border.  DO not get the data from aForFrame, since the passed in style context
702   // may be different!  Always use |aStyleContext|!
703   const nsStyleDisplay* displayData = aStyleContext->StyleDisplay();
704   if (displayData->mAppearance) {
705     nsITheme *theme = aPresContext->GetTheme();
706     if (theme &&
707         theme->ThemeSupportsWidget(aPresContext, aForFrame,
708                                    displayData->mAppearance)) {
709       return DrawResult::SUCCESS; // Let the theme handle it.
710     }
711   }
712 
713   if (aStyleBorder.IsBorderImageLoaded()) {
714     return DrawBorderImage(aPresContext, aRenderingContext, aForFrame,
715                            aBorderArea, aStyleBorder, aDirtyRect,
716                            aSkipSides, aFlags);
717   }
718 
719   DrawResult result = DrawResult::SUCCESS;
720 
721   // If we had a border-image, but it wasn't loaded, then we should return
722   // DrawResult::NOT_READY; we'll want to try again if we do a paint with sync
723   // decoding enabled.
724   if (aStyleBorder.mBorderImageSource.GetType() != eStyleImageType_Null) {
725     result = DrawResult::NOT_READY;
726   }
727 
728   // Get our style context's color struct.
729   const nsStyleColor* ourColor = aStyleContext->StyleColor();
730 
731   // In NavQuirks mode we want to use the parent's context as a starting point
732   // for determining the background color.
733   bool quirks = aPresContext->CompatibilityMode() == eCompatibility_NavQuirks;
734   nsIFrame* bgFrame = FindNonTransparentBackgroundFrame(aForFrame, quirks);
735   nsStyleContext* bgContext = bgFrame->StyleContext();
736   nscolor bgColor =
737     bgContext->GetVisitedDependentColor(eCSSProperty_background_color);
738 
739   nsMargin border = aStyleBorder.GetComputedBorder();
740   if (0 == border.left && 0 == border.right &&
741       0 == border.top  && 0 == border.bottom) {
742     // Empty border area
743     return result;
744   }
745 
746   // Compute the outermost boundary of the area that might be painted.
747   // Same coordinate space as aBorderArea & aBGClipRect.
748   nsRect joinedBorderArea =
749     ::BoxDecorationRectForBorder(aForFrame, aBorderArea, aSkipSides, &aStyleBorder);
750   RectCornerRadii bgRadii;
751   ::GetRadii(aForFrame, aStyleBorder, aBorderArea, joinedBorderArea, &bgRadii);
752 
753   PrintAsFormatString(" joinedBorderArea: %d %d %d %d\n", joinedBorderArea.x, joinedBorderArea.y,
754      joinedBorderArea.width, joinedBorderArea.height);
755 
756   // start drawing
757   bool needToPopClip = false;
758 
759   if (::IsBoxDecorationSlice(aStyleBorder)) {
760     if (joinedBorderArea.IsEqualEdges(aBorderArea)) {
761       // No need for a clip, just skip the sides we don't want.
762       border.ApplySkipSides(aSkipSides);
763     } else {
764       // We're drawing borders around the joined continuation boxes so we need
765       // to clip that to the slice that we want for this frame.
766       aDrawTarget.PushClipRect(
767         NSRectToSnappedRect(aBorderArea,
768                             aForFrame->PresContext()->AppUnitsPerDevPixel(),
769                             aDrawTarget));
770       needToPopClip = true;
771     }
772   } else {
773     MOZ_ASSERT(joinedBorderArea.IsEqualEdges(aBorderArea),
774                "Should use aBorderArea for box-decoration-break:clone");
775     MOZ_ASSERT(aForFrame->GetSkipSides().IsEmpty() ||
776                IS_TRUE_OVERFLOW_CONTAINER(aForFrame),
777                "Should not skip sides for box-decoration-break:clone except "
778                "::first-letter/line continuations or other frame types that "
779                "don't have borders but those shouldn't reach this point. "
780                "Overflow containers do reach this point though.");
781     border.ApplySkipSides(aSkipSides);
782   }
783 
784   // Convert to dev pixels.
785   nscoord twipsPerPixel = aPresContext->DevPixelsToAppUnits(1);
786   Rect joinedBorderAreaPx = NSRectToRect(joinedBorderArea, twipsPerPixel);
787   Float borderWidths[4] = { Float(border.top / twipsPerPixel),
788                             Float(border.right / twipsPerPixel),
789                             Float(border.bottom / twipsPerPixel),
790                             Float(border.left / twipsPerPixel) };
791   Rect dirtyRect = NSRectToRect(aDirtyRect, twipsPerPixel);
792 
793   uint8_t borderStyles[4];
794   nscolor borderColors[4];
795   nsBorderColors *compositeColors[4];
796 
797   // pull out styles, colors, composite colors
798   NS_FOR_CSS_SIDES (i) {
799     borderStyles[i] = aStyleBorder.GetBorderStyle(i);
800     borderColors[i] = ourColor->CalcComplexColor(aStyleBorder.mBorderColor[i]);
801     aStyleBorder.GetCompositeColors(i, &compositeColors[i]);
802   }
803 
804   PrintAsFormatString(" borderStyles: %d %d %d %d\n", borderStyles[0], borderStyles[1], borderStyles[2], borderStyles[3]);
805   //PrintAsFormatString ("bgRadii: %f %f %f %f\n", bgRadii[0], bgRadii[1], bgRadii[2], bgRadii[3]);
806 
807 #if 0
808   // this will draw a transparent red backround underneath the border area
809   ColorPattern color(ToDeviceColor(Color(1.f, 0.f, 0.f, 0.5f)));
810   aDrawTarget.FillRect(joinedBorderAreaPx, color);
811 #endif
812 
813   nsIDocument* document = nullptr;
814   nsIContent* content = aForFrame->GetContent();
815   if (content) {
816     document = content->OwnerDoc();
817   }
818 
819   nsCSSBorderRenderer br(aPresContext,
820                          document,
821                          &aDrawTarget,
822                          dirtyRect,
823                          joinedBorderAreaPx,
824                          borderStyles,
825                          borderWidths,
826                          bgRadii,
827                          borderColors,
828                          compositeColors,
829                          bgColor);
830   br.DrawBorders();
831 
832   if (needToPopClip) {
833     aDrawTarget.PopClip();
834   }
835 
836   PrintAsStringNewline();
837 
838   return result;
839 }
840 
841 static nsRect
GetOutlineInnerRect(nsIFrame * aFrame)842 GetOutlineInnerRect(nsIFrame* aFrame)
843 {
844   nsRect* savedOutlineInnerRect =
845     aFrame->Properties().Get(nsIFrame::OutlineInnerRectProperty());
846   if (savedOutlineInnerRect)
847     return *savedOutlineInnerRect;
848   NS_NOTREACHED("we should have saved a frame property");
849   return nsRect(nsPoint(0, 0), aFrame->GetSize());
850 }
851 
852 void
PaintOutline(nsPresContext * aPresContext,nsRenderingContext & aRenderingContext,nsIFrame * aForFrame,const nsRect & aDirtyRect,const nsRect & aBorderArea,nsStyleContext * aStyleContext)853 nsCSSRendering::PaintOutline(nsPresContext* aPresContext,
854                              nsRenderingContext& aRenderingContext,
855                              nsIFrame* aForFrame,
856                              const nsRect& aDirtyRect,
857                              const nsRect& aBorderArea,
858                              nsStyleContext* aStyleContext)
859 {
860   nscoord             twipsRadii[8];
861 
862   // Get our style context's color struct.
863   const nsStyleOutline* ourOutline = aStyleContext->StyleOutline();
864   MOZ_ASSERT(ourOutline != NS_STYLE_BORDER_STYLE_NONE,
865              "shouldn't have created nsDisplayOutline item");
866 
867   uint8_t outlineStyle = ourOutline->mOutlineStyle;
868   nscoord width = ourOutline->GetOutlineWidth();
869 
870   if (width == 0 && outlineStyle != NS_STYLE_BORDER_STYLE_AUTO) {
871     // Empty outline
872     return;
873   }
874 
875   nsIFrame* bgFrame = nsCSSRendering::FindNonTransparentBackgroundFrame
876     (aForFrame, false);
877   nsStyleContext* bgContext = bgFrame->StyleContext();
878   nscolor bgColor =
879     bgContext->GetVisitedDependentColor(eCSSProperty_background_color);
880 
881   nsRect innerRect;
882   if (
883 #ifdef MOZ_XUL
884       aStyleContext->GetPseudoType() == CSSPseudoElementType::XULTree
885 #else
886       false
887 #endif
888      ) {
889     innerRect = aBorderArea;
890   } else {
891     innerRect = GetOutlineInnerRect(aForFrame) + aBorderArea.TopLeft();
892   }
893   nscoord offset = ourOutline->mOutlineOffset;
894   innerRect.Inflate(offset, offset);
895   // If the dirty rect is completely inside the border area (e.g., only the
896   // content is being painted), then we can skip out now
897   // XXX this isn't exactly true for rounded borders, where the inside curves may
898   // encroach into the content area.  A safer calculation would be to
899   // shorten insideRect by the radius one each side before performing this test.
900   if (innerRect.Contains(aDirtyRect))
901     return;
902 
903   nsRect outerRect = innerRect;
904   outerRect.Inflate(width, width);
905 
906   // get the radius for our outline
907   nsIFrame::ComputeBorderRadii(ourOutline->mOutlineRadius, aBorderArea.Size(),
908                                outerRect.Size(), Sides(), twipsRadii);
909 
910   // Get our conversion values
911   nscoord twipsPerPixel = aPresContext->DevPixelsToAppUnits(1);
912 
913   // get the outer rectangles
914   Rect oRect(NSRectToRect(outerRect, twipsPerPixel));
915 
916   // convert the radii
917   nsMargin outlineMargin(width, width, width, width);
918   RectCornerRadii outlineRadii;
919   ComputePixelRadii(twipsRadii, twipsPerPixel, &outlineRadii);
920 
921   if (outlineStyle == NS_STYLE_BORDER_STYLE_AUTO) {
922     if (nsLayoutUtils::IsOutlineStyleAutoEnabled()) {
923       nsITheme* theme = aPresContext->GetTheme();
924       if (theme && theme->ThemeSupportsWidget(aPresContext, aForFrame,
925                                               NS_THEME_FOCUS_OUTLINE)) {
926         theme->DrawWidgetBackground(&aRenderingContext, aForFrame,
927                                     NS_THEME_FOCUS_OUTLINE, innerRect,
928                                     aDirtyRect);
929         return;
930       }
931     }
932     if (width == 0) {
933       return; // empty outline
934     }
935     // http://dev.w3.org/csswg/css-ui/#outline
936     // "User agents may treat 'auto' as 'solid'."
937     outlineStyle = NS_STYLE_BORDER_STYLE_SOLID;
938   }
939 
940   uint8_t outlineStyles[4] = { outlineStyle, outlineStyle,
941                                outlineStyle, outlineStyle };
942 
943   // This handles treating the initial color as 'currentColor'; if we
944   // ever want 'invert' back we'll need to do a bit of work here too.
945   nscolor outlineColor =
946     aStyleContext->GetVisitedDependentColor(eCSSProperty_outline_color);
947   nscolor outlineColors[4] = { outlineColor,
948                                outlineColor,
949                                outlineColor,
950                                outlineColor };
951 
952   // convert the border widths
953   Float outlineWidths[4] = { Float(width / twipsPerPixel),
954                              Float(width / twipsPerPixel),
955                              Float(width / twipsPerPixel),
956                              Float(width / twipsPerPixel) };
957   Rect dirtyRect = NSRectToRect(aDirtyRect, twipsPerPixel);
958 
959   nsIDocument* document = nullptr;
960   nsIContent* content = aForFrame->GetContent();
961   if (content) {
962     document = content->OwnerDoc();
963   }
964 
965   // start drawing
966 
967   nsCSSBorderRenderer br(aPresContext,
968                          document,
969                          aRenderingContext.GetDrawTarget(),
970                          dirtyRect,
971                          oRect,
972                          outlineStyles,
973                          outlineWidths,
974                          outlineRadii,
975                          outlineColors,
976                          nullptr,
977                          bgColor);
978   br.DrawBorders();
979 
980   PrintAsStringNewline();
981 }
982 
983 void
PaintFocus(nsPresContext * aPresContext,DrawTarget * aDrawTarget,const nsRect & aFocusRect,nscolor aColor)984 nsCSSRendering::PaintFocus(nsPresContext* aPresContext,
985                            DrawTarget* aDrawTarget,
986                            const nsRect& aFocusRect,
987                            nscolor aColor)
988 {
989   nscoord oneCSSPixel = nsPresContext::CSSPixelsToAppUnits(1);
990   nscoord oneDevPixel = aPresContext->DevPixelsToAppUnits(1);
991 
992   Rect focusRect(NSRectToRect(aFocusRect, oneDevPixel));
993 
994   RectCornerRadii focusRadii;
995   {
996     nscoord twipsRadii[8] = { 0, 0, 0, 0, 0, 0, 0, 0 };
997     ComputePixelRadii(twipsRadii, oneDevPixel, &focusRadii);
998   }
999   Float focusWidths[4] = { Float(oneCSSPixel / oneDevPixel),
1000                            Float(oneCSSPixel / oneDevPixel),
1001                            Float(oneCSSPixel / oneDevPixel),
1002                            Float(oneCSSPixel / oneDevPixel) };
1003 
1004   uint8_t focusStyles[4] = { NS_STYLE_BORDER_STYLE_DOTTED,
1005                              NS_STYLE_BORDER_STYLE_DOTTED,
1006                              NS_STYLE_BORDER_STYLE_DOTTED,
1007                              NS_STYLE_BORDER_STYLE_DOTTED };
1008   nscolor focusColors[4] = { aColor, aColor, aColor, aColor };
1009 
1010   // Because this renders a dotted border, the background color
1011   // should not be used.  Therefore, we provide a value that will
1012   // be blatantly wrong if it ever does get used.  (If this becomes
1013   // something that CSS can style, this function will then have access
1014   // to a style context and can use the same logic that PaintBorder
1015   // and PaintOutline do.)
1016   nsCSSBorderRenderer br(aPresContext,
1017                          nullptr,
1018                          aDrawTarget,
1019                          focusRect,
1020                          focusRect,
1021                          focusStyles,
1022                          focusWidths,
1023                          focusRadii,
1024                          focusColors,
1025                          nullptr,
1026                          NS_RGB(255, 0, 0));
1027   br.DrawBorders();
1028 
1029   PrintAsStringNewline();
1030 }
1031 
1032 // Thebes Border Rendering Code End
1033 //----------------------------------------------------------------------
1034 
1035 
1036 //----------------------------------------------------------------------
1037 
1038 /**
1039  * Helper for ComputeObjectAnchorPoint; parameters are the same as for
1040  * that function, except they're for a single coordinate / a single size
1041  * dimension. (so, x/width vs. y/height)
1042  */
1043 static void
ComputeObjectAnchorCoord(const Position::Coord & aCoord,const nscoord aOriginBounds,const nscoord aImageSize,nscoord * aTopLeftCoord,nscoord * aAnchorPointCoord)1044 ComputeObjectAnchorCoord(const Position::Coord& aCoord,
1045                          const nscoord aOriginBounds,
1046                          const nscoord aImageSize,
1047                          nscoord* aTopLeftCoord,
1048                          nscoord* aAnchorPointCoord)
1049 {
1050   *aAnchorPointCoord = aCoord.mLength;
1051   *aTopLeftCoord = aCoord.mLength;
1052 
1053   if (aCoord.mHasPercent) {
1054     // Adjust aTopLeftCoord by the specified % of the extra space.
1055     nscoord extraSpace = aOriginBounds - aImageSize;
1056     *aTopLeftCoord += NSToCoordRound(aCoord.mPercent * extraSpace);
1057 
1058     // The anchor-point doesn't care about our image's size; just the size
1059     // of the region we're rendering into.
1060     *aAnchorPointCoord += NSToCoordRound(aCoord.mPercent * aOriginBounds);
1061   }
1062 }
1063 
1064 void
ComputeObjectAnchorPoint(const Position & aPos,const nsSize & aOriginBounds,const nsSize & aImageSize,nsPoint * aTopLeft,nsPoint * aAnchorPoint)1065 nsImageRenderer::ComputeObjectAnchorPoint(
1066   const Position& aPos,
1067   const nsSize& aOriginBounds,
1068   const nsSize& aImageSize,
1069   nsPoint* aTopLeft,
1070   nsPoint* aAnchorPoint)
1071 {
1072   ComputeObjectAnchorCoord(aPos.mXPosition,
1073                            aOriginBounds.width, aImageSize.width,
1074                            &aTopLeft->x, &aAnchorPoint->x);
1075 
1076   ComputeObjectAnchorCoord(aPos.mYPosition,
1077                            aOriginBounds.height, aImageSize.height,
1078                            &aTopLeft->y, &aAnchorPoint->y);
1079 }
1080 
1081 nsIFrame*
FindNonTransparentBackgroundFrame(nsIFrame * aFrame,bool aStartAtParent)1082 nsCSSRendering::FindNonTransparentBackgroundFrame(nsIFrame* aFrame,
1083                                                   bool aStartAtParent /*= false*/)
1084 {
1085   NS_ASSERTION(aFrame, "Cannot find NonTransparentBackgroundFrame in a null frame");
1086 
1087   nsIFrame* frame = nullptr;
1088   if (aStartAtParent) {
1089     frame = nsLayoutUtils::GetParentOrPlaceholderFor(aFrame);
1090   }
1091   if (!frame) {
1092     frame = aFrame;
1093   }
1094 
1095   while (frame) {
1096     // No need to call GetVisitedDependentColor because it always uses
1097     // this alpha component anyway.
1098     if (NS_GET_A(frame->StyleBackground()->mBackgroundColor) > 0)
1099       break;
1100 
1101     if (frame->IsThemed())
1102       break;
1103 
1104     nsIFrame* parent = nsLayoutUtils::GetParentOrPlaceholderFor(frame);
1105     if (!parent)
1106       break;
1107 
1108     frame = parent;
1109   }
1110   return frame;
1111 }
1112 
1113 // Returns true if aFrame is a canvas frame.
1114 // We need to treat the viewport as canvas because, even though
1115 // it does not actually paint a background, we need to get the right
1116 // background style so we correctly detect transparent documents.
1117 bool
IsCanvasFrame(nsIFrame * aFrame)1118 nsCSSRendering::IsCanvasFrame(nsIFrame* aFrame)
1119 {
1120   nsIAtom* frameType = aFrame->GetType();
1121   return frameType == nsGkAtoms::canvasFrame ||
1122          frameType == nsGkAtoms::rootFrame ||
1123          frameType == nsGkAtoms::pageContentFrame ||
1124          frameType == nsGkAtoms::viewportFrame;
1125 }
1126 
1127 nsIFrame*
FindBackgroundStyleFrame(nsIFrame * aForFrame)1128 nsCSSRendering::FindBackgroundStyleFrame(nsIFrame* aForFrame)
1129 {
1130   const nsStyleBackground* result = aForFrame->StyleBackground();
1131 
1132   // Check if we need to do propagation from BODY rather than HTML.
1133   if (!result->IsTransparent()) {
1134     return aForFrame;
1135   }
1136 
1137   nsIContent* content = aForFrame->GetContent();
1138   // The root element content can't be null. We wouldn't know what
1139   // frame to create for aFrame.
1140   // Use |OwnerDoc| so it works during destruction.
1141   if (!content) {
1142     return aForFrame;
1143   }
1144 
1145   nsIDocument* document = content->OwnerDoc();
1146 
1147   dom::Element* bodyContent = document->GetBodyElement();
1148   // We need to null check the body node (bug 118829) since
1149   // there are cases, thanks to the fix for bug 5569, where we
1150   // will reflow a document with no body.  In particular, if a
1151   // SCRIPT element in the head blocks the parser and then has a
1152   // SCRIPT that does "document.location.href = 'foo'", then
1153   // nsParser::Terminate will call |DidBuildModel| methods
1154   // through to the content sink, which will call |StartLayout|
1155   // and thus |Initialize| on the pres shell.  See bug 119351
1156   // for the ugly details.
1157   if (!bodyContent) {
1158     return aForFrame;
1159   }
1160 
1161   nsIFrame *bodyFrame = bodyContent->GetPrimaryFrame();
1162   if (!bodyFrame) {
1163     return aForFrame;
1164   }
1165 
1166   return nsLayoutUtils::GetStyleFrame(bodyFrame);
1167 }
1168 
1169 /**
1170  * |FindBackground| finds the correct style data to use to paint the
1171  * background.  It is responsible for handling the following two
1172  * statements in section 14.2 of CSS2:
1173  *
1174  *   The background of the box generated by the root element covers the
1175  *   entire canvas.
1176  *
1177  *   For HTML documents, however, we recommend that authors specify the
1178  *   background for the BODY element rather than the HTML element. User
1179  *   agents should observe the following precedence rules to fill in the
1180  *   background: if the value of the 'background' property for the HTML
1181  *   element is different from 'transparent' then use it, else use the
1182  *   value of the 'background' property for the BODY element. If the
1183  *   resulting value is 'transparent', the rendering is undefined.
1184  *
1185  * Thus, in our implementation, it is responsible for ensuring that:
1186  *  + we paint the correct background on the |nsCanvasFrame|,
1187  *    |nsRootBoxFrame|, or |nsPageFrame|,
1188  *  + we don't paint the background on the root element, and
1189  *  + we don't paint the background on the BODY element in *some* cases,
1190  *    and for SGML-based HTML documents only.
1191  *
1192  * |FindBackground| returns true if a background should be painted, and
1193  * the resulting style context to use for the background information
1194  * will be filled in to |aBackground|.
1195  */
1196 nsStyleContext*
FindRootFrameBackground(nsIFrame * aForFrame)1197 nsCSSRendering::FindRootFrameBackground(nsIFrame* aForFrame)
1198 {
1199   return FindBackgroundStyleFrame(aForFrame)->StyleContext();
1200 }
1201 
1202 inline bool
FindElementBackground(nsIFrame * aForFrame,nsIFrame * aRootElementFrame,nsStyleContext ** aBackgroundSC)1203 FindElementBackground(nsIFrame* aForFrame, nsIFrame* aRootElementFrame,
1204                       nsStyleContext** aBackgroundSC)
1205 {
1206   if (aForFrame == aRootElementFrame) {
1207     // We must have propagated our background to the viewport or canvas. Abort.
1208     return false;
1209   }
1210 
1211   *aBackgroundSC = aForFrame->StyleContext();
1212 
1213   // Return true unless the frame is for a BODY element whose background
1214   // was propagated to the viewport.
1215 
1216   nsIContent* content = aForFrame->GetContent();
1217   if (!content || content->NodeInfo()->NameAtom() != nsGkAtoms::body)
1218     return true; // not frame for a "body" element
1219   // It could be a non-HTML "body" element but that's OK, we'd fail the
1220   // bodyContent check below
1221 
1222   if (aForFrame->StyleContext()->GetPseudo())
1223     return true; // A pseudo-element frame.
1224 
1225   // We should only look at the <html> background if we're in an HTML document
1226   nsIDocument* document = content->OwnerDoc();
1227 
1228   dom::Element* bodyContent = document->GetBodyElement();
1229   if (bodyContent != content)
1230     return true; // this wasn't the background that was propagated
1231 
1232   // This can be called even when there's no root element yet, during frame
1233   // construction, via nsLayoutUtils::FrameHasTransparency and
1234   // nsContainerFrame::SyncFrameViewProperties.
1235   if (!aRootElementFrame)
1236     return true;
1237 
1238   const nsStyleBackground* htmlBG = aRootElementFrame->StyleBackground();
1239   return !htmlBG->IsTransparent();
1240 }
1241 
1242 bool
FindBackground(nsIFrame * aForFrame,nsStyleContext ** aBackgroundSC)1243 nsCSSRendering::FindBackground(nsIFrame* aForFrame,
1244                                nsStyleContext** aBackgroundSC)
1245 {
1246   nsIFrame* rootElementFrame =
1247     aForFrame->PresContext()->PresShell()->FrameConstructor()->GetRootElementStyleFrame();
1248   if (IsCanvasFrame(aForFrame)) {
1249     *aBackgroundSC = FindCanvasBackground(aForFrame, rootElementFrame);
1250     return true;
1251   } else {
1252     return FindElementBackground(aForFrame, rootElementFrame, aBackgroundSC);
1253   }
1254 }
1255 
1256 void
BeginFrameTreesLocked()1257 nsCSSRendering::BeginFrameTreesLocked()
1258 {
1259   ++gFrameTreeLockCount;
1260 }
1261 
1262 void
EndFrameTreesLocked()1263 nsCSSRendering::EndFrameTreesLocked()
1264 {
1265   NS_ASSERTION(gFrameTreeLockCount > 0, "Unbalanced EndFrameTreeLocked");
1266   --gFrameTreeLockCount;
1267   if (gFrameTreeLockCount == 0) {
1268     gInlineBGData->Reset();
1269   }
1270 }
1271 
1272 void
PaintBoxShadowOuter(nsPresContext * aPresContext,nsRenderingContext & aRenderingContext,nsIFrame * aForFrame,const nsRect & aFrameArea,const nsRect & aDirtyRect,float aOpacity)1273 nsCSSRendering::PaintBoxShadowOuter(nsPresContext* aPresContext,
1274                                     nsRenderingContext& aRenderingContext,
1275                                     nsIFrame* aForFrame,
1276                                     const nsRect& aFrameArea,
1277                                     const nsRect& aDirtyRect,
1278                                     float aOpacity)
1279 {
1280   DrawTarget& aDrawTarget = *aRenderingContext.GetDrawTarget();
1281   nsCSSShadowArray* shadows = aForFrame->StyleEffects()->mBoxShadow;
1282   if (!shadows)
1283     return;
1284 
1285   bool hasBorderRadius;
1286   bool nativeTheme; // mutually exclusive with hasBorderRadius
1287   const nsStyleDisplay* styleDisplay = aForFrame->StyleDisplay();
1288   nsITheme::Transparency transparency;
1289   if (aForFrame->IsThemed(styleDisplay, &transparency)) {
1290     // We don't respect border-radius for native-themed widgets
1291     hasBorderRadius = false;
1292     // For opaque (rectangular) theme widgets we can take the generic
1293     // border-box path with border-radius disabled.
1294     nativeTheme = transparency != nsITheme::eOpaque;
1295   } else {
1296     nativeTheme = false;
1297     hasBorderRadius = true; // we'll update this below
1298   }
1299 
1300   nsRect frameRect = nativeTheme ?
1301     aForFrame->GetVisualOverflowRectRelativeToSelf() + aFrameArea.TopLeft() :
1302     aFrameArea;
1303   Sides skipSides = aForFrame->GetSkipSides();
1304   frameRect = ::BoxDecorationRectForBorder(aForFrame, frameRect, skipSides);
1305 
1306   // Get any border radius, since box-shadow must also have rounded corners if
1307   // the frame does.
1308   RectCornerRadii borderRadii;
1309   const nscoord twipsPerPixel = aPresContext->DevPixelsToAppUnits(1);
1310   if (hasBorderRadius) {
1311     nscoord twipsRadii[8];
1312     NS_ASSERTION(aFrameArea.Size() == aForFrame->VisualBorderRectRelativeToSelf().Size(),
1313                  "unexpected size");
1314     nsSize sz = frameRect.Size();
1315     hasBorderRadius = aForFrame->GetBorderRadii(sz, sz, Sides(), twipsRadii);
1316     if (hasBorderRadius) {
1317       ComputePixelRadii(twipsRadii, twipsPerPixel, &borderRadii);
1318     }
1319   }
1320 
1321 
1322   // We don't show anything that intersects with the frame we're blurring on. So tell the
1323   // blurrer not to do unnecessary work there.
1324   gfxRect skipGfxRect = ThebesRect(NSRectToRect(frameRect, twipsPerPixel));
1325   skipGfxRect.Round();
1326   bool useSkipGfxRect = true;
1327   if (nativeTheme) {
1328     // Optimize non-leaf native-themed frames by skipping computing pixels
1329     // in the padding-box. We assume the padding-box is going to be painted
1330     // opaquely for non-leaf frames.
1331     // XXX this may not be a safe assumption; we should make this go away
1332     // by optimizing box-shadow drawing more for the cases where we don't have a skip-rect.
1333     useSkipGfxRect = !aForFrame->IsLeaf();
1334     nsRect paddingRect =
1335       aForFrame->GetPaddingRect() - aForFrame->GetPosition() + aFrameArea.TopLeft();
1336     skipGfxRect = nsLayoutUtils::RectToGfxRect(paddingRect, twipsPerPixel);
1337   } else if (hasBorderRadius) {
1338     skipGfxRect.Deflate(gfxMargin(
1339         std::max(borderRadii[C_TL].height, borderRadii[C_TR].height), 0,
1340         std::max(borderRadii[C_BL].height, borderRadii[C_BR].height), 0));
1341   }
1342 
1343   gfxContext* renderContext = aRenderingContext.ThebesContext();
1344 
1345   for (uint32_t i = shadows->Length(); i > 0; --i) {
1346     nsCSSShadowItem* shadowItem = shadows->ShadowAt(i - 1);
1347     if (shadowItem->mInset)
1348       continue;
1349 
1350     nsRect shadowRect = frameRect;
1351     shadowRect.MoveBy(shadowItem->mXOffset, shadowItem->mYOffset);
1352     if (!nativeTheme) {
1353       shadowRect.Inflate(shadowItem->mSpread, shadowItem->mSpread);
1354     }
1355 
1356     // shadowRect won't include the blur, so make an extra rect here that includes the blur
1357     // for use in the even-odd rule below.
1358     nsRect shadowRectPlusBlur = shadowRect;
1359     nscoord blurRadius = shadowItem->mRadius;
1360     shadowRectPlusBlur.Inflate(
1361       nsContextBoxBlur::GetBlurRadiusMargin(blurRadius, twipsPerPixel));
1362 
1363     Rect shadowGfxRectPlusBlur =
1364       NSRectToRect(shadowRectPlusBlur, twipsPerPixel);
1365     shadowGfxRectPlusBlur.RoundOut();
1366     MaybeSnapToDevicePixels(shadowGfxRectPlusBlur, aDrawTarget, true);
1367 
1368     // Set the shadow color; if not specified, use the foreground color
1369     nscolor shadowColor;
1370     if (shadowItem->mHasColor)
1371       shadowColor = shadowItem->mColor;
1372     else
1373       shadowColor = aForFrame->StyleColor()->mColor;
1374 
1375     Color gfxShadowColor(Color::FromABGR(shadowColor));
1376     gfxShadowColor.a *= aOpacity;
1377 
1378     if (nativeTheme) {
1379       nsContextBoxBlur blurringArea;
1380 
1381       // When getting the widget shape from the native theme, we're going
1382       // to draw the widget into the shadow surface to create a mask.
1383       // We need to ensure that there actually *is* a shadow surface
1384       // and that we're not going to draw directly into renderContext.
1385       gfxContext* shadowContext =
1386         blurringArea.Init(shadowRect, shadowItem->mSpread,
1387                           blurRadius, twipsPerPixel, renderContext, aDirtyRect,
1388                           useSkipGfxRect ? &skipGfxRect : nullptr,
1389                           nsContextBoxBlur::FORCE_MASK);
1390       if (!shadowContext)
1391         continue;
1392 
1393       MOZ_ASSERT(shadowContext == blurringArea.GetContext());
1394 
1395       renderContext->Save();
1396       renderContext->SetColor(gfxShadowColor);
1397 
1398       // Draw the shape of the frame so it can be blurred. Recall how nsContextBoxBlur
1399       // doesn't make any temporary surfaces if blur is 0 and it just returns the original
1400       // surface? If we have no blur, we're painting this fill on the actual content surface
1401       // (renderContext == shadowContext) which is why we set up the color and clip
1402       // before doing this.
1403 
1404       // We don't clip the border-box from the shadow, nor any other box.
1405       // We assume that the native theme is going to paint over the shadow.
1406 
1407       // Draw the widget shape
1408       gfxContextMatrixAutoSaveRestore save(shadowContext);
1409       gfxPoint devPixelOffset =
1410         nsLayoutUtils::PointToGfxPoint(nsPoint(shadowItem->mXOffset,
1411                                                shadowItem->mYOffset),
1412                                        aPresContext->AppUnitsPerDevPixel());
1413       shadowContext->SetMatrix(
1414         shadowContext->CurrentMatrix().Translate(devPixelOffset));
1415 
1416       nsRect nativeRect = aDirtyRect;
1417       nativeRect.MoveBy(-nsPoint(shadowItem->mXOffset, shadowItem->mYOffset));
1418       nativeRect.IntersectRect(frameRect, nativeRect);
1419       nsRenderingContext wrapperCtx(shadowContext);
1420       aPresContext->GetTheme()->DrawWidgetBackground(&wrapperCtx, aForFrame,
1421           styleDisplay->mAppearance, aFrameArea, nativeRect);
1422 
1423       blurringArea.DoPaint();
1424       renderContext->Restore();
1425     } else {
1426       renderContext->Save();
1427 
1428       {
1429         Rect innerClipRect = NSRectToRect(frameRect, twipsPerPixel);
1430         if (!MaybeSnapToDevicePixels(innerClipRect, aDrawTarget, true)) {
1431           innerClipRect.Round();
1432         }
1433 
1434         // Clip out the interior of the frame's border edge so that the shadow
1435         // is only painted outside that area.
1436         RefPtr<PathBuilder> builder =
1437           aDrawTarget.CreatePathBuilder(FillRule::FILL_EVEN_ODD);
1438         AppendRectToPath(builder, shadowGfxRectPlusBlur);
1439         if (hasBorderRadius) {
1440           AppendRoundedRectToPath(builder, innerClipRect, borderRadii);
1441         } else {
1442           AppendRectToPath(builder, innerClipRect);
1443         }
1444         RefPtr<Path> path = builder->Finish();
1445         renderContext->Clip(path);
1446       }
1447 
1448       // Clip the shadow so that we only get the part that applies to aForFrame.
1449       nsRect fragmentClip = shadowRectPlusBlur;
1450       if (!skipSides.IsEmpty()) {
1451         if (skipSides.Left()) {
1452           nscoord xmost = fragmentClip.XMost();
1453           fragmentClip.x = aFrameArea.x;
1454           fragmentClip.width = xmost - fragmentClip.x;
1455         }
1456         if (skipSides.Right()) {
1457           nscoord xmost = fragmentClip.XMost();
1458           nscoord overflow = xmost - aFrameArea.XMost();
1459           if (overflow > 0) {
1460             fragmentClip.width -= overflow;
1461           }
1462         }
1463         if (skipSides.Top()) {
1464           nscoord ymost = fragmentClip.YMost();
1465           fragmentClip.y = aFrameArea.y;
1466           fragmentClip.height = ymost - fragmentClip.y;
1467         }
1468         if (skipSides.Bottom()) {
1469           nscoord ymost = fragmentClip.YMost();
1470           nscoord overflow = ymost - aFrameArea.YMost();
1471           if (overflow > 0) {
1472             fragmentClip.height -= overflow;
1473           }
1474         }
1475       }
1476       fragmentClip = fragmentClip.Intersect(aDirtyRect);
1477       renderContext->
1478         Clip(NSRectToSnappedRect(fragmentClip,
1479                                  aForFrame->PresContext()->AppUnitsPerDevPixel(),
1480                                  aDrawTarget));
1481 
1482       RectCornerRadii clipRectRadii;
1483       if (hasBorderRadius) {
1484         Float spreadDistance = shadowItem->mSpread / twipsPerPixel;
1485 
1486         Float borderSizes[4];
1487 
1488         borderSizes[NS_SIDE_LEFT] = spreadDistance;
1489         borderSizes[NS_SIDE_TOP] = spreadDistance;
1490         borderSizes[NS_SIDE_RIGHT] = spreadDistance;
1491         borderSizes[NS_SIDE_BOTTOM] = spreadDistance;
1492 
1493         nsCSSBorderRenderer::ComputeOuterRadii(borderRadii, borderSizes,
1494             &clipRectRadii);
1495 
1496       }
1497       nsContextBoxBlur::BlurRectangle(renderContext,
1498                                       shadowRect,
1499                                       twipsPerPixel,
1500                                       hasBorderRadius ? &clipRectRadii : nullptr,
1501                                       blurRadius,
1502                                       gfxShadowColor,
1503                                       aDirtyRect,
1504                                       skipGfxRect);
1505       renderContext->Restore();
1506     }
1507 
1508   }
1509 }
1510 
1511 void
PaintBoxShadowInner(nsPresContext * aPresContext,nsRenderingContext & aRenderingContext,nsIFrame * aForFrame,const nsRect & aFrameArea)1512 nsCSSRendering::PaintBoxShadowInner(nsPresContext* aPresContext,
1513                                     nsRenderingContext& aRenderingContext,
1514                                     nsIFrame* aForFrame,
1515                                     const nsRect& aFrameArea)
1516 {
1517   nsCSSShadowArray* shadows = aForFrame->StyleEffects()->mBoxShadow;
1518   if (!shadows)
1519     return;
1520   if (aForFrame->IsThemed() && aForFrame->GetContent() &&
1521       !nsContentUtils::IsChromeDoc(aForFrame->GetContent()->GetUncomposedDoc())) {
1522     // There's no way of getting hold of a shape corresponding to a
1523     // "padding-box" for native-themed widgets, so just don't draw
1524     // inner box-shadows for them. But we allow chrome to paint inner
1525     // box shadows since chrome can be aware of the platform theme.
1526     return;
1527   }
1528 
1529   NS_ASSERTION(aForFrame->GetType() == nsGkAtoms::fieldSetFrame ||
1530                aFrameArea.Size() == aForFrame->GetSize(), "unexpected size");
1531 
1532   Sides skipSides = aForFrame->GetSkipSides();
1533   nsRect frameRect =
1534     ::BoxDecorationRectForBorder(aForFrame, aFrameArea, skipSides);
1535   nsRect paddingRect = frameRect;
1536   nsMargin border = aForFrame->GetUsedBorder();
1537   paddingRect.Deflate(border);
1538 
1539   // Get any border radius, since box-shadow must also have rounded corners
1540   // if the frame does.
1541   nscoord twipsRadii[8];
1542   nsSize sz = frameRect.Size();
1543   bool hasBorderRadius = aForFrame->GetBorderRadii(sz, sz, Sides(), twipsRadii);
1544   const nscoord twipsPerPixel = aPresContext->DevPixelsToAppUnits(1);
1545 
1546   RectCornerRadii innerRadii;
1547   if (hasBorderRadius) {
1548     RectCornerRadii borderRadii;
1549 
1550     ComputePixelRadii(twipsRadii, twipsPerPixel, &borderRadii);
1551     Float borderSizes[4] = {
1552       Float(border.top / twipsPerPixel),
1553       Float(border.right / twipsPerPixel),
1554       Float(border.bottom / twipsPerPixel),
1555       Float(border.left / twipsPerPixel)
1556     };
1557     nsCSSBorderRenderer::ComputeInnerRadii(borderRadii, borderSizes,
1558                                            &innerRadii);
1559   }
1560 
1561   for (uint32_t i = shadows->Length(); i > 0; --i) {
1562     nsCSSShadowItem* shadowItem = shadows->ShadowAt(i - 1);
1563     if (!shadowItem->mInset)
1564       continue;
1565 
1566     // shadowPaintRect: the area to paint on the temp surface
1567     // shadowClipRect: the area on the temporary surface within shadowPaintRect
1568     //                 that we will NOT paint in
1569     nscoord blurRadius = shadowItem->mRadius;
1570     nsMargin blurMargin =
1571       nsContextBoxBlur::GetBlurRadiusMargin(blurRadius, twipsPerPixel);
1572     nsRect shadowPaintRect = paddingRect;
1573     shadowPaintRect.Inflate(blurMargin);
1574 
1575     Rect shadowPaintGfxRect = NSRectToRect(shadowPaintRect, twipsPerPixel);
1576     shadowPaintGfxRect.RoundOut();
1577 
1578     // Round the spread radius to device pixels (by truncation).
1579     // This mostly matches what we do for borders, except that we don't round
1580     // up values between zero and one device pixels to one device pixel.
1581     // This way of rounding is symmetric around zero, which makes sense for
1582     // the spread radius.
1583     int32_t spreadDistance = shadowItem->mSpread / twipsPerPixel;
1584     nscoord spreadDistanceAppUnits = aPresContext->DevPixelsToAppUnits(spreadDistance);
1585 
1586     nsRect shadowClipRect = paddingRect;
1587     shadowClipRect.MoveBy(shadowItem->mXOffset, shadowItem->mYOffset);
1588     shadowClipRect.Deflate(spreadDistanceAppUnits, spreadDistanceAppUnits);
1589 
1590     Rect shadowClipGfxRect = NSRectToRect(shadowClipRect, twipsPerPixel);
1591     shadowClipGfxRect.Round();
1592 
1593     RectCornerRadii clipRectRadii;
1594     if (hasBorderRadius) {
1595       // Calculate the radii the inner clipping rect will have
1596       Float borderSizes[4] = {0, 0, 0, 0};
1597 
1598       // See PaintBoxShadowOuter and bug 514670
1599       if (innerRadii[C_TL].width > 0 || innerRadii[C_BL].width > 0) {
1600         borderSizes[NS_SIDE_LEFT] = spreadDistance;
1601       }
1602 
1603       if (innerRadii[C_TL].height > 0 || innerRadii[C_TR].height > 0) {
1604         borderSizes[NS_SIDE_TOP] = spreadDistance;
1605       }
1606 
1607       if (innerRadii[C_TR].width > 0 || innerRadii[C_BR].width > 0) {
1608         borderSizes[NS_SIDE_RIGHT] = spreadDistance;
1609       }
1610 
1611       if (innerRadii[C_BL].height > 0 || innerRadii[C_BR].height > 0) {
1612         borderSizes[NS_SIDE_BOTTOM] = spreadDistance;
1613       }
1614 
1615       nsCSSBorderRenderer::ComputeInnerRadii(innerRadii, borderSizes,
1616                                              &clipRectRadii);
1617     }
1618 
1619     // Set the "skip rect" to the area within the frame that we don't paint in,
1620     // including after blurring.
1621     nsRect skipRect = shadowClipRect;
1622     skipRect.Deflate(blurMargin);
1623     gfxRect skipGfxRect = nsLayoutUtils::RectToGfxRect(skipRect, twipsPerPixel);
1624     if (hasBorderRadius) {
1625       skipGfxRect.Deflate(gfxMargin(
1626           std::max(clipRectRadii[C_TL].height, clipRectRadii[C_TR].height), 0,
1627           std::max(clipRectRadii[C_BL].height, clipRectRadii[C_BR].height), 0));
1628     }
1629 
1630     // When there's a blur radius, gfxAlphaBoxBlur leaves the skiprect area
1631     // unchanged. And by construction the gfxSkipRect is not touched by the
1632     // rendered shadow (even after blurring), so those pixels must be completely
1633     // transparent in the shadow, so drawing them changes nothing.
1634     gfxContext* renderContext = aRenderingContext.ThebesContext();
1635     DrawTarget* drawTarget = renderContext->GetDrawTarget();
1636     nsContextBoxBlur blurringArea;
1637 
1638     // Clip the context to the area of the frame's padding rect, so no part of the
1639     // shadow is painted outside. Also cut out anything beyond where the inset shadow
1640     // will be.
1641     Rect shadowGfxRect = NSRectToRect(paddingRect, twipsPerPixel);
1642     shadowGfxRect.Round();
1643 
1644     // Set the shadow color; if not specified, use the foreground color
1645     Color shadowColor = Color::FromABGR(shadowItem->mHasColor ?
1646                                           shadowItem->mColor :
1647                                           aForFrame->StyleColor()->mColor);
1648 
1649     renderContext->Save();
1650 
1651     // This clips the outside border radius.
1652     // clipRectRadii is the border radius inside the inset shadow.
1653     if (hasBorderRadius) {
1654       RefPtr<Path> roundedRect =
1655         MakePathForRoundedRect(*drawTarget, shadowGfxRect, innerRadii);
1656       renderContext->Clip(roundedRect);
1657     } else {
1658       renderContext->Clip(shadowGfxRect);
1659     }
1660 
1661     nsContextBoxBlur insetBoxBlur;
1662     gfxRect destRect = nsLayoutUtils::RectToGfxRect(shadowPaintRect, twipsPerPixel);
1663     Point shadowOffset(shadowItem->mXOffset / twipsPerPixel,
1664                        shadowItem->mYOffset / twipsPerPixel);
1665 
1666     insetBoxBlur.InsetBoxBlur(renderContext, ToRect(destRect),
1667                               shadowClipGfxRect, shadowColor,
1668                               blurRadius, spreadDistanceAppUnits,
1669                               twipsPerPixel, hasBorderRadius,
1670                               clipRectRadii, ToRect(skipGfxRect),
1671                               shadowOffset);
1672     renderContext->Restore();
1673   }
1674 }
1675 
1676 /* static */
1677 nsCSSRendering::PaintBGParams
ForAllLayers(nsPresContext & aPresCtx,nsRenderingContext & aRenderingCtx,const nsRect & aDirtyRect,const nsRect & aBorderArea,nsIFrame * aFrame,uint32_t aPaintFlags)1678 nsCSSRendering::PaintBGParams::ForAllLayers(nsPresContext& aPresCtx,
1679                                             nsRenderingContext& aRenderingCtx,
1680                                             const nsRect& aDirtyRect,
1681                                             const nsRect& aBorderArea,
1682                                             nsIFrame *aFrame,
1683                                             uint32_t aPaintFlags)
1684 {
1685   MOZ_ASSERT(aFrame);
1686 
1687   PaintBGParams result(aPresCtx, aRenderingCtx, aDirtyRect, aBorderArea, aFrame,
1688     aPaintFlags, -1, CompositionOp::OP_OVER);
1689 
1690   return result;
1691 }
1692 
1693 /* static */
1694 nsCSSRendering::PaintBGParams
ForSingleLayer(nsPresContext & aPresCtx,nsRenderingContext & aRenderingCtx,const nsRect & aDirtyRect,const nsRect & aBorderArea,nsIFrame * aFrame,uint32_t aPaintFlags,int32_t aLayer,CompositionOp aCompositionOp)1695 nsCSSRendering::PaintBGParams::ForSingleLayer(nsPresContext& aPresCtx,
1696                                               nsRenderingContext& aRenderingCtx,
1697                                               const nsRect& aDirtyRect,
1698                                               const nsRect& aBorderArea,
1699                                               nsIFrame *aFrame,
1700                                               uint32_t aPaintFlags,
1701                                               int32_t aLayer,
1702                                               CompositionOp aCompositionOp)
1703 {
1704   MOZ_ASSERT(aFrame && (aLayer != -1));
1705 
1706   PaintBGParams result(aPresCtx, aRenderingCtx, aDirtyRect, aBorderArea, aFrame,
1707     aPaintFlags, aLayer, aCompositionOp);
1708 
1709   return result;
1710 }
1711 
1712 DrawResult
PaintBackground(const PaintBGParams & aParams)1713 nsCSSRendering::PaintBackground(const PaintBGParams& aParams)
1714 {
1715   PROFILER_LABEL("nsCSSRendering", "PaintBackground",
1716     js::ProfileEntry::Category::GRAPHICS);
1717 
1718   NS_PRECONDITION(aParams.frame,
1719                   "Frame is expected to be provided to PaintBackground");
1720 
1721   nsStyleContext *sc;
1722   if (!FindBackground(aParams.frame, &sc)) {
1723     // We don't want to bail out if moz-appearance is set on a root
1724     // node. If it has a parent content node, bail because it's not
1725     // a root, otherwise keep going in order to let the theme stuff
1726     // draw the background. The canvas really should be drawing the
1727     // bg, but there's no way to hook that up via css.
1728     if (!aParams.frame->StyleDisplay()->mAppearance) {
1729       return DrawResult::SUCCESS;
1730     }
1731 
1732     nsIContent* content = aParams.frame->GetContent();
1733     if (!content || content->GetParent()) {
1734       return DrawResult::SUCCESS;
1735     }
1736 
1737     sc = aParams.frame->StyleContext();
1738   }
1739 
1740   return PaintBackgroundWithSC(aParams, sc, *aParams.frame->StyleBorder());
1741 }
1742 
1743 static bool
IsOpaqueBorderEdge(const nsStyleBorder & aBorder,mozilla::css::Side aSide)1744 IsOpaqueBorderEdge(const nsStyleBorder& aBorder, mozilla::css::Side aSide)
1745 {
1746   if (aBorder.GetComputedBorder().Side(aSide) == 0)
1747     return true;
1748   switch (aBorder.GetBorderStyle(aSide)) {
1749   case NS_STYLE_BORDER_STYLE_SOLID:
1750   case NS_STYLE_BORDER_STYLE_GROOVE:
1751   case NS_STYLE_BORDER_STYLE_RIDGE:
1752   case NS_STYLE_BORDER_STYLE_INSET:
1753   case NS_STYLE_BORDER_STYLE_OUTSET:
1754     break;
1755   default:
1756     return false;
1757   }
1758 
1759   // If we're using a border image, assume it's not fully opaque,
1760   // because we may not even have the image loaded at this point, and
1761   // even if we did, checking whether the relevant tile is fully
1762   // opaque would be too much work.
1763   if (aBorder.mBorderImageSource.GetType() != eStyleImageType_Null)
1764     return false;
1765 
1766   StyleComplexColor color = aBorder.mBorderColor[aSide];
1767   // We don't know the foreground color here, so if it's being used
1768   // we must assume it might be transparent.
1769   if (!color.IsNumericColor()) {
1770     return false;
1771   }
1772   return NS_GET_A(color.mColor) == 255;
1773 }
1774 
1775 /**
1776  * Returns true if all border edges are either missing or opaque.
1777  */
1778 static bool
IsOpaqueBorder(const nsStyleBorder & aBorder)1779 IsOpaqueBorder(const nsStyleBorder& aBorder)
1780 {
1781   if (aBorder.mBorderColors)
1782     return false;
1783   NS_FOR_CSS_SIDES(i) {
1784     if (!IsOpaqueBorderEdge(aBorder, i))
1785       return false;
1786   }
1787   return true;
1788 }
1789 
1790 static inline void
SetupDirtyRects(const nsRect & aBGClipArea,const nsRect & aCallerDirtyRect,nscoord aAppUnitsPerPixel,nsRect * aDirtyRect,gfxRect * aDirtyRectGfx)1791 SetupDirtyRects(const nsRect& aBGClipArea, const nsRect& aCallerDirtyRect,
1792                 nscoord aAppUnitsPerPixel,
1793                 /* OUT: */
1794                 nsRect* aDirtyRect, gfxRect* aDirtyRectGfx)
1795 {
1796   aDirtyRect->IntersectRect(aBGClipArea, aCallerDirtyRect);
1797 
1798   // Compute the Thebes equivalent of the dirtyRect.
1799   *aDirtyRectGfx = nsLayoutUtils::RectToGfxRect(*aDirtyRect, aAppUnitsPerPixel);
1800   NS_WARNING_ASSERTION(aDirtyRect->IsEmpty() || !aDirtyRectGfx->IsEmpty(),
1801                        "converted dirty rect should not be empty");
1802   MOZ_ASSERT(!aDirtyRect->IsEmpty() || aDirtyRectGfx->IsEmpty(),
1803              "second should be empty if first is");
1804 }
1805 
1806 /* static */ void
GetImageLayerClip(const nsStyleImageLayers::Layer & aLayer,nsIFrame * aForFrame,const nsStyleBorder & aBorder,const nsRect & aBorderArea,const nsRect & aCallerDirtyRect,bool aWillPaintBorder,nscoord aAppUnitsPerPixel,ImageLayerClipState * aClipState)1807 nsCSSRendering::GetImageLayerClip(const nsStyleImageLayers::Layer& aLayer,
1808                                   nsIFrame* aForFrame, const nsStyleBorder& aBorder,
1809                                   const nsRect& aBorderArea, const nsRect& aCallerDirtyRect,
1810                                   bool aWillPaintBorder, nscoord aAppUnitsPerPixel,
1811                                   /* out */ ImageLayerClipState* aClipState)
1812 {
1813   // Compute the outermost boundary of the area that might be painted.
1814   // Same coordinate space as aBorderArea.
1815   Sides skipSides = aForFrame->GetSkipSides();
1816   nsRect clipBorderArea =
1817     ::BoxDecorationRectForBorder(aForFrame, aBorderArea, skipSides, &aBorder);
1818 
1819   bool haveRoundedCorners = GetRadii(aForFrame, aBorder, aBorderArea,
1820                                      clipBorderArea, aClipState->mRadii);
1821 
1822   uint8_t backgroundClip = aLayer.mClip;
1823 
1824   bool isSolidBorder =
1825       aWillPaintBorder && IsOpaqueBorder(aBorder);
1826   if (isSolidBorder && backgroundClip == NS_STYLE_IMAGELAYER_CLIP_BORDER) {
1827     // If we have rounded corners, we need to inflate the background
1828     // drawing area a bit to avoid seams between the border and
1829     // background.
1830     backgroundClip = haveRoundedCorners ?
1831       NS_STYLE_IMAGELAYER_CLIP_MOZ_ALMOST_PADDING : NS_STYLE_IMAGELAYER_CLIP_PADDING;
1832   }
1833 
1834   aClipState->mBGClipArea = clipBorderArea;
1835   aClipState->mHasAdditionalBGClipArea = false;
1836   aClipState->mCustomClip = false;
1837 
1838   if (aForFrame->GetType() == nsGkAtoms::scrollFrame &&
1839       NS_STYLE_IMAGELAYER_ATTACHMENT_LOCAL == aLayer.mAttachment) {
1840     // As of this writing, this is still in discussion in the CSS Working Group
1841     // http://lists.w3.org/Archives/Public/www-style/2013Jul/0250.html
1842 
1843     // The rectangle for 'background-clip' scrolls with the content,
1844     // but the background is also clipped at a non-scrolling 'padding-box'
1845     // like the content. (See below.)
1846     // Therefore, only 'content-box' makes a difference here.
1847     if (backgroundClip == NS_STYLE_IMAGELAYER_CLIP_CONTENT) {
1848       nsIScrollableFrame* scrollableFrame = do_QueryFrame(aForFrame);
1849       // Clip at a rectangle attached to the scrolled content.
1850       aClipState->mHasAdditionalBGClipArea = true;
1851       aClipState->mAdditionalBGClipArea = nsRect(
1852         aClipState->mBGClipArea.TopLeft()
1853           + scrollableFrame->GetScrolledFrame()->GetPosition()
1854           // For the dir=rtl case:
1855           + scrollableFrame->GetScrollRange().TopLeft(),
1856         scrollableFrame->GetScrolledRect().Size());
1857       nsMargin padding = aForFrame->GetUsedPadding();
1858       // padding-bottom is ignored on scrollable frames:
1859       // https://bugzilla.mozilla.org/show_bug.cgi?id=748518
1860       padding.bottom = 0;
1861       padding.ApplySkipSides(skipSides);
1862       aClipState->mAdditionalBGClipArea.Deflate(padding);
1863     }
1864 
1865     // Also clip at a non-scrolling, rounded-corner 'padding-box',
1866     // same as the scrolled content because of the 'overflow' property.
1867     backgroundClip = NS_STYLE_IMAGELAYER_CLIP_PADDING;
1868   }
1869 
1870   if (backgroundClip != NS_STYLE_IMAGELAYER_CLIP_BORDER &&
1871       backgroundClip != NS_STYLE_IMAGELAYER_CLIP_TEXT) {
1872     nsMargin border = aForFrame->GetUsedBorder();
1873     if (backgroundClip == NS_STYLE_IMAGELAYER_CLIP_MOZ_ALMOST_PADDING) {
1874       // Reduce |border| by 1px (device pixels) on all sides, if
1875       // possible, so that we don't get antialiasing seams between the
1876       // background and border.
1877       border.top = std::max(0, border.top - aAppUnitsPerPixel);
1878       border.right = std::max(0, border.right - aAppUnitsPerPixel);
1879       border.bottom = std::max(0, border.bottom - aAppUnitsPerPixel);
1880       border.left = std::max(0, border.left - aAppUnitsPerPixel);
1881     } else if (backgroundClip != NS_STYLE_IMAGELAYER_CLIP_PADDING) {
1882       NS_ASSERTION(backgroundClip == NS_STYLE_IMAGELAYER_CLIP_CONTENT,
1883                    "unexpected background-clip");
1884       border += aForFrame->GetUsedPadding();
1885     }
1886     border.ApplySkipSides(skipSides);
1887     aClipState->mBGClipArea.Deflate(border);
1888 
1889     if (haveRoundedCorners) {
1890       nsIFrame::InsetBorderRadii(aClipState->mRadii, border);
1891     }
1892   }
1893 
1894   if (haveRoundedCorners) {
1895     auto d2a = aForFrame->PresContext()->AppUnitsPerDevPixel();
1896     nsCSSRendering::ComputePixelRadii(aClipState->mRadii, d2a, &aClipState->mClippedRadii);
1897     aClipState->mHasRoundedCorners = true;
1898   } else {
1899     aClipState->mHasRoundedCorners = false;
1900   }
1901 
1902 
1903   if (!haveRoundedCorners && aClipState->mHasAdditionalBGClipArea) {
1904     // Do the intersection here to account for the fast path(?) below.
1905     aClipState->mBGClipArea =
1906       aClipState->mBGClipArea.Intersect(aClipState->mAdditionalBGClipArea);
1907     aClipState->mHasAdditionalBGClipArea = false;
1908   }
1909 
1910   SetupDirtyRects(aClipState->mBGClipArea, aCallerDirtyRect, aAppUnitsPerPixel,
1911                   &aClipState->mDirtyRect, &aClipState->mDirtyRectGfx);
1912 }
1913 
1914 static void
SetupImageLayerClip(nsCSSRendering::ImageLayerClipState & aClipState,gfxContext * aCtx,nscoord aAppUnitsPerPixel,gfxContextAutoSaveRestore * aAutoSR)1915 SetupImageLayerClip(nsCSSRendering::ImageLayerClipState& aClipState,
1916                     gfxContext *aCtx, nscoord aAppUnitsPerPixel,
1917                     gfxContextAutoSaveRestore* aAutoSR)
1918 {
1919   if (aClipState.mDirtyRectGfx.IsEmpty()) {
1920     // Our caller won't draw anything under this condition, so no need
1921     // to set more up.
1922     return;
1923   }
1924 
1925   if (aClipState.mCustomClip) {
1926     // We don't support custom clips and rounded corners, arguably a bug, but
1927     // table painting seems to depend on it.
1928     return;
1929   }
1930 
1931   DrawTarget* drawTarget = aCtx->GetDrawTarget();
1932 
1933   // If we have rounded corners, clip all subsequent drawing to the
1934   // rounded rectangle defined by bgArea and bgRadii (we don't know
1935   // whether the rounded corners intrude on the dirtyRect or not).
1936   // Do not do this if we have a caller-provided clip rect --
1937   // as above with bgArea, arguably a bug, but table painting seems
1938   // to depend on it.
1939 
1940   if (aClipState.mHasAdditionalBGClipArea) {
1941     gfxRect bgAreaGfx = nsLayoutUtils::RectToGfxRect(
1942       aClipState.mAdditionalBGClipArea, aAppUnitsPerPixel);
1943     bgAreaGfx.Round();
1944     bgAreaGfx.Condition();
1945 
1946     aAutoSR->EnsureSaved(aCtx);
1947     aCtx->NewPath();
1948     aCtx->Rectangle(bgAreaGfx, true);
1949     aCtx->Clip();
1950   }
1951 
1952   if (aClipState.mHasRoundedCorners) {
1953     Rect bgAreaGfx = NSRectToRect(aClipState.mBGClipArea, aAppUnitsPerPixel);
1954     bgAreaGfx.Round();
1955 
1956     if (bgAreaGfx.IsEmpty()) {
1957       // I think it's become possible to hit this since
1958       // http://hg.mozilla.org/mozilla-central/rev/50e934e4979b landed.
1959       NS_WARNING("converted background area should not be empty");
1960       // Make our caller not do anything.
1961       aClipState.mDirtyRectGfx.SizeTo(gfxSize(0.0, 0.0));
1962       return;
1963     }
1964 
1965     aAutoSR->EnsureSaved(aCtx);
1966 
1967     RefPtr<Path> roundedRect =
1968       MakePathForRoundedRect(*drawTarget, bgAreaGfx, aClipState.mClippedRadii);
1969     aCtx->Clip(roundedRect);
1970   }
1971 }
1972 
1973 static void
DrawBackgroundColor(nsCSSRendering::ImageLayerClipState & aClipState,gfxContext * aCtx,nscoord aAppUnitsPerPixel)1974 DrawBackgroundColor(nsCSSRendering::ImageLayerClipState& aClipState,
1975                     gfxContext *aCtx, nscoord aAppUnitsPerPixel)
1976 {
1977   if (aClipState.mDirtyRectGfx.IsEmpty()) {
1978     // Our caller won't draw anything under this condition, so no need
1979     // to set more up.
1980     return;
1981   }
1982 
1983   DrawTarget* drawTarget = aCtx->GetDrawTarget();
1984 
1985   // We don't support custom clips and rounded corners, arguably a bug, but
1986   // table painting seems to depend on it.
1987   if (!aClipState.mHasRoundedCorners || aClipState.mCustomClip) {
1988     aCtx->NewPath();
1989     aCtx->Rectangle(aClipState.mDirtyRectGfx, true);
1990     aCtx->Fill();
1991     return;
1992   }
1993 
1994   Rect bgAreaGfx = NSRectToRect(aClipState.mBGClipArea, aAppUnitsPerPixel);
1995   bgAreaGfx.Round();
1996 
1997   if (bgAreaGfx.IsEmpty()) {
1998     // I think it's become possible to hit this since
1999     // http://hg.mozilla.org/mozilla-central/rev/50e934e4979b landed.
2000     NS_WARNING("converted background area should not be empty");
2001     // Make our caller not do anything.
2002     aClipState.mDirtyRectGfx.SizeTo(gfxSize(0.0, 0.0));
2003     return;
2004   }
2005 
2006   aCtx->Save();
2007   gfxRect dirty = ThebesRect(bgAreaGfx).Intersect(aClipState.mDirtyRectGfx);
2008 
2009   aCtx->NewPath();
2010   aCtx->Rectangle(dirty, true);
2011   aCtx->Clip();
2012 
2013   if (aClipState.mHasAdditionalBGClipArea) {
2014     gfxRect bgAdditionalAreaGfx = nsLayoutUtils::RectToGfxRect(
2015       aClipState.mAdditionalBGClipArea, aAppUnitsPerPixel);
2016     bgAdditionalAreaGfx.Round();
2017     bgAdditionalAreaGfx.Condition();
2018     aCtx->NewPath();
2019     aCtx->Rectangle(bgAdditionalAreaGfx, true);
2020     aCtx->Clip();
2021   }
2022 
2023   RefPtr<Path> roundedRect =
2024     MakePathForRoundedRect(*drawTarget, bgAreaGfx, aClipState.mClippedRadii);
2025   aCtx->SetPath(roundedRect);
2026   aCtx->Fill();
2027   aCtx->Restore();
2028 }
2029 
2030 nscolor
DetermineBackgroundColor(nsPresContext * aPresContext,nsStyleContext * aStyleContext,nsIFrame * aFrame,bool & aDrawBackgroundImage,bool & aDrawBackgroundColor)2031 nsCSSRendering::DetermineBackgroundColor(nsPresContext* aPresContext,
2032                                          nsStyleContext* aStyleContext,
2033                                          nsIFrame* aFrame,
2034                                          bool& aDrawBackgroundImage,
2035                                          bool& aDrawBackgroundColor)
2036 {
2037   aDrawBackgroundImage = true;
2038   aDrawBackgroundColor = true;
2039 
2040   const nsStyleVisibility* visibility = aStyleContext->StyleVisibility();
2041 
2042   if (visibility->mColorAdjust != NS_STYLE_COLOR_ADJUST_EXACT &&
2043       aFrame->HonorPrintBackgroundSettings()) {
2044     aDrawBackgroundImage = aPresContext->GetBackgroundImageDraw();
2045     aDrawBackgroundColor = aPresContext->GetBackgroundColorDraw();
2046   }
2047 
2048   const nsStyleBackground *bg = aStyleContext->StyleBackground();
2049   nscolor bgColor;
2050   if (aDrawBackgroundColor) {
2051     bgColor =
2052       aStyleContext->GetVisitedDependentColor(eCSSProperty_background_color);
2053     if (NS_GET_A(bgColor) == 0) {
2054       aDrawBackgroundColor = false;
2055     }
2056   } else {
2057     // If GetBackgroundColorDraw() is false, we are still expected to
2058     // draw color in the background of any frame that's not completely
2059     // transparent, but we are expected to use white instead of whatever
2060     // color was specified.
2061     bgColor = NS_RGB(255, 255, 255);
2062     if (aDrawBackgroundImage || !bg->IsTransparent()) {
2063       aDrawBackgroundColor = true;
2064     } else {
2065       bgColor = NS_RGBA(0,0,0,0);
2066     }
2067   }
2068 
2069   // We can skip painting the background color if a background image is opaque.
2070   nsStyleImageLayers::Repeat repeat = bg->BottomLayer().mRepeat;
2071   bool xFullRepeat = repeat.mXRepeat == NS_STYLE_IMAGELAYER_REPEAT_REPEAT ||
2072                      repeat.mXRepeat == NS_STYLE_IMAGELAYER_REPEAT_ROUND;
2073   bool yFullRepeat = repeat.mYRepeat == NS_STYLE_IMAGELAYER_REPEAT_REPEAT ||
2074                      repeat.mYRepeat == NS_STYLE_IMAGELAYER_REPEAT_ROUND;
2075   if (aDrawBackgroundColor &&
2076       xFullRepeat && yFullRepeat &&
2077       bg->BottomLayer().mImage.IsOpaque() &&
2078       bg->BottomLayer().mBlendMode == NS_STYLE_BLEND_NORMAL) {
2079     aDrawBackgroundColor = false;
2080   }
2081 
2082   return bgColor;
2083 }
2084 
2085 static gfxFloat
ConvertGradientValueToPixels(const nsStyleCoord & aCoord,gfxFloat aFillLength,int32_t aAppUnitsPerPixel)2086 ConvertGradientValueToPixels(const nsStyleCoord& aCoord,
2087                              gfxFloat aFillLength,
2088                              int32_t aAppUnitsPerPixel)
2089 {
2090   switch (aCoord.GetUnit()) {
2091     case eStyleUnit_Percent:
2092       return aCoord.GetPercentValue() * aFillLength;
2093     case eStyleUnit_Coord:
2094       return NSAppUnitsToFloatPixels(aCoord.GetCoordValue(), aAppUnitsPerPixel);
2095     case eStyleUnit_Calc: {
2096       const nsStyleCoord::Calc *calc = aCoord.GetCalcValue();
2097       return calc->mPercent * aFillLength +
2098              NSAppUnitsToFloatPixels(calc->mLength, aAppUnitsPerPixel);
2099     }
2100     default:
2101       NS_WARNING("Unexpected coord unit");
2102       return 0;
2103   }
2104 }
2105 
2106 // Given a box with size aBoxSize and origin (0,0), and an angle aAngle,
2107 // and a starting point for the gradient line aStart, find the endpoint of
2108 // the gradient line --- the intersection of the gradient line with a line
2109 // perpendicular to aAngle that passes through the farthest corner in the
2110 // direction aAngle.
2111 static gfxPoint
ComputeGradientLineEndFromAngle(const gfxPoint & aStart,double aAngle,const gfxSize & aBoxSize)2112 ComputeGradientLineEndFromAngle(const gfxPoint& aStart,
2113                                 double aAngle,
2114                                 const gfxSize& aBoxSize)
2115 {
2116   double dx = cos(-aAngle);
2117   double dy = sin(-aAngle);
2118   gfxPoint farthestCorner(dx > 0 ? aBoxSize.width : 0,
2119                           dy > 0 ? aBoxSize.height : 0);
2120   gfxPoint delta = farthestCorner - aStart;
2121   double u = delta.x*dy - delta.y*dx;
2122   return farthestCorner + gfxPoint(-u*dy, u*dx);
2123 }
2124 
2125 // Compute the start and end points of the gradient line for a linear gradient.
2126 static void
ComputeLinearGradientLine(nsPresContext * aPresContext,nsStyleGradient * aGradient,const gfxSize & aBoxSize,gfxPoint * aLineStart,gfxPoint * aLineEnd)2127 ComputeLinearGradientLine(nsPresContext* aPresContext,
2128                           nsStyleGradient* aGradient,
2129                           const gfxSize& aBoxSize,
2130                           gfxPoint* aLineStart,
2131                           gfxPoint* aLineEnd)
2132 {
2133   if (aGradient->mBgPosX.GetUnit() == eStyleUnit_None) {
2134     double angle;
2135     if (aGradient->mAngle.IsAngleValue()) {
2136       angle = aGradient->mAngle.GetAngleValueInRadians();
2137       if (!aGradient->mLegacySyntax) {
2138         angle = M_PI_2 - angle;
2139       }
2140     } else {
2141       angle = -M_PI_2; // defaults to vertical gradient starting from top
2142     }
2143     gfxPoint center(aBoxSize.width/2, aBoxSize.height/2);
2144     *aLineEnd = ComputeGradientLineEndFromAngle(center, angle, aBoxSize);
2145     *aLineStart = gfxPoint(aBoxSize.width, aBoxSize.height) - *aLineEnd;
2146   } else if (!aGradient->mLegacySyntax) {
2147     float xSign = aGradient->mBgPosX.GetPercentValue() * 2 - 1;
2148     float ySign = 1 - aGradient->mBgPosY.GetPercentValue() * 2;
2149     double angle = atan2(ySign * aBoxSize.width, xSign * aBoxSize.height);
2150     gfxPoint center(aBoxSize.width/2, aBoxSize.height/2);
2151     *aLineEnd = ComputeGradientLineEndFromAngle(center, angle, aBoxSize);
2152     *aLineStart = gfxPoint(aBoxSize.width, aBoxSize.height) - *aLineEnd;
2153   } else {
2154     int32_t appUnitsPerPixel = aPresContext->AppUnitsPerDevPixel();
2155     *aLineStart = gfxPoint(
2156       ConvertGradientValueToPixels(aGradient->mBgPosX, aBoxSize.width,
2157                                    appUnitsPerPixel),
2158       ConvertGradientValueToPixels(aGradient->mBgPosY, aBoxSize.height,
2159                                    appUnitsPerPixel));
2160     if (aGradient->mAngle.IsAngleValue()) {
2161       MOZ_ASSERT(aGradient->mLegacySyntax);
2162       double angle = aGradient->mAngle.GetAngleValueInRadians();
2163       *aLineEnd = ComputeGradientLineEndFromAngle(*aLineStart, angle, aBoxSize);
2164     } else {
2165       // No angle, the line end is just the reflection of the start point
2166       // through the center of the box
2167       *aLineEnd = gfxPoint(aBoxSize.width, aBoxSize.height) - *aLineStart;
2168     }
2169   }
2170 }
2171 
2172 // Compute the start and end points of the gradient line for a radial gradient.
2173 // Also returns the horizontal and vertical radii defining the circle or
2174 // ellipse to use.
2175 static void
ComputeRadialGradientLine(nsPresContext * aPresContext,nsStyleGradient * aGradient,const gfxSize & aBoxSize,gfxPoint * aLineStart,gfxPoint * aLineEnd,double * aRadiusX,double * aRadiusY)2176 ComputeRadialGradientLine(nsPresContext* aPresContext,
2177                           nsStyleGradient* aGradient,
2178                           const gfxSize& aBoxSize,
2179                           gfxPoint* aLineStart,
2180                           gfxPoint* aLineEnd,
2181                           double* aRadiusX,
2182                           double* aRadiusY)
2183 {
2184   if (aGradient->mBgPosX.GetUnit() == eStyleUnit_None) {
2185     // Default line start point is the center of the box
2186     *aLineStart = gfxPoint(aBoxSize.width/2, aBoxSize.height/2);
2187   } else {
2188     int32_t appUnitsPerPixel = aPresContext->AppUnitsPerDevPixel();
2189     *aLineStart = gfxPoint(
2190       ConvertGradientValueToPixels(aGradient->mBgPosX, aBoxSize.width,
2191                                    appUnitsPerPixel),
2192       ConvertGradientValueToPixels(aGradient->mBgPosY, aBoxSize.height,
2193                                    appUnitsPerPixel));
2194   }
2195 
2196   // Compute gradient shape: the x and y radii of an ellipse.
2197   double radiusX, radiusY;
2198   double leftDistance = Abs(aLineStart->x);
2199   double rightDistance = Abs(aBoxSize.width - aLineStart->x);
2200   double topDistance = Abs(aLineStart->y);
2201   double bottomDistance = Abs(aBoxSize.height - aLineStart->y);
2202   switch (aGradient->mSize) {
2203   case NS_STYLE_GRADIENT_SIZE_CLOSEST_SIDE:
2204     radiusX = std::min(leftDistance, rightDistance);
2205     radiusY = std::min(topDistance, bottomDistance);
2206     if (aGradient->mShape == NS_STYLE_GRADIENT_SHAPE_CIRCULAR) {
2207       radiusX = radiusY = std::min(radiusX, radiusY);
2208     }
2209     break;
2210   case NS_STYLE_GRADIENT_SIZE_CLOSEST_CORNER: {
2211     // Compute x and y distances to nearest corner
2212     double offsetX = std::min(leftDistance, rightDistance);
2213     double offsetY = std::min(topDistance, bottomDistance);
2214     if (aGradient->mShape == NS_STYLE_GRADIENT_SHAPE_CIRCULAR) {
2215       radiusX = radiusY = NS_hypot(offsetX, offsetY);
2216     } else {
2217       // maintain aspect ratio
2218       radiusX = offsetX*M_SQRT2;
2219       radiusY = offsetY*M_SQRT2;
2220     }
2221     break;
2222   }
2223   case NS_STYLE_GRADIENT_SIZE_FARTHEST_SIDE:
2224     radiusX = std::max(leftDistance, rightDistance);
2225     radiusY = std::max(topDistance, bottomDistance);
2226     if (aGradient->mShape == NS_STYLE_GRADIENT_SHAPE_CIRCULAR) {
2227       radiusX = radiusY = std::max(radiusX, radiusY);
2228     }
2229     break;
2230   case NS_STYLE_GRADIENT_SIZE_FARTHEST_CORNER: {
2231     // Compute x and y distances to nearest corner
2232     double offsetX = std::max(leftDistance, rightDistance);
2233     double offsetY = std::max(topDistance, bottomDistance);
2234     if (aGradient->mShape == NS_STYLE_GRADIENT_SHAPE_CIRCULAR) {
2235       radiusX = radiusY = NS_hypot(offsetX, offsetY);
2236     } else {
2237       // maintain aspect ratio
2238       radiusX = offsetX*M_SQRT2;
2239       radiusY = offsetY*M_SQRT2;
2240     }
2241     break;
2242   }
2243   case NS_STYLE_GRADIENT_SIZE_EXPLICIT_SIZE: {
2244     int32_t appUnitsPerPixel = aPresContext->AppUnitsPerDevPixel();
2245     radiusX = ConvertGradientValueToPixels(aGradient->mRadiusX,
2246                                            aBoxSize.width, appUnitsPerPixel);
2247     radiusY = ConvertGradientValueToPixels(aGradient->mRadiusY,
2248                                            aBoxSize.height, appUnitsPerPixel);
2249     break;
2250   }
2251   default:
2252     radiusX = radiusY = 0;
2253     MOZ_ASSERT(false, "unknown radial gradient sizing method");
2254   }
2255   *aRadiusX = radiusX;
2256   *aRadiusY = radiusY;
2257 
2258   double angle;
2259   if (aGradient->mAngle.IsAngleValue()) {
2260     angle = aGradient->mAngle.GetAngleValueInRadians();
2261   } else {
2262     // Default angle is 0deg
2263     angle = 0.0;
2264   }
2265 
2266   // The gradient line end point is where the gradient line intersects
2267   // the ellipse.
2268   *aLineEnd = *aLineStart + gfxPoint(radiusX*cos(-angle), radiusY*sin(-angle));
2269 }
2270 
2271 
Interpolate(float aF1,float aF2,float aFrac)2272 static float Interpolate(float aF1, float aF2, float aFrac)
2273 {
2274   return aF1 + aFrac * (aF2 - aF1);
2275 }
2276 
2277 // Returns aFrac*aC2 + (1 - aFrac)*C1. The interpolation is done
2278 // in unpremultiplied space, which is what SVG gradients and cairo
2279 // gradients expect.
2280 static Color
InterpolateColor(const Color & aC1,const Color & aC2,float aFrac)2281 InterpolateColor(const Color& aC1, const Color& aC2, float aFrac)
2282 {
2283   double other = 1 - aFrac;
2284   return Color(aC2.r*aFrac + aC1.r*other,
2285                aC2.g*aFrac + aC1.g*other,
2286                aC2.b*aFrac + aC1.b*other,
2287                aC2.a*aFrac + aC1.a*other);
2288 }
2289 
2290 static nscoord
FindTileStart(nscoord aDirtyCoord,nscoord aTilePos,nscoord aTileDim)2291 FindTileStart(nscoord aDirtyCoord, nscoord aTilePos, nscoord aTileDim)
2292 {
2293   NS_ASSERTION(aTileDim > 0, "Non-positive tile dimension");
2294   double multiples = floor(double(aDirtyCoord - aTilePos)/aTileDim);
2295   return NSToCoordRound(multiples*aTileDim + aTilePos);
2296 }
2297 
2298 static gfxFloat
LinearGradientStopPositionForPoint(const gfxPoint & aGradientStart,const gfxPoint & aGradientEnd,const gfxPoint & aPoint)2299 LinearGradientStopPositionForPoint(const gfxPoint& aGradientStart,
2300                                    const gfxPoint& aGradientEnd,
2301                                    const gfxPoint& aPoint)
2302 {
2303   gfxPoint d = aGradientEnd - aGradientStart;
2304   gfxPoint p = aPoint - aGradientStart;
2305   /**
2306    * Compute a parameter t such that a line perpendicular to the
2307    * d vector, passing through aGradientStart + d*t, also
2308    * passes through aPoint.
2309    *
2310    * t is given by
2311    *   (p.x - d.x*t)*d.x + (p.y - d.y*t)*d.y = 0
2312    *
2313    * Solving for t we get
2314    *   numerator = d.x*p.x + d.y*p.y
2315    *   denominator = d.x^2 + d.y^2
2316    *   t = numerator/denominator
2317    *
2318    * In nsCSSRendering::PaintGradient we know the length of d
2319    * is not zero.
2320    */
2321   double numerator = d.x * p.x + d.y * p.y;
2322   double denominator = d.x * d.x + d.y * d.y;
2323   return numerator / denominator;
2324 }
2325 
2326 static bool
RectIsBeyondLinearGradientEdge(const gfxRect & aRect,const gfxMatrix & aPatternMatrix,const nsTArray<ColorStop> & aStops,const gfxPoint & aGradientStart,const gfxPoint & aGradientEnd,Color * aOutEdgeColor)2327 RectIsBeyondLinearGradientEdge(const gfxRect& aRect,
2328                                const gfxMatrix& aPatternMatrix,
2329                                const nsTArray<ColorStop>& aStops,
2330                                const gfxPoint& aGradientStart,
2331                                const gfxPoint& aGradientEnd,
2332                                Color* aOutEdgeColor)
2333 {
2334   gfxFloat topLeft = LinearGradientStopPositionForPoint(
2335     aGradientStart, aGradientEnd, aPatternMatrix.Transform(aRect.TopLeft()));
2336   gfxFloat topRight = LinearGradientStopPositionForPoint(
2337     aGradientStart, aGradientEnd, aPatternMatrix.Transform(aRect.TopRight()));
2338   gfxFloat bottomLeft = LinearGradientStopPositionForPoint(
2339     aGradientStart, aGradientEnd, aPatternMatrix.Transform(aRect.BottomLeft()));
2340   gfxFloat bottomRight = LinearGradientStopPositionForPoint(
2341     aGradientStart, aGradientEnd, aPatternMatrix.Transform(aRect.BottomRight()));
2342 
2343   const ColorStop& firstStop = aStops[0];
2344   if (topLeft < firstStop.mPosition && topRight < firstStop.mPosition &&
2345       bottomLeft < firstStop.mPosition && bottomRight < firstStop.mPosition) {
2346     *aOutEdgeColor = firstStop.mColor;
2347     return true;
2348   }
2349 
2350   const ColorStop& lastStop = aStops.LastElement();
2351   if (topLeft >= lastStop.mPosition && topRight >= lastStop.mPosition &&
2352       bottomLeft >= lastStop.mPosition && bottomRight >= lastStop.mPosition) {
2353     *aOutEdgeColor = lastStop.mColor;
2354     return true;
2355   }
2356 
2357   return false;
2358 }
2359 
ResolveMidpoints(nsTArray<ColorStop> & stops)2360 static void ResolveMidpoints(nsTArray<ColorStop>& stops)
2361 {
2362   for (size_t x = 1; x < stops.Length() - 1;) {
2363     if (!stops[x].mIsMidpoint) {
2364       x++;
2365       continue;
2366     }
2367 
2368     Color color1 = stops[x-1].mColor;
2369     Color color2 = stops[x+1].mColor;
2370     float offset1 = stops[x-1].mPosition;
2371     float offset2 = stops[x+1].mPosition;
2372     float offset = stops[x].mPosition;
2373     // check if everything coincides. If so, ignore the midpoint.
2374     if (offset - offset1 == offset2 - offset) {
2375       stops.RemoveElementAt(x);
2376       continue;
2377     }
2378 
2379     // Check if we coincide with the left colorstop.
2380     if (offset1 == offset) {
2381       // Morph the midpoint to a regular stop with the color of the next
2382       // color stop.
2383       stops[x].mColor = color2;
2384       stops[x].mIsMidpoint = false;
2385       continue;
2386     }
2387 
2388     // Check if we coincide with the right colorstop.
2389     if (offset2 == offset) {
2390       // Morph the midpoint to a regular stop with the color of the previous
2391       // color stop.
2392       stops[x].mColor = color1;
2393       stops[x].mIsMidpoint = false;
2394       continue;
2395     }
2396 
2397     float midpoint = (offset - offset1) / (offset2 - offset1);
2398     ColorStop newStops[9];
2399     if (midpoint > .5f) {
2400       for (size_t y = 0; y < 7; y++) {
2401         newStops[y].mPosition = offset1 + (offset - offset1) * (7 + y) / 13;
2402       }
2403 
2404       newStops[7].mPosition = offset + (offset2 - offset) / 3;
2405       newStops[8].mPosition = offset + (offset2 - offset) * 2 / 3;
2406     } else {
2407       newStops[0].mPosition = offset1 + (offset - offset1) / 3;
2408       newStops[1].mPosition = offset1 + (offset - offset1) * 2 / 3;
2409 
2410       for (size_t y = 0; y < 7; y++) {
2411         newStops[y+2].mPosition = offset + (offset2 - offset) * y / 13;
2412       }
2413     }
2414     // calculate colors
2415 
2416     for (size_t y = 0; y < 9; y++) {
2417       // Calculate the intermediate color stops per the formula of the CSS images
2418       // spec. http://dev.w3.org/csswg/css-images/#color-stop-syntax
2419       // 9 points were chosen since it is the minimum number of stops that always
2420       // give the smoothest appearace regardless of midpoint position and difference
2421       // in luminance of the end points.
2422       float relativeOffset = (newStops[y].mPosition - offset1) / (offset2 - offset1);
2423       float multiplier = powf(relativeOffset, logf(.5f) / logf(midpoint));
2424 
2425       gfx::Float red = color1.r + multiplier * (color2.r - color1.r);
2426       gfx::Float green = color1.g + multiplier * (color2.g - color1.g);
2427       gfx::Float blue = color1.b + multiplier * (color2.b - color1.b);
2428       gfx::Float alpha = color1.a + multiplier * (color2.a - color1.a);
2429 
2430       newStops[y].mColor = Color(red, green, blue, alpha);
2431     }
2432 
2433     stops.ReplaceElementsAt(x, 1, newStops, 9);
2434     x += 9;
2435   }
2436 }
2437 
2438 static Color
Premultiply(const Color & aColor)2439 Premultiply(const Color& aColor)
2440 {
2441   gfx::Float a = aColor.a;
2442   return Color(aColor.r * a, aColor.g * a, aColor.b * a, a);
2443 }
2444 
2445 static Color
Unpremultiply(const Color & aColor)2446 Unpremultiply(const Color& aColor)
2447 {
2448   gfx::Float a = aColor.a;
2449   return (a > 0.f)
2450        ? Color(aColor.r / a, aColor.g / a, aColor.b / a, a)
2451        : aColor;
2452 }
2453 
2454 static Color
TransparentColor(Color aColor)2455 TransparentColor(Color aColor) {
2456   aColor.a = 0;
2457   return aColor;
2458 }
2459 
2460 // Adjusts and adds color stops in such a way that drawing the gradient with
2461 // unpremultiplied interpolation looks nearly the same as if it were drawn with
2462 // premultiplied interpolation.
2463 static const float kAlphaIncrementPerGradientStep = 0.1f;
2464 static void
ResolvePremultipliedAlpha(nsTArray<ColorStop> & aStops)2465 ResolvePremultipliedAlpha(nsTArray<ColorStop>& aStops)
2466 {
2467   for (size_t x = 1; x < aStops.Length(); x++) {
2468     const ColorStop leftStop = aStops[x - 1];
2469     const ColorStop rightStop = aStops[x];
2470 
2471     // if the left and right stop have the same alpha value, we don't need
2472     // to do anything
2473     if (leftStop.mColor.a == rightStop.mColor.a) {
2474       continue;
2475     }
2476 
2477     // Is the stop on the left 100% transparent? If so, have it adopt the color
2478     // of the right stop
2479     if (leftStop.mColor.a == 0) {
2480       aStops[x - 1].mColor = TransparentColor(rightStop.mColor);
2481       continue;
2482     }
2483 
2484     // Is the stop on the right completely transparent?
2485     // If so, duplicate it and assign it the color on the left.
2486     if (rightStop.mColor.a == 0) {
2487       ColorStop newStop = rightStop;
2488       newStop.mColor = TransparentColor(leftStop.mColor);
2489       aStops.InsertElementAt(x, newStop);
2490       x++;
2491       continue;
2492     }
2493 
2494     // Now handle cases where one or both of the stops are partially transparent.
2495     if (leftStop.mColor.a != 1.0f || rightStop.mColor.a != 1.0f) {
2496       Color premulLeftColor = Premultiply(leftStop.mColor);
2497       Color premulRightColor = Premultiply(rightStop.mColor);
2498       // Calculate how many extra steps. We do a step per 10% transparency.
2499       size_t stepCount = NSToIntFloor(fabsf(leftStop.mColor.a - rightStop.mColor.a) / kAlphaIncrementPerGradientStep);
2500       for (size_t y = 1; y < stepCount; y++) {
2501         float frac = static_cast<float>(y) / stepCount;
2502         ColorStop newStop(Interpolate(leftStop.mPosition, rightStop.mPosition, frac), false,
2503                           Unpremultiply(InterpolateColor(premulLeftColor, premulRightColor, frac)));
2504         aStops.InsertElementAt(x, newStop);
2505         x++;
2506       }
2507     }
2508   }
2509 }
2510 
2511 static ColorStop
InterpolateColorStop(const ColorStop & aFirst,const ColorStop & aSecond,double aPosition,const Color & aDefault)2512 InterpolateColorStop(const ColorStop& aFirst, const ColorStop& aSecond,
2513                      double aPosition, const Color& aDefault)
2514 {
2515   MOZ_ASSERT(aFirst.mPosition <= aPosition);
2516   MOZ_ASSERT(aPosition <= aSecond.mPosition);
2517 
2518   double delta = aSecond.mPosition - aFirst.mPosition;
2519 
2520   if (delta < 1e-6) {
2521     return ColorStop(aPosition, false, aDefault);
2522   }
2523 
2524   return ColorStop(aPosition, false,
2525                    Unpremultiply(InterpolateColor(Premultiply(aFirst.mColor),
2526                                                   Premultiply(aSecond.mColor),
2527                                                   (aPosition - aFirst.mPosition) / delta)));
2528 }
2529 
2530 // Clamp and extend the given ColorStop array in-place to fit exactly into the
2531 // range [0, 1].
2532 static void
ClampColorStops(nsTArray<ColorStop> & aStops)2533 ClampColorStops(nsTArray<ColorStop>& aStops)
2534 {
2535   MOZ_ASSERT(aStops.Length() > 0);
2536 
2537   // If all stops are outside the range, then get rid of everything and replace
2538   // with a single colour.
2539   if (aStops.Length() < 2 || aStops[0].mPosition > 1 ||
2540       aStops.LastElement().mPosition < 0) {
2541     Color c = aStops[0].mPosition > 1 ? aStops[0].mColor : aStops.LastElement().mColor;
2542     aStops.Clear();
2543     aStops.AppendElement(ColorStop(0, false, c));
2544     return;
2545   }
2546 
2547   // Create the 0 and 1 points if they fall in the range of |aStops|, and discard
2548   // all stops outside the range [0, 1].
2549   // XXX: If we have stops positioned at 0 or 1, we only keep the innermost of
2550   // those stops. This should be fine for the current user(s) of this function.
2551   for (size_t i = aStops.Length() - 1; i > 0; i--) {
2552     if (aStops[i - 1].mPosition < 1 && aStops[i].mPosition >= 1) {
2553       // Add a point to position 1.
2554       aStops[i] = InterpolateColorStop(aStops[i - 1], aStops[i],
2555                                        /* aPosition = */ 1,
2556                                        aStops[i - 1].mColor);
2557       // Remove all the elements whose position is greater than 1.
2558       aStops.RemoveElementsAt(i + 1, aStops.Length() - (i + 1));
2559     }
2560     if (aStops[i - 1].mPosition <= 0 && aStops[i].mPosition > 0) {
2561       // Add a point to position 0.
2562       aStops[i - 1] = InterpolateColorStop(aStops[i - 1], aStops[i],
2563                                            /* aPosition = */ 0,
2564                                            aStops[i].mColor);
2565       // Remove all of the preceding stops -- they are all negative.
2566       aStops.RemoveElementsAt(0, i - 1);
2567       break;
2568     }
2569   }
2570 
2571   MOZ_ASSERT(aStops[0].mPosition >= -1e6);
2572   MOZ_ASSERT(aStops.LastElement().mPosition - 1 <= 1e6);
2573 
2574   // The end points won't exist yet if they don't fall in the original range of
2575   // |aStops|. Create them if needed.
2576   if (aStops[0].mPosition > 0) {
2577     aStops.InsertElementAt(0, ColorStop(0, false, aStops[0].mColor));
2578   }
2579   if (aStops.LastElement().mPosition < 1) {
2580     aStops.AppendElement(ColorStop(1, false, aStops.LastElement().mColor));
2581   }
2582 }
2583 
2584 void
PaintGradient(nsPresContext * aPresContext,nsRenderingContext & aRenderingContext,nsStyleGradient * aGradient,const nsRect & aDirtyRect,const nsRect & aDest,const nsRect & aFillArea,const nsSize & aRepeatSize,const CSSIntRect & aSrc,const nsSize & aIntrinsicSize)2585 nsCSSRendering::PaintGradient(nsPresContext* aPresContext,
2586                               nsRenderingContext& aRenderingContext,
2587                               nsStyleGradient* aGradient,
2588                               const nsRect& aDirtyRect,
2589                               const nsRect& aDest,
2590                               const nsRect& aFillArea,
2591                               const nsSize& aRepeatSize,
2592                               const CSSIntRect& aSrc,
2593                               const nsSize& aIntrinsicSize)
2594 {
2595   PROFILER_LABEL("nsCSSRendering", "PaintGradient",
2596     js::ProfileEntry::Category::GRAPHICS);
2597 
2598   Telemetry::AutoTimer<Telemetry::GRADIENT_DURATION, Telemetry::Microsecond> gradientTimer;
2599   if (aDest.IsEmpty() || aFillArea.IsEmpty()) {
2600     return;
2601   }
2602 
2603   gfxContext *ctx = aRenderingContext.ThebesContext();
2604   nscoord appUnitsPerDevPixel = aPresContext->AppUnitsPerDevPixel();
2605   gfxSize srcSize = gfxSize(gfxFloat(aIntrinsicSize.width)/appUnitsPerDevPixel,
2606                             gfxFloat(aIntrinsicSize.height)/appUnitsPerDevPixel);
2607 
2608   bool cellContainsFill = aDest.Contains(aFillArea);
2609 
2610   // Compute "gradient line" start and end relative to the intrinsic size of
2611   // the gradient.
2612   gfxPoint lineStart, lineEnd;
2613   double radiusX = 0, radiusY = 0; // for radial gradients only
2614   if (aGradient->mShape == NS_STYLE_GRADIENT_SHAPE_LINEAR) {
2615     ComputeLinearGradientLine(aPresContext, aGradient, srcSize,
2616                               &lineStart, &lineEnd);
2617   } else {
2618     ComputeRadialGradientLine(aPresContext, aGradient, srcSize,
2619                               &lineStart, &lineEnd, &radiusX, &radiusY);
2620   }
2621   // Avoid sending Infs or Nans to downwind draw targets.
2622   if (!lineStart.IsFinite() || !lineEnd.IsFinite()) {
2623     lineStart = lineEnd = gfxPoint(0, 0);
2624   }
2625   gfxFloat lineLength = NS_hypot(lineEnd.x - lineStart.x,
2626                                   lineEnd.y - lineStart.y);
2627 
2628   MOZ_ASSERT(aGradient->mStops.Length() >= 2,
2629              "The parser should reject gradients with less than two stops");
2630 
2631   // Build color stop array and compute stop positions
2632   nsTArray<ColorStop> stops;
2633   // If there is a run of stops before stop i that did not have specified
2634   // positions, then this is the index of the first stop in that run, otherwise
2635   // it's -1.
2636   int32_t firstUnsetPosition = -1;
2637   for (uint32_t i = 0; i < aGradient->mStops.Length(); ++i) {
2638     const nsStyleGradientStop& stop = aGradient->mStops[i];
2639     double position;
2640     switch (stop.mLocation.GetUnit()) {
2641     case eStyleUnit_None:
2642       if (i == 0) {
2643         // First stop defaults to position 0.0
2644         position = 0.0;
2645       } else if (i == aGradient->mStops.Length() - 1) {
2646         // Last stop defaults to position 1.0
2647         position = 1.0;
2648       } else {
2649         // Other stops with no specified position get their position assigned
2650         // later by interpolation, see below.
2651         // Remeber where the run of stops with no specified position starts,
2652         // if it starts here.
2653         if (firstUnsetPosition < 0) {
2654           firstUnsetPosition = i;
2655         }
2656         stops.AppendElement(ColorStop(0, stop.mIsInterpolationHint,
2657                                       Color::FromABGR(stop.mColor)));
2658         continue;
2659       }
2660       break;
2661     case eStyleUnit_Percent:
2662       position = stop.mLocation.GetPercentValue();
2663       break;
2664     case eStyleUnit_Coord:
2665       position = lineLength < 1e-6 ? 0.0 :
2666           stop.mLocation.GetCoordValue() / appUnitsPerDevPixel / lineLength;
2667       break;
2668     case eStyleUnit_Calc:
2669       nsStyleCoord::Calc *calc;
2670       calc = stop.mLocation.GetCalcValue();
2671       position = calc->mPercent +
2672           ((lineLength < 1e-6) ? 0.0 :
2673           (NSAppUnitsToFloatPixels(calc->mLength, appUnitsPerDevPixel) / lineLength));
2674       break;
2675     default:
2676       MOZ_ASSERT(false, "Unknown stop position type");
2677     }
2678 
2679     if (i > 0) {
2680       // Prevent decreasing stop positions by advancing this position
2681       // to the previous stop position, if necessary
2682       position = std::max(position, stops[i - 1].mPosition);
2683     }
2684     stops.AppendElement(ColorStop(position, stop.mIsInterpolationHint,
2685                                   Color::FromABGR(stop.mColor)));
2686     if (firstUnsetPosition > 0) {
2687       // Interpolate positions for all stops that didn't have a specified position
2688       double p = stops[firstUnsetPosition - 1].mPosition;
2689       double d = (stops[i].mPosition - p)/(i - firstUnsetPosition + 1);
2690       for (uint32_t j = firstUnsetPosition; j < i; ++j) {
2691         p += d;
2692         stops[j].mPosition = p;
2693       }
2694       firstUnsetPosition = -1;
2695     }
2696   }
2697 
2698   // If a non-repeating linear gradient is axis-aligned and there are no gaps
2699   // between tiles, we can optimise away most of the work by converting to a
2700   // repeating linear gradient and filling the whole destination rect at once.
2701   bool forceRepeatToCoverTiles =
2702     aGradient->mShape == NS_STYLE_GRADIENT_SHAPE_LINEAR &&
2703     (lineStart.x == lineEnd.x) != (lineStart.y == lineEnd.y) &&
2704     aRepeatSize.width == aDest.width && aRepeatSize.height == aDest.height &&
2705     !aGradient->mRepeating && !aSrc.IsEmpty() && !cellContainsFill;
2706 
2707   gfxMatrix matrix;
2708   if (forceRepeatToCoverTiles) {
2709     // Length of the source rectangle along the gradient axis.
2710     double rectLen;
2711     // The position of the start of the rectangle along the gradient.
2712     double offset;
2713 
2714     // The gradient line is "backwards". Flip the line upside down to make
2715     // things easier, and then rotate the matrix to turn everything back the
2716     // right way up.
2717     if (lineStart.x > lineEnd.x || lineStart.y > lineEnd.y) {
2718       std::swap(lineStart, lineEnd);
2719       matrix.Scale(-1, -1);
2720     }
2721 
2722     // Fit the gradient line exactly into the source rect.
2723     // aSrc is relative to aIntrinsincSize.
2724     // srcRectDev will be relative to srcSize, so in the same coordinate space
2725     // as lineStart / lineEnd.
2726     gfxRect srcRectDev = nsLayoutUtils::RectToGfxRect(
2727       CSSPixel::ToAppUnits(aSrc), appUnitsPerDevPixel);
2728     if (lineStart.x != lineEnd.x) {
2729       rectLen = srcRectDev.width;
2730       offset = (srcRectDev.x - lineStart.x) / lineLength;
2731       lineStart.x = srcRectDev.x;
2732       lineEnd.x = srcRectDev.XMost();
2733     } else {
2734       rectLen = srcRectDev.height;
2735       offset = (srcRectDev.y - lineStart.y) / lineLength;
2736       lineStart.y = srcRectDev.y;
2737       lineEnd.y = srcRectDev.YMost();
2738     }
2739 
2740     // Adjust gradient stop positions for the new gradient line.
2741     double scale = lineLength / rectLen;
2742     for (size_t i = 0; i < stops.Length(); i++) {
2743       stops[i].mPosition = (stops[i].mPosition - offset) * fabs(scale);
2744     }
2745 
2746     // Clamp or extrapolate gradient stops to exactly [0, 1].
2747     ClampColorStops(stops);
2748 
2749     lineLength = rectLen;
2750   }
2751 
2752   // Eliminate negative-position stops if the gradient is radial.
2753   double firstStop = stops[0].mPosition;
2754   if (aGradient->mShape != NS_STYLE_GRADIENT_SHAPE_LINEAR && firstStop < 0.0) {
2755     if (aGradient->mRepeating) {
2756       // Choose an instance of the repeated pattern that gives us all positive
2757       // stop-offsets.
2758       double lastStop = stops[stops.Length() - 1].mPosition;
2759       double stopDelta = lastStop - firstStop;
2760       // If all the stops are in approximately the same place then logic below
2761       // will kick in that makes us draw just the last stop color, so don't
2762       // try to do anything in that case. We certainly need to avoid
2763       // dividing by zero.
2764       if (stopDelta >= 1e-6) {
2765         double instanceCount = ceil(-firstStop/stopDelta);
2766         // Advance stops by instanceCount multiples of the period of the
2767         // repeating gradient.
2768         double offset = instanceCount*stopDelta;
2769         for (uint32_t i = 0; i < stops.Length(); i++) {
2770           stops[i].mPosition += offset;
2771         }
2772       }
2773     } else {
2774       // Move negative-position stops to position 0.0. We may also need
2775       // to set the color of the stop to the color the gradient should have
2776       // at the center of the ellipse.
2777       for (uint32_t i = 0; i < stops.Length(); i++) {
2778         double pos = stops[i].mPosition;
2779         if (pos < 0.0) {
2780           stops[i].mPosition = 0.0;
2781           // If this is the last stop, we don't need to adjust the color,
2782           // it will fill the entire area.
2783           if (i < stops.Length() - 1) {
2784             double nextPos = stops[i + 1].mPosition;
2785             // If nextPos is approximately equal to pos, then we don't
2786             // need to adjust the color of this stop because it's
2787             // not going to be displayed.
2788             // If nextPos is negative, we don't need to adjust the color of
2789             // this stop since it's not going to be displayed because
2790             // nextPos will also be moved to 0.0.
2791             if (nextPos >= 0.0 && nextPos - pos >= 1e-6) {
2792               // Compute how far the new position 0.0 is along the interval
2793               // between pos and nextPos.
2794               // XXX Color interpolation (in cairo, too) should use the
2795               // CSS 'color-interpolation' property!
2796               float frac = float((0.0 - pos)/(nextPos - pos));
2797               stops[i].mColor =
2798                 InterpolateColor(stops[i].mColor, stops[i + 1].mColor, frac);
2799             }
2800           }
2801         }
2802       }
2803     }
2804     firstStop = stops[0].mPosition;
2805     MOZ_ASSERT(firstStop >= 0.0, "Failed to fix stop offsets");
2806   }
2807 
2808   if (aGradient->mShape != NS_STYLE_GRADIENT_SHAPE_LINEAR && !aGradient->mRepeating) {
2809     // Direct2D can only handle a particular class of radial gradients because
2810     // of the way the it specifies gradients. Setting firstStop to 0, when we
2811     // can, will help us stay on the fast path. Currently we don't do this
2812     // for repeating gradients but we could by adjusting the stop collection
2813     // to start at 0
2814     firstStop = 0;
2815   }
2816 
2817   double lastStop = stops[stops.Length() - 1].mPosition;
2818   // Cairo gradients must have stop positions in the range [0, 1]. So,
2819   // stop positions will be normalized below by subtracting firstStop and then
2820   // multiplying by stopScale.
2821   double stopScale;
2822   double stopOrigin = firstStop;
2823   double stopEnd = lastStop;
2824   double stopDelta = lastStop - firstStop;
2825   bool zeroRadius = aGradient->mShape != NS_STYLE_GRADIENT_SHAPE_LINEAR &&
2826                       (radiusX < 1e-6 || radiusY < 1e-6);
2827   if (stopDelta < 1e-6 || lineLength < 1e-6 || zeroRadius) {
2828     // Stops are all at the same place. Map all stops to 0.0.
2829     // For repeating radial gradients, or for any radial gradients with
2830     // a zero radius, we need to fill with the last stop color, so just set
2831     // both radii to 0.
2832     if (aGradient->mRepeating || zeroRadius) {
2833       radiusX = radiusY = 0.0;
2834     }
2835     stopDelta = 0.0;
2836     lastStop = firstStop;
2837   }
2838 
2839   // Don't normalize non-repeating or degenerate gradients below 0..1
2840   // This keeps the gradient line as large as the box and doesn't
2841   // lets us avoiding having to get padding correct for stops
2842   // at 0 and 1
2843   if (!aGradient->mRepeating || stopDelta == 0.0) {
2844     stopOrigin = std::min(stopOrigin, 0.0);
2845     stopEnd = std::max(stopEnd, 1.0);
2846   }
2847   stopScale = 1.0/(stopEnd - stopOrigin);
2848 
2849   // Create the gradient pattern.
2850   RefPtr<gfxPattern> gradientPattern;
2851   gfxPoint gradientStart;
2852   gfxPoint gradientEnd;
2853   if (aGradient->mShape == NS_STYLE_GRADIENT_SHAPE_LINEAR) {
2854     // Compute the actual gradient line ends we need to pass to cairo after
2855     // stops have been normalized.
2856     gradientStart = lineStart + (lineEnd - lineStart)*stopOrigin;
2857     gradientEnd = lineStart + (lineEnd - lineStart)*stopEnd;
2858     gfxPoint gradientStopStart = lineStart + (lineEnd - lineStart)*firstStop;
2859     gfxPoint gradientStopEnd = lineStart + (lineEnd - lineStart)*lastStop;
2860 
2861     if (stopDelta == 0.0) {
2862       // Stops are all at the same place. For repeating gradients, this will
2863       // just paint the last stop color. We don't need to do anything.
2864       // For non-repeating gradients, this should render as two colors, one
2865       // on each "side" of the gradient line segment, which is a point. All
2866       // our stops will be at 0.0; we just need to set the direction vector
2867       // correctly.
2868       gradientEnd = gradientStart + (lineEnd - lineStart);
2869       gradientStopEnd = gradientStopStart + (lineEnd - lineStart);
2870     }
2871 
2872     gradientPattern = new gfxPattern(gradientStart.x, gradientStart.y,
2873                                       gradientEnd.x, gradientEnd.y);
2874   } else {
2875     NS_ASSERTION(firstStop >= 0.0,
2876                   "Negative stops not allowed for radial gradients");
2877 
2878     // To form an ellipse, we'll stretch a circle vertically, if necessary.
2879     // So our radii are based on radiusX.
2880     double innerRadius = radiusX*stopOrigin;
2881     double outerRadius = radiusX*stopEnd;
2882     if (stopDelta == 0.0) {
2883       // Stops are all at the same place.  See above (except we now have
2884       // the inside vs. outside of an ellipse).
2885       outerRadius = innerRadius + 1;
2886     }
2887     gradientPattern = new gfxPattern(lineStart.x, lineStart.y, innerRadius,
2888                                      lineStart.x, lineStart.y, outerRadius);
2889     if (radiusX != radiusY) {
2890       // Stretch the circles into ellipses vertically by setting a transform
2891       // in the pattern.
2892       // Recall that this is the transform from user space to pattern space.
2893       // So to stretch the ellipse by factor of P vertically, we scale
2894       // user coordinates by 1/P.
2895       matrix.Translate(lineStart);
2896       matrix.Scale(1.0, radiusX/radiusY);
2897       matrix.Translate(-lineStart);
2898     }
2899   }
2900   // Use a pattern transform to take account of source and dest rects
2901   matrix.Translate(gfxPoint(aPresContext->CSSPixelsToDevPixels(aSrc.x),
2902                             aPresContext->CSSPixelsToDevPixels(aSrc.y)));
2903   matrix.Scale(gfxFloat(aPresContext->CSSPixelsToAppUnits(aSrc.width))/aDest.width,
2904                gfxFloat(aPresContext->CSSPixelsToAppUnits(aSrc.height))/aDest.height);
2905   gradientPattern->SetMatrix(matrix);
2906 
2907   if (gradientPattern->CairoStatus())
2908     return;
2909 
2910   if (stopDelta == 0.0) {
2911     // Non-repeating gradient with all stops in same place -> just add
2912     // first stop and last stop, both at position 0.
2913     // Repeating gradient with all stops in the same place, or radial
2914     // gradient with radius of 0 -> just paint the last stop color.
2915     // We use firstStop offset to keep |stops| with same units (will later normalize to 0).
2916     Color firstColor(stops[0].mColor);
2917     Color lastColor(stops.LastElement().mColor);
2918     stops.Clear();
2919 
2920     if (!aGradient->mRepeating && !zeroRadius) {
2921       stops.AppendElement(ColorStop(firstStop, false, firstColor));
2922     }
2923     stops.AppendElement(ColorStop(firstStop, false, lastColor));
2924   }
2925 
2926   ResolveMidpoints(stops);
2927   ResolvePremultipliedAlpha(stops);
2928 
2929   bool isRepeat = aGradient->mRepeating || forceRepeatToCoverTiles;
2930 
2931   // Now set normalized color stops in pattern.
2932   // Offscreen gradient surface cache (not a tile):
2933   // On some backends (e.g. D2D), the GradientStops object holds an offscreen surface
2934   // which is a lookup table used to evaluate the gradient. This surface can use
2935   // much memory (ram and/or GPU ram) and can be expensive to create. So we cache it.
2936   // The cache key correlates 1:1 with the arguments for CreateGradientStops (also the implied backend type)
2937   // Note that GradientStop is a simple struct with a stop value (while GradientStops has the surface).
2938   nsTArray<gfx::GradientStop> rawStops(stops.Length());
2939   rawStops.SetLength(stops.Length());
2940   for(uint32_t i = 0; i < stops.Length(); i++) {
2941     rawStops[i].color = stops[i].mColor;
2942     rawStops[i].offset = stopScale * (stops[i].mPosition - stopOrigin);
2943   }
2944   RefPtr<mozilla::gfx::GradientStops> gs =
2945     gfxGradientCache::GetOrCreateGradientStops(ctx->GetDrawTarget(),
2946                                                rawStops,
2947                                                isRepeat ? gfx::ExtendMode::REPEAT : gfx::ExtendMode::CLAMP);
2948   gradientPattern->SetColorStops(gs);
2949 
2950   // Paint gradient tiles. This isn't terribly efficient, but doing it this
2951   // way is simple and sure to get pixel-snapping right. We could speed things
2952   // up by drawing tiles into temporary surfaces and copying those to the
2953   // destination, but after pixel-snapping tiles may not all be the same size.
2954   nsRect dirty;
2955   if (!dirty.IntersectRect(aDirtyRect, aFillArea))
2956     return;
2957 
2958   gfxRect areaToFill =
2959     nsLayoutUtils::RectToGfxRect(aFillArea, appUnitsPerDevPixel);
2960   gfxRect dirtyAreaToFill = nsLayoutUtils::RectToGfxRect(dirty, appUnitsPerDevPixel);
2961   dirtyAreaToFill.RoundOut();
2962 
2963   gfxMatrix ctm = ctx->CurrentMatrix();
2964   bool isCTMPreservingAxisAlignedRectangles = ctm.PreservesAxisAlignedRectangles();
2965 
2966   // xStart/yStart are the top-left corner of the top-left tile.
2967   nscoord xStart = FindTileStart(dirty.x, aDest.x, aRepeatSize.width);
2968   nscoord yStart = FindTileStart(dirty.y, aDest.y, aRepeatSize.height);
2969   nscoord xEnd = forceRepeatToCoverTiles ? xStart + aDest.width : dirty.XMost();
2970   nscoord yEnd = forceRepeatToCoverTiles ? yStart + aDest.height : dirty.YMost();
2971 
2972   // x and y are the top-left corner of the tile to draw
2973   for (nscoord y = yStart; y < yEnd; y += aRepeatSize.height) {
2974     for (nscoord x = xStart; x < xEnd; x += aRepeatSize.width) {
2975       // The coordinates of the tile
2976       gfxRect tileRect = nsLayoutUtils::RectToGfxRect(
2977                       nsRect(x, y, aDest.width, aDest.height),
2978                       appUnitsPerDevPixel);
2979       // The actual area to fill with this tile is the intersection of this
2980       // tile with the overall area we're supposed to be filling
2981       gfxRect fillRect =
2982         forceRepeatToCoverTiles ? areaToFill : tileRect.Intersect(areaToFill);
2983       // Try snapping the fill rect. Snap its top-left and bottom-right
2984       // independently to preserve the orientation.
2985       gfxPoint snappedFillRectTopLeft = fillRect.TopLeft();
2986       gfxPoint snappedFillRectTopRight = fillRect.TopRight();
2987       gfxPoint snappedFillRectBottomRight = fillRect.BottomRight();
2988       // Snap three points instead of just two to ensure we choose the
2989       // correct orientation if there's a reflection.
2990       if (isCTMPreservingAxisAlignedRectangles &&
2991           ctx->UserToDevicePixelSnapped(snappedFillRectTopLeft, true) &&
2992           ctx->UserToDevicePixelSnapped(snappedFillRectBottomRight, true) &&
2993           ctx->UserToDevicePixelSnapped(snappedFillRectTopRight, true)) {
2994         if (snappedFillRectTopLeft.x == snappedFillRectBottomRight.x ||
2995             snappedFillRectTopLeft.y == snappedFillRectBottomRight.y) {
2996           // Nothing to draw; avoid scaling by zero and other weirdness that
2997           // could put the context in an error state.
2998           continue;
2999         }
3000         // Set the context's transform to the transform that maps fillRect to
3001         // snappedFillRect. The part of the gradient that was going to
3002         // exactly fill fillRect will fill snappedFillRect instead.
3003         gfxMatrix transform = gfxUtils::TransformRectToRect(fillRect,
3004             snappedFillRectTopLeft, snappedFillRectTopRight,
3005             snappedFillRectBottomRight);
3006         ctx->SetMatrix(transform);
3007       }
3008       ctx->NewPath();
3009       ctx->Rectangle(fillRect);
3010 
3011       gfxRect dirtyFillRect = fillRect.Intersect(dirtyAreaToFill);
3012       gfxRect fillRectRelativeToTile = dirtyFillRect - tileRect.TopLeft();
3013       Color edgeColor;
3014       if (aGradient->mShape == NS_STYLE_GRADIENT_SHAPE_LINEAR && !isRepeat &&
3015           RectIsBeyondLinearGradientEdge(fillRectRelativeToTile, matrix, stops,
3016                                          gradientStart, gradientEnd, &edgeColor)) {
3017         ctx->SetColor(edgeColor);
3018       } else {
3019         ctx->SetMatrix(
3020           ctx->CurrentMatrix().Copy().Translate(tileRect.TopLeft()));
3021         ctx->SetPattern(gradientPattern);
3022       }
3023       ctx->Fill();
3024       ctx->SetMatrix(ctm);
3025     }
3026   }
3027 }
3028 
3029 static CompositionOp
DetermineCompositionOp(const nsCSSRendering::PaintBGParams & aParams,const nsStyleImageLayers & aLayers,uint32_t aLayerIndex)3030 DetermineCompositionOp(const nsCSSRendering::PaintBGParams& aParams,
3031                        const nsStyleImageLayers& aLayers,
3032                        uint32_t aLayerIndex)
3033 {
3034   if (aParams.layer >= 0) {
3035     // When drawing a single layer, use the specified composition op.
3036     return aParams.compositionOp;
3037   }
3038 
3039   const nsStyleImageLayers::Layer& layer = aLayers.mLayers[aLayerIndex];
3040   // When drawing all layers, get the compositon op from each image layer.
3041   if (aParams.paintFlags & nsCSSRendering::PAINTBG_MASK_IMAGE) {
3042     // Always using OP_OVER mode while drawing the bottom mask layer.
3043     if (aLayerIndex == (aLayers.mImageCount - 1)) {
3044       return CompositionOp::OP_OVER;
3045     }
3046 
3047     return nsCSSRendering::GetGFXCompositeMode(layer.mComposite);
3048   }
3049 
3050   return nsCSSRendering::GetGFXBlendMode(layer.mBlendMode);
3051 }
3052 
3053 DrawResult
PaintBackgroundWithSC(const PaintBGParams & aParams,nsStyleContext * aBackgroundSC,const nsStyleBorder & aBorder)3054 nsCSSRendering::PaintBackgroundWithSC(const PaintBGParams& aParams,
3055                                       nsStyleContext *aBackgroundSC,
3056                                       const nsStyleBorder& aBorder)
3057 {
3058   NS_PRECONDITION(aParams.frame,
3059                   "Frame is expected to be provided to PaintBackground");
3060 
3061   // If we're drawing all layers, aCompositonOp is ignored, so make sure that
3062   // it was left at its default value.
3063   MOZ_ASSERT_IF(aParams.layer == -1,
3064                 aParams.compositionOp == CompositionOp::OP_OVER);
3065 
3066   DrawResult result = DrawResult::SUCCESS;
3067 
3068   // Check to see if we have an appearance defined.  If so, we let the theme
3069   // renderer draw the background and bail out.
3070   // XXXzw this ignores aParams.bgClipRect.
3071   const nsStyleDisplay* displayData = aParams.frame->StyleDisplay();
3072   if (displayData->mAppearance) {
3073     nsITheme *theme = aParams.presCtx.GetTheme();
3074     if (theme && theme->ThemeSupportsWidget(&aParams.presCtx,
3075                                             aParams.frame,
3076                                             displayData->mAppearance)) {
3077       nsRect drawing(aParams.borderArea);
3078       theme->GetWidgetOverflow(aParams.presCtx.DeviceContext(),
3079                                aParams.frame, displayData->mAppearance,
3080                                &drawing);
3081       drawing.IntersectRect(drawing, aParams.dirtyRect);
3082       theme->DrawWidgetBackground(&aParams.renderingCtx, aParams.frame,
3083                                   displayData->mAppearance, aParams.borderArea,
3084                                   drawing);
3085       return DrawResult::SUCCESS;
3086     }
3087   }
3088 
3089   // For canvas frames (in the CSS sense) we draw the background color using
3090   // a solid color item that gets added in nsLayoutUtils::PaintFrame,
3091   // or nsSubDocumentFrame::BuildDisplayList (bug 488242). (The solid
3092   // color may be moved into nsDisplayCanvasBackground by
3093   // nsPresShell::AddCanvasBackgroundColorItem, and painted by
3094   // nsDisplayCanvasBackground directly.) Either way we don't need to
3095   // paint the background color here.
3096   bool isCanvasFrame = IsCanvasFrame(aParams.frame);
3097 
3098   // Determine whether we are drawing background images and/or
3099   // background colors.
3100   bool drawBackgroundImage;
3101   bool drawBackgroundColor;
3102 
3103   nscolor bgColor = DetermineBackgroundColor(&aParams.presCtx,
3104                                              aBackgroundSC,
3105                                              aParams.frame,
3106                                              drawBackgroundImage,
3107                                              drawBackgroundColor);
3108 
3109   bool paintMask = (aParams.paintFlags & PAINTBG_MASK_IMAGE);
3110   const nsStyleImageLayers& layers = paintMask ?
3111     aBackgroundSC->StyleSVGReset()->mMask :
3112     aBackgroundSC->StyleBackground()->mImage;
3113   // If we're drawing a specific layer, we don't want to draw the
3114   // background color.
3115   if ((drawBackgroundColor && aParams.layer >= 0) || paintMask) {
3116     drawBackgroundColor = false;
3117   }
3118 
3119   // At this point, drawBackgroundImage and drawBackgroundColor are
3120   // true if and only if we are actually supposed to paint an image or
3121   // color into aDirtyRect, respectively.
3122   if (!drawBackgroundImage && !drawBackgroundColor)
3123     return DrawResult::SUCCESS;
3124 
3125   // Compute the outermost boundary of the area that might be painted.
3126   // Same coordinate space as aParams.borderArea & aParams.bgClipRect.
3127   Sides skipSides = aParams.frame->GetSkipSides();
3128   nsRect paintBorderArea =
3129     ::BoxDecorationRectForBackground(aParams.frame, aParams.borderArea,
3130                                      skipSides, &aBorder);
3131   nsRect clipBorderArea =
3132     ::BoxDecorationRectForBorder(aParams.frame, aParams.borderArea,
3133                                  skipSides, &aBorder);
3134 
3135   // The 'bgClipArea' (used only by the image tiling logic, far below)
3136   // is the caller-provided aParams.bgClipRect if any, or else the area
3137   // determined by the value of 'background-clip' in
3138   // SetupCurrentBackgroundClip.  (Arguably it should be the
3139   // intersection, but that breaks the table painter -- in particular,
3140   // taking the intersection breaks reftests/bugs/403249-1[ab].)
3141   gfxContext* ctx = aParams.renderingCtx.ThebesContext();
3142   nscoord appUnitsPerPixel = aParams.presCtx.AppUnitsPerDevPixel();
3143   ImageLayerClipState clipState;
3144   if (aParams.bgClipRect) {
3145     clipState.mBGClipArea = *aParams.bgClipRect;
3146     clipState.mCustomClip = true;
3147     clipState.mHasRoundedCorners = false;
3148     SetupDirtyRects(clipState.mBGClipArea, aParams.dirtyRect, appUnitsPerPixel,
3149                     &clipState.mDirtyRect, &clipState.mDirtyRectGfx);
3150   } else {
3151     GetImageLayerClip(layers.BottomLayer(),
3152                       aParams.frame, aBorder, aParams.borderArea,
3153                       aParams.dirtyRect,
3154                       (aParams.paintFlags & PAINTBG_WILL_PAINT_BORDER),
3155                       appUnitsPerPixel,
3156                       &clipState);
3157   }
3158 
3159   // If we might be using a background color, go ahead and set it now.
3160   if (drawBackgroundColor && !isCanvasFrame)
3161     ctx->SetColor(Color::FromABGR(bgColor));
3162 
3163   // NOTE: no Save() yet, we do that later by calling autoSR.EnsureSaved(ctx)
3164   // in the cases we need it.
3165   gfxContextAutoSaveRestore autoSR;
3166 
3167   // If there is no background image, draw a color.  (If there is
3168   // neither a background image nor a color, we wouldn't have gotten
3169   // this far.)
3170   if (!drawBackgroundImage) {
3171     if (!isCanvasFrame) {
3172       DrawBackgroundColor(clipState, ctx, appUnitsPerPixel);
3173     }
3174     return DrawResult::SUCCESS;
3175   }
3176 
3177   if (layers.mImageCount < 1) {
3178     // Return if there are no background layers, all work from this point
3179     // onwards happens iteratively on these.
3180     return DrawResult::SUCCESS;
3181   }
3182 
3183   // Validate the layer range before we start iterating.
3184   int32_t startLayer = aParams.layer;
3185   int32_t nLayers = 1;
3186   if (startLayer < 0) {
3187     startLayer = (int32_t)layers.mImageCount - 1;
3188     nLayers = layers.mImageCount;
3189   }
3190 
3191   // Ensure we get invalidated for loads of the image.  We need to do
3192   // this here because this might be the only code that knows about the
3193   // association of the style data with the frame.
3194   if (aBackgroundSC != aParams.frame->StyleContext()) {
3195     NS_FOR_VISIBLE_IMAGE_LAYERS_BACK_TO_FRONT_WITH_RANGE(i, layers, startLayer, nLayers) {
3196       aParams.frame->AssociateImage(layers.mLayers[i].mImage,
3197                                     &aParams.presCtx);
3198     }
3199   }
3200 
3201   // The background color is rendered over the entire dirty area,
3202   // even if the image isn't.
3203   if (drawBackgroundColor && !isCanvasFrame) {
3204     DrawBackgroundColor(clipState, ctx, appUnitsPerPixel);
3205   }
3206 
3207   if (drawBackgroundImage) {
3208     bool clipSet = false;
3209     uint8_t currentBackgroundClip = NS_STYLE_IMAGELAYER_CLIP_BORDER;
3210     NS_FOR_VISIBLE_IMAGE_LAYERS_BACK_TO_FRONT_WITH_RANGE(i, layers, layers.mImageCount - 1,
3211                                                          nLayers + (layers.mImageCount -
3212                                                          startLayer - 1)) {
3213       const nsStyleImageLayers::Layer& layer = layers.mLayers[i];
3214       if (!aParams.bgClipRect) {
3215         if (currentBackgroundClip != layer.mClip || !clipSet) {
3216           currentBackgroundClip = layer.mClip;
3217           // If clipSet is false that means this is the bottom layer and we
3218           // already called GetImageLayerClip above and it stored its results
3219           // in clipState.
3220           if (clipSet) {
3221             autoSR.Restore(); // reset the previous one
3222             GetImageLayerClip(layer, aParams.frame,
3223                               aBorder, aParams.borderArea, aParams.dirtyRect,
3224                               (aParams.paintFlags & PAINTBG_WILL_PAINT_BORDER),
3225                               appUnitsPerPixel, &clipState);
3226           }
3227           SetupImageLayerClip(clipState, ctx, appUnitsPerPixel, &autoSR);
3228           clipSet = true;
3229           if (!clipBorderArea.IsEqualEdges(aParams.borderArea)) {
3230             // We're drawing the background for the joined continuation boxes
3231             // so we need to clip that to the slice that we want for this
3232             // frame.
3233             gfxRect clip =
3234               nsLayoutUtils::RectToGfxRect(aParams.borderArea, appUnitsPerPixel);
3235             autoSR.EnsureSaved(ctx);
3236             ctx->NewPath();
3237             ctx->SnappedRectangle(clip);
3238             ctx->Clip();
3239           }
3240         }
3241       }
3242       if ((aParams.layer < 0 || i == (uint32_t)startLayer) &&
3243           !clipState.mDirtyRectGfx.IsEmpty()) {
3244         CompositionOp co = DetermineCompositionOp(aParams, layers, i);
3245         nsBackgroundLayerState state =
3246           PrepareImageLayer(&aParams.presCtx, aParams.frame,
3247                             aParams.paintFlags, paintBorderArea, clipState.mBGClipArea,
3248                             layer, nullptr);
3249         result &= state.mImageRenderer.PrepareResult();
3250         if (!state.mFillArea.IsEmpty()) {
3251           if (co != CompositionOp::OP_OVER) {
3252             NS_ASSERTION(ctx->CurrentOp() == CompositionOp::OP_OVER,
3253                          "It is assumed the initial op is OP_OVER, when it is "
3254                          "restored later");
3255             ctx->SetOp(co);
3256           }
3257 
3258           result &=
3259             state.mImageRenderer.DrawBackground(&aParams.presCtx,
3260                                                 aParams.renderingCtx,
3261                                                 state.mDestArea, state.mFillArea,
3262                                                 state.mAnchor + paintBorderArea.TopLeft(),
3263                                                 clipState.mDirtyRect,
3264                                                 state.mRepeatSize);
3265 
3266           if (co != CompositionOp::OP_OVER) {
3267             ctx->SetOp(CompositionOp::OP_OVER);
3268           }
3269         }
3270       }
3271     }
3272   }
3273 
3274   return result;
3275 }
3276 
3277 nsRect
ComputeImageLayerPositioningArea(nsPresContext * aPresContext,nsIFrame * aForFrame,const nsRect & aBorderArea,const nsStyleImageLayers::Layer & aLayer,nsIFrame ** aAttachedToFrame,bool * aOutIsTransformedFixed)3278 nsCSSRendering::ComputeImageLayerPositioningArea(nsPresContext* aPresContext,
3279                                                  nsIFrame* aForFrame,
3280                                                  const nsRect& aBorderArea,
3281                                                  const nsStyleImageLayers::Layer& aLayer,
3282                                                  nsIFrame** aAttachedToFrame,
3283                                                  bool* aOutIsTransformedFixed)
3284 {
3285   // Compute background origin area relative to aBorderArea now as we may need
3286   // it to compute the effective image size for a CSS gradient.
3287   nsRect bgPositioningArea;
3288 
3289   nsIAtom* frameType = aForFrame->GetType();
3290   nsIFrame* geometryFrame = aForFrame;
3291   if (MOZ_UNLIKELY(frameType == nsGkAtoms::scrollFrame &&
3292                    NS_STYLE_IMAGELAYER_ATTACHMENT_LOCAL == aLayer.mAttachment)) {
3293     nsIScrollableFrame* scrollableFrame = do_QueryFrame(aForFrame);
3294     bgPositioningArea = nsRect(
3295       scrollableFrame->GetScrolledFrame()->GetPosition()
3296         // For the dir=rtl case:
3297         + scrollableFrame->GetScrollRange().TopLeft(),
3298       scrollableFrame->GetScrolledRect().Size());
3299     // The ScrolledRect’s size does not include the borders or scrollbars,
3300     // reverse the handling of background-origin
3301     // compared to the common case below.
3302     if (aLayer.mOrigin == NS_STYLE_IMAGELAYER_ORIGIN_BORDER) {
3303       nsMargin border = geometryFrame->GetUsedBorder();
3304       border.ApplySkipSides(geometryFrame->GetSkipSides());
3305       bgPositioningArea.Inflate(border);
3306       bgPositioningArea.Inflate(scrollableFrame->GetActualScrollbarSizes());
3307     } else if (aLayer.mOrigin != NS_STYLE_IMAGELAYER_ORIGIN_PADDING) {
3308       nsMargin padding = geometryFrame->GetUsedPadding();
3309       padding.ApplySkipSides(geometryFrame->GetSkipSides());
3310       bgPositioningArea.Deflate(padding);
3311       NS_ASSERTION(aLayer.mOrigin == NS_STYLE_IMAGELAYER_ORIGIN_CONTENT,
3312                    "unknown background-origin value");
3313     }
3314     *aAttachedToFrame = aForFrame;
3315     return bgPositioningArea;
3316   }
3317 
3318   if (MOZ_UNLIKELY(frameType == nsGkAtoms::canvasFrame)) {
3319     geometryFrame = aForFrame->PrincipalChildList().FirstChild();
3320     // geometryFrame might be null if this canvas is a page created
3321     // as an overflow container (e.g. the in-flow content has already
3322     // finished and this page only displays the continuations of
3323     // absolutely positioned content).
3324     if (geometryFrame) {
3325       bgPositioningArea = geometryFrame->GetRect();
3326     }
3327   } else {
3328     bgPositioningArea = nsRect(nsPoint(0,0), aBorderArea.Size());
3329   }
3330 
3331   // Background images are tiled over the 'background-clip' area
3332   // but the origin of the tiling is based on the 'background-origin' area
3333   // XXX: Bug 1303623 will bring in new origin value, we should iterate from
3334   // NS_STYLE_IMAGELAYER_ORIGIN_MARGIN instead of
3335   // NS_STYLE_IMAGELAYER_ORIGIN_BORDER.
3336   if (aLayer.mOrigin != NS_STYLE_IMAGELAYER_ORIGIN_BORDER && geometryFrame) {
3337     nsMargin border = geometryFrame->GetUsedBorder();
3338     if (aLayer.mOrigin != NS_STYLE_IMAGELAYER_ORIGIN_PADDING) {
3339       border += geometryFrame->GetUsedPadding();
3340       NS_ASSERTION(aLayer.mOrigin == NS_STYLE_IMAGELAYER_ORIGIN_CONTENT,
3341                    "unknown background-origin value");
3342     }
3343     bgPositioningArea.Deflate(border);
3344   }
3345 
3346   nsIFrame* attachedToFrame = aForFrame;
3347   if (NS_STYLE_IMAGELAYER_ATTACHMENT_FIXED == aLayer.mAttachment) {
3348     // If it's a fixed background attachment, then the image is placed
3349     // relative to the viewport, which is the area of the root frame
3350     // in a screen context or the page content frame in a print context.
3351     attachedToFrame = aPresContext->PresShell()->FrameManager()->GetRootFrame();
3352     NS_ASSERTION(attachedToFrame, "no root frame");
3353     nsIFrame* pageContentFrame = nullptr;
3354     if (aPresContext->IsPaginated()) {
3355       pageContentFrame =
3356         nsLayoutUtils::GetClosestFrameOfType(aForFrame, nsGkAtoms::pageContentFrame);
3357       if (pageContentFrame) {
3358         attachedToFrame = pageContentFrame;
3359       }
3360       // else this is an embedded shell and its root frame is what we want
3361     }
3362 
3363     // If the background is affected by a transform, treat is as if it
3364     // wasn't fixed.
3365     if (nsLayoutUtils::IsTransformed(aForFrame, attachedToFrame)) {
3366       attachedToFrame = aForFrame;
3367       *aOutIsTransformedFixed = true;
3368     } else {
3369       // Set the background positioning area to the viewport's area
3370       // (relative to aForFrame)
3371       bgPositioningArea =
3372         nsRect(-aForFrame->GetOffsetTo(attachedToFrame), attachedToFrame->GetSize());
3373 
3374       if (!pageContentFrame) {
3375         // Subtract the size of scrollbars.
3376         nsIScrollableFrame* scrollableFrame =
3377           aPresContext->PresShell()->GetRootScrollFrameAsScrollable();
3378         if (scrollableFrame) {
3379           nsMargin scrollbars = scrollableFrame->GetActualScrollbarSizes();
3380           bgPositioningArea.Deflate(scrollbars);
3381         }
3382       }
3383     }
3384   }
3385   *aAttachedToFrame = attachedToFrame;
3386 
3387   return bgPositioningArea;
3388 }
3389 
3390 // Implementation of the formula for computation of background-repeat round
3391 // See http://dev.w3.org/csswg/css3-background/#the-background-size
3392 // This function returns the adjusted size of the background image.
3393 static nscoord
ComputeRoundedSize(nscoord aCurrentSize,nscoord aPositioningSize)3394 ComputeRoundedSize(nscoord aCurrentSize, nscoord aPositioningSize)
3395 {
3396   float repeatCount = NS_roundf(float(aPositioningSize) / float(aCurrentSize));
3397   if (repeatCount < 1.0f) {
3398     return aPositioningSize;
3399   }
3400   return nscoord(NS_lround(float(aPositioningSize) / repeatCount));
3401 }
3402 
3403 // Apply the CSS image sizing algorithm as it applies to background images.
3404 // See http://www.w3.org/TR/css3-background/#the-background-size .
3405 // aIntrinsicSize is the size that the background image 'would like to be'.
3406 // It can be found by calling nsImageRenderer::ComputeIntrinsicSize.
3407 static nsSize
ComputeDrawnSizeForBackground(const CSSSizeOrRatio & aIntrinsicSize,const nsSize & aBgPositioningArea,const nsStyleImageLayers::Size & aLayerSize,uint8_t aXRepeat,uint8_t aYRepeat)3408 ComputeDrawnSizeForBackground(const CSSSizeOrRatio& aIntrinsicSize,
3409                               const nsSize& aBgPositioningArea,
3410                               const nsStyleImageLayers::Size& aLayerSize,
3411                               uint8_t aXRepeat, uint8_t aYRepeat)
3412 {
3413   nsSize imageSize;
3414 
3415   // Size is dictated by cover or contain rules.
3416   if (aLayerSize.mWidthType == nsStyleImageLayers::Size::eContain ||
3417       aLayerSize.mWidthType == nsStyleImageLayers::Size::eCover) {
3418     nsImageRenderer::FitType fitType =
3419       aLayerSize.mWidthType == nsStyleImageLayers::Size::eCover
3420         ? nsImageRenderer::COVER
3421         : nsImageRenderer::CONTAIN;
3422     imageSize = nsImageRenderer::ComputeConstrainedSize(aBgPositioningArea,
3423                                                         aIntrinsicSize.mRatio,
3424                                                         fitType);
3425   } else {
3426     // No cover/contain constraint, use default algorithm.
3427     CSSSizeOrRatio specifiedSize;
3428     if (aLayerSize.mWidthType == nsStyleImageLayers::Size::eLengthPercentage) {
3429       specifiedSize.SetWidth(
3430         aLayerSize.ResolveWidthLengthPercentage(aBgPositioningArea));
3431     }
3432     if (aLayerSize.mHeightType == nsStyleImageLayers::Size::eLengthPercentage) {
3433       specifiedSize.SetHeight(
3434         aLayerSize.ResolveHeightLengthPercentage(aBgPositioningArea));
3435     }
3436 
3437     imageSize = nsImageRenderer::ComputeConcreteSize(specifiedSize,
3438                                                      aIntrinsicSize,
3439                                                      aBgPositioningArea);
3440   }
3441 
3442   // See https://www.w3.org/TR/css3-background/#background-size .
3443   // "If 'background-repeat' is 'round' for one (or both) dimensions, there is a second
3444   //  step. The UA must scale the image in that dimension (or both dimensions) so that
3445   //  it fits a whole number of times in the background positioning area."
3446   // "If 'background-repeat' is 'round' for one dimension only and if 'background-size'
3447   //  is 'auto' for the other dimension, then there is a third step: that other dimension
3448   //  is scaled so that the original aspect ratio is restored."
3449   bool isRepeatRoundInBothDimensions = aXRepeat == NS_STYLE_IMAGELAYER_REPEAT_ROUND &&
3450                                        aYRepeat == NS_STYLE_IMAGELAYER_REPEAT_ROUND;
3451 
3452   // Calculate the rounded size only if the background-size computation
3453   // returned a correct size for the image.
3454   if (imageSize.width && aXRepeat == NS_STYLE_IMAGELAYER_REPEAT_ROUND) {
3455     imageSize.width = ComputeRoundedSize(imageSize.width, aBgPositioningArea.width);
3456     if (!isRepeatRoundInBothDimensions &&
3457         aLayerSize.mHeightType == nsStyleImageLayers::Size::DimensionType::eAuto) {
3458       // Restore intrinsic rato
3459       if (aIntrinsicSize.mRatio.width) {
3460         float scale = float(aIntrinsicSize.mRatio.height) / aIntrinsicSize.mRatio.width;
3461         imageSize.height = NSCoordSaturatingNonnegativeMultiply(imageSize.width, scale);
3462       }
3463     }
3464   }
3465 
3466   // Calculate the rounded size only if the background-size computation
3467   // returned a correct size for the image.
3468   if (imageSize.height && aYRepeat == NS_STYLE_IMAGELAYER_REPEAT_ROUND) {
3469     imageSize.height = ComputeRoundedSize(imageSize.height, aBgPositioningArea.height);
3470     if (!isRepeatRoundInBothDimensions &&
3471         aLayerSize.mWidthType == nsStyleImageLayers::Size::DimensionType::eAuto) {
3472       // Restore intrinsic rato
3473       if (aIntrinsicSize.mRatio.height) {
3474         float scale = float(aIntrinsicSize.mRatio.width) / aIntrinsicSize.mRatio.height;
3475         imageSize.width = NSCoordSaturatingNonnegativeMultiply(imageSize.height, scale);
3476       }
3477     }
3478   }
3479 
3480   return imageSize;
3481 }
3482 
3483 /* ComputeSpacedRepeatSize
3484  * aImageDimension: the image width/height
3485  * aAvailableSpace: the background positioning area width/height
3486  * aRepeat: determine whether the image is repeated
3487  * Returns the image size plus gap size of app units for use as spacing
3488  */
3489 static nscoord
ComputeSpacedRepeatSize(nscoord aImageDimension,nscoord aAvailableSpace,bool & aRepeat)3490 ComputeSpacedRepeatSize(nscoord aImageDimension,
3491                         nscoord aAvailableSpace,
3492                         bool& aRepeat) {
3493   float ratio = static_cast<float>(aAvailableSpace) / aImageDimension;
3494 
3495   if (ratio < 2.0f) { // If you can't repeat at least twice, then don't repeat.
3496     aRepeat = false;
3497     return aImageDimension;
3498   } else {
3499     aRepeat = true;
3500     return (aAvailableSpace - aImageDimension) / (NSToIntFloor(ratio) - 1);
3501   }
3502 }
3503 
3504 /* ComputeBorderSpacedRepeatSize
3505  * aImageDimension: the image width/height
3506  * aAvailableSpace: the background positioning area width/height
3507  * aSpace: the space between each image
3508  * Returns the image size plus gap size of app units for use as spacing
3509  */
3510 static nscoord
ComputeBorderSpacedRepeatSize(nscoord aImageDimension,nscoord aAvailableSpace,nscoord & aSpace)3511 ComputeBorderSpacedRepeatSize(nscoord aImageDimension,
3512                               nscoord aAvailableSpace,
3513                               nscoord& aSpace)
3514 {
3515   int32_t count = aAvailableSpace / aImageDimension;
3516   aSpace = (aAvailableSpace - aImageDimension * count) / (count + 1);
3517   return aSpace + aImageDimension;
3518 }
3519 
3520 nsBackgroundLayerState
PrepareImageLayer(nsPresContext * aPresContext,nsIFrame * aForFrame,uint32_t aFlags,const nsRect & aBorderArea,const nsRect & aBGClipRect,const nsStyleImageLayers::Layer & aLayer,bool * aOutIsTransformedFixed)3521 nsCSSRendering::PrepareImageLayer(nsPresContext* aPresContext,
3522                                   nsIFrame* aForFrame,
3523                                   uint32_t aFlags,
3524                                   const nsRect& aBorderArea,
3525                                   const nsRect& aBGClipRect,
3526                                   const nsStyleImageLayers::Layer& aLayer,
3527                                   bool* aOutIsTransformedFixed)
3528 {
3529   /*
3530    * The properties we need to keep in mind when drawing style image
3531    * layers are:
3532    *
3533    *   background-image/ mask-image
3534    *   background-repeat/ mask-repeat
3535    *   background-attachment
3536    *   background-position/ mask-position
3537    *   background-clip/ mask-clip
3538    *   background-origin/ mask-origin
3539    *   background-size/ mask-size
3540    *   background-blend-mode
3541    *   box-decoration-break
3542    *   mask-mode
3543    *   mask-composite
3544    *
3545    * (background-color applies to the entire element and not to individual
3546    * layers, so it is irrelevant to this method.)
3547    *
3548    * These properties have the following dependencies upon each other when
3549    * determining rendering:
3550    *
3551    *   background-image/ mask-image
3552    *     no dependencies
3553    *   background-repeat/ mask-repeat
3554    *     no dependencies
3555    *   background-attachment
3556    *     no dependencies
3557    *   background-position/ mask-position
3558    *     depends upon background-size/mask-size (for the image's scaled size)
3559    *     and background-break (for the background positioning area)
3560    *   background-clip/ mask-clip
3561    *     no dependencies
3562    *   background-origin/ mask-origin
3563    *     depends upon background-attachment (only in the case where that value
3564    *     is 'fixed')
3565    *   background-size/ mask-size
3566    *     depends upon box-decoration-break (for the background positioning area
3567    *     for resolving percentages), background-image (for the image's intrinsic
3568    *     size), background-repeat (if that value is 'round'), and
3569    *     background-origin (for the background painting area, when
3570    *     background-repeat is 'round')
3571    *   background-blend-mode
3572    *     no dependencies
3573    *   mask-mode
3574    *     no dependencies
3575    *   mask-composite
3576    *     no dependencies
3577    *   box-decoration-break
3578    *     no dependencies
3579    *
3580    * As a result of only-if dependencies we don't strictly do a topological
3581    * sort of the above properties when processing, but it's pretty close to one:
3582    *
3583    *   background-clip/mask-clip (by caller)
3584    *   background-image/ mask-image
3585    *   box-decoration-break, background-origin/ mask origin
3586    *   background-attachment (postfix for background-origin if 'fixed')
3587    *   background-size/ mask-size
3588    *   background-position/ mask-position
3589    *   background-repeat/ mask-repeat
3590    */
3591 
3592   uint32_t irFlags = 0;
3593   if (aFlags & nsCSSRendering::PAINTBG_SYNC_DECODE_IMAGES) {
3594     irFlags |= nsImageRenderer::FLAG_SYNC_DECODE_IMAGES;
3595   }
3596   if (aFlags & nsCSSRendering::PAINTBG_TO_WINDOW) {
3597     irFlags |= nsImageRenderer::FLAG_PAINTING_TO_WINDOW;
3598   }
3599 
3600   nsBackgroundLayerState state(aForFrame, &aLayer.mImage, irFlags);
3601   if (!state.mImageRenderer.PrepareImage()) {
3602     // There's no image or it's not ready to be painted.
3603     if (aOutIsTransformedFixed) {
3604       *aOutIsTransformedFixed = false;
3605     }
3606     return state;
3607   }
3608 
3609   // The frame to which the background is attached
3610   nsIFrame* attachedToFrame = aForFrame;
3611   // Is the background marked 'fixed', but affected by a transform?
3612   bool transformedFixed = false;
3613   // Compute background origin area relative to aBorderArea now as we may need
3614   // it to compute the effective image size for a CSS gradient.
3615   nsRect bgPositioningArea =
3616     ComputeImageLayerPositioningArea(aPresContext, aForFrame, aBorderArea,
3617                                      aLayer, &attachedToFrame, &transformedFixed);
3618   if (aOutIsTransformedFixed) {
3619     *aOutIsTransformedFixed = transformedFixed;
3620   }
3621 
3622   // For background-attachment:fixed backgrounds, we'll limit the area
3623   // where the background can be drawn to the viewport.
3624   nsRect bgClipRect = aBGClipRect;
3625 
3626   // Compute the anchor point.
3627   //
3628   // relative to aBorderArea.TopLeft() (which is where the top-left
3629   // of aForFrame's border-box will be rendered)
3630   nsPoint imageTopLeft;
3631   if (NS_STYLE_IMAGELAYER_ATTACHMENT_FIXED == aLayer.mAttachment && !transformedFixed) {
3632     if (aFlags & nsCSSRendering::PAINTBG_TO_WINDOW) {
3633       // Clip background-attachment:fixed backgrounds to the viewport, if we're
3634       // painting to the screen and not transformed. This avoids triggering
3635       // tiling in common cases, without affecting output since drawing is
3636       // always clipped to the viewport when we draw to the screen. (But it's
3637       // not a pure optimization since it can affect the values of pixels at the
3638       // edge of the viewport --- whether they're sampled from a putative "next
3639       // tile" or not.)
3640       bgClipRect.IntersectRect(bgClipRect, bgPositioningArea + aBorderArea.TopLeft());
3641     }
3642   }
3643 
3644   int repeatX = aLayer.mRepeat.mXRepeat;
3645   int repeatY = aLayer.mRepeat.mYRepeat;
3646 
3647   // Scale the image as specified for background-size and background-repeat.
3648   // Also as required for proper background positioning when background-position
3649   // is defined with percentages.
3650   CSSSizeOrRatio intrinsicSize = state.mImageRenderer.ComputeIntrinsicSize();
3651   nsSize bgPositionSize = bgPositioningArea.Size();
3652   nsSize imageSize = ComputeDrawnSizeForBackground(intrinsicSize,
3653                                                    bgPositionSize,
3654                                                    aLayer.mSize,
3655                                                    repeatX,
3656                                                    repeatY);
3657 
3658   if (imageSize.width <= 0 || imageSize.height <= 0)
3659     return state;
3660 
3661   state.mImageRenderer.SetPreferredSize(intrinsicSize,
3662                                         imageSize);
3663 
3664   // Compute the position of the background now that the background's size is
3665   // determined.
3666   nsImageRenderer::ComputeObjectAnchorPoint(aLayer.mPosition,
3667                                             bgPositionSize, imageSize,
3668                                             &imageTopLeft, &state.mAnchor);
3669   state.mRepeatSize = imageSize;
3670   if (repeatX == NS_STYLE_IMAGELAYER_REPEAT_SPACE) {
3671     bool isRepeat;
3672     state.mRepeatSize.width = ComputeSpacedRepeatSize(imageSize.width,
3673                                                       bgPositionSize.width,
3674                                                       isRepeat);
3675     if (isRepeat) {
3676       imageTopLeft.x = 0;
3677       state.mAnchor.x = 0;
3678     } else {
3679       repeatX = NS_STYLE_IMAGELAYER_REPEAT_NO_REPEAT;
3680     }
3681   }
3682 
3683   if (repeatY == NS_STYLE_IMAGELAYER_REPEAT_SPACE) {
3684     bool isRepeat;
3685     state.mRepeatSize.height = ComputeSpacedRepeatSize(imageSize.height,
3686                                                        bgPositionSize.height,
3687                                                        isRepeat);
3688     if (isRepeat) {
3689       imageTopLeft.y = 0;
3690       state.mAnchor.y = 0;
3691     } else {
3692       repeatY = NS_STYLE_IMAGELAYER_REPEAT_NO_REPEAT;
3693     }
3694   }
3695 
3696   imageTopLeft += bgPositioningArea.TopLeft();
3697   state.mAnchor += bgPositioningArea.TopLeft();
3698   state.mDestArea = nsRect(imageTopLeft + aBorderArea.TopLeft(), imageSize);
3699   state.mFillArea = state.mDestArea;
3700 
3701   ExtendMode repeatMode = ExtendMode::CLAMP;
3702   if (repeatX == NS_STYLE_IMAGELAYER_REPEAT_REPEAT ||
3703       repeatX == NS_STYLE_IMAGELAYER_REPEAT_ROUND ||
3704       repeatX == NS_STYLE_IMAGELAYER_REPEAT_SPACE) {
3705     state.mFillArea.x = bgClipRect.x;
3706     state.mFillArea.width = bgClipRect.width;
3707     repeatMode = ExtendMode::REPEAT_X;
3708   }
3709   if (repeatY == NS_STYLE_IMAGELAYER_REPEAT_REPEAT ||
3710       repeatY == NS_STYLE_IMAGELAYER_REPEAT_ROUND ||
3711       repeatY == NS_STYLE_IMAGELAYER_REPEAT_SPACE) {
3712     state.mFillArea.y = bgClipRect.y;
3713     state.mFillArea.height = bgClipRect.height;
3714 
3715     /***
3716      * We're repeating on the X axis already,
3717      * so if we have to repeat in the Y axis,
3718      * we really need to repeat in both directions.
3719      */
3720     if (repeatMode == ExtendMode::REPEAT_X) {
3721       repeatMode = ExtendMode::REPEAT;
3722     } else {
3723       repeatMode = ExtendMode::REPEAT_Y;
3724     }
3725   }
3726   state.mImageRenderer.SetExtendMode(repeatMode);
3727   state.mImageRenderer.SetMaskOp(aLayer.mMaskMode);
3728 
3729   state.mFillArea.IntersectRect(state.mFillArea, bgClipRect);
3730 
3731   return state;
3732 }
3733 
3734 nsRect
GetBackgroundLayerRect(nsPresContext * aPresContext,nsIFrame * aForFrame,const nsRect & aBorderArea,const nsRect & aClipRect,const nsStyleImageLayers::Layer & aLayer,uint32_t aFlags)3735 nsCSSRendering::GetBackgroundLayerRect(nsPresContext* aPresContext,
3736                                        nsIFrame* aForFrame,
3737                                        const nsRect& aBorderArea,
3738                                        const nsRect& aClipRect,
3739                                        const nsStyleImageLayers::Layer& aLayer,
3740                                        uint32_t aFlags)
3741 {
3742   Sides skipSides = aForFrame->GetSkipSides();
3743   nsRect borderArea =
3744     ::BoxDecorationRectForBackground(aForFrame, aBorderArea, skipSides);
3745   nsBackgroundLayerState state =
3746       PrepareImageLayer(aPresContext, aForFrame, aFlags, borderArea,
3747                              aClipRect, aLayer);
3748   return state.mFillArea;
3749 }
3750 
3751 static DrawResult
DrawBorderImage(nsPresContext * aPresContext,nsRenderingContext & aRenderingContext,nsIFrame * aForFrame,const nsRect & aBorderArea,const nsStyleBorder & aStyleBorder,const nsRect & aDirtyRect,Sides aSkipSides,PaintBorderFlags aFlags)3752 DrawBorderImage(nsPresContext*       aPresContext,
3753                 nsRenderingContext&  aRenderingContext,
3754                 nsIFrame*            aForFrame,
3755                 const nsRect&        aBorderArea,
3756                 const nsStyleBorder& aStyleBorder,
3757                 const nsRect&        aDirtyRect,
3758                 Sides                aSkipSides,
3759                 PaintBorderFlags     aFlags)
3760 {
3761   NS_PRECONDITION(aStyleBorder.IsBorderImageLoaded(),
3762                   "drawing border image that isn't successfully loaded");
3763 
3764   if (aDirtyRect.IsEmpty()) {
3765     return DrawResult::SUCCESS;
3766   }
3767 
3768   uint32_t irFlags = 0;
3769   if (aFlags & PaintBorderFlags::SYNC_DECODE_IMAGES) {
3770     irFlags |= nsImageRenderer::FLAG_SYNC_DECODE_IMAGES;
3771   }
3772   nsImageRenderer renderer(aForFrame, &aStyleBorder.mBorderImageSource, irFlags);
3773 
3774   // Ensure we get invalidated for loads and animations of the image.
3775   // We need to do this here because this might be the only code that
3776   // knows about the association of the style data with the frame.
3777   // XXX We shouldn't really... since if anybody is passing in a
3778   // different style, they'll potentially have the wrong size for the
3779   // border too.
3780   aForFrame->AssociateImage(aStyleBorder.mBorderImageSource, aPresContext);
3781 
3782   if (!renderer.PrepareImage()) {
3783     return renderer.PrepareResult();
3784   }
3785 
3786   // NOTE: no Save() yet, we do that later by calling autoSR.EnsureSaved()
3787   // in case we need it.
3788   gfxContextAutoSaveRestore autoSR;
3789 
3790   // Determine the border image area, which by default corresponds to the
3791   // border box but can be modified by 'border-image-outset'.
3792   // Note that 'border-radius' do not apply to 'border-image' borders per
3793   // <http://dev.w3.org/csswg/css-backgrounds/#corner-clipping>.
3794   nsRect borderImgArea;
3795   nsMargin borderWidths(aStyleBorder.GetComputedBorder());
3796   nsMargin imageOutset(aStyleBorder.GetImageOutset());
3797   if (::IsBoxDecorationSlice(aStyleBorder) && !aSkipSides.IsEmpty()) {
3798     borderImgArea = ::BoxDecorationRectForBorder(aForFrame, aBorderArea,
3799                                                  aSkipSides, &aStyleBorder);
3800     if (borderImgArea.IsEqualEdges(aBorderArea)) {
3801       // No need for a clip, just skip the sides we don't want.
3802       borderWidths.ApplySkipSides(aSkipSides);
3803       imageOutset.ApplySkipSides(aSkipSides);
3804       borderImgArea.Inflate(imageOutset);
3805     } else {
3806       // We're drawing borders around the joined continuation boxes so we need
3807       // to clip that to the slice that we want for this frame.
3808       borderImgArea.Inflate(imageOutset);
3809       imageOutset.ApplySkipSides(aSkipSides);
3810       nsRect clip = aBorderArea;
3811       clip.Inflate(imageOutset);
3812       autoSR.EnsureSaved(aRenderingContext.ThebesContext());
3813       aRenderingContext.ThebesContext()->
3814         Clip(NSRectToSnappedRect(clip,
3815                                  aForFrame->PresContext()->AppUnitsPerDevPixel(),
3816                                  *aRenderingContext.GetDrawTarget()));
3817     }
3818   } else {
3819     borderImgArea = aBorderArea;
3820     borderImgArea.Inflate(imageOutset);
3821   }
3822 
3823   // Calculate the image size used to compute slice points.
3824   CSSSizeOrRatio intrinsicSize = renderer.ComputeIntrinsicSize();
3825   nsSize imageSize = nsImageRenderer::ComputeConcreteSize(CSSSizeOrRatio(),
3826                                                           intrinsicSize,
3827                                                           borderImgArea.Size());
3828   renderer.SetPreferredSize(intrinsicSize, imageSize);
3829 
3830   // Compute the used values of 'border-image-slice' and 'border-image-width';
3831   // we do them together because the latter can depend on the former.
3832   nsMargin slice;
3833   nsMargin border;
3834   NS_FOR_CSS_SIDES(s) {
3835     nsStyleCoord coord = aStyleBorder.mBorderImageSlice.Get(s);
3836     int32_t imgDimension = NS_SIDE_IS_VERTICAL(s)
3837                            ? imageSize.width : imageSize.height;
3838     nscoord borderDimension = NS_SIDE_IS_VERTICAL(s)
3839                            ? borderImgArea.width : borderImgArea.height;
3840     double value;
3841     switch (coord.GetUnit()) {
3842       case eStyleUnit_Percent:
3843         value = coord.GetPercentValue() * imgDimension;
3844         break;
3845       case eStyleUnit_Factor:
3846         value = nsPresContext::CSSPixelsToAppUnits(
3847           NS_lround(coord.GetFactorValue()));
3848         break;
3849       default:
3850         NS_NOTREACHED("unexpected CSS unit for image slice");
3851         value = 0;
3852         break;
3853     }
3854     if (value < 0)
3855       value = 0;
3856     if (value > imgDimension)
3857       value = imgDimension;
3858     slice.Side(s) = value;
3859 
3860     coord = aStyleBorder.mBorderImageWidth.Get(s);
3861     switch (coord.GetUnit()) {
3862       case eStyleUnit_Coord: // absolute dimension
3863         value = coord.GetCoordValue();
3864         break;
3865       case eStyleUnit_Percent:
3866         value = coord.GetPercentValue() * borderDimension;
3867         break;
3868       case eStyleUnit_Factor:
3869         value = coord.GetFactorValue() * borderWidths.Side(s);
3870         break;
3871       case eStyleUnit_Auto:  // same as the slice value, in CSS pixels
3872         value = slice.Side(s);
3873         break;
3874       default:
3875         NS_NOTREACHED("unexpected CSS unit for border image area division");
3876         value = 0;
3877         break;
3878     }
3879     // NSToCoordRoundWithClamp rounds towards infinity, but that's OK
3880     // because we expect value to be non-negative.
3881     MOZ_ASSERT(value >= 0);
3882     border.Side(s) = NSToCoordRoundWithClamp(value);
3883     MOZ_ASSERT(border.Side(s) >= 0);
3884   }
3885 
3886   // "If two opposite border-image-width offsets are large enough that they
3887   // overlap, their used values are proportionately reduced until they no
3888   // longer overlap."
3889   uint32_t combinedBorderWidth = uint32_t(border.left) +
3890                                  uint32_t(border.right);
3891   double scaleX = combinedBorderWidth > uint32_t(borderImgArea.width)
3892                   ? borderImgArea.width / double(combinedBorderWidth)
3893                   : 1.0;
3894   uint32_t combinedBorderHeight = uint32_t(border.top) +
3895                                   uint32_t(border.bottom);
3896   double scaleY = combinedBorderHeight > uint32_t(borderImgArea.height)
3897                   ? borderImgArea.height / double(combinedBorderHeight)
3898                   : 1.0;
3899   double scale = std::min(scaleX, scaleY);
3900   if (scale < 1.0) {
3901     border.left *= scale;
3902     border.right *= scale;
3903     border.top *= scale;
3904     border.bottom *= scale;
3905     NS_ASSERTION(border.left + border.right <= borderImgArea.width &&
3906                  border.top + border.bottom <= borderImgArea.height,
3907                  "rounding error in width reduction???");
3908   }
3909 
3910   // These helper tables recharacterize the 'slice' and 'width' margins
3911   // in a more convenient form: they are the x/y/width/height coords
3912   // required for various bands of the border, and they have been transformed
3913   // to be relative to the innerRect (for 'slice') or the page (for 'border').
3914   enum {
3915     LEFT, MIDDLE, RIGHT,
3916     TOP = LEFT, BOTTOM = RIGHT
3917   };
3918   const nscoord borderX[3] = {
3919     borderImgArea.x + 0,
3920     borderImgArea.x + border.left,
3921     borderImgArea.x + borderImgArea.width - border.right,
3922   };
3923   const nscoord borderY[3] = {
3924     borderImgArea.y + 0,
3925     borderImgArea.y + border.top,
3926     borderImgArea.y + borderImgArea.height - border.bottom,
3927   };
3928   const nscoord borderWidth[3] = {
3929     border.left,
3930     borderImgArea.width - border.left - border.right,
3931     border.right,
3932   };
3933   const nscoord borderHeight[3] = {
3934     border.top,
3935     borderImgArea.height - border.top - border.bottom,
3936     border.bottom,
3937   };
3938   const int32_t sliceX[3] = {
3939     0,
3940     slice.left,
3941     imageSize.width - slice.right,
3942   };
3943   const int32_t sliceY[3] = {
3944     0,
3945     slice.top,
3946     imageSize.height - slice.bottom,
3947   };
3948   const int32_t sliceWidth[3] = {
3949     slice.left,
3950     std::max(imageSize.width - slice.left - slice.right, 0),
3951     slice.right,
3952   };
3953   const int32_t sliceHeight[3] = {
3954     slice.top,
3955     std::max(imageSize.height - slice.top - slice.bottom, 0),
3956     slice.bottom,
3957   };
3958 
3959   DrawResult result = DrawResult::SUCCESS;
3960 
3961   // intrinsicSize.CanComputeConcreteSize() return false means we can not
3962   // read intrinsic size from aStyleBorder.mBorderImageSource.
3963   // In this condition, we pass imageSize(a resolved size comes from
3964   // default sizing algorithm) to renderer as the viewport size.
3965   Maybe<nsSize> svgViewportSize = intrinsicSize.CanComputeConcreteSize() ?
3966     Nothing() : Some(imageSize);
3967   bool hasIntrinsicRatio = intrinsicSize.HasRatio();
3968   renderer.PurgeCacheForViewportChange(svgViewportSize, hasIntrinsicRatio);
3969 
3970   for (int i = LEFT; i <= RIGHT; i++) {
3971     for (int j = TOP; j <= BOTTOM; j++) {
3972       uint8_t fillStyleH, fillStyleV;
3973       nsSize unitSize;
3974 
3975       if (i == MIDDLE && j == MIDDLE) {
3976         // Discard the middle portion unless set to fill.
3977         if (NS_STYLE_BORDER_IMAGE_SLICE_NOFILL ==
3978             aStyleBorder.mBorderImageFill) {
3979           continue;
3980         }
3981 
3982         NS_ASSERTION(NS_STYLE_BORDER_IMAGE_SLICE_FILL ==
3983                      aStyleBorder.mBorderImageFill,
3984                      "Unexpected border image fill");
3985 
3986         // css-background:
3987         //     The middle image's width is scaled by the same factor as the
3988         //     top image unless that factor is zero or infinity, in which
3989         //     case the scaling factor of the bottom is substituted, and
3990         //     failing that, the width is not scaled. The height of the
3991         //     middle image is scaled by the same factor as the left image
3992         //     unless that factor is zero or infinity, in which case the
3993         //     scaling factor of the right image is substituted, and failing
3994         //     that, the height is not scaled.
3995         gfxFloat hFactor, vFactor;
3996 
3997         if (0 < border.left && 0 < slice.left)
3998           vFactor = gfxFloat(border.left)/slice.left;
3999         else if (0 < border.right && 0 < slice.right)
4000           vFactor = gfxFloat(border.right)/slice.right;
4001         else
4002           vFactor = 1;
4003 
4004         if (0 < border.top && 0 < slice.top)
4005           hFactor = gfxFloat(border.top)/slice.top;
4006         else if (0 < border.bottom && 0 < slice.bottom)
4007           hFactor = gfxFloat(border.bottom)/slice.bottom;
4008         else
4009           hFactor = 1;
4010 
4011         unitSize.width = sliceWidth[i]*hFactor;
4012         unitSize.height = sliceHeight[j]*vFactor;
4013         fillStyleH = aStyleBorder.mBorderImageRepeatH;
4014         fillStyleV = aStyleBorder.mBorderImageRepeatV;
4015 
4016       } else if (i == MIDDLE) { // top, bottom
4017         // Sides are always stretched to the thickness of their border,
4018         // and stretched proportionately on the other axis.
4019         gfxFloat factor;
4020         if (0 < borderHeight[j] && 0 < sliceHeight[j])
4021           factor = gfxFloat(borderHeight[j])/sliceHeight[j];
4022         else
4023           factor = 1;
4024 
4025         unitSize.width = sliceWidth[i]*factor;
4026         unitSize.height = borderHeight[j];
4027         fillStyleH = aStyleBorder.mBorderImageRepeatH;
4028         fillStyleV = NS_STYLE_BORDER_IMAGE_REPEAT_STRETCH;
4029 
4030       } else if (j == MIDDLE) { // left, right
4031         gfxFloat factor;
4032         if (0 < borderWidth[i] && 0 < sliceWidth[i])
4033           factor = gfxFloat(borderWidth[i])/sliceWidth[i];
4034         else
4035           factor = 1;
4036 
4037         unitSize.width = borderWidth[i];
4038         unitSize.height = sliceHeight[j]*factor;
4039         fillStyleH = NS_STYLE_BORDER_IMAGE_REPEAT_STRETCH;
4040         fillStyleV = aStyleBorder.mBorderImageRepeatV;
4041 
4042       } else {
4043         // Corners are always stretched to fit the corner.
4044         unitSize.width = borderWidth[i];
4045         unitSize.height = borderHeight[j];
4046         fillStyleH = NS_STYLE_BORDER_IMAGE_REPEAT_STRETCH;
4047         fillStyleV = NS_STYLE_BORDER_IMAGE_REPEAT_STRETCH;
4048       }
4049 
4050       nsRect destArea(borderX[i], borderY[j], borderWidth[i], borderHeight[j]);
4051       nsRect subArea(sliceX[i], sliceY[j], sliceWidth[i], sliceHeight[j]);
4052       if (subArea.IsEmpty())
4053         continue;
4054 
4055       nsIntRect intSubArea = subArea.ToOutsidePixels(nsPresContext::AppUnitsPerCSSPixel());
4056       result &=
4057         renderer.DrawBorderImageComponent(aPresContext,
4058                                           aRenderingContext, aDirtyRect,
4059                                           destArea, CSSIntRect(intSubArea.x,
4060                                                                intSubArea.y,
4061                                                                intSubArea.width,
4062                                                                intSubArea.height),
4063                                           fillStyleH, fillStyleV,
4064                                           unitSize, j * (RIGHT + 1) + i,
4065                                           svgViewportSize, hasIntrinsicRatio);
4066     }
4067   }
4068 
4069   return result;
4070 }
4071 
4072 // Begin table border-collapsing section
4073 // These functions were written to not disrupt the normal ones and yet satisfy some additional requirements
4074 // At some point, all functions should be unified to include the additional functionality that these provide
4075 
4076 static nscoord
RoundIntToPixel(nscoord aValue,nscoord aTwipsPerPixel,bool aRoundDown=false)4077 RoundIntToPixel(nscoord aValue,
4078                 nscoord aTwipsPerPixel,
4079                 bool    aRoundDown = false)
4080 {
4081   if (aTwipsPerPixel <= 0)
4082     // We must be rendering to a device that has a resolution greater than Twips!
4083     // In that case, aValue is as accurate as it's going to get.
4084     return aValue;
4085 
4086   nscoord halfPixel = NSToCoordRound(aTwipsPerPixel / 2.0f);
4087   nscoord extra = aValue % aTwipsPerPixel;
4088   nscoord finalValue = (!aRoundDown && (extra >= halfPixel)) ? aValue + (aTwipsPerPixel - extra) : aValue - extra;
4089   return finalValue;
4090 }
4091 
4092 static nscoord
RoundFloatToPixel(float aValue,nscoord aTwipsPerPixel,bool aRoundDown=false)4093 RoundFloatToPixel(float   aValue,
4094                   nscoord aTwipsPerPixel,
4095                   bool    aRoundDown = false)
4096 {
4097   return RoundIntToPixel(NSToCoordRound(aValue), aTwipsPerPixel, aRoundDown);
4098 }
4099 
SetPoly(const Rect & aRect,Point * poly)4100 static void SetPoly(const Rect& aRect, Point* poly)
4101 {
4102   poly[0].x = aRect.x;
4103   poly[0].y = aRect.y;
4104   poly[1].x = aRect.x + aRect.width;
4105   poly[1].y = aRect.y;
4106   poly[2].x = aRect.x + aRect.width;
4107   poly[2].y = aRect.y + aRect.height;
4108   poly[3].x = aRect.x;
4109   poly[3].y = aRect.y + aRect.height;
4110 }
4111 
4112 static void
DrawDashedSegment(DrawTarget & aDrawTarget,nsRect aRect,nscoord aDashLength,nscolor aColor,int32_t aAppUnitsPerDevPixel,nscoord aTwipsPerPixel,bool aHorizontal)4113 DrawDashedSegment(DrawTarget&          aDrawTarget,
4114                   nsRect               aRect,
4115                   nscoord              aDashLength,
4116                   nscolor              aColor,
4117                   int32_t              aAppUnitsPerDevPixel,
4118                   nscoord              aTwipsPerPixel,
4119                   bool                 aHorizontal)
4120 {
4121   ColorPattern color(ToDeviceColor(aColor));
4122   DrawOptions drawOptions(1.f, CompositionOp::OP_OVER, AntialiasMode::NONE);
4123   StrokeOptions strokeOptions;
4124 
4125   Float dash[2];
4126   dash[0] = Float(aDashLength) / aAppUnitsPerDevPixel;
4127   dash[1] = dash[0];
4128 
4129   strokeOptions.mDashPattern = dash;
4130   strokeOptions.mDashLength = MOZ_ARRAY_LENGTH(dash);
4131 
4132   if (aHorizontal) {
4133     nsPoint left = (aRect.TopLeft() + aRect.BottomLeft()) / 2;
4134     nsPoint right = (aRect.TopRight() + aRect.BottomRight()) / 2;
4135     strokeOptions.mLineWidth = Float(aRect.height) / aAppUnitsPerDevPixel;
4136     StrokeLineWithSnapping(left, right,
4137                            aAppUnitsPerDevPixel, aDrawTarget,
4138                            color, strokeOptions, drawOptions);
4139   } else {
4140     nsPoint top = (aRect.TopLeft() + aRect.TopRight()) / 2;
4141     nsPoint bottom = (aRect.BottomLeft() + aRect.BottomRight()) / 2;
4142     strokeOptions.mLineWidth = Float(aRect.width) / aAppUnitsPerDevPixel;
4143     StrokeLineWithSnapping(top, bottom,
4144                            aAppUnitsPerDevPixel, aDrawTarget,
4145                            color, strokeOptions, drawOptions);
4146   }
4147 }
4148 
4149 static void
DrawSolidBorderSegment(DrawTarget & aDrawTarget,nsRect aRect,nscolor aColor,int32_t aAppUnitsPerDevPixel,nscoord aTwipsPerPixel,uint8_t aStartBevelSide=0,nscoord aStartBevelOffset=0,uint8_t aEndBevelSide=0,nscoord aEndBevelOffset=0)4150 DrawSolidBorderSegment(DrawTarget&          aDrawTarget,
4151                        nsRect               aRect,
4152                        nscolor              aColor,
4153                        int32_t              aAppUnitsPerDevPixel,
4154                        nscoord              aTwipsPerPixel,
4155                        uint8_t              aStartBevelSide = 0,
4156                        nscoord              aStartBevelOffset = 0,
4157                        uint8_t              aEndBevelSide = 0,
4158                        nscoord              aEndBevelOffset = 0)
4159 {
4160   ColorPattern color(ToDeviceColor(aColor));
4161   DrawOptions drawOptions(1.f, CompositionOp::OP_OVER, AntialiasMode::NONE);
4162 
4163   // We don't need to bevel single pixel borders
4164   if ((aRect.width == aTwipsPerPixel) || (aRect.height == aTwipsPerPixel) ||
4165       ((0 == aStartBevelOffset) && (0 == aEndBevelOffset))) {
4166     // simple rectangle
4167     aDrawTarget.FillRect(NSRectToSnappedRect(aRect, aAppUnitsPerDevPixel,
4168                                              aDrawTarget),
4169                          color, drawOptions);
4170   }
4171   else {
4172     // polygon with beveling
4173     Point poly[4];
4174     SetPoly(NSRectToSnappedRect(aRect, aAppUnitsPerDevPixel, aDrawTarget),
4175             poly);
4176 
4177     Float startBevelOffset =
4178       NSAppUnitsToFloatPixels(aStartBevelOffset, aAppUnitsPerDevPixel);
4179     switch(aStartBevelSide) {
4180     case NS_SIDE_TOP:
4181       poly[0].x += startBevelOffset;
4182       break;
4183     case NS_SIDE_BOTTOM:
4184       poly[3].x += startBevelOffset;
4185       break;
4186     case NS_SIDE_RIGHT:
4187       poly[1].y += startBevelOffset;
4188       break;
4189     case NS_SIDE_LEFT:
4190       poly[0].y += startBevelOffset;
4191     }
4192 
4193     Float endBevelOffset =
4194       NSAppUnitsToFloatPixels(aEndBevelOffset, aAppUnitsPerDevPixel);
4195     switch(aEndBevelSide) {
4196     case NS_SIDE_TOP:
4197       poly[1].x -= endBevelOffset;
4198       break;
4199     case NS_SIDE_BOTTOM:
4200       poly[2].x -= endBevelOffset;
4201       break;
4202     case NS_SIDE_RIGHT:
4203       poly[2].y -= endBevelOffset;
4204       break;
4205     case NS_SIDE_LEFT:
4206       poly[3].y -= endBevelOffset;
4207     }
4208 
4209     RefPtr<PathBuilder> builder = aDrawTarget.CreatePathBuilder();
4210     builder->MoveTo(poly[0]);
4211     builder->LineTo(poly[1]);
4212     builder->LineTo(poly[2]);
4213     builder->LineTo(poly[3]);
4214     builder->Close();
4215     RefPtr<Path> path = builder->Finish();
4216     aDrawTarget.Fill(path, color, drawOptions);
4217   }
4218 }
4219 
4220 static void
GetDashInfo(nscoord aBorderLength,nscoord aDashLength,nscoord aTwipsPerPixel,int32_t & aNumDashSpaces,nscoord & aStartDashLength,nscoord & aEndDashLength)4221 GetDashInfo(nscoord  aBorderLength,
4222             nscoord  aDashLength,
4223             nscoord  aTwipsPerPixel,
4224             int32_t& aNumDashSpaces,
4225             nscoord& aStartDashLength,
4226             nscoord& aEndDashLength)
4227 {
4228   aNumDashSpaces = 0;
4229   if (aStartDashLength + aDashLength + aEndDashLength >= aBorderLength) {
4230     aStartDashLength = aBorderLength;
4231     aEndDashLength = 0;
4232   }
4233   else {
4234     aNumDashSpaces = (aBorderLength - aDashLength)/ (2 * aDashLength); // round down
4235     nscoord extra = aBorderLength - aStartDashLength - aEndDashLength - (((2 * aNumDashSpaces) - 1) * aDashLength);
4236     if (extra > 0) {
4237       nscoord half = RoundIntToPixel(extra / 2, aTwipsPerPixel);
4238       aStartDashLength += half;
4239       aEndDashLength += (extra - half);
4240     }
4241   }
4242 }
4243 
4244 void
DrawTableBorderSegment(DrawTarget & aDrawTarget,uint8_t aBorderStyle,nscolor aBorderColor,const nsStyleBackground * aBGColor,const nsRect & aBorder,int32_t aAppUnitsPerDevPixel,int32_t aAppUnitsPerCSSPixel,uint8_t aStartBevelSide,nscoord aStartBevelOffset,uint8_t aEndBevelSide,nscoord aEndBevelOffset)4245 nsCSSRendering::DrawTableBorderSegment(DrawTarget&              aDrawTarget,
4246                                        uint8_t                  aBorderStyle,
4247                                        nscolor                  aBorderColor,
4248                                        const nsStyleBackground* aBGColor,
4249                                        const nsRect&            aBorder,
4250                                        int32_t                  aAppUnitsPerDevPixel,
4251                                        int32_t                  aAppUnitsPerCSSPixel,
4252                                        uint8_t                  aStartBevelSide,
4253                                        nscoord                  aStartBevelOffset,
4254                                        uint8_t                  aEndBevelSide,
4255                                        nscoord                  aEndBevelOffset)
4256 {
4257   bool horizontal = ((NS_SIDE_TOP == aStartBevelSide) || (NS_SIDE_BOTTOM == aStartBevelSide));
4258   nscoord twipsPerPixel = NSIntPixelsToAppUnits(1, aAppUnitsPerCSSPixel);
4259   uint8_t ridgeGroove = NS_STYLE_BORDER_STYLE_RIDGE;
4260 
4261   if ((twipsPerPixel >= aBorder.width) || (twipsPerPixel >= aBorder.height) ||
4262       (NS_STYLE_BORDER_STYLE_DASHED == aBorderStyle) || (NS_STYLE_BORDER_STYLE_DOTTED == aBorderStyle)) {
4263     // no beveling for 1 pixel border, dash or dot
4264     aStartBevelOffset = 0;
4265     aEndBevelOffset = 0;
4266   }
4267 
4268   switch (aBorderStyle) {
4269   case NS_STYLE_BORDER_STYLE_NONE:
4270   case NS_STYLE_BORDER_STYLE_HIDDEN:
4271     //NS_ASSERTION(false, "style of none or hidden");
4272     break;
4273   case NS_STYLE_BORDER_STYLE_DOTTED:
4274   case NS_STYLE_BORDER_STYLE_DASHED:
4275     {
4276       nscoord dashLength = (NS_STYLE_BORDER_STYLE_DASHED == aBorderStyle) ? DASH_LENGTH : DOT_LENGTH;
4277       // make the dash length proportional to the border thickness
4278       dashLength *= (horizontal) ? aBorder.height : aBorder.width;
4279       // make the min dash length for the ends 1/2 the dash length
4280       nscoord minDashLength = (NS_STYLE_BORDER_STYLE_DASHED == aBorderStyle)
4281                               ? RoundFloatToPixel(((float)dashLength) / 2.0f, twipsPerPixel) : dashLength;
4282       minDashLength = std::max(minDashLength, twipsPerPixel);
4283       nscoord numDashSpaces = 0;
4284       nscoord startDashLength = minDashLength;
4285       nscoord endDashLength   = minDashLength;
4286       if (horizontal) {
4287         GetDashInfo(aBorder.width, dashLength, twipsPerPixel, numDashSpaces,
4288                     startDashLength, endDashLength);
4289         nsRect rect(aBorder.x, aBorder.y, startDashLength, aBorder.height);
4290         DrawSolidBorderSegment(aDrawTarget, rect, aBorderColor,
4291                                aAppUnitsPerDevPixel, twipsPerPixel);
4292 
4293         rect.x += startDashLength + dashLength;
4294         rect.width = aBorder.width
4295                      - (startDashLength + endDashLength + dashLength);
4296         DrawDashedSegment(aDrawTarget, rect, dashLength, aBorderColor,
4297                           aAppUnitsPerDevPixel, twipsPerPixel, horizontal);
4298 
4299         rect.x += rect.width;
4300         rect.width = endDashLength;
4301         DrawSolidBorderSegment(aDrawTarget, rect, aBorderColor,
4302                                aAppUnitsPerDevPixel, twipsPerPixel);
4303       }
4304       else {
4305         GetDashInfo(aBorder.height, dashLength, twipsPerPixel, numDashSpaces,
4306                     startDashLength, endDashLength);
4307         nsRect rect(aBorder.x, aBorder.y, aBorder.width, startDashLength);
4308         DrawSolidBorderSegment(aDrawTarget, rect, aBorderColor,
4309                                aAppUnitsPerDevPixel, twipsPerPixel);
4310 
4311         rect.y += rect.height + dashLength;
4312         rect.height = aBorder.height
4313                       - (startDashLength + endDashLength + dashLength);
4314         DrawDashedSegment(aDrawTarget, rect, dashLength, aBorderColor,
4315                           aAppUnitsPerDevPixel, twipsPerPixel, horizontal);
4316 
4317         rect.y += rect.height;
4318         rect.height = endDashLength;
4319         DrawSolidBorderSegment(aDrawTarget, rect, aBorderColor,
4320                                aAppUnitsPerDevPixel, twipsPerPixel);
4321       }
4322     }
4323     break;
4324   case NS_STYLE_BORDER_STYLE_GROOVE:
4325     ridgeGroove = NS_STYLE_BORDER_STYLE_GROOVE; // and fall through to ridge
4326     MOZ_FALLTHROUGH;
4327   case NS_STYLE_BORDER_STYLE_RIDGE:
4328     if ((horizontal && (twipsPerPixel >= aBorder.height)) ||
4329         (!horizontal && (twipsPerPixel >= aBorder.width))) {
4330       // a one pixel border
4331       DrawSolidBorderSegment(aDrawTarget, aBorder, aBorderColor,
4332                              aAppUnitsPerDevPixel, twipsPerPixel,
4333                              aStartBevelSide, aStartBevelOffset,
4334                              aEndBevelSide, aEndBevelOffset);
4335     }
4336     else {
4337       nscoord startBevel = (aStartBevelOffset > 0)
4338                             ? RoundFloatToPixel(0.5f * (float)aStartBevelOffset, twipsPerPixel, true) : 0;
4339       nscoord endBevel =   (aEndBevelOffset > 0)
4340                             ? RoundFloatToPixel(0.5f * (float)aEndBevelOffset, twipsPerPixel, true) : 0;
4341       mozilla::css::Side ridgeGrooveSide = (horizontal) ? NS_SIDE_TOP : NS_SIDE_LEFT;
4342       // FIXME: In theory, this should use the visited-dependent
4343       // background color, but I don't care.
4344       nscolor bevelColor = MakeBevelColor(ridgeGrooveSide, ridgeGroove,
4345                                           aBGColor->mBackgroundColor,
4346                                           aBorderColor);
4347       nsRect rect(aBorder);
4348       nscoord half;
4349       if (horizontal) { // top, bottom
4350         half = RoundFloatToPixel(0.5f * (float)aBorder.height, twipsPerPixel);
4351         rect.height = half;
4352         if (NS_SIDE_TOP == aStartBevelSide) {
4353           rect.x += startBevel;
4354           rect.width -= startBevel;
4355         }
4356         if (NS_SIDE_TOP == aEndBevelSide) {
4357           rect.width -= endBevel;
4358         }
4359         DrawSolidBorderSegment(aDrawTarget, rect, bevelColor,
4360                                aAppUnitsPerDevPixel, twipsPerPixel,
4361                                aStartBevelSide, startBevel, aEndBevelSide,
4362                                endBevel);
4363       }
4364       else { // left, right
4365         half = RoundFloatToPixel(0.5f * (float)aBorder.width, twipsPerPixel);
4366         rect.width = half;
4367         if (NS_SIDE_LEFT == aStartBevelSide) {
4368           rect.y += startBevel;
4369           rect.height -= startBevel;
4370         }
4371         if (NS_SIDE_LEFT == aEndBevelSide) {
4372           rect.height -= endBevel;
4373         }
4374         DrawSolidBorderSegment(aDrawTarget, rect, bevelColor,
4375                                aAppUnitsPerDevPixel, twipsPerPixel,
4376                                aStartBevelSide, startBevel, aEndBevelSide,
4377                                endBevel);
4378       }
4379 
4380       rect = aBorder;
4381       ridgeGrooveSide = (NS_SIDE_TOP == ridgeGrooveSide) ? NS_SIDE_BOTTOM : NS_SIDE_RIGHT;
4382       // FIXME: In theory, this should use the visited-dependent
4383       // background color, but I don't care.
4384       bevelColor = MakeBevelColor(ridgeGrooveSide, ridgeGroove,
4385                                   aBGColor->mBackgroundColor, aBorderColor);
4386       if (horizontal) {
4387         rect.y = rect.y + half;
4388         rect.height = aBorder.height - half;
4389         if (NS_SIDE_BOTTOM == aStartBevelSide) {
4390           rect.x += startBevel;
4391           rect.width -= startBevel;
4392         }
4393         if (NS_SIDE_BOTTOM == aEndBevelSide) {
4394           rect.width -= endBevel;
4395         }
4396         DrawSolidBorderSegment(aDrawTarget, rect, bevelColor,
4397                                aAppUnitsPerDevPixel, twipsPerPixel,
4398                                aStartBevelSide, startBevel, aEndBevelSide,
4399                                endBevel);
4400       }
4401       else {
4402         rect.x = rect.x + half;
4403         rect.width = aBorder.width - half;
4404         if (NS_SIDE_RIGHT == aStartBevelSide) {
4405           rect.y += aStartBevelOffset - startBevel;
4406           rect.height -= startBevel;
4407         }
4408         if (NS_SIDE_RIGHT == aEndBevelSide) {
4409           rect.height -= endBevel;
4410         }
4411         DrawSolidBorderSegment(aDrawTarget, rect, bevelColor,
4412                                aAppUnitsPerDevPixel, twipsPerPixel,
4413                                aStartBevelSide, startBevel, aEndBevelSide,
4414                                endBevel);
4415       }
4416     }
4417     break;
4418   case NS_STYLE_BORDER_STYLE_DOUBLE:
4419     // We can only do "double" borders if the thickness of the border
4420     // is more than 2px.  Otherwise, we fall through to painting a
4421     // solid border.
4422     if ((aBorder.width > 2*twipsPerPixel || horizontal) &&
4423         (aBorder.height > 2*twipsPerPixel || !horizontal)) {
4424       nscoord startBevel = (aStartBevelOffset > 0)
4425                             ? RoundFloatToPixel(0.333333f * (float)aStartBevelOffset, twipsPerPixel) : 0;
4426       nscoord endBevel =   (aEndBevelOffset > 0)
4427                             ? RoundFloatToPixel(0.333333f * (float)aEndBevelOffset, twipsPerPixel) : 0;
4428       if (horizontal) { // top, bottom
4429         nscoord thirdHeight = RoundFloatToPixel(0.333333f * (float)aBorder.height, twipsPerPixel);
4430 
4431         // draw the top line or rect
4432         nsRect topRect(aBorder.x, aBorder.y, aBorder.width, thirdHeight);
4433         if (NS_SIDE_TOP == aStartBevelSide) {
4434           topRect.x += aStartBevelOffset - startBevel;
4435           topRect.width -= aStartBevelOffset - startBevel;
4436         }
4437         if (NS_SIDE_TOP == aEndBevelSide) {
4438           topRect.width -= aEndBevelOffset - endBevel;
4439         }
4440         DrawSolidBorderSegment(aDrawTarget, topRect, aBorderColor,
4441                                aAppUnitsPerDevPixel, twipsPerPixel,
4442                                aStartBevelSide, startBevel, aEndBevelSide,
4443                                endBevel);
4444 
4445         // draw the botom line or rect
4446         nscoord heightOffset = aBorder.height - thirdHeight;
4447         nsRect bottomRect(aBorder.x, aBorder.y + heightOffset, aBorder.width, aBorder.height - heightOffset);
4448         if (NS_SIDE_BOTTOM == aStartBevelSide) {
4449           bottomRect.x += aStartBevelOffset - startBevel;
4450           bottomRect.width -= aStartBevelOffset - startBevel;
4451         }
4452         if (NS_SIDE_BOTTOM == aEndBevelSide) {
4453           bottomRect.width -= aEndBevelOffset - endBevel;
4454         }
4455         DrawSolidBorderSegment(aDrawTarget, bottomRect, aBorderColor,
4456                                aAppUnitsPerDevPixel, twipsPerPixel,
4457                                aStartBevelSide, startBevel, aEndBevelSide,
4458                                endBevel);
4459       }
4460       else { // left, right
4461         nscoord thirdWidth = RoundFloatToPixel(0.333333f * (float)aBorder.width, twipsPerPixel);
4462 
4463         nsRect leftRect(aBorder.x, aBorder.y, thirdWidth, aBorder.height);
4464         if (NS_SIDE_LEFT == aStartBevelSide) {
4465           leftRect.y += aStartBevelOffset - startBevel;
4466           leftRect.height -= aStartBevelOffset - startBevel;
4467         }
4468         if (NS_SIDE_LEFT == aEndBevelSide) {
4469           leftRect.height -= aEndBevelOffset - endBevel;
4470         }
4471         DrawSolidBorderSegment(aDrawTarget, leftRect, aBorderColor,
4472                                aAppUnitsPerDevPixel, twipsPerPixel,
4473                                aStartBevelSide, startBevel, aEndBevelSide,
4474                                endBevel);
4475 
4476         nscoord widthOffset = aBorder.width - thirdWidth;
4477         nsRect rightRect(aBorder.x + widthOffset, aBorder.y, aBorder.width - widthOffset, aBorder.height);
4478         if (NS_SIDE_RIGHT == aStartBevelSide) {
4479           rightRect.y += aStartBevelOffset - startBevel;
4480           rightRect.height -= aStartBevelOffset - startBevel;
4481         }
4482         if (NS_SIDE_RIGHT == aEndBevelSide) {
4483           rightRect.height -= aEndBevelOffset - endBevel;
4484         }
4485         DrawSolidBorderSegment(aDrawTarget, rightRect, aBorderColor,
4486                                aAppUnitsPerDevPixel, twipsPerPixel,
4487                                aStartBevelSide, startBevel, aEndBevelSide,
4488                                endBevel);
4489       }
4490       break;
4491     }
4492     // else fall through to solid
4493     MOZ_FALLTHROUGH;
4494   case NS_STYLE_BORDER_STYLE_SOLID:
4495     DrawSolidBorderSegment(aDrawTarget, aBorder, aBorderColor,
4496                            aAppUnitsPerDevPixel, twipsPerPixel, aStartBevelSide,
4497                            aStartBevelOffset, aEndBevelSide, aEndBevelOffset);
4498     break;
4499   case NS_STYLE_BORDER_STYLE_OUTSET:
4500   case NS_STYLE_BORDER_STYLE_INSET:
4501     NS_ASSERTION(false, "inset, outset should have been converted to groove, ridge");
4502     break;
4503   case NS_STYLE_BORDER_STYLE_AUTO:
4504     NS_ASSERTION(false, "Unexpected 'auto' table border");
4505     break;
4506   }
4507 }
4508 
4509 // End table border-collapsing section
4510 
4511 Rect
ExpandPaintingRectForDecorationLine(nsIFrame * aFrame,const uint8_t aStyle,const Rect & aClippedRect,const Float aICoordInFrame,const Float aCycleLength,bool aVertical)4512 nsCSSRendering::ExpandPaintingRectForDecorationLine(
4513                   nsIFrame* aFrame,
4514                   const uint8_t aStyle,
4515                   const Rect& aClippedRect,
4516                   const Float aICoordInFrame,
4517                   const Float aCycleLength,
4518                   bool aVertical)
4519 {
4520   switch (aStyle) {
4521     case NS_STYLE_TEXT_DECORATION_STYLE_DOTTED:
4522     case NS_STYLE_TEXT_DECORATION_STYLE_DASHED:
4523     case NS_STYLE_TEXT_DECORATION_STYLE_WAVY:
4524       break;
4525     default:
4526       NS_ERROR("Invalid style was specified");
4527       return aClippedRect;
4528   }
4529 
4530   nsBlockFrame* block = nullptr;
4531   // Note that when we paint the decoration lines in relative positioned
4532   // box, we should paint them like all of the boxes are positioned as static.
4533   nscoord framePosInBlockAppUnits = 0;
4534   for (nsIFrame* f = aFrame; f; f = f->GetParent()) {
4535     block = do_QueryFrame(f);
4536     if (block) {
4537       break;
4538     }
4539     framePosInBlockAppUnits += aVertical ?
4540       f->GetNormalPosition().y : f->GetNormalPosition().x;
4541   }
4542 
4543   NS_ENSURE_TRUE(block, aClippedRect);
4544 
4545   nsPresContext *pc = aFrame->PresContext();
4546   Float framePosInBlock = Float(pc->AppUnitsToGfxUnits(framePosInBlockAppUnits));
4547   int32_t rectPosInBlock =
4548     int32_t(NS_round(framePosInBlock + aICoordInFrame));
4549   int32_t extraStartEdge =
4550     rectPosInBlock - (rectPosInBlock / int32_t(aCycleLength) * aCycleLength);
4551   Rect rect(aClippedRect);
4552   if (aVertical) {
4553     rect.y -= extraStartEdge;
4554     rect.height += extraStartEdge;
4555   } else {
4556     rect.x -= extraStartEdge;
4557     rect.width += extraStartEdge;
4558   }
4559   return rect;
4560 }
4561 
4562 void
PaintDecorationLine(nsIFrame * aFrame,DrawTarget & aDrawTarget,const PaintDecorationLineParams & aParams)4563 nsCSSRendering::PaintDecorationLine(nsIFrame* aFrame, DrawTarget& aDrawTarget,
4564                                     const PaintDecorationLineParams& aParams)
4565 {
4566   NS_ASSERTION(aParams.style != NS_STYLE_TEXT_DECORATION_STYLE_NONE,
4567                "aStyle is none");
4568 
4569   Rect rect = ToRect(GetTextDecorationRectInternal(aParams.pt, aParams));
4570   if (rect.IsEmpty() || !rect.Intersects(aParams.dirtyRect)) {
4571     return;
4572   }
4573 
4574   if (aParams.decoration != NS_STYLE_TEXT_DECORATION_LINE_UNDERLINE &&
4575       aParams.decoration != NS_STYLE_TEXT_DECORATION_LINE_OVERLINE &&
4576       aParams.decoration != NS_STYLE_TEXT_DECORATION_LINE_LINE_THROUGH) {
4577     NS_ERROR("Invalid decoration value!");
4578     return;
4579   }
4580 
4581   Float lineThickness = std::max(NS_round(aParams.lineSize.height), 1.0);
4582 
4583   ColorPattern color(ToDeviceColor(aParams.color));
4584   StrokeOptions strokeOptions(lineThickness);
4585   DrawOptions drawOptions;
4586 
4587   Float dash[2];
4588 
4589   AutoPopClips autoPopClips(&aDrawTarget);
4590 
4591   switch (aParams.style) {
4592     case NS_STYLE_TEXT_DECORATION_STYLE_SOLID:
4593     case NS_STYLE_TEXT_DECORATION_STYLE_DOUBLE:
4594       break;
4595     case NS_STYLE_TEXT_DECORATION_STYLE_DASHED: {
4596       autoPopClips.PushClipRect(rect);
4597       Float dashWidth = lineThickness * DOT_LENGTH * DASH_LENGTH;
4598       dash[0] = dashWidth;
4599       dash[1] = dashWidth;
4600       strokeOptions.mDashPattern = dash;
4601       strokeOptions.mDashLength = MOZ_ARRAY_LENGTH(dash);
4602       strokeOptions.mLineCap = CapStyle::BUTT;
4603       rect = ExpandPaintingRectForDecorationLine(aFrame, aParams.style,
4604                                                  rect, aParams.icoordInFrame,
4605                                                  dashWidth * 2,
4606                                                  aParams.vertical);
4607       // We should continue to draw the last dash even if it is not in the rect.
4608       rect.width += dashWidth;
4609       break;
4610     }
4611     case NS_STYLE_TEXT_DECORATION_STYLE_DOTTED: {
4612       autoPopClips.PushClipRect(rect);
4613       Float dashWidth = lineThickness * DOT_LENGTH;
4614       if (lineThickness > 2.0) {
4615         dash[0] = 0.f;
4616         dash[1] = dashWidth * 2.f;
4617         strokeOptions.mLineCap = CapStyle::ROUND;
4618       } else {
4619         dash[0] = dashWidth;
4620         dash[1] = dashWidth;
4621       }
4622       strokeOptions.mDashPattern = dash;
4623       strokeOptions.mDashLength = MOZ_ARRAY_LENGTH(dash);
4624       rect = ExpandPaintingRectForDecorationLine(aFrame, aParams.style,
4625                                                  rect, aParams.icoordInFrame,
4626                                                  dashWidth * 2,
4627                                                  aParams.vertical);
4628       // We should continue to draw the last dot even if it is not in the rect.
4629       rect.width += dashWidth;
4630       break;
4631     }
4632     case NS_STYLE_TEXT_DECORATION_STYLE_WAVY:
4633       autoPopClips.PushClipRect(rect);
4634       if (lineThickness > 2.0) {
4635         drawOptions.mAntialiasMode = AntialiasMode::SUBPIXEL;
4636       } else {
4637         // Don't use anti-aliasing here.  Because looks like lighter color wavy
4638         // line at this case.  And probably, users don't think the
4639         // non-anti-aliased wavy line is not pretty.
4640         drawOptions.mAntialiasMode = AntialiasMode::NONE;
4641       }
4642       break;
4643     default:
4644       NS_ERROR("Invalid style value!");
4645       return;
4646   }
4647 
4648   // The block-direction position should be set to the middle of the line.
4649   if (aParams.vertical) {
4650     rect.x += lineThickness / 2;
4651   } else {
4652     rect.y += lineThickness / 2;
4653   }
4654 
4655   switch (aParams.style) {
4656     case NS_STYLE_TEXT_DECORATION_STYLE_SOLID:
4657     case NS_STYLE_TEXT_DECORATION_STYLE_DOTTED:
4658     case NS_STYLE_TEXT_DECORATION_STYLE_DASHED: {
4659       Point p1 = rect.TopLeft();
4660       Point p2 = aParams.vertical ? rect.BottomLeft() : rect.TopRight();
4661       aDrawTarget.StrokeLine(p1, p2, color, strokeOptions, drawOptions);
4662       return;
4663     }
4664     case NS_STYLE_TEXT_DECORATION_STYLE_DOUBLE: {
4665       /**
4666        *  We are drawing double line as:
4667        *
4668        * +-------------------------------------------+
4669        * |XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX| ^
4670        * |XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX| | lineThickness
4671        * |XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX| v
4672        * |                                           |
4673        * |                                           |
4674        * |XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX| ^
4675        * |XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX| | lineThickness
4676        * |XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX| v
4677        * +-------------------------------------------+
4678        */
4679       Point p1 = rect.TopLeft();
4680       Point p2 = aParams.vertical ? rect.BottomLeft() : rect.TopRight();
4681       aDrawTarget.StrokeLine(p1, p2, color, strokeOptions, drawOptions);
4682 
4683       if (aParams.vertical) {
4684         rect.width -= lineThickness;
4685       } else {
4686         rect.height -= lineThickness;
4687       }
4688 
4689       p1 = aParams.vertical ? rect.TopRight() : rect.BottomLeft();
4690       p2 = rect.BottomRight();
4691       aDrawTarget.StrokeLine(p1, p2, color, strokeOptions, drawOptions);
4692       return;
4693     }
4694     case NS_STYLE_TEXT_DECORATION_STYLE_WAVY: {
4695       /**
4696        *  We are drawing wavy line as:
4697        *
4698        *  P: Path, X: Painted pixel
4699        *
4700        *     +---------------------------------------+
4701        *   XX|X            XXXXXX            XXXXXX  |
4702        *   PP|PX          XPPPPPPX          XPPPPPPX |    ^
4703        *   XX|XPX        XPXXXXXXPX        XPXXXXXXPX|    |
4704        *     | XPX      XPX      XPX      XPX      XP|X   |adv
4705        *     |  XPXXXXXXPX        XPXXXXXXPX        X|PX  |
4706        *     |   XPPPPPPX          XPPPPPPX          |XPX v
4707        *     |    XXXXXX            XXXXXX           | XX
4708        *     +---------------------------------------+
4709        *      <---><--->                                ^
4710        *      adv  flatLengthAtVertex                   rightMost
4711        *
4712        *  1. Always starts from top-left of the drawing area, however, we need
4713        *     to draw  the line from outside of the rect.  Because the start
4714        *     point of the line is not good style if we draw from inside it.
4715        *  2. First, draw horizontal line from outside the rect to top-left of
4716        *     the rect;
4717        *  3. Goes down to bottom of the area at 45 degrees.
4718        *  4. Slides to right horizontaly, see |flatLengthAtVertex|.
4719        *  5. Goes up to top of the area at 45 degrees.
4720        *  6. Slides to right horizontaly.
4721        *  7. Repeat from 2 until reached to right-most edge of the area.
4722        *
4723        * In the vertical case, swap horizontal and vertical coordinates and
4724        * directions in the above description.
4725        */
4726 
4727       Float& rectICoord = aParams.vertical ? rect.y : rect.x;
4728       Float& rectISize = aParams.vertical ? rect.height : rect.width;
4729       const Float rectBSize = aParams.vertical ? rect.width : rect.height;
4730 
4731       const Float adv = rectBSize - lineThickness;
4732       const Float flatLengthAtVertex =
4733         std::max((lineThickness - 1.0) * 2.0, 1.0);
4734 
4735       // Align the start of wavy lines to the nearest ancestor block.
4736       const Float cycleLength = 2 * (adv + flatLengthAtVertex);
4737       rect = ExpandPaintingRectForDecorationLine(aFrame, aParams.style, rect,
4738                                                  aParams.icoordInFrame,
4739                                                  cycleLength, aParams.vertical);
4740       // figure out if we can trim whole cycles from the left and right edges
4741       // of the line, to try and avoid creating an unnecessarily long and
4742       // complex path
4743       const Float dirtyRectICoord = aParams.vertical ? aParams.dirtyRect.y
4744                                                      : aParams.dirtyRect.x;
4745       int32_t skipCycles = floor((dirtyRectICoord - rectICoord) / cycleLength);
4746       if (skipCycles > 0) {
4747         rectICoord += skipCycles * cycleLength;
4748         rectISize -= skipCycles * cycleLength;
4749       }
4750 
4751       rectICoord += lineThickness / 2.0;
4752       Point pt(rect.TopLeft());
4753       Float& ptICoord = aParams.vertical ? pt.y : pt.x;
4754       Float& ptBCoord = aParams.vertical ? pt.x : pt.y;
4755       if (aParams.vertical) {
4756         ptBCoord += adv + lineThickness / 2.0;
4757       }
4758       Float iCoordLimit = ptICoord + rectISize + lineThickness;
4759 
4760       const Float dirtyRectIMost = aParams.vertical ?
4761         aParams.dirtyRect.YMost() : aParams.dirtyRect.XMost();
4762       skipCycles = floor((iCoordLimit - dirtyRectIMost) / cycleLength);
4763       if (skipCycles > 0) {
4764         iCoordLimit -= skipCycles * cycleLength;
4765       }
4766 
4767       RefPtr<PathBuilder> builder = aDrawTarget.CreatePathBuilder();
4768       RefPtr<Path> path;
4769 
4770       ptICoord -= lineThickness;
4771       builder->MoveTo(pt); // 1
4772 
4773       ptICoord = rectICoord;
4774       builder->LineTo(pt); // 2
4775 
4776       // In vertical mode, to go "down" relative to the text we need to
4777       // decrease the block coordinate, whereas in horizontal we increase
4778       // it. So the sense of this flag is effectively inverted.
4779       bool goDown = aParams.vertical ? false : true;
4780       uint32_t iter = 0;
4781       while (ptICoord < iCoordLimit) {
4782         if (++iter > 1000) {
4783           // stroke the current path and start again, to avoid pathological
4784           // behavior in cairo with huge numbers of path segments
4785           path = builder->Finish();
4786           aDrawTarget.Stroke(path, color, strokeOptions, drawOptions);
4787           builder = aDrawTarget.CreatePathBuilder();
4788           builder->MoveTo(pt);
4789           iter = 0;
4790         }
4791         ptICoord += adv;
4792         ptBCoord += goDown ? adv : -adv;
4793 
4794         builder->LineTo(pt); // 3 and 5
4795 
4796         ptICoord += flatLengthAtVertex;
4797         builder->LineTo(pt); // 4 and 6
4798 
4799         goDown = !goDown;
4800       }
4801       path = builder->Finish();
4802       aDrawTarget.Stroke(path, color, strokeOptions, drawOptions);
4803       return;
4804     }
4805     default:
4806       NS_ERROR("Invalid style value!");
4807   }
4808 }
4809 
4810 Rect
DecorationLineToPath(const PaintDecorationLineParams & aParams)4811 nsCSSRendering::DecorationLineToPath(const PaintDecorationLineParams& aParams)
4812 {
4813   NS_ASSERTION(aParams.style != NS_STYLE_TEXT_DECORATION_STYLE_NONE,
4814                "aStyle is none");
4815 
4816   Rect path; // To benefit from RVO, we return this from all return points
4817 
4818   Rect rect = ToRect(GetTextDecorationRectInternal(aParams.pt, aParams));
4819   if (rect.IsEmpty() || !rect.Intersects(aParams.dirtyRect)) {
4820     return path;
4821   }
4822 
4823   if (aParams.decoration != NS_STYLE_TEXT_DECORATION_LINE_UNDERLINE &&
4824       aParams.decoration != NS_STYLE_TEXT_DECORATION_LINE_OVERLINE &&
4825       aParams.decoration != NS_STYLE_TEXT_DECORATION_LINE_LINE_THROUGH) {
4826     NS_ERROR("Invalid decoration value!");
4827     return path;
4828   }
4829 
4830   if (aParams.style != NS_STYLE_TEXT_DECORATION_STYLE_SOLID) {
4831     // For the moment, we support only solid text decorations.
4832     return path;
4833   }
4834 
4835   Float lineThickness = std::max(NS_round(aParams.lineSize.height), 1.0);
4836 
4837   // The block-direction position should be set to the middle of the line.
4838   if (aParams.vertical) {
4839     rect.x += lineThickness / 2;
4840     path = Rect(rect.TopLeft() - Point(lineThickness / 2, 0.0),
4841                 Size(lineThickness, rect.Height()));
4842   } else {
4843     rect.y += lineThickness / 2;
4844     path = Rect(rect.TopLeft() - Point(0.0, lineThickness / 2),
4845                 Size(rect.Width(), lineThickness));
4846   }
4847 
4848   return path;
4849 }
4850 
4851 nsRect
GetTextDecorationRect(nsPresContext * aPresContext,const DecorationRectParams & aParams)4852 nsCSSRendering::GetTextDecorationRect(nsPresContext* aPresContext,
4853                                       const DecorationRectParams& aParams)
4854 {
4855   NS_ASSERTION(aPresContext, "aPresContext is null");
4856   NS_ASSERTION(aParams.style != NS_STYLE_TEXT_DECORATION_STYLE_NONE,
4857                "aStyle is none");
4858 
4859   gfxRect rect = GetTextDecorationRectInternal(Point(0, 0), aParams);
4860   // The rect values are already rounded to nearest device pixels.
4861   nsRect r;
4862   r.x = aPresContext->GfxUnitsToAppUnits(rect.X());
4863   r.y = aPresContext->GfxUnitsToAppUnits(rect.Y());
4864   r.width = aPresContext->GfxUnitsToAppUnits(rect.Width());
4865   r.height = aPresContext->GfxUnitsToAppUnits(rect.Height());
4866   return r;
4867 }
4868 
4869 gfxRect
GetTextDecorationRectInternal(const Point & aPt,const DecorationRectParams & aParams)4870 nsCSSRendering::GetTextDecorationRectInternal(const Point& aPt,
4871                                               const DecorationRectParams& aParams)
4872 {
4873   NS_ASSERTION(aParams.style <= NS_STYLE_TEXT_DECORATION_STYLE_WAVY,
4874                "Invalid aStyle value");
4875 
4876   if (aParams.style == NS_STYLE_TEXT_DECORATION_STYLE_NONE)
4877     return gfxRect(0, 0, 0, 0);
4878 
4879   bool canLiftUnderline = aParams.descentLimit >= 0.0;
4880 
4881   gfxFloat iCoord = aParams.vertical ? aPt.y : aPt.x;
4882   gfxFloat bCoord = aParams.vertical ? aPt.x : aPt.y;
4883 
4884   // 'left' and 'right' are relative to the line, so for vertical writing modes
4885   // they will actually become top and bottom of the rendered line.
4886   // Similarly, aLineSize.width and .height are actually length and thickness
4887   // of the line, which runs horizontally or vertically according to aVertical.
4888   const gfxFloat left  = floor(iCoord + 0.5),
4889                  right = floor(iCoord + aParams.lineSize.width + 0.5);
4890 
4891   // We compute |r| as if for a horizontal text run, and then swap vertical
4892   // and horizontal coordinates at the end if vertical was requested.
4893   gfxRect r(left, 0, right - left, 0);
4894 
4895   gfxFloat lineThickness = NS_round(aParams.lineSize.height);
4896   lineThickness = std::max(lineThickness, 1.0);
4897 
4898   gfxFloat ascent = NS_round(aParams.ascent);
4899   gfxFloat descentLimit = floor(aParams.descentLimit);
4900 
4901   gfxFloat suggestedMaxRectHeight = std::max(std::min(ascent, descentLimit), 1.0);
4902   r.height = lineThickness;
4903   if (aParams.style == NS_STYLE_TEXT_DECORATION_STYLE_DOUBLE) {
4904     /**
4905      *  We will draw double line as:
4906      *
4907      * +-------------------------------------------+
4908      * |XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX| ^
4909      * |XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX| | lineThickness
4910      * |XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX| v
4911      * |                                           | ^
4912      * |                                           | | gap
4913      * |                                           | v
4914      * |XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX| ^
4915      * |XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX| | lineThickness
4916      * |XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX| v
4917      * +-------------------------------------------+
4918      */
4919     gfxFloat gap = NS_round(lineThickness / 2.0);
4920     gap = std::max(gap, 1.0);
4921     r.height = lineThickness * 2.0 + gap;
4922     if (canLiftUnderline) {
4923       if (r.Height() > suggestedMaxRectHeight) {
4924         // Don't shrink the line height, because the thickness has some meaning.
4925         // We can just shrink the gap at this time.
4926         r.height = std::max(suggestedMaxRectHeight, lineThickness * 2.0 + 1.0);
4927       }
4928     }
4929   } else if (aParams.style == NS_STYLE_TEXT_DECORATION_STYLE_WAVY) {
4930     /**
4931      *  We will draw wavy line as:
4932      *
4933      * +-------------------------------------------+
4934      * |XXXXX            XXXXXX            XXXXXX  | ^
4935      * |XXXXXX          XXXXXXXX          XXXXXXXX | | lineThickness
4936      * |XXXXXXX        XXXXXXXXXX        XXXXXXXXXX| v
4937      * |     XXX      XXX      XXX      XXX      XX|
4938      * |      XXXXXXXXXX        XXXXXXXXXX        X|
4939      * |       XXXXXXXX          XXXXXXXX          |
4940      * |        XXXXXX            XXXXXX           |
4941      * +-------------------------------------------+
4942      */
4943     r.height = lineThickness > 2.0 ? lineThickness * 4.0 : lineThickness * 3.0;
4944     if (canLiftUnderline) {
4945       if (r.Height() > suggestedMaxRectHeight) {
4946         // Don't shrink the line height even if there is not enough space,
4947         // because the thickness has some meaning.  E.g., the 1px wavy line and
4948         // 2px wavy line can be used for different meaning in IME selections
4949         // at same time.
4950         r.height = std::max(suggestedMaxRectHeight, lineThickness * 2.0);
4951       }
4952     }
4953   }
4954 
4955   gfxFloat baseline = floor(bCoord + aParams.ascent + 0.5);
4956   gfxFloat offset = 0.0;
4957   switch (aParams.decoration) {
4958     case NS_STYLE_TEXT_DECORATION_LINE_UNDERLINE:
4959       offset = aParams.offset;
4960       if (canLiftUnderline) {
4961         if (descentLimit < -offset + r.Height()) {
4962           // If we can ignore the offset and the decoration line is overflowing,
4963           // we should align the bottom edge of the decoration line rect if it's
4964           // possible.  Otherwise, we should lift up the top edge of the rect as
4965           // far as possible.
4966           gfxFloat offsetBottomAligned = -descentLimit + r.Height();
4967           gfxFloat offsetTopAligned = 0.0;
4968           offset = std::min(offsetBottomAligned, offsetTopAligned);
4969         }
4970       }
4971       break;
4972     case NS_STYLE_TEXT_DECORATION_LINE_OVERLINE:
4973       offset = aParams.offset - lineThickness + r.Height();
4974       break;
4975     case NS_STYLE_TEXT_DECORATION_LINE_LINE_THROUGH: {
4976       gfxFloat extra = floor(r.Height() / 2.0 + 0.5);
4977       extra = std::max(extra, lineThickness);
4978       offset = aParams.offset - lineThickness + extra;
4979       break;
4980     }
4981     default:
4982       NS_ERROR("Invalid decoration value!");
4983   }
4984 
4985   if (aParams.vertical) {
4986     r.y = baseline + floor(offset + 0.5);
4987     Swap(r.x, r.y);
4988     Swap(r.width, r.height);
4989   } else {
4990     r.y = baseline - floor(offset + 0.5);
4991   }
4992 
4993   return r;
4994 }
4995 
4996 // ------------------
4997 // ImageRenderer
4998 // ------------------
nsImageRenderer(nsIFrame * aForFrame,const nsStyleImage * aImage,uint32_t aFlags)4999 nsImageRenderer::nsImageRenderer(nsIFrame* aForFrame,
5000                                  const nsStyleImage* aImage,
5001                                  uint32_t aFlags)
5002   : mForFrame(aForFrame)
5003   , mImage(aImage)
5004   , mType(aImage->GetType())
5005   , mImageContainer(nullptr)
5006   , mGradientData(nullptr)
5007   , mPaintServerFrame(nullptr)
5008   , mPrepareResult(DrawResult::NOT_READY)
5009   , mSize(0, 0)
5010   , mFlags(aFlags)
5011   , mExtendMode(ExtendMode::CLAMP)
5012   , mMaskOp(NS_STYLE_MASK_MODE_MATCH_SOURCE)
5013 {
5014 }
5015 
~nsImageRenderer()5016 nsImageRenderer::~nsImageRenderer()
5017 {
5018 }
5019 
5020 static bool
ShouldTreatAsCompleteDueToSyncDecode(const nsStyleImage * aImage,uint32_t aFlags)5021 ShouldTreatAsCompleteDueToSyncDecode(const nsStyleImage* aImage,
5022                                      uint32_t aFlags)
5023 {
5024   if (!(aFlags & nsImageRenderer::FLAG_SYNC_DECODE_IMAGES)) {
5025     return false;
5026   }
5027 
5028   if (aImage->GetType() != eStyleImageType_Image) {
5029     return false;
5030   }
5031 
5032   imgRequestProxy* req = aImage->GetImageData();
5033   if (!req) {
5034     return false;
5035   }
5036 
5037   uint32_t status = 0;
5038   if (NS_FAILED(req->GetImageStatus(&status))) {
5039     return false;
5040   }
5041 
5042   if (status & imgIRequest::STATUS_ERROR) {
5043     // The image is "complete" since it's a corrupt image. If we created an
5044     // imgIContainer at all, return true.
5045     nsCOMPtr<imgIContainer> image;
5046     req->GetImage(getter_AddRefs(image));
5047     return bool(image);
5048   }
5049 
5050   if (!(status & imgIRequest::STATUS_LOAD_COMPLETE)) {
5051     // We must have loaded all of the image's data and the size must be
5052     // available, or else sync decoding won't be able to decode the image.
5053     return false;
5054   }
5055 
5056   return true;
5057 }
5058 
5059 bool
PrepareImage()5060 nsImageRenderer::PrepareImage()
5061 {
5062   if (mImage->IsEmpty()) {
5063     mPrepareResult = DrawResult::BAD_IMAGE;
5064     return false;
5065   }
5066 
5067   if (!mImage->IsComplete()) {
5068     // Make sure the image is actually decoding.
5069     mImage->StartDecoding();
5070 
5071     // Check again to see if we finished.
5072     // We cannot prepare the image for rendering if it is not fully loaded.
5073     // Special case: If we requested a sync decode and the image has loaded, push
5074     // on through because the Draw() will do a sync decode then.
5075     if (!mImage->IsComplete() &&
5076         !ShouldTreatAsCompleteDueToSyncDecode(mImage, mFlags)) {
5077       mPrepareResult = DrawResult::NOT_READY;
5078       return false;
5079     }
5080   }
5081 
5082   switch (mType) {
5083     case eStyleImageType_Image: {
5084       MOZ_ASSERT(mImage->GetImageData(),
5085                  "must have image data, since we checked IsEmpty above");
5086       nsCOMPtr<imgIContainer> srcImage;
5087       DebugOnly<nsresult> rv =
5088         mImage->GetImageData()->GetImage(getter_AddRefs(srcImage));
5089       MOZ_ASSERT(NS_SUCCEEDED(rv) && srcImage,
5090                  "If GetImage() is failing, mImage->IsComplete() "
5091                  "should have returned false");
5092 
5093       if (!mImage->GetCropRect()) {
5094         mImageContainer.swap(srcImage);
5095       } else {
5096         nsIntRect actualCropRect;
5097         bool isEntireImage;
5098         bool success =
5099           mImage->ComputeActualCropRect(actualCropRect, &isEntireImage);
5100         NS_ASSERTION(success, "ComputeActualCropRect() should not fail here");
5101         if (!success || actualCropRect.IsEmpty()) {
5102           // The cropped image has zero size
5103           mPrepareResult = DrawResult::BAD_IMAGE;
5104           return false;
5105         }
5106         if (isEntireImage) {
5107           // The cropped image is identical to the source image
5108           mImageContainer.swap(srcImage);
5109         } else {
5110           nsCOMPtr<imgIContainer> subImage = ImageOps::Clip(srcImage,
5111                                                             actualCropRect,
5112                                                             Nothing());
5113           mImageContainer.swap(subImage);
5114         }
5115       }
5116       mPrepareResult = DrawResult::SUCCESS;
5117       break;
5118     }
5119     case eStyleImageType_Gradient:
5120       mGradientData = mImage->GetGradientData();
5121       mPrepareResult = DrawResult::SUCCESS;
5122       break;
5123     case eStyleImageType_Element:
5124     {
5125       nsAutoString elementId =
5126         NS_LITERAL_STRING("#") + nsDependentString(mImage->GetElementId());
5127       nsCOMPtr<nsIURI> targetURI;
5128       nsCOMPtr<nsIURI> base = mForFrame->GetContent()->GetBaseURI();
5129       nsContentUtils::NewURIWithDocumentCharset(getter_AddRefs(targetURI), elementId,
5130                                                 mForFrame->GetContent()->GetUncomposedDoc(), base);
5131       nsSVGPaintingProperty* property = nsSVGEffects::GetPaintingPropertyForURI(
5132           targetURI, mForFrame->FirstContinuation(),
5133           nsSVGEffects::BackgroundImageProperty());
5134       if (!property) {
5135         mPrepareResult = DrawResult::BAD_IMAGE;
5136         return false;
5137       }
5138 
5139       // If the referenced element is an <img>, <canvas>, or <video> element,
5140       // prefer SurfaceFromElement as it's more reliable.
5141       mImageElementSurface =
5142         nsLayoutUtils::SurfaceFromElement(property->GetReferencedElement());
5143       if (!mImageElementSurface.GetSourceSurface()) {
5144         nsIFrame* paintServerFrame = property->GetReferencedFrame();
5145         // If there's no referenced frame, or the referenced frame is
5146         // non-displayable SVG, then we have nothing valid to paint.
5147         if (!paintServerFrame ||
5148             (paintServerFrame->IsFrameOfType(nsIFrame::eSVG) &&
5149              !paintServerFrame->IsFrameOfType(nsIFrame::eSVGPaintServer) &&
5150              !static_cast<nsISVGChildFrame*>(do_QueryFrame(paintServerFrame)))) {
5151           mPrepareResult = DrawResult::BAD_IMAGE;
5152           return false;
5153         }
5154         mPaintServerFrame = paintServerFrame;
5155       }
5156 
5157       mPrepareResult = DrawResult::SUCCESS;
5158       break;
5159     }
5160     case eStyleImageType_Null:
5161     default:
5162       break;
5163   }
5164 
5165   return IsReady();
5166 }
5167 
5168 nsSize
ComputeConcreteSize() const5169 CSSSizeOrRatio::ComputeConcreteSize() const
5170 {
5171   NS_ASSERTION(CanComputeConcreteSize(), "Cannot compute");
5172   if (mHasWidth && mHasHeight) {
5173     return nsSize(mWidth, mHeight);
5174   }
5175   if (mHasWidth) {
5176     nscoord height = NSCoordSaturatingNonnegativeMultiply(
5177       mWidth,
5178       double(mRatio.height) / mRatio.width);
5179     return nsSize(mWidth, height);
5180   }
5181 
5182   MOZ_ASSERT(mHasHeight);
5183   nscoord width = NSCoordSaturatingNonnegativeMultiply(
5184     mHeight,
5185     double(mRatio.width) / mRatio.height);
5186   return nsSize(width, mHeight);
5187 }
5188 
5189 CSSSizeOrRatio
ComputeIntrinsicSize()5190 nsImageRenderer::ComputeIntrinsicSize()
5191 {
5192   NS_ASSERTION(IsReady(), "Ensure PrepareImage() has returned true "
5193                           "before calling me");
5194 
5195   CSSSizeOrRatio result;
5196   switch (mType) {
5197     case eStyleImageType_Image:
5198     {
5199       bool haveWidth, haveHeight;
5200       CSSIntSize imageIntSize;
5201       nsLayoutUtils::ComputeSizeForDrawing(mImageContainer, imageIntSize,
5202                                            result.mRatio, haveWidth, haveHeight);
5203       if (haveWidth) {
5204         result.SetWidth(nsPresContext::CSSPixelsToAppUnits(imageIntSize.width));
5205       }
5206       if (haveHeight) {
5207         result.SetHeight(nsPresContext::CSSPixelsToAppUnits(imageIntSize.height));
5208       }
5209       break;
5210     }
5211     case eStyleImageType_Element:
5212     {
5213       // XXX element() should have the width/height of the referenced element,
5214       //     and that element's ratio, if it matches.  If it doesn't match, it
5215       //     should have no width/height or ratio.  See element() in CSS images:
5216       //     <http://dev.w3.org/csswg/css-images-4/#element-notation>.
5217       //     Make sure to change nsStyleImageLayers::Size::DependsOnFrameSize
5218       //     when fixing this!
5219       if (mPaintServerFrame) {
5220         // SVG images have no intrinsic size
5221         if (!mPaintServerFrame->IsFrameOfType(nsIFrame::eSVG)) {
5222           // The intrinsic image size for a generic nsIFrame paint server is
5223           // the union of the border-box rects of all of its continuations,
5224           // rounded to device pixels.
5225           int32_t appUnitsPerDevPixel =
5226             mForFrame->PresContext()->AppUnitsPerDevPixel();
5227           result.SetSize(
5228             IntSizeToAppUnits(
5229               nsSVGIntegrationUtils::GetContinuationUnionSize(mPaintServerFrame).
5230                 ToNearestPixels(appUnitsPerDevPixel),
5231               appUnitsPerDevPixel));
5232         }
5233       } else {
5234         NS_ASSERTION(mImageElementSurface.GetSourceSurface(),
5235                      "Surface should be ready.");
5236         IntSize surfaceSize = mImageElementSurface.mSize;
5237         result.SetSize(
5238           nsSize(nsPresContext::CSSPixelsToAppUnits(surfaceSize.width),
5239                  nsPresContext::CSSPixelsToAppUnits(surfaceSize.height)));
5240       }
5241       break;
5242     }
5243     case eStyleImageType_Gradient:
5244       // Per <http://dev.w3.org/csswg/css3-images/#gradients>, gradients have no
5245       // intrinsic dimensions.
5246     case eStyleImageType_Null:
5247     default:
5248       break;
5249   }
5250 
5251   return result;
5252 }
5253 
5254 /* static */ nsSize
ComputeConcreteSize(const CSSSizeOrRatio & aSpecifiedSize,const CSSSizeOrRatio & aIntrinsicSize,const nsSize & aDefaultSize)5255 nsImageRenderer::ComputeConcreteSize(const CSSSizeOrRatio& aSpecifiedSize,
5256                                      const CSSSizeOrRatio& aIntrinsicSize,
5257                                      const nsSize& aDefaultSize)
5258 {
5259   // The specified size is fully specified, just use that
5260   if (aSpecifiedSize.IsConcrete()) {
5261     return aSpecifiedSize.ComputeConcreteSize();
5262   }
5263 
5264   MOZ_ASSERT(!aSpecifiedSize.mHasWidth || !aSpecifiedSize.mHasHeight);
5265 
5266   if (!aSpecifiedSize.mHasWidth && !aSpecifiedSize.mHasHeight) {
5267     // no specified size, try using the intrinsic size
5268     if (aIntrinsicSize.CanComputeConcreteSize()) {
5269       return aIntrinsicSize.ComputeConcreteSize();
5270     }
5271 
5272     if (aIntrinsicSize.mHasWidth) {
5273       return nsSize(aIntrinsicSize.mWidth, aDefaultSize.height);
5274     }
5275     if (aIntrinsicSize.mHasHeight) {
5276       return nsSize(aDefaultSize.width, aIntrinsicSize.mHeight);
5277     }
5278 
5279     // couldn't use the intrinsic size either, revert to using the default size
5280     return ComputeConstrainedSize(aDefaultSize,
5281                                   aIntrinsicSize.mRatio,
5282                                   CONTAIN);
5283   }
5284 
5285   MOZ_ASSERT(aSpecifiedSize.mHasWidth || aSpecifiedSize.mHasHeight);
5286 
5287   // The specified height is partial, try to compute the missing part.
5288   if (aSpecifiedSize.mHasWidth) {
5289     nscoord height;
5290     if (aIntrinsicSize.HasRatio()) {
5291       height = NSCoordSaturatingNonnegativeMultiply(
5292         aSpecifiedSize.mWidth,
5293         double(aIntrinsicSize.mRatio.height) / aIntrinsicSize.mRatio.width);
5294     } else if (aIntrinsicSize.mHasHeight) {
5295       height = aIntrinsicSize.mHeight;
5296     } else {
5297       height = aDefaultSize.height;
5298     }
5299     return nsSize(aSpecifiedSize.mWidth, height);
5300   }
5301 
5302   MOZ_ASSERT(aSpecifiedSize.mHasHeight);
5303   nscoord width;
5304   if (aIntrinsicSize.HasRatio()) {
5305     width = NSCoordSaturatingNonnegativeMultiply(
5306       aSpecifiedSize.mHeight,
5307       double(aIntrinsicSize.mRatio.width) / aIntrinsicSize.mRatio.height);
5308   } else if (aIntrinsicSize.mHasWidth) {
5309     width = aIntrinsicSize.mWidth;
5310   } else {
5311     width = aDefaultSize.width;
5312   }
5313   return nsSize(width, aSpecifiedSize.mHeight);
5314 }
5315 
5316 /* static */ nsSize
ComputeConstrainedSize(const nsSize & aConstrainingSize,const nsSize & aIntrinsicRatio,FitType aFitType)5317 nsImageRenderer::ComputeConstrainedSize(const nsSize& aConstrainingSize,
5318                                         const nsSize& aIntrinsicRatio,
5319                                         FitType aFitType)
5320 {
5321   if (aIntrinsicRatio.width <= 0 && aIntrinsicRatio.height <= 0) {
5322     return aConstrainingSize;
5323   }
5324 
5325   float scaleX = double(aConstrainingSize.width) / aIntrinsicRatio.width;
5326   float scaleY = double(aConstrainingSize.height) / aIntrinsicRatio.height;
5327   nsSize size;
5328   if ((aFitType == CONTAIN) == (scaleX < scaleY)) {
5329     size.width = aConstrainingSize.width;
5330     size.height = NSCoordSaturatingNonnegativeMultiply(
5331                     aIntrinsicRatio.height, scaleX);
5332     // If we're reducing the size by less than one css pixel, then just use the
5333     // constraining size.
5334     if (aFitType == CONTAIN && aConstrainingSize.height - size.height < nsPresContext::AppUnitsPerCSSPixel()) {
5335       size.height = aConstrainingSize.height;
5336     }
5337   } else {
5338     size.width = NSCoordSaturatingNonnegativeMultiply(
5339                    aIntrinsicRatio.width, scaleY);
5340     if (aFitType == CONTAIN && aConstrainingSize.width - size.width < nsPresContext::AppUnitsPerCSSPixel()) {
5341       size.width = aConstrainingSize.width;
5342     }
5343     size.height = aConstrainingSize.height;
5344   }
5345   return size;
5346 }
5347 
5348 /**
5349  * mSize is the image's "preferred" size for this particular rendering, while
5350  * the drawn (aka concrete) size is the actual rendered size after accounting
5351  * for background-size etc..  The preferred size is most often the image's
5352  * intrinsic dimensions.  But for images with incomplete intrinsic dimensions,
5353  * the preferred size varies, depending on the specified and default sizes, see
5354  * nsImageRenderer::Compute*Size.
5355  *
5356  * This distinction is necessary because the components of a vector image are
5357  * specified with respect to its preferred size for a rendering situation, not
5358  * to its actual rendered size.  For example, consider a 4px wide background
5359  * vector image with no height which contains a left-aligned
5360  * 2px wide black rectangle with height 100%.  If the background-size width is
5361  * auto (or 4px), the vector image will render 4px wide, and the black rectangle
5362  * will be 2px wide.  If the background-size width is 8px, the vector image will
5363  * render 8px wide, and the black rectangle will be 4px wide -- *not* 2px wide.
5364  * In both cases mSize.width will be 4px; but in the first case the returned
5365  * width will be 4px, while in the second case the returned width will be 8px.
5366  */
5367 void
SetPreferredSize(const CSSSizeOrRatio & aIntrinsicSize,const nsSize & aDefaultSize)5368 nsImageRenderer::SetPreferredSize(const CSSSizeOrRatio& aIntrinsicSize,
5369                                   const nsSize& aDefaultSize)
5370 {
5371   mSize.width = aIntrinsicSize.mHasWidth
5372                   ? aIntrinsicSize.mWidth
5373                   : aDefaultSize.width;
5374   mSize.height = aIntrinsicSize.mHasHeight
5375                   ? aIntrinsicSize.mHeight
5376                   : aDefaultSize.height;
5377 }
5378 
5379 // Convert from nsImageRenderer flags to the flags we want to use for drawing in
5380 // the imgIContainer namespace.
5381 static uint32_t
ConvertImageRendererToDrawFlags(uint32_t aImageRendererFlags)5382 ConvertImageRendererToDrawFlags(uint32_t aImageRendererFlags)
5383 {
5384   uint32_t drawFlags = imgIContainer::FLAG_NONE;
5385   if (aImageRendererFlags & nsImageRenderer::FLAG_SYNC_DECODE_IMAGES) {
5386     drawFlags |= imgIContainer::FLAG_SYNC_DECODE;
5387   }
5388   if (aImageRendererFlags & nsImageRenderer::FLAG_PAINTING_TO_WINDOW) {
5389     drawFlags |= imgIContainer::FLAG_HIGH_QUALITY_SCALING;
5390   }
5391   return drawFlags;
5392 }
5393 
5394 /*
5395  *  SVG11: A luminanceToAlpha operation is equivalent to the following matrix operation:                                                   |
5396  *  | R' |     |      0        0        0  0  0 |   | R |
5397  *  | G' |     |      0        0        0  0  0 |   | G |
5398  *  | B' |  =  |      0        0        0  0  0 | * | B |
5399  *  | A' |     | 0.2125   0.7154   0.0721  0  0 |   | A |
5400  *  | 1  |     |      0        0        0  0  1 |   | 1 |
5401  */
5402 static void
RGBALuminanceOperation(uint8_t * aData,int32_t aStride,const IntSize & aSize)5403 RGBALuminanceOperation(uint8_t *aData,
5404                        int32_t aStride,
5405                        const IntSize &aSize)
5406 {
5407   int32_t redFactor = 55;    // 256 * 0.2125
5408   int32_t greenFactor = 183; // 256 * 0.7154
5409   int32_t blueFactor = 18;   // 256 * 0.0721
5410 
5411   for (int32_t y = 0; y < aSize.height; y++) {
5412     uint32_t *pixel = (uint32_t*)(aData + aStride * y);
5413     for (int32_t x = 0; x < aSize.width; x++) {
5414       *pixel = (((((*pixel & 0x00FF0000) >> 16) * redFactor) +
5415                  (((*pixel & 0x0000FF00) >>  8) * greenFactor) +
5416                   ((*pixel & 0x000000FF)        * blueFactor)) >> 8) << 24;
5417       pixel++;
5418     }
5419   }
5420 }
5421 
5422 
5423 DrawResult
Draw(nsPresContext * aPresContext,nsRenderingContext & aRenderingContext,const nsRect & aDirtyRect,const nsRect & aDest,const nsRect & aFill,const nsPoint & aAnchor,const nsSize & aRepeatSize,const CSSIntRect & aSrc)5424 nsImageRenderer::Draw(nsPresContext*       aPresContext,
5425                       nsRenderingContext&  aRenderingContext,
5426                       const nsRect&        aDirtyRect,
5427                       const nsRect&        aDest,
5428                       const nsRect&        aFill,
5429                       const nsPoint&       aAnchor,
5430                       const nsSize&        aRepeatSize,
5431                       const CSSIntRect&    aSrc)
5432 {
5433   if (!IsReady()) {
5434     NS_NOTREACHED("Ensure PrepareImage() has returned true before calling me");
5435     return DrawResult::TEMPORARY_ERROR;
5436   }
5437   if (aDest.IsEmpty() || aFill.IsEmpty() ||
5438       mSize.width <= 0 || mSize.height <= 0) {
5439     return DrawResult::SUCCESS;
5440   }
5441 
5442   SamplingFilter samplingFilter = nsLayoutUtils::GetSamplingFilterForFrame(mForFrame);
5443   DrawResult result = DrawResult::SUCCESS;
5444   RefPtr<gfxContext> ctx = aRenderingContext.ThebesContext();
5445   IntRect tmpDTRect;
5446 
5447   if (ctx->CurrentOp() != CompositionOp::OP_OVER || mMaskOp == NS_STYLE_MASK_MODE_LUMINANCE) {
5448     gfxRect clipRect = ctx->GetClipExtents();
5449     tmpDTRect = RoundedOut(ToRect(clipRect));
5450     if (tmpDTRect.IsEmpty()) {
5451       return DrawResult::SUCCESS;
5452     }
5453     RefPtr<DrawTarget> tempDT =
5454       gfxPlatform::GetPlatform()->CreateSimilarSoftwareDrawTarget(ctx->GetDrawTarget(),
5455                                                                   tmpDTRect.Size(),
5456                                                                   SurfaceFormat::B8G8R8A8);
5457     if (!tempDT || !tempDT->IsValid()) {
5458       gfxDevCrash(LogReason::InvalidContext) << "ImageRenderer::Draw problem " << gfx::hexa(tempDT);
5459       return DrawResult::TEMPORARY_ERROR;
5460     }
5461     tempDT->SetTransform(Matrix::Translation(-tmpDTRect.TopLeft()));
5462     ctx = gfxContext::CreatePreservingTransformOrNull(tempDT);
5463     if (!ctx) {
5464       gfxDevCrash(LogReason::InvalidContext) << "ImageRenderer::Draw problem " << gfx::hexa(tempDT);
5465       return DrawResult::TEMPORARY_ERROR;
5466     }
5467   }
5468 
5469   switch (mType) {
5470     case eStyleImageType_Image:
5471     {
5472       CSSIntSize imageSize(nsPresContext::AppUnitsToIntCSSPixels(mSize.width),
5473                            nsPresContext::AppUnitsToIntCSSPixels(mSize.height));
5474       result =
5475         nsLayoutUtils::DrawBackgroundImage(*ctx,
5476                                            aPresContext,
5477                                            mImageContainer, imageSize,
5478                                            samplingFilter,
5479                                            aDest, aFill, aRepeatSize,
5480                                            aAnchor, aDirtyRect,
5481                                            ConvertImageRendererToDrawFlags(mFlags),
5482                                            mExtendMode);
5483       break;
5484     }
5485     case eStyleImageType_Gradient:
5486     {
5487       nsCSSRendering::PaintGradient(aPresContext, aRenderingContext,
5488                                     mGradientData, aDirtyRect,
5489                                     aDest, aFill, aRepeatSize, aSrc, mSize);
5490       break;
5491     }
5492     case eStyleImageType_Element:
5493     {
5494       RefPtr<gfxDrawable> drawable = DrawableForElement(aDest,
5495                                                           aRenderingContext);
5496       if (!drawable) {
5497         NS_WARNING("Could not create drawable for element");
5498         return DrawResult::TEMPORARY_ERROR;
5499       }
5500 
5501       nsCOMPtr<imgIContainer> image(ImageOps::CreateFromDrawable(drawable));
5502       result =
5503         nsLayoutUtils::DrawImage(*ctx,
5504                                  aPresContext, image,
5505                                  samplingFilter, aDest, aFill, aAnchor, aDirtyRect,
5506                                  ConvertImageRendererToDrawFlags(mFlags));
5507       break;
5508     }
5509     case eStyleImageType_Null:
5510     default:
5511       break;
5512   }
5513 
5514   if (!tmpDTRect.IsEmpty()) {
5515     RefPtr<SourceSurface> surf = ctx->GetDrawTarget()->Snapshot();
5516     if (mMaskOp == NS_STYLE_MASK_MODE_LUMINANCE) {
5517       RefPtr<DataSourceSurface> maskData = surf->GetDataSurface();
5518       DataSourceSurface::MappedSurface map;
5519       if (!maskData->Map(DataSourceSurface::MapType::WRITE, &map)) {
5520         return result;
5521       }
5522 
5523       RGBALuminanceOperation(map.mData, map.mStride, maskData->GetSize());
5524       maskData->Unmap();
5525       surf = maskData;
5526     }
5527 
5528     DrawTarget* dt = aRenderingContext.ThebesContext()->GetDrawTarget();
5529     dt->DrawSurface(surf, Rect(tmpDTRect.x, tmpDTRect.y, tmpDTRect.width, tmpDTRect.height),
5530                     Rect(0, 0, tmpDTRect.width, tmpDTRect.height),
5531                     DrawSurfaceOptions(SamplingFilter::POINT),
5532                     DrawOptions(1.0f, aRenderingContext.ThebesContext()->CurrentOp()));
5533   }
5534 
5535   return result;
5536 }
5537 
5538 already_AddRefed<gfxDrawable>
DrawableForElement(const nsRect & aImageRect,nsRenderingContext & aRenderingContext)5539 nsImageRenderer::DrawableForElement(const nsRect& aImageRect,
5540                                     nsRenderingContext&  aRenderingContext)
5541 {
5542   NS_ASSERTION(mType == eStyleImageType_Element,
5543                "DrawableForElement only makes sense if backed by an element");
5544   if (mPaintServerFrame) {
5545     // XXX(seth): In order to not pass FLAG_SYNC_DECODE_IMAGES here,
5546     // DrawableFromPaintServer would have to return a DrawResult indicating
5547     // whether any images could not be painted because they weren't fully
5548     // decoded. Even always passing FLAG_SYNC_DECODE_IMAGES won't eliminate all
5549     // problems, as it won't help if there are image which haven't finished
5550     // loading, but it's better than nothing.
5551     int32_t appUnitsPerDevPixel = mForFrame->PresContext()->AppUnitsPerDevPixel();
5552     nsRect destRect = aImageRect - aImageRect.TopLeft();
5553     nsIntSize roundedOut = destRect.ToOutsidePixels(appUnitsPerDevPixel).Size();
5554     IntSize imageSize(roundedOut.width, roundedOut.height);
5555     RefPtr<gfxDrawable> drawable =
5556       nsSVGIntegrationUtils::DrawableFromPaintServer(
5557         mPaintServerFrame, mForFrame, mSize, imageSize,
5558         aRenderingContext.GetDrawTarget(),
5559         aRenderingContext.ThebesContext()->CurrentMatrix(),
5560         nsSVGIntegrationUtils::FLAG_SYNC_DECODE_IMAGES);
5561 
5562     return drawable.forget();
5563   }
5564   NS_ASSERTION(mImageElementSurface.GetSourceSurface(), "Surface should be ready.");
5565   RefPtr<gfxDrawable> drawable = new gfxSurfaceDrawable(
5566                                 mImageElementSurface.GetSourceSurface().get(),
5567                                 mImageElementSurface.mSize);
5568   return drawable.forget();
5569 }
5570 
5571 DrawResult
DrawBackground(nsPresContext * aPresContext,nsRenderingContext & aRenderingContext,const nsRect & aDest,const nsRect & aFill,const nsPoint & aAnchor,const nsRect & aDirty,const nsSize & aRepeatSize)5572 nsImageRenderer::DrawBackground(nsPresContext*       aPresContext,
5573                                 nsRenderingContext&  aRenderingContext,
5574                                 const nsRect&        aDest,
5575                                 const nsRect&        aFill,
5576                                 const nsPoint&       aAnchor,
5577                                 const nsRect&        aDirty,
5578                                 const nsSize&        aRepeatSize)
5579 {
5580   if (!IsReady()) {
5581     NS_NOTREACHED("Ensure PrepareImage() has returned true before calling me");
5582     return DrawResult::TEMPORARY_ERROR;
5583   }
5584   if (aDest.IsEmpty() || aFill.IsEmpty() ||
5585       mSize.width <= 0 || mSize.height <= 0) {
5586     return DrawResult::SUCCESS;
5587   }
5588 
5589   return Draw(aPresContext, aRenderingContext,
5590               aDirty, aDest, aFill, aAnchor, aRepeatSize,
5591               CSSIntRect(0, 0,
5592                          nsPresContext::AppUnitsToIntCSSPixels(mSize.width),
5593                          nsPresContext::AppUnitsToIntCSSPixels(mSize.height)));
5594 }
5595 
5596 /**
5597  * Compute the size and position of the master copy of the image. I.e., a single
5598  * tile used to fill the dest rect.
5599  * aFill The destination rect to be filled
5600  * aHFill and aVFill are the repeat patterns for the component -
5601  * NS_STYLE_BORDER_IMAGE_REPEAT_* - i.e., how a tiling unit is used to fill aFill
5602  * aUnitSize The size of the source rect in dest coords.
5603  */
5604 static nsRect
ComputeTile(nsRect & aFill,uint8_t aHFill,uint8_t aVFill,const nsSize & aUnitSize,nsSize & aRepeatSize)5605 ComputeTile(nsRect&              aFill,
5606             uint8_t              aHFill,
5607             uint8_t              aVFill,
5608             const nsSize&        aUnitSize,
5609             nsSize&              aRepeatSize)
5610 {
5611   nsRect tile;
5612   switch (aHFill) {
5613   case NS_STYLE_BORDER_IMAGE_REPEAT_STRETCH:
5614     tile.x = aFill.x;
5615     tile.width = aFill.width;
5616     aRepeatSize.width = tile.width;
5617     break;
5618   case NS_STYLE_BORDER_IMAGE_REPEAT_REPEAT:
5619     tile.x = aFill.x + aFill.width/2 - aUnitSize.width/2;
5620     tile.width = aUnitSize.width;
5621     aRepeatSize.width = tile.width;
5622     break;
5623   case NS_STYLE_BORDER_IMAGE_REPEAT_ROUND:
5624     tile.x = aFill.x;
5625     tile.width = ComputeRoundedSize(aUnitSize.width, aFill.width);
5626     aRepeatSize.width = tile.width;
5627     break;
5628   case NS_STYLE_BORDER_IMAGE_REPEAT_SPACE:
5629     {
5630       nscoord space;
5631       aRepeatSize.width =
5632         ComputeBorderSpacedRepeatSize(aUnitSize.width, aFill.width, space);
5633       tile.x = aFill.x + space;
5634       tile.width = aUnitSize.width;
5635       aFill.x = tile.x;
5636       aFill.width = aFill.width - space * 2;
5637     }
5638     break;
5639   default:
5640     NS_NOTREACHED("unrecognized border-image fill style");
5641   }
5642 
5643   switch (aVFill) {
5644   case NS_STYLE_BORDER_IMAGE_REPEAT_STRETCH:
5645     tile.y = aFill.y;
5646     tile.height = aFill.height;
5647     aRepeatSize.height = tile.height;
5648     break;
5649   case NS_STYLE_BORDER_IMAGE_REPEAT_REPEAT:
5650     tile.y = aFill.y + aFill.height/2 - aUnitSize.height/2;
5651     tile.height = aUnitSize.height;
5652     aRepeatSize.height = tile.height;
5653     break;
5654   case NS_STYLE_BORDER_IMAGE_REPEAT_ROUND:
5655     tile.y = aFill.y;
5656     tile.height = ComputeRoundedSize(aUnitSize.height, aFill.height);
5657     aRepeatSize.height = tile.height;
5658     break;
5659   case NS_STYLE_BORDER_IMAGE_REPEAT_SPACE:
5660     {
5661       nscoord space;
5662       aRepeatSize.height =
5663         ComputeBorderSpacedRepeatSize(aUnitSize.height, aFill.height, space);
5664       tile.y = aFill.y + space;
5665       tile.height = aUnitSize.height;
5666       aFill.y = tile.y;
5667       aFill.height = aFill.height - space * 2;
5668     }
5669     break;
5670   default:
5671     NS_NOTREACHED("unrecognized border-image fill style");
5672   }
5673 
5674   return tile;
5675 }
5676 
5677 /**
5678  * Returns true if the given set of arguments will require the tiles which fill
5679  * the dest rect to be scaled from the source tile. See comment on ComputeTile
5680  * for argument descriptions.
5681  */
5682 static bool
RequiresScaling(const nsRect & aFill,uint8_t aHFill,uint8_t aVFill,const nsSize & aUnitSize)5683 RequiresScaling(const nsRect&        aFill,
5684                 uint8_t              aHFill,
5685                 uint8_t              aVFill,
5686                 const nsSize&        aUnitSize)
5687 {
5688   // If we have no tiling in either direction, we can skip the intermediate
5689   // scaling step.
5690   return (aHFill != NS_STYLE_BORDER_IMAGE_REPEAT_STRETCH ||
5691           aVFill != NS_STYLE_BORDER_IMAGE_REPEAT_STRETCH) &&
5692          (aUnitSize.width != aFill.width ||
5693           aUnitSize.height != aFill.height);
5694 }
5695 
5696 DrawResult
DrawBorderImageComponent(nsPresContext * aPresContext,nsRenderingContext & aRenderingContext,const nsRect & aDirtyRect,const nsRect & aFill,const CSSIntRect & aSrc,uint8_t aHFill,uint8_t aVFill,const nsSize & aUnitSize,uint8_t aIndex,const Maybe<nsSize> & aSVGViewportSize,const bool aHasIntrinsicRatio)5697 nsImageRenderer::DrawBorderImageComponent(nsPresContext*       aPresContext,
5698                                           nsRenderingContext&  aRenderingContext,
5699                                           const nsRect&        aDirtyRect,
5700                                           const nsRect&        aFill,
5701                                           const CSSIntRect&    aSrc,
5702                                           uint8_t              aHFill,
5703                                           uint8_t              aVFill,
5704                                           const nsSize&        aUnitSize,
5705                                           uint8_t              aIndex,
5706                                           const Maybe<nsSize>& aSVGViewportSize,
5707                                           const bool           aHasIntrinsicRatio)
5708 {
5709   if (!IsReady()) {
5710     NS_NOTREACHED("Ensure PrepareImage() has returned true before calling me");
5711     return DrawResult::BAD_ARGS;
5712   }
5713   if (aFill.IsEmpty() || aSrc.IsEmpty()) {
5714     return DrawResult::SUCCESS;
5715   }
5716 
5717   if (mType == eStyleImageType_Image || mType == eStyleImageType_Element) {
5718     nsCOMPtr<imgIContainer> subImage;
5719 
5720     // To draw one portion of an image into a border component, we stretch that
5721     // portion to match the size of that border component and then draw onto.
5722     // However, preserveAspectRatio attribute of a SVG image may break this rule.
5723     // To get correct rendering result, we add
5724     // FLAG_FORCE_PRESERVEASPECTRATIO_NONE flag here, to tell mImage to ignore
5725     // preserveAspectRatio attribute, and always do non-uniform stretch.
5726     uint32_t drawFlags = ConvertImageRendererToDrawFlags(mFlags) |
5727                            imgIContainer::FLAG_FORCE_PRESERVEASPECTRATIO_NONE;
5728     // For those SVG image sources which don't have fixed aspect ratio (i.e.
5729     // without viewport size and viewBox), we should scale the source uniformly
5730     // after the viewport size is decided by "Default Sizing Algorithm".
5731     if (!aHasIntrinsicRatio) {
5732       drawFlags = drawFlags | imgIContainer::FLAG_FORCE_UNIFORM_SCALING;
5733     }
5734     // Retrieve or create the subimage we'll draw.
5735     nsIntRect srcRect(aSrc.x, aSrc.y, aSrc.width, aSrc.height);
5736     if (mType == eStyleImageType_Image) {
5737       if ((subImage = mImage->GetSubImage(aIndex)) == nullptr) {
5738         subImage = ImageOps::Clip(mImageContainer, srcRect, aSVGViewportSize);
5739         mImage->SetSubImage(aIndex, subImage);
5740       }
5741     } else {
5742       // This path, for eStyleImageType_Element, is currently slower than it
5743       // needs to be because we don't cache anything. (In particular, if we have
5744       // to draw to a temporary surface inside ClippedImage, we don't cache that
5745       // temporary surface since we immediately throw the ClippedImage we create
5746       // here away.) However, if we did cache, we'd need to know when to
5747       // invalidate that cache, and it's not clear that it's worth the trouble
5748       // since using border-image with -moz-element is rare.
5749 
5750       RefPtr<gfxDrawable> drawable = DrawableForElement(nsRect(nsPoint(), mSize),
5751                                                           aRenderingContext);
5752       if (!drawable) {
5753         NS_WARNING("Could not create drawable for element");
5754         return DrawResult::TEMPORARY_ERROR;
5755       }
5756 
5757       nsCOMPtr<imgIContainer> image(ImageOps::CreateFromDrawable(drawable));
5758       subImage = ImageOps::Clip(image, srcRect, aSVGViewportSize);
5759     }
5760 
5761     MOZ_ASSERT_IF(aSVGViewportSize,
5762                   subImage->GetType() == imgIContainer::TYPE_VECTOR);
5763 
5764     SamplingFilter samplingFilter = nsLayoutUtils::GetSamplingFilterForFrame(mForFrame);
5765 
5766     if (!RequiresScaling(aFill, aHFill, aVFill, aUnitSize)) {
5767       return nsLayoutUtils::DrawSingleImage(*aRenderingContext.ThebesContext(),
5768                                             aPresContext,
5769                                             subImage,
5770                                             samplingFilter,
5771                                             aFill, aDirtyRect,
5772                                             nullptr,
5773                                             drawFlags);
5774     }
5775 
5776     nsSize repeatSize;
5777     nsRect fillRect(aFill);
5778     nsRect tile = ComputeTile(fillRect, aHFill, aVFill, aUnitSize, repeatSize);
5779     CSSIntSize imageSize(srcRect.width, srcRect.height);
5780     return nsLayoutUtils::DrawBackgroundImage(*aRenderingContext.ThebesContext(),
5781                                               aPresContext,
5782                                               subImage, imageSize, samplingFilter,
5783                                               tile, fillRect, repeatSize,
5784                                               tile.TopLeft(), aDirtyRect,
5785                                               drawFlags,
5786                                               ExtendMode::CLAMP);
5787   }
5788 
5789   nsSize repeatSize(aFill.Size());
5790   nsRect fillRect(aFill);
5791   nsRect destTile = RequiresScaling(fillRect, aHFill, aVFill, aUnitSize)
5792                   ? ComputeTile(fillRect, aHFill, aVFill, aUnitSize, repeatSize)
5793                   : fillRect;
5794   return Draw(aPresContext, aRenderingContext, aDirtyRect, destTile,
5795               fillRect, destTile.TopLeft(), repeatSize, aSrc);
5796 }
5797 
5798 bool
IsRasterImage()5799 nsImageRenderer::IsRasterImage()
5800 {
5801   if (mType != eStyleImageType_Image || !mImageContainer)
5802     return false;
5803   return mImageContainer->GetType() == imgIContainer::TYPE_RASTER;
5804 }
5805 
5806 bool
IsAnimatedImage()5807 nsImageRenderer::IsAnimatedImage()
5808 {
5809   if (mType != eStyleImageType_Image || !mImageContainer)
5810     return false;
5811   bool animated = false;
5812   if (NS_SUCCEEDED(mImageContainer->GetAnimated(&animated)) && animated)
5813     return true;
5814 
5815   return false;
5816 }
5817 
5818 already_AddRefed<imgIContainer>
GetImage()5819 nsImageRenderer::GetImage()
5820 {
5821   if (mType != eStyleImageType_Image || !mImageContainer) {
5822     return nullptr;
5823   }
5824 
5825   nsCOMPtr<imgIContainer> image = mImageContainer;
5826   return image.forget();
5827 }
5828 
5829 void
PurgeCacheForViewportChange(const Maybe<nsSize> & aSVGViewportSize,const bool aHasIntrinsicRatio)5830 nsImageRenderer::PurgeCacheForViewportChange(
5831   const Maybe<nsSize>& aSVGViewportSize, const bool aHasIntrinsicRatio)
5832 {
5833   // Check if we should flush the cached data - only vector images need to do
5834   // the check since they might not have fixed ratio.
5835   if (mImageContainer &&
5836       mImageContainer->GetType() == imgIContainer::TYPE_VECTOR) {
5837     mImage->PurgeCacheForViewportChange(aSVGViewportSize, aHasIntrinsicRatio);
5838   }
5839 }
5840 
5841 #define MAX_BLUR_RADIUS 300
5842 #define MAX_SPREAD_RADIUS 50
5843 
ComputeBlurStdDev(nscoord aBlurRadius,int32_t aAppUnitsPerDevPixel,gfxFloat aScaleX,gfxFloat aScaleY)5844 static inline gfxPoint ComputeBlurStdDev(nscoord aBlurRadius,
5845                                          int32_t aAppUnitsPerDevPixel,
5846                                          gfxFloat aScaleX,
5847                                          gfxFloat aScaleY)
5848 {
5849   // http://dev.w3.org/csswg/css3-background/#box-shadow says that the
5850   // standard deviation of the blur should be half the given blur value.
5851   gfxFloat blurStdDev = gfxFloat(aBlurRadius) / gfxFloat(aAppUnitsPerDevPixel);
5852 
5853   return gfxPoint(std::min((blurStdDev * aScaleX),
5854                            gfxFloat(MAX_BLUR_RADIUS)) / 2.0,
5855                   std::min((blurStdDev * aScaleY),
5856                            gfxFloat(MAX_BLUR_RADIUS)) / 2.0);
5857 }
5858 
5859 static inline IntSize
ComputeBlurRadius(nscoord aBlurRadius,int32_t aAppUnitsPerDevPixel,gfxFloat aScaleX=1.0,gfxFloat aScaleY=1.0)5860 ComputeBlurRadius(nscoord aBlurRadius,
5861                   int32_t aAppUnitsPerDevPixel,
5862                   gfxFloat aScaleX = 1.0,
5863                   gfxFloat aScaleY = 1.0)
5864 {
5865   gfxPoint scaledBlurStdDev = ComputeBlurStdDev(aBlurRadius, aAppUnitsPerDevPixel,
5866                                                 aScaleX, aScaleY);
5867   return
5868     gfxAlphaBoxBlur::CalculateBlurRadius(scaledBlurStdDev);
5869 }
5870 
5871 // -----
5872 // nsContextBoxBlur
5873 // -----
5874 gfxContext*
Init(const nsRect & aRect,nscoord aSpreadRadius,nscoord aBlurRadius,int32_t aAppUnitsPerDevPixel,gfxContext * aDestinationCtx,const nsRect & aDirtyRect,const gfxRect * aSkipRect,uint32_t aFlags)5875 nsContextBoxBlur::Init(const nsRect& aRect, nscoord aSpreadRadius,
5876                        nscoord aBlurRadius,
5877                        int32_t aAppUnitsPerDevPixel,
5878                        gfxContext* aDestinationCtx,
5879                        const nsRect& aDirtyRect,
5880                        const gfxRect* aSkipRect,
5881                        uint32_t aFlags)
5882 {
5883   if (aRect.IsEmpty()) {
5884     mContext = nullptr;
5885     return nullptr;
5886   }
5887 
5888   IntSize blurRadius;
5889   IntSize spreadRadius;
5890   GetBlurAndSpreadRadius(aDestinationCtx->GetDrawTarget(), aAppUnitsPerDevPixel,
5891                          aBlurRadius, aSpreadRadius,
5892                          blurRadius, spreadRadius);
5893 
5894   mDestinationCtx = aDestinationCtx;
5895 
5896   // If not blurring, draw directly onto the destination device
5897   if (blurRadius.width <= 0 && blurRadius.height <= 0 &&
5898       spreadRadius.width <= 0 && spreadRadius.height <= 0 &&
5899       !(aFlags & FORCE_MASK)) {
5900     mContext = aDestinationCtx;
5901     return mContext;
5902   }
5903 
5904   // Convert from app units to device pixels
5905   gfxRect rect = nsLayoutUtils::RectToGfxRect(aRect, aAppUnitsPerDevPixel);
5906 
5907   gfxRect dirtyRect =
5908     nsLayoutUtils::RectToGfxRect(aDirtyRect, aAppUnitsPerDevPixel);
5909   dirtyRect.RoundOut();
5910 
5911   gfxMatrix transform = aDestinationCtx->CurrentMatrix();
5912   rect = transform.TransformBounds(rect);
5913 
5914   mPreTransformed = !transform.IsIdentity();
5915 
5916   // Create the temporary surface for blurring
5917   dirtyRect = transform.TransformBounds(dirtyRect);
5918   if (aSkipRect) {
5919     gfxRect skipRect = transform.TransformBounds(*aSkipRect);
5920     mContext = mAlphaBoxBlur.Init(rect, spreadRadius,
5921                                   blurRadius, &dirtyRect, &skipRect);
5922   } else {
5923     mContext = mAlphaBoxBlur.Init(rect, spreadRadius,
5924                                   blurRadius, &dirtyRect, nullptr);
5925   }
5926 
5927   if (mContext) {
5928     // we don't need to blur if skipRect is equal to rect
5929     // and mContext will be nullptr
5930     mContext->Multiply(transform);
5931   }
5932   return mContext;
5933 }
5934 
5935 void
DoPaint()5936 nsContextBoxBlur::DoPaint()
5937 {
5938   if (mContext == mDestinationCtx) {
5939     return;
5940   }
5941 
5942   gfxContextMatrixAutoSaveRestore saveMatrix(mDestinationCtx);
5943 
5944   if (mPreTransformed) {
5945     mDestinationCtx->SetMatrix(gfxMatrix());
5946   }
5947 
5948   mAlphaBoxBlur.Paint(mDestinationCtx);
5949 }
5950 
5951 gfxContext*
GetContext()5952 nsContextBoxBlur::GetContext()
5953 {
5954   return mContext;
5955 }
5956 
5957 /* static */ nsMargin
GetBlurRadiusMargin(nscoord aBlurRadius,int32_t aAppUnitsPerDevPixel)5958 nsContextBoxBlur::GetBlurRadiusMargin(nscoord aBlurRadius,
5959                                       int32_t aAppUnitsPerDevPixel)
5960 {
5961   IntSize blurRadius = ComputeBlurRadius(aBlurRadius, aAppUnitsPerDevPixel);
5962 
5963   nsMargin result;
5964   result.top = result.bottom = blurRadius.height * aAppUnitsPerDevPixel;
5965   result.left = result.right = blurRadius.width  * aAppUnitsPerDevPixel;
5966   return result;
5967 }
5968 
5969 /* static */ void
BlurRectangle(gfxContext * aDestinationCtx,const nsRect & aRect,int32_t aAppUnitsPerDevPixel,RectCornerRadii * aCornerRadii,nscoord aBlurRadius,const Color & aShadowColor,const nsRect & aDirtyRect,const gfxRect & aSkipRect)5970 nsContextBoxBlur::BlurRectangle(gfxContext* aDestinationCtx,
5971                                 const nsRect& aRect,
5972                                 int32_t aAppUnitsPerDevPixel,
5973                                 RectCornerRadii* aCornerRadii,
5974                                 nscoord aBlurRadius,
5975                                 const Color& aShadowColor,
5976                                 const nsRect& aDirtyRect,
5977                                 const gfxRect& aSkipRect)
5978 {
5979   DrawTarget& aDestDrawTarget = *aDestinationCtx->GetDrawTarget();
5980 
5981   if (aRect.IsEmpty()) {
5982     return;
5983   }
5984 
5985   Rect shadowGfxRect = NSRectToRect(aRect, aAppUnitsPerDevPixel);
5986 
5987   if (aBlurRadius <= 0) {
5988     ColorPattern color(ToDeviceColor(aShadowColor));
5989     if (aCornerRadii) {
5990       RefPtr<Path> roundedRect = MakePathForRoundedRect(aDestDrawTarget,
5991                                                         shadowGfxRect,
5992                                                         *aCornerRadii);
5993       aDestDrawTarget.Fill(roundedRect, color);
5994     } else {
5995       aDestDrawTarget.FillRect(shadowGfxRect, color);
5996     }
5997     return;
5998   }
5999 
6000   gfxFloat scaleX = 1;
6001   gfxFloat scaleY = 1;
6002 
6003   // Do blurs in device space when possible.
6004   // Chrome/Skia always does the blurs in device space
6005   // and will sometimes get incorrect results (e.g. rotated blurs)
6006   gfxMatrix transform = aDestinationCtx->CurrentMatrix();
6007   // XXX: we could probably handle negative scales but for now it's easier just to fallback
6008   if (!transform.HasNonAxisAlignedTransform() && transform._11 > 0.0 && transform._22 > 0.0) {
6009     scaleX = transform._11;
6010     scaleY = transform._22;
6011     aDestinationCtx->SetMatrix(gfxMatrix());
6012   } else {
6013     transform = gfxMatrix();
6014   }
6015 
6016   gfxPoint blurStdDev = ComputeBlurStdDev(aBlurRadius, aAppUnitsPerDevPixel, scaleX, scaleY);
6017 
6018   gfxRect dirtyRect =
6019     nsLayoutUtils::RectToGfxRect(aDirtyRect, aAppUnitsPerDevPixel);
6020   dirtyRect.RoundOut();
6021 
6022   gfxRect shadowThebesRect = transform.TransformBounds(ThebesRect(shadowGfxRect));
6023   dirtyRect = transform.TransformBounds(dirtyRect);
6024   gfxRect skipRect = transform.TransformBounds(aSkipRect);
6025 
6026   if (aCornerRadii) {
6027     aCornerRadii->Scale(scaleX, scaleY);
6028   }
6029 
6030   gfxAlphaBoxBlur::BlurRectangle(aDestinationCtx,
6031                                  shadowThebesRect,
6032                                  aCornerRadii,
6033                                  blurStdDev,
6034                                  aShadowColor,
6035                                  dirtyRect,
6036                                  skipRect);
6037 }
6038 
6039 /* static */ void
GetBlurAndSpreadRadius(DrawTarget * aDestDrawTarget,int32_t aAppUnitsPerDevPixel,nscoord aBlurRadius,nscoord aSpreadRadius,IntSize & aOutBlurRadius,IntSize & aOutSpreadRadius,bool aConstrainSpreadRadius)6040 nsContextBoxBlur::GetBlurAndSpreadRadius(DrawTarget* aDestDrawTarget,
6041                                          int32_t aAppUnitsPerDevPixel,
6042                                          nscoord aBlurRadius,
6043                                          nscoord aSpreadRadius,
6044                                          IntSize& aOutBlurRadius,
6045                                          IntSize& aOutSpreadRadius,
6046                                          bool aConstrainSpreadRadius)
6047 {
6048   // Do blurs in device space when possible.
6049   // Chrome/Skia always does the blurs in device space
6050   // and will sometimes get incorrect results (e.g. rotated blurs)
6051   Matrix transform = aDestDrawTarget->GetTransform();
6052   // XXX: we could probably handle negative scales but for now it's easier just to fallback
6053   gfxFloat scaleX, scaleY;
6054   if (transform.HasNonAxisAlignedTransform() || transform._11 <= 0.0 || transform._22 <= 0.0) {
6055     scaleX = 1;
6056     scaleY = 1;
6057   } else {
6058     scaleX = transform._11;
6059     scaleY = transform._22;
6060   }
6061 
6062   // compute a large or smaller blur radius
6063   aOutBlurRadius = ComputeBlurRadius(aBlurRadius, aAppUnitsPerDevPixel, scaleX, scaleY);
6064   aOutSpreadRadius =
6065       IntSize(int32_t(aSpreadRadius * scaleX / aAppUnitsPerDevPixel),
6066               int32_t(aSpreadRadius * scaleY / aAppUnitsPerDevPixel));
6067 
6068 
6069   if (aConstrainSpreadRadius) {
6070     aOutSpreadRadius.width = std::min(aOutSpreadRadius.width, int32_t(MAX_SPREAD_RADIUS));
6071     aOutSpreadRadius.height = std::min(aOutSpreadRadius.height, int32_t(MAX_SPREAD_RADIUS));
6072   }
6073 }
6074 
6075 /* static */ bool
InsetBoxBlur(gfxContext * aDestinationCtx,Rect aDestinationRect,Rect aShadowClipRect,Color & aShadowColor,nscoord aBlurRadiusAppUnits,nscoord aSpreadDistanceAppUnits,int32_t aAppUnitsPerDevPixel,bool aHasBorderRadius,RectCornerRadii & aInnerClipRectRadii,Rect aSkipRect,Point aShadowOffset)6076 nsContextBoxBlur::InsetBoxBlur(gfxContext* aDestinationCtx,
6077                                Rect aDestinationRect,
6078                                Rect aShadowClipRect,
6079                                Color& aShadowColor,
6080                                nscoord aBlurRadiusAppUnits,
6081                                nscoord aSpreadDistanceAppUnits,
6082                                int32_t aAppUnitsPerDevPixel,
6083                                bool aHasBorderRadius,
6084                                RectCornerRadii& aInnerClipRectRadii,
6085                                Rect aSkipRect, Point aShadowOffset)
6086 {
6087   if (aDestinationRect.IsEmpty()) {
6088     mContext = nullptr;
6089     return false;
6090   }
6091 
6092   gfxContextAutoSaveRestore autoRestore(aDestinationCtx);
6093 
6094   IntSize blurRadius;
6095   IntSize spreadRadius;
6096   // Convert the blur and spread radius to device pixels
6097   bool constrainSpreadRadius = false;
6098   GetBlurAndSpreadRadius(aDestinationCtx->GetDrawTarget(), aAppUnitsPerDevPixel,
6099                          aBlurRadiusAppUnits, aSpreadDistanceAppUnits,
6100                          blurRadius, spreadRadius, constrainSpreadRadius);
6101 
6102   // The blur and spread radius are scaled already, so scale all
6103   // input data to the blur. This way, we don't have to scale the min
6104   // inset blur to the invert of the dest context, then rescale it back
6105   // when we draw to the destination surface.
6106   gfxSize scale = aDestinationCtx->CurrentMatrix().ScaleFactors(true);
6107   Matrix transform = ToMatrix(aDestinationCtx->CurrentMatrix());
6108 
6109   // XXX: we could probably handle negative scales but for now it's easier just to fallback
6110   if (!transform.HasNonAxisAlignedTransform() && transform._11 > 0.0 && transform._22 > 0.0) {
6111     // If we don't have a rotation, we're pre-transforming all the rects.
6112     aDestinationCtx->SetMatrix(gfxMatrix());
6113   } else {
6114     // Don't touch anything, we have a rotation.
6115     transform = Matrix();
6116   }
6117 
6118   Rect transformedDestRect = transform.TransformBounds(aDestinationRect);
6119   Rect transformedShadowClipRect = transform.TransformBounds(aShadowClipRect);
6120   Rect transformedSkipRect = transform.TransformBounds(aSkipRect);
6121 
6122   transformedDestRect.Round();
6123   transformedShadowClipRect.Round();
6124   transformedSkipRect.RoundIn();
6125 
6126   for (size_t i = 0; i < 4; i++) {
6127     aInnerClipRectRadii[i].width = std::floor(scale.width * aInnerClipRectRadii[i].width);
6128     aInnerClipRectRadii[i].height = std::floor(scale.height * aInnerClipRectRadii[i].height);
6129   }
6130 
6131   mAlphaBoxBlur.BlurInsetBox(aDestinationCtx, transformedDestRect,
6132                              transformedShadowClipRect,
6133                              blurRadius, spreadRadius,
6134                              aShadowColor, aHasBorderRadius,
6135                              aInnerClipRectRadii, transformedSkipRect,
6136                              aShadowOffset);
6137   return true;
6138 }
6139