mirror of
				https://github.com/mozilla/gecko-dev.git
				synced 2025-11-04 02:09:05 +02:00 
			
		
		
		
	
		
			
				
	
	
		
			4981 lines
		
	
	
	
		
			192 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			4981 lines
		
	
	
	
		
			192 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 | 
						||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
 | 
						||
/* This Source Code Form is subject to the terms of the Mozilla Public
 | 
						||
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 | 
						||
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 | 
						||
 | 
						||
/* utility functions for drawing borders and backgrounds */
 | 
						||
 | 
						||
#include "nsCSSRendering.h"
 | 
						||
 | 
						||
#include <ctime>
 | 
						||
 | 
						||
#include "gfx2DGlue.h"
 | 
						||
#include "gfxContext.h"
 | 
						||
#include "mozilla/ArrayUtils.h"
 | 
						||
#include "mozilla/ComputedStyle.h"
 | 
						||
#include "mozilla/DebugOnly.h"
 | 
						||
#include "mozilla/StaticPrefs_layout.h"
 | 
						||
#include "mozilla/gfx/2D.h"
 | 
						||
#include "mozilla/gfx/Helpers.h"
 | 
						||
#include "mozilla/gfx/Logging.h"
 | 
						||
#include "mozilla/gfx/PathHelpers.h"
 | 
						||
#include "mozilla/HashFunctions.h"
 | 
						||
#include "mozilla/MathAlgorithms.h"
 | 
						||
#include "mozilla/PresShell.h"
 | 
						||
#include "mozilla/ScrollContainerFrame.h"
 | 
						||
#include "mozilla/StaticPtr.h"
 | 
						||
#include "mozilla/SVGImageContext.h"
 | 
						||
#include "gfxFont.h"
 | 
						||
#include "ScaledFontBase.h"
 | 
						||
#include "skia/include/core/SkTextBlob.h"
 | 
						||
 | 
						||
#include "BorderConsts.h"
 | 
						||
#include "nsCanvasFrame.h"
 | 
						||
#include "nsStyleConsts.h"
 | 
						||
#include "nsPresContext.h"
 | 
						||
#include "nsIFrame.h"
 | 
						||
#include "nsIFrameInlines.h"
 | 
						||
#include "nsPageSequenceFrame.h"
 | 
						||
#include "nsPoint.h"
 | 
						||
#include "nsRect.h"
 | 
						||
#include "nsFrameManager.h"
 | 
						||
#include "nsGkAtoms.h"
 | 
						||
#include "nsCSSAnonBoxes.h"
 | 
						||
#include "nsIContent.h"
 | 
						||
#include "mozilla/dom/DocumentInlines.h"
 | 
						||
#include "imgIContainer.h"
 | 
						||
#include "ImageOps.h"
 | 
						||
#include "nsCSSColorUtils.h"
 | 
						||
#include "nsITheme.h"
 | 
						||
#include "nsLayoutUtils.h"
 | 
						||
#include "nsBlockFrame.h"
 | 
						||
#include "nsStyleStructInlines.h"
 | 
						||
#include "nsCSSFrameConstructor.h"
 | 
						||
#include "nsCSSProps.h"
 | 
						||
#include "nsContentUtils.h"
 | 
						||
#include "gfxDrawable.h"
 | 
						||
#include "nsCSSRenderingBorders.h"
 | 
						||
#include "mozilla/css/ImageLoader.h"
 | 
						||
#include "ImageContainer.h"
 | 
						||
#include "mozilla/ProfilerLabels.h"
 | 
						||
#include "mozilla/StaticPrefs_layout.h"
 | 
						||
#include "mozilla/Telemetry.h"
 | 
						||
#include "gfxUtils.h"
 | 
						||
#include "gfxGradientCache.h"
 | 
						||
#include "nsInlineFrame.h"
 | 
						||
#include "nsRubyTextContainerFrame.h"
 | 
						||
#include <algorithm>
 | 
						||
#include "TextDrawTarget.h"
 | 
						||
 | 
						||
using namespace mozilla;
 | 
						||
using namespace mozilla::css;
 | 
						||
using namespace mozilla::gfx;
 | 
						||
using namespace mozilla::image;
 | 
						||
using mozilla::CSSSizeOrRatio;
 | 
						||
using mozilla::dom::Document;
 | 
						||
 | 
						||
static int gFrameTreeLockCount = 0;
 | 
						||
 | 
						||
// To avoid storing this data on nsInlineFrame (bloat) and to avoid
 | 
						||
// recalculating this for each frame in a continuation (perf), hold
 | 
						||
// a cache of various coordinate information that we need in order
 | 
						||
// to paint inline backgrounds.
 | 
						||
struct InlineBackgroundData {
 | 
						||
  InlineBackgroundData()
 | 
						||
      : mFrame(nullptr),
 | 
						||
        mLineContainer(nullptr),
 | 
						||
        mContinuationPoint(0),
 | 
						||
        mUnbrokenMeasure(0),
 | 
						||
        mLineContinuationPoint(0),
 | 
						||
        mPIStartBorderData{},
 | 
						||
        mBidiEnabled(false),
 | 
						||
        mVertical(false) {}
 | 
						||
 | 
						||
  ~InlineBackgroundData() = default;
 | 
						||
 | 
						||
  void Reset() {
 | 
						||
    mBoundingBox.SetRect(0, 0, 0, 0);
 | 
						||
    mContinuationPoint = mLineContinuationPoint = mUnbrokenMeasure = 0;
 | 
						||
    mFrame = mLineContainer = nullptr;
 | 
						||
    mPIStartBorderData.Reset();
 | 
						||
  }
 | 
						||
 | 
						||
  /**
 | 
						||
   * Return a continuous rect for (an inline) aFrame relative to the
 | 
						||
   * continuation that draws the left-most part of the background.
 | 
						||
   * This is used when painting backgrounds.
 | 
						||
   */
 | 
						||
  nsRect GetContinuousRect(nsIFrame* aFrame) {
 | 
						||
    MOZ_ASSERT(static_cast<nsInlineFrame*>(do_QueryFrame(aFrame)));
 | 
						||
 | 
						||
    SetFrame(aFrame);
 | 
						||
 | 
						||
    nscoord pos;  // an x coordinate if writing-mode is horizontal;
 | 
						||
                  // y coordinate if vertical
 | 
						||
    if (mBidiEnabled) {
 | 
						||
      pos = mLineContinuationPoint;
 | 
						||
 | 
						||
      // Scan continuations on the same line as aFrame and accumulate the widths
 | 
						||
      // of frames that are to the left (if this is an LTR block) or right
 | 
						||
      // (if it's RTL) of the current one.
 | 
						||
      bool isRtlBlock = (mLineContainer->StyleVisibility()->mDirection ==
 | 
						||
                         StyleDirection::Rtl);
 | 
						||
      nscoord curOffset = mVertical ? aFrame->GetOffsetTo(mLineContainer).y
 | 
						||
                                    : aFrame->GetOffsetTo(mLineContainer).x;
 | 
						||
 | 
						||
      // If the continuation is fluid we know inlineFrame is not on the same
 | 
						||
      // line. If it's not fluid, we need to test further to be sure.
 | 
						||
      nsIFrame* inlineFrame = aFrame->GetPrevContinuation();
 | 
						||
      while (inlineFrame && !inlineFrame->GetNextInFlow() &&
 | 
						||
             AreOnSameLine(aFrame, inlineFrame)) {
 | 
						||
        nscoord frameOffset = mVertical
 | 
						||
                                  ? inlineFrame->GetOffsetTo(mLineContainer).y
 | 
						||
                                  : inlineFrame->GetOffsetTo(mLineContainer).x;
 | 
						||
        if (isRtlBlock == (frameOffset >= curOffset)) {
 | 
						||
          pos += mVertical ? inlineFrame->GetSize().height
 | 
						||
                           : inlineFrame->GetSize().width;
 | 
						||
        }
 | 
						||
        inlineFrame = inlineFrame->GetPrevContinuation();
 | 
						||
      }
 | 
						||
 | 
						||
      inlineFrame = aFrame->GetNextContinuation();
 | 
						||
      while (inlineFrame && !inlineFrame->GetPrevInFlow() &&
 | 
						||
             AreOnSameLine(aFrame, inlineFrame)) {
 | 
						||
        nscoord frameOffset = mVertical
 | 
						||
                                  ? inlineFrame->GetOffsetTo(mLineContainer).y
 | 
						||
                                  : inlineFrame->GetOffsetTo(mLineContainer).x;
 | 
						||
        if (isRtlBlock == (frameOffset >= curOffset)) {
 | 
						||
          pos += mVertical ? inlineFrame->GetSize().height
 | 
						||
                           : inlineFrame->GetSize().width;
 | 
						||
        }
 | 
						||
        inlineFrame = inlineFrame->GetNextContinuation();
 | 
						||
      }
 | 
						||
      if (isRtlBlock) {
 | 
						||
        // aFrame itself is also to the right of its left edge, so add its
 | 
						||
        // width.
 | 
						||
        pos += mVertical ? aFrame->GetSize().height : aFrame->GetSize().width;
 | 
						||
        // pos is now the distance from the left [top] edge of aFrame to the
 | 
						||
        // right [bottom] edge of the unbroken content. Change it to indicate
 | 
						||
        // the distance from the left [top] edge of the unbroken content to the
 | 
						||
        // left [top] edge of aFrame.
 | 
						||
        pos = mUnbrokenMeasure - pos;
 | 
						||
      }
 | 
						||
    } else {
 | 
						||
      pos = mContinuationPoint;
 | 
						||
    }
 | 
						||
 | 
						||
    // Assume background-origin: border and return a rect with offsets
 | 
						||
    // relative to (0,0).  If we have a different background-origin,
 | 
						||
    // then our rect should be deflated appropriately by our caller.
 | 
						||
    return mVertical
 | 
						||
               ? nsRect(0, -pos, mFrame->GetSize().width, mUnbrokenMeasure)
 | 
						||
               : nsRect(-pos, 0, mUnbrokenMeasure, mFrame->GetSize().height);
 | 
						||
  }
 | 
						||
 | 
						||
  /**
 | 
						||
   * Return a continuous rect for (an inline) aFrame relative to the
 | 
						||
   * continuation that should draw the left[top]-border.  This is used when
 | 
						||
   * painting borders and clipping backgrounds.  This may NOT be the same
 | 
						||
   * continuous rect as for drawing backgrounds; the continuation with the
 | 
						||
   * left[top]-border might be somewhere in the middle of that rect (e.g. BIDI),
 | 
						||
   * in those cases we need the reverse background order starting at the
 | 
						||
   * left[top]-border continuation.
 | 
						||
   */
 | 
						||
  nsRect GetBorderContinuousRect(nsIFrame* aFrame, nsRect aBorderArea) {
 | 
						||
    // Calling GetContinuousRect(aFrame) here may lead to Reset/Init which
 | 
						||
    // resets our mPIStartBorderData so we save it ...
 | 
						||
    PhysicalInlineStartBorderData saved(mPIStartBorderData);
 | 
						||
    nsRect joinedBorderArea = GetContinuousRect(aFrame);
 | 
						||
    if (!saved.mIsValid || saved.mFrame != mPIStartBorderData.mFrame) {
 | 
						||
      if (aFrame == mPIStartBorderData.mFrame) {
 | 
						||
        if (mVertical) {
 | 
						||
          mPIStartBorderData.SetCoord(joinedBorderArea.y);
 | 
						||
        } else {
 | 
						||
          mPIStartBorderData.SetCoord(joinedBorderArea.x);
 | 
						||
        }
 | 
						||
      } else if (mPIStartBorderData.mFrame) {
 | 
						||
        // Copy data to a temporary object so that computing the
 | 
						||
        // continous rect here doesn't clobber our normal state.
 | 
						||
        InlineBackgroundData temp = *this;
 | 
						||
        if (mVertical) {
 | 
						||
          mPIStartBorderData.SetCoord(
 | 
						||
              temp.GetContinuousRect(mPIStartBorderData.mFrame).y);
 | 
						||
        } else {
 | 
						||
          mPIStartBorderData.SetCoord(
 | 
						||
              temp.GetContinuousRect(mPIStartBorderData.mFrame).x);
 | 
						||
        }
 | 
						||
      }
 | 
						||
    } else {
 | 
						||
      // ... and restore it when possible.
 | 
						||
      mPIStartBorderData.SetCoord(saved.mCoord);
 | 
						||
    }
 | 
						||
    if (mVertical) {
 | 
						||
      if (joinedBorderArea.y > mPIStartBorderData.mCoord) {
 | 
						||
        joinedBorderArea.y =
 | 
						||
            -(mUnbrokenMeasure + joinedBorderArea.y - aBorderArea.height);
 | 
						||
      } else {
 | 
						||
        joinedBorderArea.y -= mPIStartBorderData.mCoord;
 | 
						||
      }
 | 
						||
    } else {
 | 
						||
      if (joinedBorderArea.x > mPIStartBorderData.mCoord) {
 | 
						||
        joinedBorderArea.x =
 | 
						||
            -(mUnbrokenMeasure + joinedBorderArea.x - aBorderArea.width);
 | 
						||
      } else {
 | 
						||
        joinedBorderArea.x -= mPIStartBorderData.mCoord;
 | 
						||
      }
 | 
						||
    }
 | 
						||
    return joinedBorderArea;
 | 
						||
  }
 | 
						||
 | 
						||
  nsRect GetBoundingRect(nsIFrame* aFrame) {
 | 
						||
    SetFrame(aFrame);
 | 
						||
 | 
						||
    // Move the offsets relative to (0,0) which puts the bounding box into
 | 
						||
    // our coordinate system rather than our parent's.  We do this by
 | 
						||
    // moving it the back distance from us to the bounding box.
 | 
						||
    // This also assumes background-origin: border, so our caller will
 | 
						||
    // need to deflate us if needed.
 | 
						||
    nsRect boundingBox(mBoundingBox);
 | 
						||
    nsPoint point = mFrame->GetPosition();
 | 
						||
    boundingBox.MoveBy(-point.x, -point.y);
 | 
						||
 | 
						||
    return boundingBox;
 | 
						||
  }
 | 
						||
 | 
						||
 protected:
 | 
						||
  // This is a coordinate on the inline axis, but is not a true logical inline-
 | 
						||
  // coord because it is always measured from left to right (if horizontal) or
 | 
						||
  // from top to bottom (if vertical), ignoring any bidi RTL directionality.
 | 
						||
  // We'll call this "physical inline start", or PIStart for short.
 | 
						||
  struct PhysicalInlineStartBorderData {
 | 
						||
    nsIFrame* mFrame;  // the continuation that may have a left-border
 | 
						||
    nscoord mCoord;    // cached GetContinuousRect(mFrame).x or .y
 | 
						||
    bool mIsValid;     // true if mCoord is valid
 | 
						||
    void Reset() {
 | 
						||
      mFrame = nullptr;
 | 
						||
      mIsValid = false;
 | 
						||
    }
 | 
						||
    void SetCoord(nscoord aCoord) {
 | 
						||
      mCoord = aCoord;
 | 
						||
      mIsValid = true;
 | 
						||
    }
 | 
						||
  };
 | 
						||
 | 
						||
  nsIFrame* mFrame;
 | 
						||
  nsIFrame* mLineContainer;
 | 
						||
  nsRect mBoundingBox;
 | 
						||
  nscoord mContinuationPoint;
 | 
						||
  nscoord mUnbrokenMeasure;
 | 
						||
  nscoord mLineContinuationPoint;
 | 
						||
  PhysicalInlineStartBorderData mPIStartBorderData;
 | 
						||
  bool mBidiEnabled;
 | 
						||
  bool mVertical;
 | 
						||
 | 
						||
  void SetFrame(nsIFrame* aFrame) {
 | 
						||
    MOZ_ASSERT(aFrame, "Need a frame");
 | 
						||
    NS_ASSERTION(gFrameTreeLockCount > 0,
 | 
						||
                 "Can't call this when frame tree is not locked");
 | 
						||
 | 
						||
    if (aFrame == mFrame) {
 | 
						||
      return;
 | 
						||
    }
 | 
						||
 | 
						||
    nsIFrame* prevContinuation = GetPrevContinuation(aFrame);
 | 
						||
 | 
						||
    if (!prevContinuation || mFrame != prevContinuation) {
 | 
						||
      // Ok, we've got the wrong frame.  We have to start from scratch.
 | 
						||
      Reset();
 | 
						||
      Init(aFrame);
 | 
						||
      return;
 | 
						||
    }
 | 
						||
 | 
						||
    // Get our last frame's size and add its width to our continuation
 | 
						||
    // point before we cache the new frame.
 | 
						||
    mContinuationPoint +=
 | 
						||
        mVertical ? mFrame->GetSize().height : mFrame->GetSize().width;
 | 
						||
 | 
						||
    // If this a new line, update mLineContinuationPoint.
 | 
						||
    if (mBidiEnabled &&
 | 
						||
        (aFrame->GetPrevInFlow() || !AreOnSameLine(mFrame, aFrame))) {
 | 
						||
      mLineContinuationPoint = mContinuationPoint;
 | 
						||
    }
 | 
						||
 | 
						||
    mFrame = aFrame;
 | 
						||
  }
 | 
						||
 | 
						||
  nsIFrame* GetPrevContinuation(nsIFrame* aFrame) {
 | 
						||
    nsIFrame* prevCont = aFrame->GetPrevContinuation();
 | 
						||
    if (!prevCont && aFrame->HasAnyStateBits(NS_FRAME_PART_OF_IBSPLIT)) {
 | 
						||
      nsIFrame* block = aFrame->GetProperty(nsIFrame::IBSplitPrevSibling());
 | 
						||
      if (block) {
 | 
						||
        // The {ib} properties are only stored on first continuations
 | 
						||
        NS_ASSERTION(!block->GetPrevContinuation(),
 | 
						||
                     "Incorrect value for IBSplitPrevSibling");
 | 
						||
        prevCont = block->GetProperty(nsIFrame::IBSplitPrevSibling());
 | 
						||
        NS_ASSERTION(prevCont, "How did that happen?");
 | 
						||
      }
 | 
						||
    }
 | 
						||
    return prevCont;
 | 
						||
  }
 | 
						||
 | 
						||
  nsIFrame* GetNextContinuation(nsIFrame* aFrame) {
 | 
						||
    nsIFrame* nextCont = aFrame->GetNextContinuation();
 | 
						||
    if (!nextCont && aFrame->HasAnyStateBits(NS_FRAME_PART_OF_IBSPLIT)) {
 | 
						||
      // The {ib} properties are only stored on first continuations
 | 
						||
      aFrame = aFrame->FirstContinuation();
 | 
						||
      nsIFrame* block = aFrame->GetProperty(nsIFrame::IBSplitSibling());
 | 
						||
      if (block) {
 | 
						||
        nextCont = block->GetProperty(nsIFrame::IBSplitSibling());
 | 
						||
        NS_ASSERTION(nextCont, "How did that happen?");
 | 
						||
      }
 | 
						||
    }
 | 
						||
    return nextCont;
 | 
						||
  }
 | 
						||
 | 
						||
  void Init(nsIFrame* aFrame) {
 | 
						||
    mPIStartBorderData.Reset();
 | 
						||
    mBidiEnabled = aFrame->PresContext()->BidiEnabled();
 | 
						||
    if (mBidiEnabled) {
 | 
						||
      // Find the line container frame
 | 
						||
      mLineContainer = aFrame;
 | 
						||
      while (mLineContainer && mLineContainer->IsLineParticipant()) {
 | 
						||
        mLineContainer = mLineContainer->GetParent();
 | 
						||
      }
 | 
						||
 | 
						||
      MOZ_ASSERT(mLineContainer, "Cannot find line containing frame.");
 | 
						||
      MOZ_ASSERT(mLineContainer != aFrame,
 | 
						||
                 "line container frame "
 | 
						||
                 "should be an ancestor of the target frame.");
 | 
						||
    }
 | 
						||
 | 
						||
    mVertical = aFrame->GetWritingMode().IsVertical();
 | 
						||
 | 
						||
    // Start with the previous flow frame as our continuation point
 | 
						||
    // is the total of the widths of the previous frames.
 | 
						||
    nsIFrame* inlineFrame = GetPrevContinuation(aFrame);
 | 
						||
    bool changedLines = false;
 | 
						||
    while (inlineFrame) {
 | 
						||
      if (!mPIStartBorderData.mFrame &&
 | 
						||
          !(mVertical ? inlineFrame->GetSkipSides().Top()
 | 
						||
                      : inlineFrame->GetSkipSides().Left())) {
 | 
						||
        mPIStartBorderData.mFrame = inlineFrame;
 | 
						||
      }
 | 
						||
      nsRect rect = inlineFrame->GetRect();
 | 
						||
      mContinuationPoint += mVertical ? rect.height : rect.width;
 | 
						||
      if (mBidiEnabled &&
 | 
						||
          (changedLines || !AreOnSameLine(aFrame, inlineFrame))) {
 | 
						||
        mLineContinuationPoint += mVertical ? rect.height : rect.width;
 | 
						||
        changedLines = true;
 | 
						||
      }
 | 
						||
      mUnbrokenMeasure += mVertical ? rect.height : rect.width;
 | 
						||
      mBoundingBox.UnionRect(mBoundingBox, rect);
 | 
						||
      inlineFrame = GetPrevContinuation(inlineFrame);
 | 
						||
    }
 | 
						||
 | 
						||
    // Next add this frame and subsequent frames to the bounding box and
 | 
						||
    // unbroken width.
 | 
						||
    inlineFrame = aFrame;
 | 
						||
    while (inlineFrame) {
 | 
						||
      if (!mPIStartBorderData.mFrame &&
 | 
						||
          !(mVertical ? inlineFrame->GetSkipSides().Top()
 | 
						||
                      : inlineFrame->GetSkipSides().Left())) {
 | 
						||
        mPIStartBorderData.mFrame = inlineFrame;
 | 
						||
      }
 | 
						||
      nsRect rect = inlineFrame->GetRect();
 | 
						||
      mUnbrokenMeasure += mVertical ? rect.height : rect.width;
 | 
						||
      mBoundingBox.UnionRect(mBoundingBox, rect);
 | 
						||
      inlineFrame = GetNextContinuation(inlineFrame);
 | 
						||
    }
 | 
						||
 | 
						||
    mFrame = aFrame;
 | 
						||
  }
 | 
						||
 | 
						||
  bool AreOnSameLine(nsIFrame* aFrame1, nsIFrame* aFrame2) {
 | 
						||
    if (nsBlockFrame* blockFrame = do_QueryFrame(mLineContainer)) {
 | 
						||
      bool isValid1, isValid2;
 | 
						||
      nsBlockInFlowLineIterator it1(blockFrame, aFrame1, &isValid1);
 | 
						||
      nsBlockInFlowLineIterator it2(blockFrame, aFrame2, &isValid2);
 | 
						||
      return isValid1 && isValid2 &&
 | 
						||
             // Make sure aFrame1 and aFrame2 are in the same continuation of
 | 
						||
             // blockFrame.
 | 
						||
             it1.GetContainer() == it2.GetContainer() &&
 | 
						||
             // And on the same line in it
 | 
						||
             it1.GetLine().get() == it2.GetLine().get();
 | 
						||
    }
 | 
						||
    if (nsRubyTextContainerFrame* rtcFrame = do_QueryFrame(mLineContainer)) {
 | 
						||
      nsBlockFrame* block = nsLayoutUtils::FindNearestBlockAncestor(rtcFrame);
 | 
						||
      // Ruby text container can only hold one line of text, so if they
 | 
						||
      // are in the same continuation, they are in the same line. Since
 | 
						||
      // ruby text containers are bidi isolate, they are never split for
 | 
						||
      // bidi reordering, which means being in different continuation
 | 
						||
      // indicates being in different lines.
 | 
						||
      for (nsIFrame* frame = rtcFrame->FirstContinuation(); frame;
 | 
						||
           frame = frame->GetNextContinuation()) {
 | 
						||
        bool isDescendant1 =
 | 
						||
            nsLayoutUtils::IsProperAncestorFrame(frame, aFrame1, block);
 | 
						||
        bool isDescendant2 =
 | 
						||
            nsLayoutUtils::IsProperAncestorFrame(frame, aFrame2, block);
 | 
						||
        if (isDescendant1 && isDescendant2) {
 | 
						||
          return true;
 | 
						||
        }
 | 
						||
        if (isDescendant1 || isDescendant2) {
 | 
						||
          return false;
 | 
						||
        }
 | 
						||
      }
 | 
						||
      MOZ_ASSERT_UNREACHABLE("None of the frames is a descendant of this rtc?");
 | 
						||
    }
 | 
						||
    MOZ_ASSERT_UNREACHABLE("Do we have any other type of line container?");
 | 
						||
    return false;
 | 
						||
  }
 | 
						||
};
 | 
						||
 | 
						||
static StaticAutoPtr<InlineBackgroundData> gInlineBGData;
 | 
						||
 | 
						||
// Initialize any static variables used by nsCSSRendering.
 | 
						||
void nsCSSRendering::Init() {
 | 
						||
  NS_ASSERTION(!gInlineBGData, "Init called twice");
 | 
						||
  gInlineBGData = new InlineBackgroundData();
 | 
						||
}
 | 
						||
 | 
						||
// Clean up any global variables used by nsCSSRendering.
 | 
						||
void nsCSSRendering::Shutdown() { gInlineBGData = nullptr; }
 | 
						||
 | 
						||
/**
 | 
						||
 * Make a bevel color
 | 
						||
 */
 | 
						||
static nscolor MakeBevelColor(mozilla::Side whichSide, StyleBorderStyle style,
 | 
						||
                              nscolor aBorderColor) {
 | 
						||
  nscolor colors[2];
 | 
						||
  nscolor theColor;
 | 
						||
 | 
						||
  // Given a background color and a border color
 | 
						||
  // calculate the color used for the shading
 | 
						||
  NS_GetSpecial3DColors(colors, aBorderColor);
 | 
						||
 | 
						||
  if ((style == StyleBorderStyle::Outset) ||
 | 
						||
      (style == StyleBorderStyle::Ridge)) {
 | 
						||
    // Flip colors for these two border styles
 | 
						||
    switch (whichSide) {
 | 
						||
      case eSideBottom:
 | 
						||
        whichSide = eSideTop;
 | 
						||
        break;
 | 
						||
      case eSideRight:
 | 
						||
        whichSide = eSideLeft;
 | 
						||
        break;
 | 
						||
      case eSideTop:
 | 
						||
        whichSide = eSideBottom;
 | 
						||
        break;
 | 
						||
      case eSideLeft:
 | 
						||
        whichSide = eSideRight;
 | 
						||
        break;
 | 
						||
    }
 | 
						||
  }
 | 
						||
 | 
						||
  switch (whichSide) {
 | 
						||
    case eSideBottom:
 | 
						||
      theColor = colors[1];
 | 
						||
      break;
 | 
						||
    case eSideRight:
 | 
						||
      theColor = colors[1];
 | 
						||
      break;
 | 
						||
    case eSideTop:
 | 
						||
      theColor = colors[0];
 | 
						||
      break;
 | 
						||
    case eSideLeft:
 | 
						||
    default:
 | 
						||
      theColor = colors[0];
 | 
						||
      break;
 | 
						||
  }
 | 
						||
  return theColor;
 | 
						||
}
 | 
						||
 | 
						||
static bool GetRadii(nsIFrame* aForFrame, const nsStyleBorder& aBorder,
 | 
						||
                     const nsRect& aOrigBorderArea, const nsRect& aBorderArea,
 | 
						||
                     nscoord aRadii[8]) {
 | 
						||
  bool haveRoundedCorners;
 | 
						||
  nsSize sz = aBorderArea.Size();
 | 
						||
  nsSize frameSize = aForFrame->GetSize();
 | 
						||
  if (&aBorder == aForFrame->StyleBorder() &&
 | 
						||
      frameSize == aOrigBorderArea.Size()) {
 | 
						||
    haveRoundedCorners = aForFrame->GetBorderRadii(sz, sz, Sides(), aRadii);
 | 
						||
  } else {
 | 
						||
    haveRoundedCorners = nsIFrame::ComputeBorderRadii(
 | 
						||
        aBorder.mBorderRadius, frameSize, sz, Sides(), aRadii);
 | 
						||
  }
 | 
						||
 | 
						||
  return haveRoundedCorners;
 | 
						||
}
 | 
						||
 | 
						||
static bool GetRadii(nsIFrame* aForFrame, const nsStyleBorder& aBorder,
 | 
						||
                     const nsRect& aOrigBorderArea, const nsRect& aBorderArea,
 | 
						||
                     RectCornerRadii* aBgRadii) {
 | 
						||
  nscoord radii[8];
 | 
						||
  bool haveRoundedCorners =
 | 
						||
      GetRadii(aForFrame, aBorder, aOrigBorderArea, aBorderArea, radii);
 | 
						||
 | 
						||
  if (haveRoundedCorners) {
 | 
						||
    auto d2a = aForFrame->PresContext()->AppUnitsPerDevPixel();
 | 
						||
    nsCSSRendering::ComputePixelRadii(radii, d2a, aBgRadii);
 | 
						||
  }
 | 
						||
  return haveRoundedCorners;
 | 
						||
}
 | 
						||
 | 
						||
static nsRect JoinBoxesForBlockAxisSlice(nsIFrame* aFrame,
 | 
						||
                                         const nsRect& aBorderArea) {
 | 
						||
  // Inflate the block-axis size as if our continuations were laid out
 | 
						||
  // adjacent in that axis.  Note that we don't touch the inline size.
 | 
						||
  const auto wm = aFrame->GetWritingMode();
 | 
						||
  const nsSize dummyContainerSize;
 | 
						||
  LogicalRect borderArea(wm, aBorderArea, dummyContainerSize);
 | 
						||
  nscoord bSize = 0;
 | 
						||
  nsIFrame* f = aFrame->GetNextContinuation();
 | 
						||
  for (; f; f = f->GetNextContinuation()) {
 | 
						||
    bSize += f->BSize(wm);
 | 
						||
  }
 | 
						||
  borderArea.BSize(wm) += bSize;
 | 
						||
  bSize = 0;
 | 
						||
  f = aFrame->GetPrevContinuation();
 | 
						||
  for (; f; f = f->GetPrevContinuation()) {
 | 
						||
    bSize += f->BSize(wm);
 | 
						||
  }
 | 
						||
  borderArea.BStart(wm) -= bSize;
 | 
						||
  borderArea.BSize(wm) += bSize;
 | 
						||
  return borderArea.GetPhysicalRect(wm, dummyContainerSize);
 | 
						||
}
 | 
						||
 | 
						||
/**
 | 
						||
 * Inflate aBorderArea which is relative to aFrame's origin to calculate
 | 
						||
 * a hypothetical non-split frame area for all the continuations.
 | 
						||
 * See "Joining Boxes for 'slice'" in
 | 
						||
 * http://dev.w3.org/csswg/css-break/#break-decoration
 | 
						||
 */
 | 
						||
enum InlineBoxOrder { eForBorder, eForBackground };
 | 
						||
static nsRect JoinBoxesForSlice(nsIFrame* aFrame, const nsRect& aBorderArea,
 | 
						||
                                InlineBoxOrder aOrder) {
 | 
						||
  if (static_cast<nsInlineFrame*>(do_QueryFrame(aFrame))) {
 | 
						||
    return (aOrder == eForBorder
 | 
						||
                ? gInlineBGData->GetBorderContinuousRect(aFrame, aBorderArea)
 | 
						||
                : gInlineBGData->GetContinuousRect(aFrame)) +
 | 
						||
           aBorderArea.TopLeft();
 | 
						||
  }
 | 
						||
  return JoinBoxesForBlockAxisSlice(aFrame, aBorderArea);
 | 
						||
}
 | 
						||
 | 
						||
/* static */
 | 
						||
bool nsCSSRendering::IsBoxDecorationSlice(const nsStyleBorder& aStyleBorder) {
 | 
						||
  return aStyleBorder.mBoxDecorationBreak == StyleBoxDecorationBreak::Slice;
 | 
						||
}
 | 
						||
 | 
						||
/* static */
 | 
						||
nsRect nsCSSRendering::BoxDecorationRectForBorder(
 | 
						||
    nsIFrame* aFrame, const nsRect& aBorderArea, Sides aSkipSides,
 | 
						||
    const nsStyleBorder* aStyleBorder) {
 | 
						||
  if (!aStyleBorder) {
 | 
						||
    aStyleBorder = aFrame->StyleBorder();
 | 
						||
  }
 | 
						||
  // If aSkipSides.IsEmpty() then there are no continuations, or it's
 | 
						||
  // a ::first-letter that wants all border sides on the first continuation.
 | 
						||
  return IsBoxDecorationSlice(*aStyleBorder) && !aSkipSides.IsEmpty()
 | 
						||
             ? ::JoinBoxesForSlice(aFrame, aBorderArea, eForBorder)
 | 
						||
             : aBorderArea;
 | 
						||
}
 | 
						||
 | 
						||
/* static */
 | 
						||
nsRect nsCSSRendering::BoxDecorationRectForBackground(
 | 
						||
    nsIFrame* aFrame, const nsRect& aBorderArea, Sides aSkipSides,
 | 
						||
    const nsStyleBorder* aStyleBorder) {
 | 
						||
  if (!aStyleBorder) {
 | 
						||
    aStyleBorder = aFrame->StyleBorder();
 | 
						||
  }
 | 
						||
  // If aSkipSides.IsEmpty() then there are no continuations, or it's
 | 
						||
  // a ::first-letter that wants all border sides on the first continuation.
 | 
						||
  return IsBoxDecorationSlice(*aStyleBorder) && !aSkipSides.IsEmpty()
 | 
						||
             ? ::JoinBoxesForSlice(aFrame, aBorderArea, eForBackground)
 | 
						||
             : aBorderArea;
 | 
						||
}
 | 
						||
 | 
						||
//----------------------------------------------------------------------
 | 
						||
// Thebes Border Rendering Code Start
 | 
						||
 | 
						||
/*
 | 
						||
 * Compute the float-pixel radii that should be used for drawing
 | 
						||
 * this border/outline, given the various input bits.
 | 
						||
 */
 | 
						||
/* static */
 | 
						||
void nsCSSRendering::ComputePixelRadii(const nscoord* aAppUnitsRadii,
 | 
						||
                                       nscoord aAppUnitsPerPixel,
 | 
						||
                                       RectCornerRadii* oBorderRadii) {
 | 
						||
  Float radii[8];
 | 
						||
  for (const auto corner : mozilla::AllPhysicalHalfCorners()) {
 | 
						||
    radii[corner] = Float(aAppUnitsRadii[corner]) / aAppUnitsPerPixel;
 | 
						||
  }
 | 
						||
 | 
						||
  (*oBorderRadii)[C_TL] = Size(radii[eCornerTopLeftX], radii[eCornerTopLeftY]);
 | 
						||
  (*oBorderRadii)[C_TR] =
 | 
						||
      Size(radii[eCornerTopRightX], radii[eCornerTopRightY]);
 | 
						||
  (*oBorderRadii)[C_BR] =
 | 
						||
      Size(radii[eCornerBottomRightX], radii[eCornerBottomRightY]);
 | 
						||
  (*oBorderRadii)[C_BL] =
 | 
						||
      Size(radii[eCornerBottomLeftX], radii[eCornerBottomLeftY]);
 | 
						||
}
 | 
						||
 | 
						||
static Maybe<nsStyleBorder> GetBorderIfVisited(const ComputedStyle& aStyle) {
 | 
						||
  Maybe<nsStyleBorder> result;
 | 
						||
  // Don't check RelevantLinkVisited here, since we want to take the
 | 
						||
  // same amount of time whether or not it's true.
 | 
						||
  const ComputedStyle* styleIfVisited = aStyle.GetStyleIfVisited();
 | 
						||
  if (MOZ_LIKELY(!styleIfVisited)) {
 | 
						||
    return result;
 | 
						||
  }
 | 
						||
 | 
						||
  result.emplace(*aStyle.StyleBorder());
 | 
						||
  auto& newBorder = result.ref();
 | 
						||
  for (const auto side : mozilla::AllPhysicalSides()) {
 | 
						||
    nscolor color = aStyle.GetVisitedDependentColor(
 | 
						||
        nsStyleBorder::BorderColorFieldFor(side));
 | 
						||
    newBorder.BorderColorFor(side) = StyleColor::FromColor(color);
 | 
						||
  }
 | 
						||
 | 
						||
  return result;
 | 
						||
}
 | 
						||
 | 
						||
ImgDrawResult nsCSSRendering::PaintBorder(
 | 
						||
    nsPresContext* aPresContext, gfxContext& aRenderingContext,
 | 
						||
    nsIFrame* aForFrame, const nsRect& aDirtyRect, const nsRect& aBorderArea,
 | 
						||
    ComputedStyle* aStyle, PaintBorderFlags aFlags, Sides aSkipSides) {
 | 
						||
  AUTO_PROFILER_LABEL("nsCSSRendering::PaintBorder", GRAPHICS);
 | 
						||
 | 
						||
  Maybe<nsStyleBorder> visitedBorder = GetBorderIfVisited(*aStyle);
 | 
						||
  return PaintBorderWithStyleBorder(
 | 
						||
      aPresContext, aRenderingContext, aForFrame, aDirtyRect, aBorderArea,
 | 
						||
      visitedBorder.refOr(*aStyle->StyleBorder()), aStyle, aFlags, aSkipSides);
 | 
						||
}
 | 
						||
 | 
						||
