image/src/ClippedImage.cpp

Thu, 15 Jan 2015 15:59:08 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 15 Jan 2015 15:59:08 +0100
branch
TOR_BUG_9701
changeset 10
ac0c01689b40
permissions
-rw-r--r--

Implement a real Private Browsing Mode condition by changing the API/ABI;
This solves Tor bug #9701, complying with disk avoidance documented in
https://www.torproject.org/projects/torbrowser/design/#disk-avoidance.

     1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
     2 /* This Source Code Form is subject to the terms of the Mozilla Public
     3  * License, v. 2.0. If a copy of the MPL was not distributed with this
     4  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     6 #include "gfxDrawable.h"
     7 #include "gfxPlatform.h"
     8 #include "gfxUtils.h"
     9 #include "mozilla/gfx/2D.h"
    10 #include "mozilla/RefPtr.h"
    12 #include "ClippedImage.h"
    13 #include "Orientation.h"
    14 #include "SVGImageContext.h"
    16 using namespace mozilla;
    17 using namespace mozilla::gfx;
    18 using mozilla::layers::LayerManager;
    19 using mozilla::layers::ImageContainer;
    21 namespace mozilla {
    22 namespace image {
    24 class ClippedImageCachedSurface
    25 {
    26 public:
    27   ClippedImageCachedSurface(TemporaryRef<SourceSurface> aSurface,
    28                             const nsIntSize& aViewportSize,
    29                             const SVGImageContext* aSVGContext,
    30                             float aFrame,
    31                             uint32_t aFlags)
    32     : mSurface(aSurface)
    33     , mViewportSize(aViewportSize)
    34     , mFrame(aFrame)
    35     , mFlags(aFlags)
    36   {
    37     MOZ_ASSERT(mSurface, "Must have a valid surface");
    38     if (aSVGContext) {
    39       mSVGContext.construct(*aSVGContext);
    40     }
    41   }
    43   bool Matches(const nsIntSize& aViewportSize,
    44                const SVGImageContext* aSVGContext,
    45                float aFrame,
    46                uint32_t aFlags)
    47   {
    48     bool matchesSVGContext = (!aSVGContext && mSVGContext.empty()) ||
    49                              *aSVGContext == mSVGContext.ref();
    50     return mViewportSize == aViewportSize &&
    51            matchesSVGContext &&
    52            mFrame == aFrame &&
    53            mFlags == aFlags;
    54   }
    56   TemporaryRef<SourceSurface> Surface() {
    57     return mSurface;
    58   }
    60 private:
    61   RefPtr<SourceSurface>              mSurface;
    62   const nsIntSize                    mViewportSize;
    63   Maybe<SVGImageContext>             mSVGContext;
    64   const float                        mFrame;
    65   const uint32_t                     mFlags;
    66 };
    68 class DrawSingleTileCallback : public gfxDrawingCallback
    69 {
    70 public:
    71   DrawSingleTileCallback(ClippedImage* aImage,
    72                          const nsIntRect& aClip,
    73                          const nsIntSize& aViewportSize,
    74                          const SVGImageContext* aSVGContext,
    75                          uint32_t aWhichFrame,
    76                          uint32_t aFlags)
    77     : mImage(aImage)
    78     , mClip(aClip)
    79     , mViewportSize(aViewportSize)
    80     , mSVGContext(aSVGContext)
    81     , mWhichFrame(aWhichFrame)
    82     , mFlags(aFlags)
    83   {
    84     MOZ_ASSERT(mImage, "Must have an image to clip");
    85   }
    87   virtual bool operator()(gfxContext* aContext,
    88                           const gfxRect& aFillRect,
    89                           const GraphicsFilter& aFilter,
    90                           const gfxMatrix& aTransform)
    91   {
    92     // Draw the image. |gfxCallbackDrawable| always calls this function with
    93     // arguments that guarantee we never tile.
    94     mImage->DrawSingleTile(aContext, aFilter, aTransform, aFillRect, mClip,
    95                            mViewportSize, mSVGContext, mWhichFrame, mFlags);
    97     return true;
    98   }
   100 private:
   101   nsRefPtr<ClippedImage> mImage;
   102   const nsIntRect        mClip;
   103   const nsIntSize        mViewportSize;
   104   const SVGImageContext* mSVGContext;
   105   const uint32_t         mWhichFrame;
   106   const uint32_t         mFlags;
   107 };
   109 ClippedImage::ClippedImage(Image* aImage,
   110                            nsIntRect aClip)
   111   : ImageWrapper(aImage)
   112   , mClip(aClip)
   113 {
   114   MOZ_ASSERT(aImage != nullptr, "ClippedImage requires an existing Image");
   115 }
   117 ClippedImage::~ClippedImage()
   118 { }
   120 bool
   121 ClippedImage::ShouldClip()
   122 {
   123   // We need to evaluate the clipping region against the image's width and height
   124   // once they're available to determine if it's valid and whether we actually
   125   // need to do any work. We may fail if the image's width and height aren't
   126   // available yet, in which case we'll try again later.
   127   if (mShouldClip.empty()) {
   128     int32_t width, height;
   129     nsRefPtr<imgStatusTracker> innerImageStatusTracker =
   130       InnerImage()->GetStatusTracker();
   131     if (InnerImage()->HasError()) {
   132       // If there's a problem with the inner image we'll let it handle everything.
   133       mShouldClip.construct(false);
   134     } else if (NS_SUCCEEDED(InnerImage()->GetWidth(&width)) && width > 0 &&
   135                NS_SUCCEEDED(InnerImage()->GetHeight(&height)) && height > 0) {
   136       // Clamp the clipping region to the size of the underlying image.
   137       mClip = mClip.Intersect(nsIntRect(0, 0, width, height));
   139       // If the clipping region is the same size as the underlying image we
   140       // don't have to do anything.
   141       mShouldClip.construct(!mClip.IsEqualInterior(nsIntRect(0, 0, width, height)));
   142     } else if (innerImageStatusTracker &&
   143                innerImageStatusTracker->IsLoading()) {
   144       // The image just hasn't finished loading yet. We don't yet know whether
   145       // clipping with be needed or not for now. Just return without memoizing
   146       // anything.
   147       return false;
   148     } else {
   149       // We have a fully loaded image without a clearly defined width and
   150       // height. This can happen with SVG images.
   151       mShouldClip.construct(false);
   152     }
   153   }
   155   MOZ_ASSERT(!mShouldClip.empty(), "Should have computed a result");
   156   return mShouldClip.ref();
   157 }
   159 NS_IMPL_ISUPPORTS(ClippedImage, imgIContainer)
   161 nsIntRect
   162 ClippedImage::FrameRect(uint32_t aWhichFrame)
   163 {
   164   if (!ShouldClip()) {
   165     return InnerImage()->FrameRect(aWhichFrame);
   166   }
   168   return nsIntRect(0, 0, mClip.width, mClip.height);
   169 }
   171 NS_IMETHODIMP
   172 ClippedImage::GetWidth(int32_t* aWidth)
   173 {
   174   if (!ShouldClip()) {
   175     return InnerImage()->GetWidth(aWidth);
   176   }
   178   *aWidth = mClip.width;
   179   return NS_OK;
   180 }
   182 NS_IMETHODIMP
   183 ClippedImage::GetHeight(int32_t* aHeight)
   184 {
   185   if (!ShouldClip()) {
   186     return InnerImage()->GetHeight(aHeight);
   187   }
   189   *aHeight = mClip.height;
   190   return NS_OK;
   191 }
   193 NS_IMETHODIMP
   194 ClippedImage::GetIntrinsicSize(nsSize* aSize)
   195 {
   196   if (!ShouldClip()) {
   197     return InnerImage()->GetIntrinsicSize(aSize);
   198   }
   200   *aSize = nsSize(mClip.width, mClip.height);
   201   return NS_OK;
   202 }
   204 NS_IMETHODIMP
   205 ClippedImage::GetIntrinsicRatio(nsSize* aRatio)
   206 {
   207   if (!ShouldClip()) {
   208     return InnerImage()->GetIntrinsicRatio(aRatio);
   209   }
   211   *aRatio = nsSize(mClip.width, mClip.height);
   212   return NS_OK;
   213 }
   215 NS_IMETHODIMP_(TemporaryRef<SourceSurface>)
   216 ClippedImage::GetFrame(uint32_t aWhichFrame,
   217                        uint32_t aFlags)
   218 {
   219   return GetFrameInternal(mClip.Size(), nullptr, aWhichFrame, aFlags);
   220 }
   222 TemporaryRef<SourceSurface>
   223 ClippedImage::GetFrameInternal(const nsIntSize& aViewportSize,
   224                                const SVGImageContext* aSVGContext,
   225                                uint32_t aWhichFrame,
   226                                uint32_t aFlags)
   227 {
   228   if (!ShouldClip()) {
   229     return InnerImage()->GetFrame(aWhichFrame, aFlags);
   230   }
   232   float frameToDraw = InnerImage()->GetFrameIndex(aWhichFrame);
   233   if (!mCachedSurface || !mCachedSurface->Matches(aViewportSize,
   234                                                   aSVGContext,
   235                                                   frameToDraw,
   236                                                   aFlags)) {
   237     // Create a surface to draw into.
   238     RefPtr<DrawTarget> target = gfxPlatform::GetPlatform()->
   239       CreateOffscreenContentDrawTarget(IntSize(mClip.width, mClip.height),
   240                                        SurfaceFormat::B8G8R8A8);
   242     nsRefPtr<gfxContext> ctx = new gfxContext(target);
   244     // Create our callback.
   245     nsRefPtr<gfxDrawingCallback> drawTileCallback =
   246       new DrawSingleTileCallback(this, mClip, aViewportSize, aSVGContext, aWhichFrame, aFlags);
   247     nsRefPtr<gfxDrawable> drawable =
   248       new gfxCallbackDrawable(drawTileCallback, mClip.Size());
   250     // Actually draw. The callback will end up invoking DrawSingleTile.
   251     gfxRect imageRect(0, 0, mClip.width, mClip.height);
   252     gfxUtils::DrawPixelSnapped(ctx, drawable, gfxMatrix(),
   253                                imageRect, imageRect, imageRect, imageRect,
   254                                gfxImageFormat::ARGB32,
   255                                GraphicsFilter::FILTER_FAST);
   257     // Cache the resulting surface.
   258     mCachedSurface = new ClippedImageCachedSurface(target->Snapshot(),
   259                                                    aViewportSize,
   260                                                    aSVGContext,
   261                                                    frameToDraw,
   262                                                    aFlags);
   263   }
   265   MOZ_ASSERT(mCachedSurface, "Should have a cached surface now");
   266   return mCachedSurface->Surface();
   267 }
   269 NS_IMETHODIMP
   270 ClippedImage::GetImageContainer(LayerManager* aManager, ImageContainer** _retval)
   271 {
   272   // XXX(seth): We currently don't have a way of clipping the result of
   273   // GetImageContainer. We work around this by always returning null, but if it
   274   // ever turns out that ClippedImage is widely used on codepaths that can
   275   // actually benefit from GetImageContainer, it would be a good idea to fix
   276   // that method for performance reasons.
   278   if (!ShouldClip()) {
   279     return InnerImage()->GetImageContainer(aManager, _retval);
   280   }
   282   *_retval = nullptr;
   283   return NS_OK;
   284 }
   286 bool
   287 ClippedImage::MustCreateSurface(gfxContext* aContext,
   288                                 const gfxMatrix& aTransform,
   289                                 const gfxRect& aSourceRect,
   290                                 const nsIntRect& aSubimage,
   291                                 const uint32_t aFlags) const
   292 {
   293   gfxRect gfxImageRect(0, 0, mClip.width, mClip.height);
   294   nsIntRect intImageRect(0, 0, mClip.width, mClip.height);
   295   bool willTile = !gfxImageRect.Contains(aSourceRect) &&
   296                   !(aFlags & imgIContainer::FLAG_CLAMP);
   297   bool willResample = (aContext->CurrentMatrix().HasNonIntegerTranslation() ||
   298                        aTransform.HasNonIntegerTranslation()) &&
   299                       (willTile || !aSubimage.Contains(intImageRect));
   300   return willTile || willResample;
   301 }
   303 NS_IMETHODIMP
   304 ClippedImage::Draw(gfxContext* aContext,
   305                    GraphicsFilter aFilter,
   306                    const gfxMatrix& aUserSpaceToImageSpace,
   307                    const gfxRect& aFill,
   308                    const nsIntRect& aSubimage,
   309                    const nsIntSize& aViewportSize,
   310                    const SVGImageContext* aSVGContext,
   311                    uint32_t aWhichFrame,
   312                    uint32_t aFlags)
   313 {
   314   if (!ShouldClip()) {
   315     return InnerImage()->Draw(aContext, aFilter, aUserSpaceToImageSpace,
   316                               aFill, aSubimage, aViewportSize, aSVGContext,
   317                               aWhichFrame, aFlags);
   318   }
   320   // Check for tiling. If we need to tile then we need to create a
   321   // gfxCallbackDrawable to handle drawing for us.
   322   gfxRect sourceRect = aUserSpaceToImageSpace.Transform(aFill);
   323   if (MustCreateSurface(aContext, aUserSpaceToImageSpace, sourceRect, aSubimage, aFlags)) {
   324     // Create a temporary surface containing a single tile of this image.
   325     // GetFrame will call DrawSingleTile internally.
   326     RefPtr<SourceSurface> surface =
   327       GetFrameInternal(aViewportSize, aSVGContext, aWhichFrame, aFlags);
   328     NS_ENSURE_TRUE(surface, NS_ERROR_FAILURE);
   330     // Create a drawable from that surface.
   331     nsRefPtr<gfxSurfaceDrawable> drawable =
   332       new gfxSurfaceDrawable(surface, gfxIntSize(mClip.width, mClip.height));
   334     // Draw.
   335     gfxRect imageRect(0, 0, mClip.width, mClip.height);
   336     gfxRect subimage(aSubimage.x, aSubimage.y, aSubimage.width, aSubimage.height);
   337     gfxUtils::DrawPixelSnapped(aContext, drawable, aUserSpaceToImageSpace,
   338                                subimage, sourceRect, imageRect, aFill,
   339                                gfxImageFormat::ARGB32, aFilter);
   341     return NS_OK;
   342   }
   344   // Determine the appropriate subimage for the inner image.
   345   nsIntRect innerSubimage(aSubimage);
   346   innerSubimage.MoveBy(mClip.x, mClip.y);
   347   innerSubimage.Intersect(mClip);
   349   return DrawSingleTile(aContext, aFilter, aUserSpaceToImageSpace, aFill, innerSubimage,
   350                         aViewportSize, aSVGContext, aWhichFrame, aFlags);
   351 }
   353 gfxFloat
   354 ClippedImage::ClampFactor(const gfxFloat aToClamp, const int aReference) const
   355 {
   356   return aToClamp > aReference ? aReference / aToClamp
   357                                : 1.0;
   358 }
   360 nsresult
   361 ClippedImage::DrawSingleTile(gfxContext* aContext,
   362                              GraphicsFilter aFilter,
   363                              const gfxMatrix& aUserSpaceToImageSpace,
   364                              const gfxRect& aFill,
   365                              const nsIntRect& aSubimage,
   366                              const nsIntSize& aViewportSize,
   367                              const SVGImageContext* aSVGContext,
   368                              uint32_t aWhichFrame,
   369                              uint32_t aFlags)
   370 {
   371   MOZ_ASSERT(!MustCreateSurface(aContext, aUserSpaceToImageSpace,
   372                                 aUserSpaceToImageSpace.Transform(aFill),
   373                                 aSubimage - nsIntPoint(mClip.x, mClip.y), aFlags),
   374              "DrawSingleTile shouldn't need to create a surface");
   376   // Make the viewport reflect the original image's size.
   377   nsIntSize viewportSize(aViewportSize);
   378   int32_t imgWidth, imgHeight;
   379   if (NS_SUCCEEDED(InnerImage()->GetWidth(&imgWidth)) &&
   380       NS_SUCCEEDED(InnerImage()->GetHeight(&imgHeight))) {
   381     viewportSize = nsIntSize(imgWidth, imgHeight);
   382   } else {
   383     MOZ_ASSERT(false, "If ShouldClip() led us to draw then we should never get here");
   384   }
   386   // Add a translation to the transform to reflect the clipping region.
   387   gfxMatrix transform(aUserSpaceToImageSpace);
   388   transform.Multiply(gfxMatrix().Translate(gfxPoint(mClip.x, mClip.y)));
   390   // "Clamp the source rectangle" to the clipping region's width and height.
   391   // Really, this means modifying the transform to get the results we want.
   392   gfxRect sourceRect = transform.Transform(aFill);
   393   if (sourceRect.width > mClip.width || sourceRect.height > mClip.height) {
   394     gfxMatrix clampSource;
   395     clampSource.Translate(gfxPoint(sourceRect.x, sourceRect.y));
   396     clampSource.Scale(ClampFactor(sourceRect.width, mClip.width),
   397                       ClampFactor(sourceRect.height, mClip.height));
   398     clampSource.Translate(gfxPoint(-sourceRect.x, -sourceRect.y));
   399     transform.Multiply(clampSource);
   400   }
   402   return InnerImage()->Draw(aContext, aFilter, transform, aFill, aSubimage,
   403                             viewportSize, aSVGContext, aWhichFrame, aFlags);
   404 }
   406 NS_IMETHODIMP
   407 ClippedImage::RequestDiscard()
   408 {
   409   // We're very aggressive about discarding.
   410   mCachedSurface = nullptr;
   412   return InnerImage()->RequestDiscard();
   413 }
   415 NS_IMETHODIMP_(Orientation)
   416 ClippedImage::GetOrientation()
   417 {
   418   // XXX(seth): This should not actually be here; this is just to work around a
   419   // what appears to be a bug in MSVC's linker.
   420   return InnerImage()->GetOrientation();
   421 }
   423 } // namespace image
   424 } // namespace mozilla

mercurial