Maybe<nsCSSBorderRenderer> nsCSSRendering::CreateBorderRenderer(
 | 
						||
    nsPresContext* aPresContext, DrawTarget* aDrawTarget, nsIFrame* aForFrame,
 | 
						||
    const nsRect& aDirtyRect, const nsRect& aBorderArea, ComputedStyle* aStyle,
 | 
						||
    bool* aOutBorderIsEmpty, Sides aSkipSides) {
 | 
						||
  Maybe<nsStyleBorder> visitedBorder = GetBorderIfVisited(*aStyle);
 | 
						||
  return CreateBorderRendererWithStyleBorder(
 | 
						||
      aPresContext, aDrawTarget, aForFrame, aDirtyRect, aBorderArea,
 | 
						||
      visitedBorder.refOr(*aStyle->StyleBorder()), aStyle, aOutBorderIsEmpty,
 | 
						||
      aSkipSides);
 | 
						||
}
 | 
						||
 | 
						||
ImgDrawResult nsCSSRendering::CreateWebRenderCommandsForBorder(
 | 
						||
    nsDisplayItem* aItem, nsIFrame* aForFrame, const nsRect& aBorderArea,
 | 
						||
    mozilla::wr::DisplayListBuilder& aBuilder,
 | 
						||
    mozilla::wr::IpcResourceUpdateQueue& aResources,
 | 
						||
    const mozilla::layers::StackingContextHelper& aSc,
 | 
						||
    mozilla::layers::RenderRootStateManager* aManager,
 | 
						||
    nsDisplayListBuilder* aDisplayListBuilder) {
 | 
						||
  const auto* style = aForFrame->Style();
 | 
						||
  Maybe<nsStyleBorder> visitedBorder = GetBorderIfVisited(*style);
 | 
						||
  return nsCSSRendering::CreateWebRenderCommandsForBorderWithStyleBorder(
 | 
						||
      aItem, aForFrame, aBorderArea, aBuilder, aResources, aSc, aManager,
 | 
						||
      aDisplayListBuilder, visitedBorder.refOr(*style->StyleBorder()));
 | 
						||
}
 | 
						||
 | 
						||
void nsCSSRendering::CreateWebRenderCommandsForNullBorder(
 | 
						||
    nsDisplayItem* aItem, nsIFrame* aForFrame, const nsRect& aBorderArea,
 | 
						||
    mozilla::wr::DisplayListBuilder& aBuilder,
 | 
						||
    mozilla::wr::IpcResourceUpdateQueue& aResources,
 | 
						||
    const mozilla::layers::StackingContextHelper& aSc,
 | 
						||
    const nsStyleBorder& aStyleBorder) {
 | 
						||
  bool borderIsEmpty = false;
 | 
						||
  Maybe<nsCSSBorderRenderer> br =
 | 
						||
      nsCSSRendering::CreateNullBorderRendererWithStyleBorder(
 | 
						||
          aForFrame->PresContext(), nullptr, aForFrame, nsRect(), aBorderArea,
 | 
						||
          aStyleBorder, aForFrame->Style(), &borderIsEmpty,
 | 
						||
          aForFrame->GetSkipSides());
 | 
						||
  if (!borderIsEmpty && br) {
 | 
						||
    br->CreateWebRenderCommands(aItem, aBuilder, aResources, aSc);
 | 
						||
  }
 | 
						||
}
 | 
						||
 | 
						||
ImgDrawResult nsCSSRendering::CreateWebRenderCommandsForBorderWithStyleBorder(
 | 
						||
    nsDisplayItem* aItem, nsIFrame* aForFrame, const nsRect& aBorderArea,
 | 
						||
    mozilla::wr::DisplayListBuilder& aBuilder,
 | 
						||
    mozilla::wr::IpcResourceUpdateQueue& aResources,
 | 
						||
    const mozilla::layers::StackingContextHelper& aSc,
 | 
						||
    mozilla::layers::RenderRootStateManager* aManager,
 | 
						||
    nsDisplayListBuilder* aDisplayListBuilder,
 | 
						||
    const nsStyleBorder& aStyleBorder) {
 | 
						||
  auto& borderImage = aStyleBorder.mBorderImageSource;
 | 
						||
  // First try to create commands for simple borders.
 | 
						||
  if (borderImage.IsNone()) {
 | 
						||
    CreateWebRenderCommandsForNullBorder(
 | 
						||
        aItem, aForFrame, aBorderArea, aBuilder, aResources, aSc, aStyleBorder);
 | 
						||
    return ImgDrawResult::SUCCESS;
 | 
						||
  }
 | 
						||
 | 
						||
  // Next we try image and gradient borders. Gradients are not supported at
 | 
						||
  // this very moment.
 | 
						||
  if (!borderImage.IsImageRequestType()) {
 | 
						||
    return ImgDrawResult::NOT_SUPPORTED;
 | 
						||
  }
 | 
						||
 | 
						||
  if (aStyleBorder.mBorderImageRepeatH == StyleBorderImageRepeat::Space ||
 | 
						||
      aStyleBorder.mBorderImageRepeatV == StyleBorderImageRepeat::Space) {
 | 
						||
    return ImgDrawResult::NOT_SUPPORTED;
 | 
						||
  }
 | 
						||
 | 
						||
  uint32_t flags = 0;
 | 
						||
  if (aDisplayListBuilder->IsPaintingToWindow()) {
 | 
						||
    flags |= nsImageRenderer::FLAG_PAINTING_TO_WINDOW;
 | 
						||
  }
 | 
						||
  if (aDisplayListBuilder->ShouldSyncDecodeImages()) {
 | 
						||
    flags |= nsImageRenderer::FLAG_SYNC_DECODE_IMAGES;
 | 
						||
  }
 | 
						||
 | 
						||
  bool dummy;
 | 
						||
  image::ImgDrawResult result;
 | 
						||
  Maybe<nsCSSBorderImageRenderer> bir =
 | 
						||
      nsCSSBorderImageRenderer::CreateBorderImageRenderer(
 | 
						||
          aForFrame->PresContext(), aForFrame, aBorderArea, aStyleBorder,
 | 
						||
          aItem->GetBounds(aDisplayListBuilder, &dummy),
 | 
						||
          aForFrame->GetSkipSides(), flags, &result);
 | 
						||
 | 
						||
  if (!bir) {
 | 
						||
    // We aren't ready. Try to fallback to the null border image if present but
 | 
						||
    // return the draw result for the border image renderer.
 | 
						||
    CreateWebRenderCommandsForNullBorder(
 | 
						||
        aItem, aForFrame, aBorderArea, aBuilder, aResources, aSc, aStyleBorder);
 | 
						||
    return result;
 | 
						||
  }
 | 
						||
 | 
						||
  return bir->CreateWebRenderCommands(aItem, aForFrame, aBuilder, aResources,
 | 
						||
                                      aSc, aManager, aDisplayListBuilder);
 | 
						||
}
 | 
						||
 | 
						||
static nsCSSBorderRenderer ConstructBorderRenderer(
 | 
						||
    nsPresContext* aPresContext, ComputedStyle* aStyle, DrawTarget* aDrawTarget,
 | 
						||
    nsIFrame* aForFrame, const nsRect& aDirtyRect, const nsRect& aBorderArea,
 | 
						||
    const nsStyleBorder& aStyleBorder, Sides aSkipSides, bool* aNeedsClip) {
 | 
						||
  nsMargin border = aStyleBorder.GetComputedBorder();
 | 
						||
 | 
						||
  // Compute the outermost boundary of the area that might be painted.
 | 
						||
  // Same coordinate space as aBorderArea & aBGClipRect.
 | 
						||
  nsRect joinedBorderArea = nsCSSRendering::BoxDecorationRectForBorder(
 | 
						||
      aForFrame, aBorderArea, aSkipSides, &aStyleBorder);
 | 
						||
  RectCornerRadii bgRadii;
 | 
						||
  ::GetRadii(aForFrame, aStyleBorder, aBorderArea, joinedBorderArea, &bgRadii);
 | 
						||
 | 
						||
  PrintAsFormatString(" joinedBorderArea: %d %d %d %d\n", joinedBorderArea.x,
 | 
						||
                      joinedBorderArea.y, joinedBorderArea.width,
 | 
						||
                      joinedBorderArea.height);
 | 
						||
 | 
						||
  // start drawing
 | 
						||
  if (nsCSSRendering::IsBoxDecorationSlice(aStyleBorder)) {
 | 
						||
    if (joinedBorderArea.IsEqualEdges(aBorderArea)) {
 | 
						||
      // No need for a clip, just skip the sides we don't want.
 | 
						||
      border.ApplySkipSides(aSkipSides);
 | 
						||
    } else {
 | 
						||
      // We're drawing borders around the joined continuation boxes so we need
 | 
						||
      // to clip that to the slice that we want for this frame.
 | 
						||
      *aNeedsClip = true;
 | 
						||
    }
 | 
						||
  } else {
 | 
						||
    MOZ_ASSERT(joinedBorderArea.IsEqualEdges(aBorderArea),
 | 
						||
               "Should use aBorderArea for box-decoration-break:clone");
 | 
						||
    MOZ_ASSERT(
 | 
						||
        aForFrame->GetSkipSides().IsEmpty() ||
 | 
						||
            aForFrame->IsTrueOverflowContainer() ||
 | 
						||
            aForFrame->IsColumnSetFrame(),  // a little broader than column-rule
 | 
						||
        "Should not skip sides for box-decoration-break:clone except "
 | 
						||
        "::first-letter/line continuations or other frame types that "
 | 
						||
        "don't have borders but those shouldn't reach this point. "
 | 
						||
        "Overflow containers do reach this point though, as does "
 | 
						||
        "column-rule drawing (which always involves a columnset).");
 | 
						||
    border.ApplySkipSides(aSkipSides);
 | 
						||
  }
 | 
						||
 | 
						||
  // Convert to dev pixels.
 | 
						||
  nscoord oneDevPixel = aPresContext->DevPixelsToAppUnits(1);
 | 
						||
  Rect joinedBorderAreaPx = NSRectToRect(joinedBorderArea, oneDevPixel);
 | 
						||
  Float borderWidths[4] = {
 | 
						||
      Float(border.top) / oneDevPixel, Float(border.right) / oneDevPixel,
 | 
						||
      Float(border.bottom) / oneDevPixel, Float(border.left) / oneDevPixel};
 | 
						||
  Rect dirtyRect = NSRectToRect(aDirtyRect, oneDevPixel);
 | 
						||
 | 
						||
  StyleBorderStyle borderStyles[4];
 | 
						||
  nscolor borderColors[4];
 | 
						||
 | 
						||
  // pull out styles, colors
 | 
						||
  for (const auto i : mozilla::AllPhysicalSides()) {
 | 
						||
    borderStyles[i] = aStyleBorder.GetBorderStyle(i);
 | 
						||
    borderColors[i] = aStyleBorder.BorderColorFor(i).CalcColor(*aStyle);
 | 
						||
  }
 | 
						||
 | 
						||
  PrintAsFormatString(
 | 
						||
      " borderStyles: %d %d %d %d\n", static_cast<int>(borderStyles[0]),
 | 
						||
      static_cast<int>(borderStyles[1]), static_cast<int>(borderStyles[2]),
 | 
						||
      static_cast<int>(borderStyles[3]));
 | 
						||
 | 
						||
  return nsCSSBorderRenderer(
 | 
						||
      aPresContext, aDrawTarget, dirtyRect, joinedBorderAreaPx, borderStyles,
 | 
						||
      borderWidths, bgRadii, borderColors, !aForFrame->BackfaceIsHidden(),
 | 
						||
      *aNeedsClip ? Some(NSRectToRect(aBorderArea, oneDevPixel)) : Nothing());
 | 
						||
}
 | 
						||
 | 
						||
ImgDrawResult nsCSSRendering::PaintBorderWithStyleBorder(
 | 
						||
    nsPresContext* aPresContext, gfxContext& aRenderingContext,
 | 
						||
    nsIFrame* aForFrame, const nsRect& aDirtyRect, const nsRect& aBorderArea,
 | 
						||
    const nsStyleBorder& aStyleBorder, ComputedStyle* aStyle,
 | 
						||
    PaintBorderFlags aFlags, Sides aSkipSides) {
 | 
						||
  DrawTarget& aDrawTarget = *aRenderingContext.GetDrawTarget();
 | 
						||
 | 
						||
  PrintAsStringNewline("++ PaintBorder");
 | 
						||
 | 
						||
  // Check to see if we have an appearance defined.  If so, we let the theme
 | 
						||
  // renderer draw the border.  DO not get the data from aForFrame, since the
 | 
						||
  // passed in ComputedStyle may be different!  Always use |aStyle|!
 | 
						||
  StyleAppearance appearance = aStyle->StyleDisplay()->EffectiveAppearance();
 | 
						||
  if (appearance != StyleAppearance::None) {
 | 
						||
    nsITheme* theme = aPresContext->Theme();
 | 
						||
    if (theme->ThemeSupportsWidget(aPresContext, aForFrame, appearance)) {
 | 
						||
      return ImgDrawResult::SUCCESS;  // Let the theme handle it.
 | 
						||
    }
 | 
						||
  }
 | 
						||
 | 
						||
  if (!aStyleBorder.mBorderImageSource.IsNone()) {
 | 
						||
    ImgDrawResult result = ImgDrawResult::SUCCESS;
 | 
						||
 | 
						||
    uint32_t irFlags = 0;
 | 
						||
    if (aFlags & PaintBorderFlags::SyncDecodeImages) {
 | 
						||
      irFlags |= nsImageRenderer::FLAG_SYNC_DECODE_IMAGES;
 | 
						||
    }
 | 
						||
 | 
						||
    // Creating the border image renderer will request a decode, and we rely on
 | 
						||
    // that happening.
 | 
						||
    Maybe<nsCSSBorderImageRenderer> renderer =
 | 
						||
        nsCSSBorderImageRenderer::CreateBorderImageRenderer(
 | 
						||
            aPresContext, aForFrame, aBorderArea, aStyleBorder, aDirtyRect,
 | 
						||
            aSkipSides, irFlags, &result);
 | 
						||
    // renderer was created successfully, which means border image is ready to
 | 
						||
    // be used.
 | 
						||
    if (renderer) {
 | 
						||
      MOZ_ASSERT(result == ImgDrawResult::SUCCESS);
 | 
						||
      return renderer->DrawBorderImage(aPresContext, aRenderingContext,
 | 
						||
                                       aForFrame, aDirtyRect);
 | 
						||
    }
 | 
						||
  }
 | 
						||
 | 
						||
  ImgDrawResult result = ImgDrawResult::SUCCESS;
 | 
						||
 | 
						||
  // If we had a border-image, but it wasn't loaded, then we should return
 | 
						||
  // ImgDrawResult::NOT_READY; we'll want to try again if we do a paint with
 | 
						||
  // sync decoding enabled.
 | 
						||
  if (!aStyleBorder.mBorderImageSource.IsNone()) {
 | 
						||
    result = ImgDrawResult::NOT_READY;
 | 
						||
  }
 | 
						||
 | 
						||
  nsMargin border = aStyleBorder.GetComputedBorder();
 | 
						||
  if (0 == border.left && 0 == border.right && 0 == border.top &&
 | 
						||
      0 == border.bottom) {
 | 
						||
    // Empty border area
 | 
						||
    return result;
 | 
						||
  }
 | 
						||
 | 
						||
  bool needsClip = false;
 | 
						||
  nsCSSBorderRenderer br = ConstructBorderRenderer(
 | 
						||
      aPresContext, aStyle, &aDrawTarget, aForFrame, aDirtyRect, aBorderArea,
 | 
						||
      aStyleBorder, aSkipSides, &needsClip);
 | 
						||
  if (needsClip) {
 | 
						||
    aDrawTarget.PushClipRect(NSRectToSnappedRect(
 | 
						||
        aBorderArea, aForFrame->PresContext()->AppUnitsPerDevPixel(),
 | 
						||
        aDrawTarget));
 | 
						||
  }
 | 
						||
 | 
						||
  br.DrawBorders();
 | 
						||
 | 
						||
  if (needsClip) {
 | 
						||
    aDrawTarget.PopClip();
 | 
						||
  }
 | 
						||
 | 
						||
  PrintAsStringNewline();
 | 
						||
 | 
						||
  return result;
 | 
						||
}
 | 
						||
 | 
						||
Maybe<nsCSSBorderRenderer> nsCSSRendering::CreateBorderRendererWithStyleBorder(
 | 
						||
    nsPresContext* aPresContext, DrawTarget* aDrawTarget, nsIFrame* aForFrame,
 | 
						||
    const nsRect& aDirtyRect, const nsRect& aBorderArea,
 | 
						||
    const nsStyleBorder& aStyleBorder, ComputedStyle* aStyle,
 | 
						||
    bool* aOutBorderIsEmpty, Sides aSkipSides) {
 | 
						||
  if (!aStyleBorder.mBorderImageSource.IsNone()) {
 | 
						||
    return Nothing();
 | 
						||
  }
 | 
						||
  return CreateNullBorderRendererWithStyleBorder(
 | 
						||
      aPresContext, aDrawTarget, aForFrame, aDirtyRect, aBorderArea,
 | 
						||
      aStyleBorder, aStyle, aOutBorderIsEmpty, aSkipSides);
 | 
						||
}
 | 
						||
 | 
						||
Maybe<nsCSSBorderRenderer>
 | 
						||
nsCSSRendering::CreateNullBorderRendererWithStyleBorder(
 | 
						||
    nsPresContext* aPresContext, DrawTarget* aDrawTarget, nsIFrame* aForFrame,
 | 
						||
    const nsRect& aDirtyRect, const nsRect& aBorderArea,
 | 
						||
    const nsStyleBorder& aStyleBorder, ComputedStyle* aStyle,
 | 
						||
    bool* aOutBorderIsEmpty, Sides aSkipSides) {
 | 
						||
  StyleAppearance appearance = aStyle->StyleDisplay()->EffectiveAppearance();
 | 
						||
  if (appearance != StyleAppearance::None) {
 | 
						||
    nsITheme* theme = aPresContext->Theme();
 | 
						||
    if (theme->ThemeSupportsWidget(aPresContext, aForFrame, appearance)) {
 | 
						||
      // The border will be draw as part of the themed background item created
 | 
						||
      // for this same frame. If no themed background item was created then not
 | 
						||
      // drawing also matches that we do without webrender and what
 | 
						||
      // nsDisplayBorder does for themed borders.
 | 
						||
      if (aOutBorderIsEmpty) {
 | 
						||
        *aOutBorderIsEmpty = true;
 | 
						||
      }
 | 
						||
      return Nothing();
 | 
						||
    }
 | 
						||
  }
 | 
						||
 | 
						||
  nsMargin border = aStyleBorder.GetComputedBorder();
 | 
						||
  if (0 == border.left && 0 == border.right && 0 == border.top &&
 | 
						||
      0 == border.bottom) {
 | 
						||
    // Empty border area
 | 
						||
    if (aOutBorderIsEmpty) {
 | 
						||
      *aOutBorderIsEmpty = true;
 | 
						||
    }
 | 
						||
    return Nothing();
 | 
						||
  }
 | 
						||
 | 
						||
  bool needsClip = false;
 | 
						||
  nsCSSBorderRenderer br = ConstructBorderRenderer(
 | 
						||
      aPresContext, aStyle, aDrawTarget, aForFrame, aDirtyRect, aBorderArea,
 | 
						||
      aStyleBorder, aSkipSides, &needsClip);
 | 
						||
  return Some(br);
 | 
						||
}
 | 
						||
 | 
						||
Maybe<nsCSSBorderRenderer>
 | 
						||
nsCSSRendering::CreateBorderRendererForNonThemedOutline(
 | 
						||
    nsPresContext* aPresContext, DrawTarget* aDrawTarget, nsIFrame* aForFrame,
 | 
						||
    const nsRect& aDirtyRect, const nsRect& aInnerRect, ComputedStyle* aStyle) {
 | 
						||
  // Get our ComputedStyle's color struct.
 | 
						||
  const nsStyleOutline* ourOutline = aStyle->StyleOutline();
 | 
						||
  if (!ourOutline->ShouldPaintOutline()) {
 | 
						||
    // Empty outline
 | 
						||
    return Nothing();
 | 
						||
  }
 | 
						||
 | 
						||
  nsRect innerRect = aInnerRect;
 | 
						||
 | 
						||
  const nsSize effectiveOffset = ourOutline->EffectiveOffsetFor(innerRect);
 | 
						||
  innerRect.Inflate(effectiveOffset);
 | 
						||
 | 
						||
  // If the dirty rect is completely inside the border area (e.g., only the
 | 
						||
  // content is being painted), then we can skip out now
 | 
						||
  // XXX this isn't exactly true for rounded borders, where the inside curves
 | 
						||
  // may encroach into the content area.  A safer calculation would be to
 | 
						||
  // shorten insideRect by the radius one each side before performing this test.
 | 
						||
  if (innerRect.Contains(aDirtyRect)) {
 | 
						||
    return Nothing();
 | 
						||
  }
 | 
						||
 | 
						||
  const nscoord width = ourOutline->GetOutlineWidth();
 | 
						||
 | 
						||
  StyleBorderStyle outlineStyle;
 | 
						||
  // Themed outlines are handled by our callers, if supported.
 | 
						||
  if (ourOutline->mOutlineStyle.IsAuto()) {
 | 
						||
    if (width == 0) {
 | 
						||
      return Nothing();  // empty outline
 | 
						||
    }
 | 
						||
    // http://dev.w3.org/csswg/css-ui/#outline
 | 
						||
    // "User agents may treat 'auto' as 'solid'."
 | 
						||
    outlineStyle = StyleBorderStyle::Solid;
 | 
						||
  } else {
 | 
						||
    outlineStyle = ourOutline->mOutlineStyle.AsBorderStyle();
 | 
						||
  }
 | 
						||
 | 
						||
  RectCornerRadii outlineRadii;
 | 
						||
  nsRect outerRect = innerRect;
 | 
						||
  outerRect.Inflate(width);
 | 
						||
 | 
						||
  const nscoord oneDevPixel = aPresContext->AppUnitsPerDevPixel();
 | 
						||
  Rect oRect(NSRectToRect(outerRect, oneDevPixel));
 | 
						||
 | 
						||
  const Float outlineWidths[4] = {
 | 
						||
      Float(width) / oneDevPixel, Float(width) / oneDevPixel,
 | 
						||
      Float(width) / oneDevPixel, Float(width) / oneDevPixel};
 | 
						||
 | 
						||
  // convert the radii
 | 
						||
  nscoord twipsRadii[8];
 | 
						||
 | 
						||
  // get the radius for our outline
 | 
						||
  if (aForFrame->GetBorderRadii(twipsRadii)) {
 | 
						||
    RectCornerRadii innerRadii;
 | 
						||
    ComputePixelRadii(twipsRadii, oneDevPixel, &innerRadii);
 | 
						||
 | 
						||
    const auto devPxOffset = LayoutDeviceSize::FromAppUnits(
 | 
						||
        effectiveOffset, aPresContext->AppUnitsPerDevPixel());
 | 
						||
 | 
						||
    const Float widths[4] = {outlineWidths[0] + devPxOffset.Height(),
 | 
						||
                             outlineWidths[1] + devPxOffset.Width(),
 | 
						||
                             outlineWidths[2] + devPxOffset.Height(),
 | 
						||
                             outlineWidths[3] + devPxOffset.Width()};
 | 
						||
    nsCSSBorderRenderer::ComputeOuterRadii(innerRadii, widths, &outlineRadii);
 | 
						||
  }
 | 
						||
 | 
						||
  StyleBorderStyle outlineStyles[4] = {outlineStyle, outlineStyle, outlineStyle,
 | 
						||
                                       outlineStyle};
 | 
						||
 | 
						||
  // This handles treating the initial color as 'currentColor'; if we
 | 
						||
  // ever want 'invert' back we'll need to do a bit of work here too.
 | 
						||
  nscolor outlineColor =
 | 
						||
      aStyle->GetVisitedDependentColor(&nsStyleOutline::mOutlineColor);
 | 
						||
  nscolor outlineColors[4] = {outlineColor, outlineColor, outlineColor,
 | 
						||
                              outlineColor};
 | 
						||
 | 
						||
  Rect dirtyRect = NSRectToRect(aDirtyRect, oneDevPixel);
 | 
						||
 | 
						||
  return Some(nsCSSBorderRenderer(
 | 
						||
      aPresContext, aDrawTarget, dirtyRect, oRect, outlineStyles, outlineWidths,
 | 
						||
      outlineRadii, outlineColors, !aForFrame->BackfaceIsHidden(), Nothing()));
 | 
						||
}
 | 
						||
 | 
						||
void nsCSSRendering::PaintNonThemedOutline(nsPresContext* aPresContext,
 | 
						||
                                           gfxContext& aRenderingContext,
 | 
						||
                                           nsIFrame* aForFrame,
 | 
						||
                                           const nsRect& aDirtyRect,
 | 
						||
                                           const nsRect& aInnerRect,
 | 
						||
                                           ComputedStyle* aStyle) {
 | 
						||
  Maybe<nsCSSBorderRenderer> br = CreateBorderRendererForNonThemedOutline(
 | 
						||
      aPresContext, aRenderingContext.GetDrawTarget(), aForFrame, aDirtyRect,
 | 
						||
      aInnerRect, aStyle);
 | 
						||
  if (!br) {
 | 
						||
    return;
 | 
						||
  }
 | 
						||
 | 
						||
  // start drawing
 | 
						||
  br->DrawBorders();
 | 
						||
 | 
						||
  PrintAsStringNewline();
 | 
						||
}
 | 
						||
 | 
						||
void nsCSSRendering::PaintFocus(nsPresContext* aPresContext,
 | 
						||
                                DrawTarget* aDrawTarget,
 | 
						||
                                const nsRect& aFocusRect, nscolor aColor) {
 | 
						||
  nscoord oneCSSPixel = nsPresContext::CSSPixelsToAppUnits(1);
 | 
						||
  nscoord oneDevPixel = aPresContext->DevPixelsToAppUnits(1);
 | 
						||
 | 
						||
  Rect focusRect(NSRectToRect(aFocusRect, oneDevPixel));
 | 
						||
 | 
						||
  RectCornerRadii focusRadii;
 | 
						||
  {
 | 
						||
    nscoord twipsRadii[8] = {0, 0, 0, 0, 0, 0, 0, 0};
 | 
						||
    ComputePixelRadii(twipsRadii, oneDevPixel, &focusRadii);
 | 
						||
  }
 | 
						||
  Float focusWidths[4] = {
 | 
						||
      Float(oneCSSPixel) / oneDevPixel, Float(oneCSSPixel) / oneDevPixel,
 | 
						||
      Float(oneCSSPixel) / oneDevPixel, Float(oneCSSPixel) / oneDevPixel};
 | 
						||
 | 
						||
  StyleBorderStyle focusStyles[4] = {
 | 
						||
      StyleBorderStyle::Dotted, StyleBorderStyle::Dotted,
 | 
						||
      StyleBorderStyle::Dotted, StyleBorderStyle::Dotted};
 | 
						||
  nscolor focusColors[4] = {aColor, aColor, aColor, aColor};
 | 
						||
 | 
						||
  // Because this renders a dotted border, the background color
 | 
						||
  // should not be used.  Therefore, we provide a value that will
 | 
						||
  // be blatantly wrong if it ever does get used.  (If this becomes
 | 
						||
  // something that CSS can style, this function will then have access
 | 
						||
  // to a ComputedStyle and can use the same logic that PaintBorder
 | 
						||
  // and PaintOutline do.)
 | 
						||
  //
 | 
						||
  // WebRender layers-free mode don't use PaintFocus function. Just assign
 | 
						||
  // the backface-visibility to true for this case.
 | 
						||
  nsCSSBorderRenderer br(aPresContext, aDrawTarget, focusRect, focusRect,
 | 
						||
                         focusStyles, focusWidths, focusRadii, focusColors,
 | 
						||
                         true, Nothing());
 | 
						||
  br.DrawBorders();
 | 
						||
 | 
						||
  PrintAsStringNewline();
 | 
						||
}
 | 
						||
 | 
						||
// Thebes Border Rendering Code End
 | 
						||
//----------------------------------------------------------------------
 | 
						||
 | 
						||
//----------------------------------------------------------------------
 | 
						||
 | 
						||
/**
 | 
						||
 * Helper for ComputeObjectAnchorPoint; parameters are the same as for
 | 
						||
 * that function, except they're for a single coordinate / a single size
 | 
						||
 * dimension. (so, x/width vs. y/height)
 | 
						||
 */
 | 
						||
static void ComputeObjectAnchorCoord(const LengthPercentage& aCoord,
 | 
						||
                                     const nscoord aOriginBounds,
 | 
						||
                                     const nscoord aImageSize,
 | 
						||
                                     nscoord* aTopLeftCoord,
 | 
						||
                                     nscoord* aAnchorPointCoord) {
 | 
						||
  nscoord extraSpace = aOriginBounds - aImageSize;
 | 
						||
 | 
						||
  // The anchor-point doesn't care about our image's size; just the size
 | 
						||
  // of the region we're rendering into.
 | 
						||
  *aAnchorPointCoord = aCoord.Resolve(
 | 
						||
      aOriginBounds, static_cast<nscoord (*)(float)>(NSToCoordRoundWithClamp));
 | 
						||
  // Adjust aTopLeftCoord by the specified % of the extra space.
 | 
						||
  *aTopLeftCoord = aCoord.Resolve(
 | 
						||
      extraSpace, static_cast<nscoord (*)(float)>(NSToCoordRoundWithClamp));
 | 
						||
}
 | 
						||
 | 
						||
void nsImageRenderer::ComputeObjectAnchorPoint(const Position& aPos,
 | 
						||
                                               const nsSize& aOriginBounds,
 | 
						||
                                               const nsSize& aImageSize,
 | 
						||
                                               nsPoint* aTopLeft,
 | 
						||
                                               nsPoint* aAnchorPoint) {
 | 
						||
  ComputeObjectAnchorCoord(aPos.horizontal, aOriginBounds.width,
 | 
						||
                           aImageSize.width, &aTopLeft->x, &aAnchorPoint->x);
 | 
						||
 | 
						||
  ComputeObjectAnchorCoord(aPos.vertical, aOriginBounds.height,
 | 
						||
                           aImageSize.height, &aTopLeft->y, &aAnchorPoint->y);
 | 
						||
}
 | 
						||
 | 
						||
// In print / print preview we have multiple canvas frames (one for each page,
 | 
						||
// and one for the document as a whole). For the topmost one, we really want the
 | 
						||
// page sequence page background, not the root or body's background.
 | 
						||
static nsIFrame* GetPageSequenceForCanvas(const nsIFrame* aCanvasFrame) {
 | 
						||
  MOZ_ASSERT(aCanvasFrame->IsCanvasFrame(), "not a canvas frame");
 | 
						||
  nsPresContext* pc = aCanvasFrame->PresContext();
 | 
						||
  if (!pc->IsRootPaginatedDocument()) {
 | 
						||
    return nullptr;
 | 
						||
  }
 | 
						||
  auto* ps = pc->PresShell()->GetPageSequenceFrame();
 | 
						||
  if (NS_WARN_IF(!ps)) {
 | 
						||
    return nullptr;
 | 
						||
  }
 | 
						||
  if (ps->GetParent() != aCanvasFrame) {
 | 
						||
    return nullptr;
 | 
						||
  }
 | 
						||
  return ps;
 | 
						||
}
 | 
						||
 | 
						||
auto nsCSSRendering::FindEffectiveBackgroundColor(
 | 
						||
    nsIFrame* aFrame, bool aStopAtThemed,
 | 
						||
    bool aPreferBodyToCanvas) -> EffectiveBackgroundColor {
 | 
						||
  MOZ_ASSERT(aFrame);
 | 
						||
  nsPresContext* pc = aFrame->PresContext();
 | 
						||
  auto BgColorIfNotTransparent = [&](nsIFrame* aFrame) -> Maybe<nscolor> {
 | 
						||
    nscolor c =
 | 
						||
        aFrame->GetVisitedDependentColor(&nsStyleBackground::mBackgroundColor);
 | 
						||
    if (NS_GET_A(c) == 255) {
 | 
						||
      return Some(c);
 | 
						||
    }
 | 
						||
    if (NS_GET_A(c)) {
 | 
						||
      // TODO(emilio): We should maybe just blend with ancestor bg colors and
 | 
						||
      // such, but this is probably good enough for now, matches pre-existing
 | 
						||
      // behavior.
 | 
						||
      const nscolor defaultBg = pc->DefaultBackgroundColor();
 | 
						||
      MOZ_ASSERT(NS_GET_A(defaultBg) == 255, "PreferenceSheet guarantees this");
 | 
						||
      return Some(NS_ComposeColors(defaultBg, c));
 | 
						||
    }
 | 
						||
    return Nothing();
 | 
						||
  };
 | 
						||
 | 
						||
  for (nsIFrame* frame = aFrame; frame;
 | 
						||
       frame = nsLayoutUtils::GetParentOrPlaceholderForCrossDoc(frame)) {
 | 
						||
    if (auto bg = BgColorIfNotTransparent(frame)) {
 | 
						||
      return {*bg};
 | 
						||
    }
 | 
						||
 | 
						||
    if (aStopAtThemed && frame->IsThemed()) {
 | 
						||
      return {NS_TRANSPARENT, true};
 | 
						||
    }
 | 
						||
 | 
						||
    if (frame->IsCanvasFrame()) {
 | 
						||
      if (aPreferBodyToCanvas && !GetPageSequenceForCanvas(frame)) {
 | 
						||
        if (auto* body = pc->Document()->GetBodyElement()) {
 | 
						||
          if (nsIFrame* f = body->GetPrimaryFrame()) {
 | 
						||
            if (auto bg = BgColorIfNotTransparent(f)) {
 | 
						||
              return {*bg};
 | 
						||
            }
 | 
						||
          }
 | 
						||
        }
 | 
						||
      }
 | 
						||
      if (nsIFrame* bgFrame = FindBackgroundFrame(frame)) {
 | 
						||
        if (auto bg = BgColorIfNotTransparent(bgFrame)) {
 | 
						||
          return {*bg};
 | 
						||
        }
 | 
						||
      }
 | 
						||
    }
 | 
						||
  }
 | 
						||
 | 
						||
  return {pc->DefaultBackgroundColor()};
 | 
						||
}
 | 
						||
 | 
						||
nsIFrame* nsCSSRendering::FindBackgroundStyleFrame(nsIFrame* aForFrame) {
 | 
						||
  const nsStyleBackground* result = aForFrame->StyleBackground();
 | 
						||
 | 
						||
  // Check if we need to do propagation from BODY rather than HTML.
 | 
						||
  if (!result->IsTransparent(aForFrame)) {
 | 
						||
    return aForFrame;
 | 
						||
  }
 | 
						||
 | 
						||
  nsIContent* content = aForFrame->GetContent();
 | 
						||
  // The root element content can't be null. We wouldn't know what
 | 
						||
  // frame to create for aFrame.
 | 
						||
  // Use |OwnerDoc| so it works during destruction.
 | 
						||
  if (!content) {
 | 
						||
    return aForFrame;
 | 
						||
  }
 | 
						||
 | 
						||
  Document* document = content->OwnerDoc();
 | 
						||
 | 
						||
  dom::Element* bodyContent = document->GetBodyElement();
 | 
						||
  // We need to null check the body node (bug 118829) since
 | 
						||
  // there are cases, thanks to the fix for bug 5569, where we
 | 
						||
  // will reflow a document with no body.  In particular, if a
 | 
						||
  // SCRIPT element in the head blocks the parser and then has a
 | 
						||
  // SCRIPT that does "document.location.href = 'foo'", then
 | 
						||
  // nsParser::Terminate will call |DidBuildModel| methods
 | 
						||
  // through to the content sink, which will call |StartLayout|
 | 
						||
  // and thus |Initialize| on the pres shell.  See bug 119351
 | 
						||
  // for the ugly details.
 | 
						||
  if (!bodyContent || aForFrame->StyleDisplay()->IsContainAny()) {
 | 
						||
    return aForFrame;
 | 
						||
  }
 | 
						||
 | 
						||
  nsIFrame* bodyFrame = bodyContent->GetPrimaryFrame();
 | 
						||
  if (!bodyFrame || bodyFrame->StyleDisplay()->IsContainAny()) {
 | 
						||
    return aForFrame;
 | 
						||
  }
 | 
						||
 | 
						||
  return nsLayoutUtils::GetStyleFrame(bodyFrame);
 | 
						||
}
 | 
						||
 | 
						||
/**
 | 
						||
 * |FindBackground| finds the correct style data to use to paint the
 | 
						||
 * background.  It is responsible for handling the following two
 | 
						||
 * statements in section 14.2 of CSS2:
 | 
						||
 *
 | 
						||
 *   The background of the box generated by the root element covers the
 | 
						||
 *   entire canvas.
 | 
						||
 *
 | 
						||
 *   For HTML documents, however, we recommend that authors specify the
 | 
						||
 *   background for the BODY element rather than the HTML element. User
 | 
						||
 *   agents should observe the following precedence rules to fill in the
 | 
						||
 *   background: if the value of the 'background' property for the HTML
 | 
						||
 *   element is different from 'transparent' then use it, else use the
 | 
						||
 *   value of the 'background' property for the BODY element. If the
 | 
						||
 *   resulting value is 'transparent', the rendering is undefined.
 | 
						||
 *
 | 
						||
 * Thus, in our implementation, it is responsible for ensuring that:
 | 
						||
 *  + we paint the correct background on the |nsCanvasFrame| or |nsPageFrame|,
 | 
						||
 *  + we don't paint the background on the root element, and
 | 
						||
 *  + we don't paint the background on the BODY element in *some* cases,
 | 
						||
 *    and for SGML-based HTML documents only.
 | 
						||
 *
 | 
						||
 * |FindBackground| checks whether a background should be painted. If yes, it
 | 
						||
 * returns the resulting ComputedStyle to use for the background information;
 | 
						||
 * Otherwise, it returns nullptr.
 | 
						||
 */
 | 
						||
ComputedStyle* nsCSSRendering::FindRootFrameBackground(nsIFrame* aForFrame) {
 | 
						||
  return FindBackgroundStyleFrame(aForFrame)->Style();
 | 
						||
}
 | 
						||
 | 
						||
static nsIFrame* FindCanvasBackgroundFrame(const nsIFrame* aForFrame,
 | 
						||
                                           nsIFrame* aRootElementFrame) {
 | 
						||
  MOZ_ASSERT(aForFrame->IsCanvasFrame(), "not a canvas frame");
 | 
						||
  if (auto* ps = GetPageSequenceForCanvas(aForFrame)) {
 | 
						||
    return ps;
 | 
						||
  }
 | 
						||
  if (aRootElementFrame) {
 | 
						||
    return nsCSSRendering::FindBackgroundStyleFrame(aRootElementFrame);
 | 
						||
  }
 | 
						||
  // This should always give transparent, so we'll fill it in with the default
 | 
						||
  // color if needed.  This seems to happen a bit while a page is being loaded.
 | 
						||
  return const_cast<nsIFrame*>(aForFrame);
 | 
						||
}
 | 
						||
 | 
						||
// Helper for FindBackgroundFrame. Returns true if aForFrame has a meaningful
 | 
						||
// background that it should draw (i.e. that it hasn't propagated to another
 | 
						||
// frame).  See documentation for FindBackground.
 | 
						||
inline bool FrameHasMeaningfulBackground(const nsIFrame* aForFrame,
 | 
						||
                                         nsIFrame* aRootElementFrame) {
 | 
						||
  MOZ_ASSERT(!aForFrame->IsCanvasFrame(),
 | 
						||
             "FindBackgroundFrame handles canvas frames before calling us, "
 | 
						||
             "so we don't need to consider them here");
 | 
						||
 | 
						||
  if (aForFrame == aRootElementFrame) {
 | 
						||
    // We must have propagated our background to the viewport or canvas. Abort.
 | 
						||
    return false;
 | 
						||
  }
 | 
						||
 | 
						||
  // Return true unless the frame is for a BODY element whose background
 | 
						||
  // was propagated to the viewport.
 | 
						||
 | 
						||
  nsIContent* content = aForFrame->GetContent();
 | 
						||
  if (!content || content->NodeInfo()->NameAtom() != nsGkAtoms::body) {
 | 
						||
    return true;  // not frame for a "body" element
 | 
						||
  }
 | 
						||
  // It could be a non-HTML "body" element but that's OK, we'd fail the
 | 
						||
  // bodyContent check below
 | 
						||
 | 
						||
  if (aForFrame->Style()->GetPseudoType() != PseudoStyleType::NotPseudo ||
 | 
						||
      aForFrame->StyleDisplay()->IsContainAny()) {
 | 
						||
    return true;  // A pseudo-element frame, or contained.
 | 
						||
  }
 | 
						||
 | 
						||
  // We should only look at the <html> background if we're in an HTML document
 | 
						||
  Document* document = content->OwnerDoc();
 | 
						||
 | 
						||
  dom::Element* bodyContent = document->GetBodyElement();
 | 
						||
  if (bodyContent != content) {
 | 
						||
    return true;  // this wasn't the background that was propagated
 | 
						||
  }
 | 
						||
 | 
						||
  // This can be called even when there's no root element yet, during frame
 | 
						||
  // construction, via nsLayoutUtils::FrameHasTransparency and
 | 
						||
  // nsContainerFrame::SyncFrameViewProperties.
 | 
						||
  if (!aRootElementFrame || aRootElementFrame->StyleDisplay()->IsContainAny()) {
 | 
						||
    return true;
 | 
						||
  }
 | 
						||
 | 
						||
  const nsStyleBackground* htmlBG = aRootElementFrame->StyleBackground();
 | 
						||
  return !htmlBG->IsTransparent(aRootElementFrame);
 | 
						||
}
 | 
						||
 | 
						||
nsIFrame* nsCSSRendering::FindBackgroundFrame(const nsIFrame* aForFrame) {
 | 
						||
  nsIFrame* rootElementFrame =
 | 
						||
      aForFrame->PresShell()->FrameConstructor()->GetRootElementStyleFrame();
 | 
						||
  if (aForFrame->IsCanvasFrame()) {
 | 
						||
    return FindCanvasBackgroundFrame(aForFrame, rootElementFrame);
 | 
						||
  }
 | 
						||
 | 
						||
  if (FrameHasMeaningfulBackground(aForFrame, rootElementFrame)) {
 | 
						||
    return const_cast<nsIFrame*>(aForFrame);
 | 
						||
  }
 | 
						||
 | 
						||
  return nullptr;
 | 
						||
}
 | 
						||
 | 
						||
ComputedStyle* nsCSSRendering::FindBackground(const nsIFrame* aForFrame) {
 | 
						||
  if (auto* backgroundFrame = FindBackgroundFrame(aForFrame)) {
 | 
						||
    return backgroundFrame->Style();
 | 
						||
  }
 | 
						||
  return nullptr;
 | 
						||
}
 | 
						||
 | 
						||
void nsCSSRendering::BeginFrameTreesLocked() { ++gFrameTreeLockCount; }
 | 
						||
 | 
						||
void nsCSSRendering::EndFrameTreesLocked() {
 | 
						||
  NS_ASSERTION(gFrameTreeLockCount > 0, "Unbalanced EndFrameTreeLocked");
 | 
						||
  --gFrameTreeLockCount;
 | 
						||
  if (gFrameTreeLockCount == 0) {
 | 
						||
    gInlineBGData->Reset();
 | 
						||
  }
 | 
						||
}
 | 
						||
 | 
						||
bool nsCSSRendering::HasBoxShadowNativeTheme(nsIFrame* aFrame,
 | 
						||
                                             bool& aMaybeHasBorderRadius) {
 | 
						||
  const nsStyleDisplay* styleDisplay = aFrame->StyleDisplay();
 | 
						||
  nsITheme::Transparency transparency;
 | 
						||
  if (aFrame->IsThemed(styleDisplay, &transparency)) {
 | 
						||
    aMaybeHasBorderRadius = false;
 | 
						||
    // For opaque (rectangular) theme widgets we can take the generic
 | 
						||
    // border-box path with border-radius disabled.
 | 
						||
    return transparency != nsITheme::eOpaque;
 | 
						||
  }
 | 
						||
 | 
						||
  aMaybeHasBorderRadius = true;
 | 
						||
  return false;
 | 
						||
}
 | 
						||
 | 
						||
gfx::sRGBColor nsCSSRendering::GetShadowColor(const StyleSimpleShadow& aShadow,
 | 
						||
                                              nsIFrame* aFrame,
 | 
						||
                                              float aOpacity) {
 | 
						||
  // Get the shadow color; if not specified, use the foreground color
 | 
						||
  nscolor shadowColor = aShadow.color.CalcColor(aFrame);
 | 
						||
  sRGBColor color = sRGBColor::FromABGR(shadowColor);
 | 
						||
  color.a *= aOpacity;
 | 
						||
  return color;
 | 
						||
}
 | 
						||
 | 
						||
nsRect nsCSSRendering::GetShadowRect(const nsRect& aFrameArea,
 | 
						||
                                     bool aNativeTheme, nsIFrame* aForFrame) {
 | 
						||
  nsRect frameRect = aNativeTheme ? aForFrame->InkOverflowRectRelativeToSelf() +
 | 
						||
                                        aFrameArea.TopLeft()
 | 
						||
                                  : aFrameArea;
 | 
						||
  Sides skipSides = aForFrame->GetSkipSides();
 | 
						||
  frameRect = BoxDecorationRectForBorder(aForFrame, frameRect, skipSides);
 | 
						||
 | 
						||
  // Explicitly do not need to account for the spread radius here
 | 
						||
  // Webrender does it for us or PaintBoxShadow will for non-WR
 | 
						||
  return frameRect;
 | 
						||
}
 | 
						||
 | 
						||
bool nsCSSRendering::GetBorderRadii(const nsRect& aFrameRect,
 | 
						||
                                    const nsRect& aBorderRect, nsIFrame* aFrame,
 | 
						||
                                    RectCornerRadii& aOutRadii) {
 | 
						||
  const nscoord oneDevPixel = aFrame->PresContext()->DevPixelsToAppUnits(1);
 | 
						||
  nscoord twipsRadii[8];
 | 
						||
  NS_ASSERTION(
 | 
						||
      aBorderRect.Size() == aFrame->VisualBorderRectRelativeToSelf().Size(),
 | 
						||
      "unexpected size");
 | 
						||
  nsSize sz = aFrameRect.Size();
 | 
						||
  bool hasBorderRadius = aFrame->GetBorderRadii(sz, sz, Sides(), twipsRadii);
 | 
						||
  if (hasBorderRadius) {
 | 
						||
    ComputePixelRadii(twipsRadii, oneDevPixel, &aOutRadii);
 | 
						||
  }
 | 
						||
 | 
						||
  return hasBorderRadius;
 | 
						||
}
 | 
						||
 | 
						||
void nsCSSRendering::PaintBoxShadowOuter(nsPresContext* aPresContext,
 | 
						||
                                         gfxContext& aRenderingContext,
 | 
						||
                                         nsIFrame* aForFrame,
 | 
						||
                                         const nsRect& aFrameArea,
 | 
						||
                                         const nsRect& aDirtyRect,
 | 
						||
                                         float aOpacity) {
 | 
						||
  DrawTarget& aDrawTarget = *aRenderingContext.GetDrawTarget();
 | 
						||
  auto shadows = aForFrame->StyleEffects()->mBoxShadow.AsSpan();
 | 
						||
  if (shadows.IsEmpty()) {
 | 
						||
    return;
 | 
						||
  }
 | 
						||
 | 
						||
  bool hasBorderRadius;
 | 
						||
  // mutually exclusive with hasBorderRadius
 | 
						||
  bool nativeTheme = HasBoxShadowNativeTheme(aForFrame, hasBorderRadius);
 | 
						||
  const nsStyleDisplay* styleDisplay = aForFrame->StyleDisplay();
 | 
						||
 | 
						||
  nsRect frameRect = GetShadowRect(aFrameArea, nativeTheme, aForFrame);
 | 
						||
 | 
						||
  // Get any border radius, since box-shadow must also have rounded corners if
 | 
						||
  // the frame does.
 | 
						||
  RectCornerRadii borderRadii;
 | 
						||
  const nscoord oneDevPixel = aPresContext->DevPixelsToAppUnits(1);
 | 
						||
  if (hasBorderRadius) {
 | 
						||
    nscoord twipsRadii[8];
 | 
						||
    NS_ASSERTION(
 | 
						||
        aFrameArea.Size() == aForFrame->VisualBorderRectRelativeToSelf().Size(),
 | 
						||
        "unexpected size");
 | 
						||
    nsSize sz = frameRect.Size();
 | 
						||
    hasBorderRadius = aForFrame->GetBorderRadii(sz, sz, Sides(), twipsRadii);
 | 
						||
    if (hasBorderRadius) {
 | 
						||
      ComputePixelRadii(twipsRadii, oneDevPixel, &borderRadii);
 | 
						||
    }
 | 
						||
  }
 | 
						||
 | 
						||
  // We don't show anything that intersects with the frame we're blurring on. So
 | 
						||
  // tell the blurrer not to do unnecessary work there.
 | 
						||
  gfxRect skipGfxRect = ThebesRect(NSRectToRect(frameRect, oneDevPixel));
 | 
						||
  skipGfxRect.Round();
 | 
						||
  bool useSkipGfxRect = true;
 | 
						||
  if (nativeTheme) {
 | 
						||
    // Optimize non-leaf native-themed frames by skipping computing pixels
 | 
						||
    // in the padding-box. We assume the padding-box is going to be painted
 | 
						||
    // opaquely for non-leaf frames.
 | 
						||
    // XXX this may not be a safe assumption; we should make this go away
 | 
						||
    // by optimizing box-shadow drawing more for the cases where we don't have a
 | 
						||
    // skip-rect.
 | 
						||
    useSkipGfxRect = !aForFrame->IsLeaf();
 | 
						||
    nsRect paddingRect =
 | 
						||
        aForFrame->GetPaddingRectRelativeToSelf() + aFrameArea.TopLeft();
 | 
						||
    skipGfxRect = nsLayoutUtils::RectToGfxRect(paddingRect, oneDevPixel);
 | 
						||
  } else if (hasBorderRadius) {
 | 
						||
    skipGfxRect.Deflate(gfxMargin(
 | 
						||
        std::max(borderRadii[C_TL].height, borderRadii[C_TR].height), 0,
 | 
						||
        std::max(borderRadii[C_BL].height, borderRadii[C_BR].height), 0));
 | 
						||
  }
 | 
						||
 | 
						||
  for (const StyleBoxShadow& shadow : Reversed(shadows)) {
 | 
						||
    if (shadow.inset) {
 | 
						||
      continue;
 | 
						||
    }
 | 
						||
 | 
						||
    nsRect shadowRect = frameRect;
 | 
						||
    nsPoint shadowOffset(shadow.base.horizontal.ToAppUnits(),
 | 
						||
                         shadow.base.vertical.ToAppUnits());
 | 
						||
    shadowRect.MoveBy(shadowOffset);
 | 
						||
    nscoord shadowSpread = shadow.spread.ToAppUnits();
 | 
						||
    if (!nativeTheme) {
 | 
						||
      shadowRect.Inflate(shadowSpread);
 | 
						||
    }
 | 
						||
 | 
						||
    // shadowRect won't include the blur, so make an extra rect here that
 | 
						||
    // includes the blur for use in the even-odd rule below.
 | 
						||
    nsRect shadowRectPlusBlur = shadowRect;
 | 
						||
    nscoord blurRadius = shadow.base.blur.ToAppUnits();
 | 
						||
    shadowRectPlusBlur.Inflate(
 | 
						||
        nsContextBoxBlur::GetBlurRadiusMargin(blurRadius, oneDevPixel));
 | 
						||
 | 
						||
    Rect shadowGfxRectPlusBlur = NSRectToRect(shadowRectPlusBlur, oneDevPixel);
 | 
						||
    shadowGfxRectPlusBlur.RoundOut();
 | 
						||
    MaybeSnapToDevicePixels(shadowGfxRectPlusBlur, aDrawTarget, true);
 | 
						||
 | 
						||
    sRGBColor gfxShadowColor = GetShadowColor(shadow.base, aForFrame, aOpacity);
 | 
						||
 | 
						||
    if (nativeTheme) {
 | 
						||
      nsContextBoxBlur blurringArea;
 | 
						||
 | 
						||
      // When getting the widget shape from the native theme, we're going
 | 
						||
      // to draw the widget into the shadow surface to create a mask.
 | 
						||
      // We need to ensure that there actually *is* a shadow surface
 | 
						||
      // and that we're not going to draw directly into aRenderingContext.
 | 
						||
      gfxContext* shadowContext = blurringArea.Init(
 | 
						||
          shadowRect, shadowSpread, blurRadius, oneDevPixel, &aRenderingContext,
 | 
						||
          aDirtyRect, useSkipGfxRect ? &skipGfxRect : nullptr,
 | 
						||
          nsContextBoxBlur::FORCE_MASK);
 | 
						||
      if (!shadowContext) continue;
 | 
						||
 | 
						||
      MOZ_ASSERT(shadowContext == blurringArea.GetContext());
 | 
						||
 | 
						||
      aRenderingContext.Save();
 | 
						||
      aRenderingContext.SetColor(gfxShadowColor);
 | 
						||
 | 
						||
      // Draw the shape of the frame so it can be blurred. Recall how
 | 
						||
      // nsContextBoxBlur doesn't make any temporary surfaces if blur is 0 and
 | 
						||
      // it just returns the original surface? If we have no blur, we're
 | 
						||
      // painting this fill on the actual content surface (aRenderingContext ==
 | 
						||
      // shadowContext) which is why we set up the color and clip before doing
 | 
						||
      // this.
 | 
						||
 | 
						||
      // We don't clip the border-box from the shadow, nor any other box.
 | 
						||
      // We assume that the native theme is going to paint over the shadow.
 | 
						||
 | 
						||
      // Draw the widget shape
 | 
						||
      gfxContextMatrixAutoSaveRestore save(shadowContext);
 | 
						||
      gfxPoint devPixelOffset = nsLayoutUtils::PointToGfxPoint(
 | 
						||
          shadowOffset, aPresContext->AppUnitsPerDevPixel());
 | 
						||
      shadowContext->SetMatrixDouble(
 | 
						||
          shadowContext->CurrentMatrixDouble().PreTranslate(devPixelOffset));
 | 
						||
 | 
						||
      nsRect nativeRect = aDirtyRect;
 | 
						||
      nativeRect.MoveBy(-shadowOffset);
 | 
						||
      nativeRect.IntersectRect(frameRect, nativeRect);
 | 
						||
      aPresContext->Theme()->DrawWidgetBackground(
 | 
						||
          shadowContext, aForFrame, styleDisplay->EffectiveAppearance(),
 | 
						||
          aFrameArea, nativeRect, nsITheme::DrawOverflow::No);
 | 
						||
 | 
						||
      blurringArea.DoPaint();
 | 
						||
      aRenderingContext.Restore();
 | 
						||
    } else {
 | 
						||
      aRenderingContext.Save();
 | 
						||
 | 
						||
      {
 | 
						||
        Rect innerClipRect = NSRectToRect(frameRect, oneDevPixel);
 | 
						||
        if (!MaybeSnapToDevicePixels(innerClipRect, aDrawTarget, true)) {
 | 
						||
          innerClipRect.Round();
 | 
						||
        }
 | 
						||
 | 
						||
        // Clip out the interior of the frame's border edge so that the shadow
 | 
						||
        // is only painted outside that area.
 | 
						||
        RefPtr<PathBuilder> builder =
 | 
						||
            aDrawTarget.CreatePathBuilder(FillRule::FILL_EVEN_ODD);
 | 
						||
        AppendRectToPath(builder, shadowGfxRectPlusBlur);
 | 
						||
        if (hasBorderRadius) {
 | 
						||
          AppendRoundedRectToPath(builder, innerClipRect, borderRadii);
 | 
						||
        } else {
 | 
						||
          AppendRectToPath(builder, innerClipRect);
 | 
						||
        }
 | 
						||
        RefPtr<Path> path = builder->Finish();
 | 
						||
        aRenderingContext.Clip(path);
 | 
						||
      }
 | 
						||
 | 
						||
      // Clip the shadow so that we only get the part that applies to aForFrame.
 | 
						||
      nsRect fragmentClip = shadowRectPlusBlur;
 | 
						||
      Sides skipSides = aForFrame->GetSkipSides();
 | 
						||
      if (!skipSides.IsEmpty()) {
 | 
						||
        if (skipSides.Left()) {
 | 
						||
          nscoord xmost = fragmentClip.XMost();
 | 
						||
          fragmentClip.x = aFrameArea.x;
 | 
						||
          fragmentClip.width = xmost - fragmentClip.x;
 | 
						||
        }
 | 
						||
        if (skipSides.Right()) {
 | 
						||
          nscoord xmost = fragmentClip.XMost();
 | 
						||
          nscoord overflow = xmost - aFrameArea.XMost();
 | 
						||
          if (overflow > 0) {
 | 
						||
            fragmentClip.width -= overflow;
 | 
						||
          }
 | 
						||
        }
 | 
						||
        if (skipSides.Top()) {
 | 
						||
          nscoord ymost = fragmentClip.YMost();
 | 
						||
          fragmentClip.y = aFrameArea.y;
 | 
						||
          fragmentClip.height = ymost - fragmentClip.y;
 | 
						||
        }
 | 
						||
        if (skipSides.Bottom()) {
 | 
						||
          nscoord ymost = fragmentClip.YMost();
 | 
						||
          nscoord overflow = ymost - aFrameArea.YMost();
 | 
						||
          if (overflow > 0) {
 | 
						||
            fragmentClip.height -= overflow;
 | 
						||
          }
 | 
						||
        }
 | 
						||
      }
 | 
						||
      fragmentClip = fragmentClip.Intersect(aDirtyRect);
 | 
						||
      aRenderingContext.Clip(NSRectToSnappedRect(
 | 
						||
          fragmentClip, aForFrame->PresContext()->AppUnitsPerDevPixel(),
 | 
						||
          aDrawTarget));
 | 
						||
 | 
						||
      RectCornerRadii clipRectRadii;
 | 
						||
      if (hasBorderRadius) {
 | 
						||
        Float spreadDistance = Float(shadowSpread / oneDevPixel);
 | 
						||
 | 
						||
        Float borderSizes[4];
 | 
						||
 | 
						||
        borderSizes[eSideLeft] = spreadDistance;
 | 
						||
        borderSizes[eSideTop] = spreadDistance;
 | 
						||
        borderSizes[eSideRight] = spreadDistance;
 | 
						||
        borderSizes[eSideBottom] = spreadDistance;
 | 
						||
 | 
						||
        nsCSSBorderRenderer::ComputeOuterRadii(borderRadii, borderSizes,
 | 
						||
                                               &clipRectRadii);
 | 
						||
      }
 | 
						||
      nsContextBoxBlur::BlurRectangle(
 | 
						||
          &aRenderingContext, shadowRect, oneDevPixel,
 | 
						||
          hasBorderRadius ? &clipRectRadii : nullptr, blurRadius,
 | 
						||
          gfxShadowColor, aDirtyRect, skipGfxRect);
 | 
						||
      aRenderingContext.Restore();
 | 
						||
    }
 | 
						||
  }
 | 
						||
}
 | 
						||
 | 
						||
nsRect nsCSSRendering::GetBoxShadowInnerPaddingRect(nsIFrame* aFrame,
 | 
						||
                                                    const nsRect& aFrameArea) {
 | 
						||
  Sides skipSides = aFrame->GetSkipSides();
 | 
						||
  nsRect frameRect = BoxDecorationRectForBorder(aFrame, aFrameArea, skipSides);
 | 
						||
 | 
						||
  nsRect paddingRect = frameRect;
 | 
						||
  nsMargin border = aFrame->GetUsedBorder();
 | 
						||
  paddingRect.Deflate(border);
 | 
						||
  return paddingRect;
 | 
						||
}
 | 
						||
 | 
						||
bool nsCSSRendering::ShouldPaintBoxShadowInner(nsIFrame* aFrame) {
 | 
						||
  const Span<const StyleBoxShadow> shadows =
 | 
						||
      aFrame->StyleEffects()->mBoxShadow.AsSpan();
 | 
						||
  if (shadows.IsEmpty()) {
 | 
						||
    return false;
 | 
						||
  }
 | 
						||
 | 
						||
  if (aFrame->IsThemed() && aFrame->GetContent() &&
 | 
						||
      !nsContentUtils::IsChromeDoc(aFrame->GetContent()->GetComposedDoc())) {
 | 
						||
    // There's no way of getting hold of a shape corresponding to a
 | 
						||
    // "padding-box" for native-themed widgets, so just don't draw
 | 
						||
    // inner box-shadows for them. But we allow chrome to paint inner
 | 
						||
    // box shadows since chrome can be aware of the platform theme.
 | 
						||
    return false;
 | 
						||
  }
 | 
						||
 | 
						||
  return true;
 | 
						||
}
 | 
						||
 | 
						||
bool nsCSSRendering::GetShadowInnerRadii(nsIFrame* aFrame,
 | 
						||
                                         const nsRect& aFrameArea,
 | 
						||
                                         RectCornerRadii& aOutInnerRadii) {
 | 
						||
  // Get any border radius, since box-shadow must also have rounded corners
 | 
						||
  // if the frame does.
 | 
						||
  nscoord twipsRadii[8];
 | 
						||
  nsRect frameRect =
 | 
						||
      BoxDecorationRectForBorder(aFrame, aFrameArea, aFrame->GetSkipSides());
 | 
						||
  nsSize sz = frameRect.Size();
 | 
						||
  nsMargin border = aFrame->GetUsedBorder();
 | 
						||
  aFrame->GetBorderRadii(sz, sz, Sides(), twipsRadii);
 | 
						||
  const nscoord oneDevPixel = aFrame->PresContext()->DevPixelsToAppUnits(1);
 | 
						||
 | 
						||
  RectCornerRadii borderRadii;
 | 
						||
 | 
						||
  const bool hasBorderRadius =
 | 
						||
      GetBorderRadii(frameRect, aFrameArea, aFrame, borderRadii);
 | 
						||
 | 
						||
  if (hasBorderRadius) {
 | 
						||
    ComputePixelRadii(twipsRadii, oneDevPixel, &borderRadii);
 | 
						||
 | 
						||
    Float borderSizes[4] = {
 | 
						||
        Float(border.top) / oneDevPixel, Float(border.right) / oneDevPixel,
 | 
						||
        Float(border.bottom) / oneDevPixel, Float(border.left) / oneDevPixel};
 | 
						||
    nsCSSBorderRenderer::ComputeInnerRadii(borderRadii, borderSizes,
 | 
						||
                                           &aOutInnerRadii);
 | 
						||
  }
 | 
						||
 | 
						||
  return hasBorderRadius;
 | 
						||
}
 | 
						||
 | 
						||
void nsCSSRendering::PaintBoxShadowInner(nsPresContext* aPresContext,
 | 
						||
                                         gfxContext& aRenderingContext,
 | 
						||
                                         nsIFrame* aForFrame,
 | 
						||
                                         const nsRect& aFrameArea) {
 | 
						||
  if (!ShouldPaintBoxShadowInner(aForFrame)) {
 | 
						||
    return;
 | 
						||
  }
 | 
						||
 | 
						||
  const Span<const StyleBoxShadow> shadows =
 | 
						||
      aForFrame->StyleEffects()->mBoxShadow.AsSpan();
 | 
						||
  NS_ASSERTION(
 | 
						||
      aForFrame->IsFieldSetFrame() || aFrameArea.Size() == aForFrame->GetSize(),
 | 
						||
      "unexpected size");
 | 
						||
 | 
						||
  nsRect paddingRect = GetBoxShadowInnerPaddingRect(aForFrame, aFrameArea);
 | 
						||
 | 
						||
  RectCornerRadii innerRadii;
 | 
						||
  bool hasBorderRadius = GetShadowInnerRadii(aForFrame, aFrameArea, innerRadii);
 | 
						||
 | 
						||
  const nscoord oneDevPixel = aPresContext->DevPixelsToAppUnits(1);
 | 
						||
 | 
						||
  for (const StyleBoxShadow& shadow : Reversed(shadows)) {
 | 
						||
    if (!shadow.inset) {
 | 
						||
      continue;
 | 
						||
    }
 | 
						||
 | 
						||
    // shadowPaintRect: the area to paint on the temp surface
 | 
						||
    // shadowClipRect: the area on the temporary surface within shadowPaintRect
 | 
						||
    //                 that we will NOT paint in
 | 
						||
    nscoord blurRadius = shadow.base.blur.ToAppUnits();
 | 
						||
    nsMargin blurMargin =
 | 
						||
        nsContextBoxBlur::GetBlurRadiusMargin(blurRadius, oneDevPixel);
 | 
						||
    nsRect shadowPaintRect = paddingRect;
 | 
						||
    shadowPaintRect.Inflate(blurMargin);
 | 
						||
 | 
						||
    // Round the spread radius to device pixels (by truncation).
 | 
						||
    // This mostly matches what we do for borders, except that we don't round
 | 
						||
    // up values between zero and one device pixels to one device pixel.
 | 
						||
    // This way of rounding is symmetric around zero, which makes sense for
 | 
						||
    // the spread radius.
 | 
						||
    int32_t spreadDistance = shadow.spread.ToAppUnits() / oneDevPixel;
 | 
						||
    nscoord spreadDistanceAppUnits =
 | 
						||
        aPresContext->DevPixelsToAppUnits(spreadDistance);
 | 
						||
 | 
						||
    nsRect shadowClipRect = paddingRect;
 | 
						||
    shadowClipRect.MoveBy(shadow.base.horizontal.ToAppUnits(),
 | 
						||
                          shadow.base.vertical.ToAppUnits());
 | 
						||
    shadowClipRect.Deflate(spreadDistanceAppUnits, spreadDistanceAppUnits);
 | 
						||
 | 
						||
    Rect shadowClipGfxRect = NSRectToRect(shadowClipRect, oneDevPixel);
 | 
						||
    shadowClipGfxRect.Round();
 | 
						||
 | 
						||
    RectCornerRadii clipRectRadii;
 | 
						||
    if (hasBorderRadius) {
 | 
						||
      // Calculate the radii the inner clipping rect will have
 | 
						||
      Float borderSizes[4] = {0, 0, 0, 0};
 | 
						||
 | 
						||
      // See PaintBoxShadowOuter and bug 514670
 | 
						||
      if (innerRadii[C_TL].width > 0 || innerRadii[C_BL].width > 0) {
 | 
						||
        borderSizes[eSideLeft] = spreadDistance;
 | 
						||
      }
 | 
						||
 | 
						||
      if (innerRadii[C_TL].height > 0 || innerRadii[C_TR].height > 0) {
 | 
						||
        borderSizes[eSideTop] = spreadDistance;
 | 
						||
      }
 | 
						||
 | 
						||
      if (innerRadii[C_TR].width > 0 || innerRadii[C_BR].width > 0) {
 | 
						||
        borderSizes[eSideRight] = spreadDistance;
 | 
						||
      }
 | 
						||
 | 
						||
      if (innerRadii[C_BL].height > 0 || innerRadii[C_BR].height > 0) {
 | 
						||
        borderSizes[eSideBottom] = spreadDistance;
 | 
						||
      }
 | 
						||
 | 
						||
      nsCSSBorderRenderer::ComputeInnerRadii(innerRadii, borderSizes,
 | 
						||
                                             &clipRectRadii);
 | 
						||
    }
 | 
						||
 | 
						||
    // Set the "skip rect" to the area within the frame that we don't paint in,
 | 
						||
    // including after blurring.
 | 
						||
    nsRect skipRect = shadowClipRect;
 | 
						||
    skipRect.Deflate(blurMargin);
 | 
						||
    gfxRect skipGfxRect = nsLayoutUtils::RectToGfxRect(skipRect, oneDevPixel);
 | 
						||
    if (hasBorderRadius) {
 | 
						||
      skipGfxRect.Deflate(gfxMargin(
 | 
						||
          std::max(clipRectRadii[C_TL].height, clipRectRadii[C_TR].height), 0,
 | 
						||
          std::max(clipRectRadii[C_BL].height, clipRectRadii[C_BR].height), 0));
 | 
						||
    }
 | 
						||
 | 
						||
    // When there's a blur radius, gfxAlphaBoxBlur leaves the skiprect area
 | 
						||
    // unchanged. And by construction the gfxSkipRect is not touched by the
 | 
						||
    // rendered shadow (even after blurring), so those pixels must be completely
 | 
						||
    // transparent in the shadow, so drawing them changes nothing.
 | 
						||
    DrawTarget* drawTarget = aRenderingContext.GetDrawTarget();
 | 
						||
 | 
						||
    // Clip the context to the area of the frame's padding rect, so no part of
 | 
						||
    // the shadow is painted outside. Also cut out anything beyond where the
 | 
						||
    // inset shadow will be.
 | 
						||
    Rect shadowGfxRect = NSRectToRect(paddingRect, oneDevPixel);
 | 
						||
    shadowGfxRect.Round();
 | 
						||
 | 
						||
    sRGBColor shadowColor = GetShadowColor(shadow.base, aForFrame, 1.0);
 | 
						||
    aRenderingContext.Save();
 | 
						||
 | 
						||
    // This clips the outside border radius.
 | 
						||
    // clipRectRadii is the border radius inside the inset shadow.
 | 
						||
    if (hasBorderRadius) {
 | 
						||
      RefPtr<Path> roundedRect =
 | 
						||
          MakePathForRoundedRect(*drawTarget, shadowGfxRect, innerRadii);
 | 
						||
      aRenderingContext.Clip(roundedRect);
 | 
						||
    } else {
 | 
						||
      aRenderingContext.Clip(shadowGfxRect);
 | 
						||
    }
 | 
						||
 | 
						||
    nsContextBoxBlur insetBoxBlur;
 | 
						||
    gfxRect destRect =
 | 
						||
        nsLayoutUtils::RectToGfxRect(shadowPaintRect, oneDevPixel);
 | 
						||
    Point shadowOffset(shadow.base.horizontal.ToAppUnits() / oneDevPixel,
 | 
						||
                       shadow.base.vertical.ToAppUnits() / oneDevPixel);
 | 
						||
 | 
						||
    insetBoxBlur.InsetBoxBlur(
 | 
						||
        &aRenderingContext, ToRect(destRect), shadowClipGfxRect, shadowColor,
 | 
						||
        blurRadius, spreadDistanceAppUnits, oneDevPixel, hasBorderRadius,
 | 
						||
        clipRectRadii, ToRect(skipGfxRect), shadowOffset);
 | 
						||
    aRenderingContext.Restore();
 | 
						||
  }
 | 
						||
}
 | 
						||
 | 
						||
/* static */
 | 
						||
nsCSSRendering::PaintBGParams nsCSSRendering::PaintBGParams::ForAllLayers(
 | 
						||
    nsPresContext& aPresCtx, const nsRect& aDirtyRect,
 | 
						||
    const nsRect& aBorderArea, nsIFrame* aFrame, uint32_t aPaintFlags,
 | 
						||
    float aOpacity) {
 | 
						||
  MOZ_ASSERT(aFrame);
 | 
						||
 | 
						||
  PaintBGParams result(aPresCtx, aDirtyRect, aBorderArea, aFrame, aPaintFlags,
 | 
						||
                       -1, CompositionOp::OP_OVER, aOpacity);
 | 
						||
 | 
						||
  return result;
 | 
						||
}
 | 
						||
 | 
						||
/* static */
 | 
						||
nsCSSRendering::PaintBGParams nsCSSRendering::PaintBGParams::ForSingleLayer(
 | 
						||
    nsPresContext& aPresCtx, const nsRect& aDirtyRect,
 | 
						||
    const nsRect& aBorderArea, nsIFrame* aFrame, uint32_t aPaintFlags,
 | 
						||
    int32_t aLayer, CompositionOp aCompositionOp, float aOpacity) {
 | 
						||
  MOZ_ASSERT(aFrame && (aLayer != -1));
 | 
						||
 | 
						||
  PaintBGParams result(aPresCtx, aDirtyRect, aBorderArea, aFrame, aPaintFlags,
 | 
						||
                       aLayer, aCompositionOp, aOpacity);
 | 
						||
 | 
						||
  return result;
 | 
						||
}
 | 
						||
 | 
						||
ImgDrawResult nsCSSRendering::PaintStyleImageLayer(const PaintBGParams& aParams,
 | 
						||
                                                   gfxContext& aRenderingCtx) {
 | 
						||
  AUTO_PROFILER_LABEL("nsCSSRendering::PaintStyleImageLayer", GRAPHICS);
 | 
						||
 | 
						||
  MOZ_ASSERT(aParams.frame,
 | 
						||
             "Frame is expected to be provided to PaintStyleImageLayer");
 | 
						||
 | 
						||
  const ComputedStyle* sc = FindBackground(aParams.frame);
 | 
						||
  if (!sc) {
 | 
						||
    // We don't want to bail out if moz-appearance is set on a root
 | 
						||
    // node. If it has a parent content node, bail because it's not
 | 
						||
    // a root, otherwise keep going in order to let the theme stuff
 | 
						||
    // draw the background. The canvas really should be drawing the
 | 
						||
    // bg, but there's no way to hook that up via css.
 | 
						||
    if (!aParams.frame->StyleDisplay()->HasAppearance()) {
 | 
						||
      return ImgDrawResult::SUCCESS;
 | 
						||
    }
 | 
						||
 | 
						||
    nsIContent* content = aParams.frame->GetContent();
 | 
						||
    if (!content || content->GetParent()) {
 | 
						||
      return ImgDrawResult::SUCCESS;
 | 
						||
    }
 | 
						||
 | 
						||
    sc = aParams.frame->Style();
 | 
						||
  }
 | 
						||
 | 
						||
  return PaintStyleImageLayerWithSC(aParams, aRenderingCtx, sc,
 | 
						||
                                    *aParams.frame->StyleBorder());
 | 
						||
}
 | 
						||
 | 
						||
bool nsCSSRendering::CanBuildWebRenderDisplayItemsForStyleImageLayer(
 | 
						||
    WebRenderLayerManager* aManager, nsPresContext& aPresCtx, nsIFrame* aFrame,
 | 
						||
    const nsStyleBackground* aBackgroundStyle, int32_t aLayer,
 | 
						||
    uint32_t aPaintFlags) {
 | 
						||
  if (!aBackgroundStyle) {
 | 
						||
    return false;
 | 
						||
  }
 | 
						||
 | 
						||
  MOZ_ASSERT(aFrame && aLayer >= 0 &&
 | 
						||
             (uint32_t)aLayer < aBackgroundStyle->mImage.mLayers.Length());
 | 
						||
 | 
						||
  // We cannot draw native themed backgrounds
 | 
						||
  StyleAppearance appearance = aFrame->StyleDisplay()->EffectiveAppearance();
 | 
						||
  if (appearance != StyleAppearance::None) {
 | 
						||
    nsITheme* theme = aPresCtx.Theme();
 | 
						||
    if (theme->ThemeSupportsWidget(&aPresCtx, aFrame, appearance)) {
 | 
						||
      return false;
 | 
						||
    }
 | 
						||
  }
 | 
						||
 | 
						||
  // We only support painting gradients and image for a single style image
 | 
						||
  // layer, and we don't support crop-rects.
 | 
						||
  const auto& styleImage =
 | 
						||
      aBackgroundStyle->mImage.mLayers[aLayer].mImage.FinalImage();
 | 
						||
  if (styleImage.IsImageRequestType()) {
 | 
						||
    imgRequestProxy* requestProxy = styleImage.GetImageRequest();
 | 
						||
    if (!requestProxy) {
 | 
						||
      return false;
 | 
						||
    }
 | 
						||
 | 
						||
    uint32_t imageFlags = imgIContainer::FLAG_NONE;
 | 
						||
    if (aPaintFlags & nsCSSRendering::PAINTBG_SYNC_DECODE_IMAGES) {
 | 
						||
      imageFlags |= imgIContainer::FLAG_SYNC_DECODE;
 | 
						||
    }
 | 
						||
 | 
						||
    nsCOMPtr<imgIContainer> srcImage;
 | 
						||
    requestProxy->GetImage(getter_AddRefs(srcImage));
 | 
						||
    if (!srcImage ||
 | 
						||
        !srcImage->IsImageContainerAvailable(aManager, imageFlags)) {
 | 
						||
      return false;
 | 
						||
    }
 | 
						||
 | 
						||
    return true;
 | 
						||
  }
 | 
						||
 | 
						||
  if (styleImage.IsGradient()) {
 | 
						||
    return true;
 | 
						||
  }
 | 
						||
 | 
						||
  return false;
 | 
						||
}
 | 
						||
 | 
						||
ImgDrawResult nsCSSRendering::BuildWebRenderDisplayItemsForStyleImageLayer(
 | 
						||
    const PaintBGParams& aParams, mozilla::wr::DisplayListBuilder& aBuilder,
 | 
						||
    mozilla::wr::IpcResourceUpdateQueue& aResources,
 | 
						||
    const mozilla::layers::StackingContextHelper& aSc,
 | 
						||
    mozilla::layers::RenderRootStateManager* aManager, nsDisplayItem* aItem) {
 | 
						||
  MOZ_ASSERT(aParams.frame,
 | 
						||
             "Frame is expected to be provided to "
 | 
						||
             "BuildWebRenderDisplayItemsForStyleImageLayer");
 | 
						||
 | 
						||
  ComputedStyle* sc = FindBackground(aParams.frame);
 | 
						||
  if (!sc) {
 | 
						||
    // We don't want to bail out if moz-appearance is set on a root
 | 
						||
    // node. If it has a parent content node, bail because it's not
 | 
						||
    // a root, otherwise keep going in order to let the theme stuff
 | 
						||
    // draw the background. The canvas really should be drawing the
 | 
						||
    // bg, but there's no way to hook that up via css.
 | 
						||
    if (!aParams.frame->StyleDisplay()->HasAppearance()) {
 | 
						||
      return ImgDrawResult::SUCCESS;
 | 
						||
    }
 | 
						||
 | 
						||
    nsIContent* content = aParams.frame->GetContent();
 | 
						||
    if (!content || content->GetParent()) {
 | 
						||
      return ImgDrawResult::SUCCESS;
 | 
						||
    }
 | 
						||
 | 
						||
    sc = aParams.frame->Style();
 | 
						||
  }
 | 
						||
  return BuildWebRenderDisplayItemsForStyleImageLayerWithSC(
 | 
						||
      aParams, aBuilder, aResources, aSc, aManager, aItem, sc,
 | 
						||
      *aParams.frame->StyleBorder());
 | 
						||
}
 | 
						||
 | 
						||
static bool IsOpaqueBorderEdge(const nsStyleBorder& aBorder,
 | 
						||
                               mozilla::Side aSide) {
 | 
						||
  if (aBorder.GetComputedBorder().Side(aSide) == 0) return true;
 | 
						||
  switch (aBorder.GetBorderStyle(aSide)) {
 | 
						||
    case StyleBorderStyle::Solid:
 | 
						||
    case StyleBorderStyle::Groove:
 | 
						||
    case StyleBorderStyle::Ridge:
 | 
						||
    case StyleBorderStyle::Inset:
 | 
						||
    case StyleBorderStyle::Outset:
 | 
						||
      break;
 | 
						||
    default:
 | 
						||
      return false;
 | 
						||
  }
 | 
						||
 | 
						||
  // If we're using a border image, assume it's not fully opaque,
 | 
						||
  // because we may not even have the image loaded at this point, and
 | 
						||
  // even if we did, checking whether the relevant tile is fully
 | 
						||
  // opaque would be too much work.
 | 
						||
  if (!aBorder.mBorderImageSource.IsNone()) {
 | 
						||
    return false;
 | 
						||
  }
 | 
						||
 | 
						||
  StyleColor color = aBorder.BorderColorFor(aSide);
 | 
						||
  // We don't know the foreground color here, so if it's being used
 | 
						||
  // we must assume it might be transparent.
 | 
						||
  return !color.MaybeTransparent();
 | 
						||
}
 | 
						||
 | 
						||
/**
 | 
						||
 * Returns true if all border edges are either missing or opaque.
 | 
						||
 */
 | 
						||
static bool IsOpaqueBorder(const nsStyleBorder& aBorder) {
 | 
						||
  for (const auto i : mozilla::AllPhysicalSides()) {
 | 
						||
    if (!IsOpaqueBorderEdge(aBorder, i)) {
 | 
						||
      return false;
 | 
						||
    }
 | 
						||
  }
 | 
						||
  return true;
 | 
						||
}
 | 
						||
 | 
						||
static inline void SetupDirtyRects(const nsRect& aBGClipArea,
 | 
						||
                                   const nsRect& aCallerDirtyRect,
 | 
						||
                                   nscoord aAppUnitsPerPixel,
 | 
						||
                                   /* OUT: */
 | 
						||
                                   nsRect* aDirtyRect, gfxRect* aDirtyRectGfx) {
 | 
						||
  aDirtyRect->IntersectRect(aBGClipArea, aCallerDirtyRect);
 | 
						||
 | 
						||
  // Compute the Thebes equivalent of the dirtyRect.
 | 
						||
  *aDirtyRectGfx = nsLayoutUtils::RectToGfxRect(*aDirtyRect, aAppUnitsPerPixel);
 | 
						||
  NS_WARNING_ASSERTION(aDirtyRect->IsEmpty() || !aDirtyRectGfx->IsEmpty(),
 | 
						||
                       "converted dirty rect should not be empty");
 | 
						||
  MOZ_ASSERT(!aDirtyRect->IsEmpty() || aDirtyRectGfx->IsEmpty(),
 | 
						||
             "second should be empty if first is");
 | 
						||
}
 | 
						||
 | 
						||
static bool IsSVGStyleGeometryBox(StyleGeometryBox aBox) {
 | 
						||
  return (aBox == StyleGeometryBox::FillBox ||
 | 
						||
          aBox == StyleGeometryBox::StrokeBox ||
 | 
						||
          aBox == StyleGeometryBox::ViewBox);
 | 
						||
}
 | 
						||
 | 
						||
static bool IsHTMLStyleGeometryBox(StyleGeometryBox aBox) {
 | 
						||
  return (aBox == StyleGeometryBox::ContentBox ||
 | 
						||
          aBox == StyleGeometryBox::PaddingBox ||
 | 
						||
          aBox == StyleGeometryBox::BorderBox ||
 | 
						||
          aBox == StyleGeometryBox::MarginBox);
 | 
						||
}
 | 
						||
 | 
						||
static StyleGeometryBox ComputeBoxValueForOrigin(nsIFrame* aForFrame,
 | 
						||
                                                 StyleGeometryBox aBox) {
 | 
						||
  // The mapping for mask-origin is from
 | 
						||
  // https://drafts.fxtf.org/css-masking/#the-mask-origin
 | 
						||
  if (!aForFrame->HasAnyStateBits(NS_FRAME_SVG_LAYOUT)) {
 | 
						||
    // For elements with associated CSS layout box, the values fill-box,
 | 
						||
    // stroke-box and view-box compute to the initial value of mask-origin.
 | 
						||
    if (IsSVGStyleGeometryBox(aBox)) {
 | 
						||
      return StyleGeometryBox::BorderBox;
 | 
						||
    }
 | 
						||
  } else {
 | 
						||
    // For SVG elements without associated CSS layout box, the values
 | 
						||
    // content-box, padding-box, border-box compute to fill-box.
 | 
						||
    if (IsHTMLStyleGeometryBox(aBox)) {
 | 
						||
      return StyleGeometryBox::FillBox;
 | 
						||
    }
 | 
						||
  }
 | 
						||
 | 
						||
  return aBox;
 | 
						||
}
 | 
						||
 | 
						||
static StyleGeometryBox ComputeBoxValueForClip(const nsIFrame* aForFrame,
 | 
						||
                                               StyleGeometryBox aBox) {
 | 
						||
  // The mapping for mask-clip is from
 | 
						||
  // https://drafts.fxtf.org/css-masking/#the-mask-clip
 | 
						||
  if (aForFrame->HasAnyStateBits(NS_FRAME_SVG_LAYOUT)) {
 | 
						||
    // For SVG elements without associated CSS layout box, the used values for
 | 
						||
    // content-box and padding-box compute to fill-box and for border-box and
 | 
						||
    // margin-box compute to stroke-box.
 | 
						||
    switch (aBox) {
 | 
						||
      case StyleGeometryBox::ContentBox:
 | 
						||
      case StyleGeometryBox::PaddingBox:
 | 
						||
        return StyleGeometryBox::FillBox;
 | 
						||
      case StyleGeometryBox::BorderBox:
 | 
						||
      case StyleGeometryBox::MarginBox:
 | 
						||
        return StyleGeometryBox::StrokeBox;
 | 
						||
      default:
 | 
						||
        return aBox;
 | 
						||
    }
 | 
						||
  }
 | 
						||
 | 
						||
  // For elements with associated CSS layout box, the used values for fill-box
 | 
						||
  // compute to content-box and for stroke-box and view-box compute to
 | 
						||
  // border-box.
 | 
						||
  switch (aBox) {
 | 
						||
    case StyleGeometryBox::FillBox:
 | 
						||
      return StyleGeometryBox::ContentBox;
 | 
						||
    case StyleGeometryBox::StrokeBox:
 | 
						||
    case StyleGeometryBox::ViewBox:
 | 
						||
      return StyleGeometryBox::BorderBox;
 | 
						||
    default:
 | 
						||
      return aBox;
 | 
						||
  }
 | 
						||
}
 | 
						||
 | 
						||
bool nsCSSRendering::ImageLayerClipState::IsValid() const {
 | 
						||
  // mDirtyRectInDevPx comes from mDirtyRectInAppUnits. mDirtyRectInAppUnits
 | 
						||
  // can not be empty if mDirtyRectInDevPx is not.
 | 
						||
  if (!mDirtyRectInDevPx.IsEmpty() && mDirtyRectInAppUnits.IsEmpty()) {
 | 
						||
    return false;
 | 
						||
  }
 | 
						||
 | 
						||
  if (mHasRoundedCorners == mClippedRadii.IsEmpty()) {
 | 
						||
    return false;
 | 
						||
  }
 | 
						||
 | 
						||
  return true;
 | 
						||
}
 | 
						||
 | 
						||
/* static */
 | 
						||
void nsCSSRendering::GetImageLayerClip(
 | 
						||
    const nsStyleImageLayers::Layer& aLayer, nsIFrame* aForFrame,
 | 
						||
    const nsStyleBorder& aBorder, const nsRect& aBorderArea,
 | 
						||
    const nsRect& aCallerDirtyRect, bool aWillPaintBorder,
 | 
						||
    nscoord aAppUnitsPerPixel,
 | 
						||
    /* out */ ImageLayerClipState* aClipState) {
 | 
						||
  StyleGeometryBox layerClip = ComputeBoxValueForClip(aForFrame, aLayer.mClip);
 | 
						||
  if (IsSVGStyleGeometryBox(layerClip)) {
 | 
						||
    MOZ_ASSERT(aForFrame->HasAnyStateBits(NS_FRAME_SVG_LAYOUT));
 | 
						||
 | 
						||
    // The coordinate space of clipArea is svg user space.
 | 
						||
    nsRect clipArea =
 | 
						||
        nsLayoutUtils::ComputeSVGReferenceRect(aForFrame, layerClip);
 | 
						||
 | 
						||
    nsRect strokeBox = (layerClip == StyleGeometryBox::StrokeBox)
 | 
						||
                           ? clipArea
 | 
						||
                           : nsLayoutUtils::ComputeSVGReferenceRect(
 | 
						||
                                 aForFrame, StyleGeometryBox::StrokeBox);
 | 
						||
    nsRect clipAreaRelativeToStrokeBox = clipArea - strokeBox.TopLeft();
 | 
						||
 | 
						||
    // aBorderArea is the stroke-box area in a coordinate space defined by
 | 
						||
    // the caller. This coordinate space can be svg user space of aForFrame,
 | 
						||
    // the space of aForFrame's reference-frame, or anything else.
 | 
						||
    //
 | 
						||
    // Which coordinate space chosen for aBorderArea is not matter. What
 | 
						||
    // matter is to ensure returning aClipState->mBGClipArea in the consistent
 | 
						||
    // coordiante space with aBorderArea. So we evaluate the position of clip
 | 
						||
    // area base on the position of aBorderArea here.
 | 
						||
    aClipState->mBGClipArea =
 | 
						||
        clipAreaRelativeToStrokeBox + aBorderArea.TopLeft();
 | 
						||
 | 
						||
    SetupDirtyRects(aClipState->mBGClipArea, aCallerDirtyRect,
 | 
						||
                    aAppUnitsPerPixel, &aClipState->mDirtyRectInAppUnits,
 | 
						||
                    &aClipState->mDirtyRectInDevPx);
 | 
						||
    MOZ_ASSERT(aClipState->IsValid());
 | 
						||
    return;
 | 
						||
  }
 | 
						||
 | 
						||
  if (layerClip == StyleGeometryBox::NoClip) {
 | 
						||
    aClipState->mBGClipArea = aCallerDirtyRect;
 | 
						||
 | 
						||
    SetupDirtyRects(aClipState->mBGClipArea, aCallerDirtyRect,
 | 
						||
                    aAppUnitsPerPixel, &aClipState->mDirtyRectInAppUnits,
 | 
						||
                    &aClipState->mDirtyRectInDevPx);
 | 
						||
    MOZ_ASSERT(aClipState->IsValid());
 | 
						||
    return;
 | 
						||
  }
 | 
						||
 | 
						||
  MOZ_ASSERT(!aForFrame->HasAnyStateBits(NS_FRAME_SVG_LAYOUT));
 | 
						||
 | 
						||
  // Compute the outermost boundary of the area that might be painted.
 | 
						||
  // Same coordinate space as aBorderArea.
 | 
						||
  Sides skipSides = aForFrame->GetSkipSides();
 | 
						||
  nsRect clipBorderArea =
 | 
						||
      BoxDecorationRectForBorder(aForFrame, aBorderArea, skipSides, &aBorder);
 | 
						||
 | 
						||
  bool haveRoundedCorners = false;
 | 
						||
  LayoutFrameType fType = aForFrame->Type();
 | 
						||
  if (fType != LayoutFrameType::TableColGroup &&
 | 
						||
      fType != LayoutFrameType::TableCol &&
 | 
						||
      fType != LayoutFrameType::TableRow &&
 | 
						||
      fType != LayoutFrameType::TableRowGroup) {
 | 
						||
    haveRoundedCorners = GetRadii(aForFrame, aBorder, aBorderArea,
 | 
						||
                                  clipBorderArea, aClipState->mRadii);
 | 
						||
  }
 | 
						||
  bool isSolidBorder = aWillPaintBorder && IsOpaqueBorder(aBorder);
 | 
						||
  if (isSolidBorder && layerClip == StyleGeometryBox::BorderBox) {
 | 
						||
    // If we have rounded corners, we need to inflate the background
 | 
						||
    // drawing area a bit to avoid seams between the border and
 | 
						||
    // background.
 | 
						||
    layerClip = haveRoundedCorners ? StyleGeometryBox::MozAlmostPadding
 | 
						||
                                   : StyleGeometryBox::PaddingBox;
 | 
						||
  }
 | 
						||
 | 
						||
  aClipState->mBGClipArea = clipBorderArea;
 | 
						||
 | 
						||
  if (aForFrame->IsScrollContainerFrame() &&
 | 
						||
      StyleImageLayerAttachment::Local == aLayer.mAttachment) {
 | 
						||
    // As of this writing, this is still in discussion in the CSS Working Group
 | 
						||
    // http://lists.w3.org/Archives/Public/www-style/2013Jul/0250.html
 | 
						||
 | 
						||
    // The rectangle for 'background-clip' scrolls with the content,
 | 
						||
    // but the background is also clipped at a non-scrolling 'padding-box'
 | 
						||
    // like the content. (See below.)
 | 
						||
    // Therefore, only 'content-box' makes a difference here.
 | 
						||
    if (layerClip == StyleGeometryBox::ContentBox) {
 | 
						||
      ScrollContainerFrame* scrollContainerFrame = do_QueryFrame(aForFrame);
 | 
						||
      // Clip at a rectangle attached to the scrolled content.
 | 
						||
      aClipState->mHasAdditionalBGClipArea = true;
 | 
						||
      aClipState->mAdditionalBGClipArea =
 | 
						||
          nsRect(aClipState->mBGClipArea.TopLeft() +
 | 
						||
                     scrollContainerFrame->GetScrolledFrame()->GetPosition()
 | 
						||
                     // For the dir=rtl case:
 | 
						||
                     + scrollContainerFrame->GetScrollRange().TopLeft(),
 | 
						||
                 scrollContainerFrame->GetScrolledRect().Size());
 | 
						||
      nsMargin padding = aForFrame->GetUsedPadding();
 | 
						||
      // padding-bottom is ignored on scrollable frames:
 | 
						||
      // https://bugzilla.mozilla.org/show_bug.cgi?id=748518
 | 
						||
      padding.bottom = 0;
 | 
						||
      padding.ApplySkipSides(skipSides);
 | 
						||
      aClipState->mAdditionalBGClipArea.Deflate(padding);
 | 
						||
    }
 | 
						||
 | 
						||
    // Also clip at a non-scrolling, rounded-corner 'padding-box',
 | 
						||
    // same as the scrolled content because of the 'overflow' property.
 | 
						||
    layerClip = StyleGeometryBox::PaddingBox;
 | 
						||
  }
 | 
						||
 | 
						||
  // See the comment of StyleGeometryBox::Margin.
 | 
						||
  // Hitting this assertion means we decide to turn on margin-box support for
 | 
						||
  // positioned mask from CSS parser and style system. In this case, you
 | 
						||
  // should *inflate* mBGClipArea by the margin returning from
 | 
						||
  // aForFrame->GetUsedMargin() in the code chunk bellow.
 | 
						||
  MOZ_ASSERT(layerClip != StyleGeometryBox::MarginBox,
 | 
						||
             "StyleGeometryBox::MarginBox rendering is not supported yet.\n");
 | 
						||
 | 
						||
  if (layerClip != StyleGeometryBox::BorderBox &&
 | 
						||
      layerClip != StyleGeometryBox::Text) {
 | 
						||
    nsMargin border = aForFrame->GetUsedBorder();
 | 
						||
    if (layerClip == StyleGeometryBox::MozAlmostPadding) {
 | 
						||
      // Reduce |border| by 1px (device pixels) on all sides, if
 | 
						||
      // possible, so that we don't get antialiasing seams between the
 | 
						||
      // {background|mask} and border.
 | 
						||
      border.top = std::max(0, border.top - aAppUnitsPerPixel);
 | 
						||
      border.right = std::max(0, border.right - aAppUnitsPerPixel);
 | 
						||
      border.bottom = std::max(0, border.bottom - aAppUnitsPerPixel);
 | 
						||
      border.left = std::max(0, border.left - aAppUnitsPerPixel);
 | 
						||
    } else if (layerClip != StyleGeometryBox::PaddingBox) {
 | 
						||
      NS_ASSERTION(layerClip == StyleGeometryBox::ContentBox,
 | 
						||
                   "unexpected background-clip");
 | 
						||
      border += aForFrame->GetUsedPadding();
 | 
						||
    }
 | 
						||
    border.ApplySkipSides(skipSides);
 | 
						||
    aClipState->mBGClipArea.Deflate(border);
 | 
						||
 | 
						||
    if (haveRoundedCorners) {
 | 
						||
      nsIFrame::AdjustBorderRadii(aClipState->mRadii, -border);
 | 
						||
    }
 | 
						||
  }
 | 
						||
 | 
						||
  if (haveRoundedCorners) {
 | 
						||
    auto d2a = aForFrame->PresContext()->AppUnitsPerDevPixel();
 | 
						||
    nsCSSRendering::ComputePixelRadii(aClipState->mRadii, d2a,
 | 
						||
                                      &aClipState->mClippedRadii);
 | 
						||
    aClipState->mHasRoundedCorners = !aClipState->mClippedRadii.IsEmpty();
 | 
						||
  }
 | 
						||
 | 
						||
  if (!haveRoundedCorners && aClipState->mHasAdditionalBGClipArea) {
 | 
						||
    // Do the intersection here to account for the fast path(?) below.
 | 
						||
    aClipState->mBGClipArea =
 | 
						||
        aClipState->mBGClipArea.Intersect(aClipState->mAdditionalBGClipArea);
 | 
						||
    aClipState->mHasAdditionalBGClipArea = false;
 | 
						||
  }
 | 
						||
 | 
						||
  SetupDirtyRects(aClipState->mBGClipArea, aCallerDirtyRect, aAppUnitsPerPixel,
 | 
						||
                  &aClipState->mDirtyRectInAppUnits,
 | 
						||
                  &aClipState->mDirtyRectInDevPx);
 | 
						||
 | 
						||
  MOZ_ASSERT(aClipState->IsValid());
 | 
						||
}
 | 
						||
 | 
						||
static void SetupImageLayerClip(nsCSSRendering::ImageLayerClipState& aClipState,
 | 
						||
                                gfxContext* aCtx, nscoord aAppUnitsPerPixel,
 | 
						||
                                gfxContextAutoSaveRestore* aAutoSR) {
 | 
						||
  if (aClipState.mDirtyRectInDevPx.IsEmpty()) {
 | 
						||
    // Our caller won't draw anything under this condition, so no need
 | 
						||
    // to set more up.
 | 
						||
    return;
 | 
						||
  }
 | 
						||
 | 
						||
  if (aClipState.mCustomClip) {
 | 
						||
    // We don't support custom clips and rounded corners, arguably a bug, but
 | 
						||
    // table painting seems to depend on it.
 | 
						||
    return;
 | 
						||
  }
 | 
						||
 | 
						||
  // If we have rounded corners, clip all subsequent drawing to the
 | 
						||
  // rounded rectangle defined by bgArea and bgRadii (we don't know
 | 
						||
  // whether the rounded corners intrude on the dirtyRect or not).
 | 
						||
  // Do not do this if we have a caller-provided clip rect --
 | 
						||
  // as above with bgArea, arguably a bug, but table painting seems
 | 
						||
  // to depend on it.
 | 
						||
 | 
						||
  if (aClipState.mHasAdditionalBGClipArea) {
 | 
						||
    gfxRect bgAreaGfx = nsLayoutUtils::RectToGfxRect(
 | 
						||
        aClipState.mAdditionalBGClipArea, aAppUnitsPerPixel);
 | 
						||
    bgAreaGfx.Round();
 | 
						||
    gfxUtils::ConditionRect(bgAreaGfx);
 | 
						||
 | 
						||
    aAutoSR->EnsureSaved(aCtx);
 | 
						||
    aCtx->SnappedClip(bgAreaGfx);
 | 
						||
  }
 | 
						||
 | 
						||
  if (aClipState.mHasRoundedCorners) {
 | 
						||
    Rect bgAreaGfx = NSRectToRect(aClipState.mBGClipArea, aAppUnitsPerPixel);
 | 
						||
    bgAreaGfx.Round();
 | 
						||
 | 
						||
    if (bgAreaGfx.IsEmpty()) {
 | 
						||
      // I think it's become possible to hit this since
 | 
						||
      // https://hg.mozilla.org/mozilla-central/rev/50e934e4979b landed.
 | 
						||
      NS_WARNING("converted background area should not be empty");
 | 
						||
      // Make our caller not do anything.
 | 
						||
      aClipState.mDirtyRectInDevPx.SizeTo(gfxSize(0.0, 0.0));
 | 
						||
      return;
 | 
						||
    }
 | 
						||
 | 
						||
    aAutoSR->EnsureSaved(aCtx);
 | 
						||
 | 
						||
    RefPtr<Path> roundedRect = MakePathForRoundedRect(
 | 
						||
        *aCtx->GetDrawTarget(), bgAreaGfx, aClipState.mClippedRadii);
 | 
						||
    aCtx->Clip(roundedRect);
 | 
						||
  }
 | 
						||
}
 | 
						||
 | 
						||
static void DrawBackgroundColor(nsCSSRendering::ImageLayerClipState& aClipState,
 | 
						||
                                gfxContext* aCtx, nscoord aAppUnitsPerPixel) {
 | 
						||
  if (aClipState.mDirtyRectInDevPx.IsEmpty()) {
 | 
						||
    // Our caller won't draw anything under this condition, so no need
 | 
						||
    // to set more up.
 | 
						||
    return;
 | 
						||
  }
 | 
						||
 | 
						||
  DrawTarget* drawTarget = aCtx->GetDrawTarget();
 | 
						||
 | 
						||
  // We don't support custom clips and rounded corners, arguably a bug, but
 | 
						||
  // table painting seems to depend on it.
 | 
						||
  if (!aClipState.mHasRoundedCorners || aClipState.mCustomClip) {
 | 
						||
    aCtx->NewPath();
 | 
						||
    aCtx->SnappedRectangle(aClipState.mDirtyRectInDevPx);
 | 
						||
    aCtx->Fill();
 | 
						||
    return;
 | 
						||
  }
 | 
						||
 | 
						||
  Rect bgAreaGfx = NSRectToRect(aClipState.mBGClipArea, aAppUnitsPerPixel);
 | 
						||
  bgAreaGfx.Round();
 | 
						||
 | 
						||
  if (bgAreaGfx.IsEmpty()) {
 | 
						||
    // I think it's become possible to hit this since
 | 
						||
    // https://hg.mozilla.org/mozilla-central/rev/50e934e4979b landed.
 | 
						||
    NS_WARNING("converted background area should not be empty");
 | 
						||
    // Make our caller not do anything.
 | 
						||
    aClipState.mDirtyRectInDevPx.SizeTo(gfxSize(0.0, 0.0));
 | 
						||
    return;
 | 
						||
  }
 | 
						||
 | 
						||
  aCtx->Save();
 | 
						||
  gfxRect dirty = ThebesRect(bgAreaGfx).Intersect(aClipState.mDirtyRectInDevPx);
 | 
						||
 | 
						||
  aCtx->SnappedClip(dirty);
 | 
						||
 | 
						||
  if (aClipState.mHasAdditionalBGClipArea) {
 | 
						||
    gfxRect bgAdditionalAreaGfx = nsLayoutUtils::RectToGfxRect(
 | 
						||
        aClipState.mAdditionalBGClipArea, aAppUnitsPerPixel);
 | 
						||
    bgAdditionalAreaGfx.Round();
 | 
						||
    gfxUtils::ConditionRect(bgAdditionalAreaGfx);
 | 
						||
    aCtx->SnappedClip(bgAdditionalAreaGfx);
 | 
						||
  }
 | 
						||
 | 
						||
  RefPtr<Path> roundedRect =
 | 
						||
      MakePathForRoundedRect(*drawTarget, bgAreaGfx, aClipState.mClippedRadii);
 | 
						||
  aCtx->SetPath(roundedRect);
 | 
						||
  aCtx->Fill();
 | 
						||
  aCtx->Restore();
 | 
						||
}
 | 
						||
 | 
						||
enum class ScrollbarColorKind {
 | 
						||
  Thumb,
 | 
						||
  Track,
 | 
						||
};
 | 
						||
 | 
						||
static Maybe<nscolor> CalcScrollbarColor(nsIFrame* aFrame,
 | 
						||
                                         ScrollbarColorKind aKind) {
 | 
						||
  ComputedStyle* scrollbarStyle = nsLayoutUtils::StyleForScrollbar(aFrame);
 | 
						||
  const auto& colors = scrollbarStyle->StyleUI()->mScrollbarColor;
 | 
						||
  if (colors.IsAuto()) {
 | 
						||
    return Nothing();
 | 
						||
  }
 | 
						||
  const auto& color = aKind == ScrollbarColorKind::Thumb
 | 
						||
                          ? colors.AsColors().thumb
 | 
						||
                          : colors.AsColors().track;
 | 
						||
  return Some(color.CalcColor(*scrollbarStyle));
 | 
						||
}
 | 
						||
 | 
						||
static nscolor GetBackgroundColor(nsIFrame* aFrame,
 | 
						||
                                  const ComputedStyle* aStyle) {
 | 
						||
  switch (aStyle->StyleDisplay()->EffectiveAppearance()) {
 | 
						||
    case StyleAppearance::ScrollbarthumbVertical:
 | 
						||
    case StyleAppearance::ScrollbarthumbHorizontal: {
 | 
						||
      if (Maybe<nscolor> overrideColor =
 | 
						||
              CalcScrollbarColor(aFrame, ScrollbarColorKind::Thumb)) {
 | 
						||
        return *overrideColor;
 | 
						||
      }
 | 
						||
      break;
 | 
						||
    }
 | 
						||
    case StyleAppearance::ScrollbarVertical:
 | 
						||
    case StyleAppearance::ScrollbarHorizontal:
 | 
						||
    case StyleAppearance::Scrollcorner: {
 | 
						||
      if (Maybe<nscolor> overrideColor =
 | 
						||
              CalcScrollbarColor(aFrame, ScrollbarColorKind::Track)) {
 | 
						||
        return *overrideColor;
 | 
						||
      }
 | 
						||
      break;
 | 
						||
    }
 | 
						||
    default:
 | 
						||
      break;
 | 
						||
  }
 | 
						||
  return aStyle->GetVisitedDependentColor(&nsStyleBackground::mBackgroundColor);
 | 
						||
}
 | 
						||
 | 
						||
nscolor nsCSSRendering::DetermineBackgroundColor(nsPresContext* aPresContext,
 | 
						||
                                                 const ComputedStyle* aStyle,
 | 
						||
                                                 nsIFrame* aFrame,
 | 
						||
                                                 bool& aDrawBackgroundImage,
 | 
						||
                                                 bool& aDrawBackgroundColor) {
 | 
						||
  auto shouldPaint = aFrame->ComputeShouldPaintBackground();
 | 
						||
  aDrawBackgroundImage = shouldPaint.mImage;
 | 
						||
  aDrawBackgroundColor = shouldPaint.mColor;
 | 
						||
 | 
						||
  const nsStyleBackground* bg = aStyle->StyleBackground();
 | 
						||
  nscolor bgColor;
 | 
						||
  if (aDrawBackgroundColor) {
 | 
						||
    bgColor = GetBackgroundColor(aFrame, aStyle);
 | 
						||
    if (NS_GET_A(bgColor) == 0) {
 | 
						||
      aDrawBackgroundColor = false;
 | 
						||
    }
 | 
						||
  } else {
 | 
						||
    // If GetBackgroundColorDraw() is false, we are still expected to
 | 
						||
    // draw color in the background of any frame that's not completely
 | 
						||
    // transparent, but we are expected to use white instead of whatever
 | 
						||
    // color was specified.
 | 
						||
    bgColor = NS_RGB(255, 255, 255);
 | 
						||
    if (aDrawBackgroundImage || !bg->IsTransparent(aStyle)) {
 | 
						||
      aDrawBackgroundColor = true;
 | 
						||
    } else {
 | 
						||
      bgColor = NS_RGBA(0, 0, 0, 0);
 | 
						||
    }
 | 
						||
  }
 | 
						||
 | 
						||
  // We can skip painting the background color if a background image is opaque.
 | 
						||
  nsStyleImageLayers::Repeat repeat = bg->BottomLayer().mRepeat;
 | 
						||
  bool xFullRepeat = repeat.mXRepeat == StyleImageLayerRepeat::Repeat ||
 | 
						||
                     repeat.mXRepeat == StyleImageLayerRepeat::Round;
 | 
						||
  bool yFullRepeat = repeat.mYRepeat == StyleImageLayerRepeat::Repeat ||
 | 
						||
                     repeat.mYRepeat == StyleImageLayerRepeat::Round;
 | 
						||
  if (aDrawBackgroundColor && xFullRepeat && yFullRepeat &&
 | 
						||
      bg->BottomLayer().mImage.IsOpaque() &&
 | 
						||
      bg->BottomLayer().mBlendMode == StyleBlend::Normal) {
 | 
						||
    aDrawBackgroundColor = false;
 | 
						||
  }
 | 
						||
 | 
						||
  return bgColor;
 | 
						||
}
 | 
						||
 | 
						||
static CompositionOp DetermineCompositionOp(
 | 
						||
    const nsCSSRendering::PaintBGParams& aParams,
 | 
						||
    const nsStyleImageLayers& aLayers, uint32_t aLayerIndex) {
 | 
						||
  if (aParams.layer >= 0) {
 | 
						||
    // When drawing a single layer, use the specified composition op.
 | 
						||
    return aParams.compositionOp;
 | 
						||
  }
 | 
						||
 | 
						||
  const nsStyleImageLayers::Layer& layer = aLayers.mLayers[aLayerIndex];
 | 
						||
  // When drawing all layers, get the compositon op from each image layer.
 | 
						||
  if (aParams.paintFlags & nsCSSRendering::PAINTBG_MASK_IMAGE) {
 | 
						||
    // Always using OP_OVER mode while drawing the bottom mask layer.
 | 
						||
    if (aLayerIndex == (aLayers.mImageCount - 1)) {
 | 
						||
      return CompositionOp::OP_OVER;
 | 
						||
    }
 | 
						||
 | 
						||
    return nsCSSRendering::GetGFXCompositeMode(layer.mComposite);
 | 
						||
  }
 | 
						||
 | 
						||
  return nsCSSRendering::GetGFXBlendMode(layer.mBlendMode);
 | 
						||
}
 | 
						||
 | 
						||
ImgDrawResult nsCSSRendering::PaintStyleImageLayerWithSC(
 | 
						||
    const PaintBGParams& aParams, gfxContext& aRenderingCtx,
 | 
						||
    const ComputedStyle* aBackgroundSC, const nsStyleBorder& aBorder) {
 | 
						||
  MOZ_ASSERT(aParams.frame,
 | 
						||
             "Frame is expected to be provided to PaintStyleImageLayerWithSC");
 | 
						||
 | 
						||
  // If we're drawing all layers, aCompositonOp is ignored, so make sure that
 | 
						||
  // it was left at its default value.
 | 
						||
  MOZ_ASSERT(aParams.layer != -1 ||
 | 
						||
             aParams.compositionOp == CompositionOp::OP_OVER);
 | 
						||
 | 
						||
  // Check to see if we have an appearance defined.  If so, we let the theme
 | 
						||
  // renderer draw the background and bail out.
 | 
						||
  // XXXzw this ignores aParams.bgClipRect.
 | 
						||
  StyleAppearance appearance =
 | 
						||
      aParams.frame->StyleDisplay()->EffectiveAppearance();
 | 
						||
  if (appearance != StyleAppearance::None) {
 | 
						||
    nsITheme* theme = aParams.presCtx.Theme();
 | 
						||
    if (theme->ThemeSupportsWidget(&aParams.presCtx, aParams.frame,
 | 
						||
                                   appearance)) {
 | 
						||
      nsRect drawing(aParams.borderArea);
 | 
						||
      theme->GetWidgetOverflow(aParams.presCtx.DeviceContext(), aParams.frame,
 | 
						||
                               appearance, &drawing);
 | 
						||
      drawing.IntersectRect(drawing, aParams.dirtyRect);
 | 
						||
      theme->DrawWidgetBackground(&aRenderingCtx, aParams.frame, appearance,
 | 
						||
                                  aParams.borderArea, drawing);
 | 
						||
      return ImgDrawResult::SUCCESS;
 | 
						||
    }
 | 
						||
  }
 | 
						||
 | 
						||
  // For canvas frames (in the CSS sense) we draw the background color using
 | 
						||
  // a solid color item that gets added in nsLayoutUtils::PaintFrame,
 | 
						||
  // or nsSubDocumentFrame::BuildDisplayList (bug 488242). (The solid
 | 
						||
  // color may be moved into nsDisplayCanvasBackground by
 | 
						||
  // PresShell::AddCanvasBackgroundColorItem(), and painted by
 | 
						||
  // nsDisplayCanvasBackground directly.) Either way we don't need to
 | 
						||
  // paint the background color here.
 | 
						||
  bool isCanvasFrame = aParams.frame->IsCanvasFrame();
 | 
						||
  const bool paintMask = aParams.paintFlags & PAINTBG_MASK_IMAGE;
 | 
						||
 | 
						||
  // Determine whether we are drawing background images and/or
 | 
						||
  // background colors.
 | 
						||
  bool drawBackgroundImage = true;
 | 
						||
  bool drawBackgroundColor = !paintMask;
 | 
						||
  nscolor bgColor = NS_RGBA(0, 0, 0, 0);
 | 
						||
  if (!paintMask) {
 | 
						||
    bgColor =
 | 
						||
        DetermineBackgroundColor(&aParams.presCtx, aBackgroundSC, aParams.frame,
 | 
						||
                                 drawBackgroundImage, drawBackgroundColor);
 | 
						||
  }
 | 
						||
 | 
						||
  // Masks shouldn't be suppressed for print.
 | 
						||
  MOZ_ASSERT_IF(paintMask, drawBackgroundImage);
 | 
						||
 | 
						||
  const nsStyleImageLayers& layers =
 | 
						||
      paintMask ? aBackgroundSC->StyleSVGReset()->mMask
 | 
						||
                : aBackgroundSC->StyleBackground()->mImage;
 | 
						||
  // If we're drawing a specific layer, we don't want to draw the
 | 
						||
  // background color.
 | 
						||
  if (drawBackgroundColor && aParams.layer >= 0) {
 | 
						||
    drawBackgroundColor = false;
 | 
						||
  }
 | 
						||
 | 
						||
  // At this point, drawBackgroundImage and drawBackgroundColor are
 | 
						||
  // true if and only if we are actually supposed to paint an image or
 | 
						||
  // color into aDirtyRect, respectively.
 | 
						||
  if (!drawBackgroundImage && !drawBackgroundColor) {
 | 
						||
    return ImgDrawResult::SUCCESS;
 | 
						||
  }
 | 
						||
 | 
						||
  // The 'bgClipArea' (used only by the image tiling logic, far below)
 | 
						||
  // is the caller-provided aParams.bgClipRect if any, or else the area
 | 
						||
  // determined by the value of 'background-clip' in
 | 
						||
  // SetupCurrentBackgroundClip.  (Arguably it should be the
 | 
						||
  // intersection, but that breaks the table painter -- in particular,
 | 
						||
  // taking the intersection breaks reftests/bugs/403249-1[ab].)
 | 
						||
  nscoord appUnitsPerPixel = aParams.presCtx.AppUnitsPerDevPixel();
 | 
						||
  ImageLayerClipState clipState;
 | 
						||
  if (aParams.bgClipRect) {
 | 
						||
    clipState.mBGClipArea = *aParams.bgClipRect;
 | 
						||
    clipState.mCustomClip = true;
 | 
						||
    clipState.mHasRoundedCorners = false;
 | 
						||
    SetupDirtyRects(clipState.mBGClipArea, aParams.dirtyRect, appUnitsPerPixel,
 | 
						||
                    &clipState.mDirtyRectInAppUnits,
 | 
						||
                    &clipState.mDirtyRectInDevPx);
 | 
						||
  } else {
 | 
						||
    GetImageLayerClip(layers.BottomLayer(), aParams.frame, aBorder,
 | 
						||
                      aParams.borderArea, aParams.dirtyRect,
 | 
						||
                      (aParams.paintFlags & PAINTBG_WILL_PAINT_BORDER),
 | 
						||
                      appUnitsPerPixel, &clipState);
 | 
						||
  }
 | 
						||
 | 
						||
  // If we might be using a background color, go ahead and set it now.
 | 
						||
  if (drawBackgroundColor && !isCanvasFrame) {
 | 
						||
    aRenderingCtx.SetColor(sRGBColor::FromABGR(bgColor));
 | 
						||
  }
 | 
						||
 | 
						||
  // If there is no background image, draw a color.  (If there is
 | 
						||
  // neither a background image nor a color, we wouldn't have gotten
 | 
						||
  // this far.)
 | 
						||
  if (!drawBackgroundImage) {
 | 
						||
    if (!isCanvasFrame) {
 | 
						||
      DrawBackgroundColor(clipState, &aRenderingCtx, appUnitsPerPixel);
 | 
						||
    }
 | 
						||
    return ImgDrawResult::SUCCESS;
 | 
						||
  }
 | 
						||
 | 
						||
  if (layers.mImageCount < 1) {
 | 
						||
    // Return if there are no background layers, all work from this point
 | 
						||
    // onwards happens iteratively on these.
 | 
						||
    return ImgDrawResult::SUCCESS;
 | 
						||
  }
 | 
						||
 | 
						||
  MOZ_ASSERT((aParams.layer < 0) ||
 | 
						||
             (layers.mImageCount > uint32_t(aParams.layer)));
 | 
						||
 | 
						||
  // The background color is rendered over the entire dirty area,
 | 
						||
  // even if the image isn't.
 | 
						||
  if (drawBackgroundColor && !isCanvasFrame) {
 | 
						||
    DrawBackgroundColor(clipState, &aRenderingCtx, appUnitsPerPixel);
 | 
						||
  }
 | 
						||
 | 
						||
  // Compute the outermost boundary of the area that might be painted.
 | 
						||
  // Same coordinate space as aParams.borderArea & aParams.bgClipRect.
 | 
						||
  Sides skipSides = aParams.frame->GetSkipSides();
 | 
						||
  nsRect paintBorderArea = BoxDecorationRectForBackground(
 | 
						||
      aParams.frame, aParams.borderArea, skipSides, &aBorder);
 | 
						||
  nsRect clipBorderArea = BoxDecorationRectForBorder(
 | 
						||
      aParams.frame, aParams.borderArea, skipSides, &aBorder);
 | 
						||
 | 
						||
  ImgDrawResult result = ImgDrawResult::SUCCESS;
 | 
						||
  StyleGeometryBox currentBackgroundClip = StyleGeometryBox::BorderBox;
 | 
						||
  const bool drawAllLayers = (aParams.layer < 0);
 | 
						||
  uint32_t count = drawAllLayers
 | 
						||
                       ? layers.mImageCount  // iterate all image layers.
 | 
						||
                       : layers.mImageCount -
 | 
						||
                             aParams.layer;  // iterate from the bottom layer to
 | 
						||
                                             // the 'aParams.layer-th' layer.
 | 
						||
  NS_FOR_VISIBLE_IMAGE_LAYERS_BACK_TO_FRONT_WITH_RANGE(
 | 
						||
      i, layers, layers.mImageCount - 1, count) {
 | 
						||
    // NOTE: no Save() yet, we do that later by calling autoSR.EnsureSaved(ctx)
 | 
						||
    // in the cases we need it.
 | 
						||
    gfxContextAutoSaveRestore autoSR;
 | 
						||
    const nsStyleImageLayers::Layer& layer = layers.mLayers[i];
 | 
						||
 | 
						||
    ImageLayerClipState currentLayerClipState = clipState;
 | 
						||
    if (!aParams.bgClipRect) {
 | 
						||
      bool isBottomLayer = (i == layers.mImageCount - 1);
 | 
						||
      if (currentBackgroundClip != layer.mClip || isBottomLayer) {
 | 
						||
        currentBackgroundClip = layer.mClip;
 | 
						||
        if (!isBottomLayer) {
 | 
						||
          currentLayerClipState = {};
 | 
						||
          // For the bottom layer, we already called GetImageLayerClip above
 | 
						||
          // and it stored its results in clipState.
 | 
						||
          GetImageLayerClip(layer, aParams.frame, aBorder, aParams.borderArea,
 | 
						||
                            aParams.dirtyRect,
 | 
						||
                            (aParams.paintFlags & PAINTBG_WILL_PAINT_BORDER),
 | 
						||
                            appUnitsPerPixel, ¤tLayerClipState);
 | 
						||
        }
 | 
						||
        SetupImageLayerClip(currentLayerClipState, &aRenderingCtx,
 | 
						||
                            appUnitsPerPixel, &autoSR);
 | 
						||
        if (!clipBorderArea.IsEqualEdges(aParams.borderArea)) {
 | 
						||
          // We're drawing the background for the joined continuation boxes
 | 
						||
          // so we need to clip that to the slice that we want for this
 | 
						||
          // frame.
 | 
						||
          gfxRect clip = nsLayoutUtils::RectToGfxRect(aParams.borderArea,
 | 
						||
                                                      appUnitsPerPixel);
 | 
						||
          autoSR.EnsureSaved(&aRenderingCtx);
 | 
						||
          aRenderingCtx.SnappedClip(clip);
 | 
						||
        }
 | 
						||
      }
 | 
						||
    }
 | 
						||
 | 
						||
    // Skip the following layer preparing and painting code if the current
 | 
						||
    // layer is not selected for drawing.
 | 
						||
    if (aParams.layer >= 0 && i != (uint32_t)aParams.layer) {
 | 
						||
      continue;
 | 
						||
    }
 | 
						||
    nsBackgroundLayerState state = PrepareImageLayer(
 | 
						||
        &aParams.presCtx, aParams.frame, aParams.paintFlags, paintBorderArea,
 | 
						||
        currentLayerClipState.mBGClipArea, layer, nullptr);
 | 
						||
    result &= state.mImageRenderer.PrepareResult();
 | 
						||
 | 
						||
    // Skip the layer painting code if we found the dirty region is empty.
 | 
						||
    if (currentLayerClipState.mDirtyRectInDevPx.IsEmpty()) {
 | 
						||
      continue;
 | 
						||
    }
 | 
						||
 | 
						||
    if (!state.mFillArea.IsEmpty()) {
 | 
						||
      CompositionOp co = DetermineCompositionOp(aParams, layers, i);
 | 
						||
      if (co != CompositionOp::OP_OVER) {
 | 
						||
        NS_ASSERTION(aRenderingCtx.CurrentOp() == CompositionOp::OP_OVER,
 | 
						||
                     "It is assumed the initial op is OP_OVER, when it is "
 | 
						||
                     "restored later");
 | 
						||
        aRenderingCtx.SetOp(co);
 | 
						||
      }
 | 
						||
 | 
						||
      result &= state.mImageRenderer.DrawLayer(
 | 
						||
          &aParams.presCtx, aRenderingCtx, state.mDestArea, state.mFillArea,
 | 
						||
          state.mAnchor + paintBorderArea.TopLeft(),
 | 
						||
          currentLayerClipState.mDirtyRectInAppUnits, state.mRepeatSize,
 | 
						||
          aParams.opacity);
 | 
						||
 | 
						||
      if (co != CompositionOp::OP_OVER) {
 | 
						||
        aRenderingCtx.SetOp(CompositionOp::OP_OVER);
 | 
						||
      }
 | 
						||
    }
 | 
						||
  }
 | 
						||
 | 
						||
  return result;
 | 
						||
}
 | 
						||
 | 
						||
ImgDrawResult
 | 
						||
nsCSSRendering::BuildWebRenderDisplayItemsForStyleImageLayerWithSC(
 | 
						||
    const PaintBGParams& aParams, mozilla::wr::DisplayListBuilder& aBuilder,
 | 
						||
    mozilla::wr::IpcResourceUpdateQueue& aResources,
 | 
						||
    const mozilla::layers::StackingContextHelper& aSc,
 | 
						||
    mozilla::layers::RenderRootStateManager* aManager, nsDisplayItem* aItem,
 | 
						||
    ComputedStyle* aBackgroundSC, const nsStyleBorder& aBorder) {
 | 
						||
  MOZ_ASSERT(!(aParams.paintFlags & PAINTBG_MASK_IMAGE));
 | 
						||
 | 
						||
  nscoord appUnitsPerPixel = aParams.presCtx.AppUnitsPerDevPixel();
 | 
						||
  ImageLayerClipState clipState;
 | 
						||
 | 
						||
  clipState.mBGClipArea = *aParams.bgClipRect;
 | 
						||
  clipState.mCustomClip = true;
 | 
						||
  clipState.mHasRoundedCorners = false;
 | 
						||
  SetupDirtyRects(clipState.mBGClipArea, aParams.dirtyRect, appUnitsPerPixel,
 | 
						||
                  &clipState.mDirtyRectInAppUnits,
 | 
						||
                  &clipState.mDirtyRectInDevPx);
 | 
						||
 | 
						||
  // Compute the outermost boundary of the area that might be painted.
 | 
						||
  // Same coordinate space as aParams.borderArea & aParams.bgClipRect.
 | 
						||
  Sides skipSides = aParams.frame->GetSkipSides();
 | 
						||
  nsRect paintBorderArea = BoxDecorationRectForBackground(
 | 
						||
      aParams.frame, aParams.borderArea, skipSides, &aBorder);
 | 
						||
 | 
						||
  const nsStyleImageLayers& layers = aBackgroundSC->StyleBackground()->mImage;
 | 
						||
  const nsStyleImageLayers::Layer& layer = layers.mLayers[aParams.layer];
 | 
						||
 | 
						||
  // Skip the following layer painting code if we found the dirty region is
 | 
						||
  // empty or the current layer is not selected for drawing.
 | 
						||
  if (clipState.mDirtyRectInDevPx.IsEmpty()) {
 | 
						||
    return ImgDrawResult::SUCCESS;
 | 
						||
  }
 | 
						||
 | 
						||
  ImgDrawResult result = ImgDrawResult::SUCCESS;
 | 
						||
  nsBackgroundLayerState state =
 | 
						||
      PrepareImageLayer(&aParams.presCtx, aParams.frame, aParams.paintFlags,
 | 
						||
                        paintBorderArea, clipState.mBGClipArea, layer, nullptr);
 | 
						||
  result &= state.mImageRenderer.PrepareResult();
 | 
						||
 | 
						||
  if (!state.mFillArea.IsEmpty()) {
 | 
						||
    result &= state.mImageRenderer.BuildWebRenderDisplayItemsForLayer(
 | 
						||
        &aParams.presCtx, aBuilder, aResources, aSc, aManager, aItem,
 | 
						||
        state.mDestArea, state.mFillArea,
 | 
						||
        state.mAnchor + paintBorderArea.TopLeft(),
 | 
						||
        clipState.mDirtyRectInAppUnits, state.mRepeatSize, aParams.opacity);
 | 
						||
  }
 | 
						||
 | 
						||
  return result;
 | 
						||
}
 | 
						||
 | 
						||
nsRect nsCSSRendering::ComputeImageLayerPositioningArea(
 | 
						||
    nsPresContext* aPresContext, nsIFrame* aForFrame, const nsRect& aBorderArea,
 | 
						||
    const nsStyleImageLayers::Layer& aLayer, nsIFrame** aAttachedToFrame,
 | 
						||
    bool* aOutIsTransformedFixed) {
 | 
						||
  // Compute {background|mask} origin area relative to aBorderArea now as we
 | 
						||
  // may need  it to compute the effective image size for a CSS gradient.
 | 
						||
  nsRect positionArea;
 | 
						||
 | 
						||
  StyleGeometryBox layerOrigin =
 | 
						||
      ComputeBoxValueForOrigin(aForFrame, aLayer.mOrigin);
 | 
						||
 | 
						||
  if (IsSVGStyleGeometryBox(layerOrigin)) {
 | 
						||
    MOZ_ASSERT(aForFrame->HasAnyStateBits(NS_FRAME_SVG_LAYOUT));
 | 
						||
    *aAttachedToFrame = aForFrame;
 | 
						||
 | 
						||
    positionArea =
 | 
						||
        nsLayoutUtils::ComputeSVGReferenceRect(aForFrame, layerOrigin);
 | 
						||
 | 
						||
    nsPoint toStrokeBoxOffset = nsPoint(0, 0);
 | 
						||
    if (layerOrigin != StyleGeometryBox::StrokeBox) {
 | 
						||
      nsRect strokeBox = nsLayoutUtils::ComputeSVGReferenceRect(
 | 
						||
          aForFrame, StyleGeometryBox::StrokeBox);
 | 
						||
      toStrokeBoxOffset = positionArea.TopLeft() - strokeBox.TopLeft();
 | 
						||
    }
 | 
						||
 | 
						||
    // For SVG frames, the return value is relative to the stroke box
 | 
						||
    return nsRect(toStrokeBoxOffset, positionArea.Size());
 | 
						||
  }
 | 
						||
 | 
						||
  MOZ_ASSERT(!aForFrame->HasAnyStateBits(NS_FRAME_SVG_LAYOUT));
 | 
						||
 | 
						||
  LayoutFrameType frameType = aForFrame->Type();
 | 
						||
  nsIFrame* geometryFrame = aForFrame;
 | 
						||
  if (MOZ_UNLIKELY(frameType == LayoutFrameType::ScrollContainer &&
 | 
						||
                   StyleImageLayerAttachment::Local == aLayer.mAttachment)) {
 | 
						||
    ScrollContainerFrame* scrollContainerFrame = do_QueryFrame(aForFrame);
 | 
						||
    positionArea =
 | 
						||
        nsRect(scrollContainerFrame->GetScrolledFrame()->GetPosition()
 | 
						||
                   // For the dir=rtl case:
 | 
						||
                   + scrollContainerFrame->GetScrollRange().TopLeft(),
 | 
						||
               scrollContainerFrame->GetScrolledRect().Size());
 | 
						||
    // The ScrolledRect’s size does not include the borders or scrollbars,
 | 
						||
    // reverse the handling of background-origin
 | 
						||
    // compared to the common case below.
 | 
						||
    if (layerOrigin == StyleGeometryBox::BorderBox) {
 | 
						||
      nsMargin border = geometryFrame->GetUsedBorder();
 | 
						||
      border.ApplySkipSides(geometryFrame->GetSkipSides());
 | 
						||
      positionArea.Inflate(border);
 | 
						||
      positionArea.Inflate(scrollContainerFrame->GetActualScrollbarSizes());
 | 
						||
    } else if (layerOrigin != StyleGeometryBox::PaddingBox) {
 | 
						||
      nsMargin padding = geometryFrame->GetUsedPadding();
 | 
						||
      padding.ApplySkipSides(geometryFrame->GetSkipSides());
 | 
						||
      positionArea.Deflate(padding);
 | 
						||
      NS_ASSERTION(layerOrigin == StyleGeometryBox::ContentBox,
 | 
						||
                   "unknown background-origin value");
 | 
						||
    }
 | 
						||
    *aAttachedToFrame = aForFrame;
 | 
						||
    return positionArea;
 | 
						||
  }
 | 
						||
 | 
						||
  if (MOZ_UNLIKELY(frameType == LayoutFrameType::Canvas)) {
 | 
						||
    geometryFrame = aForFrame->PrincipalChildList().FirstChild();
 | 
						||
    // geometryFrame might be null if this canvas is a page created
 | 
						||
    // as an overflow container (e.g. the in-flow content has already
 | 
						||
    // finished and this page only displays the continuations of
 | 
						||
    // absolutely positioned content).
 | 
						||
    if (geometryFrame) {
 | 
						||
      positionArea =
 | 
						||
          nsPlaceholderFrame::GetRealFrameFor(geometryFrame)->GetRect();
 | 
						||
    }
 | 
						||
  } else {
 | 
						||
    positionArea = nsRect(nsPoint(0, 0), aBorderArea.Size());
 | 
						||
  }
 | 
						||
 | 
						||
  // See the comment of StyleGeometryBox::MarginBox.
 | 
						||
  // Hitting this assertion means we decide to turn on margin-box support for
 | 
						||
  // positioned mask from CSS parser and style system. In this case, you
 | 
						||
  // should *inflate* positionArea by the margin returning from
 | 
						||
  // geometryFrame->GetUsedMargin() in the code chunk bellow.
 | 
						||
  MOZ_ASSERT(aLayer.mOrigin != StyleGeometryBox::MarginBox,
 | 
						||
             "StyleGeometryBox::MarginBox rendering is not supported yet.\n");
 | 
						||
 | 
						||
  // {background|mask} images are tiled over the '{background|mask}-clip' area
 | 
						||
  // but the origin of the tiling is based on the '{background|mask}-origin'
 | 
						||
  // area.
 | 
						||
  if (layerOrigin != StyleGeometryBox::BorderBox && geometryFrame) {
 | 
						||
    nsMargin border = geometryFrame->GetUsedBorder();
 | 
						||
    if (layerOrigin != StyleGeometryBox::PaddingBox) {
 | 
						||
      border += geometryFrame->GetUsedPadding();
 | 
						||
      NS_ASSERTION(layerOrigin == StyleGeometryBox::ContentBox,
 | 
						||
                   "unknown background-origin value");
 | 
						||
    }
 | 
						||
    positionArea.Deflate(border);
 | 
						||
  }
 | 
						||
 | 
						||
  nsIFrame* attachedToFrame = aForFrame;
 | 
						||
  if (StyleImageLayerAttachment::Fixed == aLayer.mAttachment) {
 | 
						||
    // If it's a fixed background attachment, then the image is placed
 | 
						||
    // relative to the viewport, which is the area of the root frame
 | 
						||
    // in a screen context or the page content frame in a print context.
 | 
						||
    attachedToFrame = aPresContext->PresShell()->GetRootFrame();
 | 
						||
    NS_ASSERTION(attachedToFrame, "no root frame");
 | 
						||
    nsIFrame* pageContentFrame = nullptr;
 | 
						||
    if (aPresContext->IsPaginated()) {
 | 
						||
      pageContentFrame = nsLayoutUtils::GetClosestFrameOfType(
 | 
						||
          aForFrame, LayoutFrameType::PageContent);
 | 
						||
      if (pageContentFrame) {
 | 
						||
        attachedToFrame = pageContentFrame;
 | 
						||
      }
 | 
						||
      // else this is an embedded shell and its root frame is what we want
 | 
						||
    }
 | 
						||
 | 
						||
    // If the background is affected by a transform, treat is as if it
 | 
						||
    // wasn't fixed.
 | 
						||
    if (nsLayoutUtils::IsTransformed(aForFrame, attachedToFrame)) {
 | 
						||
      attachedToFrame = aForFrame;
 | 
						||
      *aOutIsTransformedFixed = true;
 | 
						||
    } else {
 | 
						||
      // Set the background positioning area to the viewport's area
 | 
						||
      // (relative to aForFrame)
 | 
						||
      positionArea = nsRect(-aForFrame->GetOffsetTo(attachedToFrame),
 | 
						||
                            attachedToFrame->GetSize());
 | 
						||
 | 
						||
      if (!pageContentFrame) {
 | 
						||
        // Subtract the size of scrollbars.
 | 
						||
        if (ScrollContainerFrame* sf =
 | 
						||
                aPresContext->PresShell()->GetRootScrollContainerFrame()) {
 | 
						||
          nsMargin scrollbars = sf->GetActualScrollbarSizes();
 | 
						||
          positionArea.Deflate(scrollbars);
 | 
						||
        }
 | 
						||
      }
 | 
						||
 | 
						||
      // If we have the dynamic toolbar, we need to expand the image area to
 | 
						||
      // include the region under the dynamic toolbar, otherwise we will see a
 | 
						||
      // blank space under the toolbar.
 | 
						||
      if (aPresContext->IsRootContentDocumentCrossProcess() &&
 | 
						||
          aPresContext->HasDynamicToolbar()) {
 | 
						||
        positionArea.SizeTo(nsLayoutUtils::ExpandHeightForDynamicToolbar(
 | 
						||
            aPresContext, positionArea.Size()));
 | 
						||
      }
 | 
						||
    }
 | 
						||
  }
 | 
						||
  *aAttachedToFrame = attachedToFrame;
 | 
						||
 | 
						||
  return positionArea;
 | 
						||
}
 | 
						||
 | 
						||
/* static */
 | 
						||
nscoord nsCSSRendering::ComputeRoundedSize(nscoord aCurrentSize,
 | 
						||
                                           nscoord aPositioningSize) {
 | 
						||
  float repeatCount = NS_roundf(float(aPositioningSize) / float(aCurrentSize));
 | 
						||
  if (repeatCount < 1.0f) {
 | 
						||
    return aPositioningSize;
 | 
						||
  }
 | 
						||
  return nscoord(NS_lround(float(aPositioningSize) / repeatCount));
 | 
						||
}
 | 
						||
 | 
						||
// Apply the CSS image sizing algorithm as it applies to background images.
 | 
						||
// See http://www.w3.org/TR/css3-background/#the-background-size .
 | 
						||
// aIntrinsicSize is the size that the background image 'would like to be'.
 | 
						||
// It can be found by calling nsImageRenderer::ComputeIntrinsicSize.
 | 
						||
static nsSize ComputeDrawnSizeForBackground(
 | 
						||
    const CSSSizeOrRatio& aIntrinsicSize, const nsSize& aBgPositioningArea,
 | 
						||
    const StyleBackgroundSize& aLayerSize, StyleImageLayerRepeat aXRepeat,
 | 
						||
    StyleImageLayerRepeat aYRepeat) {
 | 
						||
  nsSize imageSize;
 | 
						||
 | 
						||
  // Size is dictated by cover or contain rules.
 | 
						||
  if (aLayerSize.IsContain() || aLayerSize.IsCover()) {
 | 
						||
    nsImageRenderer::FitType fitType = aLayerSize.IsCover()
 | 
						||
                                           ? nsImageRenderer::COVER
 | 
						||
                                           : nsImageRenderer::CONTAIN;
 | 
						||
    imageSize = nsImageRenderer::ComputeConstrainedSize(
 | 
						||
        aBgPositioningArea, aIntrinsicSize.mRatio, fitType);
 | 
						||
  } else {
 | 
						||
    MOZ_ASSERT(aLayerSize.IsExplicitSize());
 | 
						||
    const auto& width = aLayerSize.explicit_size.width;
 | 
						||
    const auto& height = aLayerSize.explicit_size.height;
 | 
						||
    // No cover/contain constraint, use default algorithm.
 | 
						||
    CSSSizeOrRatio specifiedSize;
 | 
						||
    if (width.IsLengthPercentage()) {
 | 
						||
      specifiedSize.SetWidth(
 | 
						||
          width.AsLengthPercentage().Resolve(aBgPositioningArea.width));
 | 
						||
    }
 | 
						||
    if (height.IsLengthPercentage()) {
 | 
						||
      specifiedSize.SetHeight(
 | 
						||
          height.AsLengthPercentage().Resolve(aBgPositioningArea.height));
 | 
						||
    }
 | 
						||
 | 
						||
    imageSize = nsImageRenderer::ComputeConcreteSize(
 | 
						||
        specifiedSize, aIntrinsicSize, aBgPositioningArea);
 | 
						||
  }
 | 
						||
 | 
						||
  // See https://www.w3.org/TR/css3-background/#background-size .
 | 
						||
  // "If 'background-repeat' is 'round' for one (or both) dimensions, there is a
 | 
						||
  // second
 | 
						||
  //  step. The UA must scale the image in that dimension (or both dimensions)
 | 
						||
  //  so that it fits a whole number of times in the background positioning
 | 
						||
  //  area."
 | 
						||
  // "If 'background-repeat' is 'round' for one dimension only and if
 | 
						||
  // 'background-size'
 | 
						||
  //  is 'auto' for the other dimension, then there is a third step: that other
 | 
						||
  //  dimension is scaled so that the original aspect ratio is restored."
 | 
						||
  bool isRepeatRoundInBothDimensions =
 | 
						||
      aXRepeat == StyleImageLayerRepeat::Round &&
 | 
						||
      aYRepeat == StyleImageLayerRepeat::Round;
 | 
						||
 | 
						||
  // Calculate the rounded size only if the background-size computation
 | 
						||
  // returned a correct size for the image.
 | 
						||
  if (imageSize.width && aXRepeat == StyleImageLayerRepeat::Round) {
 | 
						||
    imageSize.width = nsCSSRendering::ComputeRoundedSize(
 | 
						||
        imageSize.width, aBgPositioningArea.width);
 | 
						||
    if (!isRepeatRoundInBothDimensions && aLayerSize.IsExplicitSize() &&
 | 
						||
        aLayerSize.explicit_size.height.IsAuto()) {
 | 
						||
      // Restore intrinsic ratio
 | 
						||
      if (aIntrinsicSize.mRatio) {
 | 
						||
        imageSize.height =
 | 
						||
            aIntrinsicSize.mRatio.Inverted().ApplyTo(imageSize.width);
 | 
						||
      }
 | 
						||
    }
 | 
						||
  }
 | 
						||
 | 
						||
  // Calculate the rounded size only if the background-size computation
 | 
						||
  // returned a correct size for the image.
 | 
						||
  if (imageSize.height && aYRepeat == StyleImageLayerRepeat::Round) {
 | 
						||
    imageSize.height = nsCSSRendering::ComputeRoundedSize(
 | 
						||
        imageSize.height, aBgPositioningArea.height);
 | 
						||
    if (!isRepeatRoundInBothDimensions && aLayerSize.IsExplicitSize() &&
 | 
						||
        aLayerSize.explicit_size.width.IsAuto()) {
 | 
						||
      // Restore intrinsic ratio
 | 
						||
      if (aIntrinsicSize.mRatio) {
 | 
						||
        imageSize.width = aIntrinsicSize.mRatio.ApplyTo(imageSize.height);
 | 
						||
      }
 | 
						||
    }
 | 
						||
  }
 | 
						||
 | 
						||
  return imageSize;
 | 
						||
}
 | 
						||
 | 
						||
/* ComputeSpacedRepeatSize
 | 
						||
 * aImageDimension: the image width/height
 | 
						||
 * aAvailableSpace: the background positioning area width/height
 | 
						||
 * aRepeat: determine whether the image is repeated
 | 
						||
 * Returns the image size plus gap size of app units for use as spacing
 | 
						||
 */
 | 
						||
static nscoord ComputeSpacedRepeatSize(nscoord aImageDimension,
 | 
						||
                                       nscoord aAvailableSpace, bool& aRepeat) {
 | 
						||
  float ratio = static_cast<float>(aAvailableSpace) / aImageDimension;
 | 
						||
 | 
						||
  if (ratio < 2.0f) {  // If you can't repeat at least twice, then don't repeat.
 | 
						||
    aRepeat = false;
 | 
						||
    return aImageDimension;
 | 
						||
  }
 | 
						||
 | 
						||
  aRepeat = true;
 | 
						||
  return (aAvailableSpace - aImageDimension) / (NSToIntFloor(ratio) - 1);
 | 
						||
}
 | 
						||
 | 
						||
/* static */
 | 
						||
nscoord nsCSSRendering::ComputeBorderSpacedRepeatSize(nscoord aImageDimension,
 | 
						||
                                                      nscoord aAvailableSpace,
 | 
						||
                                                      nscoord& aSpace) {
 | 
						||
  int32_t count = aImageDimension ? (aAvailableSpace / aImageDimension) : 0;
 | 
						||
  aSpace = (aAvailableSpace - aImageDimension * count) / (count + 1);
 | 
						||
  return aSpace + aImageDimension;
 | 
						||
}
 | 
						||
 | 
						||
nsBackgroundLayerState nsCSSRendering::PrepareImageLayer(
 | 
						||
    nsPresContext* aPresContext, nsIFrame* aForFrame, uint32_t aFlags,
 | 
						||
    const nsRect& aBorderArea, const nsRect& aBGClipRect,
 | 
						||
    const nsStyleImageLayers::Layer& aLayer, bool* aOutIsTransformedFixed) {
 | 
						||
  /*
 | 
						||
   * The properties we need to keep in mind when drawing style image
 | 
						||
   * layers are:
 | 
						||
   *
 | 
						||
   *   background-image/ mask-image
 | 
						||
   *   background-repeat/ mask-repeat
 | 
						||
   *   background-attachment
 | 
						||
   *   background-position/ mask-position
 | 
						||
   *   background-clip/ mask-clip
 | 
						||
   *   background-origin/ mask-origin
 | 
						||
   *   background-size/ mask-size
 | 
						||
   *   background-blend-mode
 | 
						||
   *   box-decoration-break
 | 
						||
   *   mask-mode
 | 
						||
   *   mask-composite
 | 
						||
   *
 | 
						||
   * (background-color applies to the entire element and not to individual
 | 
						||
   * layers, so it is irrelevant to this method.)
 | 
						||
   *
 | 
						||
   * These properties have the following dependencies upon each other when
 | 
						||
   * determining rendering:
 | 
						||
   *
 | 
						||
   *   background-image/ mask-image
 | 
						||
   *     no dependencies
 | 
						||
   *   background-repeat/ mask-repeat
 | 
						||
   *     no dependencies
 | 
						||
   *   background-attachment
 | 
						||
   *     no dependencies
 | 
						||
   *   background-position/ mask-position
 | 
						||
   *     depends upon background-size/mask-size (for the image's scaled size)
 | 
						||
   *     and background-break (for the background positioning area)
 | 
						||
   *   background-clip/ mask-clip
 | 
						||
   *     no dependencies
 | 
						||
   *   background-origin/ mask-origin
 | 
						||
   *     depends upon background-attachment (only in the case where that value
 | 
						||
   *     is 'fixed')
 | 
						||
   *   background-size/ mask-size
 | 
						||
   *     depends upon box-decoration-break (for the background positioning area
 | 
						||
   *     for resolving percentages), background-image (for the image's intrinsic
 | 
						||
   *     size), background-repeat (if that value is 'round'), and
 | 
						||
   *     background-origin (for the background painting area, when
 | 
						||
   *     background-repeat is 'round')
 | 
						||
   *   background-blend-mode
 | 
						||
   *     no dependencies
 | 
						||
   *   mask-mode
 | 
						||
   *     no dependencies
 | 
						||
   *   mask-composite
 | 
						||
   *     no dependencies
 | 
						||
   *   box-decoration-break
 | 
						||
   *     no dependencies
 | 
						||
   *
 | 
						||
   * As a result of only-if dependencies we don't strictly do a topological
 | 
						||
   * sort of the above properties when processing, but it's pretty close to one:
 | 
						||
   *
 | 
						||
   *   background-clip/mask-clip (by caller)
 | 
						||
   *   background-image/ mask-image
 | 
						||
   *   box-decoration-break, background-origin/ mask origin
 | 
						||
   *   background-attachment (postfix for background-origin if 'fixed')
 | 
						||
   *   background-size/ mask-size
 | 
						||
   *   background-position/ mask-position
 | 
						||
   *   background-repeat/ mask-repeat
 | 
						||
   */
 | 
						||
 | 
						||
  uint32_t irFlags = 0;
 | 
						||
  if (aFlags & nsCSSRendering::PAINTBG_SYNC_DECODE_IMAGES) {
 | 
						||
    irFlags |= nsImageRenderer::FLAG_SYNC_DECODE_IMAGES;
 | 
						||
  }
 | 
						||
  if (aFlags & nsCSSRendering::PAINTBG_TO_WINDOW) {
 | 
						||
    irFlags |= nsImageRenderer::FLAG_PAINTING_TO_WINDOW;
 | 
						||
  }
 | 
						||
  if (aFlags & nsCSSRendering::PAINTBG_HIGH_QUALITY_SCALING) {
 | 
						||
    irFlags |= nsImageRenderer::FLAG_HIGH_QUALITY_SCALING;
 | 
						||
  }
 | 
						||
  // Only do partial bg image drawing in content documents: non-content
 | 
						||
  // documents are viewed as UI of the browser and a partial draw of a bg image
 | 
						||
  // might look weird in that context.
 | 
						||
  if (StaticPrefs::layout_display_partial_background_images() &&
 | 
						||
      XRE_IsContentProcess() && !aPresContext->IsChrome()) {
 | 
						||
    irFlags |= nsImageRenderer::FLAG_DRAW_PARTIAL_FRAMES;
 | 
						||
  }
 | 
						||
 | 
						||
  nsBackgroundLayerState state(aForFrame, &aLayer.mImage, irFlags);
 | 
						||
  if (!state.mImageRenderer.PrepareImage()) {
 | 
						||
    // There's no image or it's not ready to be painted.
 | 
						||
    if (aOutIsTransformedFixed &&
 | 
						||
        StyleImageLayerAttachment::Fixed == aLayer.mAttachment) {
 | 
						||
      nsIFrame* attachedToFrame = aPresContext->PresShell()->GetRootFrame();
 | 
						||
      NS_ASSERTION(attachedToFrame, "no root frame");
 | 
						||
      nsIFrame* pageContentFrame = nullptr;
 | 
						||
      if (aPresContext->IsPaginated()) {
 | 
						||
        pageContentFrame = nsLayoutUtils::GetClosestFrameOfType(
 | 
						||
            aForFrame, LayoutFrameType::PageContent);
 | 
						||
        if (pageContentFrame) {
 | 
						||
          attachedToFrame = pageContentFrame;
 | 
						||
        }
 | 
						||
        // else this is an embedded shell and its root frame is what we want
 | 
						||
      }
 | 
						||
 | 
						||
      *aOutIsTransformedFixed =
 | 
						||
          nsLayoutUtils::IsTransformed(aForFrame, attachedToFrame);
 | 
						||
    }
 | 
						||
    return state;
 | 
						||
  }
 | 
						||
 | 
						||
  // The frame to which the background is attached
 | 
						||
  nsIFrame* attachedToFrame = aForFrame;
 | 
						||
  // Is the background marked 'fixed', but affected by a transform?
 | 
						||
  bool transformedFixed = false;
 | 
						||
  // Compute background origin area relative to aBorderArea now as we may need
 | 
						||
  // it to compute the effective image size for a CSS gradient.
 | 
						||
  nsRect positionArea = ComputeImageLayerPositioningArea(
 | 
						||
      aPresContext, aForFrame, aBorderArea, aLayer, &attachedToFrame,
 | 
						||
      &transformedFixed);
 | 
						||
  if (aOutIsTransformedFixed) {
 | 
						||
    *aOutIsTransformedFixed = transformedFixed;
 | 
						||
  }
 | 
						||
 | 
						||
  // For background-attachment:fixed backgrounds, we'll override the area
 | 
						||
  // where the background can be drawn to the viewport.
 | 
						||
  nsRect bgClipRect = aBGClipRect;
 | 
						||
 | 
						||
  if (StyleImageLayerAttachment::Fixed == aLayer.mAttachment &&
 | 
						||
      !transformedFixed && (aFlags & nsCSSRendering::PAINTBG_TO_WINDOW)) {
 | 
						||
    bgClipRect = positionArea + aBorderArea.TopLeft();
 | 
						||
  }
 | 
						||
 | 
						||
  StyleImageLayerRepeat repeatX = aLayer.mRepeat.mXRepeat;
 | 
						||
  StyleImageLayerRepeat repeatY = aLayer.mRepeat.mYRepeat;
 | 
						||
 | 
						||
  // Scale the image as specified for background-size and background-repeat.
 | 
						||
  // Also as required for proper background positioning when background-position
 | 
						||
  // is defined with percentages.
 | 
						||
  CSSSizeOrRatio intrinsicSize = state.mImageRenderer.ComputeIntrinsicSize();
 | 
						||
  nsSize bgPositionSize = positionArea.Size();
 | 
						||
  nsSize imageSize = ComputeDrawnSizeForBackground(
 | 
						||
      intrinsicSize, bgPositionSize, aLayer.mSize, repeatX, repeatY);
 | 
						||
 | 
						||
  if (imageSize.width <= 0 || imageSize.height <= 0) return state;
 | 
						||
 | 
						||
  state.mImageRenderer.SetPreferredSize(intrinsicSize, imageSize);
 | 
						||
 | 
						||
  // Compute the anchor point.
 | 
						||
  //
 | 
						||
  // relative to aBorderArea.TopLeft() (which is where the top-left
 | 
						||
  // of aForFrame's border-box will be rendered)
 | 
						||
  nsPoint imageTopLeft;
 | 
						||
 | 
						||
  // Compute the position of the background now that the background's size is
 | 
						||
  // determined.
 | 
						||
  nsImageRenderer::ComputeObjectAnchorPoint(aLayer.mPosition, bgPositionSize,
 | 
						||
                                            imageSize, &imageTopLeft,
 | 
						||
                                            &state.mAnchor);
 | 
						||
  state.mRepeatSize = imageSize;
 | 
						||
  if (repeatX == StyleImageLayerRepeat::Space) {
 | 
						||
    bool isRepeat;
 | 
						||
    state.mRepeatSize.width = ComputeSpacedRepeatSize(
 | 
						||
        imageSize.width, bgPositionSize.width, isRepeat);
 | 
						||
    if (isRepeat) {
 | 
						||
      imageTopLeft.x = 0;
 | 
						||
      state.mAnchor.x = 0;
 | 
						||
    } else {
 | 
						||
      repeatX = StyleImageLayerRepeat::NoRepeat;
 | 
						||
    }
 | 
						||
  }
 | 
						||
 | 
						||
  if (repeatY == StyleImageLayerRepeat::Space) {
 | 
						||
    bool isRepeat;
 | 
						||
    state.mRepeatSize.height = ComputeSpacedRepeatSize(
 | 
						||
        imageSize.height, bgPositionSize.height, isRepeat);
 | 
						||
    if (isRepeat) {
 | 
						||
      imageTopLeft.y = 0;
 | 
						||
      state.mAnchor.y = 0;
 | 
						||
    } else {
 | 
						||
      repeatY = StyleImageLayerRepeat::NoRepeat;
 | 
						||
    }
 | 
						||
  }
 | 
						||
 | 
						||
  imageTopLeft += positionArea.TopLeft();
 | 
						||
  state.mAnchor += positionArea.TopLeft();
 | 
						||
  state.mDestArea = nsRect(imageTopLeft + aBorderArea.TopLeft(), imageSize);
 | 
						||
  state.mFillArea = state.mDestArea;
 | 
						||
 | 
						||
  ExtendMode repeatMode = ExtendMode::CLAMP;
 | 
						||
  if (repeatX == StyleImageLayerRepeat::Repeat ||
 | 
						||
      repeatX == StyleImageLayerRepeat::Round ||
 | 
						||
      repeatX == StyleImageLayerRepeat::Space) {
 | 
						||
    state.mFillArea.x = bgClipRect.x;
 | 
						||
    state.mFillArea.width = bgClipRect.width;
 | 
						||
    repeatMode = ExtendMode::REPEAT_X;
 | 
						||
  }
 | 
						||
  if (repeatY == StyleImageLayerRepeat::Repeat ||
 | 
						||
      repeatY == StyleImageLayerRepeat::Round ||
 | 
						||
      repeatY == StyleImageLayerRepeat::Space) {
 | 
						||
    state.mFillArea.y = bgClipRect.y;
 | 
						||
    state.mFillArea.height = bgClipRect.height;
 | 
						||
 | 
						||
    /***
 | 
						||
     * We're repeating on the X axis already,
 | 
						||
     * so if we have to repeat in the Y axis,
 | 
						||
     * we really need to repeat in both directions.
 | 
						||
     */
 | 
						||
    if (repeatMode == ExtendMode::REPEAT_X) {
 | 
						||
      repeatMode = ExtendMode::REPEAT;
 | 
						||
    } else {
 | 
						||
      repeatMode = ExtendMode::REPEAT_Y;
 | 
						||
    }
 | 
						||
  }
 | 
						||
  state.mImageRenderer.SetExtendMode(repeatMode);
 | 
						||
  state.mImageRenderer.SetMaskOp(aLayer.mMaskMode);
 | 
						||
 | 
						||
  state.mFillArea.IntersectRect(state.mFillArea, bgClipRect);
 | 
						||
 | 
						||
  return state;
 | 
						||
}
 | 
						||
 | 
						||
nsRect nsCSSRendering::GetBackgroundLayerRect(
 | 
						||
    nsPresContext* aPresContext, nsIFrame* aForFrame, const nsRect& aBorderArea,
 | 
						||
    const nsRect& aClipRect, const nsStyleImageLayers::Layer& aLayer,
 | 
						||
    uint32_t aFlags) {
 | 
						||
  Sides skipSides = aForFrame->GetSkipSides();
 | 
						||
  nsRect borderArea =
 | 
						||
      BoxDecorationRectForBackground(aForFrame, aBorderArea, skipSides);
 | 
						||
  nsBackgroundLayerState state = PrepareImageLayer(
 | 
						||
      aPresContext, aForFrame, aFlags, borderArea, aClipRect, aLayer);
 | 
						||
  return state.mFillArea;
 | 
						||
}
 | 
						||
 | 
						||
// Begin table border-collapsing section
 | 
						||
// These functions were written to not disrupt the normal ones and yet satisfy
 | 
						||
// some additional requirements At some point, all functions should be unified
 | 
						||
// to include the additional functionality that these provide
 | 
						||
 | 
						||
static nscoord RoundIntToPixel(nscoord aValue, nscoord aOneDevPixel,
 | 
						||
                               bool aRoundDown = false) {
 | 
						||
  if (aOneDevPixel <= 0) {
 | 
						||
    // We must be rendering to a device that has a resolution greater than
 | 
						||
    // one device pixel!
 | 
						||
    // In that case, aValue is as accurate as it's going to get.
 | 
						||
    return aValue;
 | 
						||
  }
 | 
						||
 | 
						||
  nscoord halfPixel = NSToCoordRound(aOneDevPixel / 2.0f);
 | 
						||
  nscoord extra = aValue % aOneDevPixel;
 | 
						||
  nscoord finalValue = (!aRoundDown && (extra >= halfPixel))
 | 
						||
                           ? aValue + (aOneDevPixel - extra)
 | 
						||
                           : aValue - extra;
 | 
						||
  return finalValue;
 | 
						||
}
 | 
						||
 | 
						||
static nscoord RoundFloatToPixel(float aValue, nscoord aOneDevPixel,
 | 
						||
                                 bool aRoundDown = false) {
 | 
						||
  return RoundIntToPixel(NSToCoordRound(aValue), aOneDevPixel, aRoundDown);
 | 
						||
}
 | 
						||
 | 
						||
static void SetPoly(const Rect& aRect, Point* poly) {
 | 
						||
  poly[0].x = aRect.x;
 | 
						||
  poly[0].y = aRect.y;
 | 
						||
  poly[1].x = aRect.x + aRect.width;
 | 
						||
  poly[1].y = aRect.y;
 | 
						||
  poly[2].x = aRect.x + aRect.width;
 | 
						||
  poly[2].y = aRect.y + aRect.height;
 | 
						||
  poly[3].x = aRect.x;
 | 
						||
  poly[3].y = aRect.y + aRect.height;
 | 
						||
}
 | 
						||
 | 
						||
static void DrawDashedSegment(DrawTarget& aDrawTarget, nsRect aRect,
 | 
						||
                              nscoord aDashLength, nscolor aColor,
 | 
						||
                              int32_t aAppUnitsPerDevPixel, bool aHorizontal) {
 | 
						||
  ColorPattern color(ToDeviceColor(aColor));
 | 
						||
  DrawOptions drawOptions(1.f, CompositionOp::OP_OVER, AntialiasMode::NONE);
 | 
						||
  StrokeOptions strokeOptions;
 | 
						||
 | 
						||
  Float dash[2];
 | 
						||
  dash[0] = Float(aDashLength) / aAppUnitsPerDevPixel;
 | 
						||
  dash[1] = dash[0];
 | 
						||
 | 
						||
  strokeOptions.mDashPattern = dash;
 | 
						||
  strokeOptions.mDashLength = MOZ_ARRAY_LENGTH(dash);
 | 
						||
 | 
						||
  if (aHorizontal) {
 | 
						||
    nsPoint left = (aRect.TopLeft() + aRect.BottomLeft()) / 2;
 | 
						||
    nsPoint right = (aRect.TopRight() + aRect.BottomRight()) / 2;
 | 
						||
    strokeOptions.mLineWidth = Float(aRect.height) / aAppUnitsPerDevPixel;
 | 
						||
    StrokeLineWithSnapping(left, right, aAppUnitsPerDevPixel, aDrawTarget,
 | 
						||
                           color, strokeOptions, drawOptions);
 | 
						||
  } else {
 | 
						||
    nsPoint top = (aRect.TopLeft() + aRect.TopRight()) / 2;
 | 
						||
    nsPoint bottom = (aRect.BottomLeft() + aRect.BottomRight()) / 2;
 | 
						||
    strokeOptions.mLineWidth = Float(aRect.width) / aAppUnitsPerDevPixel;
 | 
						||
    StrokeLineWithSnapping(top, bottom, aAppUnitsPerDevPixel, aDrawTarget,
 | 
						||
                           color, strokeOptions, drawOptions);
 | 
						||
  }
 | 
						||
}
 | 
						||
 | 
						||
static void DrawSolidBorderSegment(
 | 
						||
    DrawTarget& aDrawTarget, nsRect aRect, nscolor aColor,
 | 
						||
    int32_t aAppUnitsPerDevPixel,
 | 
						||
    mozilla::Side aStartBevelSide = mozilla::eSideTop,
 | 
						||
    nscoord aStartBevelOffset = 0,
 | 
						||
    mozilla::Side aEndBevelSide = mozilla::eSideTop,
 | 
						||
    nscoord aEndBevelOffset = 0) {
 | 
						||
  ColorPattern color(ToDeviceColor(aColor));
 | 
						||
  DrawOptions drawOptions(1.f, CompositionOp::OP_OVER, AntialiasMode::NONE);
 | 
						||
 | 
						||
  nscoord oneDevPixel = NSIntPixelsToAppUnits(1, aAppUnitsPerDevPixel);
 | 
						||
  // We don't need to bevel single pixel borders
 | 
						||
  if ((aRect.width == oneDevPixel) || (aRect.height == oneDevPixel) ||
 | 
						||
      ((0 == aStartBevelOffset) && (0 == aEndBevelOffset))) {
 | 
						||
    // simple rectangle
 | 
						||
    aDrawTarget.FillRect(
 | 
						||
        NSRectToSnappedRect(aRect, aAppUnitsPerDevPixel, aDrawTarget), color,
 | 
						||
        drawOptions);
 | 
						||
  } else {
 | 
						||
    // polygon with beveling
 | 
						||
    Point poly[4];
 | 
						||
    SetPoly(NSRectToSnappedRect(aRect, aAppUnitsPerDevPixel, aDrawTarget),
 | 
						||
            poly);
 | 
						||
 | 
						||
    Float startBevelOffset =
 | 
						||
        NSAppUnitsToFloatPixels(aStartBevelOffset, aAppUnitsPerDevPixel);
 | 
						||
    switch (aStartBevelSide) {
 | 
						||
      case eSideTop:
 | 
						||
        poly[0].x += startBevelOffset;
 | 
						||
        break;
 | 
						||
      case eSideBottom:
 | 
						||
        poly[3].x += startBevelOffset;
 | 
						||
        break;
 | 
						||
      case eSideRight:
 | 
						||
        poly[1].y += startBevelOffset;
 | 
						||
        break;
 | 
						||
      case eSideLeft:
 | 
						||
        poly[0].y += startBevelOffset;
 | 
						||
    }
 | 
						||
 | 
						||
    Float endBevelOffset =
 | 
						||
        NSAppUnitsToFloatPixels(aEndBevelOffset, aAppUnitsPerDevPixel);
 | 
						||
    switch (aEndBevelSide) {
 | 
						||
      case eSideTop:
 | 
						||
        poly[1].x -= endBevelOffset;
 | 
						||
        break;
 | 
						||
      case eSideBottom:
 | 
						||
        poly[2].x -= endBevelOffset;
 | 
						||
        break;
 | 
						||
      case eSideRight:
 | 
						||
        poly[2].y -= endBevelOffset;
 | 
						||
        break;
 | 
						||
      case eSideLeft:
 | 
						||
        poly[3].y -= endBevelOffset;
 | 
						||
    }
 | 
						||
 | 
						||
    RefPtr<PathBuilder> builder = aDrawTarget.CreatePathBuilder();
 | 
						||
    builder->MoveTo(poly[0]);
 | 
						||
    builder->LineTo(poly[1]);
 | 
						||
    builder->LineTo(poly[2]);
 | 
						||
    builder->LineTo(poly[3]);
 | 
						||
    builder->Close();
 | 
						||
    RefPtr<Path> path = builder->Finish();
 | 
						||
    aDrawTarget.Fill(path, color, drawOptions);
 | 
						||
  }
 | 
						||
}
 | 
						||
 | 
						||
static void GetDashInfo(nscoord aBorderLength, nscoord aDashLength,
 | 
						||
                        nscoord aOneDevPixel, int32_t& aNumDashSpaces,
 | 
						||
                        nscoord& aStartDashLength, nscoord& aEndDashLength) {
 | 
						||
  aNumDashSpaces = 0;
 | 
						||
  if (aStartDashLength + aDashLength + aEndDashLength >= aBorderLength) {
 | 
						||
    aStartDashLength = aBorderLength;
 | 
						||
    aEndDashLength = 0;
 | 
						||
  } else {
 | 
						||
    aNumDashSpaces =
 | 
						||
        (aBorderLength - aDashLength) / (2 * aDashLength);  // round down
 | 
						||
    nscoord extra = aBorderLength - aStartDashLength - aEndDashLength -
 | 
						||
                    (((2 * aNumDashSpaces) - 1) * aDashLength);
 | 
						||
    if (extra > 0) {
 | 
						||
      nscoord half = RoundIntToPixel(extra / 2, aOneDevPixel);
 | 
						||
      aStartDashLength += half;
 | 
						||
      aEndDashLength += (extra - half);
 | 
						||
    }
 | 
						||
  }
 | 
						||
}
 | 
						||
 | 
						||
void nsCSSRendering::DrawTableBorderSegment(
 | 
						||
    DrawTarget& aDrawTarget, StyleBorderStyle aBorderStyle,
 | 
						||
    nscolor aBorderColor, const nsRect& aBorder, int32_t aAppUnitsPerDevPixel,
 | 
						||
    mozilla::Side aStartBevelSide, nscoord aStartBevelOffset,
 | 
						||
    mozilla::Side aEndBevelSide, nscoord aEndBevelOffset) {
 | 
						||
  bool horizontal =
 | 
						||
      ((eSideTop == aStartBevelSide) || (eSideBottom == aStartBevelSide));
 | 
						||
  nscoord oneDevPixel = NSIntPixelsToAppUnits(1, aAppUnitsPerDevPixel);
 | 
						||
 | 
						||
  if ((oneDevPixel >= aBorder.width) || (oneDevPixel >= aBorder.height) ||
 | 
						||
      (StyleBorderStyle::Dashed == aBorderStyle) ||
 | 
						||
      (StyleBorderStyle::Dotted == aBorderStyle)) {
 | 
						||
    // no beveling for 1 pixel border, dash or dot
 | 
						||
    aStartBevelOffset = 0;
 | 
						||
    aEndBevelOffset = 0;
 | 
						||
  }
 | 
						||
 | 
						||
  switch (aBorderStyle) {
 | 
						||
    case StyleBorderStyle::None:
 | 
						||
    case StyleBorderStyle::Hidden:
 | 
						||
      // NS_ASSERTION(false, "style of none or hidden");
 | 
						||
      break;
 | 
						||
    case StyleBorderStyle::Dotted:
 | 
						||
    case StyleBorderStyle::Dashed: {
 | 
						||
      nscoord dashLength =
 | 
						||
          (StyleBorderStyle::Dashed == aBorderStyle) ? DASH_LENGTH : DOT_LENGTH;
 | 
						||
      // make the dash length proportional to the border thickness
 | 
						||
      dashLength *= (horizontal) ? aBorder.height : aBorder.width;
 | 
						||
      // make the min dash length for the ends 1/2 the dash length
 | 
						||
      nscoord minDashLength =
 | 
						||
          (StyleBorderStyle::Dashed == aBorderStyle)
 | 
						||
              ? RoundFloatToPixel(((float)dashLength) / 2.0f,
 | 
						||
                                  aAppUnitsPerDevPixel)
 | 
						||
              : dashLength;
 | 
						||
      minDashLength = std::max(minDashLength, oneDevPixel);
 | 
						||
      nscoord numDashSpaces = 0;
 | 
						||
      nscoord startDashLength = minDashLength;
 | 
						||
      nscoord endDashLength = minDashLength;
 | 
						||
      if (horizontal) {
 | 
						||
        GetDashInfo(aBorder.width, dashLength, aAppUnitsPerDevPixel,
 | 
						||
                    numDashSpaces, startDashLength, endDashLength);
 | 
						||
        nsRect rect(aBorder.x, aBorder.y, startDashLength, aBorder.height);
 | 
						||
        DrawSolidBorderSegment(aDrawTarget, rect, aBorderColor,
 | 
						||
                               aAppUnitsPerDevPixel);
 | 
						||
 | 
						||
        rect.x += startDashLength + dashLength;
 | 
						||
        rect.width =
 | 
						||
            aBorder.width - (startDashLength + endDashLength + dashLength);
 | 
						||
        DrawDashedSegment(aDrawTarget, rect, dashLength, aBorderColor,
 | 
						||
                          aAppUnitsPerDevPixel, horizontal);
 | 
						||
 | 
						||
        rect.x += rect.width;
 | 
						||
        rect.width = endDashLength;
 | 
						||
        DrawSolidBorderSegment(aDrawTarget, rect, aBorderColor,
 | 
						||
                               aAppUnitsPerDevPixel);
 | 
						||
      } else {
 | 
						||
        GetDashInfo(aBorder.height, dashLength, aAppUnitsPerDevPixel,
 | 
						||
                    numDashSpaces, startDashLength, endDashLength);
 | 
						||
        nsRect rect(aBorder.x, aBorder.y, aBorder.width, startDashLength);
 | 
						||
        DrawSolidBorderSegment(aDrawTarget, rect, aBorderColor,
 | 
						||
                               aAppUnitsPerDevPixel);
 | 
						||
 | 
						||
        rect.y += rect.height + dashLength;
 | 
						||
        rect.height =
 | 
						||
            aBorder.height - (startDashLength + endDashLength + dashLength);
 | 
						||
        DrawDashedSegment(aDrawTarget, rect, dashLength, aBorderColor,
 | 
						||
                          aAppUnitsPerDevPixel, horizontal);
 | 
						||
 | 
						||
        rect.y += rect.height;
 | 
						||
        rect.height = endDashLength;
 | 
						||
        DrawSolidBorderSegment(aDrawTarget, rect, aBorderColor,
 | 
						||
                               aAppUnitsPerDevPixel);
 | 
						||
      }
 | 
						||
    } break;
 | 
						||
    default:
 | 
						||
      AutoTArray<SolidBeveledBorderSegment, 3> segments;
 | 
						||
      GetTableBorderSolidSegments(
 | 
						||
          segments, aBorderStyle, aBorderColor, aBorder, aAppUnitsPerDevPixel,
 | 
						||
          aStartBevelSide, aStartBevelOffset, aEndBevelSide, aEndBevelOffset);
 | 
						||
      for (const auto& segment : segments) {
 | 
						||
        DrawSolidBorderSegment(
 | 
						||
            aDrawTarget, segment.mRect, segment.mColor, aAppUnitsPerDevPixel,
 | 
						||
            segment.mStartBevel.mSide, segment.mStartBevel.mOffset,
 | 
						||
            segment.mEndBevel.mSide, segment.mEndBevel.mOffset);
 | 
						||
      }
 | 
						||
      break;
 | 
						||
  }
 | 
						||
}
 | 
						||
 | 
						||
void nsCSSRendering::GetTableBorderSolidSegments(
 | 
						||
    nsTArray<SolidBeveledBorderSegment>& aSegments,
 | 
						||
    StyleBorderStyle aBorderStyle, nscolor aBorderColor, const nsRect& aBorder,
 | 
						||
    int32_t aAppUnitsPerDevPixel, mozilla::Side aStartBevelSide,
 | 
						||
    nscoord aStartBevelOffset, mozilla::Side aEndBevelSide,
 | 
						||
    nscoord aEndBevelOffset) {
 | 
						||
  const bool horizontal =
 | 
						||
      eSideTop == aStartBevelSide || eSideBottom == aStartBevelSide;
 | 
						||
  const nscoord oneDevPixel = NSIntPixelsToAppUnits(1, aAppUnitsPerDevPixel);
 | 
						||
 | 
						||
  switch (aBorderStyle) {
 | 
						||
    case StyleBorderStyle::None:
 | 
						||
    case StyleBorderStyle::Hidden:
 | 
						||
      return;
 | 
						||
    case StyleBorderStyle::Dotted:
 | 
						||
    case StyleBorderStyle::Dashed:
 | 
						||
      MOZ_ASSERT_UNREACHABLE("Caller should have checked");
 | 
						||
      return;
 | 
						||
    case StyleBorderStyle::Groove:
 | 
						||
    case StyleBorderStyle::Ridge:
 | 
						||
      if ((horizontal && (oneDevPixel >= aBorder.height)) ||
 | 
						||
          (!horizontal && (oneDevPixel >= aBorder.width))) {
 | 
						||
        aSegments.AppendElement(
 | 
						||
            SolidBeveledBorderSegment{aBorder,
 | 
						||
                                      aBorderColor,
 | 
						||
                                      {aStartBevelSide, aStartBevelOffset},
 | 
						||
                                      {aEndBevelSide, aEndBevelOffset}});
 | 
						||
      } else {
 | 
						||
        nscoord startBevel =
 | 
						||
            (aStartBevelOffset > 0)
 | 
						||
                ? RoundFloatToPixel(0.5f * (float)aStartBevelOffset,
 | 
						||
                                    aAppUnitsPerDevPixel, true)
 | 
						||
                : 0;
 | 
						||
        nscoord endBevel =
 | 
						||
            (aEndBevelOffset > 0)
 | 
						||
                ? RoundFloatToPixel(0.5f * (float)aEndBevelOffset,
 | 
						||
                                    aAppUnitsPerDevPixel, true)
 | 
						||
                : 0;
 | 
						||
        mozilla::Side ridgeGrooveSide = (horizontal) ? eSideTop : eSideLeft;
 | 
						||
        // FIXME: In theory, this should use the visited-dependent
 | 
						||
        // background color, but I don't care.
 | 
						||
        nscolor bevelColor =
 | 
						||
            MakeBevelColor(ridgeGrooveSide, aBorderStyle, aBorderColor);
 | 
						||
        nsRect rect(aBorder);
 | 
						||
        nscoord half;
 | 
						||
        if (horizontal) {  // top, bottom
 | 
						||
          half = RoundFloatToPixel(0.5f * (float)aBorder.height,
 | 
						||
                                   aAppUnitsPerDevPixel);
 | 
						||
          rect.height = half;
 | 
						||
          if (eSideTop == aStartBevelSide) {
 | 
						||
            rect.x += startBevel;
 | 
						||
            rect.width -= startBevel;
 | 
						||
          }
 | 
						||
          if (eSideTop == aEndBevelSide) {
 | 
						||
            rect.width -= endBevel;
 | 
						||
          }
 | 
						||
          aSegments.AppendElement(
 | 
						||
              SolidBeveledBorderSegment{rect,
 | 
						||
                                        bevelColor,
 | 
						||
                                        {aStartBevelSide, startBevel},
 | 
						||
                                        {aEndBevelSide, endBevel}});
 | 
						||
        } else {  // left, right
 | 
						||
          half = RoundFloatToPixel(0.5f * (float)aBorder.width,
 | 
						||
                                   aAppUnitsPerDevPixel);
 | 
						||
          rect.width = half;
 | 
						||
          if (eSideLeft == aStartBevelSide) {
 | 
						||
            rect.y += startBevel;
 | 
						||
            rect.height -= startBevel;
 | 
						||
          }
 | 
						||
          if (eSideLeft == aEndBevelSide) {
 | 
						||
            rect.height -= endBevel;
 | 
						||
          }
 | 
						||
          aSegments.AppendElement(
 | 
						||
              SolidBeveledBorderSegment{rect,
 | 
						||
                                        bevelColor,
 | 
						||
                                        {aStartBevelSide, startBevel},
 | 
						||
                                        {aEndBevelSide, endBevel}});
 | 
						||
        }
 | 
						||
 | 
						||
        rect = aBorder;
 | 
						||
        ridgeGrooveSide =
 | 
						||
            (eSideTop == ridgeGrooveSide) ? eSideBottom : eSideRight;
 | 
						||
        // FIXME: In theory, this should use the visited-dependent
 | 
						||
        // background color, but I don't care.
 | 
						||
        bevelColor =
 | 
						||
            MakeBevelColor(ridgeGrooveSide, aBorderStyle, aBorderColor);
 | 
						||
        if (horizontal) {
 | 
						||
          rect.y = rect.y + half;
 | 
						||
          rect.height = aBorder.height - half;
 | 
						||
          if (eSideBottom == aStartBevelSide) {
 | 
						||
            rect.x += startBevel;
 | 
						||
            rect.width -= startBevel;
 | 
						||
          }
 | 
						||
          if (eSideBottom == aEndBevelSide) {
 | 
						||
            rect.width -= endBevel;
 | 
						||
          }
 | 
						||
          aSegments.AppendElement(
 | 
						||
              SolidBeveledBorderSegment{rect,
 | 
						||
                                        bevelColor,
 | 
						||
                                        {aStartBevelSide, startBevel},
 | 
						||
                                        {aEndBevelSide, endBevel}});
 | 
						||
        } else {
 | 
						||
          rect.x = rect.x + half;
 | 
						||
          rect.width = aBorder.width - half;
 | 
						||
          if (eSideRight == aStartBevelSide) {
 | 
						||
            rect.y += aStartBevelOffset - startBevel;
 | 
						||
            rect.height -= startBevel;
 | 
						||
          }
 | 
						||
          if (eSideRight == aEndBevelSide) {
 | 
						||
            rect.height -= endBevel;
 | 
						||
          }
 | 
						||
          aSegments.AppendElement(
 | 
						||
              SolidBeveledBorderSegment{rect,
 | 
						||
                                        bevelColor,
 | 
						||
                                        {aStartBevelSide, startBevel},
 | 
						||
                                        {aEndBevelSide, endBevel}});
 | 
						||
        }
 | 
						||
      }
 | 
						||
      break;
 | 
						||
    case StyleBorderStyle::Double:
 | 
						||
      // We can only do "double" borders if the thickness of the border
 | 
						||
      // is more than 2px.  Otherwise, we fall through to painting a
 | 
						||
      // solid border.
 | 
						||
      if ((aBorder.width > 2 * oneDevPixel || horizontal) &&
 | 
						||
          (aBorder.height > 2 * oneDevPixel || !horizontal)) {
 | 
						||
        nscoord startBevel =
 | 
						||
            (aStartBevelOffset > 0)
 | 
						||
                ? RoundFloatToPixel(0.333333f * (float)aStartBevelOffset,
 | 
						||
                                    aAppUnitsPerDevPixel)
 | 
						||
                : 0;
 | 
						||
        nscoord endBevel =
 | 
						||
            (aEndBevelOffset > 0)
 | 
						||
                ? RoundFloatToPixel(0.333333f * (float)aEndBevelOffset,
 | 
						||
                                    aAppUnitsPerDevPixel)
 | 
						||
                : 0;
 | 
						||
        if (horizontal) {  // top, bottom
 | 
						||
          nscoord thirdHeight = RoundFloatToPixel(
 | 
						||
              0.333333f * (float)aBorder.height, aAppUnitsPerDevPixel);
 | 
						||
 | 
						||
          // draw the top line or rect
 | 
						||
          nsRect topRect(aBorder.x, aBorder.y, aBorder.width, thirdHeight);
 | 
						||
          if (eSideTop == aStartBevelSide) {
 | 
						||
            topRect.x += aStartBevelOffset - startBevel;
 | 
						||
            topRect.width -= aStartBevelOffset - startBevel;
 | 
						||
          }
 | 
						||
          if (eSideTop == aEndBevelSide) {
 | 
						||
            topRect.width -= aEndBevelOffset - endBevel;
 | 
						||
          }
 | 
						||
 | 
						||
          aSegments.AppendElement(
 | 
						||
              SolidBeveledBorderSegment{topRect,
 | 
						||
                                        aBorderColor,
 | 
						||
                                        {aStartBevelSide, startBevel},
 | 
						||
                                        {aEndBevelSide, endBevel}});
 | 
						||
 | 
						||
          // draw the botom line or rect
 | 
						||
          nscoord heightOffset = aBorder.height - thirdHeight;
 | 
						||
          nsRect bottomRect(aBorder.x, aBorder.y + heightOffset, aBorder.width,
 | 
						||
                            aBorder.height - heightOffset);
 | 
						||
          if (eSideBottom == aStartBevelSide) {
 | 
						||
            bottomRect.x += aStartBevelOffset - startBevel;
 | 
						||
            bottomRect.width -= aStartBevelOffset - startBevel;
 | 
						||
          }
 | 
						||
          if (eSideBottom == aEndBevelSide) {
 | 
						||
            bottomRect.width -= aEndBevelOffset - endBevel;
 | 
						||
          }
 | 
						||
          aSegments.AppendElement(
 | 
						||
              SolidBeveledBorderSegment{bottomRect,
 | 
						||
                                        aBorderColor,
 | 
						||
                                        {aStartBevelSide, startBevel},
 | 
						||
                                        {aEndBevelSide, endBevel}});
 | 
						||
        } else {  // left, right
 | 
						||
          nscoord thirdWidth = RoundFloatToPixel(
 | 
						||
              0.333333f * (float)aBorder.width, aAppUnitsPerDevPixel);
 | 
						||
 | 
						||
          nsRect leftRect(aBorder.x, aBorder.y, thirdWidth, aBorder.height);
 | 
						||
          if (eSideLeft == aStartBevelSide) {
 | 
						||
            leftRect.y += aStartBevelOffset - startBevel;
 | 
						||
            leftRect.height -= aStartBevelOffset - startBevel;
 | 
						||
          }
 | 
						||
          if (eSideLeft == aEndBevelSide) {
 | 
						||
            leftRect.height -= aEndBevelOffset - endBevel;
 | 
						||
          }
 | 
						||
 | 
						||
          aSegments.AppendElement(
 | 
						||
              SolidBeveledBorderSegment{leftRect,
 | 
						||
                                        aBorderColor,
 | 
						||
                                        {aStartBevelSide, startBevel},
 | 
						||
                                        {aEndBevelSide, endBevel}});
 | 
						||
 | 
						||
          nscoord widthOffset = aBorder.width - thirdWidth;
 | 
						||
          nsRect rightRect(aBorder.x + widthOffset, aBorder.y,
 | 
						||
                           aBorder.width - widthOffset, aBorder.height);
 | 
						||
          if (eSideRight == aStartBevelSide) {
 | 
						||
            rightRect.y += aStartBevelOffset - startBevel;
 | 
						||
            rightRect.height -= aStartBevelOffset - startBevel;
 | 
						||
          }
 | 
						||
          if (eSideRight == aEndBevelSide) {
 | 
						||
            rightRect.height -= aEndBevelOffset - endBevel;
 | 
						||
          }
 | 
						||
          aSegments.AppendElement(
 | 
						||
              SolidBeveledBorderSegment{rightRect,
 | 
						||
                                        aBorderColor,
 | 
						||
                                        {aStartBevelSide, startBevel},
 | 
						||
                                        {aEndBevelSide, endBevel}});
 | 
						||
        }
 | 
						||
        break;
 | 
						||
      }
 | 
						||
      // else fall through to solid
 | 
						||
      [[fallthrough]];
 | 
						||
    case StyleBorderStyle::Solid:
 | 
						||
      aSegments.AppendElement(
 | 
						||
          SolidBeveledBorderSegment{aBorder,
 | 
						||
                                    aBorderColor,
 | 
						||
                                    {aStartBevelSide, aStartBevelOffset},
 | 
						||
                                    {aEndBevelSide, aEndBevelOffset}});
 | 
						||
      break;
 | 
						||
    case StyleBorderStyle::Outset:
 | 
						||
    case StyleBorderStyle::Inset:
 | 
						||
      MOZ_ASSERT_UNREACHABLE(
 | 
						||
          "inset, outset should have been converted to groove, ridge");
 | 
						||
      break;
 | 
						||
  }
 | 
						||
}
 | 
						||
 | 
						||
// End table border-collapsing section
 | 
						||
 | 
						||
Rect nsCSSRendering::ExpandPaintingRectForDecorationLine(
 | 
						||
    nsIFrame* aFrame, const StyleTextDecorationStyle aStyle,
 | 
						||
    const Rect& aClippedRect, const Float aICoordInFrame,
 | 
						||
    const Float aCycleLength, bool aVertical) {
 | 
						||
  switch (aStyle) {
 | 
						||
    case StyleTextDecorationStyle::Dotted:
 | 
						||
    case StyleTextDecorationStyle::Dashed:
 | 
						||
    case StyleTextDecorationStyle::Wavy:
 | 
						||
      break;
 | 
						||
    default:
 | 
						||
      NS_ERROR("Invalid style was specified");
 | 
						||
      return aClippedRect;
 | 
						||
  }
 | 
						||
 | 
						||
  nsBlockFrame* block = nullptr;
 | 
						||
  // Note that when we paint the decoration lines in relative positioned
 | 
						||
  // box, we should paint them like all of the boxes are positioned as static.
 | 
						||
  nscoord framePosInBlockAppUnits = 0;
 | 
						||
  for (nsIFrame* f = aFrame; f; f = f->GetParent()) {
 | 
						||
    block = do_QueryFrame(f);
 | 
						||
    if (block) {
 | 
						||
      break;
 | 
						||
    }
 | 
						||
    framePosInBlockAppUnits +=
 | 
						||
        aVertical ? f->GetNormalPosition().y : f->GetNormalPosition().x;
 | 
						||
  }
 | 
						||
 | 
						||
  NS_ENSURE_TRUE(block, aClippedRect);
 | 
						||
 | 
						||
  nsPresContext* pc = aFrame->PresContext();
 | 
						||
  Float framePosInBlock =
 | 
						||
      Float(pc->AppUnitsToGfxUnits(framePosInBlockAppUnits));
 | 
						||
  int32_t rectPosInBlock = int32_t(NS_round(framePosInBlock + aICoordInFrame));
 | 
						||
  int32_t extraStartEdge =
 | 
						||
      rectPosInBlock - (rectPosInBlock / int32_t(aCycleLength) * aCycleLength);
 | 
						||
  Rect rect(aClippedRect);
 | 
						||
  if (aVertical) {
 | 
						||
    rect.y -= extraStartEdge;
 | 
						||
    rect.height += extraStartEdge;
 | 
						||
  } else {
 | 
						||
    rect.x -= extraStartEdge;
 | 
						||
    rect.width += extraStartEdge;
 | 
						||
  }
 | 
						||
  return rect;
 | 
						||
}
 | 
						||
 | 
						||
// Converts a GfxFont to an SkFont
 | 
						||
// Either returns true if it was successful, or false if something went wrong
 | 
						||
static bool GetSkFontFromGfxFont(DrawTarget& aDrawTarget, gfxFont* aFont,
 | 
						||
                                 SkFont& aSkFont) {
 | 
						||
  RefPtr<ScaledFont> scaledFont = aFont->GetScaledFont(&aDrawTarget);
 | 
						||
  if (!scaledFont) {
 | 
						||
    return false;
 | 
						||
  }
 | 
						||
 | 
						||
  ScaledFontBase* fontBase = static_cast<ScaledFontBase*>(scaledFont.get());
 | 
						||
 | 
						||
  SkTypeface* typeface = fontBase->GetSkTypeface();
 | 
						||
  if (!typeface) {
 | 
						||
    return false;
 | 
						||
  }
 | 
						||
 | 
						||
  aSkFont = SkFont(sk_ref_sp(typeface), SkFloatToScalar(fontBase->GetSize()));
 | 
						||
  return true;
 | 
						||
}
 | 
						||
 | 
						||
// Computes data used to position the decoration line within a
 | 
						||
// SkTextBlob, data is returned through aBounds
 | 
						||
static void GetPositioning(
 | 
						||
    const nsCSSRendering::PaintDecorationLineParams& aParams, const Rect& aRect,
 | 
						||
    Float aOneCSSPixel, Float aCenterBaselineOffset, SkScalar aBounds[]) {
 | 
						||
  /**
 | 
						||
   * How Positioning in Skia Works
 | 
						||
   *  Take the letter "n" for example
 | 
						||
   *  We set textPos as 0, 0
 | 
						||
   *  This is represented in Skia like so (not to scale)
 | 
						||
   *        ^
 | 
						||
   *  -10px |  _ __
 | 
						||
   *        | | '_ \
 | 
						||
   *   -5px | | | | |
 | 
						||
   * y-axis | |_| |_|
 | 
						||
   *  (0,0) ----------------------->
 | 
						||
   *        |     5px        10px
 | 
						||
   *    5px |
 | 
						||
   *        |
 | 
						||
   *   10px |
 | 
						||
   *        v
 | 
						||
   *  0 on the x axis is a line that touches the bottom of the n
 | 
						||
   *  (0,0) is the bottom left-hand corner of the n character
 | 
						||
   *  Moving "up" from the n is going in a negative y direction
 | 
						||
   *  Moving "down" from the n is going in a positive y direction
 | 
						||
   *
 | 
						||
   *  The intercepts that are returned in this arrangement will be
 | 
						||
   *  offset by the original point it starts at. (This happens in
 | 
						||
   *  the SkipInk function below).
 | 
						||
   *
 | 
						||
   *  In Skia, text MUST be laid out such that the next character
 | 
						||
   *  in the RunBuffer is further along the x-axis than the previous
 | 
						||
   *  character, otherwise there is undefined/strange behavior.
 | 
						||
   */
 | 
						||
 | 
						||
  Float rectThickness = aParams.vertical ? aRect.Width() : aRect.Height();
 | 
						||
 | 
						||
  // the upper and lower lines/edges of the under or over line
 | 
						||
  SkScalar upperLine, lowerLine;
 | 
						||
  if (aParams.decoration == mozilla::StyleTextDecorationLine::OVERLINE) {
 | 
						||
    lowerLine =
 | 
						||
        -aParams.offset + aParams.defaultLineThickness - aCenterBaselineOffset;
 | 
						||
    upperLine = lowerLine - rectThickness;
 | 
						||
  } else {
 | 
						||
    // underlines in vertical text are offset from the center of
 | 
						||
    // the text, and not the baseline
 | 
						||
    // Skia sets the text at it's baseline so we have to offset it
 | 
						||
    // for text in vertical-* writing modes
 | 
						||
    upperLine = -aParams.offset - aCenterBaselineOffset;
 | 
						||
    lowerLine = upperLine + rectThickness;
 | 
						||
  }
 | 
						||
 | 
						||
  // set up the bounds, add in a little padding to the thickness of the line
 | 
						||
  // (unless the line is <= 1 CSS pixel thick)
 | 
						||
  Float lineThicknessPadding = aParams.lineSize.height > aOneCSSPixel
 | 
						||
                                   ? 0.25f * aParams.lineSize.height
 | 
						||
                                   : 0;
 | 
						||
  // don't allow padding greater than 0.75 CSS pixel
 | 
						||
  lineThicknessPadding = std::min(lineThicknessPadding, 0.75f * aOneCSSPixel);
 | 
						||
  aBounds[0] = upperLine - lineThicknessPadding;
 | 
						||
  aBounds[1] = lowerLine + lineThicknessPadding;
 | 
						||
}
 | 
						||
 | 
						||
// positions an individual glyph according to the given offset
 | 
						||
static SkPoint GlyphPosition(const gfxTextRun::DetailedGlyph& aGlyph,
 | 
						||
                             const SkPoint& aTextPos,
 | 
						||
                             int32_t aAppUnitsPerDevPixel) {
 | 
						||
  SkPoint point = {aGlyph.mOffset.x, aGlyph.mOffset.y};
 | 
						||
 | 
						||
  // convert to device pixels
 | 
						||
  point.fX /= (float)aAppUnitsPerDevPixel;
 | 
						||
  point.fY /= (float)aAppUnitsPerDevPixel;
 | 
						||
 | 
						||
  // add offsets
 | 
						||
  point.fX += aTextPos.fX;
 | 
						||
  point.fY += aTextPos.fY;
 | 
						||
  return point;
 | 
						||
}
 | 
						||
 | 
						||
// returns a count of all the glyphs that will be rendered
 | 
						||
// excludes ligature continuations, includes the number of individual
 | 
						||
// glyph records. This includes the number of DetailedGlyphs that a single
 | 
						||
// CompressedGlyph record points to. This function is necessary because Skia
 | 
						||
// needs the total length of glyphs to add to it's run buffer before it creates
 | 
						||
// the RunBuffer object, and this cannot be resized later.
 | 
						||
static uint32_t CountAllGlyphs(
 | 
						||
    const gfxTextRun* aTextRun,
 | 
						||
    const gfxTextRun::CompressedGlyph* aCompressedGlyph, uint32_t aStringStart,
 | 
						||
    uint32_t aStringEnd) {
 | 
						||
  uint32_t totalGlyphCount = 0;
 | 
						||
 | 
						||
  for (const gfxTextRun::CompressedGlyph* cg = aCompressedGlyph + aStringStart;
 | 
						||
       cg < aCompressedGlyph + aStringEnd; ++cg) {
 | 
						||
    totalGlyphCount += cg->IsSimpleGlyph() ? 1 : cg->GetGlyphCount();
 | 
						||
  }
 | 
						||
 | 
						||
  return totalGlyphCount;
 | 
						||
}
 | 
						||
 | 
						||
static void AddDetailedGlyph(const SkTextBlobBuilder::RunBuffer& aRunBuffer,
 | 
						||
                             const gfxTextRun::DetailedGlyph& aGlyph,
 | 
						||
                             int aIndex, float aAppUnitsPerDevPixel,
 | 
						||
                             SkPoint& aTextPos) {
 | 
						||
  // add glyph ID to the run buffer at i
 | 
						||
  aRunBuffer.glyphs[aIndex] = aGlyph.mGlyphID;
 | 
						||
 | 
						||
  // position the glyph correctly using the detailed offsets
 | 
						||
  SkPoint position = GlyphPosition(aGlyph, aTextPos, aAppUnitsPerDevPixel);
 | 
						||
  aRunBuffer.pos[2 * aIndex] = position.fX;
 | 
						||
  aRunBuffer.pos[(2 * aIndex) + 1] = position.fY;
 | 
						||
 | 
						||
  // increase aTextPos.fx by the advance
 | 
						||
  aTextPos.fX += ((float)aGlyph.mAdvance / aAppUnitsPerDevPixel);
 | 
						||
}
 | 
						||
 | 
						||
static void AddSimpleGlyph(const SkTextBlobBuilder::RunBuffer& aRunBuffer,
 | 
						||
                           const gfxTextRun::CompressedGlyph& aGlyph,
 | 
						||
                           int aIndex, float aAppUnitsPerDevPixel,
 | 
						||
                           SkPoint& aTextPos) {
 | 
						||
  aRunBuffer.glyphs[aIndex] = aGlyph.GetSimpleGlyph();
 | 
						||
 | 
						||
  // simple glyphs are offset from 0, so we'll just use textPos
 | 
						||
  aRunBuffer.pos[2 * aIndex] = aTextPos.fX;
 | 
						||
  aRunBuffer.pos[(2 * aIndex) + 1] = aTextPos.fY;
 | 
						||
 | 
						||
  // increase aTextPos.fX by the advance
 | 
						||
  aTextPos.fX += ((float)aGlyph.GetSimpleAdvance() / aAppUnitsPerDevPixel);
 | 
						||
}
 | 
						||
 | 
						||
// Sets up a Skia TextBlob of the specified font, text position, and made up of
 | 
						||
// the glyphs between aStringStart and aStringEnd. Handles RTL and LTR text
 | 
						||
// and positions each glyph within the text blob
 | 
						||
static sk_sp<const SkTextBlob> CreateTextBlob(
 | 
						||
    const gfxTextRun* aTextRun,
 | 
						||
    const gfxTextRun::CompressedGlyph* aCompressedGlyph, const SkFont& aFont,
 | 
						||
    const gfxTextRun::PropertyProvider::Spacing* aSpacing,
 | 
						||
    uint32_t aStringStart, uint32_t aStringEnd, float aAppUnitsPerDevPixel,
 | 
						||
    SkPoint& aTextPos, int32_t& aSpacingOffset) {
 | 
						||
  // allocate space for the run buffer, then fill it with the glyphs
 | 
						||
  uint32_t len =
 | 
						||
      CountAllGlyphs(aTextRun, aCompressedGlyph, aStringStart, aStringEnd);
 | 
						||
  if (len <= 0) {
 | 
						||
    return nullptr;
 | 
						||
  }
 | 
						||
 | 
						||
  SkTextBlobBuilder builder;
 | 
						||
  const SkTextBlobBuilder::RunBuffer& run = builder.allocRunPos(aFont, len);
 | 
						||
 | 
						||
  // RTL text should be read in by glyph starting at aStringEnd - 1 down until
 | 
						||
  // aStringStart.
 | 
						||
  bool isRTL = aTextRun->IsRightToLeft();
 | 
						||
  uint32_t currIndex = isRTL ? aStringEnd - 1 : aStringStart;  // textRun index
 | 
						||
  // currIndex will be advanced by |step| until it reaches |limit|, which is the
 | 
						||
  // final index to be handled (NOT one beyond the final index)
 | 
						||
  int step = isRTL ? -1 : 1;
 | 
						||
  uint32_t limit = isRTL ? aStringStart : aStringEnd - 1;
 | 
						||
 | 
						||
  uint32_t i = 0;  // index into the SkTextBlob we're building
 | 
						||
  while (true) {
 | 
						||
    // Loop exit test is below, just before we update currIndex.
 | 
						||
    aTextPos.fX +=
 | 
						||
        isRTL ? aSpacing[aSpacingOffset].mAfter / aAppUnitsPerDevPixel
 | 
						||
              : aSpacing[aSpacingOffset].mBefore / aAppUnitsPerDevPixel;
 | 
						||
 | 
						||
    if (aCompressedGlyph[currIndex].IsSimpleGlyph()) {
 | 
						||
      MOZ_ASSERT(i < len, "glyph count error!");
 | 
						||
      AddSimpleGlyph(run, aCompressedGlyph[currIndex], i, aAppUnitsPerDevPixel,
 | 
						||
                     aTextPos);
 | 
						||
      i++;
 | 
						||
    } else {
 | 
						||
      // if it's detailed, potentially add multiple into run.glyphs
 | 
						||
      uint32_t count = aCompressedGlyph[currIndex].GetGlyphCount();
 | 
						||
      if (count > 0) {
 | 
						||
        gfxTextRun::DetailedGlyph* detailGlyph =
 | 
						||
            aTextRun->GetDetailedGlyphs(currIndex);
 | 
						||
        for (uint32_t d = isRTL ? count - 1 : 0; count; count--, d += step) {
 | 
						||
          MOZ_ASSERT(i < len, "glyph count error!");
 | 
						||
          AddDetailedGlyph(run, detailGlyph[d], i, aAppUnitsPerDevPixel,
 | 
						||
                           aTextPos);
 | 
						||
          i++;
 | 
						||
        }
 | 
						||
      }
 | 
						||
    }
 | 
						||
    aTextPos.fX += isRTL
 | 
						||
                       ? aSpacing[aSpacingOffset].mBefore / aAppUnitsPerDevPixel
 | 
						||
                       : aSpacing[aSpacingOffset].mAfter / aAppUnitsPerDevPixel;
 | 
						||
    aSpacingOffset += step;
 | 
						||
 | 
						||
    if (currIndex == limit) {
 | 
						||
      break;
 | 
						||
    }
 | 
						||
    currIndex += step;
 | 
						||
  }
 | 
						||
 | 
						||
  MOZ_ASSERT(i == len, "glyph count error!");
 | 
						||
 | 
						||
  return builder.make();
 | 
						||
}
 | 
						||
 | 
						||
// Given a TextBlob, the bounding lines, and the set of current intercepts this
 | 
						||
// function adds the intercepts for the current TextBlob into the given set of
 | 
						||
// previoulsy calculated intercepts. This set is either of length 0, or a
 | 
						||
// multiple of 2 (since every intersection with a piece of text results in two
 | 
						||
// intercepts: entering/exiting)
 | 
						||
static void GetTextIntercepts(const sk_sp<const SkTextBlob>& aBlob,
 | 
						||
                              const SkScalar aBounds[],
 | 
						||
                              nsTArray<SkScalar>& aIntercepts) {
 | 
						||
  // It's possible that we'll encounter a Windows exception deep inside
 | 
						||
  // Skia's DirectWrite code while trying to get the intercepts. To avoid
 | 
						||
  // crashing in this case, catch any such exception here and discard the
 | 
						||
  // newly-added (and incompletely filled in) elements.
 | 
						||
  int count = 0;
 | 
						||
  MOZ_SEH_TRY {
 | 
						||
    // https://skia.org/user/api/SkTextBlob_Reference#Text_Blob_Text_Intercepts
 | 
						||
    count = aBlob->getIntercepts(aBounds, nullptr);
 | 
						||
    if (count < 2) {
 | 
						||
      return;
 | 
						||
    }
 | 
						||
    aBlob->getIntercepts(aBounds, aIntercepts.AppendElements(count));
 | 
						||
  }
 | 
						||
  MOZ_SEH_EXCEPT(EXCEPTION_EXECUTE_HANDLER) {
 | 
						||
    gfxCriticalNote << "Exception occurred getting text intercepts";
 | 
						||
    aIntercepts.TruncateLength(aIntercepts.Length() - count);
 | 
						||
  }
 | 
						||
}
 | 
						||
 | 
						||
// This function, given a set of intercepts that represent each intersection
 | 
						||
// between an under/overline and text, makes a series of calls to
 | 
						||
// PaintDecorationLineInternal that paints a series of clip rects which
 | 
						||
// implement the text-decoration-skip-ink property
 | 
						||
// Logic for where to place each clipped rect, and the length of each rect is
 | 
						||
// included here
 | 
						||
static void SkipInk(nsIFrame* aFrame, DrawTarget& aDrawTarget,
 | 
						||
                    const nsCSSRendering::PaintDecorationLineParams& aParams,
 | 
						||
                    const nsTArray<SkScalar>& aIntercepts, Float aPadding,
 | 
						||
                    Rect& aRect) {
 | 
						||
  nsCSSRendering::PaintDecorationLineParams clipParams = aParams;
 | 
						||
  int length = aIntercepts.Length();
 | 
						||
 | 
						||
  SkScalar startIntercept = 0;
 | 
						||
  SkScalar endIntercept = 0;
 | 
						||
 | 
						||
  // keep track of the direction we are drawing the clipped rects in
 | 
						||
  // for sideways text, our intercepts from the first glyph are actually
 | 
						||
  // decreasing (towards the top edge of the page), so we use a negative
 | 
						||
  // direction
 | 
						||
  Float dir = 1.0f;
 | 
						||
  Float lineStart = aParams.vertical ? aParams.pt.y : aParams.pt.x;
 | 
						||
  Float lineEnd = lineStart + aParams.lineSize.width;
 | 
						||
  if (aParams.sidewaysLeft) {
 | 
						||
    dir = -1.0f;
 | 
						||
    std::swap(lineStart, lineEnd);
 | 
						||
  }
 | 
						||
 | 
						||
  for (int i = 0; i <= length; i += 2) {
 | 
						||
    // handle start/end edge cases and set up general case
 | 
						||
    startIntercept = (i > 0) ? (dir * aIntercepts[i - 1]) + lineStart
 | 
						||
                             : lineStart - (dir * aPadding);
 | 
						||
    endIntercept = (i < length) ? (dir * aIntercepts[i]) + lineStart
 | 
						||
                                : lineEnd + (dir * aPadding);
 | 
						||
 | 
						||
    // remove padding at both ends for width
 | 
						||
    // the start of the line is calculated so the padding removes just
 | 
						||
    // enough so that the line starts at its normal position
 | 
						||
    clipParams.lineSize.width =
 | 
						||
        (dir * (endIntercept - startIntercept)) - (2.0 * aPadding);
 | 
						||
 | 
						||
    // Don't draw decoration lines that have a smaller width than 1, or half
 | 
						||
    // the line-end padding dimension.
 | 
						||
    if (clipParams.lineSize.width < std::max(aPadding * 0.5, 1.0)) {
 | 
						||
      continue;
 | 
						||
    }
 | 
						||
 | 
						||
    // Start the line right after the intercept's location plus room for
 | 
						||
    // padding; snap the rect edges to device pixels for consistent rendering
 | 
						||
    // of dots across separate fragments of a dotted line.
 | 
						||
    if (aParams.vertical) {
 | 
						||
      clipParams.pt.y = aParams.sidewaysLeft ? endIntercept + aPadding
 | 
						||
                                             : startIntercept + aPadding;
 | 
						||
      aRect.y = std::floor(clipParams.pt.y + 0.5);
 | 
						||
      aRect.SetBottomEdge(
 | 
						||
          std::floor(clipParams.pt.y + clipParams.lineSize.width + 0.5));
 | 
						||
    } else {
 | 
						||
      clipParams.pt.x = startIntercept + aPadding;
 | 
						||
      aRect.x = std::floor(clipParams.pt.x + 0.5);
 | 
						||
      aRect.SetRightEdge(
 | 
						||
          std::floor(clipParams.pt.x + clipParams.lineSize.width + 0.5));
 | 
						||
    }
 | 
						||
 | 
						||
    nsCSSRendering::PaintDecorationLineInternal(aFrame, aDrawTarget, clipParams,
 | 
						||
                                                aRect);
 | 
						||
  }
 | 
						||
}
 | 
						||
 | 
						||
void nsCSSRendering::PaintDecorationLine(
 | 
						||
    nsIFrame* aFrame, DrawTarget& aDrawTarget,
 | 
						||
    const PaintDecorationLineParams& aParams) {
 | 
						||
  NS_ASSERTION(aParams.style != StyleTextDecorationStyle::None,
 | 
						||
               "aStyle is none");
 | 
						||
 | 
						||
  Rect rect = ToRect(GetTextDecorationRectInternal(aParams.pt, aParams));
 | 
						||
  if (rect.IsEmpty() || !rect.Intersects(aParams.dirtyRect)) {
 | 
						||
    return;
 | 
						||
  }
 | 
						||
 | 
						||
  if (aParams.decoration != StyleTextDecorationLine::UNDERLINE &&
 | 
						||
      aParams.decoration != StyleTextDecorationLine::OVERLINE &&
 | 
						||
      aParams.decoration != StyleTextDecorationLine::LINE_THROUGH) {
 | 
						||
    MOZ_ASSERT_UNREACHABLE("Invalid text decoration value");
 | 
						||
    return;
 | 
						||
  }
 | 
						||
 | 
						||
  // Check if decoration line will skip past ascenders/descenders
 | 
						||
  // text-decoration-skip-ink only applies to overlines/underlines
 | 
						||
  mozilla::StyleTextDecorationSkipInk skipInk =
 | 
						||
      aFrame->StyleText()->mTextDecorationSkipInk;
 | 
						||
  bool skipInkEnabled =
 | 
						||
      skipInk != mozilla::StyleTextDecorationSkipInk::None &&
 | 
						||
      aParams.decoration != StyleTextDecorationLine::LINE_THROUGH;
 | 
						||
 | 
						||
  if (!skipInkEnabled || aParams.glyphRange.Length() == 0) {
 | 
						||
    PaintDecorationLineInternal(aFrame, aDrawTarget, aParams, rect);
 | 
						||
    return;
 | 
						||
  }
 | 
						||
 | 
						||
  // check if the frame is a text frame or not
 | 
						||
  nsTextFrame* textFrame = nullptr;
 | 
						||
  if (aFrame->IsTextFrame()) {
 | 
						||
    textFrame = static_cast<nsTextFrame*>(aFrame);
 | 
						||
  } else {
 | 
						||
    PaintDecorationLineInternal(aFrame, aDrawTarget, aParams, rect);
 | 
						||
    return;
 | 
						||
  }
 | 
						||
 | 
						||
  // get text run and current text offset (for line wrapping)
 | 
						||
  gfxTextRun* textRun =
 | 
						||
      textFrame->GetTextRun(nsTextFrame::TextRunType::eInflated);
 | 
						||
 | 
						||
  // used for conversions from app units to device pixels
 | 
						||
  int32_t appUnitsPerDevPixel = aFrame->PresContext()->AppUnitsPerDevPixel();
 | 
						||
 | 
						||
  // pointer to the array of glyphs for this TextRun
 | 
						||
  gfxTextRun::CompressedGlyph* characterGlyphs = textRun->GetCharacterGlyphs();
 | 
						||
 | 
						||
  // get positioning info
 | 
						||
  SkPoint textPos = {0, aParams.baselineOffset};
 | 
						||
  SkScalar bounds[] = {0, 0};
 | 
						||
  Float oneCSSPixel = aFrame->PresContext()->CSSPixelsToDevPixels(1.0f);
 | 
						||
  if (!textRun->UseCenterBaseline()) {
 | 
						||
    GetPositioning(aParams, rect, oneCSSPixel, 0, bounds);
 | 
						||
  }
 | 
						||
 | 
						||
  // array for the text intercepts
 | 
						||
  AutoTArray<SkScalar, 256> intercepts;
 | 
						||
 | 
						||
  // array for spacing data
 | 
						||
  AutoTArray<gfxTextRun::PropertyProvider::Spacing, 64> spacing;
 | 
						||
  spacing.SetLength(aParams.glyphRange.Length());
 | 
						||
  if (aParams.provider != nullptr) {
 | 
						||
    aParams.provider->GetSpacing(aParams.glyphRange, spacing.Elements());
 | 
						||
  }
 | 
						||
 | 
						||
  // loop through each glyph run
 | 
						||
  // in most cases there will only be one
 | 
						||
  bool isRTL = textRun->IsRightToLeft();
 | 
						||
  int32_t spacingOffset = isRTL ? aParams.glyphRange.Length() - 1 : 0;
 | 
						||
  gfxTextRun::GlyphRunIterator iter(textRun, aParams.glyphRange, isRTL);
 | 
						||
 | 
						||
  // For any glyph run where we don't actually do skipping, we'll need to
 | 
						||
  // advance the current position by its width.
 | 
						||
  // (For runs we do process, CreateTextBlob will update the position.)
 | 
						||
  auto currentGlyphRunAdvance = [&]() {
 | 
						||
    return textRun->GetAdvanceWidth(
 | 
						||
               gfxTextRun::Range(iter.StringStart(), iter.StringEnd()),
 | 
						||
               aParams.provider) /
 | 
						||
           appUnitsPerDevPixel;
 | 
						||
  };
 | 
						||
 | 
						||
  for (; !iter.AtEnd(); iter.NextRun()) {
 | 
						||
    if (iter.GlyphRun()->mOrientation ==
 | 
						||
            mozilla::gfx::ShapedTextFlags::TEXT_ORIENT_VERTICAL_UPRIGHT ||
 | 
						||
        (iter.GlyphRun()->mIsCJK &&
 | 
						||
         skipInk == mozilla::StyleTextDecorationSkipInk::Auto)) {
 | 
						||
      // We don't support upright text in vertical modes currently
 | 
						||
      // (see https://bugzilla.mozilla.org/show_bug.cgi?id=1572294),
 | 
						||
      // but we do need to update textPos so that following runs will be
 | 
						||
      // correctly positioned.
 | 
						||
      // We also don't apply skip-ink to CJK text runs because many fonts
 | 
						||
      // have an underline that looks really bad if this is done
 | 
						||
      // (see https://bugzilla.mozilla.org/show_bug.cgi?id=1573249),
 | 
						||
      // when skip-ink is set to 'auto'.
 | 
						||
      textPos.fX += currentGlyphRunAdvance();
 | 
						||
      continue;
 | 
						||
    }
 | 
						||
 | 
						||
    gfxFont* font = iter.GlyphRun()->mFont;
 | 
						||
    // Don't try to apply skip-ink to 'sbix' fonts like Apple Color Emoji,
 | 
						||
    // because old macOS (10.9) may crash trying to retrieve glyph paths
 | 
						||
    // that don't exist.
 | 
						||
    if (font->GetFontEntry()->HasFontTable(TRUETYPE_TAG('s', 'b', 'i', 'x'))) {
 | 
						||
      textPos.fX += currentGlyphRunAdvance();
 | 
						||
      continue;
 | 
						||
    }
 | 
						||
 | 
						||
    // get a Skia version of the glyph run's font
 | 
						||
    SkFont skiafont;
 | 
						||
    if (!GetSkFontFromGfxFont(aDrawTarget, font, skiafont)) {
 | 
						||
      PaintDecorationLineInternal(aFrame, aDrawTarget, aParams, rect);
 | 
						||
      return;
 | 
						||
    }
 | 
						||
 | 
						||
    // Create a text blob with correctly positioned glyphs. This also updates
 | 
						||
    // textPos.fX with the advance of the glyphs.
 | 
						||
    sk_sp<const SkTextBlob> textBlob =
 | 
						||
        CreateTextBlob(textRun, characterGlyphs, skiafont, spacing.Elements(),
 | 
						||
                       iter.StringStart(), iter.StringEnd(),
 | 
						||
                       (float)appUnitsPerDevPixel, textPos, spacingOffset);
 | 
						||
 | 
						||
    if (!textBlob) {
 | 
						||
      textPos.fX += currentGlyphRunAdvance();
 | 
						||
      continue;
 | 
						||
    }
 | 
						||
 | 
						||
    if (textRun->UseCenterBaseline()) {
 | 
						||
      // writing modes that use a center baseline need to be adjusted on a
 | 
						||
      // font-by-font basis since Skia lines up the text on a alphabetic
 | 
						||
      // baseline, but for some vertical-* writing modes the offset is from the
 | 
						||
      // center.
 | 
						||
      gfxFont::Metrics metrics = font->GetMetrics(nsFontMetrics::eHorizontal);
 | 
						||
      Float centerToBaseline = (metrics.emAscent - metrics.emDescent) / 2.0f;
 | 
						||
      GetPositioning(aParams, rect, oneCSSPixel, centerToBaseline, bounds);
 | 
						||
    }
 | 
						||
 | 
						||
    // compute the text intercepts that need to be skipped
 | 
						||
    GetTextIntercepts(textBlob, bounds, intercepts);
 | 
						||
  }
 | 
						||
  bool needsSkipInk = intercepts.Length() > 0;
 | 
						||
 | 
						||
  if (needsSkipInk) {
 | 
						||
    // Padding between glyph intercepts and the decoration line: we use the
 | 
						||
    // decoration line thickness, clamped to a minimum of 1px and a maximum
 | 
						||
    // of 0.2em.
 | 
						||
    Float padding =
 | 
						||
        std::min(std::max(aParams.lineSize.height, oneCSSPixel),
 | 
						||
                 Float(textRun->GetFontGroup()->GetStyle()->size / 5.0));
 | 
						||
    SkipInk(aFrame, aDrawTarget, aParams, intercepts, padding, rect);
 | 
						||
  } else {
 | 
						||
    PaintDecorationLineInternal(aFrame, aDrawTarget, aParams, rect);
 | 
						||
  }
 | 
						||
}
 | 
						||
 | 
						||
void nsCSSRendering::PaintDecorationLineInternal(
 | 
						||
    nsIFrame* aFrame, DrawTarget& aDrawTarget,
 | 
						||
    const PaintDecorationLineParams& aParams, Rect aRect) {
 | 
						||
  Float lineThickness = std::max(NS_round(aParams.lineSize.height), 1.0);
 | 
						||
 | 
						||
  DeviceColor color = ToDeviceColor(aParams.color);
 | 
						||
  ColorPattern colorPat(color);
 | 
						||
  StrokeOptions strokeOptions(lineThickness);
 | 
						||
  DrawOptions drawOptions;
 | 
						||
 | 
						||
  Float dash[2];
 | 
						||
 | 
						||
  AutoPopClips autoPopClips(&aDrawTarget);
 | 
						||
 | 
						||
  mozilla::layout::TextDrawTarget* textDrawer = nullptr;
 | 
						||
  if (aDrawTarget.GetBackendType() == BackendType::WEBRENDER_TEXT) {
 | 
						||
    textDrawer = static_cast<mozilla::layout::TextDrawTarget*>(&aDrawTarget);
 | 
						||
  }
 | 
						||
 | 
						||
  switch (aParams.style) {
 | 
						||
    case StyleTextDecorationStyle::Solid:
 | 
						||
    case StyleTextDecorationStyle::Double:
 | 
						||
      break;
 | 
						||
    case StyleTextDecorationStyle::Dashed: {
 | 
						||
      autoPopClips.PushClipRect(aRect);
 | 
						||
      Float dashWidth = lineThickness * DOT_LENGTH * DASH_LENGTH;
 | 
						||
      dash[0] = dashWidth;
 | 
						||
      dash[1] = dashWidth;
 | 
						||
      strokeOptions.mDashPattern = dash;
 | 
						||
      strokeOptions.mDashLength = MOZ_ARRAY_LENGTH(dash);
 | 
						||
      strokeOptions.mLineCap = CapStyle::BUTT;
 | 
						||
      aRect = ExpandPaintingRectForDecorationLine(
 | 
						||
          aFrame, aParams.style, aRect, aParams.icoordInFrame, dashWidth * 2,
 | 
						||
          aParams.vertical);
 | 
						||
      // We should continue to draw the last dash even if it is not in the rect.
 | 
						||
      aRect.width += dashWidth;
 | 
						||
      break;
 | 
						||
    }
 | 
						||
    case StyleTextDecorationStyle::Dotted: {
 | 
						||
      autoPopClips.PushClipRect(aRect);
 | 
						||
      Float dashWidth = lineThickness * DOT_LENGTH;
 | 
						||
      if (lineThickness > 2.0) {
 | 
						||
        dash[0] = 0.f;
 | 
						||
        dash[1] = dashWidth * 2.f;
 | 
						||
        strokeOptions.mLineCap = CapStyle::ROUND;
 | 
						||
      } else {
 | 
						||
        dash[0] = dashWidth;
 | 
						||
        dash[1] = dashWidth;
 | 
						||
      }
 | 
						||
      strokeOptions.mDashPattern = dash;
 | 
						||
      strokeOptions.mDashLength = MOZ_ARRAY_LENGTH(dash);
 | 
						||
      aRect = ExpandPaintingRectForDecorationLine(
 | 
						||
          aFrame, aParams.style, aRect, aParams.icoordInFrame, dashWidth * 2,
 | 
						||
          aParams.vertical);
 | 
						||
      // We should continue to draw the last dot even if it is not in the rect.
 | 
						||
      aRect.width += dashWidth;
 | 
						||
      break;
 | 
						||
    }
 | 
						||
    case StyleTextDecorationStyle::Wavy:
 | 
						||
      autoPopClips.PushClipRect(aRect);
 | 
						||
      if (lineThickness > 2.0) {
 | 
						||
        drawOptions.mAntialiasMode = AntialiasMode::SUBPIXEL;
 | 
						||
      } else {
 | 
						||
        // Don't use anti-aliasing here.  Because looks like lighter color wavy
 | 
						||
        // line at this case.  And probably, users don't think the
 | 
						||
        // non-anti-aliased wavy line is not pretty.
 | 
						||
        drawOptions.mAntialiasMode = AntialiasMode::NONE;
 | 
						||
      }
 | 
						||
      break;
 | 
						||
    default:
 | 
						||
      NS_ERROR("Invalid style value!");
 | 
						||
      return;
 | 
						||
  }
 | 
						||
 | 
						||
  // The block-direction position should be set to the middle of the line.
 | 
						||
  if (aParams.vertical) {
 | 
						||
    aRect.x += lineThickness / 2;
 | 
						||
  } else {
 | 
						||
    aRect.y += lineThickness / 2;
 | 
						||
  }
 | 
						||
 | 
						||
  switch (aParams.style) {
 | 
						||
    case StyleTextDecorationStyle::Solid:
 | 
						||
    case StyleTextDecorationStyle::Dotted:
 | 
						||
    case StyleTextDecorationStyle::Dashed: {
 | 
						||
      Point p1 = aRect.TopLeft();
 | 
						||
      Point p2 = aParams.vertical ? aRect.BottomLeft() : aRect.TopRight();
 | 
						||
      if (textDrawer) {
 | 
						||
        textDrawer->AppendDecoration(p1, p2, lineThickness, aParams.vertical,
 | 
						||
                                     color, aParams.style);
 | 
						||
      } else {
 | 
						||
        aDrawTarget.StrokeLine(p1, p2, colorPat, strokeOptions, drawOptions);
 | 
						||
      }
 | 
						||
      return;
 | 
						||
    }
 | 
						||
    case StyleTextDecorationStyle::Double: {
 | 
						||
      /**
 | 
						||
       *  We are drawing double line as:
 | 
						||
       *
 | 
						||
       * +-------------------------------------------+
 | 
						||
       * |XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX| ^
 | 
						||
       * |XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX| | lineThickness
 | 
						||
       * |XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX| v
 | 
						||
       * |                                           |
 | 
						||
       * |                                           |
 | 
						||
       * |XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX| ^
 | 
						||
       * |XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX| | lineThickness
 | 
						||
       * |XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX| v
 | 
						||
       * +-------------------------------------------+
 | 
						||
       */
 | 
						||
      Point p1a = aRect.TopLeft();
 | 
						||
      Point p2a = aParams.vertical ? aRect.BottomLeft() : aRect.TopRight();
 | 
						||
 | 
						||
      if (aParams.vertical) {
 | 
						||
        aRect.width -= lineThickness;
 | 
						||
      } else {
 | 
						||
        aRect.height -= lineThickness;
 | 
						||
      }
 | 
						||
 | 
						||
      Point p1b = aParams.vertical ? aRect.TopRight() : aRect.BottomLeft();
 | 
						||
      Point p2b = aRect.BottomRight();
 | 
						||
 | 
						||
      if (textDrawer) {
 | 
						||
        textDrawer->AppendDecoration(p1a, p2a, lineThickness, aParams.vertical,
 | 
						||
                                     color, StyleTextDecorationStyle::Solid);
 | 
						||
        textDrawer->AppendDecoration(p1b, p2b, lineThickness, aParams.vertical,
 | 
						||
                                     color, StyleTextDecorationStyle::Solid);
 | 
						||
      } else {
 | 
						||
        aDrawTarget.StrokeLine(p1a, p2a, colorPat, strokeOptions, drawOptions);
 | 
						||
        aDrawTarget.StrokeLine(p1b, p2b, colorPat, strokeOptions, drawOptions);
 | 
						||
      }
 | 
						||
      return;
 | 
						||
    }
 | 
						||
    case StyleTextDecorationStyle::Wavy: {
 | 
						||
      /**
 | 
						||
       *  We are drawing wavy line as:
 | 
						||
       *
 | 
						||
       *  P: Path, X: Painted pixel
 | 
						||
       *
 | 
						||
       *     +---------------------------------------+
 | 
						||
       *   XX|X            XXXXXX            XXXXXX  |
 | 
						||
       *   PP|PX          XPPPPPPX          XPPPPPPX |    ^
 | 
						||
       *   XX|XPX        XPXXXXXXPX        XPXXXXXXPX|    |
 | 
						||
       *     | XPX      XPX      XPX      XPX      XP|X   |adv
 | 
						||
       *     |  XPXXXXXXPX        XPXXXXXXPX        X|PX  |
 | 
						||
       *     |   XPPPPPPX          XPPPPPPX          |XPX v
 | 
						||
       *     |    XXXXXX            XXXXXX           | XX
 | 
						||
       *     +---------------------------------------+
 | 
						||
       *      <---><--->                                ^
 | 
						||
       *      adv  flatLengthAtVertex                   rightMost
 | 
						||
       *
 | 
						||
       *  1. Always starts from top-left of the drawing area, however, we need
 | 
						||
       *     to draw  the line from outside of the rect.  Because the start
 | 
						||
       *     point of the line is not good style if we draw from inside it.
 | 
						||
       *  2. First, draw horizontal line from outside the rect to top-left of
 | 
						||
       *     the rect;
 | 
						||
       *  3. Goes down to bottom of the area at 45 degrees.
 | 
						||
       *  4. Slides to right horizontaly, see |flatLengthAtVertex|.
 | 
						||
       *  5. Goes up to top of the area at 45 degrees.
 | 
						||
       *  6. Slides to right horizontaly.
 | 
						||
       *  7. Repeat from 2 until reached to right-most edge of the area.
 | 
						||
       *
 | 
						||
       * In the vertical case, swap horizontal and vertical coordinates and
 | 
						||
       * directions in the above description.
 | 
						||
       */
 | 
						||
 | 
						||
      Float& rectICoord = aParams.vertical ? aRect.y : aRect.x;
 | 
						||
      Float& rectISize = aParams.vertical ? aRect.height : aRect.width;
 | 
						||
      const Float rectBSize = aParams.vertical ? aRect.width : aRect.height;
 | 
						||
 | 
						||
      const Float adv = rectBSize - lineThickness;
 | 
						||
      const Float flatLengthAtVertex =
 | 
						||
          std::max((lineThickness - 1.0) * 2.0, 1.0);
 | 
						||
 | 
						||
      // Align the start of wavy lines to the nearest ancestor block.
 | 
						||
      const Float cycleLength = 2 * (adv + flatLengthAtVertex);
 | 
						||
      aRect = ExpandPaintingRectForDecorationLine(
 | 
						||
          aFrame, aParams.style, aRect, aParams.icoordInFrame, cycleLength,
 | 
						||
          aParams.vertical);
 | 
						||
 | 
						||
      if (textDrawer) {
 | 
						||
        // Undo attempted centering
 | 
						||
        Float& rectBCoord = aParams.vertical ? aRect.x : aRect.y;
 | 
						||
        rectBCoord -= lineThickness / 2;
 | 
						||
 | 
						||
        textDrawer->AppendWavyDecoration(aRect, lineThickness, aParams.vertical,
 | 
						||
                                         color);
 | 
						||
        return;
 | 
						||
      }
 | 
						||
 | 
						||
      // figure out if we can trim whole cycles from the left and right edges
 | 
						||
      // of the line, to try and avoid creating an unnecessarily long and
 | 
						||
      // complex path (but don't do this for webrender, )
 | 
						||
      const Float dirtyRectICoord =
 | 
						||
          aParams.vertical ? aParams.dirtyRect.y : aParams.dirtyRect.x;
 | 
						||
      int32_t skipCycles = floor((dirtyRectICoord - rectICoord) / cycleLength);
 | 
						||
      if (skipCycles > 0) {
 | 
						||
        rectICoord += skipCycles * cycleLength;
 | 
						||
        rectISize -= skipCycles * cycleLength;
 | 
						||
      }
 | 
						||
 | 
						||
      rectICoord += lineThickness / 2.0;
 | 
						||
 | 
						||
      Point pt(aRect.TopLeft());
 | 
						||
      Float& ptICoord = aParams.vertical ? pt.y.value : pt.x.value;
 | 
						||
      Float& ptBCoord = aParams.vertical ? pt.x.value : pt.y.value;
 | 
						||
      if (aParams.vertical) {
 | 
						||
        ptBCoord += adv;
 | 
						||
      }
 | 
						||
      Float iCoordLimit = ptICoord + rectISize + lineThickness;
 | 
						||
 | 
						||
      const Float dirtyRectIMost = aParams.vertical ? aParams.dirtyRect.YMost()
 | 
						||
                                                    : aParams.dirtyRect.XMost();
 | 
						||
      skipCycles = floor((iCoordLimit - dirtyRectIMost) / cycleLength);
 | 
						||
      if (skipCycles > 0) {
 | 
						||
        iCoordLimit -= skipCycles * cycleLength;
 | 
						||
      }
 | 
						||
 | 
						||
      RefPtr<PathBuilder> builder = aDrawTarget.CreatePathBuilder();
 | 
						||
      RefPtr<Path> path;
 | 
						||
 | 
						||
      ptICoord -= lineThickness;
 | 
						||
      builder->MoveTo(pt);  // 1
 | 
						||
 | 
						||
      ptICoord = rectICoord;
 | 
						||
      builder->LineTo(pt);  // 2
 | 
						||
 | 
						||
      // In vertical mode, to go "down" relative to the text we need to
 | 
						||
      // decrease the block coordinate, whereas in horizontal we increase
 | 
						||
      // it. So the sense of this flag is effectively inverted.
 | 
						||
      bool goDown = !aParams.vertical;
 | 
						||
      uint32_t iter = 0;
 | 
						||
      while (ptICoord < iCoordLimit) {
 | 
						||
        if (++iter > 1000) {
 | 
						||
          // stroke the current path and start again, to avoid pathological
 | 
						||
          // behavior in cairo with huge numbers of path segments
 | 
						||
          path = builder->Finish();
 | 
						||
          aDrawTarget.Stroke(path, colorPat, strokeOptions, drawOptions);
 | 
						||
          builder = aDrawTarget.CreatePathBuilder();
 | 
						||
          builder->MoveTo(pt);
 | 
						||
          iter = 0;
 | 
						||
        }
 | 
						||
        ptICoord += adv;
 | 
						||
        ptBCoord += goDown ? adv : -adv;
 | 
						||
 | 
						||
        builder->LineTo(pt);  // 3 and 5
 | 
						||
 | 
						||
        ptICoord += flatLengthAtVertex;
 | 
						||
        builder->LineTo(pt);  // 4 and 6
 | 
						||
 | 
						||
        goDown = !goDown;
 | 
						||
      }
 | 
						||
      path = builder->Finish();
 | 
						||
      aDrawTarget.Stroke(path, colorPat, strokeOptions, drawOptions);
 | 
						||
      return;
 | 
						||
    }
 | 
						||
    default:
 | 
						||
      NS_ERROR("Invalid style value!");
 | 
						||
  }
 | 
						||
}
 | 
						||
 | 
						||
Rect nsCSSRendering::DecorationLineToPath(
 | 
						||
    const PaintDecorationLineParams& aParams) {
 | 
						||
  NS_ASSERTION(aParams.style != StyleTextDecorationStyle::None,
 | 
						||
               "aStyle is none");
 | 
						||
 | 
						||
  Rect path;  // To benefit from RVO, we return this from all return points
 | 
						||
 | 
						||
  Rect rect = ToRect(GetTextDecorationRectInternal(aParams.pt, aParams));
 | 
						||
  if (rect.IsEmpty() || !rect.Intersects(aParams.dirtyRect)) {
 | 
						||
    return path;
 | 
						||
  }
 | 
						||
 | 
						||
  if (aParams.decoration != StyleTextDecorationLine::UNDERLINE &&
 | 
						||
      aParams.decoration != StyleTextDecorationLine::OVERLINE &&
 | 
						||
      aParams.decoration != StyleTextDecorationLine::LINE_THROUGH) {
 | 
						||
    MOZ_ASSERT_UNREACHABLE("Invalid text decoration value");
 | 
						||
    return path;
 | 
						||
  }
 | 
						||
 | 
						||
  if (aParams.style != StyleTextDecorationStyle::Solid) {
 | 
						||
    // For the moment, we support only solid text decorations.
 | 
						||
    return path;
 | 
						||
  }
 | 
						||
 | 
						||
  Float lineThickness = std::max(NS_round(aParams.lineSize.height), 1.0);
 | 
						||
 | 
						||
  // The block-direction position should be set to the middle of the line.
 | 
						||
  if (aParams.vertical) {
 | 
						||
    rect.x += lineThickness / 2;
 | 
						||
    path = Rect(rect.TopLeft() - Point(lineThickness / 2, 0.0),
 | 
						||
                Size(lineThickness, rect.Height()));
 | 
						||
  } else {
 | 
						||
    rect.y += lineThickness / 2;
 | 
						||
    path = Rect(rect.TopLeft() - Point(0.0, lineThickness / 2),
 | 
						||
                Size(rect.Width(), lineThickness));
 | 
						||
  }
 | 
						||
 | 
						||
  return path;
 | 
						||
}
 | 
						||
 | 
						||
nsRect nsCSSRendering::GetTextDecorationRect(
 | 
						||
    nsPresContext* aPresContext, const DecorationRectParams& aParams) {
 | 
						||
  NS_ASSERTION(aPresContext, "aPresContext is null");
 | 
						||
  NS_ASSERTION(aParams.style != StyleTextDecorationStyle::None,
 | 
						||
               "aStyle is none");
 | 
						||
 | 
						||
  gfxRect rect = GetTextDecorationRectInternal(Point(0, 0), aParams);
 | 
						||
  // The rect values are already rounded to nearest device pixels.
 | 
						||
  nsRect r;
 | 
						||
  r.x = aPresContext->GfxUnitsToAppUnits(rect.X());
 | 
						||
  r.y = aPresContext->GfxUnitsToAppUnits(rect.Y());
 | 
						||
  r.width = aPresContext->GfxUnitsToAppUnits(rect.Width());
 | 
						||
  r.height = aPresContext->GfxUnitsToAppUnits(rect.Height());
 | 
						||
  return r;
 | 
						||
}
 | 
						||
 | 
						||
gfxRect nsCSSRendering::GetTextDecorationRectInternal(
 | 
						||
    const Point& aPt, const DecorationRectParams& aParams) {
 | 
						||
  NS_ASSERTION(aParams.style <= StyleTextDecorationStyle::Wavy,
 | 
						||
               "Invalid aStyle value");
 | 
						||
 | 
						||
  if (aParams.style == StyleTextDecorationStyle::None) {
 | 
						||
    return gfxRect(0, 0, 0, 0);
 | 
						||
  }
 | 
						||
 | 
						||
  bool canLiftUnderline = aParams.descentLimit >= 0.0;
 | 
						||
 | 
						||
  gfxFloat iCoord = aParams.vertical ? aPt.y : aPt.x;
 | 
						||
  gfxFloat bCoord = aParams.vertical ? aPt.x : aPt.y;
 | 
						||
 | 
						||
  // 'left' and 'right' are relative to the line, so for vertical writing modes
 | 
						||
  // they will actually become top and bottom of the rendered line.
 | 
						||
  // Similarly, aLineSize.width and .height are actually length and thickness
 | 
						||
  // of the line, which runs horizontally or vertically according to aVertical.
 | 
						||
  const gfxFloat left = floor(iCoord + 0.5),
 | 
						||
                 right = floor(iCoord + aParams.lineSize.width + 0.5);
 | 
						||
 | 
						||
  // We compute |r| as if for a horizontal text run, and then swap vertical
 | 
						||
  // and horizontal coordinates at the end if vertical was requested.
 | 
						||
  gfxRect r(left, 0, right - left, 0);
 | 
						||
 | 
						||
  gfxFloat lineThickness = NS_round(aParams.lineSize.height);
 | 
						||
  lineThickness = std::max(lineThickness, 1.0);
 | 
						||
  gfxFloat defaultLineThickness = NS_round(aParams.defaultLineThickness);
 | 
						||
  defaultLineThickness = std::max(defaultLineThickness, 1.0);
 | 
						||
 | 
						||
  gfxFloat ascent = NS_round(aParams.ascent);
 | 
						||
  gfxFloat descentLimit = floor(aParams.descentLimit);
 | 
						||
 | 
						||
  gfxFloat suggestedMaxRectHeight =
 | 
						||
      std::max(std::min(ascent, descentLimit), 1.0);
 | 
						||
  r.height = lineThickness;
 | 
						||
  if (aParams.style == StyleTextDecorationStyle::Double) {
 | 
						||
    /**
 | 
						||
     *  We will draw double line as:
 | 
						||
     *
 | 
						||
     * +-------------------------------------------+
 | 
						||
     * |XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX| ^
 | 
						||
     * |XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX| | lineThickness
 | 
						||
     * |XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX| v
 | 
						||
     * |                                           | ^
 | 
						||
     * |                                           | | gap
 | 
						||
     * |                                           | v
 | 
						||
     * |XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX| ^
 | 
						||
     * |XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX| | lineThickness
 | 
						||
     * |XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX| v
 | 
						||
     * +-------------------------------------------+
 | 
						||
     */
 | 
						||
    gfxFloat gap = NS_round(lineThickness / 2.0);
 | 
						||
    gap = std::max(gap, 1.0);
 | 
						||
    r.height = lineThickness * 2.0 + gap;
 | 
						||
    if (canLiftUnderline) {
 | 
						||
      if (r.Height() > suggestedMaxRectHeight) {
 | 
						||
        // Don't shrink the line height, because the thickness has some meaning.
 | 
						||
        // We can just shrink the gap at this time.
 | 
						||
        r.height = std::max(suggestedMaxRectHeight, lineThickness * 2.0 + 1.0);
 | 
						||
      }
 | 
						||
    }
 | 
						||
  } else if (aParams.style == StyleTextDecorationStyle::Wavy) {
 | 
						||
    /**
 | 
						||
     *  We will draw wavy line as:
 | 
						||
     *
 | 
						||
     * +-------------------------------------------+
 | 
						||
     * |XXXXX            XXXXXX            XXXXXX  | ^
 | 
						||
     * |XXXXXX          XXXXXXXX          XXXXXXXX | | lineThickness
 | 
						||
     * |XXXXXXX        XXXXXXXXXX        XXXXXXXXXX| v
 | 
						||
     * |     XXX      XXX      XXX      XXX      XX|
 | 
						||
     * |      XXXXXXXXXX        XXXXXXXXXX        X|
 | 
						||
     * |       XXXXXXXX          XXXXXXXX          |
 | 
						||
     * |        XXXXXX            XXXXXX           |
 | 
						||
     * +-------------------------------------------+
 | 
						||
     */
 | 
						||
    r.height = lineThickness > 2.0 ? lineThickness * 4.0 : lineThickness * 3.0;
 | 
						||
    if (canLiftUnderline) {
 | 
						||
      if (r.Height() > suggestedMaxRectHeight) {
 | 
						||
        // Don't shrink the line height even if there is not enough space,
 | 
						||
        // because the thickness has some meaning.  E.g., the 1px wavy line and
 | 
						||
        // 2px wavy line can be used for different meaning in IME selections
 | 
						||
        // at same time.
 | 
						||
        r.height = std::max(suggestedMaxRectHeight, lineThickness * 2.0);
 | 
						||
      }
 | 
						||
    }
 | 
						||
  }
 | 
						||
 | 
						||
  gfxFloat baseline = floor(bCoord + aParams.ascent + 0.5);
 | 
						||
 | 
						||
  // Calculate adjusted offset based on writing-mode/orientation and thickness
 | 
						||
  // of decoration line. The input value aParams.offset is the nominal position
 | 
						||
  // (offset from baseline) where we would draw a single, infinitely-thin line;
 | 
						||
  // but for a wavy or double line, we'll need to move the bounding rect of the
 | 
						||
  // decoration outwards from the baseline so that an underline remains below
 | 
						||
  // the glyphs, and an overline above them, despite the increased block-dir
 | 
						||
  // extent of the decoration.
 | 
						||
  //
 | 
						||
  // So adjustments by r.Height() are used to make the wider line styles (wavy
 | 
						||
  // and double) "grow" in the appropriate direction compared to the basic
 | 
						||
  // single line.
 | 
						||
  //
 | 
						||
  // Note that at this point, the decoration rect is being calculated in line-
 | 
						||
  // relative coordinates, where 'x' is line-rightwards, and 'y' is line-
 | 
						||
  // upwards. We'll swap them to be physical coords at the end.
 | 
						||
  gfxFloat offset = 0.0;
 | 
						||
 | 
						||
  if (aParams.decoration == StyleTextDecorationLine::UNDERLINE) {
 | 
						||
    offset = aParams.offset;
 | 
						||
    if (canLiftUnderline) {
 | 
						||
      if (descentLimit < -offset + r.Height()) {
 | 
						||
        // If we can ignore the offset and the decoration line is overflowing,
 | 
						||
        // we should align the bottom edge of the decoration line rect if it's
 | 
						||
        // possible.  Otherwise, we should lift up the top edge of the rect as
 | 
						||
        // far as possible.
 | 
						||
        gfxFloat offsetBottomAligned = -descentLimit + r.Height();
 | 
						||
        gfxFloat offsetTopAligned = 0.0;
 | 
						||
        offset = std::min(offsetBottomAligned, offsetTopAligned);
 | 
						||
      }
 | 
						||
    }
 | 
						||
  } else if (aParams.decoration == StyleTextDecorationLine::OVERLINE) {
 | 
						||
    // For overline, we adjust the offset by defaultlineThickness (the default
 | 
						||
    // thickness of a single decoration line) because empirically it looks
 | 
						||
    // better to draw the overline just inside rather than outside the font's
 | 
						||
    // ascent, which is what nsTextFrame passes as aParams.offset (as fonts
 | 
						||
    // don't provide an explicit overline-offset).
 | 
						||
    offset = aParams.offset - defaultLineThickness + r.Height();
 | 
						||
  } else if (aParams.decoration == StyleTextDecorationLine::LINE_THROUGH) {
 | 
						||
    // To maintain a consistent mid-point for line-through decorations,
 | 
						||
    // we adjust the offset by half of the decoration rect's height.
 | 
						||
    gfxFloat extra = floor(r.Height() / 2.0 + 0.5);
 | 
						||
    extra = std::max(extra, lineThickness);
 | 
						||
    // computes offset for when user specifies a decoration width since
 | 
						||
    // aParams.offset is derived from the font metric's line height
 | 
						||
    gfxFloat decorationThicknessOffset =
 | 
						||
        (lineThickness - defaultLineThickness) / 2.0;
 | 
						||
    offset = aParams.offset - lineThickness + extra + decorationThicknessOffset;
 | 
						||
  } else {
 | 
						||
    MOZ_ASSERT_UNREACHABLE("Invalid text decoration value");
 | 
						||
  }
 | 
						||
 | 
						||
  // Convert line-relative coordinate system (x = line-right, y = line-up)
 | 
						||
  // to physical coords, and move the decoration rect to the calculated
 | 
						||
  // offset from baseline.
 | 
						||
  if (aParams.vertical) {
 | 
						||
    std::swap(r.x, r.y);
 | 
						||
    std::swap(r.width, r.height);
 | 
						||
    // line-upwards in vertical mode = physical-right, so we /add/ offset
 | 
						||
    // to baseline. Except in sideways-lr mode, where line-upwards will be
 | 
						||
    // physical leftwards.
 | 
						||
    if (aParams.sidewaysLeft) {
 | 
						||
      r.x = baseline - floor(offset + 0.5);
 | 
						||
    } else {
 | 
						||
      r.x = baseline + floor(offset - r.Width() + 0.5);
 | 
						||
    }
 | 
						||
  } else {
 | 
						||
    // line-upwards in horizontal mode = physical-up, but our physical coord
 | 
						||
    // system works downwards, so we /subtract/ offset from baseline.
 | 
						||
    r.y = baseline - floor(offset + 0.5);
 | 
						||
  }
 | 
						||
 | 
						||
  return r;
 | 
						||
}
 | 
						||
 | 
						||
#define MAX_BLUR_RADIUS 300
 | 
						||
#define MAX_SPREAD_RADIUS 50
 | 
						||
 | 
						||
static inline gfxPoint ComputeBlurStdDev(nscoord aBlurRadius,
 | 
						||
                                         int32_t aAppUnitsPerDevPixel,
 | 
						||
                                         gfxFloat aScaleX, gfxFloat aScaleY) {
 | 
						||
  // http://dev.w3.org/csswg/css3-background/#box-shadow says that the
 | 
						||
  // standard deviation of the blur should be half the given blur value.
 | 
						||
  gfxFloat blurStdDev = gfxFloat(aBlurRadius) / gfxFloat(aAppUnitsPerDevPixel);
 | 
						||
 | 
						||
  return gfxPoint(
 | 
						||
      std::min((blurStdDev * aScaleX), gfxFloat(MAX_BLUR_RADIUS)) / 2.0,
 | 
						||
      std::min((blurStdDev * aScaleY), gfxFloat(MAX_BLUR_RADIUS)) / 2.0);
 | 
						||
}
 | 
						||
 | 
						||
static inline IntSize ComputeBlurRadius(nscoord aBlurRadius,
 | 
						||
                                        int32_t aAppUnitsPerDevPixel,
 | 
						||
                                        gfxFloat aScaleX = 1.0,
 | 
						||
                                        gfxFloat aScaleY = 1.0) {
 | 
						||
  gfxPoint scaledBlurStdDev =
 | 
						||
      ComputeBlurStdDev(aBlurRadius, aAppUnitsPerDevPixel, aScaleX, aScaleY);
 | 
						||
  return gfxAlphaBoxBlur::CalculateBlurRadius(scaledBlurStdDev);
 | 
						||
}
 | 
						||
 | 
						||
// -----
 | 
						||
// nsContextBoxBlur
 | 
						||
// -----
 | 
						||
gfxContext* nsContextBoxBlur::Init(const nsRect& aRect, nscoord aSpreadRadius,
 | 
						||
                                   nscoord aBlurRadius,
 | 
						||
                                   int32_t aAppUnitsPerDevPixel,
 | 
						||
                                   gfxContext* aDestinationCtx,
 | 
						||
                                   const nsRect& aDirtyRect,
 | 
						||
                                   const gfxRect* aSkipRect, uint32_t aFlags) {
 | 
						||
  if (aRect.IsEmpty()) {
 | 
						||
    mContext = nullptr;
 | 
						||
    return nullptr;
 | 
						||
  }
 | 
						||
 | 
						||
  IntSize blurRadius;
 | 
						||
  IntSize spreadRadius;
 | 
						||
  GetBlurAndSpreadRadius(aDestinationCtx->GetDrawTarget(), aAppUnitsPerDevPixel,
 | 
						||
                         aBlurRadius, aSpreadRadius, blurRadius, spreadRadius);
 | 
						||
 | 
						||
  mDestinationCtx = aDestinationCtx;
 | 
						||
 | 
						||
  // If not blurring, draw directly onto the destination device
 | 
						||
  if (blurRadius.width <= 0 && blurRadius.height <= 0 &&
 | 
						||
      spreadRadius.width <= 0 && spreadRadius.height <= 0 &&
 | 
						||
      !(aFlags & FORCE_MASK)) {
 | 
						||
    mContext = aDestinationCtx;
 | 
						||
    return mContext;
 | 
						||
  }
 | 
						||
 | 
						||
  // Convert from app units to device pixels
 | 
						||
  gfxRect rect = nsLayoutUtils::RectToGfxRect(aRect, aAppUnitsPerDevPixel);
 | 
						||
 | 
						||
  gfxRect dirtyRect =
 | 
						||
      nsLayoutUtils::RectToGfxRect(aDirtyRect, aAppUnitsPerDevPixel);
 | 
						||
  dirtyRect.RoundOut();
 | 
						||
 | 
						||
  gfxMatrix transform = aDestinationCtx->CurrentMatrixDouble();
 | 
						||
  rect = transform.TransformBounds(rect);
 | 
						||
 | 
						||
  mPreTransformed = !transform.IsIdentity();
 | 
						||
 | 
						||
  // Create the temporary surface for blurring
 | 
						||
  dirtyRect = transform.TransformBounds(dirtyRect);
 | 
						||
  bool useHardwareAccel = !(aFlags & DISABLE_HARDWARE_ACCELERATION_BLUR);
 | 
						||
  if (aSkipRect) {
 | 
						||
    gfxRect skipRect = transform.TransformBounds(*aSkipRect);
 | 
						||
    mOwnedContext =
 | 
						||
        mAlphaBoxBlur.Init(aDestinationCtx, rect, spreadRadius, blurRadius,
 | 
						||
                           &dirtyRect, &skipRect, useHardwareAccel);
 | 
						||
  } else {
 | 
						||
    mOwnedContext =
 | 
						||
        mAlphaBoxBlur.Init(aDestinationCtx, rect, spreadRadius, blurRadius,
 | 
						||
                           &dirtyRect, nullptr, useHardwareAccel);
 | 
						||
  }
 | 
						||
  mContext = mOwnedContext.get();
 | 
						||
 | 
						||
  if (mContext) {
 | 
						||
    // we don't need to blur if skipRect is equal to rect
 | 
						||
    // and mContext will be nullptr
 | 
						||
    mContext->Multiply(transform);
 | 
						||
  }
 | 
						||
  return mContext;
 | 
						||
}
 | 
						||
 | 
						||
void nsContextBoxBlur::DoPaint() {
 | 
						||
  if (mContext == mDestinationCtx) {
 | 
						||
    return;
 | 
						||
  }
 | 
						||
 | 
						||
  gfxContextMatrixAutoSaveRestore saveMatrix(mDestinationCtx);
 | 
						||
 | 
						||
  if (mPreTransformed) {
 | 
						||
    mDestinationCtx->SetMatrix(Matrix());
 | 
						||
  }
 | 
						||
 | 
						||
  mAlphaBoxBlur.Paint(mDestinationCtx);
 | 
						||
}
 | 
						||
 | 
						||
gfxContext* nsContextBoxBlur::GetContext() { return mContext; }
 | 
						||
 | 
						||
/* static */
 | 
						||
nsMargin nsContextBoxBlur::GetBlurRadiusMargin(nscoord aBlurRadius,
 | 
						||
                                               int32_t aAppUnitsPerDevPixel) {
 | 
						||
  IntSize blurRadius = ComputeBlurRadius(aBlurRadius, aAppUnitsPerDevPixel);
 | 
						||
 | 
						||
  nsMargin result;
 | 
						||
  result.top = result.bottom = blurRadius.height * aAppUnitsPerDevPixel;
 | 
						||
  result.left = result.right = blurRadius.width * aAppUnitsPerDevPixel;
 | 
						||
  return result;
 | 
						||
}
 | 
						||
 | 
						||
/* static */
 | 
						||
void nsContextBoxBlur::BlurRectangle(
 | 
						||
    gfxContext* aDestinationCtx, const nsRect& aRect,
 | 
						||
    int32_t aAppUnitsPerDevPixel, RectCornerRadii* aCornerRadii,
 | 
						||
    nscoord aBlurRadius, const sRGBColor& aShadowColor,
 | 
						||
    const nsRect& aDirtyRect, const gfxRect& aSkipRect) {
 | 
						||
  DrawTarget& aDestDrawTarget = *aDestinationCtx->GetDrawTarget();
 | 
						||
 | 
						||
  if (aRect.IsEmpty()) {
 | 
						||
    return;
 | 
						||
  }
 | 
						||
 | 
						||
  Rect shadowGfxRect = NSRectToRect(aRect, aAppUnitsPerDevPixel);
 | 
						||
 | 
						||
  if (aBlurRadius <= 0) {
 | 
						||
    ColorPattern color(ToDeviceColor(aShadowColor));
 | 
						||
    if (aCornerRadii) {
 | 
						||
      RefPtr<Path> roundedRect =
 | 
						||
          MakePathForRoundedRect(aDestDrawTarget, shadowGfxRect, *aCornerRadii);
 | 
						||
      aDestDrawTarget.Fill(roundedRect, color);
 | 
						||
    } else {
 | 
						||
      aDestDrawTarget.FillRect(shadowGfxRect, color);
 | 
						||
    }
 | 
						||
    return;
 | 
						||
  }
 | 
						||
 | 
						||
  gfxFloat scaleX = 1;
 | 
						||
  gfxFloat scaleY = 1;
 | 
						||
 | 
						||
  // Do blurs in device space when possible.
 | 
						||
  // Chrome/Skia always does the blurs in device space
 | 
						||
  // and will sometimes get incorrect results (e.g. rotated blurs)
 | 
						||
  gfxMatrix transform = aDestinationCtx->CurrentMatrixDouble();
 | 
						||
  // XXX: we could probably handle negative scales but for now it's easier just
 | 
						||
  // to fallback
 | 
						||
  if (!transform.HasNonAxisAlignedTransform() && transform._11 > 0.0 &&
 | 
						||
      transform._22 > 0.0) {
 | 
						||
    scaleX = transform._11;
 | 
						||
    scaleY = transform._22;
 | 
						||
    aDestinationCtx->SetMatrix(Matrix());
 | 
						||
  } else {
 | 
						||
    transform = gfxMatrix();
 | 
						||
  }
 | 
						||
 | 
						||
  gfxPoint blurStdDev =
 | 
						||
      ComputeBlurStdDev(aBlurRadius, aAppUnitsPerDevPixel, scaleX, scaleY);
 | 
						||
 | 
						||
  gfxRect dirtyRect =
 | 
						||
      nsLayoutUtils::RectToGfxRect(aDirtyRect, aAppUnitsPerDevPixel);
 | 
						||
  dirtyRect.RoundOut();
 | 
						||
 | 
						||
  gfxRect shadowThebesRect =
 | 
						||
      transform.TransformBounds(ThebesRect(shadowGfxRect));
 | 
						||
  dirtyRect = transform.TransformBounds(dirtyRect);
 | 
						||
  gfxRect skipRect = transform.TransformBounds(aSkipRect);
 | 
						||
 | 
						||
  if (aCornerRadii) {
 | 
						||
    aCornerRadii->Scale(scaleX, scaleY);
 | 
						||
  }
 | 
						||
 | 
						||
  gfxAlphaBoxBlur::BlurRectangle(aDestinationCtx, shadowThebesRect,
 | 
						||
                                 aCornerRadii, blurStdDev, aShadowColor,
 | 
						||
                                 dirtyRect, skipRect);
 | 
						||
}
 | 
						||
 | 
						||
/* static */
 | 
						||
void nsContextBoxBlur::GetBlurAndSpreadRadius(
 | 
						||
    DrawTarget* aDestDrawTarget, int32_t aAppUnitsPerDevPixel,
 | 
						||
    nscoord aBlurRadius, nscoord aSpreadRadius, IntSize& aOutBlurRadius,
 | 
						||
    IntSize& aOutSpreadRadius, bool aConstrainSpreadRadius) {
 | 
						||
  // Do blurs in device space when possible.
 | 
						||
  // Chrome/Skia always does the blurs in device space
 | 
						||
  // and will sometimes get incorrect results (e.g. rotated blurs)
 | 
						||
  Matrix transform = aDestDrawTarget->GetTransform();
 | 
						||
  // XXX: we could probably handle negative scales but for now it's easier just
 | 
						||
  // to fallback
 | 
						||
  gfxFloat scaleX, scaleY;
 | 
						||
  if (transform.HasNonAxisAlignedTransform() || transform._11 <= 0.0 ||
 | 
						||
      transform._22 <= 0.0) {
 | 
						||
    scaleX = 1;
 | 
						||
    scaleY = 1;
 | 
						||
  } else {
 | 
						||
    scaleX = transform._11;
 | 
						||
    scaleY = transform._22;
 | 
						||
  }
 | 
						||
 | 
						||
  // compute a large or smaller blur radius
 | 
						||
  aOutBlurRadius =
 | 
						||
      ComputeBlurRadius(aBlurRadius, aAppUnitsPerDevPixel, scaleX, scaleY);
 | 
						||
  aOutSpreadRadius =
 | 
						||
      IntSize(int32_t(aSpreadRadius * scaleX / aAppUnitsPerDevPixel),
 | 
						||
              int32_t(aSpreadRadius * scaleY / aAppUnitsPerDevPixel));
 | 
						||
 | 
						||
  if (aConstrainSpreadRadius) {
 | 
						||
    aOutSpreadRadius.width =
 | 
						||
        std::min(aOutSpreadRadius.width, int32_t(MAX_SPREAD_RADIUS));
 | 
						||
    aOutSpreadRadius.height =
 | 
						||
        std::min(aOutSpreadRadius.height, int32_t(MAX_SPREAD_RADIUS));
 | 
						||
  }
 | 
						||
}
 | 
						||
 | 
						||
/* static */
 | 
						||
bool nsContextBoxBlur::InsetBoxBlur(
 | 
						||
    gfxContext* aDestinationCtx, Rect aDestinationRect, Rect aShadowClipRect,
 | 
						||
    sRGBColor& aShadowColor, nscoord aBlurRadiusAppUnits,
 | 
						||
    nscoord aSpreadDistanceAppUnits, int32_t aAppUnitsPerDevPixel,
 | 
						||
    bool aHasBorderRadius, RectCornerRadii& aInnerClipRectRadii, Rect aSkipRect,
 | 
						||
    Point aShadowOffset) {
 | 
						||
  if (aDestinationRect.IsEmpty()) {
 | 
						||
    mContext = nullptr;
 | 
						||
    return false;
 | 
						||
  }
 | 
						||
 | 
						||
  gfxContextAutoSaveRestore autoRestore(aDestinationCtx);
 | 
						||
 | 
						||
  IntSize blurRadius;
 | 
						||
  IntSize spreadRadius;
 | 
						||
  // Convert the blur and spread radius to device pixels
 | 
						||
  bool constrainSpreadRadius = false;
 | 
						||
  GetBlurAndSpreadRadius(aDestinationCtx->GetDrawTarget(), aAppUnitsPerDevPixel,
 | 
						||
                         aBlurRadiusAppUnits, aSpreadDistanceAppUnits,
 | 
						||
                         blurRadius, spreadRadius, constrainSpreadRadius);
 | 
						||
 | 
						||
  // The blur and spread radius are scaled already, so scale all
 | 
						||
  // input data to the blur. This way, we don't have to scale the min
 | 
						||
  // inset blur to the invert of the dest context, then rescale it back
 | 
						||
  // when we draw to the destination surface.
 | 
						||
  auto scale = aDestinationCtx->CurrentMatrix().ScaleFactors();
 | 
						||
  Matrix transform = aDestinationCtx->CurrentMatrix();
 | 
						||
 | 
						||
  // XXX: we could probably handle negative scales but for now it's easier just
 | 
						||
  // to fallback
 | 
						||
  if (!transform.HasNonAxisAlignedTransform() && transform._11 > 0.0 &&
 | 
						||
      transform._22 > 0.0) {
 | 
						||
    // If we don't have a rotation, we're pre-transforming all the rects.
 | 
						||
    aDestinationCtx->SetMatrix(Matrix());
 | 
						||
  } else {
 | 
						||
    // Don't touch anything, we have a rotation.
 | 
						||
    transform = Matrix();
 | 
						||
  }
 | 
						||
 | 
						||
  Rect transformedDestRect = transform.TransformBounds(aDestinationRect);
 | 
						||
  Rect transformedShadowClipRect = transform.TransformBounds(aShadowClipRect);
 | 
						||
  Rect transformedSkipRect = transform.TransformBounds(aSkipRect);
 | 
						||
 | 
						||
  transformedDestRect.Round();
 | 
						||
  transformedShadowClipRect.Round();
 | 
						||
  transformedSkipRect.RoundIn();
 | 
						||
 | 
						||
  for (size_t i = 0; i < 4; i++) {
 | 
						||
    aInnerClipRectRadii[i].width =
 | 
						||
        std::floor(scale.xScale * aInnerClipRectRadii[i].width);
 | 
						||
    aInnerClipRectRadii[i].height =
 | 
						||
        std::floor(scale.yScale * aInnerClipRectRadii[i].height);
 | 
						||
  }
 | 
						||
 | 
						||
  mAlphaBoxBlur.BlurInsetBox(aDestinationCtx, transformedDestRect,
 | 
						||
                             transformedShadowClipRect, blurRadius,
 | 
						||
                             aShadowColor,
 | 
						||
                             aHasBorderRadius ? &aInnerClipRectRadii : nullptr,
 | 
						||
                             transformedSkipRect, aShadowOffset);
 | 
						||
  return true;
 | 
						||
}
 |