import FIREFOX_52_6_0esr_RELEASE from mozilla-esr52 hg repo

This commit is contained in:
Roy Tam 2018-01-19 03:59:58 +08:00
commit dcd9973243
150858 changed files with 23884658 additions and 0 deletions

View file

@ -0,0 +1,291 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* 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/. */
#include "AnimationSurfaceProvider.h"
#include "gfxPrefs.h"
#include "nsProxyRelease.h"
#include "Decoder.h"
using namespace mozilla::gfx;
namespace mozilla {
namespace image {
AnimationSurfaceProvider::AnimationSurfaceProvider(NotNull<RasterImage*> aImage,
const SurfaceKey& aSurfaceKey,
NotNull<Decoder*> aDecoder)
: ISurfaceProvider(ImageKey(aImage.get()), aSurfaceKey,
AvailabilityState::StartAsPlaceholder())
, mImage(aImage.get())
, mDecodingMutex("AnimationSurfaceProvider::mDecoder")
, mDecoder(aDecoder.get())
, mFramesMutex("AnimationSurfaceProvider::mFrames")
{
MOZ_ASSERT(!mDecoder->IsMetadataDecode(),
"Use MetadataDecodingTask for metadata decodes");
MOZ_ASSERT(!mDecoder->IsFirstFrameDecode(),
"Use DecodedSurfaceProvider for single-frame image decodes");
}
AnimationSurfaceProvider::~AnimationSurfaceProvider()
{
DropImageReference();
}
void
AnimationSurfaceProvider::DropImageReference()
{
if (!mImage) {
return; // Nothing to do.
}
// RasterImage objects need to be destroyed on the main thread. We also need
// to destroy them asynchronously, because if our surface cache entry is
// destroyed and we were the only thing keeping |mImage| alive, RasterImage's
// destructor may call into the surface cache while whatever code caused us to
// get evicted is holding the surface cache lock, causing deadlock.
RefPtr<RasterImage> image = mImage;
mImage = nullptr;
NS_ReleaseOnMainThread(image.forget(), /* aAlwaysProxy = */ true);
}
DrawableFrameRef
AnimationSurfaceProvider::DrawableRef(size_t aFrame)
{
MutexAutoLock lock(mFramesMutex);
if (Availability().IsPlaceholder()) {
MOZ_ASSERT_UNREACHABLE("Calling DrawableRef() on a placeholder");
return DrawableFrameRef();
}
if (mFrames.IsEmpty()) {
MOZ_ASSERT_UNREACHABLE("Calling DrawableRef() when we have no frames");
return DrawableFrameRef();
}
// If we don't have that frame, return an empty frame ref.
if (aFrame >= mFrames.Length()) {
return DrawableFrameRef();
}
// We've got the requested frame. Return it.
MOZ_ASSERT(mFrames[aFrame]);
return mFrames[aFrame]->DrawableRef();
}
bool
AnimationSurfaceProvider::IsFinished() const
{
MutexAutoLock lock(mFramesMutex);
if (Availability().IsPlaceholder()) {
MOZ_ASSERT_UNREACHABLE("Calling IsFinished() on a placeholder");
return false;
}
if (mFrames.IsEmpty()) {
MOZ_ASSERT_UNREACHABLE("Calling IsFinished() when we have no frames");
return false;
}
// As long as we have at least one finished frame, we're finished.
return mFrames[0]->IsFinished();
}
size_t
AnimationSurfaceProvider::LogicalSizeInBytes() const
{
// When decoding animated images, we need at most three live surfaces: the
// composited surface, the previous composited surface for
// DisposalMethod::RESTORE_PREVIOUS, and the surface we're currently decoding
// into. The composited surfaces are always BGRA. Although the surface we're
// decoding into may be paletted, and may be smaller than the real size of the
// image, we assume the worst case here.
// XXX(seth): Note that this is actually not accurate yet; we're storing the
// full sequence of frames, not just the three live surfaces mentioned above.
// Unfortunately there's no way to know in advance how many frames an
// animation has, so we really can't do better here. This will become correct
// once bug 1289954 is complete.
IntSize size = GetSurfaceKey().Size();
return 3 * size.width * size.height * sizeof(uint32_t);
}
void
AnimationSurfaceProvider::AddSizeOfExcludingThis(MallocSizeOf aMallocSizeOf,
size_t& aHeapSizeOut,
size_t& aNonHeapSizeOut)
{
// Note that the surface cache lock is already held here, and then we acquire
// mFramesMutex. For this method, this ordering is unavoidable, which means
// that we must be careful to always use the same ordering elsewhere.
MutexAutoLock lock(mFramesMutex);
for (const RawAccessFrameRef& frame : mFrames) {
frame->AddSizeOfExcludingThis(aMallocSizeOf, aHeapSizeOut, aNonHeapSizeOut);
}
}
void
AnimationSurfaceProvider::Run()
{
MutexAutoLock lock(mDecodingMutex);
if (!mDecoder || !mImage) {
MOZ_ASSERT_UNREACHABLE("Running after decoding finished?");
return;
}
while (true) {
// Run the decoder.
LexerResult result = mDecoder->Decode(WrapNotNull(this));
if (result.is<TerminalState>()) {
// We may have a new frame now, but it's not guaranteed - a decoding
// failure or truncated data may mean that no new frame got produced.
// Since we're not sure, rather than call CheckForNewFrameAtYield() here
// we call CheckForNewFrameAtTerminalState(), which handles both of these
// possibilities.
CheckForNewFrameAtTerminalState();
// We're done!
FinishDecoding();
return;
}
// Notify for the progress we've made so far.
if (mDecoder->HasProgress()) {
NotifyProgress(WrapNotNull(mImage), WrapNotNull(mDecoder));
}
if (result == LexerResult(Yield::NEED_MORE_DATA)) {
// We can't make any more progress right now. The decoder itself will ensure
// that we get reenqueued when more data is available; just return for now.
return;
}
// There's new output available - a new frame! Grab it.
MOZ_ASSERT(result == LexerResult(Yield::OUTPUT_AVAILABLE));
CheckForNewFrameAtYield();
}
}
void
AnimationSurfaceProvider::CheckForNewFrameAtYield()
{
mDecodingMutex.AssertCurrentThreadOwns();
MOZ_ASSERT(mDecoder);
bool justGotFirstFrame = false;
{
MutexAutoLock lock(mFramesMutex);
// Try to get the new frame from the decoder.
RawAccessFrameRef frame = mDecoder->GetCurrentFrameRef();
if (!frame) {
MOZ_ASSERT_UNREACHABLE("Decoder yielded but didn't produce a frame?");
return;
}
// We should've gotten a different frame than last time.
MOZ_ASSERT_IF(!mFrames.IsEmpty(),
mFrames.LastElement().get() != frame.get());
// Append the new frame to the list.
mFrames.AppendElement(Move(frame));
if (mFrames.Length() == 1) {
justGotFirstFrame = true;
}
}
if (justGotFirstFrame) {
AnnounceSurfaceAvailable();
}
}
void
AnimationSurfaceProvider::CheckForNewFrameAtTerminalState()
{
mDecodingMutex.AssertCurrentThreadOwns();
MOZ_ASSERT(mDecoder);
bool justGotFirstFrame = false;
{
MutexAutoLock lock(mFramesMutex);
RawAccessFrameRef frame = mDecoder->GetCurrentFrameRef();
if (!frame) {
return;
}
if (!mFrames.IsEmpty() && mFrames.LastElement().get() == frame.get()) {
return; // We already have this one.
}
// Append the new frame to the list.
mFrames.AppendElement(Move(frame));
if (mFrames.Length() == 1) {
justGotFirstFrame = true;
}
}
if (justGotFirstFrame) {
AnnounceSurfaceAvailable();
}
}
void
AnimationSurfaceProvider::AnnounceSurfaceAvailable()
{
mFramesMutex.AssertNotCurrentThreadOwns();
MOZ_ASSERT(mImage);
// We just got the first frame; let the surface cache know. We deliberately do
// this outside of mFramesMutex to avoid a potential deadlock with
// AddSizeOfExcludingThis(), since otherwise we'd be acquiring mFramesMutex
// and then the surface cache lock, while the memory reporting code would
// acquire the surface cache lock and then mFramesMutex.
SurfaceCache::SurfaceAvailable(WrapNotNull(this));
}
void
AnimationSurfaceProvider::FinishDecoding()
{
mDecodingMutex.AssertCurrentThreadOwns();
MOZ_ASSERT(mImage);
MOZ_ASSERT(mDecoder);
// Send notifications.
NotifyDecodeComplete(WrapNotNull(mImage), WrapNotNull(mDecoder));
// Destroy our decoder; we don't need it anymore.
mDecoder = nullptr;
// We don't need a reference to our image anymore, either, and we don't want
// one. We may be stored in the surface cache for a long time after decoding
// finishes. If we don't drop our reference to the image, we'll end up
// keeping it alive as long as we remain in the surface cache, which could
// greatly extend the image's lifetime - in fact, if the image isn't
// discardable, it'd result in a leak!
DropImageReference();
}
bool
AnimationSurfaceProvider::ShouldPreferSyncRun() const
{
MutexAutoLock lock(mDecodingMutex);
MOZ_ASSERT(mDecoder);
return mDecoder->ShouldSyncDecode(gfxPrefs::ImageMemDecodeBytesAtATime());
}
} // namespace image
} // namespace mozilla

View file

@ -0,0 +1,104 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* 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/. */
/**
* An ISurfaceProvider for animated images.
*/
#ifndef mozilla_image_AnimationSurfaceProvider_h
#define mozilla_image_AnimationSurfaceProvider_h
#include "FrameAnimator.h"
#include "IDecodingTask.h"
#include "ISurfaceProvider.h"
namespace mozilla {
namespace image {
/**
* An ISurfaceProvider that manages the decoding of animated images and
* dynamically generates surfaces for the current playback state of the
* animation.
*/
class AnimationSurfaceProvider final
: public ISurfaceProvider
, public IDecodingTask
{
public:
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(AnimationSurfaceProvider, override)
AnimationSurfaceProvider(NotNull<RasterImage*> aImage,
const SurfaceKey& aSurfaceKey,
NotNull<Decoder*> aDecoder);
//////////////////////////////////////////////////////////////////////////////
// ISurfaceProvider implementation.
//////////////////////////////////////////////////////////////////////////////
public:
// We use the ISurfaceProvider constructor of DrawableSurface to indicate that
// our surfaces are computed lazily.
DrawableSurface Surface() override { return DrawableSurface(WrapNotNull(this)); }
bool IsFinished() const override;
size_t LogicalSizeInBytes() const override;
void AddSizeOfExcludingThis(MallocSizeOf aMallocSizeOf,
size_t& aHeapSizeOut,
size_t& aNonHeapSizeOut) override;
protected:
DrawableFrameRef DrawableRef(size_t aFrame) override;
// Animation frames are always locked. This is because we only want to release
// their memory atomically (due to the surface cache discarding them). If they
// were unlocked, the OS could end up releasing the memory of random frames
// from the middle of the animation, which is not worth the complexity of
// dealing with.
bool IsLocked() const override { return true; }
void SetLocked(bool) override { }
//////////////////////////////////////////////////////////////////////////////
// IDecodingTask implementation.
//////////////////////////////////////////////////////////////////////////////
public:
void Run() override;
bool ShouldPreferSyncRun() const override;
// Full decodes are low priority compared to metadata decodes because they
// don't block layout or page load.
TaskPriority Priority() const override { return TaskPriority::eLow; }
private:
virtual ~AnimationSurfaceProvider();
void DropImageReference();
void CheckForNewFrameAtYield();
void CheckForNewFrameAtTerminalState();
void AnnounceSurfaceAvailable();
void FinishDecoding();
/// The image associated with our decoder.
RefPtr<RasterImage> mImage;
/// A mutex to protect mDecoder. Always taken before mFramesMutex.
mutable Mutex mDecodingMutex;
/// The decoder used to decode this animation.
RefPtr<Decoder> mDecoder;
/// A mutex to protect mFrames. Always taken after mDecodingMutex.
mutable Mutex mFramesMutex;
/// The frames of this animation, in order.
nsTArray<RawAccessFrameRef> mFrames;
};
} // namespace image
} // namespace mozilla
#endif // mozilla_image_AnimationSurfaceProvider_h

38
image/BMPHeaders.h Normal file
View file

@ -0,0 +1,38 @@
/* 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/. */
#ifndef mozilla_image_BMPHeaders_h
#define mozilla_image_BMPHeaders_h
#include <stddef.h>
#include <stdint.h>
namespace mozilla {
namespace image {
namespace bmp {
// The length of the file header as defined in the BMP spec.
static const size_t FILE_HEADER_LENGTH = 14;
// This lengths of the info header for the different BMP versions.
struct InfoHeaderLength {
enum {
WIN_V2 = 12,
WIN_V3 = 40,
WIN_V4 = 108,
WIN_V5 = 124,
// OS2_V1 is omitted; it's the same as WIN_V2.
OS2_V2_MIN = 16, // Minimum allowed value for OS2v2.
OS2_V2_MAX = 64, // Maximum allowed value for OS2v2.
WIN_ICO = WIN_V3,
};
};
} // namespace bmp
} // namespace image
} // namespace mozilla
#endif // mozilla_image_BMPHeaders_h

565
image/ClippedImage.cpp Normal file
View file

@ -0,0 +1,565 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* 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/. */
#include "ClippedImage.h"
#include <algorithm>
#include <new> // Workaround for bug in VS10; see bug 981264.
#include <cmath>
#include <utility>
#include "gfxDrawable.h"
#include "gfxPlatform.h"
#include "gfxUtils.h"
#include "mozilla/gfx/2D.h"
#include "mozilla/Move.h"
#include "mozilla/RefPtr.h"
#include "mozilla/Pair.h"
#include "mozilla/Tuple.h"
#include "ImageRegion.h"
#include "Orientation.h"
#include "SVGImageContext.h"
namespace mozilla {
using namespace gfx;
using layers::LayerManager;
using layers::ImageContainer;
using std::make_pair;
using std::max;
using std::modf;
using std::pair;
namespace image {
class ClippedImageCachedSurface
{
public:
ClippedImageCachedSurface(already_AddRefed<SourceSurface> aSurface,
const nsIntSize& aSize,
const Maybe<SVGImageContext>& aSVGContext,
float aFrame,
uint32_t aFlags,
DrawResult aDrawResult)
: mSurface(aSurface)
, mSize(aSize)
, mSVGContext(aSVGContext)
, mFrame(aFrame)
, mFlags(aFlags)
, mDrawResult(aDrawResult)
{
MOZ_ASSERT(mSurface, "Must have a valid surface");
}
bool Matches(const nsIntSize& aSize,
const Maybe<SVGImageContext>& aSVGContext,
float aFrame,
uint32_t aFlags) const
{
return mSize == aSize &&
mSVGContext == aSVGContext &&
mFrame == aFrame &&
mFlags == aFlags;
}
already_AddRefed<SourceSurface> Surface() const
{
RefPtr<SourceSurface> surf(mSurface);
return surf.forget();
}
DrawResult GetDrawResult() const
{
return mDrawResult;
}
bool NeedsRedraw() const
{
return mDrawResult != DrawResult::SUCCESS &&
mDrawResult != DrawResult::BAD_IMAGE;
}
private:
RefPtr<SourceSurface> mSurface;
const nsIntSize mSize;
Maybe<SVGImageContext> mSVGContext;
const float mFrame;
const uint32_t mFlags;
const DrawResult mDrawResult;
};
class DrawSingleTileCallback : public gfxDrawingCallback
{
public:
DrawSingleTileCallback(ClippedImage* aImage,
const nsIntSize& aSize,
const Maybe<SVGImageContext>& aSVGContext,
uint32_t aWhichFrame,
uint32_t aFlags)
: mImage(aImage)
, mSize(aSize)
, mSVGContext(aSVGContext)
, mWhichFrame(aWhichFrame)
, mFlags(aFlags)
, mDrawResult(DrawResult::NOT_READY)
{
MOZ_ASSERT(mImage, "Must have an image to clip");
}
virtual bool operator()(gfxContext* aContext,
const gfxRect& aFillRect,
const SamplingFilter aSamplingFilter,
const gfxMatrix& aTransform)
{
MOZ_ASSERT(aTransform.IsIdentity(),
"Caller is probably CreateSamplingRestrictedDrawable, "
"which should not happen");
// Draw the image. |gfxCallbackDrawable| always calls this function with
// arguments that guarantee we never tile.
mDrawResult =
mImage->DrawSingleTile(aContext, mSize, ImageRegion::Create(aFillRect),
mWhichFrame, aSamplingFilter, mSVGContext, mFlags);
return true;
}
DrawResult GetDrawResult() { return mDrawResult; }
private:
RefPtr<ClippedImage> mImage;
const nsIntSize mSize;
const Maybe<SVGImageContext>& mSVGContext;
const uint32_t mWhichFrame;
const uint32_t mFlags;
DrawResult mDrawResult;
};
ClippedImage::ClippedImage(Image* aImage,
nsIntRect aClip,
const Maybe<nsSize>& aSVGViewportSize)
: ImageWrapper(aImage)
, mClip(aClip)
{
MOZ_ASSERT(aImage != nullptr, "ClippedImage requires an existing Image");
MOZ_ASSERT_IF(aSVGViewportSize,
aImage->GetType() == imgIContainer::TYPE_VECTOR);
if (aSVGViewportSize) {
mSVGViewportSize = Some(aSVGViewportSize->ToNearestPixels(
nsPresContext::AppUnitsPerCSSPixel()));
}
}
ClippedImage::~ClippedImage()
{ }
bool
ClippedImage::ShouldClip()
{
// We need to evaluate the clipping region against the image's width and
// height once they're available to determine if it's valid and whether we
// actually need to do any work. We may fail if the image's width and height
// aren't available yet, in which case we'll try again later.
if (mShouldClip.isNothing()) {
int32_t width, height;
RefPtr<ProgressTracker> progressTracker =
InnerImage()->GetProgressTracker();
if (InnerImage()->HasError()) {
// If there's a problem with the inner image we'll let it handle
// everything.
mShouldClip.emplace(false);
} else if (mSVGViewportSize && !mSVGViewportSize->IsEmpty()) {
// Clamp the clipping region to the size of the SVG viewport.
nsIntRect svgViewportRect(nsIntPoint(0,0), *mSVGViewportSize);
mClip = mClip.Intersect(svgViewportRect);
// If the clipping region is the same size as the SVG viewport size
// we don't have to do anything.
mShouldClip.emplace(!mClip.IsEqualInterior(svgViewportRect));
} else if (NS_SUCCEEDED(InnerImage()->GetWidth(&width)) && width > 0 &&
NS_SUCCEEDED(InnerImage()->GetHeight(&height)) && height > 0) {
// Clamp the clipping region to the size of the underlying image.
mClip = mClip.Intersect(nsIntRect(0, 0, width, height));
// If the clipping region is the same size as the underlying image we
// don't have to do anything.
mShouldClip.emplace(!mClip.IsEqualInterior(nsIntRect(0, 0, width,
height)));
} else if (progressTracker &&
!(progressTracker->GetProgress() & FLAG_LOAD_COMPLETE)) {
// The image just hasn't finished loading yet. We don't yet know whether
// clipping with be needed or not for now. Just return without memorizing
// anything.
return false;
} else {
// We have a fully loaded image without a clearly defined width and
// height. This can happen with SVG images.
mShouldClip.emplace(false);
}
}
MOZ_ASSERT(mShouldClip.isSome(), "Should have computed a result");
return *mShouldClip;
}
NS_IMPL_ISUPPORTS_INHERITED0(ClippedImage, ImageWrapper)
NS_IMETHODIMP
ClippedImage::GetWidth(int32_t* aWidth)
{
if (!ShouldClip()) {
return InnerImage()->GetWidth(aWidth);
}
*aWidth = mClip.width;
return NS_OK;
}
NS_IMETHODIMP
ClippedImage::GetHeight(int32_t* aHeight)
{
if (!ShouldClip()) {
return InnerImage()->GetHeight(aHeight);
}
*aHeight = mClip.height;
return NS_OK;
}
NS_IMETHODIMP
ClippedImage::GetIntrinsicSize(nsSize* aSize)
{
if (!ShouldClip()) {
return InnerImage()->GetIntrinsicSize(aSize);
}
*aSize = nsSize(mClip.width, mClip.height);
return NS_OK;
}
NS_IMETHODIMP
ClippedImage::GetIntrinsicRatio(nsSize* aRatio)
{
if (!ShouldClip()) {
return InnerImage()->GetIntrinsicRatio(aRatio);
}
*aRatio = nsSize(mClip.width, mClip.height);
return NS_OK;
}
NS_IMETHODIMP_(already_AddRefed<SourceSurface>)
ClippedImage::GetFrame(uint32_t aWhichFrame,
uint32_t aFlags)
{
DrawResult result;
RefPtr<SourceSurface> surface;
Tie(result, surface) = GetFrameInternal(mClip.Size(), Nothing(), aWhichFrame, aFlags);
return surface.forget();
}
NS_IMETHODIMP_(already_AddRefed<SourceSurface>)
ClippedImage::GetFrameAtSize(const IntSize& aSize,
uint32_t aWhichFrame,
uint32_t aFlags)
{
// XXX(seth): It'd be nice to support downscale-during-decode for this case,
// but right now we just fall back to the intrinsic size.
return GetFrame(aWhichFrame, aFlags);
}
Pair<DrawResult, RefPtr<SourceSurface>>
ClippedImage::GetFrameInternal(const nsIntSize& aSize,
const Maybe<SVGImageContext>& aSVGContext,
uint32_t aWhichFrame,
uint32_t aFlags)
{
if (!ShouldClip()) {
RefPtr<SourceSurface> surface = InnerImage()->GetFrame(aWhichFrame, aFlags);
return MakePair(surface ? DrawResult::SUCCESS : DrawResult::NOT_READY,
Move(surface));
}
float frameToDraw = InnerImage()->GetFrameIndex(aWhichFrame);
if (!mCachedSurface ||
!mCachedSurface->Matches(aSize, aSVGContext, frameToDraw, aFlags) ||
mCachedSurface->NeedsRedraw()) {
// Create a surface to draw into.
RefPtr<DrawTarget> target = gfxPlatform::GetPlatform()->
CreateOffscreenContentDrawTarget(IntSize(aSize.width, aSize.height),
SurfaceFormat::B8G8R8A8);
if (!target || !target->IsValid()) {
NS_ERROR("Could not create a DrawTarget");
return MakePair(DrawResult::TEMPORARY_ERROR, RefPtr<SourceSurface>());
}
RefPtr<gfxContext> ctx = gfxContext::CreateOrNull(target);
MOZ_ASSERT(ctx); // already checked the draw target above
// Create our callback.
RefPtr<DrawSingleTileCallback> drawTileCallback =
new DrawSingleTileCallback(this, aSize, aSVGContext, aWhichFrame, aFlags);
RefPtr<gfxDrawable> drawable =
new gfxCallbackDrawable(drawTileCallback, aSize);
// Actually draw. The callback will end up invoking DrawSingleTile.
gfxUtils::DrawPixelSnapped(ctx, drawable, aSize,
ImageRegion::Create(aSize),
SurfaceFormat::B8G8R8A8,
SamplingFilter::LINEAR,
imgIContainer::FLAG_CLAMP);
// Cache the resulting surface.
mCachedSurface =
MakeUnique<ClippedImageCachedSurface>(target->Snapshot(), aSize, aSVGContext,
frameToDraw, aFlags,
drawTileCallback->GetDrawResult());
}
MOZ_ASSERT(mCachedSurface, "Should have a cached surface now");
RefPtr<SourceSurface> surface = mCachedSurface->Surface();
return MakePair(mCachedSurface->GetDrawResult(), Move(surface));
}
NS_IMETHODIMP_(bool)
ClippedImage::IsImageContainerAvailable(LayerManager* aManager, uint32_t aFlags)
{
if (!ShouldClip()) {
return InnerImage()->IsImageContainerAvailable(aManager, aFlags);
}
return false;
}
NS_IMETHODIMP_(already_AddRefed<ImageContainer>)
ClippedImage::GetImageContainer(LayerManager* aManager, uint32_t aFlags)
{
// XXX(seth): We currently don't have a way of clipping the result of
// GetImageContainer. We work around this by always returning null, but if it
// ever turns out that ClippedImage is widely used on codepaths that can
// actually benefit from GetImageContainer, it would be a good idea to fix
// that method for performance reasons.
if (!ShouldClip()) {
return InnerImage()->GetImageContainer(aManager, aFlags);
}
return nullptr;
}
static bool
MustCreateSurface(gfxContext* aContext,
const nsIntSize& aSize,
const ImageRegion& aRegion,
const uint32_t aFlags)
{
gfxRect imageRect(0, 0, aSize.width, aSize.height);
bool willTile = !imageRect.Contains(aRegion.Rect()) &&
!(aFlags & imgIContainer::FLAG_CLAMP);
bool willResample = aContext->CurrentMatrix().HasNonIntegerTranslation() &&
(willTile || !aRegion.RestrictionContains(imageRect));
return willTile || willResample;
}
NS_IMETHODIMP_(DrawResult)
ClippedImage::Draw(gfxContext* aContext,
const nsIntSize& aSize,
const ImageRegion& aRegion,
uint32_t aWhichFrame,
SamplingFilter aSamplingFilter,
const Maybe<SVGImageContext>& aSVGContext,
uint32_t aFlags)
{
if (!ShouldClip()) {
return InnerImage()->Draw(aContext, aSize, aRegion, aWhichFrame,
aSamplingFilter, aSVGContext, aFlags);
}
// Check for tiling. If we need to tile then we need to create a
// gfxCallbackDrawable to handle drawing for us.
if (MustCreateSurface(aContext, aSize, aRegion, aFlags)) {
// Create a temporary surface containing a single tile of this image.
// GetFrame will call DrawSingleTile internally.
DrawResult result;
RefPtr<SourceSurface> surface;
Tie(result, surface) =
GetFrameInternal(aSize, aSVGContext, aWhichFrame, aFlags);
if (!surface) {
MOZ_ASSERT(result != DrawResult::SUCCESS);
return result;
}
// Create a drawable from that surface.
RefPtr<gfxSurfaceDrawable> drawable =
new gfxSurfaceDrawable(surface, aSize);
// Draw.
gfxUtils::DrawPixelSnapped(aContext, drawable, aSize, aRegion,
SurfaceFormat::B8G8R8A8, aSamplingFilter);
return result;
}
return DrawSingleTile(aContext, aSize, aRegion, aWhichFrame,
aSamplingFilter, aSVGContext, aFlags);
}
DrawResult
ClippedImage::DrawSingleTile(gfxContext* aContext,
const nsIntSize& aSize,
const ImageRegion& aRegion,
uint32_t aWhichFrame,
SamplingFilter aSamplingFilter,
const Maybe<SVGImageContext>& aSVGContext,
uint32_t aFlags)
{
MOZ_ASSERT(!MustCreateSurface(aContext, aSize, aRegion, aFlags),
"Shouldn't need to create a surface");
gfxRect clip(mClip.x, mClip.y, mClip.width, mClip.height);
nsIntSize size(aSize), innerSize(aSize);
bool needScale = false;
if (mSVGViewportSize && !mSVGViewportSize->IsEmpty()) {
innerSize = *mSVGViewportSize;
needScale = true;
} else if (NS_SUCCEEDED(InnerImage()->GetWidth(&innerSize.width)) &&
NS_SUCCEEDED(InnerImage()->GetHeight(&innerSize.height))) {
needScale = true;
} else {
MOZ_ASSERT_UNREACHABLE(
"If ShouldClip() led us to draw then we should never get here");
}
if (needScale) {
double scaleX = aSize.width / clip.width;
double scaleY = aSize.height / clip.height;
// Map the clip and size to the scale requested by the caller.
clip.Scale(scaleX, scaleY);
size = innerSize;
size.Scale(scaleX, scaleY);
}
// We restrict our drawing to only the clipping region, and translate so that
// the clipping region is placed at the position the caller expects.
ImageRegion region(aRegion);
region.MoveBy(clip.x, clip.y);
region = region.Intersect(clip);
gfxContextMatrixAutoSaveRestore saveMatrix(aContext);
aContext->Multiply(gfxMatrix::Translation(-clip.x, -clip.y));
auto unclipViewport = [&](const SVGImageContext& aOldContext) {
// Map the viewport to the inner image. Note that we don't take the aSize
// parameter of imgIContainer::Draw into account, just the clipping region.
// The size in pixels at which the output will ultimately be drawn is
// irrelevant here since the purpose of the SVG viewport size is to
// determine what *region* of the SVG document will be drawn.
CSSIntSize vSize(aOldContext.GetViewportSize());
vSize.width = ceil(vSize.width * double(innerSize.width) / mClip.width);
vSize.height =
ceil(vSize.height * double(innerSize.height) / mClip.height);
return SVGImageContext(vSize,
aOldContext.GetPreserveAspectRatio());
};
return InnerImage()->Draw(aContext, size, region,
aWhichFrame, aSamplingFilter,
aSVGContext.map(unclipViewport),
aFlags);
}
NS_IMETHODIMP
ClippedImage::RequestDiscard()
{
// We're very aggressive about discarding.
mCachedSurface = nullptr;
return InnerImage()->RequestDiscard();
}
NS_IMETHODIMP_(Orientation)
ClippedImage::GetOrientation()
{
// XXX(seth): This should not actually be here; this is just to work around a
// what appears to be a bug in MSVC's linker.
return InnerImage()->GetOrientation();
}
nsIntSize
ClippedImage::OptimalImageSizeForDest(const gfxSize& aDest,
uint32_t aWhichFrame,
SamplingFilter aSamplingFilter,
uint32_t aFlags)
{
if (!ShouldClip()) {
return InnerImage()->OptimalImageSizeForDest(aDest, aWhichFrame,
aSamplingFilter, aFlags);
}
int32_t imgWidth, imgHeight;
bool needScale = false;
bool forceUniformScaling = false;
if (mSVGViewportSize && !mSVGViewportSize->IsEmpty()) {
imgWidth = mSVGViewportSize->width;
imgHeight = mSVGViewportSize->height;
needScale = true;
forceUniformScaling = (aFlags & imgIContainer::FLAG_FORCE_UNIFORM_SCALING);
} else if (NS_SUCCEEDED(InnerImage()->GetWidth(&imgWidth)) &&
NS_SUCCEEDED(InnerImage()->GetHeight(&imgHeight))) {
needScale = true;
}
if (needScale) {
// To avoid ugly sampling artifacts, ClippedImage needs the image size to
// be chosen such that the clipping region lies on pixel boundaries.
// First, we select a scale that's good for ClippedImage. An integer
// multiple of the size of the clipping region is always fine.
IntSize scale = IntSize::Ceil(aDest.width / mClip.width,
aDest.height / mClip.height);
if (forceUniformScaling) {
scale.width = scale.height = max(scale.height, scale.width);
}
// Determine the size we'd prefer to render the inner image at, and ask the
// inner image what size we should actually use.
gfxSize desiredSize(imgWidth * scale.width, imgHeight * scale.height);
nsIntSize innerDesiredSize =
InnerImage()->OptimalImageSizeForDest(desiredSize, aWhichFrame,
aSamplingFilter, aFlags);
// To get our final result, we take the inner image's desired size and
// determine how large the clipped region would be at that scale. (Again, we
// ensure an integer multiple of the size of the clipping region.)
IntSize finalScale = IntSize::Ceil(double(innerDesiredSize.width) / imgWidth,
double(innerDesiredSize.height) / imgHeight);
return mClip.Size() * finalScale;
}
MOZ_ASSERT(false,
"If ShouldClip() led us to draw then we should never get here");
return InnerImage()->OptimalImageSizeForDest(aDest, aWhichFrame,
aSamplingFilter, aFlags);
}
NS_IMETHODIMP_(nsIntRect)
ClippedImage::GetImageSpaceInvalidationRect(const nsIntRect& aRect)
{
if (!ShouldClip()) {
return InnerImage()->GetImageSpaceInvalidationRect(aRect);
}
nsIntRect rect(InnerImage()->GetImageSpaceInvalidationRect(aRect));
rect = rect.Intersect(mClip);
rect.MoveBy(-mClip.x, -mClip.y);
return rect;
}
} // namespace image
} // namespace mozilla

101
image/ClippedImage.h Normal file
View file

@ -0,0 +1,101 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* 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/. */
#ifndef mozilla_image_ClippedImage_h
#define mozilla_image_ClippedImage_h
#include "ImageWrapper.h"
#include "mozilla/gfx/2D.h"
#include "mozilla/Maybe.h"
#include "mozilla/RefPtr.h"
#include "mozilla/UniquePtr.h"
namespace mozilla {
namespace image {
class ClippedImageCachedSurface;
class DrawSingleTileCallback;
/**
* An Image wrapper that clips an image against a rectangle. Right now only
* absolute coordinates in pixels are supported.
*
* XXX(seth): There a known (performance, not correctness) issue with
* GetImageContainer. See the comments for that method for more information.
*/
class ClippedImage : public ImageWrapper
{
typedef gfx::SourceSurface SourceSurface;
public:
NS_DECL_ISUPPORTS_INHERITED
NS_IMETHOD GetWidth(int32_t* aWidth) override;
NS_IMETHOD GetHeight(int32_t* aHeight) override;
NS_IMETHOD GetIntrinsicSize(nsSize* aSize) override;
NS_IMETHOD GetIntrinsicRatio(nsSize* aRatio) override;
NS_IMETHOD_(already_AddRefed<SourceSurface>)
GetFrame(uint32_t aWhichFrame, uint32_t aFlags) override;
NS_IMETHOD_(already_AddRefed<SourceSurface>)
GetFrameAtSize(const gfx::IntSize& aSize,
uint32_t aWhichFrame,
uint32_t aFlags) override;
NS_IMETHOD_(bool) IsImageContainerAvailable(layers::LayerManager* aManager,
uint32_t aFlags) override;
NS_IMETHOD_(already_AddRefed<layers::ImageContainer>)
GetImageContainer(layers::LayerManager* aManager,
uint32_t aFlags) override;
NS_IMETHOD_(DrawResult) Draw(gfxContext* aContext,
const nsIntSize& aSize,
const ImageRegion& aRegion,
uint32_t aWhichFrame,
gfx::SamplingFilter aSamplingFilter,
const Maybe<SVGImageContext>& aSVGContext,
uint32_t aFlags) override;
NS_IMETHOD RequestDiscard() override;
NS_IMETHOD_(Orientation) GetOrientation() override;
NS_IMETHOD_(nsIntRect) GetImageSpaceInvalidationRect(const nsIntRect& aRect)
override;
nsIntSize OptimalImageSizeForDest(const gfxSize& aDest,
uint32_t aWhichFrame,
gfx::SamplingFilter aSamplingFilter,
uint32_t aFlags) override;
protected:
ClippedImage(Image* aImage, nsIntRect aClip,
const Maybe<nsSize>& aSVGViewportSize);
virtual ~ClippedImage();
private:
Pair<DrawResult, RefPtr<SourceSurface>>
GetFrameInternal(const nsIntSize& aSize,
const Maybe<SVGImageContext>& aSVGContext,
uint32_t aWhichFrame,
uint32_t aFlags);
bool ShouldClip();
DrawResult DrawSingleTile(gfxContext* aContext,
const nsIntSize& aSize,
const ImageRegion& aRegion,
uint32_t aWhichFrame,
gfx::SamplingFilter aSamplingFilter,
const Maybe<SVGImageContext>& aSVGContext,
uint32_t aFlags);
// If we are forced to draw a temporary surface, we cache it here.
UniquePtr<ClippedImageCachedSurface> mCachedSurface;
nsIntRect mClip; // The region to clip to.
Maybe<bool> mShouldClip; // Memoized ShouldClip() if present.
Maybe<nsIntSize> mSVGViewportSize; // If we're clipping a VectorImage, this
// is the size of viewport of that image.
friend class DrawSingleTileCallback;
friend class ImageOps;
};
} // namespace image
} // namespace mozilla
#endif // mozilla_image_ClippedImage_h

250
image/CopyOnWrite.h Normal file
View file

@ -0,0 +1,250 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* 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/. */
/**
* CopyOnWrite<T> allows code to safely read from a data structure without
* worrying that reentrant code will modify it.
*/
#ifndef mozilla_image_CopyOnWrite_h
#define mozilla_image_CopyOnWrite_h
#include "mozilla/RefPtr.h"
#include "MainThreadUtils.h"
#include "nsISupportsImpl.h"
namespace mozilla {
namespace image {
///////////////////////////////////////////////////////////////////////////////
// Implementation Details
///////////////////////////////////////////////////////////////////////////////
namespace detail {
template <typename T>
class CopyOnWriteValue final
{
public:
NS_INLINE_DECL_REFCOUNTING(CopyOnWriteValue)
explicit CopyOnWriteValue(T* aValue) : mValue(aValue) { }
explicit CopyOnWriteValue(already_AddRefed<T>& aValue) : mValue(aValue) { }
explicit CopyOnWriteValue(already_AddRefed<T>&& aValue) : mValue(aValue) { }
explicit CopyOnWriteValue(const RefPtr<T>& aValue) : mValue(aValue) { }
explicit CopyOnWriteValue(RefPtr<T>&& aValue) : mValue(aValue) { }
T* get() { return mValue.get(); }
const T* get() const { return mValue.get(); }
bool HasReaders() const { return mReaders > 0; }
bool HasWriter() const { return mWriter; }
bool HasUsers() const { return HasReaders() || HasWriter(); }
void LockForReading() { MOZ_ASSERT(!HasWriter()); mReaders++; }
void UnlockForReading() { MOZ_ASSERT(HasReaders()); mReaders--; }
struct MOZ_STACK_CLASS AutoReadLock
{
explicit AutoReadLock(CopyOnWriteValue* aValue)
: mValue(aValue)
{
mValue->LockForReading();
}
~AutoReadLock() { mValue->UnlockForReading(); }
CopyOnWriteValue<T>* mValue;
};
void LockForWriting() { MOZ_ASSERT(!HasUsers()); mWriter = true; }
void UnlockForWriting() { MOZ_ASSERT(HasWriter()); mWriter = false; }
struct MOZ_STACK_CLASS AutoWriteLock
{
explicit AutoWriteLock(CopyOnWriteValue* aValue)
: mValue(aValue)
{
mValue->LockForWriting();
}
~AutoWriteLock() { mValue->UnlockForWriting(); }
CopyOnWriteValue<T>* mValue;
};
private:
CopyOnWriteValue(const CopyOnWriteValue&) = delete;
CopyOnWriteValue(CopyOnWriteValue&&) = delete;
~CopyOnWriteValue() { }
RefPtr<T> mValue;
uint64_t mReaders = 0;
bool mWriter = false;
};
} // namespace detail
///////////////////////////////////////////////////////////////////////////////
// Public API
///////////////////////////////////////////////////////////////////////////////
/**
* CopyOnWrite<T> allows code to safely read from a data structure without
* worrying that reentrant code will modify it. If reentrant code would modify
* the data structure while other code is reading from it, a copy is made so
* that readers can continue to use the old version.
*
* Note that it's legal to nest a writer inside any number of readers, but
* nothing can be nested inside a writer. This is because it's assumed that the
* state of the contained data structure may not be consistent during the write.
*
* This is a main-thread-only data structure.
*
* To work with CopyOnWrite<T>, a type T needs to be reference counted and to
* support copy construction.
*/
template <typename T>
class CopyOnWrite final
{
typedef detail::CopyOnWriteValue<T> CopyOnWriteValue;
public:
explicit CopyOnWrite(T* aValue)
: mValue(new CopyOnWriteValue(aValue))
{ }
explicit CopyOnWrite(already_AddRefed<T>& aValue)
: mValue(new CopyOnWriteValue(aValue))
{ }
explicit CopyOnWrite(already_AddRefed<T>&& aValue)
: mValue(new CopyOnWriteValue(aValue))
{ }
explicit CopyOnWrite(const RefPtr<T>& aValue)
: mValue(new CopyOnWriteValue(aValue))
{ }
explicit CopyOnWrite(RefPtr<T>&& aValue)
: mValue(new CopyOnWriteValue(aValue))
{ }
/// @return true if it's safe to read at this time.
bool CanRead() const { return !mValue->HasWriter(); }
/**
* Read from the contained data structure using the function @aReader.
* @aReader will be passed a pointer of type |const T*|. It's not legal to
* call this while a writer is active.
*
* @return whatever value @aReader returns, or nothing if @aReader is a void
* function.
*/
template <typename ReadFunc>
auto Read(ReadFunc aReader) const
-> decltype(aReader(static_cast<const T*>(nullptr)))
{
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(CanRead());
// Run the provided function while holding a read lock.
RefPtr<CopyOnWriteValue> cowValue = mValue;
typename CopyOnWriteValue::AutoReadLock lock(cowValue);
return aReader(cowValue->get());
}
/**
* Read from the contained data structure using the function @aReader.
* @aReader will be passed a pointer of type |const T*|. If it's currently not
* possible to read because a writer is currently active, @aOnError will be
* called instead.
*
* @return whatever value @aReader or @aOnError returns (their return types
* must be consistent), or nothing if the provided functions are void.
*/
template <typename ReadFunc, typename ErrorFunc>
auto Read(ReadFunc aReader, ErrorFunc aOnError) const
-> decltype(aReader(static_cast<const T*>(nullptr)))
{
MOZ_ASSERT(NS_IsMainThread());
if (!CanRead()) {
return aOnError();
}
return Read(aReader);
}
/// @return true if it's safe to write at this time.
bool CanWrite() const { return !mValue->HasWriter(); }
/**
* Write to the contained data structure using the function @aWriter.
* @aWriter will be passed a pointer of type |T*|. It's not legal to call this
* while another writer is active.
*
* If readers are currently active, they will be able to continue reading from
* a copy of the old version of the data structure. The copy will be destroyed
* when all its readers finish. Later readers and writers will see the
* version of the data structure produced by the most recent call to Write().
*
* @return whatever value @aWriter returns, or nothing if @aWriter is a void
* function.
*/
template <typename WriteFunc>
auto Write(WriteFunc aWriter)
-> decltype(aWriter(static_cast<T*>(nullptr)))
{
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(CanWrite());
// If there are readers, we need to copy first.
if (mValue->HasReaders()) {
mValue = new CopyOnWriteValue(new T(*mValue->get()));
}
// Run the provided function while holding a write lock.
RefPtr<CopyOnWriteValue> cowValue = mValue;
typename CopyOnWriteValue::AutoWriteLock lock(cowValue);
return aWriter(cowValue->get());
}
/**
* Write to the contained data structure using the function @aWriter.
* @aWriter will be passed a pointer of type |T*|. If it's currently not
* possible to write because a writer is currently active, @aOnError will be
* called instead.
*
* If readers are currently active, they will be able to continue reading from
* a copy of the old version of the data structure. The copy will be destroyed
* when all its readers finish. Later readers and writers will see the
* version of the data structure produced by the most recent call to Write().
*
* @return whatever value @aWriter or @aOnError returns (their return types
* must be consistent), or nothing if the provided functions are void.
*/
template <typename WriteFunc, typename ErrorFunc>
auto Write(WriteFunc aWriter, ErrorFunc aOnError)
-> decltype(aWriter(static_cast<T*>(nullptr)))
{
MOZ_ASSERT(NS_IsMainThread());
if (!CanWrite()) {
return aOnError();
}
return Write(aWriter);
}
private:
CopyOnWrite(const CopyOnWrite&) = delete;
CopyOnWrite(CopyOnWrite&&) = delete;
RefPtr<CopyOnWriteValue> mValue;
};
} // namespace image
} // namespace mozilla
#endif // mozilla_image_CopyOnWrite_h

340
image/DecodePool.cpp Normal file
View file

@ -0,0 +1,340 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* 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/. */
#include "DecodePool.h"
#include <algorithm>
#include "mozilla/ClearOnShutdown.h"
#include "mozilla/Monitor.h"
#include "nsCOMPtr.h"
#include "nsIObserverService.h"
#include "nsIThreadPool.h"
#include "nsThreadManager.h"
#include "nsThreadUtils.h"
#include "nsXPCOMCIDInternal.h"
#include "prsystem.h"
#include "gfxPrefs.h"
#include "Decoder.h"
#include "IDecodingTask.h"
#include "RasterImage.h"
using std::max;
using std::min;
namespace mozilla {
namespace image {
///////////////////////////////////////////////////////////////////////////////
// DecodePool implementation.
///////////////////////////////////////////////////////////////////////////////
/* static */ StaticRefPtr<DecodePool> DecodePool::sSingleton;
/* static */ uint32_t DecodePool::sNumCores = 0;
NS_IMPL_ISUPPORTS(DecodePool, nsIObserver)
struct Work
{
enum class Type {
TASK,
SHUTDOWN
} mType;
RefPtr<IDecodingTask> mTask;
};
class DecodePoolImpl
{
public:
MOZ_DECLARE_REFCOUNTED_TYPENAME(DecodePoolImpl)
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(DecodePoolImpl)
DecodePoolImpl()
: mMonitor("DecodePoolImpl")
, mShuttingDown(false)
{ }
/// Initialize the current thread for use by the decode pool.
void InitCurrentThread()
{
MOZ_ASSERT(!NS_IsMainThread());
mThreadNaming.SetThreadPoolName(NS_LITERAL_CSTRING("ImgDecoder"));
}
/// Shut down the provided decode pool thread.
static void ShutdownThread(nsIThread* aThisThread)
{
// Threads have to be shut down from another thread, so we'll ask the
// main thread to do it for us.
NS_DispatchToMainThread(NewRunnableMethod(aThisThread, &nsIThread::Shutdown));
}
/**
* Requests shutdown. New work items will be dropped on the floor, and all
* decode pool threads will be shut down once existing work items have been
* processed.
*/
void RequestShutdown()
{
MonitorAutoLock lock(mMonitor);
mShuttingDown = true;
mMonitor.NotifyAll();
}
/// Pushes a new decode work item.
void PushWork(IDecodingTask* aTask)
{
MOZ_ASSERT(aTask);
RefPtr<IDecodingTask> task(aTask);
MonitorAutoLock lock(mMonitor);
if (mShuttingDown) {
// Drop any new work on the floor if we're shutting down.
return;
}
if (task->Priority() == TaskPriority::eHigh) {
mHighPriorityQueue.AppendElement(Move(task));
} else {
mLowPriorityQueue.AppendElement(Move(task));
}
mMonitor.Notify();
}
/// Pops a new work item, blocking if necessary.
Work PopWork()
{
MonitorAutoLock lock(mMonitor);
do {
if (!mHighPriorityQueue.IsEmpty()) {
return PopWorkFromQueue(mHighPriorityQueue);
}
if (!mLowPriorityQueue.IsEmpty()) {
return PopWorkFromQueue(mLowPriorityQueue);
}
if (mShuttingDown) {
Work work;
work.mType = Work::Type::SHUTDOWN;
return work;
}
// Nothing to do; block until some work is available.
mMonitor.Wait();
} while (true);
}
private:
~DecodePoolImpl() { }
Work PopWorkFromQueue(nsTArray<RefPtr<IDecodingTask>>& aQueue)
{
Work work;
work.mType = Work::Type::TASK;
work.mTask = aQueue.LastElement().forget();
aQueue.RemoveElementAt(aQueue.Length() - 1);
return work;
}
nsThreadPoolNaming mThreadNaming;
// mMonitor guards the queues and mShuttingDown.
Monitor mMonitor;
nsTArray<RefPtr<IDecodingTask>> mHighPriorityQueue;
nsTArray<RefPtr<IDecodingTask>> mLowPriorityQueue;
bool mShuttingDown;
};
class DecodePoolWorker : public Runnable
{
public:
explicit DecodePoolWorker(DecodePoolImpl* aImpl) : mImpl(aImpl) { }
NS_IMETHOD Run() override
{
MOZ_ASSERT(!NS_IsMainThread());
mImpl->InitCurrentThread();
nsCOMPtr<nsIThread> thisThread;
nsThreadManager::get().GetCurrentThread(getter_AddRefs(thisThread));
do {
Work work = mImpl->PopWork();
switch (work.mType) {
case Work::Type::TASK:
work.mTask->Run();
break;
case Work::Type::SHUTDOWN:
DecodePoolImpl::ShutdownThread(thisThread);
return NS_OK;
default:
MOZ_ASSERT_UNREACHABLE("Unknown work type");
}
} while (true);
MOZ_ASSERT_UNREACHABLE("Exiting thread without Work::Type::SHUTDOWN");
return NS_OK;
}
private:
RefPtr<DecodePoolImpl> mImpl;
};
/* static */ void
DecodePool::Initialize()
{
MOZ_ASSERT(NS_IsMainThread());
sNumCores = max<int32_t>(PR_GetNumberOfProcessors(), 1);
DecodePool::Singleton();
}
/* static */ DecodePool*
DecodePool::Singleton()
{
if (!sSingleton) {
MOZ_ASSERT(NS_IsMainThread());
sSingleton = new DecodePool();
ClearOnShutdown(&sSingleton);
}
return sSingleton;
}
/* static */ uint32_t
DecodePool::NumberOfCores()
{
return sNumCores;
}
DecodePool::DecodePool()
: mImpl(new DecodePoolImpl)
, mMutex("image::DecodePool")
{
// Determine the number of threads we want.
int32_t prefLimit = gfxPrefs::ImageMTDecodingLimit();
uint32_t limit;
if (prefLimit <= 0) {
int32_t numCores = NumberOfCores();
if (numCores <= 1) {
limit = 1;
} else if (numCores == 2) {
// On an otherwise mostly idle system, having two image decoding threads
// doubles decoding performance, so it's worth doing on dual-core devices,
// even if under load we can't actually get that level of parallelism.
limit = 2;
} else {
limit = numCores - 1;
}
} else {
limit = static_cast<uint32_t>(prefLimit);
}
if (limit > 32) {
limit = 32;
}
// Initialize the thread pool.
for (uint32_t i = 0 ; i < limit ; ++i) {
nsCOMPtr<nsIRunnable> worker = new DecodePoolWorker(mImpl);
nsCOMPtr<nsIThread> thread;
nsresult rv = NS_NewThread(getter_AddRefs(thread), worker);
MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv) && thread,
"Should successfully create image decoding threads");
mThreads.AppendElement(Move(thread));
}
// Initialize the I/O thread.
nsresult rv = NS_NewNamedThread("ImageIO", getter_AddRefs(mIOThread));
MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv) && mIOThread,
"Should successfully create image I/O thread");
nsCOMPtr<nsIObserverService> obsSvc = services::GetObserverService();
if (obsSvc) {
obsSvc->AddObserver(this, "xpcom-shutdown-threads", false);
}
}
DecodePool::~DecodePool()
{
MOZ_ASSERT(NS_IsMainThread(), "Must shut down DecodePool on main thread!");
}
NS_IMETHODIMP
DecodePool::Observe(nsISupports*, const char* aTopic, const char16_t*)
{
MOZ_ASSERT(strcmp(aTopic, "xpcom-shutdown-threads") == 0, "Unexpected topic");
nsTArray<nsCOMPtr<nsIThread>> threads;
nsCOMPtr<nsIThread> ioThread;
{
MutexAutoLock lock(mMutex);
threads.SwapElements(mThreads);
ioThread.swap(mIOThread);
}
mImpl->RequestShutdown();
for (uint32_t i = 0 ; i < threads.Length() ; ++i) {
threads[i]->Shutdown();
}
if (ioThread) {
ioThread->Shutdown();
}
return NS_OK;
}
void
DecodePool::AsyncRun(IDecodingTask* aTask)
{
MOZ_ASSERT(aTask);
mImpl->PushWork(aTask);
}
void
DecodePool::SyncRunIfPreferred(IDecodingTask* aTask)
{
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(aTask);
if (aTask->ShouldPreferSyncRun()) {
aTask->Run();
return;
}
AsyncRun(aTask);
}
void
DecodePool::SyncRunIfPossible(IDecodingTask* aTask)
{
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(aTask);
aTask->Run();
}
already_AddRefed<nsIEventTarget>
DecodePool::GetIOEventTarget()
{
MutexAutoLock threadPoolLock(mMutex);
nsCOMPtr<nsIEventTarget> target = do_QueryInterface(mIOThread);
return target.forget();
}
} // namespace image
} // namespace mozilla

105
image/DecodePool.h Normal file
View file

@ -0,0 +1,105 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* 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/. */
/**
* DecodePool manages the threads used for decoding raster images.
*/
#ifndef mozilla_image_DecodePool_h
#define mozilla_image_DecodePool_h
#include "mozilla/Mutex.h"
#include "mozilla/StaticPtr.h"
#include "nsCOMArray.h"
#include "nsCOMPtr.h"
#include "nsIEventTarget.h"
#include "nsIObserver.h"
#include "mozilla/RefPtr.h"
class nsIThread;
class nsIThreadPool;
namespace mozilla {
namespace image {
class Decoder;
class DecodePoolImpl;
class IDecodingTask;
/**
* DecodePool is a singleton class that manages decoding of raster images. It
* owns a pool of image decoding threads that are used for asynchronous
* decoding.
*
* DecodePool allows callers to run a decoder, handling management of the
* decoder's lifecycle and whether it executes on the main thread,
* off-main-thread in the image decoding thread pool, or on some combination of
* the two.
*/
class DecodePool : public nsIObserver
{
public:
NS_DECL_THREADSAFE_ISUPPORTS
NS_DECL_NSIOBSERVER
/// Initializes the singleton instance. Should be called from the main thread.
static void Initialize();
/// Returns the singleton instance.
static DecodePool* Singleton();
/// @return the number of processor cores we have available. This is not the
/// same as the number of decoding threads we're actually using.
static uint32_t NumberOfCores();
/// Ask the DecodePool to run @aTask asynchronously and return immediately.
void AsyncRun(IDecodingTask* aTask);
/**
* Run @aTask synchronously if the task would prefer it. It's up to the task
* itself to make this decision; @see IDecodingTask::ShouldPreferSyncRun(). If
* @aTask doesn't prefer it, just run @aTask asynchronously and return
* immediately.
*/
void SyncRunIfPreferred(IDecodingTask* aTask);
/**
* Run @aTask synchronously. This does not guarantee that @aTask will complete
* synchronously. If, for example, @aTask doesn't yet have the data it needs to
* run synchronously, it may recover by scheduling an async task to finish up
* the work when the remaining data is available.
*/
void SyncRunIfPossible(IDecodingTask* aTask);
/**
* Returns an event target interface to the DecodePool's I/O thread. Callers
* who want to deliver data to workers on the DecodePool can use this event
* target.
*
* @return An nsIEventTarget interface to the thread pool's I/O thread.
*/
already_AddRefed<nsIEventTarget> GetIOEventTarget();
private:
friend class DecodePoolWorker;
DecodePool();
virtual ~DecodePool();
static StaticRefPtr<DecodePool> sSingleton;
static uint32_t sNumCores;
RefPtr<DecodePoolImpl> mImpl;
// mMutex protects mThreads and mIOThread.
Mutex mMutex;
nsTArray<nsCOMPtr<nsIThread>> mThreads;
nsCOMPtr<nsIThread> mIOThread;
};
} // namespace image
} // namespace mozilla
#endif // mozilla_image_DecodePool_h

View file

@ -0,0 +1,224 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* 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/. */
#include "DecodedSurfaceProvider.h"
#include "gfxPrefs.h"
#include "nsProxyRelease.h"
#include "Decoder.h"
using namespace mozilla::gfx;
namespace mozilla {
namespace image {
DecodedSurfaceProvider::DecodedSurfaceProvider(NotNull<RasterImage*> aImage,
const SurfaceKey& aSurfaceKey,
NotNull<Decoder*> aDecoder)
: ISurfaceProvider(ImageKey(aImage.get()), aSurfaceKey,
AvailabilityState::StartAsPlaceholder())
, mImage(aImage.get())
, mMutex("mozilla::image::DecodedSurfaceProvider")
, mDecoder(aDecoder.get())
{
MOZ_ASSERT(!mDecoder->IsMetadataDecode(),
"Use MetadataDecodingTask for metadata decodes");
MOZ_ASSERT(mDecoder->IsFirstFrameDecode(),
"Use AnimationSurfaceProvider for animation decodes");
}
DecodedSurfaceProvider::~DecodedSurfaceProvider()
{
DropImageReference();
}
void
DecodedSurfaceProvider::DropImageReference()
{
if (!mImage) {
return; // Nothing to do.
}
// RasterImage objects need to be destroyed on the main thread. We also need
// to destroy them asynchronously, because if our surface cache entry is
// destroyed and we were the only thing keeping |mImage| alive, RasterImage's
// destructor may call into the surface cache while whatever code caused us to
// get evicted is holding the surface cache lock, causing deadlock.
RefPtr<RasterImage> image = mImage;
mImage = nullptr;
NS_ReleaseOnMainThread(image.forget(), /* aAlwaysProxy = */ true);
}
DrawableFrameRef
DecodedSurfaceProvider::DrawableRef(size_t aFrame)
{
MOZ_ASSERT(aFrame == 0,
"Requesting an animation frame from a DecodedSurfaceProvider?");
// We depend on SurfaceCache::SurfaceAvailable() to provide synchronization
// for methods that touch |mSurface|; after SurfaceAvailable() is called,
// |mSurface| should be non-null and shouldn't be mutated further until we get
// destroyed. That means that the assertions below are very important; we'll
// end up with data races if these assumptions are violated.
if (Availability().IsPlaceholder()) {
MOZ_ASSERT_UNREACHABLE("Calling DrawableRef() on a placeholder");
return DrawableFrameRef();
}
if (!mSurface) {
MOZ_ASSERT_UNREACHABLE("Calling DrawableRef() when we have no surface");
return DrawableFrameRef();
}
return mSurface->DrawableRef();
}
bool
DecodedSurfaceProvider::IsFinished() const
{
// See DrawableRef() for commentary on these assertions.
if (Availability().IsPlaceholder()) {
MOZ_ASSERT_UNREACHABLE("Calling IsFinished() on a placeholder");
return false;
}
if (!mSurface) {
MOZ_ASSERT_UNREACHABLE("Calling IsFinished() when we have no surface");
return false;
}
return mSurface->IsFinished();
}
void
DecodedSurfaceProvider::SetLocked(bool aLocked)
{
// See DrawableRef() for commentary on these assertions.
if (Availability().IsPlaceholder()) {
MOZ_ASSERT_UNREACHABLE("Calling SetLocked() on a placeholder");
return;
}
if (!mSurface) {
MOZ_ASSERT_UNREACHABLE("Calling SetLocked() when we have no surface");
return;
}
if (aLocked == IsLocked()) {
return; // Nothing to do.
}
// If we're locked, hold a DrawableFrameRef to |mSurface|, which will keep any
// volatile buffer it owns in memory.
mLockRef = aLocked ? mSurface->DrawableRef()
: DrawableFrameRef();
}
size_t
DecodedSurfaceProvider::LogicalSizeInBytes() const
{
// Single frame images are always 32bpp.
IntSize size = GetSurfaceKey().Size();
return size.width * size.height * sizeof(uint32_t);
}
void
DecodedSurfaceProvider::Run()
{
MutexAutoLock lock(mMutex);
if (!mDecoder || !mImage) {
MOZ_ASSERT_UNREACHABLE("Running after decoding finished?");
return;
}
// Run the decoder.
LexerResult result = mDecoder->Decode(WrapNotNull(this));
// If there's a new surface available, announce it to the surface cache.
CheckForNewSurface();
if (result.is<TerminalState>()) {
FinishDecoding();
return; // We're done.
}
// Notify for the progress we've made so far.
if (mDecoder->HasProgress()) {
NotifyProgress(WrapNotNull(mImage), WrapNotNull(mDecoder));
}
MOZ_ASSERT(result.is<Yield>());
if (result == LexerResult(Yield::NEED_MORE_DATA)) {
// We can't make any more progress right now. The decoder itself will ensure
// that we get reenqueued when more data is available; just return for now.
return;
}
// Single-frame images shouldn't yield for any reason except NEED_MORE_DATA.
MOZ_ASSERT_UNREACHABLE("Unexpected yield for single-frame image");
mDecoder->TerminateFailure();
FinishDecoding();
}
void
DecodedSurfaceProvider::CheckForNewSurface()
{
mMutex.AssertCurrentThreadOwns();
MOZ_ASSERT(mDecoder);
if (mSurface) {
// Single-frame images should produce no more than one surface, so if we
// have one, it should be the same one the decoder is working on.
MOZ_ASSERT(mSurface.get() == mDecoder->GetCurrentFrameRef().get(),
"DecodedSurfaceProvider and Decoder have different surfaces?");
return;
}
// We don't have a surface yet; try to get one from the decoder.
mSurface = mDecoder->GetCurrentFrameRef().get();
if (!mSurface) {
return; // No surface yet.
}
// We just got a surface for the first time; let the surface cache know.
MOZ_ASSERT(mImage);
SurfaceCache::SurfaceAvailable(WrapNotNull(this));
}
void
DecodedSurfaceProvider::FinishDecoding()
{
mMutex.AssertCurrentThreadOwns();
MOZ_ASSERT(mImage);
MOZ_ASSERT(mDecoder);
// Send notifications.
NotifyDecodeComplete(WrapNotNull(mImage), WrapNotNull(mDecoder));
// Destroy our decoder; we don't need it anymore. (And if we don't destroy it,
// our surface can never be optimized, because the decoder has a
// RawAccessFrameRef to it.)
mDecoder = nullptr;
// We don't need a reference to our image anymore, either, and we don't want
// one. We may be stored in the surface cache for a long time after decoding
// finishes. If we don't drop our reference to the image, we'll end up
// keeping it alive as long as we remain in the surface cache, which could
// greatly extend the image's lifetime - in fact, if the image isn't
// discardable, it'd result in a leak!
DropImageReference();
}
bool
DecodedSurfaceProvider::ShouldPreferSyncRun() const
{
return mDecoder->ShouldSyncDecode(gfxPrefs::ImageMemDecodeBytesAtATime());
}
} // namespace image
} // namespace mozilla

View file

@ -0,0 +1,89 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* 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/. */
/**
* An ISurfaceProvider implemented for single-frame decoded surfaces.
*/
#ifndef mozilla_image_DecodedSurfaceProvider_h
#define mozilla_image_DecodedSurfaceProvider_h
#include "IDecodingTask.h"
#include "ISurfaceProvider.h"
#include "SurfaceCache.h"
namespace mozilla {
namespace image {
/**
* An ISurfaceProvider that manages the decoding of a single-frame image and
* stores the resulting surface.
*/
class DecodedSurfaceProvider final
: public ISurfaceProvider
, public IDecodingTask
{
public:
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(DecodedSurfaceProvider, override)
DecodedSurfaceProvider(NotNull<RasterImage*> aImage,
const SurfaceKey& aSurfaceKey,
NotNull<Decoder*> aDecoder);
//////////////////////////////////////////////////////////////////////////////
// ISurfaceProvider implementation.
//////////////////////////////////////////////////////////////////////////////
public:
bool IsFinished() const override;
size_t LogicalSizeInBytes() const override;
protected:
DrawableFrameRef DrawableRef(size_t aFrame) override;
bool IsLocked() const override { return bool(mLockRef); }
void SetLocked(bool aLocked) override;
//////////////////////////////////////////////////////////////////////////////
// IDecodingTask implementation.
//////////////////////////////////////////////////////////////////////////////
public:
void Run() override;
bool ShouldPreferSyncRun() const override;
// Full decodes are low priority compared to metadata decodes because they
// don't block layout or page load.
TaskPriority Priority() const override { return TaskPriority::eLow; }
private:
virtual ~DecodedSurfaceProvider();
void DropImageReference();
void CheckForNewSurface();
void FinishDecoding();
/// The image associated with our decoder. Dropped after decoding.
RefPtr<RasterImage> mImage;
/// Mutex protecting access to mDecoder.
Mutex mMutex;
/// The decoder that will generate our surface. Dropped after decoding.
RefPtr<Decoder> mDecoder;
/// Our surface. Initially null until it's generated by the decoder.
RefPtr<imgFrame> mSurface;
/// A drawable reference to our service; used for locking.
DrawableFrameRef mLockRef;
};
} // namespace image
} // namespace mozilla
#endif // mozilla_image_DecodedSurfaceProvider_h

521
image/Decoder.cpp Normal file
View file

@ -0,0 +1,521 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* 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/. */
#include "Decoder.h"
#include "mozilla/gfx/2D.h"
#include "DecodePool.h"
#include "GeckoProfiler.h"
#include "IDecodingTask.h"
#include "ISurfaceProvider.h"
#include "nsProxyRelease.h"
#include "nsServiceManagerUtils.h"
#include "nsComponentManagerUtils.h"
#include "mozilla/Telemetry.h"
using mozilla::gfx::IntSize;
using mozilla::gfx::SurfaceFormat;
namespace mozilla {
namespace image {
class MOZ_STACK_CLASS AutoRecordDecoderTelemetry final
{
public:
explicit AutoRecordDecoderTelemetry(Decoder* aDecoder)
: mDecoder(aDecoder)
{
MOZ_ASSERT(mDecoder);
// Begin recording telemetry data.
mStartTime = TimeStamp::Now();
}
~AutoRecordDecoderTelemetry()
{
// Finish telemetry.
mDecoder->mDecodeTime += (TimeStamp::Now() - mStartTime);
}
private:
Decoder* mDecoder;
TimeStamp mStartTime;
};
Decoder::Decoder(RasterImage* aImage)
: mImageData(nullptr)
, mImageDataLength(0)
, mColormap(nullptr)
, mColormapSize(0)
, mImage(aImage)
, mProgress(NoProgress)
, mFrameCount(0)
, mLoopLength(FrameTimeout::Zero())
, mDecoderFlags(DefaultDecoderFlags())
, mSurfaceFlags(DefaultSurfaceFlags())
, mInitialized(false)
, mMetadataDecode(false)
, mHaveExplicitOutputSize(false)
, mInFrame(false)
, mFinishedNewFrame(false)
, mReachedTerminalState(false)
, mDecodeDone(false)
, mError(false)
, mShouldReportError(false)
{ }
Decoder::~Decoder()
{
MOZ_ASSERT(mProgress == NoProgress || !mImage,
"Destroying Decoder without taking all its progress changes");
MOZ_ASSERT(mInvalidRect.IsEmpty() || !mImage,
"Destroying Decoder without taking all its invalidations");
mInitialized = false;
if (mImage && !NS_IsMainThread()) {
// Dispatch mImage to main thread to prevent it from being destructed by the
// decode thread.
NS_ReleaseOnMainThread(mImage.forget());
}
}
/*
* Common implementation of the decoder interface.
*/
nsresult
Decoder::Init()
{
// No re-initializing
MOZ_ASSERT(!mInitialized, "Can't re-initialize a decoder!");
// All decoders must have a SourceBufferIterator.
MOZ_ASSERT(mIterator);
// Metadata decoders must not set an output size.
MOZ_ASSERT_IF(mMetadataDecode, !mHaveExplicitOutputSize);
// All decoders must be anonymous except for metadata decoders.
// XXX(seth): Soon that exception will be removed.
MOZ_ASSERT_IF(mImage, IsMetadataDecode());
// Implementation-specific initialization.
nsresult rv = InitInternal();
mInitialized = true;
return rv;
}
LexerResult
Decoder::Decode(IResumable* aOnResume /* = nullptr */)
{
MOZ_ASSERT(mInitialized, "Should be initialized here");
MOZ_ASSERT(mIterator, "Should have a SourceBufferIterator");
// If we're already done, don't attempt to keep decoding.
if (GetDecodeDone()) {
return LexerResult(HasError() ? TerminalState::FAILURE
: TerminalState::SUCCESS);
}
LexerResult lexerResult(TerminalState::FAILURE);
{
PROFILER_LABEL("ImageDecoder", "Decode", js::ProfileEntry::Category::GRAPHICS);
AutoRecordDecoderTelemetry telemetry(this);
lexerResult = DoDecode(*mIterator, aOnResume);
};
if (lexerResult.is<Yield>()) {
// We either need more data to continue (in which case either @aOnResume or
// the caller will reschedule us to run again later), or the decoder is
// yielding to allow the caller access to some intermediate output.
return lexerResult;
}
// We reached a terminal state; we're now done decoding.
MOZ_ASSERT(lexerResult.is<TerminalState>());
mReachedTerminalState = true;
// If decoding failed, record that fact.
if (lexerResult.as<TerminalState>() == TerminalState::FAILURE) {
PostError();
}
// Perform final cleanup.
CompleteDecode();
return LexerResult(HasError() ? TerminalState::FAILURE
: TerminalState::SUCCESS);
}
LexerResult
Decoder::TerminateFailure()
{
PostError();
// Perform final cleanup if need be.
if (!mReachedTerminalState) {
mReachedTerminalState = true;
CompleteDecode();
}
return LexerResult(TerminalState::FAILURE);
}
bool
Decoder::ShouldSyncDecode(size_t aByteLimit)
{
MOZ_ASSERT(aByteLimit > 0);
MOZ_ASSERT(mIterator, "Should have a SourceBufferIterator");
return mIterator->RemainingBytesIsNoMoreThan(aByteLimit);
}
void
Decoder::CompleteDecode()
{
// Implementation-specific finalization.
nsresult rv = BeforeFinishInternal();
if (NS_FAILED(rv)) {
PostError();
}
rv = HasError() ? FinishWithErrorInternal()
: FinishInternal();
if (NS_FAILED(rv)) {
PostError();
}
if (IsMetadataDecode()) {
// If this was a metadata decode and we never got a size, the decode failed.
if (!HasSize()) {
PostError();
}
return;
}
// If the implementation left us mid-frame, finish that up. Note that it may
// have left us transparent.
if (mInFrame) {
PostHasTransparency();
PostFrameStop();
}
// If PostDecodeDone() has not been called, we may need to send teardown
// notifications if it is unrecoverable.
if (!mDecodeDone) {
// We should always report an error to the console in this case.
mShouldReportError = true;
if (GetCompleteFrameCount() > 0) {
// We're usable if we have at least one complete frame, so do exactly
// what we should have when the decoder completed.
PostHasTransparency();
PostDecodeDone();
} else {
// We're not usable. Record some final progress indicating the error.
mProgress |= FLAG_DECODE_COMPLETE | FLAG_HAS_ERROR;
}
}
if (mDecodeDone) {
MOZ_ASSERT(HasError() || mCurrentFrame, "Should have an error or a frame");
// If this image wasn't animated and isn't a transient image, mark its frame
// as optimizable. We don't support optimizing animated images and
// optimizing transient images isn't worth it.
if (!HasAnimation() &&
!(mDecoderFlags & DecoderFlags::IMAGE_IS_TRANSIENT) &&
mCurrentFrame) {
mCurrentFrame->SetOptimizable();
}
}
}
void
Decoder::SetOutputSize(const gfx::IntSize& aSize)
{
mOutputSize = Some(aSize);
mHaveExplicitOutputSize = true;
}
Maybe<gfx::IntSize>
Decoder::ExplicitOutputSize() const
{
MOZ_ASSERT_IF(mHaveExplicitOutputSize, mOutputSize);
return mHaveExplicitOutputSize ? mOutputSize : Nothing();
}
Maybe<uint32_t>
Decoder::TakeCompleteFrameCount()
{
const bool finishedNewFrame = mFinishedNewFrame;
mFinishedNewFrame = false;
return finishedNewFrame ? Some(GetCompleteFrameCount()) : Nothing();
}
DecoderFinalStatus
Decoder::FinalStatus() const
{
return DecoderFinalStatus(IsMetadataDecode(),
GetDecodeDone(),
HasError(),
ShouldReportError());
}
DecoderTelemetry
Decoder::Telemetry() const
{
MOZ_ASSERT(mIterator);
return DecoderTelemetry(SpeedHistogram(),
mIterator->ByteCount(),
mIterator->ChunkCount(),
mDecodeTime);
}
nsresult
Decoder::AllocateFrame(uint32_t aFrameNum,
const gfx::IntSize& aOutputSize,
const gfx::IntRect& aFrameRect,
gfx::SurfaceFormat aFormat,
uint8_t aPaletteDepth)
{
mCurrentFrame = AllocateFrameInternal(aFrameNum, aOutputSize, aFrameRect,
aFormat, aPaletteDepth,
mCurrentFrame.get());
if (mCurrentFrame) {
// Gather the raw pointers the decoders will use.
mCurrentFrame->GetImageData(&mImageData, &mImageDataLength);
mCurrentFrame->GetPaletteData(&mColormap, &mColormapSize);
// We should now be on |aFrameNum|. (Note that we're comparing the frame
// number, which is zero-based, with the frame count, which is one-based.)
MOZ_ASSERT(aFrameNum + 1 == mFrameCount);
// If we're past the first frame, PostIsAnimated() should've been called.
MOZ_ASSERT_IF(mFrameCount > 1, HasAnimation());
// Update our state to reflect the new frame.
MOZ_ASSERT(!mInFrame, "Starting new frame but not done with old one!");
mInFrame = true;
}
return mCurrentFrame ? NS_OK : NS_ERROR_FAILURE;
}
RawAccessFrameRef
Decoder::AllocateFrameInternal(uint32_t aFrameNum,
const gfx::IntSize& aOutputSize,
const gfx::IntRect& aFrameRect,
SurfaceFormat aFormat,
uint8_t aPaletteDepth,
imgFrame* aPreviousFrame)
{
if (HasError()) {
return RawAccessFrameRef();
}
if (aFrameNum != mFrameCount) {
MOZ_ASSERT_UNREACHABLE("Allocating frames out of order");
return RawAccessFrameRef();
}
if (aOutputSize.width <= 0 || aOutputSize.height <= 0 ||
aFrameRect.width <= 0 || aFrameRect.height <= 0) {
NS_WARNING("Trying to add frame with zero or negative size");
return RawAccessFrameRef();
}
NotNull<RefPtr<imgFrame>> frame = WrapNotNull(new imgFrame());
bool nonPremult = bool(mSurfaceFlags & SurfaceFlags::NO_PREMULTIPLY_ALPHA);
if (NS_FAILED(frame->InitForDecoder(aOutputSize, aFrameRect, aFormat,
aPaletteDepth, nonPremult))) {
NS_WARNING("imgFrame::Init should succeed");
return RawAccessFrameRef();
}
RawAccessFrameRef ref = frame->RawAccessRef();
if (!ref) {
frame->Abort();
return RawAccessFrameRef();
}
if (aFrameNum == 1) {
MOZ_ASSERT(aPreviousFrame, "Must provide a previous frame when animated");
aPreviousFrame->SetRawAccessOnly();
// If we dispose of the first frame by clearing it, then the first frame's
// refresh area is all of itself.
// RESTORE_PREVIOUS is invalid (assumed to be DISPOSE_CLEAR).
AnimationData previousFrameData = aPreviousFrame->GetAnimationData();
if (previousFrameData.mDisposalMethod == DisposalMethod::CLEAR ||
previousFrameData.mDisposalMethod == DisposalMethod::CLEAR_ALL ||
previousFrameData.mDisposalMethod == DisposalMethod::RESTORE_PREVIOUS) {
mFirstFrameRefreshArea = previousFrameData.mRect;
}
}
if (aFrameNum > 0) {
ref->SetRawAccessOnly();
// Some GIFs are huge but only have a small area that they animate. We only
// need to refresh that small area when frame 0 comes around again.
mFirstFrameRefreshArea.UnionRect(mFirstFrameRefreshArea, frame->GetRect());
}
mFrameCount++;
return ref;
}
/*
* Hook stubs. Override these as necessary in decoder implementations.
*/
nsresult Decoder::InitInternal() { return NS_OK; }
nsresult Decoder::BeforeFinishInternal() { return NS_OK; }
nsresult Decoder::FinishInternal() { return NS_OK; }
nsresult Decoder::FinishWithErrorInternal() { return NS_OK; }
/*
* Progress Notifications
*/
void
Decoder::PostSize(int32_t aWidth,
int32_t aHeight,
Orientation aOrientation /* = Orientation()*/)
{
// Validate.
MOZ_ASSERT(aWidth >= 0, "Width can't be negative!");
MOZ_ASSERT(aHeight >= 0, "Height can't be negative!");
// Set our intrinsic size.
mImageMetadata.SetSize(aWidth, aHeight, aOrientation);
// Set our output size if it's not already set.
if (!mOutputSize) {
mOutputSize = Some(IntSize(aWidth, aHeight));
}
MOZ_ASSERT(mOutputSize->width <= aWidth && mOutputSize->height <= aHeight,
"Output size will result in upscaling");
// Create a downscaler if we need to downscale. This is used by legacy
// decoders that haven't been converted to use SurfacePipe yet.
// XXX(seth): Obviously, we'll remove this once all decoders use SurfacePipe.
if (mOutputSize->width < aWidth || mOutputSize->height < aHeight) {
mDownscaler.emplace(*mOutputSize);
}
// Record this notification.
mProgress |= FLAG_SIZE_AVAILABLE;
}
void
Decoder::PostHasTransparency()
{
mProgress |= FLAG_HAS_TRANSPARENCY;
}
void
Decoder::PostIsAnimated(FrameTimeout aFirstFrameTimeout)
{
mProgress |= FLAG_IS_ANIMATED;
mImageMetadata.SetHasAnimation();
mImageMetadata.SetFirstFrameTimeout(aFirstFrameTimeout);
}
void
Decoder::PostFrameStop(Opacity aFrameOpacity
/* = Opacity::SOME_TRANSPARENCY */,
DisposalMethod aDisposalMethod
/* = DisposalMethod::KEEP */,
FrameTimeout aTimeout /* = FrameTimeout::Forever() */,
BlendMethod aBlendMethod /* = BlendMethod::OVER */,
const Maybe<nsIntRect>& aBlendRect /* = Nothing() */)
{
// We should be mid-frame
MOZ_ASSERT(!IsMetadataDecode(), "Stopping frame during metadata decode");
MOZ_ASSERT(mInFrame, "Stopping frame when we didn't start one");
MOZ_ASSERT(mCurrentFrame, "Stopping frame when we don't have one");
// Update our state.
mInFrame = false;
mFinishedNewFrame = true;
mCurrentFrame->Finish(aFrameOpacity, aDisposalMethod, aTimeout,
aBlendMethod, aBlendRect);
mProgress |= FLAG_FRAME_COMPLETE;
mLoopLength += aTimeout;
// If we're not sending partial invalidations, then we send an invalidation
// here when the first frame is complete.
if (!ShouldSendPartialInvalidations() && mFrameCount == 1) {
mInvalidRect.UnionRect(mInvalidRect,
IntRect(IntPoint(), Size()));
}
}
void
Decoder::PostInvalidation(const gfx::IntRect& aRect,
const Maybe<gfx::IntRect>& aRectAtOutputSize
/* = Nothing() */)
{
// We should be mid-frame
MOZ_ASSERT(mInFrame, "Can't invalidate when not mid-frame!");
MOZ_ASSERT(mCurrentFrame, "Can't invalidate when not mid-frame!");
// Record this invalidation, unless we're not sending partial invalidations
// or we're past the first frame.
if (ShouldSendPartialInvalidations() && mFrameCount == 1) {
mInvalidRect.UnionRect(mInvalidRect, aRect);
mCurrentFrame->ImageUpdated(aRectAtOutputSize.valueOr(aRect));
}
}
void
Decoder::PostDecodeDone(int32_t aLoopCount /* = 0 */)
{
MOZ_ASSERT(!IsMetadataDecode(), "Done with decoding in metadata decode");
MOZ_ASSERT(!mInFrame, "Can't be done decoding if we're mid-frame!");
MOZ_ASSERT(!mDecodeDone, "Decode already done!");
mDecodeDone = true;
mImageMetadata.SetLoopCount(aLoopCount);
// Some metadata that we track should take into account every frame in the
// image. If this is a first-frame-only decode, our accumulated loop length
// and first frame refresh area only includes the first frame, so it's not
// correct and we don't record it.
if (!IsFirstFrameDecode()) {
mImageMetadata.SetLoopLength(mLoopLength);
mImageMetadata.SetFirstFrameRefreshArea(mFirstFrameRefreshArea);
}
mProgress |= FLAG_DECODE_COMPLETE;
}
void
Decoder::PostError()
{
mError = true;
if (mInFrame) {
MOZ_ASSERT(mCurrentFrame);
MOZ_ASSERT(mFrameCount > 0);
mCurrentFrame->Abort();
mInFrame = false;
--mFrameCount;
}
}
} // namespace image
} // namespace mozilla

562
image/Decoder.h Normal file
View file

@ -0,0 +1,562 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* 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/. */
#ifndef mozilla_image_Decoder_h
#define mozilla_image_Decoder_h
#include "FrameAnimator.h"
#include "RasterImage.h"
#include "mozilla/Maybe.h"
#include "mozilla/NotNull.h"
#include "mozilla/RefPtr.h"
#include "DecodePool.h"
#include "DecoderFlags.h"
#include "Downscaler.h"
#include "ImageMetadata.h"
#include "Orientation.h"
#include "SourceBuffer.h"
#include "StreamingLexer.h"
#include "SurfaceFlags.h"
namespace mozilla {
namespace Telemetry {
enum ID : uint32_t;
} // namespace Telemetry
namespace image {
struct DecoderFinalStatus final
{
DecoderFinalStatus(bool aWasMetadataDecode,
bool aFinished,
bool aHadError,
bool aShouldReportError)
: mWasMetadataDecode(aWasMetadataDecode)
, mFinished(aFinished)
, mHadError(aHadError)
, mShouldReportError(aShouldReportError)
{ }
/// True if this was a metadata decode.
const bool mWasMetadataDecode : 1;
/// True if this decoder finished, whether successfully or due to failure.
const bool mFinished : 1;
/// True if this decoder encountered an error.
const bool mHadError : 1;
/// True if this decoder encountered the kind of error that should be reported
/// to the console.
const bool mShouldReportError : 1;
};
struct DecoderTelemetry final
{
DecoderTelemetry(Maybe<Telemetry::ID> aSpeedHistogram,
size_t aBytesDecoded,
uint32_t aChunkCount,
TimeDuration aDecodeTime)
: mSpeedHistogram(aSpeedHistogram)
, mBytesDecoded(aBytesDecoded)
, mChunkCount(aChunkCount)
, mDecodeTime(aDecodeTime)
{ }
/// @return our decoder's speed, in KBps.
int32_t Speed() const
{
return mBytesDecoded / (1024 * mDecodeTime.ToSeconds());
}
/// @return our decoder's decode time, in microseconds.
int32_t DecodeTimeMicros() { return mDecodeTime.ToMicroseconds(); }
/// The per-image-format telemetry ID for recording our decoder's speed, or
/// Nothing() if we don't record speed telemetry for this kind of decoder.
const Maybe<Telemetry::ID> mSpeedHistogram;
/// The number of bytes of input our decoder processed.
const size_t mBytesDecoded;
/// The number of chunks our decoder's input was divided into.
const uint32_t mChunkCount;
/// The amount of time our decoder spent inside DoDecode().
const TimeDuration mDecodeTime;
};
class Decoder
{
public:
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(Decoder)
explicit Decoder(RasterImage* aImage);
/**
* Initialize an image decoder. Decoders may not be re-initialized.
*
* @return NS_OK if the decoder could be initialized successfully.
*/
nsresult Init();
/**
* Decodes, reading all data currently available in the SourceBuffer.
*
* If more data is needed and @aOnResume is non-null, Decode() will schedule
* @aOnResume to be called when more data is available.
*
* @return a LexerResult which may indicate:
* - the image has been successfully decoded (TerminalState::SUCCESS), or
* - the image has failed to decode (TerminalState::FAILURE), or
* - the decoder is yielding until it gets more data (Yield::NEED_MORE_DATA), or
* - the decoder is yielding to allow the caller to access intermediate
* output (Yield::OUTPUT_AVAILABLE).
*/
LexerResult Decode(IResumable* aOnResume = nullptr);
/**
* Terminate this decoder in a failure state, just as if the decoder
* implementation had returned TerminalState::FAILURE from DoDecode().
*
* XXX(seth): This method should be removed ASAP; it exists only because
* RasterImage::FinalizeDecoder() requires an actual Decoder object as an
* argument, so we can't simply tell RasterImage a decode failed except via an
* intervening decoder. We'll fix this in bug 1291071.
*/
LexerResult TerminateFailure();
/**
* Given a maximum number of bytes we're willing to decode, @aByteLimit,
* returns true if we should attempt to run this decoder synchronously.
*/
bool ShouldSyncDecode(size_t aByteLimit);
/**
* Gets the invalidation region accumulated by the decoder so far, and clears
* the decoder's invalidation region. This means that each call to
* TakeInvalidRect() returns only the invalidation region accumulated since
* the last call to TakeInvalidRect().
*/
nsIntRect TakeInvalidRect()
{
nsIntRect invalidRect = mInvalidRect;
mInvalidRect.SetEmpty();
return invalidRect;
}
/**
* Gets the progress changes accumulated by the decoder so far, and clears
* them. This means that each call to TakeProgress() returns only the changes
* accumulated since the last call to TakeProgress().
*/
Progress TakeProgress()
{
Progress progress = mProgress;
mProgress = NoProgress;
return progress;
}
/**
* Returns true if there's any progress to report.
*/
bool HasProgress() const
{
return mProgress != NoProgress || !mInvalidRect.IsEmpty() || mFinishedNewFrame;
}
/*
* State.
*/
/**
* If we're doing a metadata decode, we only decode the image's headers, which
* is enough to determine the image's intrinsic size. A metadata decode is
* enabled by calling SetMetadataDecode() *before* calling Init().
*/
void SetMetadataDecode(bool aMetadataDecode)
{
MOZ_ASSERT(!mInitialized, "Shouldn't be initialized yet");
mMetadataDecode = aMetadataDecode;
}
bool IsMetadataDecode() const { return mMetadataDecode; }
/**
* Sets the output size of this decoder. If this is smaller than the intrinsic
* size of the image, we'll downscale it while decoding. For memory usage
* reasons, upscaling is forbidden and will trigger assertions in debug
* builds.
*
* Not calling SetOutputSize() means that we should just decode at the
* intrinsic size, whatever it is.
*
* If SetOutputSize() was called, ExplicitOutputSize() can be used to
* determine the value that was passed to it.
*
* This must be called before Init() is called.
*/
void SetOutputSize(const gfx::IntSize& aSize);
/**
* @return the output size of this decoder. If this is smaller than the
* intrinsic size, then the image will be downscaled during the decoding
* process.
*
* Illegal to call if HasSize() returns false.
*/
gfx::IntSize OutputSize() const { MOZ_ASSERT(HasSize()); return *mOutputSize; }
/**
* @return either the size passed to SetOutputSize() or Nothing(), indicating
* that SetOutputSize() was not explicitly called.
*/
Maybe<gfx::IntSize> ExplicitOutputSize() const;
/**
* Set the requested sample size for this decoder. Used to implement the
* -moz-sample-size media fragment.
*
* XXX(seth): Support for -moz-sample-size will be removed in bug 1120056.
*/
virtual void SetSampleSize(int aSampleSize) { }
/**
* Set an iterator to the SourceBuffer which will feed data to this decoder.
* This must always be called before calling Init(). (And only before Init().)
*
* XXX(seth): We should eliminate this method and pass a SourceBufferIterator
* to the various decoder constructors instead.
*/
void SetIterator(SourceBufferIterator&& aIterator)
{
MOZ_ASSERT(!mInitialized, "Shouldn't be initialized yet");
mIterator.emplace(Move(aIterator));
}
/**
* Should this decoder send partial invalidations?
*/
bool ShouldSendPartialInvalidations() const
{
return !(mDecoderFlags & DecoderFlags::IS_REDECODE);
}
/**
* Should we stop decoding after the first frame?
*/
bool IsFirstFrameDecode() const
{
return bool(mDecoderFlags & DecoderFlags::FIRST_FRAME_ONLY);
}
/**
* @return the number of complete animation frames which have been decoded so
* far, if it has changed since the last call to TakeCompleteFrameCount();
* otherwise, returns Nothing().
*/
Maybe<uint32_t> TakeCompleteFrameCount();
// The number of frames we have, including anything in-progress. Thus, this
// is only 0 if we haven't begun any frames.
uint32_t GetFrameCount() { return mFrameCount; }
// Did we discover that the image we're decoding is animated?
bool HasAnimation() const { return mImageMetadata.HasAnimation(); }
// Error tracking
bool HasError() const { return mError; }
bool ShouldReportError() const { return mShouldReportError; }
/// Did we finish decoding enough that calling Decode() again would be useless?
bool GetDecodeDone() const
{
return mReachedTerminalState || mDecodeDone ||
(mMetadataDecode && HasSize()) || HasError();
}
/// Are we in the middle of a frame right now? Used for assertions only.
bool InFrame() const { return mInFrame; }
enum DecodeStyle {
PROGRESSIVE, // produce intermediate frames representing the partial
// state of the image
SEQUENTIAL // decode to final image immediately
};
/**
* Get or set the DecoderFlags that influence the behavior of this decoder.
*/
void SetDecoderFlags(DecoderFlags aDecoderFlags)
{
MOZ_ASSERT(!mInitialized);
mDecoderFlags = aDecoderFlags;
}
DecoderFlags GetDecoderFlags() const { return mDecoderFlags; }
/**
* Get or set the SurfaceFlags that select the kind of output this decoder
* will produce.
*/
void SetSurfaceFlags(SurfaceFlags aSurfaceFlags)
{
MOZ_ASSERT(!mInitialized);
mSurfaceFlags = aSurfaceFlags;
}
SurfaceFlags GetSurfaceFlags() const { return mSurfaceFlags; }
/// @return true if we know the intrinsic size of the image we're decoding.
bool HasSize() const { return mImageMetadata.HasSize(); }
/**
* @return the intrinsic size of the image we're decoding.
*
* Illegal to call if HasSize() returns false.
*/
gfx::IntSize Size() const
{
MOZ_ASSERT(HasSize());
return mImageMetadata.GetSize();
}
/**
* @return an IntRect which covers the entire area of this image at its
* intrinsic size, appropriate for use as a frame rect when the image itself
* does not specify one.
*
* Illegal to call if HasSize() returns false.
*/
gfx::IntRect FullFrame() const
{
return gfx::IntRect(gfx::IntPoint(), Size());
}
/**
* @return an IntRect which covers the entire area of this image at its size
* after scaling - that is, at its output size.
*
* XXX(seth): This is only used for decoders which are using the old
* Downscaler code instead of SurfacePipe, since the old AllocateFrame() and
* Downscaler APIs required that the frame rect be specified in output space.
* We should remove this once all decoders use SurfacePipe.
*
* Illegal to call if HasSize() returns false.
*/
gfx::IntRect FullOutputFrame() const
{
return gfx::IntRect(gfx::IntPoint(), OutputSize());
}
/// @return final status information about this decoder. Should be called
/// after we decide we're not going to run the decoder anymore.
DecoderFinalStatus FinalStatus() const;
/// @return the metadata we collected about this image while decoding.
const ImageMetadata& GetImageMetadata() { return mImageMetadata; }
/// @return performance telemetry we collected while decoding.
DecoderTelemetry Telemetry() const;
/**
* @return a weak pointer to the image associated with this decoder. Illegal
* to call if this decoder is not associated with an image.
*/
NotNull<RasterImage*> GetImage() const { return WrapNotNull(mImage.get()); }
/**
* @return a possibly-null weak pointer to the image associated with this
* decoder. May be called even if this decoder is not associated with an
* image.
*/
RasterImage* GetImageMaybeNull() const { return mImage.get(); }
RawAccessFrameRef GetCurrentFrameRef()
{
return mCurrentFrame ? mCurrentFrame->RawAccessRef()
: RawAccessFrameRef();
}
protected:
friend class AutoRecordDecoderTelemetry;
friend class nsICODecoder;
friend class PalettedSurfaceSink;
friend class SurfaceSink;
virtual ~Decoder();
/*
* Internal hooks. Decoder implementations may override these and
* only these methods.
*
* BeforeFinishInternal() can be used to detect if decoding is in an
* incomplete state, e.g. due to file truncation, in which case it should
* return a failing nsresult.
*/
virtual nsresult InitInternal();
virtual LexerResult DoDecode(SourceBufferIterator& aIterator,
IResumable* aOnResume) = 0;
virtual nsresult BeforeFinishInternal();
virtual nsresult FinishInternal();
virtual nsresult FinishWithErrorInternal();
/**
* @return the per-image-format telemetry ID for recording this decoder's
* speed, or Nothing() if we don't record speed telemetry for this kind of
* decoder.
*/
virtual Maybe<Telemetry::ID> SpeedHistogram() const { return Nothing(); }
/*
* Progress notifications.
*/
// Called by decoders when they determine the size of the image. Informs
// the image of its size and sends notifications.
void PostSize(int32_t aWidth,
int32_t aHeight,
Orientation aOrientation = Orientation());
// Called by decoders if they determine that the image has transparency.
//
// This should be fired as early as possible to allow observers to do things
// that affect content, so it's necessarily pessimistic - if there's a
// possibility that the image has transparency, for example because its header
// specifies that it has an alpha channel, we fire PostHasTransparency
// immediately. PostFrameStop's aFrameOpacity argument, on the other hand, is
// only used internally to ImageLib. Because PostFrameStop isn't delivered
// until the entire frame has been decoded, decoders may take into account the
// actual contents of the frame and give a more accurate result.
void PostHasTransparency();
// Called by decoders if they determine that the image is animated.
//
// @param aTimeout The time for which the first frame should be shown before
// we advance to the next frame.
void PostIsAnimated(FrameTimeout aFirstFrameTimeout);
// Called by decoders when they end a frame. Informs the image, sends
// notifications, and does internal book-keeping.
// Specify whether this frame is opaque as an optimization.
// For animated images, specify the disposal, blend method and timeout for
// this frame.
void PostFrameStop(Opacity aFrameOpacity = Opacity::SOME_TRANSPARENCY,
DisposalMethod aDisposalMethod = DisposalMethod::KEEP,
FrameTimeout aTimeout = FrameTimeout::Forever(),
BlendMethod aBlendMethod = BlendMethod::OVER,
const Maybe<nsIntRect>& aBlendRect = Nothing());
/**
* Called by the decoders when they have a region to invalidate. We may not
* actually pass these invalidations on right away.
*
* @param aRect The invalidation rect in the coordinate system of the unscaled
* image (that is, the image at its intrinsic size).
* @param aRectAtOutputSize If not Nothing(), the invalidation rect in the
* coordinate system of the scaled image (that is,
* the image at our output size). This must
* be supplied if we're downscaling during decode.
*/
void PostInvalidation(const gfx::IntRect& aRect,
const Maybe<gfx::IntRect>& aRectAtOutputSize = Nothing());
// Called by the decoders when they have successfully decoded the image. This
// may occur as the result of the decoder getting to the appropriate point in
// the stream, or by us calling FinishInternal().
//
// May not be called mid-frame.
//
// For animated images, specify the loop count. -1 means loop forever, 0
// means a single iteration, stopping on the last frame.
void PostDecodeDone(int32_t aLoopCount = 0);
/**
* Allocates a new frame, making it our current frame if successful.
*
* The @aFrameNum parameter only exists as a sanity check; it's illegal to
* create a new frame anywhere but immediately after the existing frames.
*
* If a non-paletted frame is desired, pass 0 for aPaletteDepth.
*/
nsresult AllocateFrame(uint32_t aFrameNum,
const gfx::IntSize& aOutputSize,
const gfx::IntRect& aFrameRect,
gfx::SurfaceFormat aFormat,
uint8_t aPaletteDepth = 0);
private:
/// Report that an error was encountered while decoding.
void PostError();
/**
* CompleteDecode() finishes up the decoding process after Decode() determines
* that we're finished. It records final progress and does all the cleanup
* that's possible off-main-thread.
*/
void CompleteDecode();
/// @return the number of complete frames we have. Does not include the
/// current frame if it's unfinished.
uint32_t GetCompleteFrameCount()
{
if (mFrameCount == 0) {
return 0;
}
return mInFrame ? mFrameCount - 1 : mFrameCount;
}
RawAccessFrameRef AllocateFrameInternal(uint32_t aFrameNum,
const gfx::IntSize& aOutputSize,
const gfx::IntRect& aFrameRect,
gfx::SurfaceFormat aFormat,
uint8_t aPaletteDepth,
imgFrame* aPreviousFrame);
protected:
Maybe<Downscaler> mDownscaler;
uint8_t* mImageData; // Pointer to image data in either Cairo or 8bit format
uint32_t mImageDataLength;
uint32_t* mColormap; // Current colormap to be used in Cairo format
uint32_t mColormapSize;
private:
RefPtr<RasterImage> mImage;
Maybe<SourceBufferIterator> mIterator;
RawAccessFrameRef mCurrentFrame;
ImageMetadata mImageMetadata;
gfx::IntRect mInvalidRect; // Tracks an invalidation region in the current frame.
Maybe<gfx::IntSize> mOutputSize; // The size of our output surface.
Progress mProgress;
uint32_t mFrameCount; // Number of frames, including anything in-progress
FrameTimeout mLoopLength; // Length of a single loop of this image.
gfx::IntRect mFirstFrameRefreshArea; // The area of the image that needs to
// be invalidated when the animation loops.
// Telemetry data for this decoder.
TimeDuration mDecodeTime;
DecoderFlags mDecoderFlags;
SurfaceFlags mSurfaceFlags;
bool mInitialized : 1;
bool mMetadataDecode : 1;
bool mHaveExplicitOutputSize : 1;
bool mInFrame : 1;
bool mFinishedNewFrame : 1; // True if PostFrameStop() has been called since
// the last call to TakeCompleteFrameCount().
bool mReachedTerminalState : 1;
bool mDecodeDone : 1;
bool mError : 1;
bool mShouldReportError : 1;
};
} // namespace image
} // namespace mozilla
#endif // mozilla_image_Decoder_h

350
image/DecoderFactory.cpp Normal file
View file

@ -0,0 +1,350 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* 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/. */
#include "DecoderFactory.h"
#include "nsMimeTypes.h"
#include "mozilla/RefPtr.h"
#include "AnimationSurfaceProvider.h"
#include "Decoder.h"
#include "IDecodingTask.h"
#include "nsPNGDecoder.h"
#include "nsGIFDecoder2.h"
#include "nsJPEGDecoder.h"
#include "nsBMPDecoder.h"
#include "nsICODecoder.h"
#include "nsIconDecoder.h"
namespace mozilla {
using namespace gfx;
namespace image {
/* static */ DecoderType
DecoderFactory::GetDecoderType(const char* aMimeType)
{
// By default we don't know.
DecoderType type = DecoderType::UNKNOWN;
// PNG
if (!strcmp(aMimeType, IMAGE_PNG)) {
type = DecoderType::PNG;
} else if (!strcmp(aMimeType, IMAGE_X_PNG)) {
type = DecoderType::PNG;
} else if (!strcmp(aMimeType, IMAGE_APNG)) {
type = DecoderType::PNG;
// GIF
} else if (!strcmp(aMimeType, IMAGE_GIF)) {
type = DecoderType::GIF;
// JPEG
} else if (!strcmp(aMimeType, IMAGE_JPEG)) {
type = DecoderType::JPEG;
} else if (!strcmp(aMimeType, IMAGE_PJPEG)) {
type = DecoderType::JPEG;
} else if (!strcmp(aMimeType, IMAGE_JPG)) {
type = DecoderType::JPEG;
// BMP
} else if (!strcmp(aMimeType, IMAGE_BMP)) {
type = DecoderType::BMP;
} else if (!strcmp(aMimeType, IMAGE_BMP_MS)) {
type = DecoderType::BMP;
// ICO
} else if (!strcmp(aMimeType, IMAGE_ICO)) {
type = DecoderType::ICO;
} else if (!strcmp(aMimeType, IMAGE_ICO_MS)) {
type = DecoderType::ICO;
// Icon
} else if (!strcmp(aMimeType, IMAGE_ICON_MS)) {
type = DecoderType::ICON;
}
return type;
}
/* static */ already_AddRefed<Decoder>
DecoderFactory::GetDecoder(DecoderType aType,
RasterImage* aImage,
bool aIsRedecode)
{
RefPtr<Decoder> decoder;
switch (aType) {
case DecoderType::PNG:
decoder = new nsPNGDecoder(aImage);
break;
case DecoderType::GIF:
decoder = new nsGIFDecoder2(aImage);
break;
case DecoderType::JPEG:
// If we have all the data we don't want to waste cpu time doing
// a progressive decode.
decoder = new nsJPEGDecoder(aImage,
aIsRedecode ? Decoder::SEQUENTIAL
: Decoder::PROGRESSIVE);
break;
case DecoderType::BMP:
decoder = new nsBMPDecoder(aImage);
break;
case DecoderType::ICO:
decoder = new nsICODecoder(aImage);
break;
case DecoderType::ICON:
decoder = new nsIconDecoder(aImage);
break;
default:
MOZ_ASSERT_UNREACHABLE("Unknown decoder type");
}
return decoder.forget();
}
/* static */ already_AddRefed<IDecodingTask>
DecoderFactory::CreateDecoder(DecoderType aType,
NotNull<RasterImage*> aImage,
NotNull<SourceBuffer*> aSourceBuffer,
const IntSize& aIntrinsicSize,
const IntSize& aOutputSize,
DecoderFlags aDecoderFlags,
SurfaceFlags aSurfaceFlags,
int aSampleSize)
{
if (aType == DecoderType::UNKNOWN) {
return nullptr;
}
// Create an anonymous decoder. Interaction with the SurfaceCache and the
// owning RasterImage will be mediated by DecodedSurfaceProvider.
RefPtr<Decoder> decoder =
GetDecoder(aType, nullptr, bool(aDecoderFlags & DecoderFlags::IS_REDECODE));
MOZ_ASSERT(decoder, "Should have a decoder now");
// Initialize the decoder.
decoder->SetMetadataDecode(false);
decoder->SetIterator(aSourceBuffer->Iterator());
decoder->SetOutputSize(aOutputSize);
decoder->SetDecoderFlags(aDecoderFlags | DecoderFlags::FIRST_FRAME_ONLY);
decoder->SetSurfaceFlags(aSurfaceFlags);
decoder->SetSampleSize(aSampleSize);
if (NS_FAILED(decoder->Init())) {
return nullptr;
}
// Create a DecodedSurfaceProvider which will manage the decoding process and
// make this decoder's output available in the surface cache.
SurfaceKey surfaceKey =
RasterSurfaceKey(aOutputSize, aSurfaceFlags, PlaybackType::eStatic);
NotNull<RefPtr<DecodedSurfaceProvider>> provider =
WrapNotNull(new DecodedSurfaceProvider(aImage,
surfaceKey,
WrapNotNull(decoder)));
// Attempt to insert the surface provider into the surface cache right away so
// we won't trigger any more decoders with the same parameters.
if (SurfaceCache::Insert(provider) != InsertOutcome::SUCCESS) {
return nullptr;
}
// Return the surface provider in its IDecodingTask guise.
RefPtr<IDecodingTask> task = provider.get();
return task.forget();
}
/* static */ already_AddRefed<IDecodingTask>
DecoderFactory::CreateAnimationDecoder(DecoderType aType,
NotNull<RasterImage*> aImage,
NotNull<SourceBuffer*> aSourceBuffer,
const IntSize& aIntrinsicSize,
DecoderFlags aDecoderFlags,
SurfaceFlags aSurfaceFlags)
{
if (aType == DecoderType::UNKNOWN) {
return nullptr;
}
MOZ_ASSERT(aType == DecoderType::GIF || aType == DecoderType::PNG,
"Calling CreateAnimationDecoder for non-animating DecoderType");
// Create an anonymous decoder. Interaction with the SurfaceCache and the
// owning RasterImage will be mediated by AnimationSurfaceProvider.
RefPtr<Decoder> decoder = GetDecoder(aType, nullptr, /* aIsRedecode = */ true);
MOZ_ASSERT(decoder, "Should have a decoder now");
// Initialize the decoder.
decoder->SetMetadataDecode(false);
decoder->SetIterator(aSourceBuffer->Iterator());
decoder->SetDecoderFlags(aDecoderFlags | DecoderFlags::IS_REDECODE);
decoder->SetSurfaceFlags(aSurfaceFlags);
if (NS_FAILED(decoder->Init())) {
return nullptr;
}
// Create an AnimationSurfaceProvider which will manage the decoding process
// and make this decoder's output available in the surface cache.
SurfaceKey surfaceKey =
RasterSurfaceKey(aIntrinsicSize, aSurfaceFlags, PlaybackType::eAnimated);
NotNull<RefPtr<AnimationSurfaceProvider>> provider =
WrapNotNull(new AnimationSurfaceProvider(aImage,
surfaceKey,
WrapNotNull(decoder)));
// Attempt to insert the surface provider into the surface cache right away so
// we won't trigger any more decoders with the same parameters.
if (SurfaceCache::Insert(provider) != InsertOutcome::SUCCESS) {
return nullptr;
}
// Return the surface provider in its IDecodingTask guise.
RefPtr<IDecodingTask> task = provider.get();
return task.forget();
}
/* static */ already_AddRefed<IDecodingTask>
DecoderFactory::CreateMetadataDecoder(DecoderType aType,
NotNull<RasterImage*> aImage,
NotNull<SourceBuffer*> aSourceBuffer,
int aSampleSize)
{
if (aType == DecoderType::UNKNOWN) {
return nullptr;
}
RefPtr<Decoder> decoder =
GetDecoder(aType, aImage, /* aIsRedecode = */ false);
MOZ_ASSERT(decoder, "Should have a decoder now");
// Initialize the decoder.
decoder->SetMetadataDecode(true);
decoder->SetIterator(aSourceBuffer->Iterator());
decoder->SetSampleSize(aSampleSize);
if (NS_FAILED(decoder->Init())) {
return nullptr;
}
RefPtr<IDecodingTask> task = new MetadataDecodingTask(WrapNotNull(decoder));
return task.forget();
}
/* static */ already_AddRefed<Decoder>
DecoderFactory::CreateDecoderForICOResource(DecoderType aType,
NotNull<SourceBuffer*> aSourceBuffer,
NotNull<nsICODecoder*> aICODecoder,
const Maybe<uint32_t>& aDataOffset
/* = Nothing() */)
{
// Create the decoder.
RefPtr<Decoder> decoder;
switch (aType) {
case DecoderType::BMP:
MOZ_ASSERT(aDataOffset);
decoder = new nsBMPDecoder(aICODecoder->GetImageMaybeNull(), *aDataOffset);
break;
case DecoderType::PNG:
MOZ_ASSERT(!aDataOffset);
decoder = new nsPNGDecoder(aICODecoder->GetImageMaybeNull());
break;
default:
MOZ_ASSERT_UNREACHABLE("Invalid ICO resource decoder type");
return nullptr;
}
MOZ_ASSERT(decoder);
// Initialize the decoder, copying settings from @aICODecoder.
MOZ_ASSERT(!aICODecoder->IsMetadataDecode());
decoder->SetMetadataDecode(aICODecoder->IsMetadataDecode());
decoder->SetIterator(aSourceBuffer->Iterator());
decoder->SetOutputSize(aICODecoder->OutputSize());
decoder->SetDecoderFlags(aICODecoder->GetDecoderFlags());
decoder->SetSurfaceFlags(aICODecoder->GetSurfaceFlags());
if (NS_FAILED(decoder->Init())) {
return nullptr;
}
return decoder.forget();
}
/* static */ already_AddRefed<Decoder>
DecoderFactory::CreateAnonymousDecoder(DecoderType aType,
NotNull<SourceBuffer*> aSourceBuffer,
const Maybe<IntSize>& aOutputSize,
SurfaceFlags aSurfaceFlags)
{
if (aType == DecoderType::UNKNOWN) {
return nullptr;
}
RefPtr<Decoder> decoder =
GetDecoder(aType, /* aImage = */ nullptr, /* aIsRedecode = */ false);
MOZ_ASSERT(decoder, "Should have a decoder now");
// Initialize the decoder.
decoder->SetMetadataDecode(false);
decoder->SetIterator(aSourceBuffer->Iterator());
// Anonymous decoders are always transient; we don't want to optimize surfaces
// or do any other expensive work that might be wasted.
DecoderFlags decoderFlags = DecoderFlags::IMAGE_IS_TRANSIENT;
// Without an image, the decoder can't store anything in the SurfaceCache, so
// callers will only be able to retrieve the most recent frame via
// Decoder::GetCurrentFrame(). That means that anonymous decoders should
// always be first-frame-only decoders, because nobody ever wants the *last*
// frame.
decoderFlags |= DecoderFlags::FIRST_FRAME_ONLY;
decoder->SetDecoderFlags(decoderFlags);
decoder->SetSurfaceFlags(aSurfaceFlags);
// Set an output size for downscale-during-decode if requested.
if (aOutputSize) {
decoder->SetOutputSize(*aOutputSize);
}
if (NS_FAILED(decoder->Init())) {
return nullptr;
}
return decoder.forget();
}
/* static */ already_AddRefed<Decoder>
DecoderFactory::CreateAnonymousMetadataDecoder(DecoderType aType,
NotNull<SourceBuffer*> aSourceBuffer)
{
if (aType == DecoderType::UNKNOWN) {
return nullptr;
}
RefPtr<Decoder> decoder =
GetDecoder(aType, /* aImage = */ nullptr, /* aIsRedecode = */ false);
MOZ_ASSERT(decoder, "Should have a decoder now");
// Initialize the decoder.
decoder->SetMetadataDecode(true);
decoder->SetIterator(aSourceBuffer->Iterator());
decoder->SetDecoderFlags(DecoderFlags::FIRST_FRAME_ONLY);
if (NS_FAILED(decoder->Init())) {
return nullptr;
}
return decoder.forget();
}
} // namespace image
} // namespace mozilla

193
image/DecoderFactory.h Normal file
View file

@ -0,0 +1,193 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
*
* 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/. */
#ifndef mozilla_image_DecoderFactory_h
#define mozilla_image_DecoderFactory_h
#include "DecoderFlags.h"
#include "mozilla/Attributes.h"
#include "mozilla/Maybe.h"
#include "mozilla/NotNull.h"
#include "mozilla/gfx/2D.h"
#include "nsCOMPtr.h"
#include "SurfaceFlags.h"
namespace mozilla {
namespace image {
class Decoder;
class IDecodingTask;
class nsICODecoder;
class RasterImage;
class SourceBuffer;
/**
* The type of decoder; this is usually determined from a MIME type using
* DecoderFactory::GetDecoderType().
*/
enum class DecoderType
{
PNG,
GIF,
JPEG,
BMP,
ICO,
ICON,
UNKNOWN
};
class DecoderFactory
{
public:
/// @return the type of decoder which is appropriate for @aMimeType.
static DecoderType GetDecoderType(const char* aMimeType);
/**
* Creates and initializes a decoder for non-animated images of type @aType.
* (If the image *is* animated, only the first frame will be decoded.) The
* decoder will send notifications to @aImage.
*
* @param aType Which type of decoder to create - JPEG, PNG, etc.
* @param aImage The image will own the decoder and which should receive
* notifications as decoding progresses.
* @param aSourceBuffer The SourceBuffer which the decoder will read its data
* from.
* @param aIntrinsicSize The intrinsic size of the image, normally obtained
* during the metadata decode.
* @param aOutputSize The output size for the decoder. If this is smaller than
* the intrinsic size, the decoder will downscale the
* image.
* @param aDecoderFlags Flags specifying the behavior of this decoder.
* @param aSurfaceFlags Flags specifying the type of output this decoder
* should produce.
* @param aSampleSize The sample size requested using #-moz-samplesize (or 0
* if none).
*/
static already_AddRefed<IDecodingTask>
CreateDecoder(DecoderType aType,
NotNull<RasterImage*> aImage,
NotNull<SourceBuffer*> aSourceBuffer,
const gfx::IntSize& aIntrinsicSize,
const gfx::IntSize& aOutputSize,
DecoderFlags aDecoderFlags,
SurfaceFlags aSurfaceFlags,
int aSampleSize);
/**
* Creates and initializes a decoder for animated images of type @aType.
* The decoder will send notifications to @aImage.
*
* @param aType Which type of decoder to create - JPEG, PNG, etc.
* @param aImage The image will own the decoder and which should receive
* notifications as decoding progresses.
* @param aSourceBuffer The SourceBuffer which the decoder will read its data
* from.
* @param aIntrinsicSize The intrinsic size of the image, normally obtained
* during the metadata decode.
* @param aDecoderFlags Flags specifying the behavior of this decoder.
* @param aSurfaceFlags Flags specifying the type of output this decoder
* should produce.
*/
static already_AddRefed<IDecodingTask>
CreateAnimationDecoder(DecoderType aType,
NotNull<RasterImage*> aImage,
NotNull<SourceBuffer*> aSourceBuffer,
const gfx::IntSize& aIntrinsicSize,
DecoderFlags aDecoderFlags,
SurfaceFlags aSurfaceFlags);
/**
* Creates and initializes a metadata decoder of type @aType. This decoder
* will only decode the image's header, extracting metadata like the size of
* the image. No actual image data will be decoded and no surfaces will be
* allocated. The decoder will send notifications to @aImage.
*
* @param aType Which type of decoder to create - JPEG, PNG, etc.
* @param aImage The image will own the decoder and which should receive
* notifications as decoding progresses.
* @param aSourceBuffer The SourceBuffer which the decoder will read its data
* from.
* @param aSampleSize The sample size requested using #-moz-samplesize (or 0
* if none).
*/
static already_AddRefed<IDecodingTask>
CreateMetadataDecoder(DecoderType aType,
NotNull<RasterImage*> aImage,
NotNull<SourceBuffer*> aSourceBuffer,
int aSampleSize);
/**
* Creates and initializes a decoder for an ICO resource, which may be either
* a BMP or PNG image.
*
* @param aType Which type of decoder to create. This must be either BMP or
* PNG.
* @param aSourceBuffer The SourceBuffer which the decoder will read its data
* from.
* @param aICODecoder The ICO decoder which is controlling this resource
* decoder. @aICODecoder's settings will be copied to the
* resource decoder, so the two decoders will have the
* same decoder flags, surface flags, target size, and
* other parameters.
* @param aDataOffset If @aType is BMP, specifies the offset at which data
* begins in the BMP resource. Must be Some() if and only
* if @aType is BMP.
*/
static already_AddRefed<Decoder>
CreateDecoderForICOResource(DecoderType aType,
NotNull<SourceBuffer*> aSourceBuffer,
NotNull<nsICODecoder*> aICODecoder,
const Maybe<uint32_t>& aDataOffset = Nothing());
/**
* Creates and initializes an anonymous decoder (one which isn't associated
* with an Image object). Only the first frame of the image will be decoded.
*
* @param aType Which type of decoder to create - JPEG, PNG, etc.
* @param aSourceBuffer The SourceBuffer which the decoder will read its data
* from.
* @param aOutputSize If Some(), the output size for the decoder. If this is
* smaller than the intrinsic size, the decoder will
* downscale the image. If Nothing(), the output size will
* be the intrinsic size.
* @param aSurfaceFlags Flags specifying the type of output this decoder
* should produce.
*/
static already_AddRefed<Decoder>
CreateAnonymousDecoder(DecoderType aType,
NotNull<SourceBuffer*> aSourceBuffer,
const Maybe<gfx::IntSize>& aOutputSize,
SurfaceFlags aSurfaceFlags);
/**
* Creates and initializes an anonymous metadata decoder (one which isn't
* associated with an Image object). This decoder will only decode the image's
* header, extracting metadata like the size of the image. No actual image
* data will be decoded and no surfaces will be allocated.
*
* @param aType Which type of decoder to create - JPEG, PNG, etc.
* @param aSourceBuffer The SourceBuffer which the decoder will read its data
* from.
*/
static already_AddRefed<Decoder>
CreateAnonymousMetadataDecoder(DecoderType aType,
NotNull<SourceBuffer*> aSourceBuffer);
private:
virtual ~DecoderFactory() = 0;
/**
* An internal method which allocates a new decoder of the requested @aType.
*/
static already_AddRefed<Decoder> GetDecoder(DecoderType aType,
RasterImage* aImage,
bool aIsRedecode);
};
} // namespace image
} // namespace mozilla
#endif // mozilla_image_DecoderFactory_h

42
image/DecoderFlags.h Normal file
View file

@ -0,0 +1,42 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* 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/. */
#ifndef mozilla_image_DecoderFlags_h
#define mozilla_image_DecoderFlags_h
#include "mozilla/TypedEnumBits.h"
namespace mozilla {
namespace image {
/**
* Flags that influence decoder behavior. Note that these flags *don't*
* influence the logical content of the surfaces that the decoder generates, so
* they're not in a factor in SurfaceCache lookups and the like. These flags
* instead either influence which surfaces are generated at all or the tune the
* decoder's behavior for a particular scenario.
*/
enum class DecoderFlags : uint8_t
{
FIRST_FRAME_ONLY = 1 << 0,
IS_REDECODE = 1 << 1,
IMAGE_IS_TRANSIENT = 1 << 2,
ASYNC_NOTIFY = 1 << 3
};
MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(DecoderFlags)
/**
* @return the default set of decode flags.
*/
inline DecoderFlags
DefaultDecoderFlags()
{
return DecoderFlags();
}
} // namespace image
} // namespace mozilla
#endif // mozilla_image_DecoderFlags_h

352
image/Downscaler.cpp Normal file
View file

@ -0,0 +1,352 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
*
* 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/. */
#include "Downscaler.h"
#include <algorithm>
#include <ctime>
#include "gfxPrefs.h"
#include "image_operations.h"
#include "mozilla/gfx/2D.h"
#include "mozilla/SSE.h"
#include "mozilla/mips.h"
#include "convolver.h"
#include "skia/include/core/SkTypes.h"
using std::max;
using std::swap;
namespace mozilla {
using gfx::IntRect;
namespace image {
Downscaler::Downscaler(const nsIntSize& aTargetSize)
: mTargetSize(aTargetSize)
, mOutputBuffer(nullptr)
, mXFilter(MakeUnique<skia::ConvolutionFilter1D>())
, mYFilter(MakeUnique<skia::ConvolutionFilter1D>())
, mWindowCapacity(0)
, mHasAlpha(true)
, mFlipVertically(false)
{
MOZ_ASSERT(gfxPrefs::ImageDownscaleDuringDecodeEnabled(),
"Downscaling even though downscale-during-decode is disabled?");
MOZ_ASSERT(mTargetSize.width > 0 && mTargetSize.height > 0,
"Invalid target size");
}
Downscaler::~Downscaler()
{
ReleaseWindow();
}
void
Downscaler::ReleaseWindow()
{
if (!mWindow) {
return;
}
for (int32_t i = 0; i < mWindowCapacity; ++i) {
delete[] mWindow[i];
}
mWindow = nullptr;
mWindowCapacity = 0;
}
nsresult
Downscaler::BeginFrame(const nsIntSize& aOriginalSize,
const Maybe<nsIntRect>& aFrameRect,
uint8_t* aOutputBuffer,
bool aHasAlpha,
bool aFlipVertically /* = false */)
{
MOZ_ASSERT(aOutputBuffer);
MOZ_ASSERT(mTargetSize != aOriginalSize,
"Created a downscaler, but not downscaling?");
MOZ_ASSERT(mTargetSize.width <= aOriginalSize.width,
"Created a downscaler, but width is larger");
MOZ_ASSERT(mTargetSize.height <= aOriginalSize.height,
"Created a downscaler, but height is larger");
MOZ_ASSERT(aOriginalSize.width > 0 && aOriginalSize.height > 0,
"Invalid original size");
// Only downscale from reasonable sizes to avoid using too much memory/cpu
// downscaling and decoding. 1 << 20 == 1,048,576 seems a reasonable limit.
if (aOriginalSize.width > (1 << 20) || aOriginalSize.height > (1 << 20)) {
NS_WARNING("Trying to downscale image frame that is too large");
return NS_ERROR_INVALID_ARG;
}
mFrameRect = aFrameRect.valueOr(nsIntRect(nsIntPoint(), aOriginalSize));
MOZ_ASSERT(mFrameRect.x >= 0 && mFrameRect.y >= 0 &&
mFrameRect.width >= 0 && mFrameRect.height >= 0,
"Frame rect must have non-negative components");
MOZ_ASSERT(nsIntRect(0, 0, aOriginalSize.width, aOriginalSize.height)
.Contains(mFrameRect),
"Frame rect must fit inside image");
MOZ_ASSERT_IF(!nsIntRect(0, 0, aOriginalSize.width, aOriginalSize.height)
.IsEqualEdges(mFrameRect),
aHasAlpha);
mOriginalSize = aOriginalSize;
mScale = gfxSize(double(mOriginalSize.width) / mTargetSize.width,
double(mOriginalSize.height) / mTargetSize.height);
mOutputBuffer = aOutputBuffer;
mHasAlpha = aHasAlpha;
mFlipVertically = aFlipVertically;
ReleaseWindow();
auto resizeMethod = skia::ImageOperations::RESIZE_LANCZOS3;
skia::resize::ComputeFilters(resizeMethod,
mOriginalSize.width, mTargetSize.width,
0, mTargetSize.width,
mXFilter.get());
if (mXFilter->max_filter() <= 0 || mXFilter->num_values() != mTargetSize.width) {
NS_WARNING("Failed to compute filters for image downscaling");
return NS_ERROR_OUT_OF_MEMORY;
}
skia::resize::ComputeFilters(resizeMethod,
mOriginalSize.height, mTargetSize.height,
0, mTargetSize.height,
mYFilter.get());
if (mYFilter->max_filter() <= 0 || mYFilter->num_values() != mTargetSize.height) {
NS_WARNING("Failed to compute filters for image downscaling");
return NS_ERROR_OUT_OF_MEMORY;
}
// Allocate the buffer, which contains scanlines of the original image.
// pad by 15 to handle overreads by the simd code
size_t bufferLen = mOriginalSize.width * sizeof(uint32_t) + 15;
mRowBuffer.reset(new (fallible) uint8_t[bufferLen]);
if (MOZ_UNLIKELY(!mRowBuffer)) {
return NS_ERROR_OUT_OF_MEMORY;
}
// Zero buffer to keep valgrind happy.
memset(mRowBuffer.get(), 0, bufferLen);
// Allocate the window, which contains horizontally downscaled scanlines. (We
// can store scanlines which are already downscale because our downscaling
// filter is separable.)
mWindowCapacity = mYFilter->max_filter();
mWindow.reset(new (fallible) uint8_t*[mWindowCapacity]);
if (MOZ_UNLIKELY(!mWindow)) {
return NS_ERROR_OUT_OF_MEMORY;
}
bool anyAllocationFailed = false;
// pad by 15 to handle overreads by the simd code
const int rowSize = mTargetSize.width * sizeof(uint32_t) + 15;
for (int32_t i = 0; i < mWindowCapacity; ++i) {
mWindow[i] = new (fallible) uint8_t[rowSize];
anyAllocationFailed = anyAllocationFailed || mWindow[i] == nullptr;
}
if (MOZ_UNLIKELY(anyAllocationFailed)) {
// We intentionally iterate through the entire array even if an allocation
// fails, to ensure that all the pointers in it are either valid or nullptr.
// That in turn ensures that ReleaseWindow() can clean up correctly.
return NS_ERROR_OUT_OF_MEMORY;
}
ResetForNextProgressivePass();
return NS_OK;
}
void
Downscaler::SkipToRow(int32_t aRow)
{
if (mCurrentInLine < aRow) {
ClearRow();
do {
CommitRow();
} while (mCurrentInLine < aRow);
}
}
void
Downscaler::ResetForNextProgressivePass()
{
mPrevInvalidatedLine = 0;
mCurrentOutLine = 0;
mCurrentInLine = 0;
mLinesInBuffer = 0;
if (mFrameRect.IsEmpty()) {
// Our frame rect is zero size; commit rows until the end of the image.
SkipToRow(mOriginalSize.height - 1);
} else {
// If we have a vertical offset, commit rows to shift us past it.
SkipToRow(mFrameRect.y);
}
}
static void
GetFilterOffsetAndLength(UniquePtr<skia::ConvolutionFilter1D>& aFilter,
int32_t aOutputImagePosition,
int32_t* aFilterOffsetOut,
int32_t* aFilterLengthOut)
{
MOZ_ASSERT(aOutputImagePosition < aFilter->num_values());
aFilter->FilterForValue(aOutputImagePosition,
aFilterOffsetOut,
aFilterLengthOut);
}
void
Downscaler::ClearRestOfRow(uint32_t aStartingAtCol)
{
MOZ_ASSERT(int64_t(aStartingAtCol) <= int64_t(mOriginalSize.width));
uint32_t bytesToClear = (mOriginalSize.width - aStartingAtCol)
* sizeof(uint32_t);
memset(mRowBuffer.get() + (aStartingAtCol * sizeof(uint32_t)),
0, bytesToClear);
}
void
Downscaler::CommitRow()
{
MOZ_ASSERT(mOutputBuffer, "Should have a current frame");
MOZ_ASSERT(mCurrentInLine < mOriginalSize.height, "Past end of input");
if (mCurrentOutLine < mTargetSize.height) {
int32_t filterOffset = 0;
int32_t filterLength = 0;
GetFilterOffsetAndLength(mYFilter, mCurrentOutLine,
&filterOffset, &filterLength);
int32_t inLineToRead = filterOffset + mLinesInBuffer;
MOZ_ASSERT(mCurrentInLine <= inLineToRead, "Reading past end of input");
if (mCurrentInLine == inLineToRead) {
skia::ConvolveHorizontally(mRowBuffer.get(), *mXFilter,
mWindow[mLinesInBuffer++], mHasAlpha,
supports_sse2() || supports_mmi());
}
MOZ_ASSERT(mCurrentOutLine < mTargetSize.height,
"Writing past end of output");
while (mLinesInBuffer == filterLength) {
DownscaleInputLine();
if (mCurrentOutLine == mTargetSize.height) {
break; // We're done.
}
GetFilterOffsetAndLength(mYFilter, mCurrentOutLine,
&filterOffset, &filterLength);
}
}
mCurrentInLine += 1;
// If we're at the end of the part of the original image that has data, commit
// rows to shift us to the end.
if (mCurrentInLine == (mFrameRect.y + mFrameRect.height)) {
SkipToRow(mOriginalSize.height - 1);
}
}
bool
Downscaler::HasInvalidation() const
{
return mCurrentOutLine > mPrevInvalidatedLine;
}
DownscalerInvalidRect
Downscaler::TakeInvalidRect()
{
if (MOZ_UNLIKELY(!HasInvalidation())) {
return DownscalerInvalidRect();
}
DownscalerInvalidRect invalidRect;
// Compute the target size invalid rect.
if (mFlipVertically) {
// We need to flip it. This will implicitly flip the original size invalid
// rect, since we compute it by scaling this rect.
invalidRect.mTargetSizeRect =
IntRect(0, mTargetSize.height - mCurrentOutLine,
mTargetSize.width, mCurrentOutLine - mPrevInvalidatedLine);
} else {
invalidRect.mTargetSizeRect =
IntRect(0, mPrevInvalidatedLine,
mTargetSize.width, mCurrentOutLine - mPrevInvalidatedLine);
}
mPrevInvalidatedLine = mCurrentOutLine;
// Compute the original size invalid rect.
invalidRect.mOriginalSizeRect = invalidRect.mTargetSizeRect;
invalidRect.mOriginalSizeRect.ScaleRoundOut(mScale.width, mScale.height);
return invalidRect;
}
void
Downscaler::DownscaleInputLine()
{
typedef skia::ConvolutionFilter1D::Fixed FilterValue;
MOZ_ASSERT(mOutputBuffer);
MOZ_ASSERT(mCurrentOutLine < mTargetSize.height,
"Writing past end of output");
int32_t filterOffset = 0;
int32_t filterLength = 0;
MOZ_ASSERT(mCurrentOutLine < mYFilter->num_values());
auto filterValues =
mYFilter->FilterForValue(mCurrentOutLine, &filterOffset, &filterLength);
int32_t currentOutLine = mFlipVertically
? mTargetSize.height - (mCurrentOutLine + 1)
: mCurrentOutLine;
MOZ_ASSERT(currentOutLine >= 0);
uint8_t* outputLine =
&mOutputBuffer[currentOutLine * mTargetSize.width * sizeof(uint32_t)];
skia::ConvolveVertically(static_cast<const FilterValue*>(filterValues),
filterLength, mWindow.get(), mXFilter->num_values(),
outputLine, mHasAlpha, supports_sse2() || supports_mmi());
mCurrentOutLine += 1;
if (mCurrentOutLine == mTargetSize.height) {
// We're done.
return;
}
int32_t newFilterOffset = 0;
int32_t newFilterLength = 0;
GetFilterOffsetAndLength(mYFilter, mCurrentOutLine,
&newFilterOffset, &newFilterLength);
int diff = newFilterOffset - filterOffset;
MOZ_ASSERT(diff >= 0, "Moving backwards in the filter?");
// Shift the buffer. We're just moving pointers here, so this is cheap.
mLinesInBuffer -= diff;
mLinesInBuffer = max(mLinesInBuffer, 0);
for (int32_t i = 0; i < mLinesInBuffer; ++i) {
swap(mWindow[i], mWindow[filterLength - mLinesInBuffer + i]);
}
}
} // namespace image
} // namespace mozilla

189
image/Downscaler.h Normal file
View file

@ -0,0 +1,189 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
*
* 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/. */
/**
* Downscaler is a high-quality, streaming image downscaler based upon Skia's
* scaling implementation.
*/
#ifndef mozilla_image_Downscaler_h
#define mozilla_image_Downscaler_h
#include "mozilla/Maybe.h"
#include "mozilla/UniquePtr.h"
#include "gfxPoint.h"
#include "nsRect.h"
namespace skia {
class ConvolutionFilter1D;
} // namespace skia
namespace mozilla {
namespace image {
/**
* DownscalerInvalidRect wraps two invalidation rects: one in terms of the
* original image size, and one in terms of the target size.
*/
struct DownscalerInvalidRect
{
nsIntRect mOriginalSizeRect;
nsIntRect mTargetSizeRect;
};
#ifdef MOZ_ENABLE_SKIA
/**
* Downscaler is a high-quality, streaming image downscaler based upon Skia's
* scaling implementation.
*
* Decoders can construct a Downscaler once they know their target size, then
* call BeginFrame() for each frame they decode. They should write a decoded row
* into the buffer returned by RowBuffer(), and then call CommitRow() to signal
* that they have finished.
*
* Because invalidations need to be computed in terms of the scaled version of
* the image, Downscaler also tracks them. Decoders can call HasInvalidation()
* and TakeInvalidRect() instead of tracking invalidations themselves.
*/
class Downscaler
{
public:
/// Constructs a new Downscaler which to scale to size @aTargetSize.
explicit Downscaler(const nsIntSize& aTargetSize);
~Downscaler();
const nsIntSize& OriginalSize() const { return mOriginalSize; }
const nsIntSize& TargetSize() const { return mTargetSize; }
const nsIntSize FrameSize() const { return nsIntSize(mFrameRect.width, mFrameRect.height); }
const gfxSize& Scale() const { return mScale; }
/**
* Begins a new frame and reinitializes the Downscaler.
*
* @param aOriginalSize The original size of this frame, before scaling.
* @param aFrameRect The region of the original image which has data.
* Every pixel outside @aFrameRect is considered blank and
* has zero alpha.
* @param aOutputBuffer The buffer to which the Downscaler should write its
* output; this is the same buffer where the Decoder
* would write its output when not downscaling during
* decode.
* @param aHasAlpha Whether or not this frame has an alpha channel.
* Performance is a little better if it doesn't have one.
* @param aFlipVertically If true, output rows will be written to the output
* buffer in reverse order vertically, which matches
* the way they are stored in some image formats.
*/
nsresult BeginFrame(const nsIntSize& aOriginalSize,
const Maybe<nsIntRect>& aFrameRect,
uint8_t* aOutputBuffer,
bool aHasAlpha,
bool aFlipVertically = false);
bool IsFrameComplete() const { return mCurrentInLine >= mOriginalSize.height; }
/// Retrieves the buffer into which the Decoder should write each row.
uint8_t* RowBuffer()
{
return mRowBuffer.get() + mFrameRect.x * sizeof(uint32_t);
}
/// Clears the current row buffer.
void ClearRow() { ClearRestOfRow(0); }
/// Clears the current row buffer starting at @aStartingAtCol.
void ClearRestOfRow(uint32_t aStartingAtCol);
/// Signals that the decoder has finished writing a row into the row buffer.
void CommitRow();
/// Returns true if there is a non-empty invalid rect available.
bool HasInvalidation() const;
/// Takes the Downscaler's current invalid rect and resets it.
DownscalerInvalidRect TakeInvalidRect();
/**
* Resets the Downscaler's position in the image, for a new progressive pass
* over the same frame. Because the same data structures can be reused, this
* is more efficient than calling BeginFrame.
*/
void ResetForNextProgressivePass();
private:
void DownscaleInputLine();
void ReleaseWindow();
void SkipToRow(int32_t aRow);
nsIntSize mOriginalSize;
nsIntSize mTargetSize;
nsIntRect mFrameRect;
gfxSize mScale;
uint8_t* mOutputBuffer;
UniquePtr<uint8_t[]> mRowBuffer;
UniquePtr<uint8_t*[]> mWindow;
UniquePtr<skia::ConvolutionFilter1D> mXFilter;
UniquePtr<skia::ConvolutionFilter1D> mYFilter;
int32_t mWindowCapacity;
int32_t mLinesInBuffer;
int32_t mPrevInvalidatedLine;
int32_t mCurrentOutLine;
int32_t mCurrentInLine;
bool mHasAlpha : 1;
bool mFlipVertically : 1;
};
#else
/**
* Downscaler requires Skia to work, so we provide a dummy implementation if
* Skia is disabled that asserts if constructed.
*/
class Downscaler
{
public:
explicit Downscaler(const nsIntSize&)
{
MOZ_RELEASE_ASSERT(false, "Skia is not enabled");
}
const nsIntSize& OriginalSize() const { return nsIntSize(); }
const nsIntSize& TargetSize() const { return nsIntSize(); }
const gfxSize& Scale() const { return gfxSize(1.0, 1.0); }
nsresult BeginFrame(const nsIntSize&, const Maybe<nsIntRect>&, uint8_t*, bool, bool = false)
{
return NS_ERROR_FAILURE;
}
bool IsFrameComplete() const { return false; }
uint8_t* RowBuffer() { return nullptr; }
void ClearRow() { }
void ClearRestOfRow(uint32_t) { }
void CommitRow() { }
bool HasInvalidation() const { return false; }
DownscalerInvalidRect TakeInvalidRect() { return DownscalerInvalidRect(); }
void ResetForNextProgressivePass() { }
const nsIntSize FrameSize() const { return nsIntSize(0, 0); }
};
#endif // MOZ_ENABLE_SKIA
} // namespace image
} // namespace mozilla
#endif // mozilla_image_Downscaler_h

380
image/DownscalingFilter.h Normal file
View file

@ -0,0 +1,380 @@
/* -*- 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/. */
/**
* DownscalingSurfaceFilter is a SurfaceFilter implementation for use with
* SurfacePipe which performs Lanczos downscaling.
*
* It's in this header file, separated from the other SurfaceFilters, because
* some preprocessor magic is necessary to ensure that there aren't compilation
* issues on platforms where Skia is unavailable.
*/
#ifndef mozilla_image_DownscalingFilter_h
#define mozilla_image_DownscalingFilter_h
#include <algorithm>
#include <ctime>
#include <stdint.h>
#include "mozilla/Maybe.h"
#include "mozilla/SSE.h"
#include "mozilla/mips.h"
#include "mozilla/UniquePtr.h"
#include "mozilla/gfx/2D.h"
#include "gfxPrefs.h"
#ifdef MOZ_ENABLE_SKIA
#include "convolver.h"
#include "image_operations.h"
#include "skia/include/core/SkTypes.h"
#endif
#include "SurfacePipe.h"
namespace mozilla {
namespace image {
//////////////////////////////////////////////////////////////////////////////
// DownscalingFilter
//////////////////////////////////////////////////////////////////////////////
template <typename Next> class DownscalingFilter;
/**
* A configuration struct for DownscalingConfig.
*/
struct DownscalingConfig
{
template <typename Next> using Filter = DownscalingFilter<Next>;
gfx::IntSize mInputSize; /// The size of the input image. We'll downscale
/// from this size to the input size of the next
/// SurfaceFilter in the chain.
gfx::SurfaceFormat mFormat; /// The pixel format - BGRA or BGRX. (BGRX has
/// slightly better performance.)
};
#ifndef MOZ_ENABLE_SKIA
/**
* DownscalingFilter requires Skia. This is a fallback implementation for
* non-Skia builds that fails when Configure() is called (which will prevent
* SurfacePipeFactory from returning an instance of it) and crashes if a caller
* manually constructs an instance and attempts to actually use it. Callers
* should avoid this by ensuring that they do not request downscaling in
* non-Skia builds.
*/
template <typename Next>
class DownscalingFilter final : public SurfaceFilter
{
public:
Maybe<SurfaceInvalidRect> TakeInvalidRect() override { return Nothing(); }
template <typename... Rest>
nsresult Configure(const DownscalingConfig& aConfig, Rest... aRest)
{
return NS_ERROR_FAILURE;
}
protected:
uint8_t* DoResetToFirstRow() override { MOZ_CRASH(); return nullptr; }
uint8_t* DoAdvanceRow() override { MOZ_CRASH(); return nullptr; }
};
#else
/**
* DownscalingFilter performs Lanczos downscaling, taking image input data at one size
* and outputting it rescaled to a different size.
*
* The 'Next' template parameter specifies the next filter in the chain.
*/
template <typename Next>
class DownscalingFilter final : public SurfaceFilter
{
public:
DownscalingFilter()
: mXFilter(MakeUnique<skia::ConvolutionFilter1D>())
, mYFilter(MakeUnique<skia::ConvolutionFilter1D>())
, mWindowCapacity(0)
, mRowsInWindow(0)
, mInputRow(0)
, mOutputRow(0)
, mHasAlpha(true)
{
MOZ_ASSERT(gfxPrefs::ImageDownscaleDuringDecodeEnabled(),
"Downscaling even though downscale-during-decode is disabled?");
}
~DownscalingFilter()
{
ReleaseWindow();
}
template <typename... Rest>
nsresult Configure(const DownscalingConfig& aConfig, Rest... aRest)
{
nsresult rv = mNext.Configure(aRest...);
if (NS_FAILED(rv)) {
return rv;
}
if (mNext.IsValidPalettedPipe()) {
NS_WARNING("Created a downscaler for a paletted surface?");
return NS_ERROR_INVALID_ARG;
}
if (mNext.InputSize() == aConfig.mInputSize) {
NS_WARNING("Created a downscaler, but not downscaling?");
return NS_ERROR_INVALID_ARG;
}
if (mNext.InputSize().width > aConfig.mInputSize.width) {
NS_WARNING("Created a downscaler, but width is larger");
return NS_ERROR_INVALID_ARG;
}
if (mNext.InputSize().height > aConfig.mInputSize.height) {
NS_WARNING("Created a downscaler, but height is larger");
return NS_ERROR_INVALID_ARG;
}
if (aConfig.mInputSize.width <= 0 || aConfig.mInputSize.height <= 0) {
NS_WARNING("Invalid input size for DownscalingFilter");
return NS_ERROR_INVALID_ARG;
}
mInputSize = aConfig.mInputSize;
gfx::IntSize outputSize = mNext.InputSize();
mScale = gfxSize(double(mInputSize.width) / outputSize.width,
double(mInputSize.height) / outputSize.height);
mHasAlpha = aConfig.mFormat == gfx::SurfaceFormat::B8G8R8A8;
ReleaseWindow();
auto resizeMethod = skia::ImageOperations::RESIZE_LANCZOS3;
skia::resize::ComputeFilters(resizeMethod,
mInputSize.width, outputSize.width,
0, outputSize.width,
mXFilter.get());
skia::resize::ComputeFilters(resizeMethod,
mInputSize.height, outputSize.height,
0, outputSize.height,
mYFilter.get());
// Allocate the buffer, which contains scanlines of the input image.
mRowBuffer.reset(new (fallible)
uint8_t[PaddedWidthInBytes(mInputSize.width)]);
if (MOZ_UNLIKELY(!mRowBuffer)) {
return NS_ERROR_OUT_OF_MEMORY;
}
// Clear the buffer to avoid writing uninitialized memory to the output.
memset(mRowBuffer.get(), 0, PaddedWidthInBytes(mInputSize.width));
// Allocate the window, which contains horizontally downscaled scanlines. (We
// can store scanlines which are already downscaled because our downscaling
// filter is separable.)
mWindowCapacity = mYFilter->max_filter();
mWindow.reset(new (fallible) uint8_t*[mWindowCapacity]);
if (MOZ_UNLIKELY(!mWindow)) {
return NS_ERROR_OUT_OF_MEMORY;
}
// Allocate the "window" of recent rows that we keep in memory as input for
// the downscaling code. We intentionally iterate through the entire array
// even if an allocation fails, to ensure that all the pointers in it are
// either valid or nullptr. That in turn ensures that ReleaseWindow() can
// clean up correctly.
bool anyAllocationFailed = false;
const uint32_t windowRowSizeInBytes = PaddedWidthInBytes(outputSize.width);
for (int32_t i = 0; i < mWindowCapacity; ++i) {
mWindow[i] = new (fallible) uint8_t[windowRowSizeInBytes];
anyAllocationFailed = anyAllocationFailed || mWindow[i] == nullptr;
}
if (MOZ_UNLIKELY(anyAllocationFailed)) {
return NS_ERROR_OUT_OF_MEMORY;
}
ConfigureFilter(mInputSize, sizeof(uint32_t));
return NS_OK;
}
Maybe<SurfaceInvalidRect> TakeInvalidRect() override
{
Maybe<SurfaceInvalidRect> invalidRect = mNext.TakeInvalidRect();
if (invalidRect) {
// Compute the input space invalid rect by scaling.
invalidRect->mInputSpaceRect.ScaleRoundOut(mScale.width, mScale.height);
}
return invalidRect;
}
protected:
uint8_t* DoResetToFirstRow() override
{
mNext.ResetToFirstRow();
mInputRow = 0;
mOutputRow = 0;
mRowsInWindow = 0;
return GetRowPointer();
}
uint8_t* DoAdvanceRow() override
{
if (mInputRow >= mInputSize.height) {
NS_WARNING("Advancing DownscalingFilter past the end of the input");
return nullptr;
}
if (mOutputRow >= mNext.InputSize().height) {
NS_WARNING("Advancing DownscalingFilter past the end of the output");
return nullptr;
}
int32_t filterOffset = 0;
int32_t filterLength = 0;
GetFilterOffsetAndLength(mYFilter, mOutputRow,
&filterOffset, &filterLength);
int32_t inputRowToRead = filterOffset + mRowsInWindow;
MOZ_ASSERT(mInputRow <= inputRowToRead, "Reading past end of input");
if (mInputRow == inputRowToRead) {
skia::ConvolveHorizontally(mRowBuffer.get(), *mXFilter,
mWindow[mRowsInWindow++], mHasAlpha,
supports_sse2() || supports_mmi());
}
MOZ_ASSERT(mOutputRow < mNext.InputSize().height,
"Writing past end of output");
while (mRowsInWindow == filterLength) {
DownscaleInputRow();
if (mOutputRow == mNext.InputSize().height) {
break; // We're done.
}
GetFilterOffsetAndLength(mYFilter, mOutputRow,
&filterOffset, &filterLength);
}
mInputRow++;
return mInputRow < mInputSize.height ? GetRowPointer()
: nullptr;
}
private:
uint8_t* GetRowPointer() const { return mRowBuffer.get(); }
static uint32_t PaddedWidthInBytes(uint32_t aLogicalWidth)
{
// Convert from width in BGRA/BGRX pixels to width in bytes, padding by 15
// to handle overreads by the SIMD code inside Skia.
return aLogicalWidth * sizeof(uint32_t) + 15;
}
static void
GetFilterOffsetAndLength(UniquePtr<skia::ConvolutionFilter1D>& aFilter,
int32_t aOutputImagePosition,
int32_t* aFilterOffsetOut,
int32_t* aFilterLengthOut)
{
MOZ_ASSERT(aOutputImagePosition < aFilter->num_values());
aFilter->FilterForValue(aOutputImagePosition,
aFilterOffsetOut,
aFilterLengthOut);
}
void DownscaleInputRow()
{
typedef skia::ConvolutionFilter1D::Fixed FilterValue;
MOZ_ASSERT(mOutputRow < mNext.InputSize().height,
"Writing past end of output");
int32_t filterOffset = 0;
int32_t filterLength = 0;
MOZ_ASSERT(mOutputRow < mYFilter->num_values());
auto filterValues =
mYFilter->FilterForValue(mOutputRow, &filterOffset, &filterLength);
mNext.template WriteUnsafeComputedRow<uint32_t>([&](uint32_t* aRow,
uint32_t aLength) {
skia::ConvolveVertically(static_cast<const FilterValue*>(filterValues),
filterLength, mWindow.get(), mXFilter->num_values(),
reinterpret_cast<uint8_t*>(aRow), mHasAlpha,
supports_sse2() || supports_mmi());
});
mOutputRow++;
if (mOutputRow == mNext.InputSize().height) {
return; // We're done.
}
int32_t newFilterOffset = 0;
int32_t newFilterLength = 0;
GetFilterOffsetAndLength(mYFilter, mOutputRow,
&newFilterOffset, &newFilterLength);
int diff = newFilterOffset - filterOffset;
MOZ_ASSERT(diff >= 0, "Moving backwards in the filter?");
// Shift the buffer. We're just moving pointers here, so this is cheap.
mRowsInWindow -= diff;
mRowsInWindow = std::max(mRowsInWindow, 0);
for (int32_t i = 0; i < mRowsInWindow; ++i) {
std::swap(mWindow[i], mWindow[filterLength - mRowsInWindow + i]);
}
}
void ReleaseWindow()
{
if (!mWindow) {
return;
}
for (int32_t i = 0; i < mWindowCapacity; ++i) {
delete[] mWindow[i];
}
mWindow = nullptr;
mWindowCapacity = 0;
}
Next mNext; /// The next SurfaceFilter in the chain.
gfx::IntSize mInputSize; /// The size of the input image.
gfxSize mScale; /// The scale factors in each dimension.
/// Computed from @mInputSize and
/// the next filter's input size.
UniquePtr<uint8_t[]> mRowBuffer; /// The buffer into which input is written.
UniquePtr<uint8_t*[]> mWindow; /// The last few rows which were written.
UniquePtr<skia::ConvolutionFilter1D> mXFilter; /// The Lanczos filter in X.
UniquePtr<skia::ConvolutionFilter1D> mYFilter; /// The Lanczos filter in Y.
int32_t mWindowCapacity; /// How many rows the window contains.
int32_t mRowsInWindow; /// How many rows we've buffered in the window.
int32_t mInputRow; /// The current row we're reading. (0-indexed)
int32_t mOutputRow; /// The current row we're writing. (0-indexed)
bool mHasAlpha; /// If true, the image has transparency.
};
#endif
} // namespace image
} // namespace mozilla
#endif // mozilla_image_DownscalingFilter_h

89
image/DrawResult.h Normal file
View file

@ -0,0 +1,89 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* 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/. */
#ifndef mozilla_image_DrawResult_h
#define mozilla_image_DrawResult_h
#include "mozilla/Attributes.h"
#include "mozilla/Likely.h"
namespace mozilla {
namespace image {
/**
* An enumeration representing the result of a drawing operation.
*
* Most users of DrawResult will only be interested in whether the value is
* SUCCESS or not. The other values are primarily useful for debugging and error
* handling.
*
* SUCCESS: We successfully drew a completely decoded frame of the requested
* size. Drawing again with FLAG_SYNC_DECODE would not change the result.
*
* INCOMPLETE: We successfully drew a frame that was partially decoded. (Note
* that successfully drawing a partially decoded frame may not actually draw any
* pixels!) Drawing again with FLAG_SYNC_DECODE would improve the result.
*
* WRONG_SIZE: We successfully drew a wrongly-sized frame that had to be scaled.
* This is only returned if drawing again with FLAG_SYNC_DECODE would improve
* the result; if the size requested was larger than the intrinsic size of the
* image, for example, we would generally have to scale whether FLAG_SYNC_DECODE
* was specified or not, and therefore we would not return WRONG_SIZE.
*
* NOT_READY: We failed to draw because no decoded version of the image was
* available. Drawing again with FLAG_SYNC_DECODE would improve the result.
* (Though FLAG_SYNC_DECODE will not necessarily work until after the image's
* load event!)
*
* TEMPORARY_ERROR: We failed to draw due to a temporary error. Drawing may
* succeed at a later time.
*
* BAD_IMAGE: We failed to draw because the image has an error. This is a
* permanent condition.
*
* BAD_ARGS: We failed to draw because bad arguments were passed to draw().
*/
enum class MOZ_MUST_USE_TYPE DrawResult : uint8_t
{
SUCCESS,
INCOMPLETE,
WRONG_SIZE,
NOT_READY,
TEMPORARY_ERROR,
BAD_IMAGE,
BAD_ARGS
};
/**
* You can combine DrawResults with &. By analogy to bitwise-&, the result is
* DrawResult::SUCCESS only if both operands are DrawResult::SUCCESS. Otherwise,
* a failing DrawResult is returned; we favor the left operand's failure when
* deciding which failure to return, with the exception that we always prefer
* any other kind of failure over DrawResult::BAD_IMAGE, since other failures
* are recoverable and we want to know if any recoverable failures occurred.
*/
inline DrawResult
operator&(const DrawResult aLeft, const DrawResult aRight)
{
if (MOZ_LIKELY(aLeft == DrawResult::SUCCESS)) {
return aRight;
}
if (aLeft == DrawResult::BAD_IMAGE && aRight != DrawResult::SUCCESS) {
return aRight;
}
return aLeft;
}
inline DrawResult&
operator&=(DrawResult& aLeft, const DrawResult aRight)
{
aLeft = aLeft & aRight;
return aLeft;
}
} // namespace image
} // namespace mozilla
#endif // mozilla_image_DrawResult_h

347
image/DynamicImage.cpp Normal file
View file

@ -0,0 +1,347 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* 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/. */
#include "DynamicImage.h"
#include "gfxPlatform.h"
#include "gfxUtils.h"
#include "mozilla/gfx/2D.h"
#include "mozilla/RefPtr.h"
#include "ImageRegion.h"
#include "Orientation.h"
#include "SVGImageContext.h"
#include "mozilla/MemoryReporting.h"
using namespace mozilla;
using namespace mozilla::gfx;
using mozilla::layers::LayerManager;
using mozilla::layers::ImageContainer;
namespace mozilla {
namespace image {
// Inherited methods from Image.
already_AddRefed<ProgressTracker>
DynamicImage::GetProgressTracker()
{
return nullptr;
}
size_t
DynamicImage::SizeOfSourceWithComputedFallback(MallocSizeOf aMallocSizeOf) const
{
return 0;
}
void
DynamicImage::CollectSizeOfSurfaces(nsTArray<SurfaceMemoryCounter>& aCounters,
MallocSizeOf aMallocSizeOf) const
{
// We can't report anything useful because gfxDrawable doesn't expose this
// information.
}
void
DynamicImage::IncrementAnimationConsumers()
{ }
void
DynamicImage::DecrementAnimationConsumers()
{ }
#ifdef DEBUG
uint32_t
DynamicImage::GetAnimationConsumers()
{
return 0;
}
#endif
nsresult
DynamicImage::OnImageDataAvailable(nsIRequest* aRequest,
nsISupports* aContext,
nsIInputStream* aInStr,
uint64_t aSourceOffset,
uint32_t aCount)
{
return NS_OK;
}
nsresult
DynamicImage::OnImageDataComplete(nsIRequest* aRequest,
nsISupports* aContext,
nsresult aStatus,
bool aLastPart)
{
return NS_OK;
}
void
DynamicImage::OnSurfaceDiscarded()
{ }
void
DynamicImage::SetInnerWindowID(uint64_t aInnerWindowId)
{ }
uint64_t
DynamicImage::InnerWindowID() const
{
return 0;
}
bool
DynamicImage::HasError()
{
return !mDrawable;
}
void
DynamicImage::SetHasError()
{ }
ImageURL*
DynamicImage::GetURI()
{
return nullptr;
}
// Methods inherited from XPCOM interfaces.
NS_IMPL_ISUPPORTS(DynamicImage, imgIContainer)
NS_IMETHODIMP
DynamicImage::GetWidth(int32_t* aWidth)
{
*aWidth = mDrawable->Size().width;
return NS_OK;
}
NS_IMETHODIMP
DynamicImage::GetHeight(int32_t* aHeight)
{
*aHeight = mDrawable->Size().height;
return NS_OK;
}
NS_IMETHODIMP
DynamicImage::GetIntrinsicSize(nsSize* aSize)
{
IntSize intSize(mDrawable->Size());
*aSize = nsSize(intSize.width, intSize.height);
return NS_OK;
}
NS_IMETHODIMP
DynamicImage::GetIntrinsicRatio(nsSize* aSize)
{
IntSize intSize(mDrawable->Size());
*aSize = nsSize(intSize.width, intSize.height);
return NS_OK;
}
NS_IMETHODIMP_(Orientation)
DynamicImage::GetOrientation()
{
return Orientation();
}
NS_IMETHODIMP
DynamicImage::GetType(uint16_t* aType)
{
*aType = imgIContainer::TYPE_RASTER;
return NS_OK;
}
NS_IMETHODIMP
DynamicImage::GetAnimated(bool* aAnimated)
{
*aAnimated = false;
return NS_OK;
}
NS_IMETHODIMP_(already_AddRefed<SourceSurface>)
DynamicImage::GetFrame(uint32_t aWhichFrame,
uint32_t aFlags)
{
IntSize size(mDrawable->Size());
return GetFrameAtSize(IntSize(size.width, size.height),
aWhichFrame,
aFlags);
}
NS_IMETHODIMP_(already_AddRefed<SourceSurface>)
DynamicImage::GetFrameAtSize(const IntSize& aSize,
uint32_t aWhichFrame,
uint32_t aFlags)
{
RefPtr<DrawTarget> dt = gfxPlatform::GetPlatform()->
CreateOffscreenContentDrawTarget(aSize, SurfaceFormat::B8G8R8A8);
if (!dt || !dt->IsValid()) {
gfxWarning() <<
"DynamicImage::GetFrame failed in CreateOffscreenContentDrawTarget";
return nullptr;
}
RefPtr<gfxContext> context = gfxContext::CreateOrNull(dt);
MOZ_ASSERT(context); // already checked the draw target above
auto result = Draw(context, aSize, ImageRegion::Create(aSize),
aWhichFrame, SamplingFilter::POINT, Nothing(), aFlags);
return result == DrawResult::SUCCESS ? dt->Snapshot() : nullptr;
}
NS_IMETHODIMP_(bool)
DynamicImage::WillDrawOpaqueNow()
{
return false;
}
NS_IMETHODIMP_(bool)
DynamicImage::IsImageContainerAvailable(LayerManager* aManager, uint32_t aFlags)
{
return false;
}
NS_IMETHODIMP_(already_AddRefed<ImageContainer>)
DynamicImage::GetImageContainer(LayerManager* aManager, uint32_t aFlags)
{
return nullptr;
}
NS_IMETHODIMP_(DrawResult)
DynamicImage::Draw(gfxContext* aContext,
const nsIntSize& aSize,
const ImageRegion& aRegion,
uint32_t aWhichFrame,
SamplingFilter aSamplingFilter,
const Maybe<SVGImageContext>& aSVGContext,
uint32_t aFlags)
{
MOZ_ASSERT(!aSize.IsEmpty(), "Unexpected empty size");
IntSize drawableSize(mDrawable->Size());
if (aSize == drawableSize) {
gfxUtils::DrawPixelSnapped(aContext, mDrawable, drawableSize, aRegion,
SurfaceFormat::B8G8R8A8, aSamplingFilter);
return DrawResult::SUCCESS;
}
gfxSize scale(double(aSize.width) / drawableSize.width,
double(aSize.height) / drawableSize.height);
ImageRegion region(aRegion);
region.Scale(1.0 / scale.width, 1.0 / scale.height);
gfxContextMatrixAutoSaveRestore saveMatrix(aContext);
aContext->Multiply(gfxMatrix::Scaling(scale.width, scale.height));
gfxUtils::DrawPixelSnapped(aContext, mDrawable, drawableSize, region,
SurfaceFormat::B8G8R8A8, aSamplingFilter);
return DrawResult::SUCCESS;
}
NS_IMETHODIMP
DynamicImage::StartDecoding()
{
return NS_OK;
}
NS_IMETHODIMP
DynamicImage::RequestDecodeForSize(const nsIntSize& aSize, uint32_t aFlags)
{
return NS_OK;
}
NS_IMETHODIMP
DynamicImage::LockImage()
{
return NS_OK;
}
NS_IMETHODIMP
DynamicImage::UnlockImage()
{
return NS_OK;
}
NS_IMETHODIMP
DynamicImage::RequestDiscard()
{
return NS_OK;
}
NS_IMETHODIMP_(void)
DynamicImage::RequestRefresh(const mozilla::TimeStamp& aTime)
{ }
NS_IMETHODIMP
DynamicImage::GetAnimationMode(uint16_t* aAnimationMode)
{
*aAnimationMode = kNormalAnimMode;
return NS_OK;
}
NS_IMETHODIMP
DynamicImage::SetAnimationMode(uint16_t aAnimationMode)
{
return NS_OK;
}
NS_IMETHODIMP
DynamicImage::ResetAnimation()
{
return NS_OK;
}
NS_IMETHODIMP_(float)
DynamicImage::GetFrameIndex(uint32_t aWhichFrame)
{
return 0;
}
NS_IMETHODIMP_(int32_t)
DynamicImage::GetFirstFrameDelay()
{
return 0;
}
NS_IMETHODIMP_(void)
DynamicImage::SetAnimationStartTime(const mozilla::TimeStamp& aTime)
{ }
nsIntSize
DynamicImage::OptimalImageSizeForDest(const gfxSize& aDest,
uint32_t aWhichFrame,
SamplingFilter aSamplingFilter,
uint32_t aFlags)
{
IntSize size(mDrawable->Size());
return nsIntSize(size.width, size.height);
}
NS_IMETHODIMP_(nsIntRect)
DynamicImage::GetImageSpaceInvalidationRect(const nsIntRect& aRect)
{
return aRect;
}
already_AddRefed<imgIContainer>
DynamicImage::Unwrap()
{
nsCOMPtr<imgIContainer> self(this);
return self.forget();
}
void
DynamicImage::PropagateUseCounters(nsIDocument*)
{
// No use counters.
}
} // namespace image
} // namespace mozilla

75
image/DynamicImage.h Normal file
View file

@ -0,0 +1,75 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* 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/. */
#ifndef mozilla_image_DynamicImage_h
#define mozilla_image_DynamicImage_h
#include "mozilla/MemoryReporting.h"
#include "gfxDrawable.h"
#include "Image.h"
namespace mozilla {
namespace image {
/**
* An Image that is dynamically created. The content of the image is provided by
* a gfxDrawable. It's anticipated that most uses of DynamicImage will be
* ephemeral.
*/
class DynamicImage : public Image
{
public:
NS_DECL_ISUPPORTS
NS_DECL_IMGICONTAINER
explicit DynamicImage(gfxDrawable* aDrawable)
: mDrawable(aDrawable)
{
MOZ_ASSERT(aDrawable, "Must have a gfxDrawable to wrap");
}
// Inherited methods from Image.
virtual already_AddRefed<ProgressTracker> GetProgressTracker() override;
virtual size_t SizeOfSourceWithComputedFallback(
MallocSizeOf aMallocSizeOf) const override;
virtual void CollectSizeOfSurfaces(nsTArray<SurfaceMemoryCounter>& aCounters,
MallocSizeOf aMallocSizeOf) const override;
virtual void IncrementAnimationConsumers() override;
virtual void DecrementAnimationConsumers() override;
#ifdef DEBUG
virtual uint32_t GetAnimationConsumers() override;
#endif
virtual nsresult OnImageDataAvailable(nsIRequest* aRequest,
nsISupports* aContext,
nsIInputStream* aInStr,
uint64_t aSourceOffset,
uint32_t aCount) override;
virtual nsresult OnImageDataComplete(nsIRequest* aRequest,
nsISupports* aContext,
nsresult aStatus,
bool aLastPart) override;
virtual void OnSurfaceDiscarded() override;
virtual void SetInnerWindowID(uint64_t aInnerWindowId) override;
virtual uint64_t InnerWindowID() const override;
virtual bool HasError() override;
virtual void SetHasError() override;
virtual ImageURL* GetURI() override;
private:
virtual ~DynamicImage() { }
RefPtr<gfxDrawable> mDrawable;
};
} // namespace image
} // namespace mozilla
#endif // mozilla_image_DynamicImage_h

887
image/FrameAnimator.cpp Normal file
View file

@ -0,0 +1,887 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
* 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/. */
#include "FrameAnimator.h"
#include "mozilla/MemoryReporting.h"
#include "mozilla/Move.h"
#include "imgIContainer.h"
#include "LookupResult.h"
#include "MainThreadUtils.h"
#include "RasterImage.h"
#include "pixman.h"
namespace mozilla {
using namespace gfx;
namespace image {
///////////////////////////////////////////////////////////////////////////////
// AnimationState implementation.
///////////////////////////////////////////////////////////////////////////////
void
AnimationState::SetDoneDecoding(bool aDone)
{
mDoneDecoding = aDone;
}
void
AnimationState::ResetAnimation()
{
mCurrentAnimationFrameIndex = 0;
}
void
AnimationState::SetAnimationMode(uint16_t aAnimationMode)
{
mAnimationMode = aAnimationMode;
}
void
AnimationState::UpdateKnownFrameCount(uint32_t aFrameCount)
{
if (aFrameCount <= mFrameCount) {
// Nothing to do. Since we can redecode animated images, we may see the same
// sequence of updates replayed again, so seeing a smaller frame count than
// what we already know about doesn't indicate an error.
return;
}
MOZ_ASSERT(!mDoneDecoding, "Adding new frames after decoding is finished?");
MOZ_ASSERT(aFrameCount <= mFrameCount + 1, "Skipped a frame?");
mFrameCount = aFrameCount;
}
Maybe<uint32_t>
AnimationState::FrameCount() const
{
return mDoneDecoding ? Some(mFrameCount) : Nothing();
}
void
AnimationState::SetFirstFrameRefreshArea(const IntRect& aRefreshArea)
{
mFirstFrameRefreshArea = aRefreshArea;
}
void
AnimationState::InitAnimationFrameTimeIfNecessary()
{
if (mCurrentAnimationFrameTime.IsNull()) {
mCurrentAnimationFrameTime = TimeStamp::Now();
}
}
void
AnimationState::SetAnimationFrameTime(const TimeStamp& aTime)
{
mCurrentAnimationFrameTime = aTime;
}
uint32_t
AnimationState::GetCurrentAnimationFrameIndex() const
{
return mCurrentAnimationFrameIndex;
}
FrameTimeout
AnimationState::LoopLength() const
{
// If we don't know the loop length yet, we have to treat it as infinite.
if (!mLoopLength) {
return FrameTimeout::Forever();
}
MOZ_ASSERT(mDoneDecoding, "We know the loop length but decoding isn't done?");
// If we're not looping, a single loop time has no meaning.
if (mAnimationMode != imgIContainer::kNormalAnimMode) {
return FrameTimeout::Forever();
}
return *mLoopLength;
}
///////////////////////////////////////////////////////////////////////////////
// FrameAnimator implementation.
///////////////////////////////////////////////////////////////////////////////
TimeStamp
FrameAnimator::GetCurrentImgFrameEndTime(AnimationState& aState) const
{
TimeStamp currentFrameTime = aState.mCurrentAnimationFrameTime;
FrameTimeout timeout = GetTimeoutForFrame(aState.mCurrentAnimationFrameIndex);
if (timeout == FrameTimeout::Forever()) {
// We need to return a sentinel value in this case, because our logic
// doesn't work correctly if we have an infinitely long timeout. We use one
// year in the future as the sentinel because it works with the loop in
// RequestRefresh() below.
// XXX(seth): It'd be preferable to make our logic work correctly with
// infinitely long timeouts.
return TimeStamp::NowLoRes() +
TimeDuration::FromMilliseconds(31536000.0);
}
TimeDuration durationOfTimeout =
TimeDuration::FromMilliseconds(double(timeout.AsMilliseconds()));
TimeStamp currentFrameEndTime = currentFrameTime + durationOfTimeout;
return currentFrameEndTime;
}
RefreshResult
FrameAnimator::AdvanceFrame(AnimationState& aState, TimeStamp aTime)
{
NS_ASSERTION(aTime <= TimeStamp::Now(),
"Given time appears to be in the future");
PROFILER_LABEL_FUNC(js::ProfileEntry::Category::GRAPHICS);
RefreshResult ret;
// Determine what the next frame is, taking into account looping.
uint32_t currentFrameIndex = aState.mCurrentAnimationFrameIndex;
uint32_t nextFrameIndex = currentFrameIndex + 1;
// Check if we're at the end of the loop. (FrameCount() returns Nothing() if
// we don't know the total count yet.)
if (aState.FrameCount() == Some(nextFrameIndex)) {
// If we are not looping forever, initialize the loop counter
if (aState.mLoopRemainingCount < 0 && aState.LoopCount() >= 0) {
aState.mLoopRemainingCount = aState.LoopCount();
}
// If animation mode is "loop once", or we're at end of loop counter,
// it's time to stop animating.
if (aState.mAnimationMode == imgIContainer::kLoopOnceAnimMode ||
aState.mLoopRemainingCount == 0) {
ret.mAnimationFinished = true;
}
nextFrameIndex = 0;
if (aState.mLoopRemainingCount > 0) {
aState.mLoopRemainingCount--;
}
// If we're done, exit early.
if (ret.mAnimationFinished) {
return ret;
}
}
if (nextFrameIndex >= aState.KnownFrameCount()) {
// We've already advanced to the last decoded frame, nothing more we can do.
// We're blocked by network/decoding from displaying the animation at the
// rate specified, so that means the frame we are displaying (the latest
// available) is the frame we want to be displaying at this time. So we
// update the current animation time. If we didn't update the current
// animation time then it could lag behind, which would indicate that we are
// behind in the animation and should try to catch up. When we are done
// decoding (and thus can loop around back to the start of the animation) we
// would then jump to a random point in the animation to try to catch up.
// But we were never behind in the animation.
aState.mCurrentAnimationFrameTime = aTime;
return ret;
}
// There can be frames in the surface cache with index >= KnownFrameCount()
// which GetRawFrame() can access because an async decoder has decoded them,
// but which AnimationState doesn't know about yet because we haven't received
// the appropriate notification on the main thread. Make sure we stay in sync
// with AnimationState.
MOZ_ASSERT(nextFrameIndex < aState.KnownFrameCount());
RawAccessFrameRef nextFrame = GetRawFrame(nextFrameIndex);
// We should always check to see if we have the next frame even if we have
// previously finished decoding. If we needed to redecode (e.g. due to a draw
// failure) we would have discarded all the old frames and may not yet have
// the new ones.
if (!nextFrame || !nextFrame->IsFinished()) {
// Uh oh, the frame we want to show is currently being decoded (partial)
// Wait until the next refresh driver tick and try again
return ret;
}
if (GetTimeoutForFrame(nextFrameIndex) == FrameTimeout::Forever()) {
ret.mAnimationFinished = true;
}
if (nextFrameIndex == 0) {
ret.mDirtyRect = aState.FirstFrameRefreshArea();
} else {
MOZ_ASSERT(nextFrameIndex == currentFrameIndex + 1);
// Change frame
if (!DoBlend(&ret.mDirtyRect, currentFrameIndex, nextFrameIndex)) {
// something went wrong, move on to next
NS_WARNING("FrameAnimator::AdvanceFrame(): Compositing of frame failed");
nextFrame->SetCompositingFailed(true);
aState.mCurrentAnimationFrameTime = GetCurrentImgFrameEndTime(aState);
aState.mCurrentAnimationFrameIndex = nextFrameIndex;
return ret;
}
nextFrame->SetCompositingFailed(false);
}
aState.mCurrentAnimationFrameTime = GetCurrentImgFrameEndTime(aState);
// If we can get closer to the current time by a multiple of the image's loop
// time, we should. We can only do this if we're done decoding; otherwise, we
// don't know the full loop length, and LoopLength() will have to return
// FrameTimeout::Forever().
FrameTimeout loopTime = aState.LoopLength();
if (loopTime != FrameTimeout::Forever()) {
TimeDuration delay = aTime - aState.mCurrentAnimationFrameTime;
if (delay.ToMilliseconds() > loopTime.AsMilliseconds()) {
// Explicitly use integer division to get the floor of the number of
// loops.
uint64_t loops = static_cast<uint64_t>(delay.ToMilliseconds())
/ loopTime.AsMilliseconds();
aState.mCurrentAnimationFrameTime +=
TimeDuration::FromMilliseconds(loops * loopTime.AsMilliseconds());
}
}
// Set currentAnimationFrameIndex at the last possible moment
aState.mCurrentAnimationFrameIndex = nextFrameIndex;
// If we're here, we successfully advanced the frame.
ret.mFrameAdvanced = true;
return ret;
}
RefreshResult
FrameAnimator::RequestRefresh(AnimationState& aState, const TimeStamp& aTime)
{
// only advance the frame if the current time is greater than or
// equal to the current frame's end time.
TimeStamp currentFrameEndTime = GetCurrentImgFrameEndTime(aState);
// By default, an empty RefreshResult.
RefreshResult ret;
while (currentFrameEndTime <= aTime) {
TimeStamp oldFrameEndTime = currentFrameEndTime;
RefreshResult frameRes = AdvanceFrame(aState, aTime);
// Accumulate our result for returning to callers.
ret.Accumulate(frameRes);
currentFrameEndTime = GetCurrentImgFrameEndTime(aState);
// If we didn't advance a frame, and our frame end time didn't change,
// then we need to break out of this loop & wait for the frame(s)
// to finish downloading.
if (!frameRes.mFrameAdvanced && (currentFrameEndTime == oldFrameEndTime)) {
break;
}
}
return ret;
}
LookupResult
FrameAnimator::GetCompositedFrame(uint32_t aFrameNum)
{
// If we have a composited version of this frame, return that.
if (mLastCompositedFrameIndex == int32_t(aFrameNum)) {
return LookupResult(DrawableSurface(mCompositingFrame->DrawableRef()),
MatchType::EXACT);
}
// Otherwise return the raw frame. DoBlend is required to ensure that we only
// hit this case if the frame is not paletted and doesn't require compositing.
LookupResult result =
SurfaceCache::Lookup(ImageKey(mImage),
RasterSurfaceKey(mSize,
DefaultSurfaceFlags(),
PlaybackType::eAnimated));
if (!result) {
return result;
}
// Seek to the appropriate frame. If seeking fails, it means that we couldn't
// get the frame we're looking for; treat this as if the lookup failed.
if (NS_FAILED(result.Surface().Seek(aFrameNum))) {
return LookupResult(MatchType::NOT_FOUND);
}
MOZ_ASSERT(!result.Surface()->GetIsPaletted(),
"About to return a paletted frame");
return result;
}
FrameTimeout
FrameAnimator::GetTimeoutForFrame(uint32_t aFrameNum) const
{
RawAccessFrameRef frame = GetRawFrame(aFrameNum);
if (frame) {
AnimationData data = frame->GetAnimationData();
return data.mTimeout;
}
NS_WARNING("No frame; called GetTimeoutForFrame too early?");
return FrameTimeout::FromRawMilliseconds(100);
}
static void
DoCollectSizeOfCompositingSurfaces(const RawAccessFrameRef& aSurface,
SurfaceMemoryCounterType aType,
nsTArray<SurfaceMemoryCounter>& aCounters,
MallocSizeOf aMallocSizeOf)
{
// Concoct a SurfaceKey for this surface.
SurfaceKey key = RasterSurfaceKey(aSurface->GetImageSize(),
DefaultSurfaceFlags(),
PlaybackType::eStatic);
// Create a counter for this surface.
SurfaceMemoryCounter counter(key, /* aIsLocked = */ true, aType);
// Extract the surface's memory usage information.
size_t heap = 0, nonHeap = 0;
aSurface->AddSizeOfExcludingThis(aMallocSizeOf, heap, nonHeap);
counter.Values().SetDecodedHeap(heap);
counter.Values().SetDecodedNonHeap(nonHeap);
// Record it.
aCounters.AppendElement(counter);
}
void
FrameAnimator::CollectSizeOfCompositingSurfaces(
nsTArray<SurfaceMemoryCounter>& aCounters,
MallocSizeOf aMallocSizeOf) const
{
if (mCompositingFrame) {
DoCollectSizeOfCompositingSurfaces(mCompositingFrame,
SurfaceMemoryCounterType::COMPOSITING,
aCounters,
aMallocSizeOf);
}
if (mCompositingPrevFrame) {
DoCollectSizeOfCompositingSurfaces(mCompositingPrevFrame,
SurfaceMemoryCounterType::COMPOSITING_PREV,
aCounters,
aMallocSizeOf);
}
}
RawAccessFrameRef
FrameAnimator::GetRawFrame(uint32_t aFrameNum) const
{
LookupResult result =
SurfaceCache::Lookup(ImageKey(mImage),
RasterSurfaceKey(mSize,
DefaultSurfaceFlags(),
PlaybackType::eAnimated));
if (!result) {
return RawAccessFrameRef();
}
// Seek to the frame we want. If seeking fails, it means we couldn't get the
// frame we're looking for, so we bail here to avoid returning the wrong frame
// to the caller.
if (NS_FAILED(result.Surface().Seek(aFrameNum))) {
return RawAccessFrameRef(); // Not available yet.
}
return result.Surface()->RawAccessRef();
}
//******************************************************************************
// DoBlend gets called when the timer for animation get fired and we have to
// update the composited frame of the animation.
bool
FrameAnimator::DoBlend(IntRect* aDirtyRect,
uint32_t aPrevFrameIndex,
uint32_t aNextFrameIndex)
{
RawAccessFrameRef prevFrame = GetRawFrame(aPrevFrameIndex);
RawAccessFrameRef nextFrame = GetRawFrame(aNextFrameIndex);
MOZ_ASSERT(prevFrame && nextFrame, "Should have frames here");
AnimationData prevFrameData = prevFrame->GetAnimationData();
if (prevFrameData.mDisposalMethod == DisposalMethod::RESTORE_PREVIOUS &&
!mCompositingPrevFrame) {
prevFrameData.mDisposalMethod = DisposalMethod::CLEAR;
}
IntRect prevRect = prevFrameData.mBlendRect
? prevFrameData.mRect.Intersect(*prevFrameData.mBlendRect)
: prevFrameData.mRect;
bool isFullPrevFrame = prevRect.x == 0 && prevRect.y == 0 &&
prevRect.width == mSize.width &&
prevRect.height == mSize.height;
// Optimization: DisposeClearAll if the previous frame is the same size as
// container and it's clearing itself
if (isFullPrevFrame &&
(prevFrameData.mDisposalMethod == DisposalMethod::CLEAR)) {
prevFrameData.mDisposalMethod = DisposalMethod::CLEAR_ALL;
}
AnimationData nextFrameData = nextFrame->GetAnimationData();
IntRect nextRect = nextFrameData.mBlendRect
? nextFrameData.mRect.Intersect(*nextFrameData.mBlendRect)
: nextFrameData.mRect;
bool isFullNextFrame = nextRect.x == 0 && nextRect.y == 0 &&
nextRect.width == mSize.width &&
nextRect.height == mSize.height;
if (!nextFrame->GetIsPaletted()) {
// Optimization: Skip compositing if the previous frame wants to clear the
// whole image
if (prevFrameData.mDisposalMethod == DisposalMethod::CLEAR_ALL) {
aDirtyRect->SetRect(0, 0, mSize.width, mSize.height);
return true;
}
// Optimization: Skip compositing if this frame is the same size as the
// container and it's fully drawing over prev frame (no alpha)
if (isFullNextFrame &&
(nextFrameData.mDisposalMethod != DisposalMethod::RESTORE_PREVIOUS) &&
!nextFrameData.mHasAlpha) {
aDirtyRect->SetRect(0, 0, mSize.width, mSize.height);
return true;
}
}
// Calculate area that needs updating
switch (prevFrameData.mDisposalMethod) {
default:
MOZ_FALLTHROUGH_ASSERT("Unexpected DisposalMethod");
case DisposalMethod::NOT_SPECIFIED:
case DisposalMethod::KEEP:
*aDirtyRect = nextRect;
break;
case DisposalMethod::CLEAR_ALL:
// Whole image container is cleared
aDirtyRect->SetRect(0, 0, mSize.width, mSize.height);
break;
case DisposalMethod::CLEAR:
// Calc area that needs to be redrawn (the combination of previous and
// this frame)
// XXX - This could be done with multiple framechanged calls
// Having prevFrame way at the top of the image, and nextFrame
// way at the bottom, and both frames being small, we'd be
// telling framechanged to refresh the whole image when only two
// small areas are needed.
aDirtyRect->UnionRect(nextRect, prevRect);
break;
case DisposalMethod::RESTORE_PREVIOUS:
aDirtyRect->SetRect(0, 0, mSize.width, mSize.height);
break;
}
// Optimization:
// Skip compositing if the last composited frame is this frame
// (Only one composited frame was made for this animation. Example:
// Only Frame 3 of a 10 frame image required us to build a composite frame
// On the second loop, we do not need to rebuild the frame
// since it's still sitting in compositingFrame)
if (mLastCompositedFrameIndex == int32_t(aNextFrameIndex)) {
return true;
}
bool needToBlankComposite = false;
// Create the Compositing Frame
if (!mCompositingFrame) {
RefPtr<imgFrame> newFrame = new imgFrame;
nsresult rv = newFrame->InitForDecoder(mSize,
SurfaceFormat::B8G8R8A8);
if (NS_FAILED(rv)) {
mCompositingFrame.reset();
return false;
}
mCompositingFrame = newFrame->RawAccessRef();
needToBlankComposite = true;
} else if (int32_t(aNextFrameIndex) != mLastCompositedFrameIndex+1) {
// If we are not drawing on top of last composited frame,
// then we are building a new composite frame, so let's clear it first.
needToBlankComposite = true;
}
AnimationData compositingFrameData = mCompositingFrame->GetAnimationData();
// More optimizations possible when next frame is not transparent
// But if the next frame has DisposalMethod::RESTORE_PREVIOUS,
// this "no disposal" optimization is not possible,
// because the frame in "after disposal operation" state
// needs to be stored in compositingFrame, so it can be
// copied into compositingPrevFrame later.
bool doDisposal = true;
if (!nextFrameData.mHasAlpha &&
nextFrameData.mDisposalMethod != DisposalMethod::RESTORE_PREVIOUS) {
if (isFullNextFrame) {
// Optimization: No need to dispose prev.frame when
// next frame is full frame and not transparent.
doDisposal = false;
// No need to blank the composite frame
needToBlankComposite = false;
} else {
if ((prevRect.x >= nextRect.x) && (prevRect.y >= nextRect.y) &&
(prevRect.x + prevRect.width <= nextRect.x + nextRect.width) &&
(prevRect.y + prevRect.height <= nextRect.y + nextRect.height)) {
// Optimization: No need to dispose prev.frame when
// next frame fully overlaps previous frame.
doDisposal = false;
}
}
}
if (doDisposal) {
// Dispose of previous: clear, restore, or keep (copy)
switch (prevFrameData.mDisposalMethod) {
case DisposalMethod::CLEAR:
if (needToBlankComposite) {
// If we just created the composite, it could have anything in its
// buffer. Clear whole frame
ClearFrame(compositingFrameData.mRawData,
compositingFrameData.mRect);
} else {
// Only blank out previous frame area (both color & Mask/Alpha)
ClearFrame(compositingFrameData.mRawData,
compositingFrameData.mRect,
prevRect);
}
break;
case DisposalMethod::CLEAR_ALL:
ClearFrame(compositingFrameData.mRawData,
compositingFrameData.mRect);
break;
case DisposalMethod::RESTORE_PREVIOUS:
// It would be better to copy only the area changed back to
// compositingFrame.
if (mCompositingPrevFrame) {
AnimationData compositingPrevFrameData =
mCompositingPrevFrame->GetAnimationData();
CopyFrameImage(compositingPrevFrameData.mRawData,
compositingPrevFrameData.mRect,
compositingFrameData.mRawData,
compositingFrameData.mRect);
// destroy only if we don't need it for this frame's disposal
if (nextFrameData.mDisposalMethod !=
DisposalMethod::RESTORE_PREVIOUS) {
mCompositingPrevFrame.reset();
}
} else {
ClearFrame(compositingFrameData.mRawData,
compositingFrameData.mRect);
}
break;
default:
MOZ_FALLTHROUGH_ASSERT("Unexpected DisposalMethod");
case DisposalMethod::NOT_SPECIFIED:
case DisposalMethod::KEEP:
// Copy previous frame into compositingFrame before we put the new
// frame on top
// Assumes that the previous frame represents a full frame (it could be
// smaller in size than the container, as long as the frame before it
// erased itself)
// Note: Frame 1 never gets into DoBlend(), so (aNextFrameIndex - 1)
// will always be a valid frame number.
if (mLastCompositedFrameIndex != int32_t(aNextFrameIndex - 1)) {
if (isFullPrevFrame && !prevFrame->GetIsPaletted()) {
// Just copy the bits
CopyFrameImage(prevFrameData.mRawData,
prevRect,
compositingFrameData.mRawData,
compositingFrameData.mRect);
} else {
if (needToBlankComposite) {
// Only blank composite when prev is transparent or not full.
if (prevFrameData.mHasAlpha || !isFullPrevFrame) {
ClearFrame(compositingFrameData.mRawData,
compositingFrameData.mRect);
}
}
DrawFrameTo(prevFrameData.mRawData, prevFrameData.mRect,
prevFrameData.mPaletteDataLength,
prevFrameData.mHasAlpha,
compositingFrameData.mRawData,
compositingFrameData.mRect,
prevFrameData.mBlendMethod,
prevFrameData.mBlendRect);
}
}
}
} else if (needToBlankComposite) {
// If we just created the composite, it could have anything in its
// buffers. Clear them
ClearFrame(compositingFrameData.mRawData,
compositingFrameData.mRect);
}
// Check if the frame we are composing wants the previous image restored after
// it is done. Don't store it (again) if last frame wanted its image restored
// too
if ((nextFrameData.mDisposalMethod == DisposalMethod::RESTORE_PREVIOUS) &&
(prevFrameData.mDisposalMethod != DisposalMethod::RESTORE_PREVIOUS)) {
// We are storing the whole image.
// It would be better if we just stored the area that nextFrame is going to
// overwrite.
if (!mCompositingPrevFrame) {
RefPtr<imgFrame> newFrame = new imgFrame;
nsresult rv = newFrame->InitForDecoder(mSize,
SurfaceFormat::B8G8R8A8);
if (NS_FAILED(rv)) {
mCompositingPrevFrame.reset();
return false;
}
mCompositingPrevFrame = newFrame->RawAccessRef();
}
AnimationData compositingPrevFrameData =
mCompositingPrevFrame->GetAnimationData();
CopyFrameImage(compositingFrameData.mRawData,
compositingFrameData.mRect,
compositingPrevFrameData.mRawData,
compositingPrevFrameData.mRect);
mCompositingPrevFrame->Finish();
}
// blit next frame into it's correct spot
DrawFrameTo(nextFrameData.mRawData, nextFrameData.mRect,
nextFrameData.mPaletteDataLength,
nextFrameData.mHasAlpha,
compositingFrameData.mRawData,
compositingFrameData.mRect,
nextFrameData.mBlendMethod,
nextFrameData.mBlendRect);
// Tell the image that it is fully 'downloaded'.
mCompositingFrame->Finish();
mLastCompositedFrameIndex = int32_t(aNextFrameIndex);
return true;
}
//******************************************************************************
// Fill aFrame with black. Does also clears the mask.
void
FrameAnimator::ClearFrame(uint8_t* aFrameData, const IntRect& aFrameRect)
{
if (!aFrameData) {
return;
}
memset(aFrameData, 0, aFrameRect.width * aFrameRect.height * 4);
}
//******************************************************************************
void
FrameAnimator::ClearFrame(uint8_t* aFrameData, const IntRect& aFrameRect,
const IntRect& aRectToClear)
{
if (!aFrameData || aFrameRect.width <= 0 || aFrameRect.height <= 0 ||
aRectToClear.width <= 0 || aRectToClear.height <= 0) {
return;
}
IntRect toClear = aFrameRect.Intersect(aRectToClear);
if (toClear.IsEmpty()) {
return;
}
uint32_t bytesPerRow = aFrameRect.width * 4;
for (int row = toClear.y; row < toClear.y + toClear.height; ++row) {
memset(aFrameData + toClear.x * 4 + row * bytesPerRow, 0,
toClear.width * 4);
}
}
//******************************************************************************
// Whether we succeed or fail will not cause a crash, and there's not much
// we can do about a failure, so there we don't return a nsresult
bool
FrameAnimator::CopyFrameImage(const uint8_t* aDataSrc,
const IntRect& aRectSrc,
uint8_t* aDataDest,
const IntRect& aRectDest)
{
uint32_t dataLengthSrc = aRectSrc.width * aRectSrc.height * 4;
uint32_t dataLengthDest = aRectDest.width * aRectDest.height * 4;
if (!aDataDest || !aDataSrc || dataLengthSrc != dataLengthDest) {
return false;
}
memcpy(aDataDest, aDataSrc, dataLengthDest);
return true;
}
nsresult
FrameAnimator::DrawFrameTo(const uint8_t* aSrcData, const IntRect& aSrcRect,
uint32_t aSrcPaletteLength, bool aSrcHasAlpha,
uint8_t* aDstPixels, const IntRect& aDstRect,
BlendMethod aBlendMethod, const Maybe<IntRect>& aBlendRect)
{
NS_ENSURE_ARG_POINTER(aSrcData);
NS_ENSURE_ARG_POINTER(aDstPixels);
// According to both AGIF and APNG specs, offsets are unsigned
if (aSrcRect.x < 0 || aSrcRect.y < 0) {
NS_WARNING("FrameAnimator::DrawFrameTo: negative offsets not allowed");
return NS_ERROR_FAILURE;
}
// Outside the destination frame, skip it
if ((aSrcRect.x > aDstRect.width) || (aSrcRect.y > aDstRect.height)) {
return NS_OK;
}
if (aSrcPaletteLength) {
// Larger than the destination frame, clip it
int32_t width = std::min(aSrcRect.width, aDstRect.width - aSrcRect.x);
int32_t height = std::min(aSrcRect.height, aDstRect.height - aSrcRect.y);
// The clipped image must now fully fit within destination image frame
NS_ASSERTION((aSrcRect.x >= 0) && (aSrcRect.y >= 0) &&
(aSrcRect.x + width <= aDstRect.width) &&
(aSrcRect.y + height <= aDstRect.height),
"FrameAnimator::DrawFrameTo: Invalid aSrcRect");
// clipped image size may be smaller than source, but not larger
NS_ASSERTION((width <= aSrcRect.width) && (height <= aSrcRect.height),
"FrameAnimator::DrawFrameTo: source must be smaller than dest");
// Get pointers to image data
const uint8_t* srcPixels = aSrcData + aSrcPaletteLength;
uint32_t* dstPixels = reinterpret_cast<uint32_t*>(aDstPixels);
const uint32_t* colormap = reinterpret_cast<const uint32_t*>(aSrcData);
// Skip to the right offset
dstPixels += aSrcRect.x + (aSrcRect.y * aDstRect.width);
if (!aSrcHasAlpha) {
for (int32_t r = height; r > 0; --r) {
for (int32_t c = 0; c < width; c++) {
dstPixels[c] = colormap[srcPixels[c]];
}
// Go to the next row in the source resp. destination image
srcPixels += aSrcRect.width;
dstPixels += aDstRect.width;
}
} else {
for (int32_t r = height; r > 0; --r) {
for (int32_t c = 0; c < width; c++) {
const uint32_t color = colormap[srcPixels[c]];
if (color) {
dstPixels[c] = color;
}
}
// Go to the next row in the source resp. destination image
srcPixels += aSrcRect.width;
dstPixels += aDstRect.width;
}
}
} else {
pixman_image_t* src =
pixman_image_create_bits(
aSrcHasAlpha ? PIXMAN_a8r8g8b8 : PIXMAN_x8r8g8b8,
aSrcRect.width, aSrcRect.height,
reinterpret_cast<uint32_t*>(const_cast<uint8_t*>(aSrcData)),
aSrcRect.width * 4);
if (!src) {
return NS_ERROR_OUT_OF_MEMORY;
}
pixman_image_t* dst =
pixman_image_create_bits(PIXMAN_a8r8g8b8,
aDstRect.width,
aDstRect.height,
reinterpret_cast<uint32_t*>(aDstPixels),
aDstRect.width * 4);
if (!dst) {
pixman_image_unref(src);
return NS_ERROR_OUT_OF_MEMORY;
}
// XXX(seth): This is inefficient but we'll remove it quite soon when we
// move frame compositing into SurfacePipe. For now we need this because
// RemoveFrameRectFilter has transformed PNG frames with frame rects into
// imgFrame's with no frame rects, but with a region of 0 alpha where the
// frame rect should be. This works really nicely if we're using
// BlendMethod::OVER, but BlendMethod::SOURCE will result in that frame rect
// area overwriting the previous frame, which makes the animation look
// wrong. This quick hack fixes that by first compositing the whle new frame
// with BlendMethod::OVER, and then recopying the area that uses
// BlendMethod::SOURCE if needed. To make this work, the decoder has to
// provide a "blend rect" that tells us where to do this. This is just the
// frame rect, but hidden in a way that makes it invisible to most of the
// system, so we can keep eliminating dependencies on it.
auto op = aBlendMethod == BlendMethod::SOURCE ? PIXMAN_OP_SRC
: PIXMAN_OP_OVER;
if (aBlendMethod == BlendMethod::OVER || !aBlendRect ||
(aBlendMethod == BlendMethod::SOURCE && aSrcRect.IsEqualEdges(*aBlendRect))) {
// We don't need to do anything clever. (Or, in the case where no blend
// rect was specified, we can't.)
pixman_image_composite32(op,
src,
nullptr,
dst,
0, 0,
0, 0,
aSrcRect.x, aSrcRect.y,
aSrcRect.width, aSrcRect.height);
} else {
// We need to do the OVER followed by SOURCE trick above.
pixman_image_composite32(PIXMAN_OP_OVER,
src,
nullptr,
dst,
0, 0,
0, 0,
aSrcRect.x, aSrcRect.y,
aSrcRect.width, aSrcRect.height);
pixman_image_composite32(PIXMAN_OP_SRC,
src,
nullptr,
dst,
aBlendRect->x, aBlendRect->y,
0, 0,
aBlendRect->x, aBlendRect->y,
aBlendRect->width, aBlendRect->height);
}
pixman_image_unref(src);
pixman_image_unref(dst);
}
return NS_OK;
}
} // namespace image
} // namespace mozilla

323
image/FrameAnimator.h Normal file
View file

@ -0,0 +1,323 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
*
* 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/. */
#ifndef mozilla_image_FrameAnimator_h
#define mozilla_image_FrameAnimator_h
#include "mozilla/Maybe.h"
#include "mozilla/MemoryReporting.h"
#include "mozilla/TimeStamp.h"
#include "gfxTypes.h"
#include "imgFrame.h"
#include "nsCOMPtr.h"
#include "nsRect.h"
#include "SurfaceCache.h"
namespace mozilla {
namespace image {
class RasterImage;
class AnimationState
{
public:
explicit AnimationState(uint16_t aAnimationMode)
: mFrameCount(0)
, mCurrentAnimationFrameIndex(0)
, mLoopRemainingCount(-1)
, mLoopCount(-1)
, mFirstFrameTimeout(FrameTimeout::FromRawMilliseconds(0))
, mAnimationMode(aAnimationMode)
, mDoneDecoding(false)
{ }
/**
* Call when this image is finished decoding so we know that there aren't any
* more frames coming.
*/
void SetDoneDecoding(bool aDone);
/**
* Call when you need to re-start animating. Ensures we start from the first
* frame.
*/
void ResetAnimation();
/**
* The animation mode of the image.
*
* Constants defined in imgIContainer.idl.
*/
void SetAnimationMode(uint16_t aAnimationMode);
/// Update the number of frames of animation this image is known to have.
void UpdateKnownFrameCount(uint32_t aFrameCount);
/// @return the number of frames of animation we know about so far.
uint32_t KnownFrameCount() const { return mFrameCount; }
/// @return the number of frames this animation has, if we know for sure.
/// (In other words, if decoding is finished.) Otherwise, returns Nothing().
Maybe<uint32_t> FrameCount() const;
/**
* Get or set the area of the image to invalidate when we loop around to the
* first frame.
*/
void SetFirstFrameRefreshArea(const gfx::IntRect& aRefreshArea);
gfx::IntRect FirstFrameRefreshArea() const { return mFirstFrameRefreshArea; }
/**
* If the animation frame time has not yet been set, set it to
* TimeStamp::Now().
*/
void InitAnimationFrameTimeIfNecessary();
/**
* Set the animation frame time to @aTime.
*/
void SetAnimationFrameTime(const TimeStamp& aTime);
/**
* The current frame we're on, from 0 to (numFrames - 1).
*/
uint32_t GetCurrentAnimationFrameIndex() const;
/*
* Set number of times to loop the image.
* @note -1 means loop forever.
*/
void SetLoopCount(int32_t aLoopCount) { mLoopCount = aLoopCount; }
int32_t LoopCount() const { return mLoopCount; }
/// Set the @aLength of a single loop through this image.
void SetLoopLength(FrameTimeout aLength) { mLoopLength = Some(aLength); }
/**
* @return the length of a single loop of this image. If this image is not
* finished decoding, is not animated, or it is animated but does not loop,
* returns FrameTimeout::Forever().
*/
FrameTimeout LoopLength() const;
/*
* Get or set the timeout for the first frame. This is used to allow animation
* scheduling even before a full decode runs for this image.
*/
void SetFirstFrameTimeout(FrameTimeout aTimeout) { mFirstFrameTimeout = aTimeout; }
FrameTimeout FirstFrameTimeout() const { return mFirstFrameTimeout; }
private:
friend class FrameAnimator;
//! Area of the first frame that needs to be redrawn on subsequent loops.
gfx::IntRect mFirstFrameRefreshArea;
//! the time that the animation advanced to the current frame
TimeStamp mCurrentAnimationFrameTime;
//! The number of frames of animation this image has.
uint32_t mFrameCount;
//! The current frame index we're on, in the range [0, mFrameCount).
uint32_t mCurrentAnimationFrameIndex;
//! number of loops remaining before animation stops (-1 no stop)
int32_t mLoopRemainingCount;
//! The total number of loops for the image.
int32_t mLoopCount;
//! The length of a single loop through this image.
Maybe<FrameTimeout> mLoopLength;
//! The timeout for the first frame of this image.
FrameTimeout mFirstFrameTimeout;
//! The animation mode of this image. Constants defined in imgIContainer.
uint16_t mAnimationMode;
//! Whether this image is done being decoded.
bool mDoneDecoding;
};
/**
* RefreshResult is used to let callers know how the state of the animation
* changed during a call to FrameAnimator::RequestRefresh().
*/
struct RefreshResult
{
RefreshResult()
: mFrameAdvanced(false)
, mAnimationFinished(false)
{ }
/// Merges another RefreshResult's changes into this RefreshResult.
void Accumulate(const RefreshResult& aOther)
{
mFrameAdvanced = mFrameAdvanced || aOther.mFrameAdvanced;
mAnimationFinished = mAnimationFinished || aOther.mAnimationFinished;
mDirtyRect = mDirtyRect.Union(aOther.mDirtyRect);
}
// The region of the image that has changed.
gfx::IntRect mDirtyRect;
// If true, we changed frames at least once. Note that, due to looping, we
// could still have ended up on the same frame!
bool mFrameAdvanced : 1;
// Whether the animation has finished playing.
bool mAnimationFinished : 1;
};
class FrameAnimator
{
public:
FrameAnimator(RasterImage* aImage, const gfx::IntSize& aSize)
: mImage(aImage)
, mSize(aSize)
, mLastCompositedFrameIndex(-1)
{
MOZ_COUNT_CTOR(FrameAnimator);
}
~FrameAnimator()
{
MOZ_COUNT_DTOR(FrameAnimator);
}
/**
* Re-evaluate what frame we're supposed to be on, and do whatever blending
* is necessary to get us to that frame.
*
* Returns the result of that blending, including whether the current frame
* changed and what the resulting dirty rectangle is.
*/
RefreshResult RequestRefresh(AnimationState& aState, const TimeStamp& aTime);
/**
* If we have a composited frame for @aFrameNum, returns it. Otherwise,
* returns an empty LookupResult. It is an error to call this method with
* aFrameNum == 0, because the first frame is never composited.
*/
LookupResult GetCompositedFrame(uint32_t aFrameNum);
/**
* Collect an accounting of the memory occupied by the compositing surfaces we
* use during animation playback. All of the actual animation frames are
* stored in the SurfaceCache, so we don't need to report them here.
*/
void CollectSizeOfCompositingSurfaces(nsTArray<SurfaceMemoryCounter>& aCounters,
MallocSizeOf aMallocSizeOf) const;
private: // methods
/**
* Advances the animation. Typically, this will advance a single frame, but it
* may advance multiple frames. This may happen if we have infrequently
* "ticking" refresh drivers (e.g. in background tabs), or extremely short-
* lived animation frames.
*
* @param aTime the time that the animation should advance to. This will
* typically be <= TimeStamp::Now().
*
* @returns a RefreshResult that shows whether the frame was successfully
* advanced, and its resulting dirty rect.
*/
RefreshResult AdvanceFrame(AnimationState& aState, TimeStamp aTime);
/**
* Get the @aIndex-th frame in the frame index, ignoring results of blending.
*/
RawAccessFrameRef GetRawFrame(uint32_t aFrameNum) const;
/// @return the given frame's timeout.
FrameTimeout GetTimeoutForFrame(uint32_t aFrameNum) const;
/**
* Get the time the frame we're currently displaying is supposed to end.
*
* In the error case, returns an "infinity" timestamp.
*/
TimeStamp GetCurrentImgFrameEndTime(AnimationState& aState) const;
bool DoBlend(gfx::IntRect* aDirtyRect,
uint32_t aPrevFrameIndex,
uint32_t aNextFrameIndex);
/** Clears an area of <aFrame> with transparent black.
*
* @param aFrameData Target Frame data
* @param aFrameRect The rectangle of the data pointed ot by aFrameData
*
* @note Does also clears the transparency mask
*/
static void ClearFrame(uint8_t* aFrameData, const gfx::IntRect& aFrameRect);
//! @overload
static void ClearFrame(uint8_t* aFrameData, const gfx::IntRect& aFrameRect,
const gfx::IntRect& aRectToClear);
//! Copy one frame's image and mask into another
static bool CopyFrameImage(const uint8_t* aDataSrc, const gfx::IntRect& aRectSrc,
uint8_t* aDataDest, const gfx::IntRect& aRectDest);
/**
* Draws one frame's image to into another, at the position specified by
* aSrcRect.
*
* @aSrcData the raw data of the current frame being drawn
* @aSrcRect the size of the source frame, and the position of that frame in
* the composition frame
* @aSrcPaletteLength the length (in bytes) of the palette at the beginning
* of the source data (0 if image is not paletted)
* @aSrcHasAlpha whether the source data represents an image with alpha
* @aDstPixels the raw data of the composition frame where the current frame
* is drawn into (32-bit ARGB)
* @aDstRect the size of the composition frame
* @aBlendMethod the blend method for how to blend src on the composition
* frame.
*/
static nsresult DrawFrameTo(const uint8_t* aSrcData,
const gfx::IntRect& aSrcRect,
uint32_t aSrcPaletteLength, bool aSrcHasAlpha,
uint8_t* aDstPixels, const gfx::IntRect& aDstRect,
BlendMethod aBlendMethod,
const Maybe<gfx::IntRect>& aBlendRect);
private: // data
//! A weak pointer to our owning image.
RasterImage* mImage;
//! The intrinsic size of the image.
gfx::IntSize mSize;
/** For managing blending of frames
*
* Some animations will use the compositingFrame to composite images
* and just hand this back to the caller when it is time to draw the frame.
* NOTE: When clearing compositingFrame, remember to set
* lastCompositedFrameIndex to -1. Code assume that if
* lastCompositedFrameIndex >= 0 then compositingFrame exists.
*/
RawAccessFrameRef mCompositingFrame;
/** the previous composited frame, for DISPOSE_RESTORE_PREVIOUS
*
* The Previous Frame (all frames composited up to the current) needs to be
* stored in cases where the image specifies it wants the last frame back
* when it's done with the current frame.
*/
RawAccessFrameRef mCompositingPrevFrame;
//! Track the last composited frame for Optimizations (See DoComposite code)
int32_t mLastCompositedFrameIndex;
};
} // namespace image
} // namespace mozilla
#endif // mozilla_image_FrameAnimator_h

122
image/FrozenImage.cpp Normal file
View file

@ -0,0 +1,122 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* 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/. */
#include "FrozenImage.h"
namespace mozilla {
using namespace gfx;
using layers::ImageContainer;
using layers::LayerManager;
namespace image {
NS_IMPL_ISUPPORTS_INHERITED0(FrozenImage, ImageWrapper)
void
FrozenImage::IncrementAnimationConsumers()
{
// Do nothing. This will prevent animation from starting if there are no other
// instances of this image.
}
void
FrozenImage::DecrementAnimationConsumers()
{
// Do nothing.
}
NS_IMETHODIMP
FrozenImage::GetAnimated(bool* aAnimated)
{
bool dummy;
nsresult rv = InnerImage()->GetAnimated(&dummy);
if (NS_SUCCEEDED(rv)) {
*aAnimated = false;
}
return rv;
}
NS_IMETHODIMP_(already_AddRefed<SourceSurface>)
FrozenImage::GetFrame(uint32_t aWhichFrame,
uint32_t aFlags)
{
return InnerImage()->GetFrame(FRAME_FIRST, aFlags);
}
NS_IMETHODIMP_(already_AddRefed<SourceSurface>)
FrozenImage::GetFrameAtSize(const IntSize& aSize,
uint32_t aWhichFrame,
uint32_t aFlags)
{
return InnerImage()->GetFrameAtSize(aSize, FRAME_FIRST, aFlags);
}
NS_IMETHODIMP_(bool)
FrozenImage::IsImageContainerAvailable(LayerManager* aManager, uint32_t aFlags)
{
return false;
}
NS_IMETHODIMP_(already_AddRefed<ImageContainer>)
FrozenImage::GetImageContainer(layers::LayerManager* aManager, uint32_t aFlags)
{
// XXX(seth): GetImageContainer does not currently support anything but the
// current frame. We work around this by always returning null, but if it ever
// turns out that FrozenImage is widely used on codepaths that can actually
// benefit from GetImageContainer, it would be a good idea to fix that method
// for performance reasons.
return nullptr;
}
NS_IMETHODIMP_(DrawResult)
FrozenImage::Draw(gfxContext* aContext,
const nsIntSize& aSize,
const ImageRegion& aRegion,
uint32_t /* aWhichFrame - ignored */,
SamplingFilter aSamplingFilter,
const Maybe<SVGImageContext>& aSVGContext,
uint32_t aFlags)
{
return InnerImage()->Draw(aContext, aSize, aRegion, FRAME_FIRST,
aSamplingFilter, aSVGContext, aFlags);
}
NS_IMETHODIMP_(void)
FrozenImage::RequestRefresh(const TimeStamp& aTime)
{
// Do nothing.
}
NS_IMETHODIMP
FrozenImage::GetAnimationMode(uint16_t* aAnimationMode)
{
*aAnimationMode = kNormalAnimMode;
return NS_OK;
}
NS_IMETHODIMP
FrozenImage::SetAnimationMode(uint16_t aAnimationMode)
{
// Do nothing.
return NS_OK;
}
NS_IMETHODIMP
FrozenImage::ResetAnimation()
{
// Do nothing.
return NS_OK;
}
NS_IMETHODIMP_(float)
FrozenImage::GetFrameIndex(uint32_t aWhichFrame)
{
MOZ_ASSERT(aWhichFrame <= FRAME_MAX_VALUE, "Invalid argument");
return 0;
}
} // namespace image
} // namespace mozilla

73
image/FrozenImage.h Normal file
View file

@ -0,0 +1,73 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* 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/. */
#ifndef mozilla_image_FrozenImage_h
#define mozilla_image_FrozenImage_h
#include "ImageWrapper.h"
#include "mozilla/gfx/2D.h"
#include "mozilla/RefPtr.h"
namespace mozilla {
namespace image {
/**
* An Image wrapper that disables animation, freezing the image at its first
* frame. It does this using two strategies. If this is the only instance of the
* image, animation will never start, because IncrementAnimationConsumers is
* ignored. If there is another instance that is animated, that's still OK,
* because any imgIContainer method that is affected by animation gets its
* aWhichFrame argument set to FRAME_FIRST when it passes through FrozenImage.
*
* XXX(seth): There a known (performance, not correctness) issue with
* GetImageContainer. See the comments for that method for more information.
*/
class FrozenImage : public ImageWrapper
{
typedef gfx::SourceSurface SourceSurface;
public:
NS_DECL_ISUPPORTS_INHERITED
virtual void IncrementAnimationConsumers() override;
virtual void DecrementAnimationConsumers() override;
NS_IMETHOD GetAnimated(bool* aAnimated) override;
NS_IMETHOD_(already_AddRefed<SourceSurface>)
GetFrame(uint32_t aWhichFrame, uint32_t aFlags) override;
NS_IMETHOD_(already_AddRefed<SourceSurface>)
GetFrameAtSize(const gfx::IntSize& aSize,
uint32_t aWhichFrame,
uint32_t aFlags) override;
NS_IMETHOD_(bool) IsImageContainerAvailable(layers::LayerManager* aManager,
uint32_t aFlags) override;
NS_IMETHOD_(already_AddRefed<layers::ImageContainer>)
GetImageContainer(layers::LayerManager* aManager,
uint32_t aFlags) override;
NS_IMETHOD_(DrawResult) Draw(gfxContext* aContext,
const nsIntSize& aSize,
const ImageRegion& aRegion,
uint32_t aWhichFrame,
gfx::SamplingFilter aSamplingFilter,
const Maybe<SVGImageContext>& aSVGContext,
uint32_t aFlags) override;
NS_IMETHOD_(void) RequestRefresh(const TimeStamp& aTime) override;
NS_IMETHOD GetAnimationMode(uint16_t* aAnimationMode) override;
NS_IMETHOD SetAnimationMode(uint16_t aAnimationMode) override;
NS_IMETHOD ResetAnimation() override;
NS_IMETHOD_(float) GetFrameIndex(uint32_t aWhichFrame) override;
protected:
explicit FrozenImage(Image* aImage) : ImageWrapper(aImage) { }
virtual ~FrozenImage() { }
private:
friend class ImageOps;
};
} // namespace image
} // namespace mozilla
#endif // mozilla_image_FrozenImage_h

81
image/ICOFileHeaders.h Normal file
View file

@ -0,0 +1,81 @@
/* 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/. */
#ifndef mozilla_image_ICOFileHeaders_h
#define mozilla_image_ICOFileHeaders_h
namespace mozilla {
namespace image {
#define ICONFILEHEADERSIZE 6
#define ICODIRENTRYSIZE 16
#define PNGSIGNATURESIZE 8
#define BMPFILEHEADERSIZE 14
/**
* The header that comes right at the start of an icon file. (This
* corresponds to the Windows ICONDIR structure.)
*/
struct IconFileHeader
{
/**
* Must be set to 0;
*/
uint16_t mReserved;
/**
* 1 for icon (.ICO) image (or 2 for cursor (.CUR) image (icon with the
* addition of a hotspot), but we don't support cursor).
*/
uint16_t mType;
/**
* The number of BMP/PNG images contained in the icon file.
*/
uint16_t mCount;
};
/**
* For each BMP/PNG image that the icon file contains there must be a
* corresponding icon dir entry. (This corresponds to the Windows
* ICONDIRENTRY structure.) These entries are encoded directly after the
* IconFileHeader.
*/
struct IconDirEntry
{
uint8_t mWidth;
uint8_t mHeight;
/**
* The number of colors in the color palette of the BMP/PNG that this dir
* entry corresponds to, or 0 if the image does not use a color palette.
*/
uint8_t mColorCount;
/**
* Should be set to 0.
*/
uint8_t mReserved;
union {
uint16_t mPlanes; // ICO
uint16_t mXHotspot; // CUR
};
union {
uint16_t mBitCount; // ICO (bits per pixel)
uint16_t mYHotspot; // CUR
};
/**
* "bytes in resource" is the length of the encoded BMP/PNG that this dir
* entry corresponds to.
*/
uint32_t mBytesInRes;
/**
* The offset of the start of the encoded BMP/PNG that this dir entry
* corresponds to (from the start of the icon file).
*/
uint32_t mImageOffset;
};
} // namespace image
} // namespace mozilla
#endif // mozilla_image_ICOFileHeaders_h

172
image/IDecodingTask.cpp Normal file
View file

@ -0,0 +1,172 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* 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/. */
#include "IDecodingTask.h"
#include "gfxPrefs.h"
#include "nsThreadUtils.h"
#include "Decoder.h"
#include "DecodePool.h"
#include "RasterImage.h"
#include "SurfaceCache.h"
namespace mozilla {
using gfx::IntRect;
namespace image {
///////////////////////////////////////////////////////////////////////////////
// Helpers for sending notifications to the image associated with a decoder.
///////////////////////////////////////////////////////////////////////////////
/* static */ void
IDecodingTask::NotifyProgress(NotNull<RasterImage*> aImage,
NotNull<Decoder*> aDecoder)
{
MOZ_ASSERT(aDecoder->HasProgress() && !aDecoder->IsMetadataDecode());
// Capture the decoder's state. If we need to notify asynchronously, it's
// important that we don't wait until the lambda actually runs to capture the
// state that we're going to notify. That would both introduce data races on
// the decoder's state and cause inconsistencies between the NotifyProgress()
// calls we make off-main-thread and the notifications that RasterImage
// actually receives, which would cause bugs.
Progress progress = aDecoder->TakeProgress();
IntRect invalidRect = aDecoder->TakeInvalidRect();
Maybe<uint32_t> frameCount = aDecoder->TakeCompleteFrameCount();
DecoderFlags decoderFlags = aDecoder->GetDecoderFlags();
SurfaceFlags surfaceFlags = aDecoder->GetSurfaceFlags();
// Synchronously notify if we can.
if (NS_IsMainThread() && !(decoderFlags & DecoderFlags::ASYNC_NOTIFY)) {
aImage->NotifyProgress(progress, invalidRect, frameCount,
decoderFlags, surfaceFlags);
return;
}
// We're forced to notify asynchronously.
NotNull<RefPtr<RasterImage>> image = aImage;
NS_DispatchToMainThread(NS_NewRunnableFunction([=]() -> void {
image->NotifyProgress(progress, invalidRect, frameCount,
decoderFlags, surfaceFlags);
}));
}
/* static */ void
IDecodingTask::NotifyDecodeComplete(NotNull<RasterImage*> aImage,
NotNull<Decoder*> aDecoder)
{
MOZ_ASSERT(aDecoder->HasError() || !aDecoder->InFrame(),
"Decode complete in the middle of a frame?");
// Capture the decoder's state.
DecoderFinalStatus finalStatus = aDecoder->FinalStatus();
ImageMetadata metadata = aDecoder->GetImageMetadata();
DecoderTelemetry telemetry = aDecoder->Telemetry();
Progress progress = aDecoder->TakeProgress();
IntRect invalidRect = aDecoder->TakeInvalidRect();
Maybe<uint32_t> frameCount = aDecoder->TakeCompleteFrameCount();
DecoderFlags decoderFlags = aDecoder->GetDecoderFlags();
SurfaceFlags surfaceFlags = aDecoder->GetSurfaceFlags();
// Synchronously notify if we can.
if (NS_IsMainThread() && !(decoderFlags & DecoderFlags::ASYNC_NOTIFY)) {
aImage->NotifyDecodeComplete(finalStatus, metadata, telemetry, progress,
invalidRect, frameCount, decoderFlags,
surfaceFlags);
return;
}
// We're forced to notify asynchronously.
NotNull<RefPtr<RasterImage>> image = aImage;
NS_DispatchToMainThread(NS_NewRunnableFunction([=]() -> void {
image->NotifyDecodeComplete(finalStatus, metadata, telemetry, progress,
invalidRect, frameCount, decoderFlags,
surfaceFlags);
}));
}
///////////////////////////////////////////////////////////////////////////////
// IDecodingTask implementation.
///////////////////////////////////////////////////////////////////////////////
void
IDecodingTask::Resume()
{
DecodePool::Singleton()->AsyncRun(this);
}
///////////////////////////////////////////////////////////////////////////////
// MetadataDecodingTask implementation.
///////////////////////////////////////////////////////////////////////////////
MetadataDecodingTask::MetadataDecodingTask(NotNull<Decoder*> aDecoder)
: mMutex("mozilla::image::MetadataDecodingTask")
, mDecoder(aDecoder)
{
MOZ_ASSERT(mDecoder->IsMetadataDecode(),
"Use DecodingTask for non-metadata decodes");
}
void
MetadataDecodingTask::Run()
{
MutexAutoLock lock(mMutex);
LexerResult result = mDecoder->Decode(WrapNotNull(this));
if (result.is<TerminalState>()) {
NotifyDecodeComplete(mDecoder->GetImage(), mDecoder);
return; // We're done.
}
if (result == LexerResult(Yield::NEED_MORE_DATA)) {
// We can't make any more progress right now. We also don't want to report
// any progress, because it's important that metadata decode results are
// delivered atomically. The decoder itself will ensure that we get
// reenqueued when more data is available; just return for now.
return;
}
MOZ_ASSERT_UNREACHABLE("Metadata decode yielded for an unexpected reason");
}
///////////////////////////////////////////////////////////////////////////////
// AnonymousDecodingTask implementation.
///////////////////////////////////////////////////////////////////////////////
AnonymousDecodingTask::AnonymousDecodingTask(NotNull<Decoder*> aDecoder)
: mDecoder(aDecoder)
{ }
void
AnonymousDecodingTask::Run()
{
while (true) {
LexerResult result = mDecoder->Decode(WrapNotNull(this));
if (result.is<TerminalState>()) {
return; // We're done.
}
if (result == LexerResult(Yield::NEED_MORE_DATA)) {
// We can't make any more progress right now. Let the caller decide how to
// handle it.
return;
}
// Right now we don't do anything special for other kinds of yields, so just
// keep working.
MOZ_ASSERT(result.is<Yield>());
}
}
} // namespace image
} // namespace mozilla

126
image/IDecodingTask.h Normal file
View file

@ -0,0 +1,126 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* 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/. */
/**
* An interface for tasks which can execute on the ImageLib DecodePool, and
* various implementations.
*/
#ifndef mozilla_image_IDecodingTask_h
#define mozilla_image_IDecodingTask_h
#include "mozilla/NotNull.h"
#include "mozilla/RefPtr.h"
#include "imgFrame.h"
#include "SourceBuffer.h"
namespace mozilla {
namespace image {
class Decoder;
class RasterImage;
/// A priority hint that DecodePool can use when scheduling an IDecodingTask.
enum class TaskPriority : uint8_t
{
eLow,
eHigh
};
/**
* An interface for tasks which can execute on the ImageLib DecodePool.
*/
class IDecodingTask : public IResumable
{
public:
/// Run the task.
virtual void Run() = 0;
/// @return true if, given the option, this task prefers to run synchronously.
virtual bool ShouldPreferSyncRun() const = 0;
/// @return a priority hint that DecodePool can use when scheduling this task.
virtual TaskPriority Priority() const = 0;
/// A default implementation of IResumable which resubmits the task to the
/// DecodePool. Subclasses can override this if they need different behavior.
void Resume() override;
protected:
/// Notify @aImage of @aDecoder's progress.
static void NotifyProgress(NotNull<RasterImage*> aImage,
NotNull<Decoder*> aDecoder);
/// Notify @aImage that @aDecoder has finished.
static void NotifyDecodeComplete(NotNull<RasterImage*> aImage,
NotNull<Decoder*> aDecoder);
virtual ~IDecodingTask() { }
};
/**
* An IDecodingTask implementation for metadata decodes of images.
*/
class MetadataDecodingTask final : public IDecodingTask
{
public:
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(MetadataDecodingTask, override)
explicit MetadataDecodingTask(NotNull<Decoder*> aDecoder);
void Run() override;
// Metadata decodes are very fast (since they only need to examine an image's
// header) so there's no reason to refuse to run them synchronously if the
// caller will allow us to.
bool ShouldPreferSyncRun() const override { return true; }
// Metadata decodes run at the highest priority because they block layout and
// page load.
TaskPriority Priority() const override { return TaskPriority::eHigh; }
private:
virtual ~MetadataDecodingTask() { }
/// Mutex protecting access to mDecoder.
Mutex mMutex;
NotNull<RefPtr<Decoder>> mDecoder;
};
/**
* An IDecodingTask implementation for anonymous decoders - that is, decoders
* with no associated Image object.
*/
class AnonymousDecodingTask final : public IDecodingTask
{
public:
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(AnonymousDecodingTask, override)
explicit AnonymousDecodingTask(NotNull<Decoder*> aDecoder);
void Run() override;
bool ShouldPreferSyncRun() const override { return true; }
TaskPriority Priority() const override { return TaskPriority::eLow; }
// Anonymous decoders normally get all their data at once. We have tests where
// they don't; in these situations, the test re-runs them manually. So no
// matter what, we don't want to resume by posting a task to the DecodePool.
void Resume() override { }
private:
virtual ~AnonymousDecodingTask() { }
NotNull<RefPtr<Decoder>> mDecoder;
};
} // namespace image
} // namespace mozilla
#endif // mozilla_image_IDecodingTask_h

58
image/IProgressObserver.h Normal file
View file

@ -0,0 +1,58 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* 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/. */
#ifndef mozilla_image_IProgressObserver_h
#define mozilla_image_IProgressObserver_h
#include "mozilla/WeakPtr.h"
#include "nsISupports.h"
#include "nsRect.h"
namespace mozilla {
namespace image {
/**
* An interface for observing changes to image state, as reported by
* ProgressTracker.
*
* This is the ImageLib-internal version of imgINotificationObserver,
* essentially, with implementation details that code outside of ImageLib
* shouldn't see.
*
* XXX(seth): It's preferable to avoid adding anything to this interface if
* possible. In the long term, it would be ideal to get to a place where we can
* just use the imgINotificationObserver interface internally as well.
*/
class IProgressObserver : public SupportsWeakPtr<IProgressObserver>
{
public:
MOZ_DECLARE_WEAKREFERENCE_TYPENAME(IProgressObserver)
// Subclasses may or may not be XPCOM classes, so we just require that they
// implement AddRef and Release.
NS_IMETHOD_(MozExternalRefCountType) AddRef(void) = 0;
NS_IMETHOD_(MozExternalRefCountType) Release(void) = 0;
// imgINotificationObserver methods:
virtual void Notify(int32_t aType, const nsIntRect* aRect = nullptr) = 0;
virtual void OnLoadComplete(bool aLastPart) = 0;
// imgIOnloadBlocker methods:
virtual void BlockOnload() = 0;
virtual void UnblockOnload() = 0;
// Other, internal-only methods:
virtual void SetHasImage() = 0;
virtual bool NotificationsDeferred() const = 0;
virtual void SetNotificationsDeferred(bool aDeferNotifications) = 0;
protected:
virtual ~IProgressObserver() { }
};
} // namespace image
} // namespace mozilla
#endif // mozilla_image_IProgressObserver_h

290
image/ISurfaceProvider.h Normal file
View file

@ -0,0 +1,290 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* 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/. */
/**
* An interface for objects which can either store a surface or dynamically
* generate one, and various implementations.
*/
#ifndef mozilla_image_ISurfaceProvider_h
#define mozilla_image_ISurfaceProvider_h
#include "mozilla/Attributes.h"
#include "mozilla/Maybe.h"
#include "mozilla/MemoryReporting.h"
#include "mozilla/NotNull.h"
#include "mozilla/TimeStamp.h"
#include "mozilla/Variant.h"
#include "mozilla/gfx/2D.h"
#include "imgFrame.h"
#include "SurfaceCache.h"
namespace mozilla {
namespace image {
class CachedSurface;
class DrawableSurface;
/**
* An interface for objects which can either store a surface or dynamically
* generate one.
*/
class ISurfaceProvider
{
public:
// Subclasses may or may not be XPCOM classes, so we just require that they
// implement AddRef and Release.
NS_IMETHOD_(MozExternalRefCountType) AddRef() = 0;
NS_IMETHOD_(MozExternalRefCountType) Release() = 0;
/// @return key data used for identifying which image this ISurfaceProvider is
/// associated with in the surface cache.
ImageKey GetImageKey() const { return mImageKey; }
/// @return key data used to uniquely identify this ISurfaceProvider's cache
/// entry in the surface cache.
const SurfaceKey& GetSurfaceKey() const { return mSurfaceKey; }
/// @return a (potentially lazily computed) drawable reference to a surface.
virtual DrawableSurface Surface();
/// @return true if DrawableRef() will return a completely decoded surface.
virtual bool IsFinished() const = 0;
/// @return the number of bytes of memory this ISurfaceProvider is expected to
/// require. Optimizations may result in lower real memory usage. Trivial
/// overhead is ignored. Because this value is used in bookkeeping, it's
/// important that it be constant over the lifetime of this object.
virtual size_t LogicalSizeInBytes() const = 0;
/// @return the actual number of bytes of memory this ISurfaceProvider is
/// using. May vary over the lifetime of the ISurfaceProvider. The default
/// implementation is appropriate for static ISurfaceProviders.
virtual void AddSizeOfExcludingThis(MallocSizeOf aMallocSizeOf,
size_t& aHeapSizeOut,
size_t& aNonHeapSizeOut)
{
DrawableFrameRef ref = DrawableRef(/* aFrame = */ 0);
if (!ref) {
return;
}
ref->AddSizeOfExcludingThis(aMallocSizeOf, aHeapSizeOut, aNonHeapSizeOut);
}
/// @return the availability state of this ISurfaceProvider, which indicates
/// whether DrawableRef() could successfully return a surface. Should only be
/// called from SurfaceCache code as it relies on SurfaceCache for
/// synchronization.
AvailabilityState& Availability() { return mAvailability; }
const AvailabilityState& Availability() const { return mAvailability; }
protected:
ISurfaceProvider(const ImageKey aImageKey,
const SurfaceKey& aSurfaceKey,
AvailabilityState aAvailability)
: mImageKey(aImageKey)
, mSurfaceKey(aSurfaceKey)
, mAvailability(aAvailability)
{
MOZ_ASSERT(aImageKey, "Must have a valid image key");
}
virtual ~ISurfaceProvider() { }
/// @return an eagerly computed drawable reference to a surface. For
/// dynamically generated animation surfaces, @aFrame specifies the 0-based
/// index of the desired frame.
virtual DrawableFrameRef DrawableRef(size_t aFrame) = 0;
/// @return true if this ISurfaceProvider is locked. (@see SetLocked())
/// Should only be called from SurfaceCache code as it relies on SurfaceCache
/// for synchronization.
virtual bool IsLocked() const = 0;
/// If @aLocked is true, hint that this ISurfaceProvider is in use and it
/// should avoid releasing its resources. Should only be called from
/// SurfaceCache code as it relies on SurfaceCache for synchronization.
virtual void SetLocked(bool aLocked) = 0;
private:
friend class CachedSurface;
friend class DrawableSurface;
const ImageKey mImageKey;
const SurfaceKey mSurfaceKey;
AvailabilityState mAvailability;
};
/**
* A reference to a surface (stored in an imgFrame) that holds the surface in
* memory, guaranteeing that it can be drawn. If you have a DrawableSurface
* |surf| and |if (surf)| returns true, then calls to |surf->Draw()| and
* |surf->GetSourceSurface()| are guaranteed to succeed.
*
* Note that the surface may be computed lazily, so a DrawableSurface should not
* be dereferenced (i.e., operator->() should not be called) until you're
* sure that you want to draw it.
*/
class MOZ_STACK_CLASS DrawableSurface final
{
public:
DrawableSurface() : mHaveSurface(false) { }
explicit DrawableSurface(DrawableFrameRef&& aDrawableRef)
: mDrawableRef(Move(aDrawableRef))
, mHaveSurface(bool(mDrawableRef))
{ }
explicit DrawableSurface(NotNull<ISurfaceProvider*> aProvider)
: mProvider(aProvider)
, mHaveSurface(true)
{ }
DrawableSurface(DrawableSurface&& aOther)
: mDrawableRef(Move(aOther.mDrawableRef))
, mProvider(Move(aOther.mProvider))
, mHaveSurface(aOther.mHaveSurface)
{
aOther.mHaveSurface = false;
}
DrawableSurface& operator=(DrawableSurface&& aOther)
{
MOZ_ASSERT(this != &aOther, "Self-moves are prohibited");
mDrawableRef = Move(aOther.mDrawableRef);
mProvider = Move(aOther.mProvider);
mHaveSurface = aOther.mHaveSurface;
aOther.mHaveSurface = false;
return *this;
}
/**
* If this DrawableSurface is dynamically generated from an animation, attempt
* to seek to frame @aFrame, where @aFrame is a 0-based index into the frames
* of the animation. Otherwise, nothing will blow up at runtime, but we assert
* in debug builds, since calling this in an unexpected situation probably
* indicates a bug.
*
* @return a successful result if we could obtain frame @aFrame. Note that
* |mHaveSurface| being true means that we're guaranteed to have *some* frame,
* so the caller can dereference this DrawableSurface even if Seek() fails,
* but while nothing will blow up, the frame won't be the one they expect.
*/
nsresult Seek(size_t aFrame)
{
MOZ_ASSERT(mHaveSurface, "Trying to seek an empty DrawableSurface?");
if (!mProvider) {
MOZ_ASSERT_UNREACHABLE("Trying to seek a static DrawableSurface?");
return NS_ERROR_FAILURE;
}
mDrawableRef = mProvider->DrawableRef(aFrame);
return mDrawableRef ? NS_OK : NS_ERROR_FAILURE;
}
explicit operator bool() const { return mHaveSurface; }
imgFrame* operator->() { return DrawableRef().get(); }
private:
DrawableSurface(const DrawableSurface& aOther) = delete;
DrawableSurface& operator=(const DrawableSurface& aOther) = delete;
DrawableFrameRef& DrawableRef()
{
MOZ_ASSERT(mHaveSurface);
// If we weren't created with a DrawableFrameRef directly, we should've been
// created with an ISurfaceProvider which can give us one. Note that if
// Seek() has been called, we'll already have a DrawableFrameRef, so we
// won't need to get one here.
if (!mDrawableRef) {
MOZ_ASSERT(mProvider);
mDrawableRef = mProvider->DrawableRef(/* aFrame = */ 0);
}
MOZ_ASSERT(mDrawableRef);
return mDrawableRef;
}
DrawableFrameRef mDrawableRef;
RefPtr<ISurfaceProvider> mProvider;
bool mHaveSurface;
};
// Surface() is implemented here so that DrawableSurface's definition is
// visible. This default implementation eagerly obtains a DrawableFrameRef for
// the first frame and is intended for static ISurfaceProviders.
inline DrawableSurface
ISurfaceProvider::Surface()
{
return DrawableSurface(DrawableRef(/* aFrame = */ 0));
}
/**
* An ISurfaceProvider that stores a single surface.
*/
class SimpleSurfaceProvider final : public ISurfaceProvider
{
public:
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(SimpleSurfaceProvider, override)
SimpleSurfaceProvider(const ImageKey aImageKey,
const SurfaceKey& aSurfaceKey,
NotNull<imgFrame*> aSurface)
: ISurfaceProvider(aImageKey, aSurfaceKey,
AvailabilityState::StartAvailable())
, mSurface(aSurface)
{
MOZ_ASSERT(aSurfaceKey.Size() == mSurface->GetSize());
}
bool IsFinished() const override { return mSurface->IsFinished(); }
size_t LogicalSizeInBytes() const override
{
gfx::IntSize size = mSurface->GetSize();
return size.width * size.height * mSurface->GetBytesPerPixel();
}
protected:
DrawableFrameRef DrawableRef(size_t aFrame) override
{
MOZ_ASSERT(aFrame == 0,
"Requesting an animation frame from a SimpleSurfaceProvider?");
return mSurface->DrawableRef();
}
bool IsLocked() const override { return bool(mLockRef); }
void SetLocked(bool aLocked) override
{
if (aLocked == IsLocked()) {
return; // Nothing changed.
}
// If we're locked, hold a DrawableFrameRef to |mSurface|, which will keep
// any volatile buffer it owns in memory.
mLockRef = aLocked ? mSurface->DrawableRef()
: DrawableFrameRef();
}
private:
virtual ~SimpleSurfaceProvider() { }
NotNull<RefPtr<imgFrame>> mSurface;
DrawableFrameRef mLockRef;
};
} // namespace image
} // namespace mozilla
#endif // mozilla_image_ISurfaceProvider_h

150
image/Image.cpp Normal file
View file

@ -0,0 +1,150 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* 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/. */
#include "Image.h"
#include "nsRefreshDriver.h"
#include "mozilla/TimeStamp.h"
namespace mozilla {
namespace image {
///////////////////////////////////////////////////////////////////////////////
// Memory Reporting
///////////////////////////////////////////////////////////////////////////////
ImageMemoryCounter::ImageMemoryCounter(Image* aImage,
MallocSizeOf aMallocSizeOf,
bool aIsUsed)
: mIsUsed(aIsUsed)
{
MOZ_ASSERT(aImage);
// Extract metadata about the image.
RefPtr<ImageURL> imageURL(aImage->GetURI());
if (imageURL) {
imageURL->GetSpec(mURI);
}
int32_t width = 0;
int32_t height = 0;
aImage->GetWidth(&width);
aImage->GetHeight(&height);
mIntrinsicSize.SizeTo(width, height);
mType = aImage->GetType();
// Populate memory counters for source and decoded data.
mValues.SetSource(aImage->SizeOfSourceWithComputedFallback(aMallocSizeOf));
aImage->CollectSizeOfSurfaces(mSurfaces, aMallocSizeOf);
// Compute totals.
for (const SurfaceMemoryCounter& surfaceCounter : mSurfaces) {
mValues += surfaceCounter.Values();
}
}
///////////////////////////////////////////////////////////////////////////////
// Image Base Types
///////////////////////////////////////////////////////////////////////////////
// Constructor
ImageResource::ImageResource(ImageURL* aURI) :
mURI(aURI),
mInnerWindowId(0),
mAnimationConsumers(0),
mAnimationMode(kNormalAnimMode),
mInitialized(false),
mAnimating(false),
mError(false)
{ }
ImageResource::~ImageResource()
{
// Ask our ProgressTracker to drop its weak reference to us.
mProgressTracker->ResetImage();
}
void
ImageResource::IncrementAnimationConsumers()
{
MOZ_ASSERT(NS_IsMainThread(), "Main thread only to encourage serialization "
"with DecrementAnimationConsumers");
mAnimationConsumers++;
}
void
ImageResource::DecrementAnimationConsumers()
{
MOZ_ASSERT(NS_IsMainThread(), "Main thread only to encourage serialization "
"with IncrementAnimationConsumers");
MOZ_ASSERT(mAnimationConsumers >= 1,
"Invalid no. of animation consumers!");
mAnimationConsumers--;
}
nsresult
ImageResource::GetAnimationModeInternal(uint16_t* aAnimationMode)
{
if (mError) {
return NS_ERROR_FAILURE;
}
NS_ENSURE_ARG_POINTER(aAnimationMode);
*aAnimationMode = mAnimationMode;
return NS_OK;
}
nsresult
ImageResource::SetAnimationModeInternal(uint16_t aAnimationMode)
{
if (mError) {
return NS_ERROR_FAILURE;
}
NS_ASSERTION(aAnimationMode == kNormalAnimMode ||
aAnimationMode == kDontAnimMode ||
aAnimationMode == kLoopOnceAnimMode,
"Wrong Animation Mode is being set!");
mAnimationMode = aAnimationMode;
return NS_OK;
}
bool
ImageResource::HadRecentRefresh(const TimeStamp& aTime)
{
// Our threshold for "recent" is 1/2 of the default refresh-driver interval.
// This ensures that we allow for frame rates at least as fast as the
// refresh driver's default rate.
static TimeDuration recentThreshold =
TimeDuration::FromMilliseconds(nsRefreshDriver::DefaultInterval() / 2.0);
if (!mLastRefreshTime.IsNull() &&
aTime - mLastRefreshTime < recentThreshold) {
return true;
}
// else, we can proceed with a refresh.
// But first, update our last refresh time:
mLastRefreshTime = aTime;
return false;
}
void
ImageResource::EvaluateAnimation()
{
if (!mAnimating && ShouldAnimate()) {
nsresult rv = StartAnimation();
mAnimating = NS_SUCCEEDED(rv);
} else if (mAnimating && !ShouldAnimate()) {
StopAnimation();
}
}
} // namespace image
} // namespace mozilla

323
image/Image.h Normal file
View file

@ -0,0 +1,323 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* 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/. */
#ifndef mozilla_image_Image_h
#define mozilla_image_Image_h
#include "mozilla/MemoryReporting.h"
#include "mozilla/TimeStamp.h"
#include "gfx2DGlue.h"
#include "imgIContainer.h"
#include "ImageURL.h"
#include "nsStringFwd.h"
#include "ProgressTracker.h"
#include "SurfaceCache.h"
class nsIRequest;
class nsIInputStream;
namespace mozilla {
namespace image {
class Image;
///////////////////////////////////////////////////////////////////////////////
// Memory Reporting
///////////////////////////////////////////////////////////////////////////////
struct MemoryCounter
{
MemoryCounter()
: mSource(0)
, mDecodedHeap(0)
, mDecodedNonHeap(0)
{ }
void SetSource(size_t aCount) { mSource = aCount; }
size_t Source() const { return mSource; }
void SetDecodedHeap(size_t aCount) { mDecodedHeap = aCount; }
size_t DecodedHeap() const { return mDecodedHeap; }
void SetDecodedNonHeap(size_t aCount) { mDecodedNonHeap = aCount; }
size_t DecodedNonHeap() const { return mDecodedNonHeap; }
MemoryCounter& operator+=(const MemoryCounter& aOther)
{
mSource += aOther.mSource;
mDecodedHeap += aOther.mDecodedHeap;
mDecodedNonHeap += aOther.mDecodedNonHeap;
return *this;
}
private:
size_t mSource;
size_t mDecodedHeap;
size_t mDecodedNonHeap;
};
enum class SurfaceMemoryCounterType
{
NORMAL,
COMPOSITING,
COMPOSITING_PREV
};
struct SurfaceMemoryCounter
{
SurfaceMemoryCounter(const SurfaceKey& aKey,
bool aIsLocked,
SurfaceMemoryCounterType aType =
SurfaceMemoryCounterType::NORMAL)
: mKey(aKey)
, mType(aType)
, mIsLocked(aIsLocked)
{ }
const SurfaceKey& Key() const { return mKey; }
MemoryCounter& Values() { return mValues; }
const MemoryCounter& Values() const { return mValues; }
SurfaceMemoryCounterType Type() const { return mType; }
bool IsLocked() const { return mIsLocked; }
private:
const SurfaceKey mKey;
MemoryCounter mValues;
const SurfaceMemoryCounterType mType;
const bool mIsLocked;
};
struct ImageMemoryCounter
{
ImageMemoryCounter(Image* aImage,
MallocSizeOf aMallocSizeOf,
bool aIsUsed);
nsCString& URI() { return mURI; }
const nsCString& URI() const { return mURI; }
const nsTArray<SurfaceMemoryCounter>& Surfaces() const { return mSurfaces; }
const gfx::IntSize IntrinsicSize() const { return mIntrinsicSize; }
const MemoryCounter& Values() const { return mValues; }
uint16_t Type() const { return mType; }
bool IsUsed() const { return mIsUsed; }
bool IsNotable() const
{
const size_t NotableThreshold = 16 * 1024;
size_t total = mValues.Source() + mValues.DecodedHeap()
+ mValues.DecodedNonHeap();
return total >= NotableThreshold;
}
private:
nsCString mURI;
nsTArray<SurfaceMemoryCounter> mSurfaces;
gfx::IntSize mIntrinsicSize;
MemoryCounter mValues;
uint16_t mType;
const bool mIsUsed;
};
///////////////////////////////////////////////////////////////////////////////
// Image Base Types
///////////////////////////////////////////////////////////////////////////////
class Image : public imgIContainer
{
public:
/**
* Flags for Image initialization.
*
* Meanings:
*
* INIT_FLAG_NONE: Lack of flags
*
* INIT_FLAG_DISCARDABLE: The container should be discardable
*
* INIT_FLAG_DECODE_IMMEDIATELY: The container should decode as soon as
* possible, regardless of what our heuristics say.
*
* INIT_FLAG_TRANSIENT: The container is likely to exist for only a short time
* before being destroyed. (For example, containers for
* multipart/x-mixed-replace image parts fall into this category.) If this
* flag is set, INIT_FLAG_DISCARDABLE and INIT_FLAG_DECODE_ONLY_ON_DRAW must
* not be set.
*
* INIT_FLAG_SYNC_LOAD: The container is being loaded synchronously, so
* it should avoid relying on async workers to get the container ready.
*/
static const uint32_t INIT_FLAG_NONE = 0x0;
static const uint32_t INIT_FLAG_DISCARDABLE = 0x1;
static const uint32_t INIT_FLAG_DECODE_IMMEDIATELY = 0x2;
static const uint32_t INIT_FLAG_TRANSIENT = 0x4;
static const uint32_t INIT_FLAG_SYNC_LOAD = 0x8;
virtual already_AddRefed<ProgressTracker> GetProgressTracker() = 0;
virtual void SetProgressTracker(ProgressTracker* aProgressTracker) {}
/**
* The size, in bytes, occupied by the compressed source data of the image.
* If MallocSizeOf does not work on this platform, uses a fallback approach to
* ensure that something reasonable is always returned.
*/
virtual size_t
SizeOfSourceWithComputedFallback(MallocSizeOf aMallocSizeOf) const = 0;
/**
* Collect an accounting of the memory occupied by the image's surfaces (which
* together make up its decoded data). Each surface is recorded as a separate
* SurfaceMemoryCounter, stored in @aCounters.
*/
virtual void CollectSizeOfSurfaces(nsTArray<SurfaceMemoryCounter>& aCounters,
MallocSizeOf aMallocSizeOf) const = 0;
virtual void IncrementAnimationConsumers() = 0;
virtual void DecrementAnimationConsumers() = 0;
#ifdef DEBUG
virtual uint32_t GetAnimationConsumers() = 0;
#endif
/**
* Called from OnDataAvailable when the stream associated with the image has
* received new image data. The arguments are the same as OnDataAvailable's,
* but by separating this functionality into a different method we don't
* interfere with subclasses which wish to implement nsIStreamListener.
*
* Images should not do anything that could send out notifications until they
* have received their first OnImageDataAvailable notification; in
* particular, this means that instantiating decoders should be deferred
* until OnImageDataAvailable is called.
*/
virtual nsresult OnImageDataAvailable(nsIRequest* aRequest,
nsISupports* aContext,
nsIInputStream* aInStr,
uint64_t aSourceOffset,
uint32_t aCount) = 0;
/**
* Called from OnStopRequest when the image's underlying request completes.
*
* @param aRequest The completed request.
* @param aContext Context from Necko's OnStopRequest.
* @param aStatus A success or failure code.
* @param aLastPart Whether this is the final part of the underlying request.
*/
virtual nsresult OnImageDataComplete(nsIRequest* aRequest,
nsISupports* aContext,
nsresult aStatus,
bool aLastPart) = 0;
/**
* Called when the SurfaceCache discards a surface belonging to this image.
*/
virtual void OnSurfaceDiscarded() = 0;
virtual void SetInnerWindowID(uint64_t aInnerWindowId) = 0;
virtual uint64_t InnerWindowID() const = 0;
virtual bool HasError() = 0;
virtual void SetHasError() = 0;
virtual ImageURL* GetURI() = 0;
virtual void ReportUseCounters() { }
};
class ImageResource : public Image
{
public:
already_AddRefed<ProgressTracker> GetProgressTracker() override
{
RefPtr<ProgressTracker> progressTracker = mProgressTracker;
MOZ_ASSERT(progressTracker);
return progressTracker.forget();
}
void SetProgressTracker(
ProgressTracker* aProgressTracker) override final
{
MOZ_ASSERT(aProgressTracker);
MOZ_ASSERT(!mProgressTracker);
mProgressTracker = aProgressTracker;
}
virtual void IncrementAnimationConsumers() override;
virtual void DecrementAnimationConsumers() override;
#ifdef DEBUG
virtual uint32_t GetAnimationConsumers() override
{
return mAnimationConsumers;
}
#endif
virtual void OnSurfaceDiscarded() override { }
virtual void SetInnerWindowID(uint64_t aInnerWindowId) override
{
mInnerWindowId = aInnerWindowId;
}
virtual uint64_t InnerWindowID() const override { return mInnerWindowId; }
virtual bool HasError() override { return mError; }
virtual void SetHasError() override { mError = true; }
/*
* Returns a non-AddRefed pointer to the URI associated with this image.
* Illegal to use off-main-thread.
*/
virtual ImageURL* GetURI() override { return mURI.get(); }
protected:
explicit ImageResource(ImageURL* aURI);
~ImageResource();
// Shared functionality for implementors of imgIContainer. Every
// implementation of attribute animationMode should forward here.
nsresult GetAnimationModeInternal(uint16_t* aAnimationMode);
nsresult SetAnimationModeInternal(uint16_t aAnimationMode);
/**
* Helper for RequestRefresh.
*
* If we've had a "recent" refresh (i.e. if this image is being used in
* multiple documents & some other document *just* called RequestRefresh() on
* this image with a timestamp close to aTime), this method returns true.
*
* Otherwise, this method updates mLastRefreshTime to aTime & returns false.
*/
bool HadRecentRefresh(const TimeStamp& aTime);
/**
* Decides whether animation should or should not be happening,
* and makes sure the right thing is being done.
*/
virtual void EvaluateAnimation();
/**
* Extended by child classes, if they have additional
* conditions for being able to animate.
*/
virtual bool ShouldAnimate() {
return mAnimationConsumers > 0 && mAnimationMode != kDontAnimMode;
}
virtual nsresult StartAnimation() = 0;
virtual nsresult StopAnimation() = 0;
// Member data shared by all implementations of this abstract class
RefPtr<ProgressTracker> mProgressTracker;
RefPtr<ImageURL> mURI;
TimeStamp mLastRefreshTime;
uint64_t mInnerWindowId;
uint32_t mAnimationConsumers;
uint16_t mAnimationMode; // Enum values in imgIContainer
bool mInitialized:1; // Have we been initalized?
bool mAnimating:1; // Are we currently animating?
bool mError:1; // Error handling
};
} // namespace image
} // namespace mozilla
#endif // mozilla_image_Image_h

167
image/ImageCacheKey.cpp Normal file
View file

@ -0,0 +1,167 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* 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/. */
#include "ImageCacheKey.h"
#include "mozilla/Move.h"
#include "File.h"
#include "ImageURL.h"
#include "nsHostObjectProtocolHandler.h"
#include "nsString.h"
#include "mozilla/dom/workers/ServiceWorkerManager.h"
#include "nsIDocument.h"
#include "nsPrintfCString.h"
namespace mozilla {
using namespace dom;
namespace image {
bool
URISchemeIs(ImageURL* aURI, const char* aScheme)
{
bool schemeMatches = false;
if (NS_WARN_IF(NS_FAILED(aURI->SchemeIs(aScheme, &schemeMatches)))) {
return false;
}
return schemeMatches;
}
static Maybe<uint64_t>
BlobSerial(ImageURL* aURI)
{
nsAutoCString spec;
aURI->GetSpec(spec);
RefPtr<BlobImpl> blob;
if (NS_SUCCEEDED(NS_GetBlobForBlobURISpec(spec, getter_AddRefs(blob))) &&
blob) {
return Some(blob->GetSerialNumber());
}
return Nothing();
}
ImageCacheKey::ImageCacheKey(nsIURI* aURI,
const PrincipalOriginAttributes& aAttrs,
nsIDocument* aDocument,
nsresult& aRv)
: mURI(new ImageURL(aURI, aRv))
, mOriginAttributes(aAttrs)
, mControlledDocument(GetControlledDocumentToken(aDocument))
, mIsChrome(URISchemeIs(mURI, "chrome"))
{
NS_ENSURE_SUCCESS_VOID(aRv);
MOZ_ASSERT(NS_IsMainThread());
if (URISchemeIs(mURI, "blob")) {
mBlobSerial = BlobSerial(mURI);
}
mHash = ComputeHash(mURI, mBlobSerial, mOriginAttributes, mControlledDocument);
}
ImageCacheKey::ImageCacheKey(ImageURL* aURI,
const PrincipalOriginAttributes& aAttrs,
nsIDocument* aDocument)
: mURI(aURI)
, mOriginAttributes(aAttrs)
, mControlledDocument(GetControlledDocumentToken(aDocument))
, mIsChrome(URISchemeIs(mURI, "chrome"))
{
MOZ_ASSERT(aURI);
if (URISchemeIs(mURI, "blob")) {
mBlobSerial = BlobSerial(mURI);
}
mHash = ComputeHash(mURI, mBlobSerial, mOriginAttributes, mControlledDocument);
}
ImageCacheKey::ImageCacheKey(const ImageCacheKey& aOther)
: mURI(aOther.mURI)
, mBlobSerial(aOther.mBlobSerial)
, mOriginAttributes(aOther.mOriginAttributes)
, mControlledDocument(aOther.mControlledDocument)
, mHash(aOther.mHash)
, mIsChrome(aOther.mIsChrome)
{ }
ImageCacheKey::ImageCacheKey(ImageCacheKey&& aOther)
: mURI(Move(aOther.mURI))
, mBlobSerial(Move(aOther.mBlobSerial))
, mOriginAttributes(aOther.mOriginAttributes)
, mControlledDocument(aOther.mControlledDocument)
, mHash(aOther.mHash)
, mIsChrome(aOther.mIsChrome)
{ }
bool
ImageCacheKey::operator==(const ImageCacheKey& aOther) const
{
// Don't share the image cache between a controlled document and anything else.
if (mControlledDocument != aOther.mControlledDocument) {
return false;
}
// The origin attributes always have to match.
if (mOriginAttributes != aOther.mOriginAttributes) {
return false;
}
if (mBlobSerial || aOther.mBlobSerial) {
// If at least one of us has a blob serial, just compare the blob serial and
// the ref portion of the URIs.
return mBlobSerial == aOther.mBlobSerial &&
mURI->HasSameRef(*aOther.mURI);
}
// For non-blob URIs, compare the URIs.
return *mURI == *aOther.mURI;
}
const char*
ImageCacheKey::Spec() const
{
return mURI->Spec();
}
/* static */ uint32_t
ImageCacheKey::ComputeHash(ImageURL* aURI,
const Maybe<uint64_t>& aBlobSerial,
const PrincipalOriginAttributes& aAttrs,
void* aControlledDocument)
{
// Since we frequently call Hash() several times in a row on the same
// ImageCacheKey, as an optimization we compute our hash once and store it.
nsPrintfCString ptr("%p", aControlledDocument);
nsAutoCString suffix;
aAttrs.CreateSuffix(suffix);
return AddToHash(0, aURI->ComputeHash(aBlobSerial),
HashString(suffix), HashString(ptr));
}
/* static */ void*
ImageCacheKey::GetControlledDocumentToken(nsIDocument* aDocument)
{
// For non-controlled documents, we just return null. For controlled
// documents, we cast the pointer into a void* to avoid dereferencing
// it (since we only use it for comparisons), and return it.
void* pointer = nullptr;
using dom::workers::ServiceWorkerManager;
RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
if (aDocument && swm) {
ErrorResult rv;
if (swm->IsControlled(aDocument, rv)) {
pointer = aDocument;
}
}
return pointer;
}
} // namespace image
} // namespace mozilla

75
image/ImageCacheKey.h Normal file
View file

@ -0,0 +1,75 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* 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/. */
/**
* ImageCacheKey is the key type for the image cache (see imgLoader.h).
*/
#ifndef mozilla_image_src_ImageCacheKey_h
#define mozilla_image_src_ImageCacheKey_h
#include "mozilla/BasePrincipal.h"
#include "mozilla/Maybe.h"
#include "mozilla/RefPtr.h"
class nsIDocument;
class nsIURI;
namespace mozilla {
namespace image {
class ImageURL;
/**
* An ImageLib cache entry key.
*
* We key the cache on the initial URI (before any redirects), with some
* canonicalization applied. See ComputeHash() for the details.
* Controlled documents do not share their cache entries with
* non-controlled documents, or other controlled documents.
*/
class ImageCacheKey final
{
public:
ImageCacheKey(nsIURI* aURI, const PrincipalOriginAttributes& aAttrs,
nsIDocument* aDocument, nsresult& aRv);
ImageCacheKey(ImageURL* aURI, const PrincipalOriginAttributes& aAttrs,
nsIDocument* aDocument);
ImageCacheKey(const ImageCacheKey& aOther);
ImageCacheKey(ImageCacheKey&& aOther);
bool operator==(const ImageCacheKey& aOther) const;
uint32_t Hash() const { return mHash; }
/// A weak pointer to the URI spec for this cache entry. For logging only.
const char* Spec() const;
/// Is this cache entry for a chrome image?
bool IsChrome() const { return mIsChrome; }
/// A token indicating which service worker controlled document this entry
/// belongs to, if any.
void* ControlledDocument() const { return mControlledDocument; }
private:
static uint32_t ComputeHash(ImageURL* aURI,
const Maybe<uint64_t>& aBlobSerial,
const PrincipalOriginAttributes& aAttrs,
void* aControlledDocument);
static void* GetControlledDocumentToken(nsIDocument* aDocument);
RefPtr<ImageURL> mURI;
Maybe<uint64_t> mBlobSerial;
PrincipalOriginAttributes mOriginAttributes;
void* mControlledDocument;
uint32_t mHash;
bool mIsChrome;
};
} // namespace image
} // namespace mozilla
#endif // mozilla_image_src_ImageCacheKey_h

288
image/ImageFactory.cpp Normal file
View file

@ -0,0 +1,288 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
*
* 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/. */
#include "ImageFactory.h"
#include <algorithm>
#include "mozilla/Likely.h"
#include "nsIHttpChannel.h"
#include "nsIFileChannel.h"
#include "nsIFile.h"
#include "nsMimeTypes.h"
#include "nsIRequest.h"
#include "MultipartImage.h"
#include "RasterImage.h"
#include "VectorImage.h"
#include "Image.h"
#include "nsMediaFragmentURIParser.h"
#include "nsContentUtils.h"
#include "nsIScriptSecurityManager.h"
#include "gfxPrefs.h"
namespace mozilla {
namespace image {
/*static*/ void
ImageFactory::Initialize()
{ }
static uint32_t
ComputeImageFlags(ImageURL* uri, const nsCString& aMimeType, bool isMultiPart)
{
nsresult rv;
// We default to the static globals.
bool isDiscardable = gfxPrefs::ImageMemDiscardable();
bool doDecodeImmediately = gfxPrefs::ImageDecodeImmediatelyEnabled();
// We want UI to be as snappy as possible and not to flicker. Disable
// discarding for chrome URLS.
bool isChrome = false;
rv = uri->SchemeIs("chrome", &isChrome);
if (NS_SUCCEEDED(rv) && isChrome) {
isDiscardable = false;
}
// We don't want resources like the "loading" icon to be discardable either.
bool isResource = false;
rv = uri->SchemeIs("resource", &isResource);
if (NS_SUCCEEDED(rv) && isResource) {
isDiscardable = false;
}
// For multipart/x-mixed-replace, we basically want a direct channel to the
// decoder. Disable everything for this case.
if (isMultiPart) {
isDiscardable = false;
}
// We have all the information we need.
uint32_t imageFlags = Image::INIT_FLAG_NONE;
if (isDiscardable) {
imageFlags |= Image::INIT_FLAG_DISCARDABLE;
}
if (doDecodeImmediately) {
imageFlags |= Image::INIT_FLAG_DECODE_IMMEDIATELY;
}
if (isMultiPart) {
imageFlags |= Image::INIT_FLAG_TRANSIENT;
}
return imageFlags;
}
/* static */ already_AddRefed<Image>
ImageFactory::CreateImage(nsIRequest* aRequest,
ProgressTracker* aProgressTracker,
const nsCString& aMimeType,
ImageURL* aURI,
bool aIsMultiPart,
uint32_t aInnerWindowId)
{
MOZ_ASSERT(gfxPrefs::SingletonExists(),
"Pref observers should have been initialized already");
// Compute the image's initialization flags.
uint32_t imageFlags = ComputeImageFlags(aURI, aMimeType, aIsMultiPart);
// Select the type of image to create based on MIME type.
if (aMimeType.EqualsLiteral(IMAGE_SVG_XML)) {
return CreateVectorImage(aRequest, aProgressTracker, aMimeType,
aURI, imageFlags, aInnerWindowId);
} else {
return CreateRasterImage(aRequest, aProgressTracker, aMimeType,
aURI, imageFlags, aInnerWindowId);
}
}
// Marks an image as having an error before returning it.
template <typename T>
static already_AddRefed<Image>
BadImage(const char* aMessage, RefPtr<T>& aImage)
{
aImage->SetHasError();
return aImage.forget();
}
/* static */ already_AddRefed<Image>
ImageFactory::CreateAnonymousImage(const nsCString& aMimeType)
{
nsresult rv;
RefPtr<RasterImage> newImage = new RasterImage();
RefPtr<ProgressTracker> newTracker = new ProgressTracker();
newTracker->SetImage(newImage);
newImage->SetProgressTracker(newTracker);
rv = newImage->Init(aMimeType.get(), Image::INIT_FLAG_SYNC_LOAD);
if (NS_FAILED(rv)) {
return BadImage("RasterImage::Init failed", newImage);
}
return newImage.forget();
}
/* static */ already_AddRefed<MultipartImage>
ImageFactory::CreateMultipartImage(Image* aFirstPart,
ProgressTracker* aProgressTracker)
{
MOZ_ASSERT(aFirstPart);
MOZ_ASSERT(aProgressTracker);
RefPtr<MultipartImage> newImage = new MultipartImage(aFirstPart);
aProgressTracker->SetImage(newImage);
newImage->SetProgressTracker(aProgressTracker);
newImage->Init();
return newImage.forget();
}
int32_t
SaturateToInt32(int64_t val)
{
if (val > INT_MAX) {
return INT_MAX;
}
if (val < INT_MIN) {
return INT_MIN;
}
return static_cast<int32_t>(val);
}
uint32_t
GetContentSize(nsIRequest* aRequest)
{
nsCOMPtr<nsIChannel> channel(do_QueryInterface(aRequest));
if (channel) {
int64_t size;
nsresult rv = channel->GetContentLength(&size);
if (NS_SUCCEEDED(rv)) {
return std::max(SaturateToInt32(size), 0);
}
}
// Use the file size as a size hint for file channels.
nsCOMPtr<nsIFileChannel> fileChannel(do_QueryInterface(aRequest));
if (fileChannel) {
nsCOMPtr<nsIFile> file;
nsresult rv = fileChannel->GetFile(getter_AddRefs(file));
if (NS_SUCCEEDED(rv)) {
int64_t filesize;
rv = file->GetFileSize(&filesize);
if (NS_SUCCEEDED(rv)) {
return std::max(SaturateToInt32(filesize), 0);
}
}
}
// Fallback - neither http nor file. We'll use dynamic allocation.
return 0;
}
/* static */ already_AddRefed<Image>
ImageFactory::CreateRasterImage(nsIRequest* aRequest,
ProgressTracker* aProgressTracker,
const nsCString& aMimeType,
ImageURL* aURI,
uint32_t aImageFlags,
uint32_t aInnerWindowId)
{
MOZ_ASSERT(aProgressTracker);
nsresult rv;
RefPtr<RasterImage> newImage = new RasterImage(aURI);
aProgressTracker->SetImage(newImage);
newImage->SetProgressTracker(aProgressTracker);
nsAutoCString ref;
aURI->GetRef(ref);
net::nsMediaFragmentURIParser parser(ref);
if (parser.HasSampleSize()) {
/* Get our principal */
nsCOMPtr<nsIChannel> chan(do_QueryInterface(aRequest));
nsCOMPtr<nsIPrincipal> principal;
if (chan) {
nsContentUtils::GetSecurityManager()
->GetChannelResultPrincipal(chan, getter_AddRefs(principal));
}
if ((principal &&
principal->GetAppStatus() == nsIPrincipal::APP_STATUS_CERTIFIED) ||
gfxPrefs::ImageMozSampleSizeEnabled()) {
newImage->SetRequestedSampleSize(parser.GetSampleSize());
}
}
rv = newImage->Init(aMimeType.get(), aImageFlags);
if (NS_FAILED(rv)) {
return BadImage("RasterImage::Init failed", newImage);
}
newImage->SetInnerWindowID(aInnerWindowId);
uint32_t len = GetContentSize(aRequest);
// Pass anything usable on so that the RasterImage can preallocate
// its source buffer.
if (len > 0) {
// Bound by something reasonable
uint32_t sizeHint = std::min<uint32_t>(len, 20000000);
rv = newImage->SetSourceSizeHint(sizeHint);
if (NS_FAILED(rv)) {
// Flush memory, try to get some back, and try again.
rv = nsMemory::HeapMinimize(true);
nsresult rv2 = newImage->SetSourceSizeHint(sizeHint);
// If we've still failed at this point, things are going downhill.
if (NS_FAILED(rv) || NS_FAILED(rv2)) {
NS_WARNING("About to hit OOM in imagelib!");
}
}
}
return newImage.forget();
}
/* static */ already_AddRefed<Image>
ImageFactory::CreateVectorImage(nsIRequest* aRequest,
ProgressTracker* aProgressTracker,
const nsCString& aMimeType,
ImageURL* aURI,
uint32_t aImageFlags,
uint32_t aInnerWindowId)
{
MOZ_ASSERT(aProgressTracker);
nsresult rv;
RefPtr<VectorImage> newImage = new VectorImage(aURI);
aProgressTracker->SetImage(newImage);
newImage->SetProgressTracker(aProgressTracker);
rv = newImage->Init(aMimeType.get(), aImageFlags);
if (NS_FAILED(rv)) {
return BadImage("VectorImage::Init failed", newImage);
}
newImage->SetInnerWindowID(aInnerWindowId);
rv = newImage->OnStartRequest(aRequest, nullptr);
if (NS_FAILED(rv)) {
return BadImage("VectorImage::OnStartRequest failed", newImage);
}
return newImage.forget();
}
} // namespace image
} // namespace mozilla

95
image/ImageFactory.h Normal file
View file

@ -0,0 +1,95 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
*
* 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/. */
#ifndef mozilla_image_ImageFactory_h
#define mozilla_image_ImageFactory_h
#include "nsCOMPtr.h"
#include "nsProxyRelease.h"
class nsCString;
class nsIRequest;
namespace mozilla {
namespace image {
class Image;
class ImageURL;
class MultipartImage;
class ProgressTracker;
class ImageFactory
{
public:
/**
* Registers vars with Preferences. Should only be called on the main thread.
*/
static void Initialize();
/**
* Creates a new image with the given properties.
* Can be called on or off the main thread.
*
* @param aRequest The associated request.
* @param aProgressTracker A status tracker for the image to use.
* @param aMimeType The mimetype of the image.
* @param aURI The URI of the image.
* @param aIsMultiPart Whether the image is part of a multipart request.
* @param aInnerWindowId The window this image belongs to.
*/
static already_AddRefed<Image> CreateImage(nsIRequest* aRequest,
ProgressTracker* aProgressTracker,
const nsCString& aMimeType,
ImageURL* aURI,
bool aIsMultiPart,
uint32_t aInnerWindowId);
/**
* Creates a new image which isn't associated with a URI or loaded through
* the usual image loading mechanism.
*
* @param aMimeType The mimetype of the image.
*/
static already_AddRefed<Image>
CreateAnonymousImage(const nsCString& aMimeType);
/**
* Creates a new multipart/x-mixed-replace image wrapper, and initializes it
* with the first part. Subsequent parts should be passed to the existing
* MultipartImage via MultipartImage::BeginTransitionToPart().
*
* @param aFirstPart An image containing the first part of the multipart
* stream.
* @param aProgressTracker A progress tracker for the multipart image.
*/
static already_AddRefed<MultipartImage>
CreateMultipartImage(Image* aFirstPart, ProgressTracker* aProgressTracker);
private:
// Factory functions that create specific types of image containers.
static already_AddRefed<Image>
CreateRasterImage(nsIRequest* aRequest,
ProgressTracker* aProgressTracker,
const nsCString& aMimeType,
ImageURL* aURI,
uint32_t aImageFlags,
uint32_t aInnerWindowId);
static already_AddRefed<Image>
CreateVectorImage(nsIRequest* aRequest,
ProgressTracker* aProgressTracker,
const nsCString& aMimeType,
ImageURL* aURI,
uint32_t aImageFlags,
uint32_t aInnerWindowId);
// This is a static factory class, so disallow instantiation.
virtual ~ImageFactory() = 0;
};
} // namespace image
} // namespace mozilla
#endif // mozilla_image_ImageFactory_h

161
image/ImageLogging.h Normal file
View file

@ -0,0 +1,161 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
*
* 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/. */
#ifndef mozilla_image_ImageLogging_h
#define mozilla_image_ImageLogging_h
#include "mozilla/Logging.h"
#include "prinrval.h"
static mozilla::LazyLogModule gImgLog("imgRequest");
#define GIVE_ME_MS_NOW() PR_IntervalToMilliseconds(PR_IntervalNow())
using mozilla::LogLevel;
class LogScope {
public:
LogScope(mozilla::LogModule* aLog, void* aFrom, const char* aFunc)
: mLog(aLog)
, mFrom(aFrom)
, mFunc(aFunc)
{
MOZ_LOG(mLog, LogLevel::Debug, ("%d [this=%p] %s {ENTER}\n",
GIVE_ME_MS_NOW(), mFrom, mFunc));
}
/* const char * constructor */
LogScope(mozilla::LogModule* aLog, void* from, const char* fn,
const char* paramName, const char* paramValue)
: mLog(aLog)
, mFrom(from)
, mFunc(fn)
{
MOZ_LOG(mLog, LogLevel::Debug, ("%d [this=%p] %s (%s=\"%s\") {ENTER}\n",
GIVE_ME_MS_NOW(), mFrom, mFunc,
paramName, paramValue));
}
/* void ptr constructor */
LogScope(mozilla::LogModule* aLog, void* from, const char* fn,
const char* paramName, const void* paramValue)
: mLog(aLog)
, mFrom(from)
, mFunc(fn)
{
MOZ_LOG(mLog, LogLevel::Debug, ("%d [this=%p] %s (%s=%p) {ENTER}\n",
GIVE_ME_MS_NOW(), mFrom, mFunc,
paramName, paramValue));
}
/* int32_t constructor */
LogScope(mozilla::LogModule* aLog, void* from, const char* fn,
const char* paramName, int32_t paramValue)
: mLog(aLog)
, mFrom(from)
, mFunc(fn)
{
MOZ_LOG(mLog, LogLevel::Debug, ("%d [this=%p] %s (%s=\"%d\") {ENTER}\n",
GIVE_ME_MS_NOW(), mFrom, mFunc,
paramName, paramValue));
}
/* uint32_t constructor */
LogScope(mozilla::LogModule* aLog, void* from, const char* fn,
const char* paramName, uint32_t paramValue)
: mLog(aLog)
, mFrom(from)
, mFunc(fn)
{
MOZ_LOG(mLog, LogLevel::Debug, ("%d [this=%p] %s (%s=\"%d\") {ENTER}\n",
GIVE_ME_MS_NOW(), mFrom, mFunc,
paramName, paramValue));
}
~LogScope()
{
MOZ_LOG(mLog, LogLevel::Debug, ("%d [this=%p] %s {EXIT}\n",
GIVE_ME_MS_NOW(), mFrom, mFunc));
}
private:
mozilla::LogModule* mLog;
void* mFrom;
const char* mFunc;
};
class LogFunc {
public:
LogFunc(mozilla::LogModule* aLog, void* from, const char* fn)
{
MOZ_LOG(aLog, LogLevel::Debug, ("%d [this=%p] %s\n",
GIVE_ME_MS_NOW(), from, fn));
}
LogFunc(mozilla::LogModule* aLog, void* from, const char* fn,
const char* paramName, const char* paramValue)
{
MOZ_LOG(aLog, LogLevel::Debug, ("%d [this=%p] %s (%s=\"%s\")\n",
GIVE_ME_MS_NOW(), from, fn,
paramName, paramValue));
}
LogFunc(mozilla::LogModule* aLog, void* from, const char* fn,
const char* paramName, const void* paramValue)
{
MOZ_LOG(aLog, LogLevel::Debug, ("%d [this=%p] %s (%s=\"%p\")\n",
GIVE_ME_MS_NOW(), from, fn,
paramName, paramValue));
}
LogFunc(mozilla::LogModule* aLog, void* from, const char* fn,
const char* paramName, uint32_t paramValue)
{
MOZ_LOG(aLog, LogLevel::Debug, ("%d [this=%p] %s (%s=\"%d\")\n",
GIVE_ME_MS_NOW(), from, fn,
paramName, paramValue));
}
};
class LogMessage {
public:
LogMessage(mozilla::LogModule* aLog, void* from, const char* fn,
const char* msg)
{
MOZ_LOG(aLog, LogLevel::Debug, ("%d [this=%p] %s -- %s\n",
GIVE_ME_MS_NOW(), from, fn, msg));
}
};
#define LOG_SCOPE_APPEND_LINE_NUMBER_PASTE(id, line) id ## line
#define LOG_SCOPE_APPEND_LINE_NUMBER_EXPAND(id, line) \
LOG_SCOPE_APPEND_LINE_NUMBER_PASTE(id, line)
#define LOG_SCOPE_APPEND_LINE_NUMBER(id) \
LOG_SCOPE_APPEND_LINE_NUMBER_EXPAND(id, __LINE__)
#define LOG_SCOPE(l, s) \
LogScope LOG_SCOPE_APPEND_LINE_NUMBER(LOG_SCOPE_TMP_VAR) (l, this, s)
#define LOG_SCOPE_WITH_PARAM(l, s, pn, pv) \
LogScope LOG_SCOPE_APPEND_LINE_NUMBER(LOG_SCOPE_TMP_VAR) (l, this, s, pn, pv)
#define LOG_FUNC(l, s) LogFunc(l, this, s)
#define LOG_FUNC_WITH_PARAM(l, s, pn, pv) LogFunc(l, this, s, pn, pv)
#define LOG_STATIC_FUNC(l, s) LogFunc(l, nullptr, s)
#define LOG_STATIC_FUNC_WITH_PARAM(l, s, pn, pv) LogFunc(l, nullptr, s, pn, pv)
#define LOG_MSG(l, s, m) LogMessage(l, this, s, m)
#define LOG_MSG_WITH_PARAM LOG_FUNC_WITH_PARAM
#endif // mozilla_image_ImageLogging_h

99
image/ImageMetadata.h Normal file
View file

@ -0,0 +1,99 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
*
* 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/. */
#ifndef mozilla_image_ImageMetadata_h
#define mozilla_image_ImageMetadata_h
#include <stdint.h>
#include "mozilla/Maybe.h"
#include "nsSize.h"
#include "Orientation.h"
namespace mozilla {
namespace image {
class RasterImage;
// The metadata about an image that decoders accumulate as they decode.
class ImageMetadata
{
public:
ImageMetadata()
: mLoopCount(-1)
, mFirstFrameTimeout(FrameTimeout::Forever())
, mHasAnimation(false)
{ }
void SetHotspot(uint16_t aHotspotX, uint16_t aHotspotY)
{
mHotspot = Some(gfx::IntPoint(aHotspotX, aHotspotY));
}
gfx::IntPoint GetHotspot() const { return *mHotspot; }
bool HasHotspot() const { return mHotspot.isSome(); }
void SetLoopCount(int32_t loopcount)
{
mLoopCount = loopcount;
}
int32_t GetLoopCount() const { return mLoopCount; }
void SetLoopLength(FrameTimeout aLength) { mLoopLength = Some(aLength); }
FrameTimeout GetLoopLength() const { return *mLoopLength; }
bool HasLoopLength() const { return mLoopLength.isSome(); }
void SetFirstFrameTimeout(FrameTimeout aTimeout) { mFirstFrameTimeout = aTimeout; }
FrameTimeout GetFirstFrameTimeout() const { return mFirstFrameTimeout; }
void SetFirstFrameRefreshArea(const gfx::IntRect& aRefreshArea)
{
mFirstFrameRefreshArea = Some(aRefreshArea);
}
gfx::IntRect GetFirstFrameRefreshArea() const { return *mFirstFrameRefreshArea; }
bool HasFirstFrameRefreshArea() const { return mFirstFrameRefreshArea.isSome(); }
void SetSize(int32_t width, int32_t height, Orientation orientation)
{
if (!HasSize()) {
mSize.emplace(nsIntSize(width, height));
mOrientation.emplace(orientation);
}
}
nsIntSize GetSize() const { return *mSize; }
bool HasSize() const { return mSize.isSome(); }
Orientation GetOrientation() const { return *mOrientation; }
bool HasOrientation() const { return mOrientation.isSome(); }
void SetHasAnimation() { mHasAnimation = true; }
bool HasAnimation() const { return mHasAnimation; }
private:
/// The hotspot found on cursors, if present.
Maybe<gfx::IntPoint> mHotspot;
/// The loop count for animated images, or -1 for infinite loop.
int32_t mLoopCount;
// The total length of a single loop through an animated image.
Maybe<FrameTimeout> mLoopLength;
/// The timeout of an animated image's first frame.
FrameTimeout mFirstFrameTimeout;
// The area of the image that needs to be invalidated when the animation
// loops.
Maybe<gfx::IntRect> mFirstFrameRefreshArea;
Maybe<nsIntSize> mSize;
Maybe<Orientation> mOrientation;
bool mHasAnimation : 1;
};
} // namespace image
} // namespace mozilla
#endif // mozilla_image_ImageMetadata_h

150
image/ImageOps.cpp Normal file
View file

@ -0,0 +1,150 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
*
* 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/. */
#include "ImageOps.h"
#include "ClippedImage.h"
#include "DecodePool.h"
#include "Decoder.h"
#include "DecoderFactory.h"
#include "DynamicImage.h"
#include "FrozenImage.h"
#include "IDecodingTask.h"
#include "Image.h"
#include "imgIContainer.h"
#include "mozilla/gfx/2D.h"
#include "nsStreamUtils.h"
#include "OrientedImage.h"
#include "SourceBuffer.h"
using namespace mozilla::gfx;
namespace mozilla {
namespace image {
/* static */ already_AddRefed<Image>
ImageOps::Freeze(Image* aImage)
{
RefPtr<Image> frozenImage = new FrozenImage(aImage);
return frozenImage.forget();
}
/* static */ already_AddRefed<imgIContainer>
ImageOps::Freeze(imgIContainer* aImage)
{
nsCOMPtr<imgIContainer> frozenImage =
new FrozenImage(static_cast<Image*>(aImage));
return frozenImage.forget();
}
/* static */ already_AddRefed<Image>
ImageOps::Clip(Image* aImage, nsIntRect aClip,
const Maybe<nsSize>& aSVGViewportSize)
{
RefPtr<Image> clippedImage = new ClippedImage(aImage, aClip, aSVGViewportSize);
return clippedImage.forget();
}
/* static */ already_AddRefed<imgIContainer>
ImageOps::Clip(imgIContainer* aImage, nsIntRect aClip,
const Maybe<nsSize>& aSVGViewportSize)
{
nsCOMPtr<imgIContainer> clippedImage =
new ClippedImage(static_cast<Image*>(aImage), aClip, aSVGViewportSize);
return clippedImage.forget();
}
/* static */ already_AddRefed<Image>
ImageOps::Orient(Image* aImage, Orientation aOrientation)
{
RefPtr<Image> orientedImage = new OrientedImage(aImage, aOrientation);
return orientedImage.forget();
}
/* static */ already_AddRefed<imgIContainer>
ImageOps::Orient(imgIContainer* aImage, Orientation aOrientation)
{
nsCOMPtr<imgIContainer> orientedImage =
new OrientedImage(static_cast<Image*>(aImage), aOrientation);
return orientedImage.forget();
}
/* static */ already_AddRefed<imgIContainer>
ImageOps::CreateFromDrawable(gfxDrawable* aDrawable)
{
nsCOMPtr<imgIContainer> drawableImage = new DynamicImage(aDrawable);
return drawableImage.forget();
}
/* static */ already_AddRefed<gfx::SourceSurface>
ImageOps::DecodeToSurface(nsIInputStream* aInputStream,
const nsACString& aMimeType,
uint32_t aFlags)
{
MOZ_ASSERT(aInputStream);
nsresult rv;
// Prepare the input stream.
nsCOMPtr<nsIInputStream> inputStream = aInputStream;
if (!NS_InputStreamIsBuffered(aInputStream)) {
nsCOMPtr<nsIInputStream> bufStream;
rv = NS_NewBufferedInputStream(getter_AddRefs(bufStream),
aInputStream, 1024);
if (NS_SUCCEEDED(rv)) {
inputStream = bufStream;
}
}
// Figure out how much data we've been passed.
uint64_t length;
rv = inputStream->Available(&length);
if (NS_FAILED(rv) || length > UINT32_MAX) {
return nullptr;
}
// Write the data into a SourceBuffer.
NotNull<RefPtr<SourceBuffer>> sourceBuffer = WrapNotNull(new SourceBuffer());
sourceBuffer->ExpectLength(length);
rv = sourceBuffer->AppendFromInputStream(inputStream, length);
if (NS_FAILED(rv)) {
return nullptr;
}
sourceBuffer->Complete(NS_OK);
// Create a decoder.
DecoderType decoderType =
DecoderFactory::GetDecoderType(PromiseFlatCString(aMimeType).get());
RefPtr<Decoder> decoder =
DecoderFactory::CreateAnonymousDecoder(decoderType, sourceBuffer,
Nothing(), ToSurfaceFlags(aFlags));
if (!decoder) {
return nullptr;
}
// Run the decoder synchronously.
RefPtr<IDecodingTask> task = new AnonymousDecodingTask(WrapNotNull(decoder));
task->Run();
if (!decoder->GetDecodeDone() || decoder->HasError()) {
return nullptr;
}
// Pull out the surface.
RawAccessFrameRef frame = decoder->GetCurrentFrameRef();
if (!frame) {
return nullptr;
}
RefPtr<SourceSurface> surface = frame->GetSourceSurface();
if (!surface) {
return nullptr;
}
return surface.forget();
}
} // namespace image
} // namespace mozilla

102
image/ImageOps.h Normal file
View file

@ -0,0 +1,102 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
*
* 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/. */
#ifndef mozilla_image_ImageOps_h
#define mozilla_image_ImageOps_h
#include "nsCOMPtr.h"
#include "nsRect.h"
class gfxDrawable;
class imgIContainer;
class nsIInputStream;
namespace mozilla {
namespace gfx {
class SourceSurface;
}
namespace image {
class Image;
struct Orientation;
class ImageOps
{
public:
/**
* Creates a version of an existing image which does not animate and is frozen
* at the first frame.
*
* @param aImage The existing image.
*/
static already_AddRefed<Image> Freeze(Image* aImage);
static already_AddRefed<imgIContainer> Freeze(imgIContainer* aImage);
/**
* Creates a clipped version of an existing image. Animation is unaffected.
*
* @param aImage The existing image.
* @param aClip The rectangle to clip the image against.
* @param aSVGViewportSize The specific viewort size of aImage. Unless aImage
* is a vector image without intrinsic size, this
* argument should be pass as Nothing().
*/
static already_AddRefed<Image> Clip(Image* aImage, nsIntRect aClip,
const Maybe<nsSize>& aSVGViewportSize =
Nothing());
static already_AddRefed<imgIContainer> Clip(imgIContainer* aImage,
nsIntRect aClip,
const Maybe<nsSize>& aSVGViewportSize =
Nothing());
/**
* Creates a version of an existing image which is rotated and/or flipped to
* the specified orientation.
*
* @param aImage The existing image.
* @param aOrientation The desired orientation.
*/
static already_AddRefed<Image> Orient(Image* aImage,
Orientation aOrientation);
static already_AddRefed<imgIContainer> Orient(imgIContainer* aImage,
Orientation aOrientation);
/**
* Creates an image from a gfxDrawable.
*
* @param aDrawable The gfxDrawable.
*/
static already_AddRefed<imgIContainer>
CreateFromDrawable(gfxDrawable* aDrawable);
/**
* Decodes an image from an nsIInputStream directly into a SourceSurface,
* without ever creating an Image or imgIContainer (which are mostly
* main-thread-only). That means that this function may be called
* off-main-thread.
*
* @param aInputStream An input stream containing an encoded image.
* @param aMimeType The MIME type of the image.
* @param aFlags Flags of the imgIContainer::FLAG_DECODE_* variety.
* @return A SourceSurface containing the first frame of the image at its
* intrinsic size, or nullptr if the image cannot be decoded.
*/
static already_AddRefed<gfx::SourceSurface>
DecodeToSurface(nsIInputStream* aInputStream,
const nsACString& aMimeType,
uint32_t aFlags);
private:
// This is a static utility class, so disallow instantiation.
virtual ~ImageOps() = 0;
};
} // namespace image
} // namespace mozilla
#endif // mozilla_image_ImageOps_h

173
image/ImageRegion.h Normal file
View file

@ -0,0 +1,173 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* 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/. */
#ifndef mozilla_image_ImageRegion_h
#define mozilla_image_ImageRegion_h
#include "gfxRect.h"
#include "mozilla/gfx/Types.h"
namespace mozilla {
namespace image {
/**
* An axis-aligned rectangle in tiled image space, with an optional sampling
* restriction rect. The drawing code ensures that if a sampling restriction
* rect is present, any pixels sampled during the drawing process are found
* within that rect.
*
* The sampling restriction rect exists primarily for callers which perform
* pixel snapping. Other callers should generally use one of the Create()
* overloads.
*/
class ImageRegion
{
typedef mozilla::gfx::ExtendMode ExtendMode;
public:
static ImageRegion Empty()
{
return ImageRegion(gfxRect(), ExtendMode::CLAMP);
}
static ImageRegion Create(const gfxRect& aRect,
ExtendMode aExtendMode = ExtendMode::CLAMP)
{
return ImageRegion(aRect, aExtendMode);
}
static ImageRegion Create(const gfxSize& aSize,
ExtendMode aExtendMode = ExtendMode::CLAMP)
{
return ImageRegion(gfxRect(0, 0, aSize.width, aSize.height), aExtendMode);
}
static ImageRegion Create(const nsIntSize& aSize,
ExtendMode aExtendMode = ExtendMode::CLAMP)
{
return ImageRegion(gfxRect(0, 0, aSize.width, aSize.height), aExtendMode);
}
static ImageRegion CreateWithSamplingRestriction(const gfxRect& aRect,
const gfxRect& aRestriction,
ExtendMode aExtendMode = ExtendMode::CLAMP)
{
return ImageRegion(aRect, aRestriction, aExtendMode);
}
bool IsRestricted() const { return mIsRestricted; }
const gfxRect& Rect() const { return mRect; }
const gfxRect& Restriction() const
{
MOZ_ASSERT(mIsRestricted);
return mRestriction;
}
bool RestrictionContains(const gfxRect& aRect) const
{
if (!mIsRestricted) {
return true;
}
return mRestriction.Contains(aRect);
}
ImageRegion Intersect(const gfxRect& aRect) const
{
if (mIsRestricted) {
return CreateWithSamplingRestriction(aRect.Intersect(mRect),
aRect.Intersect(mRestriction));
}
return Create(aRect.Intersect(mRect));
}
gfxRect IntersectAndRestrict(const gfxRect& aRect) const
{
gfxRect intersection = mRect.Intersect(aRect);
if (mIsRestricted) {
intersection = mRestriction.Intersect(intersection);
}
return intersection;
}
void MoveBy(gfxFloat dx, gfxFloat dy)
{
mRect.MoveBy(dx, dy);
if (mIsRestricted) {
mRestriction.MoveBy(dx, dy);
}
}
void Scale(gfxFloat sx, gfxFloat sy)
{
mRect.Scale(sx, sy);
if (mIsRestricted) {
mRestriction.Scale(sx, sy);
}
}
void TransformBy(const gfxMatrix& aMatrix)
{
mRect = aMatrix.Transform(mRect);
if (mIsRestricted) {
mRestriction = aMatrix.Transform(mRestriction);
}
}
void TransformBoundsBy(const gfxMatrix& aMatrix)
{
mRect = aMatrix.TransformBounds(mRect);
if (mIsRestricted) {
mRestriction = aMatrix.TransformBounds(mRestriction);
}
}
ImageRegion operator-(const gfxPoint& aPt) const
{
if (mIsRestricted) {
return CreateWithSamplingRestriction(mRect - aPt, mRestriction - aPt);
}
return Create(mRect - aPt);
}
ImageRegion operator+(const gfxPoint& aPt) const
{
if (mIsRestricted) {
return CreateWithSamplingRestriction(mRect + aPt, mRestriction + aPt);
}
return Create(mRect + aPt);
}
gfx::ExtendMode GetExtendMode() const
{
return mExtendMode;
}
/* ImageRegion() : mIsRestricted(false) { } */
private:
explicit ImageRegion(const gfxRect& aRect, ExtendMode aExtendMode)
: mRect(aRect)
, mExtendMode(aExtendMode)
, mIsRestricted(false)
{ }
ImageRegion(const gfxRect& aRect, const gfxRect& aRestriction, ExtendMode aExtendMode)
: mRect(aRect)
, mRestriction(aRestriction)
, mExtendMode(aExtendMode)
, mIsRestricted(true)
{ }
gfxRect mRect;
gfxRect mRestriction;
ExtendMode mExtendMode;
bool mIsRestricted;
};
} // namespace image
} // namespace mozilla
#endif // mozilla_image_ImageRegion_h

151
image/ImageURL.h Normal file
View file

@ -0,0 +1,151 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* 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/. */
#ifndef mozilla_image_ImageURL_h
#define mozilla_image_ImageURL_h
#include "nsIURI.h"
#include "MainThreadUtils.h"
#include "nsNetUtil.h"
#include "mozilla/HashFunctions.h"
#include "nsHashKeys.h"
namespace mozilla {
namespace image {
class ImageCacheKey;
/** ImageURL
*
* nsStandardURL is not threadsafe, so this class is created to hold only the
* necessary URL data required for image loading and decoding.
*
* Note: Although several APIs have the same or similar prototypes as those
* found in nsIURI/nsStandardURL, the class does not implement nsIURI. This is
* intentional; functionality is limited, and is only useful for imagelib code.
* By not implementing nsIURI, external code cannot unintentionally be given an
* nsIURI pointer with this limited class behind it; instead, conversion to a
* fully implemented nsIURI is required (e.g. through NS_NewURI).
*/
class ImageURL
{
public:
explicit ImageURL(nsIURI* aURI, nsresult& aRv)
{
MOZ_ASSERT(NS_IsMainThread(), "Cannot use nsIURI off main thread!");
aRv = aURI->GetSpec(mSpec);
NS_ENSURE_SUCCESS_VOID(aRv);
aRv = aURI->GetScheme(mScheme);
NS_ENSURE_SUCCESS_VOID(aRv);
aRv = aURI->GetRef(mRef);
NS_ENSURE_SUCCESS_VOID(aRv);
}
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(ImageURL)
nsresult GetSpec(nsACString& result)
{
result = mSpec;
return NS_OK;
}
/// A weak pointer to the URI spec for this ImageURL. For logging only.
const char* Spec() const { return mSpec.get(); }
enum TruncatedSpecStatus {
FitsInto1k,
TruncatedTo1k
};
TruncatedSpecStatus GetSpecTruncatedTo1k(nsACString& result)
{
static const size_t sMaxTruncatedLength = 1024;
if (sMaxTruncatedLength >= mSpec.Length()) {
result = mSpec;
return FitsInto1k;
}
result = Substring(mSpec, 0, sMaxTruncatedLength);
return TruncatedTo1k;
}
nsresult GetScheme(nsACString& result)
{
result = mScheme;
return NS_OK;
}
nsresult SchemeIs(const char* scheme, bool* result)
{
NS_PRECONDITION(scheme, "scheme is null");
NS_PRECONDITION(result, "result is null");
*result = mScheme.Equals(scheme);
return NS_OK;
}
nsresult GetRef(nsACString& result)
{
result = mRef;
return NS_OK;
}
already_AddRefed<nsIURI> ToIURI()
{
MOZ_ASSERT(NS_IsMainThread(),
"Convert to nsIURI on main thread only; it is not threadsafe.");
nsCOMPtr<nsIURI> newURI;
NS_NewURI(getter_AddRefs(newURI), mSpec);
return newURI.forget();
}
bool operator==(const ImageURL& aOther) const
{
// Note that we don't need to consider mScheme and mRef, because they're
// already represented in mSpec.
return mSpec == aOther.mSpec;
}
bool HasSameRef(const ImageURL& aOther) const
{
return mRef == aOther.mRef;
}
private:
friend class ImageCacheKey;
uint32_t ComputeHash(const Maybe<uint64_t>& aBlobSerial) const
{
if (aBlobSerial) {
// For blob URIs, we hash the serial number of the underlying blob, so that
// different blob URIs which point to the same blob share a cache entry. We
// also include the ref portion of the URI to support media fragments which
// requires us to create different Image objects even if the source data is
// the same.
return HashGeneric(*aBlobSerial, HashString(mRef));
}
// For non-blob URIs, we hash the URI spec.
return HashString(mSpec);
}
// Since this is a basic storage class, no duplication of spec parsing is
// included in the functionality. Instead, the class depends upon the
// parsing implementation in the nsIURI class used in object construction.
// This means each field is stored separately, but since only a few are
// required, this small memory tradeoff for threadsafe usage should be ok.
nsAutoCString mSpec;
nsAutoCString mScheme;
nsAutoCString mRef;
~ImageURL() { }
};
} // namespace image
} // namespace mozilla
#endif // mozilla_image_ImageURL_h

323
image/ImageWrapper.cpp Normal file
View file

@ -0,0 +1,323 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* 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/. */
#include "ImageWrapper.h"
#include "mozilla/gfx/2D.h"
#include "mozilla/RefPtr.h"
#include "Orientation.h"
#include "mozilla/MemoryReporting.h"
namespace mozilla {
using gfx::DataSourceSurface;
using gfx::IntSize;
using gfx::SamplingFilter;
using gfx::SourceSurface;
using layers::LayerManager;
using layers::ImageContainer;
namespace image {
// Inherited methods from Image.
already_AddRefed<ProgressTracker>
ImageWrapper::GetProgressTracker()
{
return mInnerImage->GetProgressTracker();
}
size_t
ImageWrapper::SizeOfSourceWithComputedFallback(MallocSizeOf aMallocSizeOf) const
{
return mInnerImage->SizeOfSourceWithComputedFallback(aMallocSizeOf);
}
void
ImageWrapper::CollectSizeOfSurfaces(nsTArray<SurfaceMemoryCounter>& aCounters,
MallocSizeOf aMallocSizeOf) const
{
mInnerImage->CollectSizeOfSurfaces(aCounters, aMallocSizeOf);
}
void
ImageWrapper::IncrementAnimationConsumers()
{
MOZ_ASSERT(NS_IsMainThread(), "Main thread only to encourage serialization "
"with DecrementAnimationConsumers");
mInnerImage->IncrementAnimationConsumers();
}
void
ImageWrapper::DecrementAnimationConsumers()
{
MOZ_ASSERT(NS_IsMainThread(), "Main thread only to encourage serialization "
"with IncrementAnimationConsumers");
mInnerImage->DecrementAnimationConsumers();
}
#ifdef DEBUG
uint32_t
ImageWrapper::GetAnimationConsumers()
{
return mInnerImage->GetAnimationConsumers();
}
#endif
nsresult
ImageWrapper::OnImageDataAvailable(nsIRequest* aRequest,
nsISupports* aContext,
nsIInputStream* aInStr,
uint64_t aSourceOffset,
uint32_t aCount)
{
return mInnerImage->OnImageDataAvailable(aRequest, aContext, aInStr,
aSourceOffset, aCount);
}
nsresult
ImageWrapper::OnImageDataComplete(nsIRequest* aRequest,
nsISupports* aContext,
nsresult aStatus,
bool aLastPart)
{
return mInnerImage->OnImageDataComplete(aRequest, aContext, aStatus,
aLastPart);
}
void
ImageWrapper::OnSurfaceDiscarded()
{
return mInnerImage->OnSurfaceDiscarded();
}
void
ImageWrapper::SetInnerWindowID(uint64_t aInnerWindowId)
{
mInnerImage->SetInnerWindowID(aInnerWindowId);
}
uint64_t
ImageWrapper::InnerWindowID() const
{
return mInnerImage->InnerWindowID();
}
bool
ImageWrapper::HasError()
{
return mInnerImage->HasError();
}
void
ImageWrapper::SetHasError()
{
mInnerImage->SetHasError();
}
ImageURL*
ImageWrapper::GetURI()
{
return mInnerImage->GetURI();
}
// Methods inherited from XPCOM interfaces.
NS_IMPL_ISUPPORTS(ImageWrapper, imgIContainer)
NS_IMETHODIMP
ImageWrapper::GetWidth(int32_t* aWidth)
{
return mInnerImage->GetWidth(aWidth);
}
NS_IMETHODIMP
ImageWrapper::GetHeight(int32_t* aHeight)
{
return mInnerImage->GetHeight(aHeight);
}
NS_IMETHODIMP
ImageWrapper::GetIntrinsicSize(nsSize* aSize)
{
return mInnerImage->GetIntrinsicSize(aSize);
}
NS_IMETHODIMP
ImageWrapper::GetIntrinsicRatio(nsSize* aSize)
{
return mInnerImage->GetIntrinsicRatio(aSize);
}
NS_IMETHODIMP_(Orientation)
ImageWrapper::GetOrientation()
{
return mInnerImage->GetOrientation();
}
NS_IMETHODIMP
ImageWrapper::GetType(uint16_t* aType)
{
return mInnerImage->GetType(aType);
}
NS_IMETHODIMP
ImageWrapper::GetAnimated(bool* aAnimated)
{
return mInnerImage->GetAnimated(aAnimated);
}
NS_IMETHODIMP_(already_AddRefed<SourceSurface>)
ImageWrapper::GetFrame(uint32_t aWhichFrame,
uint32_t aFlags)
{
return mInnerImage->GetFrame(aWhichFrame, aFlags);
}
NS_IMETHODIMP_(already_AddRefed<SourceSurface>)
ImageWrapper::GetFrameAtSize(const IntSize& aSize,
uint32_t aWhichFrame,
uint32_t aFlags)
{
return mInnerImage->GetFrameAtSize(aSize, aWhichFrame, aFlags);
}
NS_IMETHODIMP_(bool)
ImageWrapper::WillDrawOpaqueNow()
{
return mInnerImage->WillDrawOpaqueNow();
}
NS_IMETHODIMP_(bool)
ImageWrapper::IsImageContainerAvailable(LayerManager* aManager, uint32_t aFlags)
{
return mInnerImage->IsImageContainerAvailable(aManager, aFlags);
}
NS_IMETHODIMP_(already_AddRefed<ImageContainer>)
ImageWrapper::GetImageContainer(LayerManager* aManager, uint32_t aFlags)
{
return mInnerImage->GetImageContainer(aManager, aFlags);
}
NS_IMETHODIMP_(DrawResult)
ImageWrapper::Draw(gfxContext* aContext,
const nsIntSize& aSize,
const ImageRegion& aRegion,
uint32_t aWhichFrame,
SamplingFilter aSamplingFilter,
const Maybe<SVGImageContext>& aSVGContext,
uint32_t aFlags)
{
return mInnerImage->Draw(aContext, aSize, aRegion, aWhichFrame,
aSamplingFilter, aSVGContext, aFlags);
}
NS_IMETHODIMP
ImageWrapper::StartDecoding()
{
return mInnerImage->StartDecoding();
}
NS_IMETHODIMP
ImageWrapper::RequestDecodeForSize(const nsIntSize& aSize, uint32_t aFlags)
{
return mInnerImage->RequestDecodeForSize(aSize, aFlags);
}
NS_IMETHODIMP
ImageWrapper::LockImage()
{
MOZ_ASSERT(NS_IsMainThread(),
"Main thread to encourage serialization with UnlockImage");
return mInnerImage->LockImage();
}
NS_IMETHODIMP
ImageWrapper::UnlockImage()
{
MOZ_ASSERT(NS_IsMainThread(),
"Main thread to encourage serialization with LockImage");
return mInnerImage->UnlockImage();
}
NS_IMETHODIMP
ImageWrapper::RequestDiscard()
{
return mInnerImage->RequestDiscard();
}
NS_IMETHODIMP_(void)
ImageWrapper::RequestRefresh(const TimeStamp& aTime)
{
return mInnerImage->RequestRefresh(aTime);
}
NS_IMETHODIMP
ImageWrapper::GetAnimationMode(uint16_t* aAnimationMode)
{
return mInnerImage->GetAnimationMode(aAnimationMode);
}
NS_IMETHODIMP
ImageWrapper::SetAnimationMode(uint16_t aAnimationMode)
{
return mInnerImage->SetAnimationMode(aAnimationMode);
}
NS_IMETHODIMP
ImageWrapper::ResetAnimation()
{
return mInnerImage->ResetAnimation();
}
NS_IMETHODIMP_(float)
ImageWrapper::GetFrameIndex(uint32_t aWhichFrame)
{
return mInnerImage->GetFrameIndex(aWhichFrame);
}
NS_IMETHODIMP_(int32_t)
ImageWrapper::GetFirstFrameDelay()
{
return mInnerImage->GetFirstFrameDelay();
}
NS_IMETHODIMP_(void)
ImageWrapper::SetAnimationStartTime(const TimeStamp& aTime)
{
mInnerImage->SetAnimationStartTime(aTime);
}
void
ImageWrapper::PropagateUseCounters(nsIDocument* aParentDocument)
{
mInnerImage->PropagateUseCounters(aParentDocument);
}
nsIntSize
ImageWrapper::OptimalImageSizeForDest(const gfxSize& aDest,
uint32_t aWhichFrame,
SamplingFilter aSamplingFilter,
uint32_t aFlags)
{
return mInnerImage->OptimalImageSizeForDest(aDest, aWhichFrame,
aSamplingFilter, aFlags);
}
NS_IMETHODIMP_(nsIntRect)
ImageWrapper::GetImageSpaceInvalidationRect(const nsIntRect& aRect)
{
return mInnerImage->GetImageSpaceInvalidationRect(aRect);
}
already_AddRefed<imgIContainer>
ImageWrapper::Unwrap()
{
return mInnerImage->Unwrap();
}
} // namespace image
} // namespace mozilla

85
image/ImageWrapper.h Normal file
View file

@ -0,0 +1,85 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* 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/. */
#ifndef mozilla_image_ImageWrapper_h
#define mozilla_image_ImageWrapper_h
#include "mozilla/MemoryReporting.h"
#include "Image.h"
namespace mozilla {
namespace image {
/**
* Abstract superclass for Images that wrap other Images.
*/
class ImageWrapper : public Image
{
public:
NS_DECL_ISUPPORTS
NS_DECL_IMGICONTAINER
// Inherited methods from Image.
virtual already_AddRefed<ProgressTracker> GetProgressTracker() override;
virtual size_t
SizeOfSourceWithComputedFallback(MallocSizeOf aMallocSizeOf) const override;
virtual void CollectSizeOfSurfaces(nsTArray<SurfaceMemoryCounter>& aCounters,
MallocSizeOf aMallocSizeOf) const override;
virtual void IncrementAnimationConsumers() override;
virtual void DecrementAnimationConsumers() override;
#ifdef DEBUG
virtual uint32_t GetAnimationConsumers() override;
#endif
virtual nsresult OnImageDataAvailable(nsIRequest* aRequest,
nsISupports* aContext,
nsIInputStream* aInStr,
uint64_t aSourceOffset,
uint32_t aCount) override;
virtual nsresult OnImageDataComplete(nsIRequest* aRequest,
nsISupports* aContext,
nsresult aStatus,
bool aLastPart) override;
virtual void OnSurfaceDiscarded() override;
virtual void SetInnerWindowID(uint64_t aInnerWindowId) override;
virtual uint64_t InnerWindowID() const override;
virtual bool HasError() override;
virtual void SetHasError() override;
virtual ImageURL* GetURI() override;
protected:
explicit ImageWrapper(Image* aInnerImage)
: mInnerImage(aInnerImage)
{
MOZ_ASSERT(aInnerImage, "Need an image to wrap");
}
virtual ~ImageWrapper() { }
/**
* Returns a weak reference to the inner image wrapped by this ImageWrapper.
*/
Image* InnerImage() const { return mInnerImage.get(); }
void SetInnerImage(Image* aInnerImage)
{
MOZ_ASSERT(aInnerImage, "Need an image to wrap");
mInnerImage = aInnerImage;
}
private:
RefPtr<Image> mInnerImage;
};
} // namespace image
} // namespace mozilla
#endif // mozilla_image_ImageWrapper_h

90
image/LookupResult.h Normal file
View file

@ -0,0 +1,90 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* 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/. */
/**
* LookupResult is the return type of SurfaceCache's Lookup*() functions. It
* combines a surface with relevant metadata tracked by SurfaceCache.
*/
#ifndef mozilla_image_LookupResult_h
#define mozilla_image_LookupResult_h
#include "mozilla/Attributes.h"
#include "mozilla/Move.h"
#include "ISurfaceProvider.h"
namespace mozilla {
namespace image {
enum class MatchType : uint8_t
{
NOT_FOUND, // No matching surface and no placeholder.
PENDING, // Found a matching placeholder, but no surface.
EXACT, // Found a surface that matches exactly.
SUBSTITUTE_BECAUSE_NOT_FOUND, // No exact match, but found a similar one.
SUBSTITUTE_BECAUSE_PENDING // Found a similar surface and a placeholder
// for an exact match.
};
/**
* LookupResult is the return type of SurfaceCache's Lookup*() functions. It
* combines a surface with relevant metadata tracked by SurfaceCache.
*/
class MOZ_STACK_CLASS LookupResult
{
public:
explicit LookupResult(MatchType aMatchType)
: mMatchType(aMatchType)
{
MOZ_ASSERT(mMatchType == MatchType::NOT_FOUND ||
mMatchType == MatchType::PENDING,
"Only NOT_FOUND or PENDING make sense with no surface");
}
LookupResult(LookupResult&& aOther)
: mSurface(Move(aOther.mSurface))
, mMatchType(aOther.mMatchType)
{ }
LookupResult(DrawableSurface&& aSurface, MatchType aMatchType)
: mSurface(Move(aSurface))
, mMatchType(aMatchType)
{
MOZ_ASSERT(!mSurface || !(mMatchType == MatchType::NOT_FOUND ||
mMatchType == MatchType::PENDING),
"Only NOT_FOUND or PENDING make sense with no surface");
MOZ_ASSERT(mSurface || mMatchType == MatchType::NOT_FOUND ||
mMatchType == MatchType::PENDING,
"NOT_FOUND or PENDING do not make sense with a surface");
}
LookupResult& operator=(LookupResult&& aOther)
{
MOZ_ASSERT(&aOther != this, "Self-move-assignment is not supported");
mSurface = Move(aOther.mSurface);
mMatchType = aOther.mMatchType;
return *this;
}
DrawableSurface& Surface() { return mSurface; }
const DrawableSurface& Surface() const { return mSurface; }
/// @return true if this LookupResult contains a surface.
explicit operator bool() const { return bool(mSurface); }
/// @return what kind of match this is (exact, substitute, etc.)
MatchType Type() const { return mMatchType; }
private:
LookupResult(const LookupResult&) = delete;
DrawableSurface mSurface;
MatchType mMatchType;
};
} // namespace image
} // namespace mozilla
#endif // mozilla_image_LookupResult_h

346
image/MultipartImage.cpp Normal file
View file

@ -0,0 +1,346 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* 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/. */
#include "MultipartImage.h"
#include "imgINotificationObserver.h"
namespace mozilla {
using gfx::IntSize;
using gfx::SourceSurface;
namespace image {
///////////////////////////////////////////////////////////////////////////////
// Helpers
///////////////////////////////////////////////////////////////////////////////
class NextPartObserver : public IProgressObserver
{
public:
MOZ_DECLARE_REFCOUNTED_TYPENAME(NextPartObserver)
NS_INLINE_DECL_REFCOUNTING(NextPartObserver, override)
explicit NextPartObserver(MultipartImage* aOwner)
: mOwner(aOwner)
{
MOZ_ASSERT(mOwner);
}
void BeginObserving(Image* aImage)
{
MOZ_ASSERT(aImage);
mImage = aImage;
RefPtr<ProgressTracker> tracker = mImage->GetProgressTracker();
tracker->AddObserver(this);
}
void BlockUntilDecodedAndFinishObserving()
{
// Use GetFrame() to block until our image finishes decoding.
RefPtr<SourceSurface> surface =
mImage->GetFrame(imgIContainer::FRAME_CURRENT,
imgIContainer::FLAG_SYNC_DECODE);
// GetFrame() should've sent synchronous notifications that would have
// caused us to call FinishObserving() (and null out mImage) already. If for
// some reason it didn't, we should do so here.
if (mImage) {
FinishObserving();
}
}
virtual void Notify(int32_t aType,
const nsIntRect* aRect = nullptr) override
{
if (!mImage) {
// We've already finished observing the last image we were given.
return;
}
if (aType == imgINotificationObserver::FRAME_COMPLETE) {
FinishObserving();
}
}
virtual void OnLoadComplete(bool aLastPart) override
{
if (!mImage) {
// We've already finished observing the last image we were given.
return;
}
// Retrieve the image's intrinsic size.
int32_t width = 0;
int32_t height = 0;
mImage->GetWidth(&width);
mImage->GetHeight(&height);
// Request decoding at the intrinsic size.
mImage->RequestDecodeForSize(IntSize(width, height),
imgIContainer::DECODE_FLAGS_DEFAULT);
// If there's already an error, we may never get a FRAME_COMPLETE
// notification, so go ahead and notify our owner right away.
RefPtr<ProgressTracker> tracker = mImage->GetProgressTracker();
if (tracker->GetProgress() & FLAG_HAS_ERROR) {
FinishObserving();
}
}
// Other notifications are ignored.
virtual void BlockOnload() override { }
virtual void UnblockOnload() override { }
virtual void SetHasImage() override { }
virtual bool NotificationsDeferred() const override { return false; }
virtual void SetNotificationsDeferred(bool) override { }
private:
virtual ~NextPartObserver() { }
void FinishObserving()
{
MOZ_ASSERT(mImage);
RefPtr<ProgressTracker> tracker = mImage->GetProgressTracker();
tracker->RemoveObserver(this);
mImage = nullptr;
mOwner->FinishTransition();
}
MultipartImage* mOwner;
RefPtr<Image> mImage;
};
///////////////////////////////////////////////////////////////////////////////
// Implementation
///////////////////////////////////////////////////////////////////////////////
MultipartImage::MultipartImage(Image* aFirstPart)
: ImageWrapper(aFirstPart)
, mDeferNotifications(false)
{
mNextPartObserver = new NextPartObserver(this);
}
void
MultipartImage::Init()
{
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(mTracker, "Should've called SetProgressTracker() by now");
// Start observing the first part.
RefPtr<ProgressTracker> firstPartTracker =
InnerImage()->GetProgressTracker();
firstPartTracker->AddObserver(this);
InnerImage()->IncrementAnimationConsumers();
}
MultipartImage::~MultipartImage()
{
// Ask our ProgressTracker to drop its weak reference to us.
mTracker->ResetImage();
}
NS_IMPL_ISUPPORTS_INHERITED0(MultipartImage, ImageWrapper)
void
MultipartImage::BeginTransitionToPart(Image* aNextPart)
{
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(aNextPart);
if (mNextPart) {
// Let the decoder catch up so we don't drop frames.
mNextPartObserver->BlockUntilDecodedAndFinishObserving();
MOZ_ASSERT(!mNextPart);
}
mNextPart = aNextPart;
// Start observing the next part; we'll complete the transition when
// NextPartObserver calls FinishTransition.
mNextPartObserver->BeginObserving(mNextPart);
mNextPart->IncrementAnimationConsumers();
}
static Progress
FilterProgress(Progress aProgress)
{
// Filter out onload blocking notifications, since we don't want to block
// onload for multipart images.
return aProgress & ~(FLAG_ONLOAD_BLOCKED | FLAG_ONLOAD_UNBLOCKED);
}
void
MultipartImage::FinishTransition()
{
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(mNextPart, "Should have a next part here");
RefPtr<ProgressTracker> newCurrentPartTracker =
mNextPart->GetProgressTracker();
if (newCurrentPartTracker->GetProgress() & FLAG_HAS_ERROR) {
// This frame has an error; drop it.
mNextPart = nullptr;
// We still need to notify, though.
mTracker->ResetForNewRequest();
RefPtr<ProgressTracker> currentPartTracker =
InnerImage()->GetProgressTracker();
mTracker
->SyncNotifyProgress(FilterProgress(currentPartTracker->GetProgress()));
return;
}
// Stop observing the current part.
{
RefPtr<ProgressTracker> currentPartTracker =
InnerImage()->GetProgressTracker();
currentPartTracker->RemoveObserver(this);
}
// Make the next part become the current part.
mTracker->ResetForNewRequest();
SetInnerImage(mNextPart);
mNextPart = nullptr;
newCurrentPartTracker->AddObserver(this);
// Finally, send all the notifications for the new current part and send a
// FRAME_UPDATE notification so that observers know to redraw.
mTracker
->SyncNotifyProgress(FilterProgress(newCurrentPartTracker->GetProgress()),
GetMaxSizedIntRect());
}
already_AddRefed<imgIContainer>
MultipartImage::Unwrap()
{
// Although we wrap another image, we don't allow callers to unwrap as. As far
// as external code is concerned, MultipartImage is atomic.
nsCOMPtr<imgIContainer> image = this;
return image.forget();
}
already_AddRefed<ProgressTracker>
MultipartImage::GetProgressTracker()
{
MOZ_ASSERT(mTracker);
RefPtr<ProgressTracker> tracker = mTracker;
return tracker.forget();
}
void
MultipartImage::SetProgressTracker(ProgressTracker* aTracker)
{
MOZ_ASSERT(aTracker);
MOZ_ASSERT(!mTracker);
mTracker = aTracker;
}
nsresult
MultipartImage::OnImageDataAvailable(nsIRequest* aRequest,
nsISupports* aContext,
nsIInputStream* aInStr,
uint64_t aSourceOffset,
uint32_t aCount)
{
// Note that this method is special in that we forward it to the next part if
// one exists, and *not* the current part.
// We may trigger notifications that will free mNextPart, so keep it alive.
RefPtr<Image> nextPart = mNextPart;
if (nextPart) {
nextPart->OnImageDataAvailable(aRequest, aContext, aInStr,
aSourceOffset, aCount);
} else {
InnerImage()->OnImageDataAvailable(aRequest, aContext, aInStr,
aSourceOffset, aCount);
}
return NS_OK;
}
nsresult
MultipartImage::OnImageDataComplete(nsIRequest* aRequest,
nsISupports* aContext,
nsresult aStatus,
bool aLastPart)
{
// Note that this method is special in that we forward it to the next part if
// one exists, and *not* the current part.
// We may trigger notifications that will free mNextPart, so keep it alive.
RefPtr<Image> nextPart = mNextPart;
if (nextPart) {
nextPart->OnImageDataComplete(aRequest, aContext, aStatus, aLastPart);
} else {
InnerImage()->OnImageDataComplete(aRequest, aContext, aStatus, aLastPart);
}
return NS_OK;
}
void
MultipartImage::Notify(int32_t aType, const nsIntRect* aRect /* = nullptr*/)
{
if (aType == imgINotificationObserver::SIZE_AVAILABLE) {
mTracker->SyncNotifyProgress(FLAG_SIZE_AVAILABLE);
} else if (aType == imgINotificationObserver::FRAME_UPDATE) {
mTracker->SyncNotifyProgress(NoProgress, *aRect);
} else if (aType == imgINotificationObserver::FRAME_COMPLETE) {
mTracker->SyncNotifyProgress(FLAG_FRAME_COMPLETE);
} else if (aType == imgINotificationObserver::LOAD_COMPLETE) {
mTracker->SyncNotifyProgress(FLAG_LOAD_COMPLETE);
} else if (aType == imgINotificationObserver::DECODE_COMPLETE) {
mTracker->SyncNotifyProgress(FLAG_DECODE_COMPLETE);
} else if (aType == imgINotificationObserver::DISCARD) {
mTracker->OnDiscard();
} else if (aType == imgINotificationObserver::UNLOCKED_DRAW) {
mTracker->OnUnlockedDraw();
} else if (aType == imgINotificationObserver::IS_ANIMATED) {
mTracker->SyncNotifyProgress(FLAG_IS_ANIMATED);
} else if (aType == imgINotificationObserver::HAS_TRANSPARENCY) {
mTracker->SyncNotifyProgress(FLAG_HAS_TRANSPARENCY);
} else {
NS_NOTREACHED("Notification list should be exhaustive");
}
}
void
MultipartImage::OnLoadComplete(bool aLastPart)
{
Progress progress = FLAG_LOAD_COMPLETE;
if (aLastPart) {
progress |= FLAG_LAST_PART_COMPLETE;
}
mTracker->SyncNotifyProgress(progress);
}
void
MultipartImage::SetHasImage()
{
mTracker->OnImageAvailable();
}
bool
MultipartImage::NotificationsDeferred() const
{
return mDeferNotifications;
}
void
MultipartImage::SetNotificationsDeferred(bool aDeferNotifications)
{
mDeferNotifications = aDeferNotifications;
}
} // namespace image
} // namespace mozilla

90
image/MultipartImage.h Normal file
View file

@ -0,0 +1,90 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* 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/. */
#ifndef mozilla_image_MultipartImage_h
#define mozilla_image_MultipartImage_h
#include "ImageWrapper.h"
#include "IProgressObserver.h"
#include "ProgressTracker.h"
namespace mozilla {
namespace image {
class NextPartObserver;
/**
* An Image wrapper that implements support for multipart/x-mixed-replace
* images.
*/
class MultipartImage
: public ImageWrapper
, public IProgressObserver
{
public:
MOZ_DECLARE_REFCOUNTED_TYPENAME(MultipartImage)
NS_DECL_ISUPPORTS_INHERITED
void BeginTransitionToPart(Image* aNextPart);
// Overridden ImageWrapper methods:
virtual already_AddRefed<imgIContainer> Unwrap() override;
virtual already_AddRefed<ProgressTracker> GetProgressTracker() override;
virtual void SetProgressTracker(ProgressTracker* aTracker) override;
virtual nsresult OnImageDataAvailable(nsIRequest* aRequest,
nsISupports* aContext,
nsIInputStream* aInStr,
uint64_t aSourceOffset,
uint32_t aCount) override;
virtual nsresult OnImageDataComplete(nsIRequest* aRequest,
nsISupports* aContext,
nsresult aStatus,
bool aLastPart) override;
// We don't support locking or track animation consumers for individual parts,
// so we override these methods to do nothing.
NS_IMETHOD LockImage() override { return NS_OK; }
NS_IMETHOD UnlockImage() override { return NS_OK; }
virtual void IncrementAnimationConsumers() override { }
virtual void DecrementAnimationConsumers() override { }
#ifdef DEBUG
virtual uint32_t GetAnimationConsumers() override { return 1; }
#endif
// Overridden IProgressObserver methods:
virtual void Notify(int32_t aType,
const nsIntRect* aRect = nullptr) override;
virtual void OnLoadComplete(bool aLastPart) override;
virtual void SetHasImage() override;
virtual bool NotificationsDeferred() const override;
virtual void SetNotificationsDeferred(bool aDeferNotifications) override;
// We don't allow multipart images to block onload, so we override these
// methods to do nothing.
virtual void BlockOnload() override { }
virtual void UnblockOnload() override { }
protected:
virtual ~MultipartImage();
private:
friend class ImageFactory;
friend class NextPartObserver;
explicit MultipartImage(Image* aFirstPart);
void Init();
void FinishTransition();
RefPtr<ProgressTracker> mTracker;
RefPtr<NextPartObserver> mNextPartObserver;
RefPtr<Image> mNextPart;
bool mDeferNotifications : 1;
};
} // namespace image
} // namespace mozilla
#endif // mozilla_image_MultipartImage_h

62
image/Orientation.h Normal file
View file

@ -0,0 +1,62 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* 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/. */
#ifndef mozilla_image_Orientation_h
#define mozilla_image_Orientation_h
#include <stdint.h>
namespace mozilla {
namespace image {
enum class Angle : uint8_t {
D0,
D90,
D180,
D270
};
enum class Flip : uint8_t {
Unflipped,
Horizontal
};
/**
* A struct that describes an image's orientation as a rotation optionally
* followed by a reflection. This may be used to be indicate an image's inherent
* orientation or a desired orientation for the image.
*/
struct Orientation
{
explicit Orientation(Angle aRotation = Angle::D0,
Flip mFlip = Flip::Unflipped)
: rotation(aRotation)
, flip(mFlip)
{ }
bool IsIdentity() const {
return (rotation == Angle::D0) && (flip == Flip::Unflipped);
}
bool SwapsWidthAndHeight() const {
return (rotation == Angle::D90) || (rotation == Angle::D270);
}
bool operator==(const Orientation& aOther) const {
return (rotation == aOther.rotation) && (flip == aOther.flip);
}
bool operator!=(const Orientation& aOther) const {
return !(*this == aOther);
}
Angle rotation;
Flip flip;
};
} // namespace image
} // namespace mozilla
#endif // mozilla_image_Orientation_h

354
image/OrientedImage.cpp Normal file
View file

@ -0,0 +1,354 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* 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/. */
#include "OrientedImage.h"
#include <algorithm>
#include "gfx2DGlue.h"
#include "gfxDrawable.h"
#include "gfxPlatform.h"
#include "gfxUtils.h"
#include "ImageRegion.h"
#include "SVGImageContext.h"
using std::swap;
namespace mozilla {
using namespace gfx;
using layers::LayerManager;
using layers::ImageContainer;
namespace image {
NS_IMPL_ISUPPORTS_INHERITED0(OrientedImage, ImageWrapper)
NS_IMETHODIMP
OrientedImage::GetWidth(int32_t* aWidth)
{
if (mOrientation.SwapsWidthAndHeight()) {
return InnerImage()->GetHeight(aWidth);
} else {
return InnerImage()->GetWidth(aWidth);
}
}
NS_IMETHODIMP
OrientedImage::GetHeight(int32_t* aHeight)
{
if (mOrientation.SwapsWidthAndHeight()) {
return InnerImage()->GetWidth(aHeight);
} else {
return InnerImage()->GetHeight(aHeight);
}
}
NS_IMETHODIMP
OrientedImage::GetIntrinsicSize(nsSize* aSize)
{
nsresult rv = InnerImage()->GetIntrinsicSize(aSize);
if (mOrientation.SwapsWidthAndHeight()) {
swap(aSize->width, aSize->height);
}
return rv;
}
NS_IMETHODIMP
OrientedImage::GetIntrinsicRatio(nsSize* aRatio)
{
nsresult rv = InnerImage()->GetIntrinsicRatio(aRatio);
if (mOrientation.SwapsWidthAndHeight()) {
swap(aRatio->width, aRatio->height);
}
return rv;
}
NS_IMETHODIMP_(already_AddRefed<SourceSurface>)
OrientedImage::GetFrame(uint32_t aWhichFrame,
uint32_t aFlags)
{
nsresult rv;
if (mOrientation.IsIdentity()) {
return InnerImage()->GetFrame(aWhichFrame, aFlags);
}
// Get the underlying dimensions.
IntSize size;
rv = InnerImage()->GetWidth(&size.width);
NS_ENSURE_SUCCESS(rv, nullptr);
rv = InnerImage()->GetHeight(&size.height);
NS_ENSURE_SUCCESS(rv, nullptr);
// Determine an appropriate format for the surface.
gfx::SurfaceFormat surfaceFormat;
if (InnerImage()->WillDrawOpaqueNow()) {
surfaceFormat = gfx::SurfaceFormat::B8G8R8X8;
} else {
surfaceFormat = gfx::SurfaceFormat::B8G8R8A8;
}
// Create a surface to draw into.
RefPtr<DrawTarget> target =
gfxPlatform::GetPlatform()->
CreateOffscreenContentDrawTarget(size, surfaceFormat);
if (!target || !target->IsValid()) {
NS_ERROR("Could not create a DrawTarget");
return nullptr;
}
// Create our drawable.
RefPtr<SourceSurface> innerSurface =
InnerImage()->GetFrame(aWhichFrame, aFlags);
NS_ENSURE_TRUE(innerSurface, nullptr);
RefPtr<gfxDrawable> drawable =
new gfxSurfaceDrawable(innerSurface, size);
// Draw.
RefPtr<gfxContext> ctx = gfxContext::CreateOrNull(target);
MOZ_ASSERT(ctx); // already checked the draw target above
ctx->Multiply(OrientationMatrix(size));
gfxUtils::DrawPixelSnapped(ctx, drawable, size, ImageRegion::Create(size),
surfaceFormat, SamplingFilter::LINEAR);
return target->Snapshot();
}
NS_IMETHODIMP_(already_AddRefed<SourceSurface>)
OrientedImage::GetFrameAtSize(const IntSize& aSize,
uint32_t aWhichFrame,
uint32_t aFlags)
{
// XXX(seth): It'd be nice to support downscale-during-decode for this case,
// but right now we just fall back to the intrinsic size.
return GetFrame(aWhichFrame, aFlags);
}
NS_IMETHODIMP_(bool)
OrientedImage::IsImageContainerAvailable(LayerManager* aManager, uint32_t aFlags)
{
if (mOrientation.IsIdentity()) {
return InnerImage()->IsImageContainerAvailable(aManager, aFlags);
}
return false;
}
NS_IMETHODIMP_(already_AddRefed<ImageContainer>)
OrientedImage::GetImageContainer(LayerManager* aManager, uint32_t aFlags)
{
// XXX(seth): We currently don't have a way of orienting the result of
// GetImageContainer. We work around this by always returning null, but if it
// ever turns out that OrientedImage is widely used on codepaths that can
// actually benefit from GetImageContainer, it would be a good idea to fix
// that method for performance reasons.
if (mOrientation.IsIdentity()) {
return InnerImage()->GetImageContainer(aManager, aFlags);
}
return nullptr;
}
struct MatrixBuilder
{
explicit MatrixBuilder(bool aInvert) : mInvert(aInvert) { }
gfxMatrix Build() { return mMatrix; }
void Scale(gfxFloat aX, gfxFloat aY)
{
if (mInvert) {
mMatrix *= gfxMatrix::Scaling(1.0 / aX, 1.0 / aY);
} else {
mMatrix.Scale(aX, aY);
}
}
void Rotate(gfxFloat aPhi)
{
if (mInvert) {
mMatrix *= gfxMatrix::Rotation(-aPhi);
} else {
mMatrix.Rotate(aPhi);
}
}
void Translate(gfxPoint aDelta)
{
if (mInvert) {
mMatrix *= gfxMatrix::Translation(-aDelta);
} else {
mMatrix.Translate(aDelta);
}
}
private:
gfxMatrix mMatrix;
bool mInvert;
};
/*
* OrientationMatrix() computes a matrix that applies the rotation and
* reflection specified by mOrientation, or that matrix's inverse if aInvert is
* true.
*
* @param aSize The scaled size of the inner image. (When outside code specifies
* the scaled size, as with imgIContainer::Draw and its aSize
* parameter, it's necessary to swap the width and height if
* mOrientation.SwapsWidthAndHeight() is true.)
* @param aInvert If true, compute the inverse of the orientation matrix. Prefer
* this approach to OrientationMatrix(..).Invert(), because it's
* more numerically accurate.
*/
gfxMatrix
OrientedImage::OrientationMatrix(const nsIntSize& aSize,
bool aInvert /* = false */)
{
MatrixBuilder builder(aInvert);
// Apply reflection, if present. (This logically happens second, but we
// apply it first because these transformations are all premultiplied.) A
// translation is necessary to place the image back in the first quadrant.
switch (mOrientation.flip) {
case Flip::Unflipped:
break;
case Flip::Horizontal:
if (mOrientation.SwapsWidthAndHeight()) {
builder.Translate(gfxPoint(aSize.height, 0));
} else {
builder.Translate(gfxPoint(aSize.width, 0));
}
builder.Scale(-1.0, 1.0);
break;
default:
MOZ_ASSERT(false, "Invalid flip value");
}
// Apply rotation, if present. Again, a translation is used to place the
// image back in the first quadrant.
switch (mOrientation.rotation) {
case Angle::D0:
break;
case Angle::D90:
builder.Translate(gfxPoint(aSize.height, 0));
builder.Rotate(-1.5 * M_PI);
break;
case Angle::D180:
builder.Translate(gfxPoint(aSize.width, aSize.height));
builder.Rotate(-1.0 * M_PI);
break;
case Angle::D270:
builder.Translate(gfxPoint(0, aSize.width));
builder.Rotate(-0.5 * M_PI);
break;
default:
MOZ_ASSERT(false, "Invalid rotation value");
}
return builder.Build();
}
NS_IMETHODIMP_(DrawResult)
OrientedImage::Draw(gfxContext* aContext,
const nsIntSize& aSize,
const ImageRegion& aRegion,
uint32_t aWhichFrame,
SamplingFilter aSamplingFilter,
const Maybe<SVGImageContext>& aSVGContext,
uint32_t aFlags)
{
if (mOrientation.IsIdentity()) {
return InnerImage()->Draw(aContext, aSize, aRegion,
aWhichFrame, aSamplingFilter,
aSVGContext, aFlags);
}
// Update the image size to match the image's coordinate system. (This could
// be done using TransformBounds but since it's only a size a swap is enough.)
nsIntSize size(aSize);
if (mOrientation.SwapsWidthAndHeight()) {
swap(size.width, size.height);
}
// Update the matrix so that we transform the image into the orientation
// expected by the caller before drawing.
gfxMatrix matrix(OrientationMatrix(size));
gfxContextMatrixAutoSaveRestore saveMatrix(aContext);
aContext->Multiply(matrix);
// The region is already in the orientation expected by the caller, but we
// need it to be in the image's coordinate system, so we transform it using
// the inverse of the orientation matrix.
gfxMatrix inverseMatrix(OrientationMatrix(size, /* aInvert = */ true));
ImageRegion region(aRegion);
region.TransformBoundsBy(inverseMatrix);
auto orientViewport = [&](const SVGImageContext& aOldContext) {
CSSIntSize viewportSize(aOldContext.GetViewportSize());
if (mOrientation.SwapsWidthAndHeight()) {
swap(viewportSize.width, viewportSize.height);
}
return SVGImageContext(viewportSize,
aOldContext.GetPreserveAspectRatio());
};
return InnerImage()->Draw(aContext, size, region, aWhichFrame, aSamplingFilter,
aSVGContext.map(orientViewport), aFlags);
}
nsIntSize
OrientedImage::OptimalImageSizeForDest(const gfxSize& aDest,
uint32_t aWhichFrame,
SamplingFilter aSamplingFilter,
uint32_t aFlags)
{
if (!mOrientation.SwapsWidthAndHeight()) {
return InnerImage()->OptimalImageSizeForDest(aDest, aWhichFrame,
aSamplingFilter, aFlags);
}
// Swap the size for the calculation, then swap it back for the caller.
gfxSize destSize(aDest.height, aDest.width);
nsIntSize innerImageSize(InnerImage()->OptimalImageSizeForDest(destSize,
aWhichFrame,
aSamplingFilter,
aFlags));
return nsIntSize(innerImageSize.height, innerImageSize.width);
}
NS_IMETHODIMP_(nsIntRect)
OrientedImage::GetImageSpaceInvalidationRect(const nsIntRect& aRect)
{
nsIntRect rect(InnerImage()->GetImageSpaceInvalidationRect(aRect));
if (mOrientation.IsIdentity()) {
return rect;
}
nsIntSize innerSize;
nsresult rv = InnerImage()->GetWidth(&innerSize.width);
rv = NS_FAILED(rv) ? rv : InnerImage()->GetHeight(&innerSize.height);
if (NS_FAILED(rv)) {
// Fall back to identity if the width and height aren't available.
return rect;
}
// Transform the invalidation rect into the correct orientation.
gfxMatrix matrix(OrientationMatrix(innerSize));
gfxRect invalidRect(matrix.TransformBounds(gfxRect(rect.x, rect.y,
rect.width, rect.height)));
return IntRect::RoundOut(invalidRect.x, invalidRect.y,
invalidRect.width, invalidRect.height);
}
} // namespace image
} // namespace mozilla

79
image/OrientedImage.h Normal file
View file

@ -0,0 +1,79 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* 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/. */
#ifndef mozilla_image_OrientedImage_h
#define mozilla_image_OrientedImage_h
#include "ImageWrapper.h"
#include "mozilla/gfx/2D.h"
#include "mozilla/RefPtr.h"
#include "Orientation.h"
namespace mozilla {
namespace image {
/**
* An Image wrapper that rotates and/or flips an image according to a specified
* Orientation.
*
* XXX(seth): There a known (performance, not correctness) issue with
* GetImageContainer. See the comments for that method for more information.
*/
class OrientedImage : public ImageWrapper
{
typedef gfx::SourceSurface SourceSurface;
public:
NS_DECL_ISUPPORTS_INHERITED
NS_IMETHOD GetWidth(int32_t* aWidth) override;
NS_IMETHOD GetHeight(int32_t* aHeight) override;
NS_IMETHOD GetIntrinsicSize(nsSize* aSize) override;
NS_IMETHOD GetIntrinsicRatio(nsSize* aRatio) override;
NS_IMETHOD_(already_AddRefed<SourceSurface>)
GetFrame(uint32_t aWhichFrame, uint32_t aFlags) override;
NS_IMETHOD_(already_AddRefed<SourceSurface>)
GetFrameAtSize(const gfx::IntSize& aSize,
uint32_t aWhichFrame,
uint32_t aFlags) override;
NS_IMETHOD_(bool) IsImageContainerAvailable(layers::LayerManager* aManager,
uint32_t aFlags) override;
NS_IMETHOD_(already_AddRefed<layers::ImageContainer>)
GetImageContainer(layers::LayerManager* aManager,
uint32_t aFlags) override;
NS_IMETHOD_(DrawResult) Draw(gfxContext* aContext,
const nsIntSize& aSize,
const ImageRegion& aRegion,
uint32_t aWhichFrame,
gfx::SamplingFilter aSamplingFilter,
const Maybe<SVGImageContext>& aSVGContext,
uint32_t aFlags) override;
NS_IMETHOD_(nsIntRect) GetImageSpaceInvalidationRect(
const nsIntRect& aRect) override;
nsIntSize OptimalImageSizeForDest(const gfxSize& aDest,
uint32_t aWhichFrame,
gfx::SamplingFilter aSamplingFilter,
uint32_t aFlags) override;
protected:
OrientedImage(Image* aImage, Orientation aOrientation)
: ImageWrapper(aImage)
, mOrientation(aOrientation)
{ }
virtual ~OrientedImage() { }
gfxMatrix OrientationMatrix(const nsIntSize& aSize, bool aInvert = false);
private:
Orientation mOrientation;
friend class ImageOps;
};
} // namespace image
} // namespace mozilla
#endif // mozilla_image_OrientedImage_h

44
image/PlaybackType.h Normal file
View file

@ -0,0 +1,44 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* 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/. */
#ifndef mozilla_image_PlaybackType_h
#define mozilla_image_PlaybackType_h
#include "imgIContainer.h"
namespace mozilla {
namespace image {
/**
* PlaybackType identifies a surface cache entry as either a static surface or
* an animation. Note that a specific cache entry is one or the other, but
* images may be associated with both types of cache entries, since in some
* circumstances we may want to treat an animated image as if it were static.
*/
enum class PlaybackType : uint8_t
{
eStatic, // Calls to DrawableRef() will always return the same surface.
eAnimated // An animation; calls to DrawableRef() may return different
// surfaces at different times.
};
/**
* Given an imgIContainer FRAME_* value, returns the corresponding PlaybackType
* for use in surface cache lookups.
*/
inline PlaybackType
ToPlaybackType(uint32_t aWhichFrame)
{
MOZ_ASSERT(aWhichFrame == imgIContainer::FRAME_FIRST ||
aWhichFrame == imgIContainer::FRAME_CURRENT);
return aWhichFrame == imgIContainer::FRAME_CURRENT
? PlaybackType::eAnimated
: PlaybackType::eStatic;
}
} // namespace image
} // namespace mozilla
#endif // mozilla_image_PlaybackType_h

570
image/ProgressTracker.cpp Normal file
View file

@ -0,0 +1,570 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
*
* 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/. */
#include "ImageLogging.h"
#include "ProgressTracker.h"
#include "imgIContainer.h"
#include "imgINotificationObserver.h"
#include "imgIRequest.h"
#include "Image.h"
#include "nsNetUtil.h"
#include "nsIObserverService.h"
#include "mozilla/Assertions.h"
#include "mozilla/Services.h"
using mozilla::WeakPtr;
namespace mozilla {
namespace image {
static void
CheckProgressConsistency(Progress aOldProgress, Progress aNewProgress)
{
// Check preconditions for every progress bit.
if (aNewProgress & FLAG_SIZE_AVAILABLE) {
// No preconditions.
}
if (aNewProgress & FLAG_DECODE_COMPLETE) {
MOZ_ASSERT(aNewProgress & FLAG_SIZE_AVAILABLE);
MOZ_ASSERT(aNewProgress & (FLAG_FRAME_COMPLETE | FLAG_HAS_ERROR));
}
if (aNewProgress & FLAG_FRAME_COMPLETE) {
MOZ_ASSERT(aNewProgress & FLAG_SIZE_AVAILABLE);
}
if (aNewProgress & FLAG_LOAD_COMPLETE) {
MOZ_ASSERT(aNewProgress & (FLAG_SIZE_AVAILABLE | FLAG_HAS_ERROR));
}
if (aNewProgress & FLAG_ONLOAD_BLOCKED) {
// No preconditions.
}
if (aNewProgress & FLAG_ONLOAD_UNBLOCKED) {
MOZ_ASSERT(aNewProgress & FLAG_ONLOAD_BLOCKED);
MOZ_ASSERT(aNewProgress & (FLAG_SIZE_AVAILABLE | FLAG_HAS_ERROR));
}
if (aNewProgress & FLAG_IS_ANIMATED) {
// No preconditions; like FLAG_HAS_TRANSPARENCY, we should normally never
// discover this *after* FLAG_SIZE_AVAILABLE, but unfortunately some corrupt
// GIFs may fool us.
}
if (aNewProgress & FLAG_HAS_TRANSPARENCY) {
// XXX We'd like to assert that transparency is only set during metadata
// decode but we don't have any way to assert that until bug 1254892 is fixed.
}
if (aNewProgress & FLAG_LAST_PART_COMPLETE) {
MOZ_ASSERT(aNewProgress & FLAG_LOAD_COMPLETE);
}
if (aNewProgress & FLAG_HAS_ERROR) {
// No preconditions.
}
}
void
ProgressTracker::SetImage(Image* aImage)
{
MutexAutoLock lock(mImageMutex);
MOZ_ASSERT(aImage, "Setting null image");
MOZ_ASSERT(!mImage, "Setting image when we already have one");
mImage = aImage;
}
void
ProgressTracker::ResetImage()
{
MutexAutoLock lock(mImageMutex);
MOZ_ASSERT(mImage, "Resetting image when it's already null!");
mImage = nullptr;
}
uint32_t
ProgressTracker::GetImageStatus() const
{
uint32_t status = imgIRequest::STATUS_NONE;
// Translate our current state to a set of imgIRequest::STATE_* flags.
if (mProgress & FLAG_SIZE_AVAILABLE) {
status |= imgIRequest::STATUS_SIZE_AVAILABLE;
}
if (mProgress & FLAG_DECODE_COMPLETE) {
status |= imgIRequest::STATUS_DECODE_COMPLETE;
}
if (mProgress & FLAG_FRAME_COMPLETE) {
status |= imgIRequest::STATUS_FRAME_COMPLETE;
}
if (mProgress & FLAG_LOAD_COMPLETE) {
status |= imgIRequest::STATUS_LOAD_COMPLETE;
}
if (mProgress & FLAG_IS_ANIMATED) {
status |= imgIRequest::STATUS_IS_ANIMATED;
}
if (mProgress & FLAG_HAS_TRANSPARENCY) {
status |= imgIRequest::STATUS_HAS_TRANSPARENCY;
}
if (mProgress & FLAG_HAS_ERROR) {
status |= imgIRequest::STATUS_ERROR;
}
return status;
}
// A helper class to allow us to call SyncNotify asynchronously.
class AsyncNotifyRunnable : public Runnable
{
public:
AsyncNotifyRunnable(ProgressTracker* aTracker,
IProgressObserver* aObserver)
: mTracker(aTracker)
{
MOZ_ASSERT(NS_IsMainThread(), "Should be created on the main thread");
MOZ_ASSERT(aTracker, "aTracker should not be null");
MOZ_ASSERT(aObserver, "aObserver should not be null");
mObservers.AppendElement(aObserver);
}
NS_IMETHOD Run() override
{
MOZ_ASSERT(NS_IsMainThread(), "Should be running on the main thread");
MOZ_ASSERT(mTracker, "mTracker should not be null");
for (uint32_t i = 0; i < mObservers.Length(); ++i) {
mObservers[i]->SetNotificationsDeferred(false);
mTracker->SyncNotify(mObservers[i]);
}
mTracker->mRunnable = nullptr;
return NS_OK;
}
void AddObserver(IProgressObserver* aObserver)
{
mObservers.AppendElement(aObserver);
}
void RemoveObserver(IProgressObserver* aObserver)
{
mObservers.RemoveElement(aObserver);
}
private:
friend class ProgressTracker;
RefPtr<ProgressTracker> mTracker;
nsTArray<RefPtr<IProgressObserver>> mObservers;
};
void
ProgressTracker::Notify(IProgressObserver* aObserver)
{
MOZ_ASSERT(NS_IsMainThread());
if (MOZ_LOG_TEST(gImgLog, LogLevel::Debug)) {
RefPtr<Image> image = GetImage();
if (image && image->GetURI()) {
RefPtr<ImageURL> uri(image->GetURI());
nsAutoCString spec;
uri->GetSpec(spec);
LOG_FUNC_WITH_PARAM(gImgLog,
"ProgressTracker::Notify async", "uri", spec.get());
} else {
LOG_FUNC_WITH_PARAM(gImgLog,
"ProgressTracker::Notify async", "uri", "<unknown>");
}
}
aObserver->SetNotificationsDeferred(true);
// If we have an existing runnable that we can use, we just append this
// observer to its list of observers to be notified. This ensures we don't
// unnecessarily delay onload.
AsyncNotifyRunnable* runnable =
static_cast<AsyncNotifyRunnable*>(mRunnable.get());
if (runnable) {
runnable->AddObserver(aObserver);
} else {
mRunnable = new AsyncNotifyRunnable(this, aObserver);
NS_DispatchToCurrentThread(mRunnable);
}
}
// A helper class to allow us to call SyncNotify asynchronously for a given,
// fixed, state.
class AsyncNotifyCurrentStateRunnable : public Runnable
{
public:
AsyncNotifyCurrentStateRunnable(ProgressTracker* aProgressTracker,
IProgressObserver* aObserver)
: mProgressTracker(aProgressTracker)
, mObserver(aObserver)
{
MOZ_ASSERT(NS_IsMainThread(), "Should be created on the main thread");
MOZ_ASSERT(mProgressTracker, "mProgressTracker should not be null");
MOZ_ASSERT(mObserver, "mObserver should not be null");
mImage = mProgressTracker->GetImage();
}
NS_IMETHOD Run() override
{
MOZ_ASSERT(NS_IsMainThread(), "Should be running on the main thread");
mObserver->SetNotificationsDeferred(false);
mProgressTracker->SyncNotify(mObserver);
return NS_OK;
}
private:
RefPtr<ProgressTracker> mProgressTracker;
RefPtr<IProgressObserver> mObserver;
// We have to hold on to a reference to the tracker's image, just in case
// it goes away while we're in the event queue.
RefPtr<Image> mImage;
};
void
ProgressTracker::NotifyCurrentState(IProgressObserver* aObserver)
{
MOZ_ASSERT(NS_IsMainThread());
if (MOZ_LOG_TEST(gImgLog, LogLevel::Debug)) {
RefPtr<Image> image = GetImage();
nsAutoCString spec;
if (image && image->GetURI()) {
image->GetURI()->GetSpec(spec);
}
LOG_FUNC_WITH_PARAM(gImgLog,
"ProgressTracker::NotifyCurrentState", "uri", spec.get());
}
aObserver->SetNotificationsDeferred(true);
nsCOMPtr<nsIRunnable> ev = new AsyncNotifyCurrentStateRunnable(this,
aObserver);
NS_DispatchToCurrentThread(ev);
}
/**
* ImageObserverNotifier is a helper type that abstracts over the difference
* between sending notifications to all of the observers in an ObserverTable,
* and sending them to a single observer. This allows the same notification code
* to be used for both cases.
*/
template <typename T> struct ImageObserverNotifier;
template <>
struct MOZ_STACK_CLASS ImageObserverNotifier<const ObserverTable*>
{
explicit ImageObserverNotifier(const ObserverTable* aObservers,
bool aIgnoreDeferral = false)
: mObservers(aObservers)
, mIgnoreDeferral(aIgnoreDeferral)
{ }
template <typename Lambda>
void operator()(Lambda aFunc)
{
for (auto iter = mObservers->ConstIter(); !iter.Done(); iter.Next()) {
RefPtr<IProgressObserver> observer = iter.Data().get();
if (observer &&
(mIgnoreDeferral || !observer->NotificationsDeferred())) {
aFunc(observer);
}
}
}
private:
const ObserverTable* mObservers;
const bool mIgnoreDeferral;
};
template <>
struct MOZ_STACK_CLASS ImageObserverNotifier<IProgressObserver*>
{
explicit ImageObserverNotifier(IProgressObserver* aObserver)
: mObserver(aObserver)
{ }
template <typename Lambda>
void operator()(Lambda aFunc)
{
if (mObserver && !mObserver->NotificationsDeferred()) {
aFunc(mObserver);
}
}
private:
IProgressObserver* mObserver;
};
template <typename T> void
SyncNotifyInternal(const T& aObservers,
bool aHasImage,
Progress aProgress,
const nsIntRect& aDirtyRect)
{
MOZ_ASSERT(NS_IsMainThread());
typedef imgINotificationObserver I;
ImageObserverNotifier<T> notify(aObservers);
if (aProgress & FLAG_SIZE_AVAILABLE) {
notify([](IProgressObserver* aObs) { aObs->Notify(I::SIZE_AVAILABLE); });
}
if (aProgress & FLAG_ONLOAD_BLOCKED) {
notify([](IProgressObserver* aObs) { aObs->BlockOnload(); });
}
if (aHasImage) {
// OnFrameUpdate
// If there's any content in this frame at all (always true for
// vector images, true for raster images that have decoded at
// least one frame) then send OnFrameUpdate.
if (!aDirtyRect.IsEmpty()) {
notify([&](IProgressObserver* aObs) {
aObs->Notify(I::FRAME_UPDATE, &aDirtyRect);
});
}
if (aProgress & FLAG_FRAME_COMPLETE) {
notify([](IProgressObserver* aObs) { aObs->Notify(I::FRAME_COMPLETE); });
}
if (aProgress & FLAG_HAS_TRANSPARENCY) {
notify([](IProgressObserver* aObs) { aObs->Notify(I::HAS_TRANSPARENCY); });
}
if (aProgress & FLAG_IS_ANIMATED) {
notify([](IProgressObserver* aObs) { aObs->Notify(I::IS_ANIMATED); });
}
}
// Send UnblockOnload before OnStopDecode and OnStopRequest. This allows
// observers that can fire events when they receive those notifications to do
// so then, instead of being forced to wait for UnblockOnload.
if (aProgress & FLAG_ONLOAD_UNBLOCKED) {
notify([](IProgressObserver* aObs) { aObs->UnblockOnload(); });
}
if (aProgress & FLAG_DECODE_COMPLETE) {
MOZ_ASSERT(aHasImage, "Stopped decoding without ever having an image?");
notify([](IProgressObserver* aObs) { aObs->Notify(I::DECODE_COMPLETE); });
}
if (aProgress & FLAG_LOAD_COMPLETE) {
notify([=](IProgressObserver* aObs) {
aObs->OnLoadComplete(aProgress & FLAG_LAST_PART_COMPLETE);
});
}
}
void
ProgressTracker::SyncNotifyProgress(Progress aProgress,
const nsIntRect& aInvalidRect
/* = nsIntRect() */)
{
MOZ_ASSERT(NS_IsMainThread(), "Use mObservers on main thread only");
// Don't unblock onload if we're not blocked.
Progress progress = Difference(aProgress);
if (!((mProgress | progress) & FLAG_ONLOAD_BLOCKED)) {
progress &= ~FLAG_ONLOAD_UNBLOCKED;
}
CheckProgressConsistency(mProgress, mProgress | progress);
// XXX(seth): Hack to work around the fact that some observers have bugs and
// need to get onload blocking notifications multiple times. We should fix
// those observers and remove this.
if ((aProgress & FLAG_DECODE_COMPLETE) &&
(mProgress & FLAG_ONLOAD_BLOCKED) &&
(mProgress & FLAG_ONLOAD_UNBLOCKED)) {
progress |= FLAG_ONLOAD_BLOCKED | FLAG_ONLOAD_UNBLOCKED;
}
// Apply the changes.
mProgress |= progress;
// Send notifications.
mObservers.Read([&](const ObserverTable* aTable) {
SyncNotifyInternal(aTable, HasImage(), progress, aInvalidRect);
});
if (progress & FLAG_HAS_ERROR) {
FireFailureNotification();
}
}
void
ProgressTracker::SyncNotify(IProgressObserver* aObserver)
{
MOZ_ASSERT(NS_IsMainThread());
RefPtr<Image> image = GetImage();
nsAutoCString spec;
if (image && image->GetURI()) {
image->GetURI()->GetSpec(spec);
}
LOG_SCOPE_WITH_PARAM(gImgLog,
"ProgressTracker::SyncNotify", "uri", spec.get());
nsIntRect rect;
if (image) {
if (NS_FAILED(image->GetWidth(&rect.width)) ||
NS_FAILED(image->GetHeight(&rect.height))) {
// Either the image has no intrinsic size, or it has an error.
rect = GetMaxSizedIntRect();
}
}
SyncNotifyInternal(aObserver, !!image, mProgress, rect);
}
void
ProgressTracker::EmulateRequestFinished(IProgressObserver* aObserver)
{
MOZ_ASSERT(NS_IsMainThread(),
"SyncNotifyState and mObservers are not threadsafe");
RefPtr<IProgressObserver> kungFuDeathGrip(aObserver);
if (mProgress & FLAG_ONLOAD_BLOCKED && !(mProgress & FLAG_ONLOAD_UNBLOCKED)) {
aObserver->UnblockOnload();
}
if (!(mProgress & FLAG_LOAD_COMPLETE)) {
aObserver->OnLoadComplete(true);
}
}
void
ProgressTracker::AddObserver(IProgressObserver* aObserver)
{
MOZ_ASSERT(NS_IsMainThread());
RefPtr<IProgressObserver> observer = aObserver;
mObservers.Write([=](ObserverTable* aTable) {
MOZ_ASSERT(!aTable->Get(observer, nullptr),
"Adding duplicate entry for image observer");
WeakPtr<IProgressObserver> weakPtr = observer.get();
aTable->Put(observer, weakPtr);
});
}
bool
ProgressTracker::RemoveObserver(IProgressObserver* aObserver)
{
MOZ_ASSERT(NS_IsMainThread());
RefPtr<IProgressObserver> observer = aObserver;
// Remove the observer from the list.
bool removed = mObservers.Write([=](ObserverTable* aTable) {
bool removed = aTable->Get(observer, nullptr);
aTable->Remove(observer);
return removed;
});
// Observers can get confused if they don't get all the proper teardown
// notifications. Part ways on good terms.
if (removed && !aObserver->NotificationsDeferred()) {
EmulateRequestFinished(aObserver);
}
// Make sure we don't give callbacks to an observer that isn't interested in
// them any more.
AsyncNotifyRunnable* runnable =
static_cast<AsyncNotifyRunnable*>(mRunnable.get());
if (aObserver->NotificationsDeferred() && runnable) {
runnable->RemoveObserver(aObserver);
aObserver->SetNotificationsDeferred(false);
}
return removed;
}
uint32_t
ProgressTracker::ObserverCount() const
{
MOZ_ASSERT(NS_IsMainThread());
return mObservers.Read([](const ObserverTable* aTable) {
return aTable->Count();
});
}
void
ProgressTracker::OnUnlockedDraw()
{
MOZ_ASSERT(NS_IsMainThread());
mObservers.Read([](const ObserverTable* aTable) {
ImageObserverNotifier<const ObserverTable*> notify(aTable);
notify([](IProgressObserver* aObs) {
aObs->Notify(imgINotificationObserver::UNLOCKED_DRAW);
});
});
}
void
ProgressTracker::ResetForNewRequest()
{
MOZ_ASSERT(NS_IsMainThread());
mProgress = NoProgress;
}
void
ProgressTracker::OnDiscard()
{
MOZ_ASSERT(NS_IsMainThread());
mObservers.Read([](const ObserverTable* aTable) {
ImageObserverNotifier<const ObserverTable*> notify(aTable);
notify([](IProgressObserver* aObs) {
aObs->Notify(imgINotificationObserver::DISCARD);
});
});
}
void
ProgressTracker::OnImageAvailable()
{
MOZ_ASSERT(NS_IsMainThread());
// Notify any imgRequestProxys that are observing us that we have an Image.
mObservers.Read([](const ObserverTable* aTable) {
ImageObserverNotifier<const ObserverTable*>
notify(aTable, /* aIgnoreDeferral = */ true);
notify([](IProgressObserver* aObs) {
aObs->SetHasImage();
});
});
}
void
ProgressTracker::FireFailureNotification()
{
MOZ_ASSERT(NS_IsMainThread());
// Some kind of problem has happened with image decoding.
// Report the URI to net:failed-to-process-uri-conent observers.
RefPtr<Image> image = GetImage();
if (image) {
// Should be on main thread, so ok to create a new nsIURI.
nsCOMPtr<nsIURI> uri;
{
RefPtr<ImageURL> threadsafeUriData = image->GetURI();
uri = threadsafeUriData ? threadsafeUriData->ToIURI() : nullptr;
}
if (uri) {
nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
if (os) {
os->NotifyObservers(uri, "net:failed-to-process-uri-content", nullptr);
}
}
}
}
} // namespace image
} // namespace mozilla

234
image/ProgressTracker.h Normal file
View file

@ -0,0 +1,234 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
*
* 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/. */
#ifndef mozilla_image_ProgressTracker_h
#define mozilla_image_ProgressTracker_h
#include "CopyOnWrite.h"
#include "mozilla/Mutex.h"
#include "mozilla/RefPtr.h"
#include "mozilla/WeakPtr.h"
#include "nsDataHashtable.h"
#include "nsCOMPtr.h"
#include "nsTObserverArray.h"
#include "nsThreadUtils.h"
#include "nsRect.h"
#include "IProgressObserver.h"
class nsIRunnable;
namespace mozilla {
namespace image {
class AsyncNotifyRunnable;
class AsyncNotifyCurrentStateRunnable;
class Image;
/**
* Image progress bitflags.
*
* See CheckProgressConsistency() for the invariants we enforce about the
* ordering dependencies betweeen these flags.
*/
enum {
FLAG_SIZE_AVAILABLE = 1u << 0, // STATUS_SIZE_AVAILABLE
FLAG_DECODE_COMPLETE = 1u << 1, // STATUS_DECODE_COMPLETE
FLAG_FRAME_COMPLETE = 1u << 2, // STATUS_FRAME_COMPLETE
FLAG_LOAD_COMPLETE = 1u << 3, // STATUS_LOAD_COMPLETE
FLAG_ONLOAD_BLOCKED = 1u << 4,
FLAG_ONLOAD_UNBLOCKED = 1u << 5,
FLAG_IS_ANIMATED = 1u << 6, // STATUS_IS_ANIMATED
FLAG_HAS_TRANSPARENCY = 1u << 7, // STATUS_HAS_TRANSPARENCY
FLAG_LAST_PART_COMPLETE = 1u << 8,
FLAG_HAS_ERROR = 1u << 9 // STATUS_ERROR
};
typedef uint32_t Progress;
const uint32_t NoProgress = 0;
inline Progress LoadCompleteProgress(bool aLastPart,
bool aError,
nsresult aStatus)
{
Progress progress = FLAG_LOAD_COMPLETE;
if (aLastPart) {
progress |= FLAG_LAST_PART_COMPLETE;
}
if (NS_FAILED(aStatus) || aError) {
progress |= FLAG_HAS_ERROR;
}
return progress;
}
/**
* ProgressTracker stores its observers in an ObserverTable, which is a hash
* table mapping raw pointers to WeakPtr's to the same objects. This sounds like
* unnecessary duplication of information, but it's necessary for stable hash
* values since WeakPtr's lose the knowledge of which object they used to point
* to when that object is destroyed.
*
* ObserverTable subclasses nsDataHashtable to add reference counting support
* and a copy constructor, both of which are needed for use with CopyOnWrite<T>.
*/
class ObserverTable
: public nsDataHashtable<nsPtrHashKey<IProgressObserver>,
WeakPtr<IProgressObserver>>
{
public:
NS_INLINE_DECL_REFCOUNTING(ObserverTable);
ObserverTable() = default;
ObserverTable(const ObserverTable& aOther)
{
NS_WARNING("Forced to copy ObserverTable due to nested notifications");
for (auto iter = aOther.ConstIter(); !iter.Done(); iter.Next()) {
this->Put(iter.Key(), iter.Data());
}
}
private:
~ObserverTable() { }
};
/**
* ProgressTracker is a class that records an Image's progress through the
* loading and decoding process, and makes it possible to send notifications to
* IProgressObservers, both synchronously and asynchronously.
*
* When a new observer needs to be notified of the current progress of an image,
* call the Notify() method on this class with the relevant observer as its
* argument, and the notifications will be replayed to the observer
* asynchronously.
*/
class ProgressTracker : public mozilla::SupportsWeakPtr<ProgressTracker>
{
virtual ~ProgressTracker() { }
public:
MOZ_DECLARE_WEAKREFERENCE_TYPENAME(ProgressTracker)
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(ProgressTracker)
ProgressTracker()
: mImageMutex("ProgressTracker::mImage")
, mImage(nullptr)
, mObservers(new ObserverTable)
, mProgress(NoProgress)
{ }
bool HasImage() const { MutexAutoLock lock(mImageMutex); return mImage; }
already_AddRefed<Image> GetImage() const
{
MutexAutoLock lock(mImageMutex);
RefPtr<Image> image = mImage;
return image.forget();
}
// Get the current image status (as in imgIRequest).
uint32_t GetImageStatus() const;
// Get the current Progress.
Progress GetProgress() const { return mProgress; }
// Schedule an asynchronous "replaying" of all the notifications that would
// have to happen to put us in the current state.
// We will also take note of any notifications that happen between the time
// Notify() is called and when we call SyncNotify on |aObserver|, and replay
// them as well.
// Should be called on the main thread only, since observers and GetURI are
// not threadsafe.
void Notify(IProgressObserver* aObserver);
// Schedule an asynchronous "replaying" of all the notifications that would
// have to happen to put us in the state we are in right now.
// Unlike Notify(), does *not* take into account future notifications.
// This is only useful if you do not have an imgRequest, e.g., if you are a
// static request returned from imgIRequest::GetStaticRequest().
// Should be called on the main thread only, since observers and GetURI are
// not threadsafe.
void NotifyCurrentState(IProgressObserver* aObserver);
// "Replay" all of the notifications that would have to happen to put us in
// the state we're currently in.
// Only use this if you're already servicing an asynchronous call (e.g.
// OnStartRequest).
// Should be called on the main thread only, since observers and GetURI are
// not threadsafe.
void SyncNotify(IProgressObserver* aObserver);
// Get this ProgressTracker ready for a new request. This resets all the
// state that doesn't persist between requests.
void ResetForNewRequest();
// Stateless notifications. These are dispatched and immediately forgotten
// about. All of these notifications are main thread only.
void OnDiscard();
void OnUnlockedDraw();
void OnImageAvailable();
// Compute the difference between this our progress and aProgress. This allows
// callers to predict whether SyncNotifyProgress will send any notifications.
Progress Difference(Progress aProgress) const
{
return ~mProgress & aProgress;
}
// Update our state to incorporate the changes in aProgress and synchronously
// notify our observers.
//
// Because this may result in recursive notifications, no decoding locks may
// be held. Called on the main thread only.
void SyncNotifyProgress(Progress aProgress,
const nsIntRect& aInvalidRect = nsIntRect());
// We manage a set of observers that are using an image and thus concerned
// with its loading progress. Weak pointers.
void AddObserver(IProgressObserver* aObserver);
bool RemoveObserver(IProgressObserver* aObserver);
uint32_t ObserverCount() const;
// Resets our weak reference to our image. Image subclasses should call this
// in their destructor.
void ResetImage();
private:
friend class AsyncNotifyRunnable;
friend class AsyncNotifyCurrentStateRunnable;
friend class ImageFactory;
ProgressTracker(const ProgressTracker& aOther) = delete;
// Sets our weak reference to our image. Only ImageFactory should call this.
void SetImage(Image* aImage);
// Send some notifications that would be necessary to make |aObserver| believe
// the request is finished downloading and decoding. We only send
// FLAG_LOAD_COMPLETE and FLAG_ONLOAD_UNBLOCKED, and only if necessary.
void EmulateRequestFinished(IProgressObserver* aObserver);
// Main thread only because it deals with the observer service.
void FireFailureNotification();
// The runnable, if any, that we've scheduled to deliver async notifications.
nsCOMPtr<nsIRunnable> mRunnable;
// mImage is a weak ref; it should be set to null when the image goes out of
// scope. mImageMutex protects mImage.
mutable Mutex mImageMutex;
Image* mImage;
// Hashtable of observers attached to the image. Each observer represents a
// consumer using the image. Main thread only.
CopyOnWrite<ObserverTable> mObservers;
Progress mProgress;
};
} // namespace image
} // namespace mozilla
#endif // mozilla_image_ProgressTracker_h

1724
image/RasterImage.cpp Normal file

File diff suppressed because it is too large Load diff

514
image/RasterImage.h Normal file
View file

@ -0,0 +1,514 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
*
* 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/. */
/** @file
* This file declares the RasterImage class, which
* handles static and animated rasterized images.
*
* @author Stuart Parmenter <pavlov@netscape.com>
* @author Chris Saari <saari@netscape.com>
* @author Arron Mogge <paper@animecity.nu>
* @author Andrew Smith <asmith15@learn.senecac.on.ca>
*/
#ifndef mozilla_image_RasterImage_h
#define mozilla_image_RasterImage_h
#include "Image.h"
#include "nsCOMPtr.h"
#include "imgIContainer.h"
#include "nsIProperties.h"
#include "nsTArray.h"
#include "LookupResult.h"
#include "nsThreadUtils.h"
#include "DecodePool.h"
#include "DecoderFactory.h"
#include "FrameAnimator.h"
#include "ImageMetadata.h"
#include "ISurfaceProvider.h"
#include "Orientation.h"
#include "nsIObserver.h"
#include "mozilla/Attributes.h"
#include "mozilla/Maybe.h"
#include "mozilla/MemoryReporting.h"
#include "mozilla/NotNull.h"
#include "mozilla/Pair.h"
#include "mozilla/TimeStamp.h"
#include "mozilla/WeakPtr.h"
#include "mozilla/UniquePtr.h"
#include "ImageContainer.h"
#include "PlaybackType.h"
#ifdef DEBUG
#include "imgIContainerDebug.h"
#endif
class nsIInputStream;
class nsIRequest;
#define NS_RASTERIMAGE_CID \
{ /* 376ff2c1-9bf6-418a-b143-3340c00112f7 */ \
0x376ff2c1, \
0x9bf6, \
0x418a, \
{0xb1, 0x43, 0x33, 0x40, 0xc0, 0x01, 0x12, 0xf7} \
}
/**
* Handles static and animated image containers.
*
*
* @par A Quick Walk Through
* The decoder initializes this class and calls AppendFrame() to add a frame.
* Once RasterImage detects more than one frame, it starts the animation
* with StartAnimation(). Note that the invalidation events for RasterImage are
* generated automatically using nsRefreshDriver.
*
* @par
* StartAnimation() initializes the animation helper object and sets the time
* the first frame was displayed to the current clock time.
*
* @par
* When the refresh driver corresponding to the imgIContainer that this image is
* a part of notifies the RasterImage that it's time to invalidate,
* RequestRefresh() is called with a given TimeStamp to advance to. As long as
* the timeout of the given frame (the frame's "delay") plus the time that frame
* was first displayed is less than or equal to the TimeStamp given,
* RequestRefresh() calls AdvanceFrame().
*
* @par
* AdvanceFrame() is responsible for advancing a single frame of the animation.
* It can return true, meaning that the frame advanced, or false, meaning that
* the frame failed to advance (usually because the next frame hasn't been
* decoded yet). It is also responsible for performing the final animation stop
* procedure if the final frame of a non-looping animation is reached.
*
* @par
* Each frame can have a different method of removing itself. These are
* listed as imgIContainer::cDispose... constants. Notify() calls
* DoComposite() to handle any special frame destruction.
*
* @par
* The basic path through DoComposite() is:
* 1) Calculate Area that needs updating, which is at least the area of
* aNextFrame.
* 2) Dispose of previous frame.
* 3) Draw new image onto compositingFrame.
* See comments in DoComposite() for more information and optimizations.
*
* @par
* The rest of the RasterImage specific functions are used by DoComposite to
* destroy the old frame and build the new one.
*
* @note
* <li> "Mask", "Alpha", and "Alpha Level" are interchangeable phrases in
* respects to RasterImage.
*
* @par
* <li> GIFs never have more than a 1 bit alpha.
* <li> APNGs may have a full alpha channel.
*
* @par
* <li> Background color specified in GIF is ignored by web browsers.
*
* @par
* <li> If Frame 3 wants to dispose by restoring previous, what it wants is to
* restore the composition up to and including Frame 2, as well as Frame 2s
* disposal. So, in the middle of DoComposite when composing Frame 3, right
* after destroying Frame 2's area, we copy compositingFrame to
* prevCompositingFrame. When DoComposite gets called to do Frame 4, we
* copy prevCompositingFrame back, and then draw Frame 4 on top.
*
* @par
* The mAnim structure has members only needed for animated images, so
* it's not allocated until the second frame is added.
*/
namespace mozilla {
namespace layers {
class ImageContainer;
class Image;
} // namespace layers
namespace image {
class Decoder;
struct DecoderFinalStatus;
struct DecoderTelemetry;
class ImageMetadata;
class SourceBuffer;
class RasterImage final : public ImageResource
, public nsIProperties
, public SupportsWeakPtr<RasterImage>
#ifdef DEBUG
, public imgIContainerDebug
#endif
{
// (no public constructor - use ImageFactory)
virtual ~RasterImage();
public:
MOZ_DECLARE_WEAKREFERENCE_TYPENAME(RasterImage)
NS_DECL_THREADSAFE_ISUPPORTS
NS_DECL_NSIPROPERTIES
NS_DECL_IMGICONTAINER
#ifdef DEBUG
NS_DECL_IMGICONTAINERDEBUG
#endif
virtual nsresult StartAnimation() override;
virtual nsresult StopAnimation() override;
// Methods inherited from Image
virtual void OnSurfaceDiscarded() override;
virtual size_t SizeOfSourceWithComputedFallback(MallocSizeOf aMallocSizeOf)
const override;
virtual void CollectSizeOfSurfaces(nsTArray<SurfaceMemoryCounter>& aCounters,
MallocSizeOf aMallocSizeOf) const override;
/* Triggers discarding. */
void Discard();
//////////////////////////////////////////////////////////////////////////////
// Decoder callbacks.
//////////////////////////////////////////////////////////////////////////////
/**
* Sends the provided progress notifications to ProgressTracker.
*
* Main-thread only.
*
* @param aProgress The progress notifications to send.
* @param aInvalidRect An invalidation rect to send.
* @param aFrameCount If Some(), an updated count of the number of frames of
* animation the decoder has finished decoding so far. This
* is a lower bound for the total number of animation
* frames this image has.
* @param aDecoderFlags The decoder flags used by the decoder that generated
* these notifications, or DefaultDecoderFlags() if the
* notifications don't come from a decoder.
* @param aSurfaceFlags The surface flags used by the decoder that generated
* these notifications, or DefaultSurfaceFlags() if the
* notifications don't come from a decoder.
*/
void NotifyProgress(Progress aProgress,
const gfx::IntRect& aInvalidRect = nsIntRect(),
const Maybe<uint32_t>& aFrameCount = Nothing(),
DecoderFlags aDecoderFlags = DefaultDecoderFlags(),
SurfaceFlags aSurfaceFlags = DefaultSurfaceFlags());
/**
* Records decoding results, sends out any final notifications, updates the
* state of this image, and records telemetry.
*
* Main-thread only.
*
* @param aStatus Final status information about the decoder. (Whether it
* encountered an error, etc.)
* @param aMetadata Metadata about this image that the decoder gathered.
* @param aTelemetry Telemetry data about the decoder.
* @param aProgress Any final progress notifications to send.
* @param aInvalidRect Any final invalidation rect to send.
* @param aFrameCount If Some(), a final updated count of the number of frames
* of animation the decoder has finished decoding so far.
* This is a lower bound for the total number of animation
* frames this image has.
* @param aDecoderFlags The decoder flags used by the decoder.
* @param aSurfaceFlags The surface flags used by the decoder.
*/
void NotifyDecodeComplete(const DecoderFinalStatus& aStatus,
const ImageMetadata& aMetadata,
const DecoderTelemetry& aTelemetry,
Progress aProgress,
const gfx::IntRect& aInvalidRect,
const Maybe<uint32_t>& aFrameCount,
DecoderFlags aDecoderFlags,
SurfaceFlags aSurfaceFlags);
// Helper method for NotifyDecodeComplete.
void ReportDecoderError();
//////////////////////////////////////////////////////////////////////////////
// Network callbacks.
//////////////////////////////////////////////////////////////////////////////
virtual nsresult OnImageDataAvailable(nsIRequest* aRequest,
nsISupports* aContext,
nsIInputStream* aInStr,
uint64_t aSourceOffset,
uint32_t aCount) override;
virtual nsresult OnImageDataComplete(nsIRequest* aRequest,
nsISupports* aContext,
nsresult aStatus,
bool aLastPart) override;
void NotifyForLoadEvent(Progress aProgress);
/**
* A hint of the number of bytes of source data that the image contains. If
* called early on, this can help reduce copying and reallocations by
* appropriately preallocating the source data buffer.
*
* We take this approach rather than having the source data management code do
* something more complicated (like chunklisting) because HTTP is by far the
* dominant source of images, and the Content-Length header is quite reliable.
* Thus, pre-allocation simplifies code and reduces the total number of
* allocations.
*/
nsresult SetSourceSizeHint(uint32_t aSizeHint);
/* Provide a hint for the requested dimension of the resulting image. */
void SetRequestedSampleSize(int requestedSampleSize) {
mRequestedSampleSize = requestedSampleSize;
}
nsCString GetURIString() {
nsCString spec;
if (GetURI()) {
GetURI()->GetSpec(spec);
}
return spec;
}
private:
nsresult Init(const char* aMimeType, uint32_t aFlags);
/**
* Tries to retrieve a surface for this image with size @aSize, surface flags
* matching @aFlags, and a playback type of @aPlaybackType.
*
* If @aFlags specifies FLAG_SYNC_DECODE and we already have all the image
* data, we'll attempt a sync decode if no matching surface is found. If
* FLAG_SYNC_DECODE was not specified and no matching surface was found, we'll
* kick off an async decode so that the surface is (hopefully) available next
* time it's requested.
*
* @return a drawable surface, which may be empty if the requested surface
* could not be found.
*/
DrawableSurface LookupFrame(const gfx::IntSize& aSize,
uint32_t aFlags,
PlaybackType aPlaybackType);
/// Helper method for LookupFrame().
LookupResult LookupFrameInternal(const gfx::IntSize& aSize,
uint32_t aFlags,
PlaybackType aPlaybackType);
DrawResult DrawInternal(DrawableSurface&& aFrameRef,
gfxContext* aContext,
const nsIntSize& aSize,
const ImageRegion& aRegion,
gfx::SamplingFilter aSamplingFilter,
uint32_t aFlags);
Pair<DrawResult, RefPtr<gfx::SourceSurface>>
GetFrameInternal(const gfx::IntSize& aSize,
uint32_t aWhichFrame,
uint32_t aFlags);
Pair<DrawResult, RefPtr<layers::Image>>
GetCurrentImage(layers::ImageContainer* aContainer, uint32_t aFlags);
void UpdateImageContainer();
// We would like to just check if we have a zero lock count, but we can't do
// that for animated images because in EnsureAnimExists we lock the image and
// never unlock so that animated images always have their lock count >= 1. In
// that case we use our animation consumers count as a proxy for lock count.
bool IsUnlocked() {
return (mLockCount == 0 || (mAnimationState && mAnimationConsumers == 0));
}
//////////////////////////////////////////////////////////////////////////////
// Decoding.
//////////////////////////////////////////////////////////////////////////////
/**
* Creates and runs a decoder, either synchronously or asynchronously
* according to @aFlags. Decodes at the provided target size @aSize, using
* decode flags @aFlags. Performs a single-frame decode of this image unless
* we know the image is animated *and* @aPlaybackType is
* PlaybackType::eAnimated.
*
* It's an error to call Decode() before this image's intrinsic size is
* available. A metadata decode must successfully complete first.
*/
NS_IMETHOD Decode(const gfx::IntSize& aSize,
uint32_t aFlags,
PlaybackType aPlaybackType);
/**
* Creates and runs a metadata decoder, either synchronously or
* asynchronously according to @aFlags.
*/
NS_IMETHOD DecodeMetadata(uint32_t aFlags);
/**
* Sets the size, inherent orientation, animation metadata, and other
* information about the image gathered during decoding.
*
* This function may be called multiple times, but will throw an error if
* subsequent calls do not match the first.
*
* @param aMetadata The metadata to set on this image.
* @param aFromMetadataDecode True if this metadata came from a metadata
* decode; false if it came from a full decode.
* @return |true| unless a catastrophic failure was discovered. If |false| is
* returned, it indicates that the image is corrupt in a way that requires all
* surfaces to be discarded to recover.
*/
bool SetMetadata(const ImageMetadata& aMetadata, bool aFromMetadataDecode);
/**
* In catastrophic circumstances like a GPU driver crash, the contents of our
* frames may become invalid. If the information we gathered during the
* metadata decode proves to be wrong due to image corruption, the frames we
* have may violate this class's invariants. Either way, we need to
* immediately discard the invalid frames and redecode so that callers don't
* perceive that we've entered an invalid state.
*
* RecoverFromInvalidFrames discards all existing frames and redecodes using
* the provided @aSize and @aFlags.
*/
void RecoverFromInvalidFrames(const nsIntSize& aSize, uint32_t aFlags);
private: // data
nsIntSize mSize;
Orientation mOrientation;
/// If this has a value, we're waiting for SetSize() to send the load event.
Maybe<Progress> mLoadProgress;
nsCOMPtr<nsIProperties> mProperties;
/// If this image is animated, a FrameAnimator which manages its animation.
UniquePtr<FrameAnimator> mFrameAnimator;
/// Animation timeline and other state for animation images.
Maybe<AnimationState> mAnimationState;
// Image locking.
uint32_t mLockCount;
// The type of decoder this image needs. Computed from the MIME type in Init().
DecoderType mDecoderType;
// How many times we've decoded this image.
// This is currently only used for statistics
int32_t mDecodeCount;
// A hint for image decoder that directly scale the image to smaller buffer
int mRequestedSampleSize;
// A weak pointer to our ImageContainer, which stays alive only as long as
// the layer system needs it.
WeakPtr<layers::ImageContainer> mImageContainer;
layers::ImageContainer::ProducerID mImageProducerID;
layers::ImageContainer::FrameID mLastFrameID;
// If mImageContainer is non-null, this contains the DrawResult we obtained
// the last time we updated it.
DrawResult mLastImageContainerDrawResult;
#ifdef DEBUG
uint32_t mFramesNotified;
#endif
// The source data for this image.
NotNull<RefPtr<SourceBuffer>> mSourceBuffer;
// Boolean flags (clustered together to conserve space):
bool mHasSize:1; // Has SetSize() been called?
bool mTransient:1; // Is the image short-lived?
bool mSyncLoad:1; // Are we loading synchronously?
bool mDiscardable:1; // Is container discardable?
bool mHasSourceData:1; // Do we have source data?
bool mHasBeenDecoded:1; // Decoded at least once?
// Whether we're waiting to start animation. If we get a StartAnimation() call
// but we don't yet have more than one frame, mPendingAnimation is set so that
// we know to start animation later if/when we have more frames.
bool mPendingAnimation:1;
// Whether the animation can stop, due to running out
// of frames, or no more owning request
bool mAnimationFinished:1;
// Whether, once we are done doing a metadata decode, we should immediately
// kick off a full decode.
bool mWantFullDecode:1;
TimeStamp mDrawStartTime;
//////////////////////////////////////////////////////////////////////////////
// Scaling.
//////////////////////////////////////////////////////////////////////////////
// Determines whether we can downscale during decode with the given
// parameters.
bool CanDownscaleDuringDecode(const nsIntSize& aSize, uint32_t aFlags);
// Error handling.
void DoError();
class HandleErrorWorker : public Runnable
{
public:
/**
* Called from decoder threads when DoError() is called, since errors can't
* be handled safely off-main-thread. Dispatches an event which reinvokes
* DoError on the main thread if there isn't one already pending.
*/
static void DispatchIfNeeded(RasterImage* aImage);
NS_IMETHOD Run();
private:
explicit HandleErrorWorker(RasterImage* aImage);
RefPtr<RasterImage> mImage;
};
// Helpers
bool CanDiscard();
bool IsOpaque();
protected:
explicit RasterImage(ImageURL* aURI = nullptr);
bool ShouldAnimate() override;
friend class ImageFactory;
};
inline NS_IMETHODIMP
RasterImage::GetAnimationMode(uint16_t* aAnimationMode) {
return GetAnimationModeInternal(aAnimationMode);
}
} // namespace image
} // namespace mozilla
/**
* Casting RasterImage to nsISupports is ambiguous. This method handles that.
*/
inline nsISupports*
ToSupports(mozilla::image::RasterImage* p)
{
return NS_ISUPPORTS_CAST(mozilla::image::ImageResource*, p);
}
#endif /* mozilla_image_RasterImage_h */

View file

@ -0,0 +1,460 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* 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/. */
#include "SVGDocumentWrapper.h"
#include "mozilla/dom/DocumentTimeline.h"
#include "mozilla/dom/Element.h"
#include "nsICategoryManager.h"
#include "nsIChannel.h"
#include "nsIContentViewer.h"
#include "nsIDocument.h"
#include "nsIDocumentLoaderFactory.h"
#include "nsIDOMSVGLength.h"
#include "nsIHttpChannel.h"
#include "nsIObserverService.h"
#include "nsIParser.h"
#include "nsIPresShell.h"
#include "nsIRequest.h"
#include "nsIStreamListener.h"
#include "nsIXMLContentSink.h"
#include "nsNetCID.h"
#include "nsComponentManagerUtils.h"
#include "nsSMILAnimationController.h"
#include "nsServiceManagerUtils.h"
#include "mozilla/dom/SVGSVGElement.h"
#include "nsSVGEffects.h"
#include "mozilla/dom/SVGAnimatedLength.h"
#include "nsMimeTypes.h"
#include "DOMSVGLength.h"
#include "nsDocument.h"
#include "mozilla/dom/ImageTracker.h"
// undef the GetCurrentTime macro defined in WinBase.h from the MS Platform SDK
#undef GetCurrentTime
namespace mozilla {
using namespace dom;
using namespace gfx;
namespace image {
NS_IMPL_ISUPPORTS(SVGDocumentWrapper,
nsIStreamListener,
nsIRequestObserver,
nsIObserver,
nsISupportsWeakReference)
SVGDocumentWrapper::SVGDocumentWrapper()
: mIgnoreInvalidation(false),
mRegisteredForXPCOMShutdown(false)
{ }
SVGDocumentWrapper::~SVGDocumentWrapper()
{
DestroyViewer();
if (mRegisteredForXPCOMShutdown) {
UnregisterForXPCOMShutdown();
}
}
void
SVGDocumentWrapper::DestroyViewer()
{
if (mViewer) {
mViewer->GetDocument()->OnPageHide(false, nullptr);
mViewer->Close(nullptr);
mViewer->Destroy();
mViewer = nullptr;
}
}
nsIFrame*
SVGDocumentWrapper::GetRootLayoutFrame()
{
Element* rootElem = GetRootSVGElem();
return rootElem ? rootElem->GetPrimaryFrame() : nullptr;
}
void
SVGDocumentWrapper::UpdateViewportBounds(const nsIntSize& aViewportSize)
{
MOZ_ASSERT(!mIgnoreInvalidation, "shouldn't be reentrant");
mIgnoreInvalidation = true;
nsIntRect currentBounds;
mViewer->GetBounds(currentBounds);
// If the bounds have changed, we need to do a layout flush.
if (currentBounds.Size() != aViewportSize) {
mViewer->SetBounds(IntRect(IntPoint(0, 0), aViewportSize));
FlushLayout();
}
mIgnoreInvalidation = false;
}
void
SVGDocumentWrapper::FlushImageTransformInvalidation()
{
MOZ_ASSERT(!mIgnoreInvalidation, "shouldn't be reentrant");
SVGSVGElement* svgElem = GetRootSVGElem();
if (!svgElem) {
return;
}
mIgnoreInvalidation = true;
svgElem->FlushImageTransformInvalidation();
FlushLayout();
mIgnoreInvalidation = false;
}
bool
SVGDocumentWrapper::IsAnimated()
{
// Can be called for animated images during shutdown, after we've
// already Observe()'d XPCOM shutdown and cleared out our mViewer pointer.
if (!mViewer) {
return false;
}
nsIDocument* doc = mViewer->GetDocument();
if (!doc) {
return false;
}
if (doc->Timeline()->HasAnimations()) {
// CSS animations (technically HasAnimations() also checks for CSS
// transitions and Web animations but since SVG-as-an-image doesn't run
// script they will never run in the document that we wrap).
return true;
}
if (doc->HasAnimationController() &&
doc->GetAnimationController()->HasRegisteredAnimations()) {
// SMIL animations
return true;
}
return false;
}
void
SVGDocumentWrapper::StartAnimation()
{
// Can be called for animated images during shutdown, after we've
// already Observe()'d XPCOM shutdown and cleared out our mViewer pointer.
if (!mViewer) {
return;
}
nsIDocument* doc = mViewer->GetDocument();
if (doc) {
nsSMILAnimationController* controller = doc->GetAnimationController();
if (controller) {
controller->Resume(nsSMILTimeContainer::PAUSE_IMAGE);
}
doc->ImageTracker()->SetAnimatingState(true);
}
}
void
SVGDocumentWrapper::StopAnimation()
{
// Can be called for animated images during shutdown, after we've
// already Observe()'d XPCOM shutdown and cleared out our mViewer pointer.
if (!mViewer) {
return;
}
nsIDocument* doc = mViewer->GetDocument();
if (doc) {
nsSMILAnimationController* controller = doc->GetAnimationController();
if (controller) {
controller->Pause(nsSMILTimeContainer::PAUSE_IMAGE);
}
doc->ImageTracker()->SetAnimatingState(false);
}
}
void
SVGDocumentWrapper::ResetAnimation()
{
SVGSVGElement* svgElem = GetRootSVGElem();
if (!svgElem) {
return;
}
svgElem->SetCurrentTime(0.0f);
}
float
SVGDocumentWrapper::GetCurrentTime()
{
SVGSVGElement* svgElem = GetRootSVGElem();
return svgElem ? svgElem->GetCurrentTime()
: 0.0f;
}
void
SVGDocumentWrapper::SetCurrentTime(float aTime)
{
SVGSVGElement* svgElem = GetRootSVGElem();
if (svgElem && svgElem->GetCurrentTime() != aTime) {
svgElem->SetCurrentTime(aTime);
}
}
void
SVGDocumentWrapper::TickRefreshDriver()
{
nsCOMPtr<nsIPresShell> presShell;
mViewer->GetPresShell(getter_AddRefs(presShell));
if (presShell) {
nsPresContext* presContext = presShell->GetPresContext();
if (presContext) {
presContext->RefreshDriver()->DoTick();
}
}
}
/** nsIStreamListener methods **/
NS_IMETHODIMP
SVGDocumentWrapper::OnDataAvailable(nsIRequest* aRequest, nsISupports* ctxt,
nsIInputStream* inStr,
uint64_t sourceOffset,
uint32_t count)
{
return mListener->OnDataAvailable(aRequest, ctxt, inStr,
sourceOffset, count);
}
/** nsIRequestObserver methods **/
NS_IMETHODIMP
SVGDocumentWrapper::OnStartRequest(nsIRequest* aRequest, nsISupports* ctxt)
{
nsresult rv = SetupViewer(aRequest,
getter_AddRefs(mViewer),
getter_AddRefs(mLoadGroup));
if (NS_SUCCEEDED(rv) &&
NS_SUCCEEDED(mListener->OnStartRequest(aRequest, nullptr))) {
mViewer->GetDocument()->SetIsBeingUsedAsImage();
StopAnimation(); // otherwise animations start automatically in helper doc
rv = mViewer->Init(nullptr, nsIntRect(0, 0, 0, 0));
if (NS_SUCCEEDED(rv)) {
rv = mViewer->Open(nullptr, nullptr);
}
}
return rv;
}
NS_IMETHODIMP
SVGDocumentWrapper::OnStopRequest(nsIRequest* aRequest, nsISupports* ctxt,
nsresult status)
{
if (mListener) {
mListener->OnStopRequest(aRequest, ctxt, status);
mListener = nullptr;
}
return NS_OK;
}
/** nsIObserver Methods **/
NS_IMETHODIMP
SVGDocumentWrapper::Observe(nsISupports* aSubject,
const char* aTopic,
const char16_t* aData)
{
if (!strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) {
// Sever ties from rendering observers to helper-doc's root SVG node
SVGSVGElement* svgElem = GetRootSVGElem();
if (svgElem) {
nsSVGEffects::RemoveAllRenderingObservers(svgElem);
}
// Clean up at XPCOM shutdown time.
DestroyViewer();
if (mListener) {
mListener = nullptr;
}
if (mLoadGroup) {
mLoadGroup = nullptr;
}
// Turn off "registered" flag, or else we'll try to unregister when we die.
// (No need for that now, and the try would fail anyway -- it's too late.)
mRegisteredForXPCOMShutdown = false;
} else {
NS_ERROR("Unexpected observer topic.");
}
return NS_OK;
}
/** Private helper methods **/
// This method is largely cribbed from
// nsExternalResourceMap::PendingLoad::SetupViewer.
nsresult
SVGDocumentWrapper::SetupViewer(nsIRequest* aRequest,
nsIContentViewer** aViewer,
nsILoadGroup** aLoadGroup)
{
nsCOMPtr<nsIChannel> chan(do_QueryInterface(aRequest));
NS_ENSURE_TRUE(chan, NS_ERROR_UNEXPECTED);
// Check for HTTP error page
nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(aRequest));
if (httpChannel) {
bool requestSucceeded;
if (NS_FAILED(httpChannel->GetRequestSucceeded(&requestSucceeded)) ||
!requestSucceeded) {
return NS_ERROR_FAILURE;
}
}
// Give this document its own loadgroup
nsCOMPtr<nsILoadGroup> loadGroup;
chan->GetLoadGroup(getter_AddRefs(loadGroup));
nsCOMPtr<nsILoadGroup> newLoadGroup =
do_CreateInstance(NS_LOADGROUP_CONTRACTID);
NS_ENSURE_TRUE(newLoadGroup, NS_ERROR_OUT_OF_MEMORY);
newLoadGroup->SetLoadGroup(loadGroup);
nsCOMPtr<nsICategoryManager> catMan =
do_GetService(NS_CATEGORYMANAGER_CONTRACTID);
NS_ENSURE_TRUE(catMan, NS_ERROR_NOT_AVAILABLE);
nsXPIDLCString contractId;
nsresult rv = catMan->GetCategoryEntry("Gecko-Content-Viewers", IMAGE_SVG_XML,
getter_Copies(contractId));
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsIDocumentLoaderFactory> docLoaderFactory =
do_GetService(contractId);
NS_ENSURE_TRUE(docLoaderFactory, NS_ERROR_NOT_AVAILABLE);
nsCOMPtr<nsIContentViewer> viewer;
nsCOMPtr<nsIStreamListener> listener;
rv = docLoaderFactory->CreateInstance("external-resource", chan,
newLoadGroup,
NS_LITERAL_CSTRING(IMAGE_SVG_XML),
nullptr, nullptr,
getter_AddRefs(listener),
getter_AddRefs(viewer));
NS_ENSURE_SUCCESS(rv, rv);
NS_ENSURE_TRUE(viewer, NS_ERROR_UNEXPECTED);
// Create a navigation time object and pass it to the SVG document through
// the viewer.
// The timeline(DocumentTimeline, used in CSS animation) of this SVG
// document needs this navigation timing object for time computation, such
// as to calculate current time stamp based on the start time of navigation
// time object.
//
// For a root document, DocShell would do these sort of things
// automatically. Since there is no DocShell for this wrapped SVG document,
// we must set it up manually.
RefPtr<nsDOMNavigationTiming> timing = new nsDOMNavigationTiming();
timing->NotifyNavigationStart(nsDOMNavigationTiming::DocShellState::eInactive);
viewer->SetNavigationTiming(timing);
nsCOMPtr<nsIParser> parser = do_QueryInterface(listener);
NS_ENSURE_TRUE(parser, NS_ERROR_UNEXPECTED);
// XML-only, because this is for SVG content
nsCOMPtr<nsIContentSink> sink = parser->GetContentSink();
NS_ENSURE_TRUE(sink, NS_ERROR_UNEXPECTED);
listener.swap(mListener);
viewer.forget(aViewer);
newLoadGroup.forget(aLoadGroup);
RegisterForXPCOMShutdown();
return NS_OK;
}
void
SVGDocumentWrapper::RegisterForXPCOMShutdown()
{
MOZ_ASSERT(!mRegisteredForXPCOMShutdown,
"re-registering for XPCOM shutdown");
// Listen for xpcom-shutdown so that we can drop references to our
// helper-document at that point. (Otherwise, we won't get cleaned up
// until imgLoader::Shutdown, which can happen after the JAR service
// and RDF service have been unregistered.)
nsresult rv;
nsCOMPtr<nsIObserverService> obsSvc = do_GetService(OBSERVER_SVC_CID, &rv);
if (NS_FAILED(rv) ||
NS_FAILED(obsSvc->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID,
true))) {
NS_WARNING("Failed to register as observer of XPCOM shutdown");
} else {
mRegisteredForXPCOMShutdown = true;
}
}
void
SVGDocumentWrapper::UnregisterForXPCOMShutdown()
{
MOZ_ASSERT(mRegisteredForXPCOMShutdown,
"unregistering for XPCOM shutdown w/out being registered");
nsresult rv;
nsCOMPtr<nsIObserverService> obsSvc = do_GetService(OBSERVER_SVC_CID, &rv);
if (NS_FAILED(rv) ||
NS_FAILED(obsSvc->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID))) {
NS_WARNING("Failed to unregister as observer of XPCOM shutdown");
} else {
mRegisteredForXPCOMShutdown = false;
}
}
void
SVGDocumentWrapper::FlushLayout()
{
nsCOMPtr<nsIPresShell> presShell;
mViewer->GetPresShell(getter_AddRefs(presShell));
if (presShell) {
presShell->FlushPendingNotifications(Flush_Layout);
}
}
nsIDocument*
SVGDocumentWrapper::GetDocument()
{
if (!mViewer) {
return nullptr;
}
return mViewer->GetDocument(); // May be nullptr.
}
SVGSVGElement*
SVGDocumentWrapper::GetRootSVGElem()
{
if (!mViewer) {
return nullptr; // Can happen during destruction
}
nsIDocument* doc = mViewer->GetDocument();
if (!doc) {
return nullptr; // Can happen during destruction
}
Element* rootElem = mViewer->GetDocument()->GetRootElement();
if (!rootElem || !rootElem->IsSVGElement(nsGkAtoms::svg)) {
return nullptr;
}
return static_cast<SVGSVGElement*>(rootElem);
}
} // namespace image
} // namespace mozilla

151
image/SVGDocumentWrapper.h Normal file
View file

@ -0,0 +1,151 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* 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/. */
/* This class wraps an SVG document, for use by VectorImage objects. */
#ifndef mozilla_image_SVGDocumentWrapper_h
#define mozilla_image_SVGDocumentWrapper_h
#include "mozilla/Attributes.h"
#include "nsCOMPtr.h"
#include "nsIStreamListener.h"
#include "nsIObserver.h"
#include "nsIContentViewer.h"
#include "nsWeakReference.h"
#include "nsSize.h"
class nsIPresShell;
class nsIRequest;
class nsILoadGroup;
class nsIFrame;
#define OBSERVER_SVC_CID "@mozilla.org/observer-service;1"
// undef the GetCurrentTime macro defined in WinBase.h from the MS Platform SDK
#undef GetCurrentTime
namespace mozilla {
namespace dom {
class SVGSVGElement;
} // namespace dom
namespace image {
class SVGDocumentWrapper final : public nsIStreamListener,
public nsIObserver,
nsSupportsWeakReference
{
public:
SVGDocumentWrapper();
NS_DECL_ISUPPORTS
NS_DECL_NSISTREAMLISTENER
NS_DECL_NSIREQUESTOBSERVER
NS_DECL_NSIOBSERVER
enum Dimension {
eWidth,
eHeight
};
/**
* Returns the wrapped document, or nullptr on failure. (No AddRef.)
*/
nsIDocument* GetDocument();
/**
* Returns the root <svg> element for the wrapped document, or nullptr on
* failure.
*/
mozilla::dom::SVGSVGElement* GetRootSVGElem();
/**
* Returns the root nsIFrame* for the wrapped document, or nullptr on failure.
*
* @return the root nsIFrame* for the wrapped document, or nullptr on failure.
*/
nsIFrame* GetRootLayoutFrame();
/**
* Returns (by reference) the nsIPresShell for the wrapped document.
*
* @param[out] aPresShell On success, this will be populated with a pointer
* to the wrapped document's nsIPresShell.
*
* @return NS_OK on success, or an error code on failure.
*/
inline nsresult GetPresShell(nsIPresShell** aPresShell)
{ return mViewer->GetPresShell(aPresShell); }
/**
* Modifier to update the viewport dimensions of the wrapped document. This
* method performs a synchronous "Flush_Layout" on the wrapped document,
* since a viewport-change affects layout.
*
* @param aViewportSize The new viewport dimensions.
*/
void UpdateViewportBounds(const nsIntSize& aViewportSize);
/**
* If an SVG image's helper document has a pending notification for an
* override on the root node's "preserveAspectRatio" attribute, then this
* method will flush that notification so that the image can paint correctly.
* (First, though, it sets the mIgnoreInvalidation flag so that we won't
* notify the image's observers and trigger unwanted repaint-requests.)
*/
void FlushImageTransformInvalidation();
/**
* Returns a bool indicating whether the document has any SMIL animations.
*
* @return true if the document has any SMIL animations. Else, false.
*/
bool IsAnimated();
/**
* Indicates whether we should currently ignore rendering invalidations sent
* from the wrapped SVG doc.
*
* @return true if we should ignore invalidations sent from this SVG doc.
*/
bool ShouldIgnoreInvalidation() { return mIgnoreInvalidation; }
/**
* Methods to control animation.
*/
void StartAnimation();
void StopAnimation();
void ResetAnimation();
float GetCurrentTime();
void SetCurrentTime(float aTime);
void TickRefreshDriver();
/**
* Force a layout flush of the underlying SVG document.
*/
void FlushLayout();
private:
~SVGDocumentWrapper();
nsresult SetupViewer(nsIRequest* aRequest,
nsIContentViewer** aViewer,
nsILoadGroup** aLoadGroup);
void DestroyViewer();
void RegisterForXPCOMShutdown();
void UnregisterForXPCOMShutdown();
nsCOMPtr<nsIContentViewer> mViewer;
nsCOMPtr<nsILoadGroup> mLoadGroup;
nsCOMPtr<nsIStreamListener> mListener;
bool mIgnoreInvalidation;
bool mRegisteredForXPCOMShutdown;
};
} // namespace image
} // namespace mozilla
#endif // mozilla_image_SVGDocumentWrapper_h

View file

@ -0,0 +1,62 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
*
* 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/. */
#include "ScriptedNotificationObserver.h"
#include "imgIScriptedNotificationObserver.h"
#include "nsCycleCollectionParticipant.h"
namespace mozilla {
namespace image {
NS_IMPL_CYCLE_COLLECTION(ScriptedNotificationObserver, mInner)
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ScriptedNotificationObserver)
NS_INTERFACE_MAP_ENTRY(imgINotificationObserver)
NS_INTERFACE_MAP_ENTRY(nsISupports)
NS_INTERFACE_MAP_END
NS_IMPL_CYCLE_COLLECTING_ADDREF(ScriptedNotificationObserver)
NS_IMPL_CYCLE_COLLECTING_RELEASE(ScriptedNotificationObserver)
ScriptedNotificationObserver::ScriptedNotificationObserver(
imgIScriptedNotificationObserver* aInner)
: mInner(aInner)
{ }
NS_IMETHODIMP
ScriptedNotificationObserver::Notify(imgIRequest* aRequest,
int32_t aType,
const nsIntRect* /*aUnused*/)
{
if (aType == imgINotificationObserver::SIZE_AVAILABLE) {
return mInner->SizeAvailable(aRequest);
}
if (aType == imgINotificationObserver::FRAME_UPDATE) {
return mInner->FrameUpdate(aRequest);
}
if (aType == imgINotificationObserver::FRAME_COMPLETE) {
return mInner->FrameComplete(aRequest);
}
if (aType == imgINotificationObserver::DECODE_COMPLETE) {
return mInner->DecodeComplete(aRequest);
}
if (aType == imgINotificationObserver::LOAD_COMPLETE) {
return mInner->LoadComplete(aRequest);
}
if (aType == imgINotificationObserver::DISCARD) {
return mInner->Discard(aRequest);
}
if (aType == imgINotificationObserver::IS_ANIMATED) {
return mInner->IsAnimated(aRequest);
}
if (aType == imgINotificationObserver::HAS_TRANSPARENCY) {
return mInner->HasTransparency(aRequest);
}
return NS_OK;
}
} // namespace image
} // namespace mozilla

View file

@ -0,0 +1,37 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
*
* 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/. */
#ifndef mozilla_image_ScriptedNotificationObserver_h
#define mozilla_image_ScriptedNotificationObserver_h
#include "imgINotificationObserver.h"
#include "nsCOMPtr.h"
#include "nsCycleCollectionParticipant.h"
class imgIScriptedNotificationObserver;
namespace mozilla {
namespace image {
class ScriptedNotificationObserver : public imgINotificationObserver
{
public:
explicit
ScriptedNotificationObserver(imgIScriptedNotificationObserver* aInner);
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
NS_DECL_IMGINOTIFICATIONOBSERVER
NS_DECL_CYCLE_COLLECTION_CLASS(ScriptedNotificationObserver)
private:
virtual ~ScriptedNotificationObserver() { }
nsCOMPtr<imgIScriptedNotificationObserver> mInner;
};
} // namespace image
} // namespace mozilla
#endif // mozilla_image_ScriptedNotificationObserver_h

75
image/ShutdownTracker.cpp Normal file
View file

@ -0,0 +1,75 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* 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/. */
#include "ShutdownTracker.h"
#include "mozilla/Services.h"
#include "nsIObserver.h"
#include "nsIObserverService.h"
namespace mozilla {
namespace image {
class ShutdownTrackerImpl;
///////////////////////////////////////////////////////////////////////////////
// Static Data
///////////////////////////////////////////////////////////////////////////////
// Whether we've observed shutdown starting yet.
static bool sShutdownHasStarted = false;
///////////////////////////////////////////////////////////////////////////////
// Implementation
///////////////////////////////////////////////////////////////////////////////
struct ShutdownObserver : public nsIObserver
{
NS_DECL_ISUPPORTS
NS_IMETHOD Observe(nsISupports*, const char* aTopic, const char16_t*) override
{
if (strcmp(aTopic, "xpcom-will-shutdown") != 0) {
return NS_OK;
}
nsCOMPtr<nsIObserverService> os = services::GetObserverService();
if (os) {
os->RemoveObserver(this, "xpcom-will-shutdown");
}
sShutdownHasStarted = true;
return NS_OK;
}
private:
virtual ~ShutdownObserver() { }
};
NS_IMPL_ISUPPORTS(ShutdownObserver, nsIObserver)
///////////////////////////////////////////////////////////////////////////////
// Public API
///////////////////////////////////////////////////////////////////////////////
/* static */ void
ShutdownTracker::Initialize()
{
nsCOMPtr<nsIObserverService> os = services::GetObserverService();
if (os) {
os->AddObserver(new ShutdownObserver, "xpcom-will-shutdown", false);
}
}
/* static */ bool
ShutdownTracker::ShutdownHasStarted()
{
return sShutdownHasStarted;
}
} // namespace image
} // namespace mozilla

46
image/ShutdownTracker.h Normal file
View file

@ -0,0 +1,46 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* 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/. */
/**
* ShutdownTracker is an imagelib-global service that allows callers to check
* whether shutdown has started.
*/
#ifndef mozilla_image_ShutdownTracker_h
#define mozilla_image_ShutdownTracker_h
namespace mozilla {
namespace image {
/**
* ShutdownTracker is an imagelib-global service that allows callers to check
* whether shutdown has started. It exists to avoid the need for registering
* many 'xpcom-will-shutdown' notification observers on short-lived objects,
* which would have an unnecessary performance cost.
*/
struct ShutdownTracker
{
/**
* Initialize static data. Called during imagelib module initialization.
*/
static void Initialize();
/**
* Check whether shutdown has started. Callers can use this to check whether
* it's safe to access XPCOM services; if shutdown has started, such calls
* must be avoided.
*
* @return true if shutdown has already started.
*/
static bool ShutdownHasStarted();
private:
virtual ~ShutdownTracker() = 0; // Forbid instantiation.
};
} // namespace image
} // namespace mozilla
#endif // mozilla_image_ShutdownTracker_h

682
image/SourceBuffer.cpp Normal file
View file

@ -0,0 +1,682 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* 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/. */
#include "SourceBuffer.h"
#include <algorithm>
#include <cmath>
#include <cstring>
#include "mozilla/Likely.h"
#include "nsIInputStream.h"
#include "MainThreadUtils.h"
#include "SurfaceCache.h"
using std::max;
using std::min;
namespace mozilla {
namespace image {
//////////////////////////////////////////////////////////////////////////////
// SourceBufferIterator implementation.
//////////////////////////////////////////////////////////////////////////////
SourceBufferIterator::~SourceBufferIterator()
{
if (mOwner) {
mOwner->OnIteratorRelease();
}
}
SourceBufferIterator&
SourceBufferIterator::operator=(SourceBufferIterator&& aOther)
{
if (mOwner) {
mOwner->OnIteratorRelease();
}
mOwner = Move(aOther.mOwner);
mState = aOther.mState;
mData = aOther.mData;
mChunkCount = aOther.mChunkCount;
mByteCount = aOther.mByteCount;
return *this;
}
SourceBufferIterator::State
SourceBufferIterator::AdvanceOrScheduleResume(size_t aRequestedBytes,
IResumable* aConsumer)
{
MOZ_ASSERT(mOwner);
if (MOZ_UNLIKELY(!HasMore())) {
MOZ_ASSERT_UNREACHABLE("Should not advance a completed iterator");
return COMPLETE;
}
// The range of data [mOffset, mOffset + mNextReadLength) has just been read
// by the caller (or at least they don't have any interest in it), so consume
// that data.
MOZ_ASSERT(mData.mIterating.mNextReadLength <= mData.mIterating.mAvailableLength);
mData.mIterating.mOffset += mData.mIterating.mNextReadLength;
mData.mIterating.mAvailableLength -= mData.mIterating.mNextReadLength;
mData.mIterating.mNextReadLength = 0;
if (MOZ_LIKELY(mState == READY)) {
// If the caller wants zero bytes of data, that's easy enough; we just
// configured ourselves for a zero-byte read above! In theory we could do
// this even in the START state, but it's not important for performance and
// breaking the ability of callers to assert that the pointer returned by
// Data() is non-null doesn't seem worth it.
if (aRequestedBytes == 0) {
MOZ_ASSERT(mData.mIterating.mNextReadLength == 0);
return READY;
}
// Try to satisfy the request out of our local buffer. This is potentially
// much faster than requesting data from our owning SourceBuffer because we
// don't have to take the lock. Note that if we have anything at all in our
// local buffer, we use it to satisfy the request; @aRequestedBytes is just
// the *maximum* number of bytes we can return.
if (mData.mIterating.mAvailableLength > 0) {
return AdvanceFromLocalBuffer(aRequestedBytes);
}
}
// Our local buffer is empty, so we'll have to request data from our owning
// SourceBuffer.
return mOwner->AdvanceIteratorOrScheduleResume(*this,
aRequestedBytes,
aConsumer);
}
bool
SourceBufferIterator::RemainingBytesIsNoMoreThan(size_t aBytes) const
{
MOZ_ASSERT(mOwner);
return mOwner->RemainingBytesIsNoMoreThan(*this, aBytes);
}
//////////////////////////////////////////////////////////////////////////////
// SourceBuffer implementation.
//////////////////////////////////////////////////////////////////////////////
const size_t SourceBuffer::MIN_CHUNK_CAPACITY;
SourceBuffer::SourceBuffer()
: mMutex("image::SourceBuffer")
, mConsumerCount(0)
{ }
SourceBuffer::~SourceBuffer()
{
MOZ_ASSERT(mConsumerCount == 0,
"SourceBuffer destroyed with active consumers");
}
nsresult
SourceBuffer::AppendChunk(Maybe<Chunk>&& aChunk)
{
mMutex.AssertCurrentThreadOwns();
#ifdef DEBUG
if (mChunks.Length() > 0) {
NS_WARNING("Appending an extra chunk for SourceBuffer");
}
#endif
if (MOZ_UNLIKELY(!aChunk)) {
return NS_ERROR_OUT_OF_MEMORY;
}
if (MOZ_UNLIKELY(aChunk->AllocationFailed())) {
return NS_ERROR_OUT_OF_MEMORY;
}
if (MOZ_UNLIKELY(!mChunks.AppendElement(Move(*aChunk), fallible))) {
return NS_ERROR_OUT_OF_MEMORY;
}
return NS_OK;
}
Maybe<SourceBuffer::Chunk>
SourceBuffer::CreateChunk(size_t aCapacity, bool aRoundUp /* = true */)
{
if (MOZ_UNLIKELY(aCapacity == 0)) {
MOZ_ASSERT_UNREACHABLE("Appending a chunk of zero size?");
return Nothing();
}
// Round up if requested.
size_t finalCapacity = aRoundUp ? RoundedUpCapacity(aCapacity)
: aCapacity;
// Use the size of the SurfaceCache as an additional heuristic to avoid
// allocating huge buffers. Generally images do not get smaller when decoded,
// so if we could store the source data in the SurfaceCache, we assume that
// there's no way we'll be able to store the decoded version.
if (MOZ_UNLIKELY(!SurfaceCache::CanHold(finalCapacity))) {
NS_WARNING("SourceBuffer refused to create chunk too large for SurfaceCache");
return Nothing();
}
return Some(Chunk(finalCapacity));
}
nsresult
SourceBuffer::Compact()
{
mMutex.AssertCurrentThreadOwns();
MOZ_ASSERT(mConsumerCount == 0, "Should have no consumers here");
MOZ_ASSERT(mWaitingConsumers.Length() == 0, "Shouldn't have waiters");
MOZ_ASSERT(mStatus, "Should be complete here");
// Compact our waiting consumers list, since we're complete and no future
// consumer will ever have to wait.
mWaitingConsumers.Compact();
// If we have no chunks, then there's nothing to compact.
if (mChunks.Length() < 1) {
return NS_OK;
}
// If we have one chunk, then we can compact if it has excess capacity.
if (mChunks.Length() == 1 && mChunks[0].Length() == mChunks[0].Capacity()) {
return NS_OK;
}
// We can compact our buffer. Determine the total length.
size_t length = 0;
for (uint32_t i = 0 ; i < mChunks.Length() ; ++i) {
length += mChunks[i].Length();
}
// If our total length is zero (which means ExpectLength() got called, but no
// data ever actually got written) then just empty our chunk list.
if (MOZ_UNLIKELY(length == 0)) {
mChunks.Clear();
return NS_OK;
}
Maybe<Chunk> newChunk = CreateChunk(length, /* aRoundUp = */ false);
if (MOZ_UNLIKELY(!newChunk || newChunk->AllocationFailed())) {
NS_WARNING("Failed to allocate chunk for SourceBuffer compacting - OOM?");
return NS_OK;
}
// Copy our old chunks into the new chunk.
for (uint32_t i = 0 ; i < mChunks.Length() ; ++i) {
size_t offset = newChunk->Length();
MOZ_ASSERT(offset < newChunk->Capacity());
MOZ_ASSERT(offset + mChunks[i].Length() <= newChunk->Capacity());
memcpy(newChunk->Data() + offset, mChunks[i].Data(), mChunks[i].Length());
newChunk->AddLength(mChunks[i].Length());
}
MOZ_ASSERT(newChunk->Length() == newChunk->Capacity(),
"Compacted chunk has slack space");
// Replace the old chunks with the new, compact chunk.
mChunks.Clear();
if (MOZ_UNLIKELY(NS_FAILED(AppendChunk(Move(newChunk))))) {
return HandleError(NS_ERROR_OUT_OF_MEMORY);
}
mChunks.Compact();
return NS_OK;
}
/* static */ size_t
SourceBuffer::RoundedUpCapacity(size_t aCapacity)
{
// Protect against overflow.
if (MOZ_UNLIKELY(SIZE_MAX - aCapacity < MIN_CHUNK_CAPACITY)) {
return aCapacity;
}
// Round up to the next multiple of MIN_CHUNK_CAPACITY (which should be the
// size of a page).
size_t roundedCapacity =
(aCapacity + MIN_CHUNK_CAPACITY - 1) & ~(MIN_CHUNK_CAPACITY - 1);
MOZ_ASSERT(roundedCapacity >= aCapacity, "Bad math?");
MOZ_ASSERT(roundedCapacity - aCapacity < MIN_CHUNK_CAPACITY, "Bad math?");
return roundedCapacity;
}
size_t
SourceBuffer::FibonacciCapacityWithMinimum(size_t aMinCapacity)
{
mMutex.AssertCurrentThreadOwns();
// We grow the source buffer using a Fibonacci growth rate.
size_t length = mChunks.Length();
if (length == 0) {
return aMinCapacity;
}
if (length == 1) {
return max(2 * mChunks[0].Capacity(), aMinCapacity);
}
return max(mChunks[length - 1].Capacity() + mChunks[length - 2].Capacity(),
aMinCapacity);
}
void
SourceBuffer::AddWaitingConsumer(IResumable* aConsumer)
{
mMutex.AssertCurrentThreadOwns();
MOZ_ASSERT(!mStatus, "Waiting when we're complete?");
if (aConsumer) {
mWaitingConsumers.AppendElement(aConsumer);
}
}
void
SourceBuffer::ResumeWaitingConsumers()
{
mMutex.AssertCurrentThreadOwns();
if (mWaitingConsumers.Length() == 0) {
return;
}
for (uint32_t i = 0 ; i < mWaitingConsumers.Length() ; ++i) {
mWaitingConsumers[i]->Resume();
}
mWaitingConsumers.Clear();
}
nsresult
SourceBuffer::ExpectLength(size_t aExpectedLength)
{
MOZ_ASSERT(aExpectedLength > 0, "Zero expected size?");
MutexAutoLock lock(mMutex);
if (MOZ_UNLIKELY(mStatus)) {
MOZ_ASSERT_UNREACHABLE("ExpectLength after SourceBuffer is complete");
return NS_OK;
}
if (MOZ_UNLIKELY(mChunks.Length() > 0)) {
MOZ_ASSERT_UNREACHABLE("Duplicate or post-Append call to ExpectLength");
return NS_OK;
}
if (MOZ_UNLIKELY(NS_FAILED(AppendChunk(CreateChunk(aExpectedLength))))) {
return HandleError(NS_ERROR_OUT_OF_MEMORY);
}
return NS_OK;
}
nsresult
SourceBuffer::Append(const char* aData, size_t aLength)
{
MOZ_ASSERT(aData, "Should have a buffer");
MOZ_ASSERT(aLength > 0, "Writing a zero-sized chunk");
size_t currentChunkCapacity = 0;
size_t currentChunkLength = 0;
char* currentChunkData = nullptr;
size_t currentChunkRemaining = 0;
size_t forCurrentChunk = 0;
size_t forNextChunk = 0;
size_t nextChunkCapacity = 0;
{
MutexAutoLock lock(mMutex);
if (MOZ_UNLIKELY(mStatus)) {
// This SourceBuffer is already complete; ignore further data.
return NS_ERROR_FAILURE;
}
if (MOZ_UNLIKELY(mChunks.Length() == 0)) {
if (MOZ_UNLIKELY(NS_FAILED(AppendChunk(CreateChunk(aLength))))) {
return HandleError(NS_ERROR_OUT_OF_MEMORY);
}
}
// Copy out the current chunk's information so we can release the lock.
// Note that this wouldn't be safe if multiple producers were allowed!
Chunk& currentChunk = mChunks.LastElement();
currentChunkCapacity = currentChunk.Capacity();
currentChunkLength = currentChunk.Length();
currentChunkData = currentChunk.Data();
// Partition this data between the current chunk and the next chunk.
// (Because we always allocate a chunk big enough to fit everything passed
// to Append, we'll never need more than those two chunks to store
// everything.)
currentChunkRemaining = currentChunkCapacity - currentChunkLength;
forCurrentChunk = min(aLength, currentChunkRemaining);
forNextChunk = aLength - forCurrentChunk;
// If we'll need another chunk, determine what its capacity should be while
// we still hold the lock.
nextChunkCapacity = forNextChunk > 0
? FibonacciCapacityWithMinimum(forNextChunk)
: 0;
}
// Write everything we can fit into the current chunk.
MOZ_ASSERT(currentChunkLength + forCurrentChunk <= currentChunkCapacity);
memcpy(currentChunkData + currentChunkLength, aData, forCurrentChunk);
// If there's something left, create a new chunk and write it there.
Maybe<Chunk> nextChunk;
if (forNextChunk > 0) {
MOZ_ASSERT(nextChunkCapacity >= forNextChunk, "Next chunk too small?");
nextChunk = CreateChunk(nextChunkCapacity);
if (MOZ_LIKELY(nextChunk && !nextChunk->AllocationFailed())) {
memcpy(nextChunk->Data(), aData + forCurrentChunk, forNextChunk);
nextChunk->AddLength(forNextChunk);
}
}
// Update shared data structures.
{
MutexAutoLock lock(mMutex);
// Update the length of the current chunk.
Chunk& currentChunk = mChunks.LastElement();
MOZ_ASSERT(currentChunk.Data() == currentChunkData, "Multiple producers?");
MOZ_ASSERT(currentChunk.Length() == currentChunkLength,
"Multiple producers?");
currentChunk.AddLength(forCurrentChunk);
// If we created a new chunk, add it to the series.
if (forNextChunk > 0) {
if (MOZ_UNLIKELY(!nextChunk)) {
return HandleError(NS_ERROR_OUT_OF_MEMORY);
}
if (MOZ_UNLIKELY(NS_FAILED(AppendChunk(Move(nextChunk))))) {
return HandleError(NS_ERROR_OUT_OF_MEMORY);
}
}
// Resume any waiting readers now that there's new data.
ResumeWaitingConsumers();
}
return NS_OK;
}
static nsresult
AppendToSourceBuffer(nsIInputStream*,
void* aClosure,
const char* aFromRawSegment,
uint32_t,
uint32_t aCount,
uint32_t* aWriteCount)
{
SourceBuffer* sourceBuffer = static_cast<SourceBuffer*>(aClosure);
// Copy the source data. Unless we hit OOM, we squelch the return value here,
// because returning an error means that ReadSegments stops reading data, and
// we want to ensure that we read everything we get. If we hit OOM then we
// return a failed status to the caller.
nsresult rv = sourceBuffer->Append(aFromRawSegment, aCount);
if (rv == NS_ERROR_OUT_OF_MEMORY) {
return rv;
}
// Report that we wrote everything we got.
*aWriteCount = aCount;
return NS_OK;
}
nsresult
SourceBuffer::AppendFromInputStream(nsIInputStream* aInputStream,
uint32_t aCount)
{
uint32_t bytesRead;
nsresult rv = aInputStream->ReadSegments(AppendToSourceBuffer, this,
aCount, &bytesRead);
if (!NS_WARN_IF(NS_FAILED(rv))) {
MOZ_ASSERT(bytesRead == aCount,
"AppendToSourceBuffer should consume everything");
}
return rv;
}
void
SourceBuffer::Complete(nsresult aStatus)
{
MutexAutoLock lock(mMutex);
if (MOZ_UNLIKELY(mStatus)) {
MOZ_ASSERT_UNREACHABLE("Called Complete more than once");
return;
}
if (MOZ_UNLIKELY(NS_SUCCEEDED(aStatus) && IsEmpty())) {
// It's illegal to succeed without writing anything.
aStatus = NS_ERROR_FAILURE;
}
mStatus = Some(aStatus);
// Resume any waiting consumers now that we're complete.
ResumeWaitingConsumers();
// If we still have active consumers, just return.
if (mConsumerCount > 0) {
return;
}
// Attempt to compact our buffer down to a single chunk.
Compact();
}
bool
SourceBuffer::IsComplete()
{
MutexAutoLock lock(mMutex);
return bool(mStatus);
}
size_t
SourceBuffer::SizeOfIncludingThisWithComputedFallback(MallocSizeOf
aMallocSizeOf) const
{
MutexAutoLock lock(mMutex);
size_t n = aMallocSizeOf(this);
n += mChunks.ShallowSizeOfExcludingThis(aMallocSizeOf);
for (uint32_t i = 0 ; i < mChunks.Length() ; ++i) {
size_t chunkSize = aMallocSizeOf(mChunks[i].Data());
if (chunkSize == 0) {
// We're on a platform where moz_malloc_size_of always returns 0.
chunkSize = mChunks[i].Capacity();
}
n += chunkSize;
}
return n;
}
SourceBufferIterator
SourceBuffer::Iterator()
{
{
MutexAutoLock lock(mMutex);
mConsumerCount++;
}
return SourceBufferIterator(this);
}
void
SourceBuffer::OnIteratorRelease()
{
MutexAutoLock lock(mMutex);
MOZ_ASSERT(mConsumerCount > 0, "Consumer count doesn't add up");
mConsumerCount--;
// If we still have active consumers, or we're not complete yet, then return.
if (mConsumerCount > 0 || !mStatus) {
return;
}
// Attempt to compact our buffer down to a single chunk.
Compact();
}
bool
SourceBuffer::RemainingBytesIsNoMoreThan(const SourceBufferIterator& aIterator,
size_t aBytes) const
{
MutexAutoLock lock(mMutex);
// If we're not complete, we always say no.
if (!mStatus) {
return false;
}
// If the iterator's at the end, the answer is trivial.
if (!aIterator.HasMore()) {
return true;
}
uint32_t iteratorChunk = aIterator.mData.mIterating.mChunk;
size_t iteratorOffset = aIterator.mData.mIterating.mOffset;
size_t iteratorLength = aIterator.mData.mIterating.mAvailableLength;
// Include the bytes the iterator is currently pointing to in the limit, so
// that the current chunk doesn't have to be a special case.
size_t bytes = aBytes + iteratorOffset + iteratorLength;
// Count the length over all of our chunks, starting with the one that the
// iterator is currently pointing to. (This is O(N), but N is expected to be
// ~1, so it doesn't seem worth caching the length separately.)
size_t lengthSoFar = 0;
for (uint32_t i = iteratorChunk ; i < mChunks.Length() ; ++i) {
lengthSoFar += mChunks[i].Length();
if (lengthSoFar > bytes) {
return false;
}
}
return true;
}
SourceBufferIterator::State
SourceBuffer::AdvanceIteratorOrScheduleResume(SourceBufferIterator& aIterator,
size_t aRequestedBytes,
IResumable* aConsumer)
{
MutexAutoLock lock(mMutex);
MOZ_ASSERT(aIterator.HasMore(), "Advancing a completed iterator and "
"AdvanceOrScheduleResume didn't catch it");
if (MOZ_UNLIKELY(mStatus && NS_FAILED(*mStatus))) {
// This SourceBuffer is complete due to an error; all reads fail.
return aIterator.SetComplete(*mStatus);
}
if (MOZ_UNLIKELY(mChunks.Length() == 0)) {
// We haven't gotten an initial chunk yet.
AddWaitingConsumer(aConsumer);
return aIterator.SetWaiting();
}
uint32_t iteratorChunkIdx = aIterator.mData.mIterating.mChunk;
MOZ_ASSERT(iteratorChunkIdx < mChunks.Length());
const Chunk& currentChunk = mChunks[iteratorChunkIdx];
size_t iteratorEnd = aIterator.mData.mIterating.mOffset +
aIterator.mData.mIterating.mAvailableLength;
MOZ_ASSERT(iteratorEnd <= currentChunk.Length());
MOZ_ASSERT(iteratorEnd <= currentChunk.Capacity());
if (iteratorEnd < currentChunk.Length()) {
// There's more data in the current chunk.
return aIterator.SetReady(iteratorChunkIdx, currentChunk.Data(),
iteratorEnd, currentChunk.Length() - iteratorEnd,
aRequestedBytes);
}
if (iteratorEnd == currentChunk.Capacity() &&
!IsLastChunk(iteratorChunkIdx)) {
// Advance to the next chunk.
const Chunk& nextChunk = mChunks[iteratorChunkIdx + 1];
return aIterator.SetReady(iteratorChunkIdx + 1, nextChunk.Data(), 0,
nextChunk.Length(), aRequestedBytes);
}
MOZ_ASSERT(IsLastChunk(iteratorChunkIdx), "Should've advanced");
if (mStatus) {
// There's no more data and this SourceBuffer completed successfully.
MOZ_ASSERT(NS_SUCCEEDED(*mStatus), "Handled failures earlier");
return aIterator.SetComplete(*mStatus);
}
// We're not complete, but there's no more data right now. Arrange to wake up
// the consumer when we get more data.
AddWaitingConsumer(aConsumer);
return aIterator.SetWaiting();
}
nsresult
SourceBuffer::HandleError(nsresult aError)
{
MOZ_ASSERT(NS_FAILED(aError), "Should have an error here");
MOZ_ASSERT(aError == NS_ERROR_OUT_OF_MEMORY,
"Unexpected error; may want to notify waiting readers, which "
"HandleError currently doesn't do");
mMutex.AssertCurrentThreadOwns();
NS_WARNING("SourceBuffer encountered an unrecoverable error");
// Record the error.
mStatus = Some(aError);
// Drop our references to waiting readers.
mWaitingConsumers.Clear();
return *mStatus;
}
bool
SourceBuffer::IsEmpty()
{
mMutex.AssertCurrentThreadOwns();
return mChunks.Length() == 0 ||
mChunks[0].Length() == 0;
}
bool
SourceBuffer::IsLastChunk(uint32_t aChunk)
{
mMutex.AssertCurrentThreadOwns();
return aChunk + 1 == mChunks.Length();
}
} // namespace image
} // namespace mozilla

459
image/SourceBuffer.h Normal file
View file

@ -0,0 +1,459 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* 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/. */
/**
* SourceBuffer is a single producer, multiple consumer data structure used for
* storing image source (compressed) data.
*/
#ifndef mozilla_image_sourcebuffer_h
#define mozilla_image_sourcebuffer_h
#include <algorithm>
#include "mozilla/Maybe.h"
#include "mozilla/MemoryReporting.h"
#include "mozilla/Mutex.h"
#include "mozilla/Move.h"
#include "mozilla/MemoryReporting.h"
#include "mozilla/RefPtr.h"
#include "mozilla/RefCounted.h"
#include "mozilla/UniquePtr.h"
#include "mozilla/RefPtr.h"
#include "nsTArray.h"
class nsIInputStream;
namespace mozilla {
namespace image {
class SourceBuffer;
/**
* IResumable is an interface for classes that can schedule themselves to resume
* their work later. An implementation of IResumable generally should post a
* runnable to some event target which continues the work of the task.
*/
struct IResumable
{
MOZ_DECLARE_REFCOUNTED_TYPENAME(IResumable)
// Subclasses may or may not be XPCOM classes, so we just require that they
// implement AddRef and Release.
NS_IMETHOD_(MozExternalRefCountType) AddRef(void) = 0;
NS_IMETHOD_(MozExternalRefCountType) Release(void) = 0;
virtual void Resume() = 0;
protected:
virtual ~IResumable() { }
};
/**
* SourceBufferIterator is a class that allows consumers of image source data to
* read the contents of a SourceBuffer sequentially.
*
* Consumers can advance through the SourceBuffer by calling
* AdvanceOrScheduleResume() repeatedly. After every advance, they should call
* check the return value, which will tell them the iterator's new state.
*
* If WAITING is returned, AdvanceOrScheduleResume() has arranged
* to call the consumer's Resume() method later, so the consumer should save its
* state if needed and stop running.
*
* If the iterator's new state is READY, then the consumer can call Data() and
* Length() to read new data from the SourceBuffer.
*
* Finally, in the COMPLETE state the consumer can call CompletionStatus() to
* get the status passed to SourceBuffer::Complete().
*/
class SourceBufferIterator final
{
public:
enum State {
START, // The iterator is at the beginning of the buffer.
READY, // The iterator is pointing to new data.
WAITING, // The iterator is blocked and the caller must yield.
COMPLETE // The iterator is pointing to the end of the buffer.
};
explicit SourceBufferIterator(SourceBuffer* aOwner)
: mOwner(aOwner)
, mState(START)
, mChunkCount(0)
, mByteCount(0)
{
MOZ_ASSERT(aOwner);
mData.mIterating.mChunk = 0;
mData.mIterating.mData = nullptr;
mData.mIterating.mOffset = 0;
mData.mIterating.mAvailableLength = 0;
mData.mIterating.mNextReadLength = 0;
}
SourceBufferIterator(SourceBufferIterator&& aOther)
: mOwner(Move(aOther.mOwner))
, mState(aOther.mState)
, mData(aOther.mData)
, mChunkCount(aOther.mChunkCount)
, mByteCount(aOther.mByteCount)
{ }
~SourceBufferIterator();
SourceBufferIterator& operator=(SourceBufferIterator&& aOther);
/**
* Returns true if there are no more than @aBytes remaining in the
* SourceBuffer. If the SourceBuffer is not yet complete, returns false.
*/
bool RemainingBytesIsNoMoreThan(size_t aBytes) const;
/**
* Advances the iterator through the SourceBuffer if possible. Advances no
* more than @aRequestedBytes bytes. (Use SIZE_MAX to advance as much as
* possible.)
*
* This is a wrapper around AdvanceOrScheduleResume() that makes it clearer at
* the callsite when the no resuming is intended.
*
* @return State::READY if the iterator was successfully advanced.
* State::WAITING if the iterator could not be advanced because it's
* at the end of the underlying SourceBuffer, but the SourceBuffer
* may still receive additional data.
* State::COMPLETE if the iterator could not be advanced because it's
* at the end of the underlying SourceBuffer and the SourceBuffer is
* marked complete (i.e., it will never receive any additional
* data).
*/
State Advance(size_t aRequestedBytes)
{
return AdvanceOrScheduleResume(aRequestedBytes, nullptr);
}
/**
* Advances the iterator through the SourceBuffer if possible. Advances no
* more than @aRequestedBytes bytes. (Use SIZE_MAX to advance as much as
* possible.) If advancing is not possible and @aConsumer is not null,
* arranges to call the @aConsumer's Resume() method when more data is
* available.
*
* @return State::READY if the iterator was successfully advanced.
* State::WAITING if the iterator could not be advanced because it's
* at the end of the underlying SourceBuffer, but the SourceBuffer
* may still receive additional data. @aConsumer's Resume() method
* will be called when additional data is available.
* State::COMPLETE if the iterator could not be advanced because it's
* at the end of the underlying SourceBuffer and the SourceBuffer is
* marked complete (i.e., it will never receive any additional
* data).
*/
State AdvanceOrScheduleResume(size_t aRequestedBytes, IResumable* aConsumer);
/// If at the end, returns the status passed to SourceBuffer::Complete().
nsresult CompletionStatus() const
{
MOZ_ASSERT(mState == COMPLETE,
"Calling CompletionStatus() in the wrong state");
return mState == COMPLETE ? mData.mAtEnd.mStatus : NS_OK;
}
/// If we're ready to read, returns a pointer to the new data.
const char* Data() const
{
MOZ_ASSERT(mState == READY, "Calling Data() in the wrong state");
return mState == READY ? mData.mIterating.mData + mData.mIterating.mOffset
: nullptr;
}
/// If we're ready to read, returns the length of the new data.
size_t Length() const
{
MOZ_ASSERT(mState == READY, "Calling Length() in the wrong state");
return mState == READY ? mData.mIterating.mNextReadLength : 0;
}
/// @return a count of the chunks we've advanced through.
uint32_t ChunkCount() const { return mChunkCount; }
/// @return a count of the bytes in all chunks we've advanced through.
size_t ByteCount() const { return mByteCount; }
private:
friend class SourceBuffer;
SourceBufferIterator(const SourceBufferIterator&) = delete;
SourceBufferIterator& operator=(const SourceBufferIterator&) = delete;
bool HasMore() const { return mState != COMPLETE; }
State AdvanceFromLocalBuffer(size_t aRequestedBytes)
{
MOZ_ASSERT(mState == READY, "Advancing in the wrong state");
MOZ_ASSERT(mData.mIterating.mAvailableLength > 0,
"The local buffer shouldn't be empty");
MOZ_ASSERT(mData.mIterating.mNextReadLength == 0,
"Advancing without consuming previous data");
mData.mIterating.mNextReadLength =
std::min(mData.mIterating.mAvailableLength, aRequestedBytes);
return READY;
}
State SetReady(uint32_t aChunk, const char* aData,
size_t aOffset, size_t aAvailableLength,
size_t aRequestedBytes)
{
MOZ_ASSERT(mState != COMPLETE);
mState = READY;
// Update state.
mData.mIterating.mChunk = aChunk;
mData.mIterating.mData = aData;
mData.mIterating.mOffset = aOffset;
mData.mIterating.mAvailableLength = aAvailableLength;
// Update metrics.
mChunkCount++;
mByteCount += aAvailableLength;
// Attempt to advance by the requested number of bytes.
return AdvanceFromLocalBuffer(aRequestedBytes);
}
State SetWaiting()
{
MOZ_ASSERT(mState != COMPLETE);
MOZ_ASSERT(mState != WAITING, "Did we get a spurious wakeup somehow?");
return mState = WAITING;
}
State SetComplete(nsresult aStatus)
{
mData.mAtEnd.mStatus = aStatus;
return mState = COMPLETE;
}
RefPtr<SourceBuffer> mOwner;
State mState;
/**
* This union contains our iteration state if we're still iterating (for
* states START, READY, and WAITING) and the status the SourceBuffer was
* completed with if we're in state COMPLETE.
*/
union {
struct {
uint32_t mChunk;
const char* mData;
size_t mOffset;
size_t mAvailableLength;
size_t mNextReadLength;
} mIterating;
struct {
nsresult mStatus;
} mAtEnd;
} mData;
uint32_t mChunkCount; // Count of chunks we've advanced through.
size_t mByteCount; // Count of bytes in all chunks we've advanced through.
};
/**
* SourceBuffer is a parallel data structure used for storing image source
* (compressed) data.
*
* SourceBuffer is a single producer, multiple consumer data structure. The
* single producer calls Append() to append data to the buffer. In parallel,
* multiple consumers can call Iterator(), which returns a SourceBufferIterator
* that they can use to iterate through the buffer. The SourceBufferIterator
* returns a series of pointers which remain stable for lifetime of the
* SourceBuffer, and the data they point to is immutable, ensuring that the
* producer never interferes with the consumers.
*
* In order to avoid blocking, SourceBuffer works with SourceBufferIterator to
* keep a list of consumers which are waiting for new data, and to resume them
* when the producer appends more. All consumers must implement the IResumable
* interface to make this possible.
*/
class SourceBuffer final
{
public:
MOZ_DECLARE_REFCOUNTED_TYPENAME(image::SourceBuffer)
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(image::SourceBuffer)
SourceBuffer();
//////////////////////////////////////////////////////////////////////////////
// Producer methods.
//////////////////////////////////////////////////////////////////////////////
/**
* If the producer knows how long the source data will be, it should call
* ExpectLength, which enables SourceBuffer to preallocate its buffer.
*/
nsresult ExpectLength(size_t aExpectedLength);
/// Append the provided data to the buffer.
nsresult Append(const char* aData, size_t aLength);
/// Append the data available on the provided nsIInputStream to the buffer.
nsresult AppendFromInputStream(nsIInputStream* aInputStream, uint32_t aCount);
/**
* Mark the buffer complete, with a status that will be available to
* consumers. Further calls to Append() are forbidden after Complete().
*/
void Complete(nsresult aStatus);
/// Returns true if the buffer is complete.
bool IsComplete();
/// Memory reporting.
size_t SizeOfIncludingThisWithComputedFallback(MallocSizeOf) const;
//////////////////////////////////////////////////////////////////////////////
// Consumer methods.
//////////////////////////////////////////////////////////////////////////////
/// Returns an iterator to this SourceBuffer.
SourceBufferIterator Iterator();
//////////////////////////////////////////////////////////////////////////////
// Consumer methods.
//////////////////////////////////////////////////////////////////////////////
/**
* The minimum chunk capacity we'll allocate, if we don't know the correct
* capacity (which would happen because ExpectLength() wasn't called or gave
* us the wrong value). This is only exposed for use by tests; if normal code
* is using this, it's doing something wrong.
*/
static const size_t MIN_CHUNK_CAPACITY = 4096;
private:
friend class SourceBufferIterator;
~SourceBuffer();
//////////////////////////////////////////////////////////////////////////////
// Chunk type and chunk-related methods.
//////////////////////////////////////////////////////////////////////////////
class Chunk
{
public:
explicit Chunk(size_t aCapacity)
: mCapacity(aCapacity)
, mLength(0)
{
MOZ_ASSERT(aCapacity > 0, "Creating zero-capacity chunk");
mData.reset(new (fallible) char[mCapacity]);
}
Chunk(Chunk&& aOther)
: mCapacity(aOther.mCapacity)
, mLength(aOther.mLength)
, mData(Move(aOther.mData))
{
aOther.mCapacity = aOther.mLength = 0;
aOther.mData = nullptr;
}
Chunk& operator=(Chunk&& aOther)
{
mCapacity = aOther.mCapacity;
mLength = aOther.mLength;
mData = Move(aOther.mData);
aOther.mCapacity = aOther.mLength = 0;
aOther.mData = nullptr;
return *this;
}
bool AllocationFailed() const { return !mData; }
size_t Capacity() const { return mCapacity; }
size_t Length() const { return mLength; }
char* Data() const
{
MOZ_ASSERT(mData, "Allocation failed but nobody checked for it");
return mData.get();
}
void AddLength(size_t aAdditionalLength)
{
MOZ_ASSERT(mLength + aAdditionalLength <= mCapacity);
mLength += aAdditionalLength;
}
private:
Chunk(const Chunk&) = delete;
Chunk& operator=(const Chunk&) = delete;
size_t mCapacity;
size_t mLength;
UniquePtr<char[]> mData;
};
nsresult AppendChunk(Maybe<Chunk>&& aChunk);
Maybe<Chunk> CreateChunk(size_t aCapacity, bool aRoundUp = true);
nsresult Compact();
static size_t RoundedUpCapacity(size_t aCapacity);
size_t FibonacciCapacityWithMinimum(size_t aMinCapacity);
//////////////////////////////////////////////////////////////////////////////
// Iterator / consumer methods.
//////////////////////////////////////////////////////////////////////////////
void AddWaitingConsumer(IResumable* aConsumer);
void ResumeWaitingConsumers();
typedef SourceBufferIterator::State State;
State AdvanceIteratorOrScheduleResume(SourceBufferIterator& aIterator,
size_t aRequestedBytes,
IResumable* aConsumer);
bool RemainingBytesIsNoMoreThan(const SourceBufferIterator& aIterator,
size_t aBytes) const;
void OnIteratorRelease();
//////////////////////////////////////////////////////////////////////////////
// Helper methods.
//////////////////////////////////////////////////////////////////////////////
nsresult HandleError(nsresult aError);
bool IsEmpty();
bool IsLastChunk(uint32_t aChunk);
//////////////////////////////////////////////////////////////////////////////
// Member variables.
//////////////////////////////////////////////////////////////////////////////
/// All private members are protected by mMutex.
mutable Mutex mMutex;
/// The data in this SourceBuffer, stored as a series of Chunks.
FallibleTArray<Chunk> mChunks;
/// Consumers which are waiting to be notified when new data is available.
nsTArray<RefPtr<IResumable>> mWaitingConsumers;
/// If present, marks this SourceBuffer complete with the given final status.
Maybe<nsresult> mStatus;
/// Count of active consumers.
uint32_t mConsumerCount;
};
} // namespace image
} // namespace mozilla
#endif // mozilla_image_sourcebuffer_h

750
image/StreamingLexer.h Normal file
View file

@ -0,0 +1,750 @@
/* -*- 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/. */
/**
* StreamingLexer is a lexing framework designed to make it simple to write
* image decoders without worrying about the details of how the data is arriving
* from the network.
*/
#ifndef mozilla_image_StreamingLexer_h
#define mozilla_image_StreamingLexer_h
#include <algorithm>
#include <cstdint>
#include "mozilla/Assertions.h"
#include "mozilla/Attributes.h"
#include "mozilla/Maybe.h"
#include "mozilla/Move.h"
#include "mozilla/Variant.h"
#include "mozilla/Vector.h"
namespace mozilla {
namespace image {
/// Buffering behaviors for StreamingLexer transitions.
enum class BufferingStrategy
{
BUFFERED, // Data will be buffered and processed in one chunk.
UNBUFFERED // Data will be processed as it arrives, in multiple chunks.
};
/// Control flow behaviors for StreamingLexer transitions.
enum class ControlFlowStrategy
{
CONTINUE, // If there's enough data, proceed to the next state immediately.
YIELD // Yield to the caller before proceeding to the next state.
};
/// Possible terminal states for the lexer.
enum class TerminalState
{
SUCCESS,
FAILURE
};
/// Possible yield reasons for the lexer.
enum class Yield
{
NEED_MORE_DATA, // The lexer cannot continue without more data.
OUTPUT_AVAILABLE // There is output available for the caller to consume.
};
/// The result of a call to StreamingLexer::Lex().
typedef Variant<TerminalState, Yield> LexerResult;
/**
* LexerTransition is a type used to give commands to the lexing framework.
* Code that uses StreamingLexer can create LexerTransition values using the
* static methods on Transition, and then return them to the lexing framework
* for execution.
*/
template <typename State>
class LexerTransition
{
public:
// This is implicit so that Terminate{Success,Failure}() can return a
// TerminalState and have it implicitly converted to a
// LexerTransition<State>, which avoids the need for a "<State>"
// qualification to the Terminate{Success,Failure}() callsite.
MOZ_IMPLICIT LexerTransition(TerminalState aFinalState)
: mNextState(aFinalState)
{}
bool NextStateIsTerminal() const
{
return mNextState.template is<TerminalState>();
}
TerminalState NextStateAsTerminal() const
{
return mNextState.template as<TerminalState>();
}
State NextState() const
{
return mNextState.template as<NonTerminalState>().mState;
}
State UnbufferedState() const
{
return *mNextState.template as<NonTerminalState>().mUnbufferedState;
}
size_t Size() const
{
return mNextState.template as<NonTerminalState>().mSize;
}
BufferingStrategy Buffering() const
{
return mNextState.template as<NonTerminalState>().mBufferingStrategy;
}
ControlFlowStrategy ControlFlow() const
{
return mNextState.template as<NonTerminalState>().mControlFlowStrategy;
}
private:
friend struct Transition;
LexerTransition(State aNextState,
const Maybe<State>& aUnbufferedState,
size_t aSize,
BufferingStrategy aBufferingStrategy,
ControlFlowStrategy aControlFlowStrategy)
: mNextState(NonTerminalState(aNextState, aUnbufferedState, aSize,
aBufferingStrategy, aControlFlowStrategy))
{}
struct NonTerminalState
{
State mState;
Maybe<State> mUnbufferedState;
size_t mSize;
BufferingStrategy mBufferingStrategy;
ControlFlowStrategy mControlFlowStrategy;
NonTerminalState(State aState,
const Maybe<State>& aUnbufferedState,
size_t aSize,
BufferingStrategy aBufferingStrategy,
ControlFlowStrategy aControlFlowStrategy)
: mState(aState)
, mUnbufferedState(aUnbufferedState)
, mSize(aSize)
, mBufferingStrategy(aBufferingStrategy)
, mControlFlowStrategy(aControlFlowStrategy)
{
MOZ_ASSERT_IF(mBufferingStrategy == BufferingStrategy::UNBUFFERED,
mUnbufferedState);
MOZ_ASSERT_IF(mUnbufferedState,
mBufferingStrategy == BufferingStrategy::UNBUFFERED);
}
};
Variant<NonTerminalState, TerminalState> mNextState;
};
struct Transition
{
/// Transition to @aNextState, buffering @aSize bytes of data.
template <typename State>
static LexerTransition<State>
To(const State& aNextState, size_t aSize)
{
return LexerTransition<State>(aNextState, Nothing(), aSize,
BufferingStrategy::BUFFERED,
ControlFlowStrategy::CONTINUE);
}
/// Yield to the caller, transitioning to @aNextState when Lex() is next
/// invoked. The same data that was delivered for the current state will be
/// delivered again.
template <typename State>
static LexerTransition<State>
ToAfterYield(const State& aNextState)
{
return LexerTransition<State>(aNextState, Nothing(), 0,
BufferingStrategy::BUFFERED,
ControlFlowStrategy::YIELD);
}
/**
* Transition to @aNextState via @aUnbufferedState, reading @aSize bytes of
* data unbuffered.
*
* The unbuffered data will be delivered in state @aUnbufferedState, which may
* be invoked repeatedly until all @aSize bytes have been delivered. Then,
* @aNextState will be invoked with no data. No state transitions are allowed
* from @aUnbufferedState except for transitions to a terminal state, so
* @aNextState will always be reached unless lexing terminates early.
*/
template <typename State>
static LexerTransition<State>
ToUnbuffered(const State& aNextState,
const State& aUnbufferedState,
size_t aSize)
{
return LexerTransition<State>(aNextState, Some(aUnbufferedState), aSize,
BufferingStrategy::UNBUFFERED,
ControlFlowStrategy::CONTINUE);
}
/**
* Continue receiving unbuffered data. @aUnbufferedState should be the same
* state as the @aUnbufferedState specified in the preceding call to
* ToUnbuffered().
*
* This should be used during an unbuffered read initiated by ToUnbuffered().
*/
template <typename State>
static LexerTransition<State>
ContinueUnbuffered(const State& aUnbufferedState)
{
return LexerTransition<State>(aUnbufferedState, Nothing(), 0,
BufferingStrategy::BUFFERED,
ControlFlowStrategy::CONTINUE);
}
/**
* Continue receiving unbuffered data. @aUnbufferedState should be the same
* state as the @aUnbufferedState specified in the preceding call to
* ToUnbuffered(). @aSize indicates the amount of data that has already been
* consumed; the next state will receive the same data that was delivered to
* the current state, without the first @aSize bytes.
*
* This should be used during an unbuffered read initiated by ToUnbuffered().
*/
template <typename State>
static LexerTransition<State>
ContinueUnbufferedAfterYield(const State& aUnbufferedState, size_t aSize)
{
return LexerTransition<State>(aUnbufferedState, Nothing(), aSize,
BufferingStrategy::BUFFERED,
ControlFlowStrategy::YIELD);
}
/**
* Terminate lexing, ending up in terminal state SUCCESS. (The implicit
* LexerTransition constructor will convert the result to a LexerTransition
* as needed.)
*
* No more data will be delivered after this function is used.
*/
static TerminalState
TerminateSuccess()
{
return TerminalState::SUCCESS;
}
/**
* Terminate lexing, ending up in terminal state FAILURE. (The implicit
* LexerTransition constructor will convert the result to a LexerTransition
* as needed.)
*
* No more data will be delivered after this function is used.
*/
static TerminalState
TerminateFailure()
{
return TerminalState::FAILURE;
}
private:
Transition();
};
/**
* StreamingLexer is a lexing framework designed to make it simple to write
* image decoders without worrying about the details of how the data is arriving
* from the network.
*
* To use StreamingLexer:
*
* - Create a State type. This should be an |enum class| listing all of the
* states that you can be in while lexing the image format you're trying to
* read.
*
* - Add an instance of StreamingLexer<State> to your decoder class. Initialize
* it with a Transition::To() the state that you want to start lexing in, and
* a Transition::To() the state you'd like to use to handle truncated data.
*
* - In your decoder's DoDecode() method, call Lex(), passing in the input
* data and length that are passed to DoDecode(). You also need to pass
* a lambda which dispatches to lexing code for each state based on the State
* value that's passed in. The lambda generally should just continue a
* |switch| statement that calls different methods for each State value. Each
* method should return a LexerTransition<State>, which the lambda should
* return in turn.
*
* - Write the methods that actually implement lexing for your image format.
* These methods should return either Transition::To(), to move on to another
* state, or Transition::Terminate{Success,Failure}(), if lexing has
* terminated in either success or failure. (There are also additional
* transitions for unbuffered reads; see below.)
*
* That's the basics. The StreamingLexer will track your position in the input
* and buffer enough data so that your lexing methods can process everything in
* one pass. Lex() returns Yield::NEED_MORE_DATA if more data is needed, in
* which case you should just return from DoDecode(). If lexing reaches a
* terminal state, Lex() returns TerminalState::SUCCESS or
* TerminalState::FAILURE, and you can check which one to determine if lexing
* succeeded or failed and do any necessary cleanup.
*
* Sometimes, the input data is truncated. StreamingLexer will notify you when
* this happens by invoking the truncated data state you passed to the
* constructor. At this point you can attempt to recover and return
* TerminalState::SUCCESS or TerminalState::FAILURE, depending on whether you
* were successful. Note that you can't return anything other than a terminal
* state in this situation, since there's no more data to read. For the same
* reason, your truncated data state shouldn't require any data. (That is, the
* @aSize argument you pass to Transition::To() must be zero.) Violating these
* requirements will trigger assertions and an immediate transition to
* TerminalState::FAILURE.
*
* Some lexers may want to *avoid* buffering in some cases, and just process the
* data as it comes in. This is useful if, for example, you just want to skip
* over a large section of data; there's no point in buffering data you're just
* going to ignore.
*
* You can begin an unbuffered read with Transition::ToUnbuffered(). This works
* a little differently than Transition::To() in that you specify *two* states.
* The @aUnbufferedState argument specifies a state that will be called
* repeatedly with unbuffered data, as soon as it arrives. The implementation
* for that state should return either a transition to a terminal state, or a
* Transition::ContinueUnbuffered() to the same @aUnbufferedState. (From a
* technical perspective, it's not necessary to specify the state again, but
* it's helpful to human readers.) Once the amount of data requested in the
* original call to Transition::ToUnbuffered() has been delivered, Lex() will
* transition to the @aNextState state specified via Transition::ToUnbuffered().
* That state will be invoked with *no* data; it's just called to signal that
* the unbuffered read is over.
*
* It's sometimes useful for a lexer to provide incremental results, rather
* than simply running to completion and presenting all its output at once. For
* example, when decoding animated images, it may be useful to produce each
* frame incrementally. StreamingLexer supports this by allowing a lexer to
* yield.
*
* To yield back to the caller, a state implementation can simply return
* Transition::ToAfterYield(). ToAfterYield()'s @aNextState argument specifies
* the next state that the lexer should transition to, just like when using
* Transition::To(), but there are two differences. One is that Lex() will
* return to the caller before processing any more data when it encounters a
* yield transition. This provides an opportunity for the caller to interact with the
* lexer's intermediate results. The second difference is that @aNextState
* will be called with *the same data as the state that you returned
* Transition::ToAfterYield() from*. This allows a lexer to partially consume
* the data, return intermediate results, and then finish consuming the data
* when @aNextState is called.
*
* It's also possible to yield during an unbuffered read. Just return a
* Transition::ContinueUnbufferedAfterYield(). Just like with
* Transition::ContinueUnbuffered(), the @aUnbufferedState must be the same as
* the one originally passed to Transition::ToUnbuffered(). The second argument,
* @aSize, specifies the amount of data that the lexer has already consumed.
* When @aUnbufferedState is next invoked, it will get the same data that it
* received previously, except that the first @aSize bytes will be excluded.
* This makes it easy to consume unbuffered data incrementally.
*
* XXX(seth): We should be able to get of the |State| stuff totally once bug
* 1198451 lands, since we can then just return a function representing the next
* state directly.
*/
template <typename State, size_t InlineBufferSize = 16>
class StreamingLexer
{
public:
StreamingLexer(LexerTransition<State> aStartState,
LexerTransition<State> aTruncatedState)
: mTransition(TerminalState::FAILURE)
, mTruncatedTransition(aTruncatedState)
{
if (!aStartState.NextStateIsTerminal() &&
aStartState.ControlFlow() == ControlFlowStrategy::YIELD) {
// Allowing a StreamingLexer to start in a yield state doesn't make sense
// semantically (since yield states are supposed to deliver the same data
// as previous states, and there's no previous state here), but more
// importantly, it's necessary to advance a SourceBufferIterator at least
// once before you can read from it, and adding the necessary checks to
// Lex() to avoid that issue has the potential to mask real bugs. So
// instead, it's better to forbid starting in a yield state.
MOZ_ASSERT_UNREACHABLE("Starting in a yield state");
return;
}
if (!aTruncatedState.NextStateIsTerminal() &&
(aTruncatedState.ControlFlow() == ControlFlowStrategy::YIELD ||
aTruncatedState.Buffering() == BufferingStrategy::UNBUFFERED ||
aTruncatedState.Size() != 0)) {
// The truncated state can't receive any data because, by definition,
// there is no more data to receive. That means that yielding or an
// unbuffered read would not make sense, and that the state must require
// zero bytes.
MOZ_ASSERT_UNREACHABLE("Truncated state makes no sense");
return;
}
SetTransition(aStartState);
}
template <typename Func>
LexerResult Lex(SourceBufferIterator& aIterator,
IResumable* aOnResume,
Func aFunc)
{
if (mTransition.NextStateIsTerminal()) {
// We've already reached a terminal state. We never deliver any more data
// in this case; just return the terminal state again immediately.
return LexerResult(mTransition.NextStateAsTerminal());
}
Maybe<LexerResult> result;
// If the lexer requested a yield last time, we deliver the same data again
// before we read anything else from |aIterator|. Note that although to the
// callers of Lex(), Yield::NEED_MORE_DATA is just another type of yield,
// internally they're different in that we don't redeliver the same data in
// the Yield::NEED_MORE_DATA case, and |mYieldingToState| is not set. This
// means that for Yield::NEED_MORE_DATA, we go directly to the loop below.
if (mYieldingToState) {
result = mTransition.Buffering() == BufferingStrategy::UNBUFFERED
? UnbufferedReadAfterYield(aIterator, aFunc)
: BufferedReadAfterYield(aIterator, aFunc);
}
while (!result) {
MOZ_ASSERT_IF(mTransition.Buffering() == BufferingStrategy::UNBUFFERED,
mUnbufferedState);
// Figure out how much we need to read.
const size_t toRead = mTransition.Buffering() == BufferingStrategy::UNBUFFERED
? mUnbufferedState->mBytesRemaining
: mTransition.Size() - mBuffer.length();
// Attempt to advance the iterator by |toRead| bytes.
switch (aIterator.AdvanceOrScheduleResume(toRead, aOnResume)) {
case SourceBufferIterator::WAITING:
// We can't continue because the rest of the data hasn't arrived from
// the network yet. We don't have to do anything special; the
// SourceBufferIterator will ensure that |aOnResume| gets called when
// more data is available.
result = Some(LexerResult(Yield::NEED_MORE_DATA));
break;
case SourceBufferIterator::COMPLETE:
// The data is truncated; if not, the lexer would've reached a
// terminal state by now. We only get to
// SourceBufferIterator::COMPLETE after every byte of data has been
// delivered to the lexer.
result = Truncated(aIterator, aFunc);
break;
case SourceBufferIterator::READY:
// Process the new data that became available.
MOZ_ASSERT(aIterator.Data());
result = mTransition.Buffering() == BufferingStrategy::UNBUFFERED
? UnbufferedRead(aIterator, aFunc)
: BufferedRead(aIterator, aFunc);
break;
default:
MOZ_ASSERT_UNREACHABLE("Unknown SourceBufferIterator state");
result = SetTransition(Transition::TerminateFailure());
}
};
return *result;
}
private:
template <typename Func>
Maybe<LexerResult> UnbufferedRead(SourceBufferIterator& aIterator, Func aFunc)
{
MOZ_ASSERT(mTransition.Buffering() == BufferingStrategy::UNBUFFERED);
MOZ_ASSERT(mUnbufferedState);
MOZ_ASSERT(!mYieldingToState);
MOZ_ASSERT(mBuffer.empty(),
"Buffered read at the same time as unbuffered read?");
MOZ_ASSERT(aIterator.Length() <= mUnbufferedState->mBytesRemaining,
"Read too much data during unbuffered read?");
MOZ_ASSERT(mUnbufferedState->mBytesConsumedInCurrentChunk == 0,
"Already consumed data in the current chunk, but not yielding?");
if (mUnbufferedState->mBytesRemaining == 0) {
// We're done with the unbuffered read, so transition to the next state.
return SetTransition(aFunc(mTransition.NextState(), nullptr, 0));
}
return ContinueUnbufferedRead(aIterator.Data(), aIterator.Length(),
aIterator.Length(), aFunc);
}
template <typename Func>
Maybe<LexerResult> UnbufferedReadAfterYield(SourceBufferIterator& aIterator, Func aFunc)
{
MOZ_ASSERT(mTransition.Buffering() == BufferingStrategy::UNBUFFERED);
MOZ_ASSERT(mUnbufferedState);
MOZ_ASSERT(mYieldingToState);
MOZ_ASSERT(mBuffer.empty(),
"Buffered read at the same time as unbuffered read?");
MOZ_ASSERT(aIterator.Length() <= mUnbufferedState->mBytesRemaining,
"Read too much data during unbuffered read?");
MOZ_ASSERT(mUnbufferedState->mBytesConsumedInCurrentChunk <= aIterator.Length(),
"Consumed more data than the current chunk holds?");
MOZ_ASSERT(mTransition.UnbufferedState() == *mYieldingToState);
mYieldingToState = Nothing();
if (mUnbufferedState->mBytesRemaining == 0) {
// We're done with the unbuffered read, so transition to the next state.
return SetTransition(aFunc(mTransition.NextState(), nullptr, 0));
}
// Since we've yielded, we may have already consumed some data in this
// chunk. Make the necessary adjustments. (Note that the std::min call is
// just belt-and-suspenders to keep this code memory safe even if there's
// a bug somewhere.)
const size_t toSkip =
std::min(mUnbufferedState->mBytesConsumedInCurrentChunk, aIterator.Length());
const char* data = aIterator.Data() + toSkip;
const size_t length = aIterator.Length() - toSkip;
// If |length| is zero, we've hit the end of the current chunk. This only
// happens if we yield right at the end of a chunk. Rather than call |aFunc|
// with a |length| of zero bytes (which seems potentially surprising to
// decoder authors), we go ahead and read more data.
if (length == 0) {
return FinishCurrentChunkOfUnbufferedRead(aIterator.Length());
}
return ContinueUnbufferedRead(data, length, aIterator.Length(), aFunc);
}
template <typename Func>
Maybe<LexerResult> ContinueUnbufferedRead(const char* aData,
size_t aLength,
size_t aChunkLength,
Func aFunc)
{
// Call aFunc with the unbuffered state to indicate that we're in the
// middle of an unbuffered read. We enforce that any state transition
// passed back to us is either a terminal state or takes us back to the
// unbuffered state.
LexerTransition<State> unbufferedTransition =
aFunc(mTransition.UnbufferedState(), aData, aLength);
// If we reached a terminal state, we're done.
if (unbufferedTransition.NextStateIsTerminal()) {
return SetTransition(unbufferedTransition);
}
MOZ_ASSERT(mTransition.UnbufferedState() ==
unbufferedTransition.NextState());
// Perform bookkeeping.
if (unbufferedTransition.ControlFlow() == ControlFlowStrategy::YIELD) {
mUnbufferedState->mBytesConsumedInCurrentChunk += unbufferedTransition.Size();
return SetTransition(unbufferedTransition);
}
MOZ_ASSERT(unbufferedTransition.Size() == 0);
return FinishCurrentChunkOfUnbufferedRead(aChunkLength);
}
Maybe<LexerResult> FinishCurrentChunkOfUnbufferedRead(size_t aChunkLength)
{
// We've finished an unbuffered read of a chunk of length |aChunkLength|, so
// update |myBytesRemaining| to reflect that we're |aChunkLength| closer to
// the end of the unbuffered read. (The std::min call is just
// belt-and-suspenders to keep this code memory safe even if there's a bug
// somewhere.)
mUnbufferedState->mBytesRemaining -=
std::min(mUnbufferedState->mBytesRemaining, aChunkLength);
// Since we're moving on to a new chunk, we can forget about the count of
// bytes consumed by yielding in the current chunk.
mUnbufferedState->mBytesConsumedInCurrentChunk = 0;
return Nothing(); // Keep processing.
}
template <typename Func>
Maybe<LexerResult> BufferedRead(SourceBufferIterator& aIterator, Func aFunc)
{
MOZ_ASSERT(mTransition.Buffering() == BufferingStrategy::BUFFERED);
MOZ_ASSERT(!mYieldingToState);
MOZ_ASSERT(!mUnbufferedState,
"Buffered read at the same time as unbuffered read?");
MOZ_ASSERT(mBuffer.length() < mTransition.Size() ||
(mBuffer.length() == 0 && mTransition.Size() == 0),
"Buffered more than we needed?");
// If we have all the data, we don't actually need to buffer anything.
if (mBuffer.empty() && aIterator.Length() == mTransition.Size()) {
return SetTransition(aFunc(mTransition.NextState(),
aIterator.Data(),
aIterator.Length()));
}
// We do need to buffer, so make sure the buffer has enough capacity. We
// deliberately wait until we know for sure we need to buffer to call
// reserve() since it could require memory allocation.
if (!mBuffer.reserve(mTransition.Size())) {
return SetTransition(Transition::TerminateFailure());
}
// Append the new data we just got to the buffer.
if (!mBuffer.append(aIterator.Data(), aIterator.Length())) {
return SetTransition(Transition::TerminateFailure());
}
if (mBuffer.length() != mTransition.Size()) {
return Nothing(); // Keep processing.
}
// We've buffered everything, so transition to the next state.
return SetTransition(aFunc(mTransition.NextState(),
mBuffer.begin(),
mBuffer.length()));
}
template <typename Func>
Maybe<LexerResult> BufferedReadAfterYield(SourceBufferIterator& aIterator,
Func aFunc)
{
MOZ_ASSERT(mTransition.Buffering() == BufferingStrategy::BUFFERED);
MOZ_ASSERT(mYieldingToState);
MOZ_ASSERT(!mUnbufferedState,
"Buffered read at the same time as unbuffered read?");
MOZ_ASSERT(mBuffer.length() <= mTransition.Size(),
"Buffered more than we needed?");
State nextState = Move(*mYieldingToState);
// After a yield, we need to take the same data that we delivered to the
// last state, and deliver it again to the new state. We know that this is
// happening right at a state transition, and that the last state was a
// buffered read, so there are two cases:
// 1. We got the data from the SourceBufferIterator directly.
if (mBuffer.empty() && aIterator.Length() == mTransition.Size()) {
return SetTransition(aFunc(nextState,
aIterator.Data(),
aIterator.Length()));
}
// 2. We got the data from the buffer.
if (mBuffer.length() == mTransition.Size()) {
return SetTransition(aFunc(nextState,
mBuffer.begin(),
mBuffer.length()));
}
// Anything else indicates a bug.
MOZ_ASSERT_UNREACHABLE("Unexpected state encountered during yield");
return SetTransition(Transition::TerminateFailure());
}
template <typename Func>
Maybe<LexerResult> Truncated(SourceBufferIterator& aIterator,
Func aFunc)
{
// The data is truncated. Let the lexer clean up and decide which terminal
// state we should end up in.
LexerTransition<State> transition
= mTruncatedTransition.NextStateIsTerminal()
? mTruncatedTransition
: aFunc(mTruncatedTransition.NextState(), nullptr, 0);
if (!transition.NextStateIsTerminal()) {
MOZ_ASSERT_UNREACHABLE("Truncated state didn't lead to terminal state?");
return SetTransition(Transition::TerminateFailure());
}
// If the SourceBuffer was completed with a failing state, we end in
// TerminalState::FAILURE no matter what. This only happens in exceptional
// situations like SourceBuffer itself encountering a failure due to OOM.
if (NS_FAILED(aIterator.CompletionStatus())) {
return SetTransition(Transition::TerminateFailure());
}
return SetTransition(transition);
}
Maybe<LexerResult> SetTransition(const LexerTransition<State>& aTransition)
{
// There should be no transitions while we're buffering for a buffered read
// unless they're to terminal states. (The terminal state transitions would
// generally be triggered by error handling code.)
MOZ_ASSERT_IF(!mBuffer.empty(),
aTransition.NextStateIsTerminal() ||
mBuffer.length() == mTransition.Size());
// Similarly, the only transitions allowed in the middle of an unbuffered
// read are to a terminal state, or a yield to the same state. Otherwise, we
// should remain in the same state until the unbuffered read completes.
MOZ_ASSERT_IF(mUnbufferedState,
aTransition.NextStateIsTerminal() ||
(aTransition.ControlFlow() == ControlFlowStrategy::YIELD &&
aTransition.NextState() == mTransition.UnbufferedState()) ||
mUnbufferedState->mBytesRemaining == 0);
// If this transition is a yield, save the next state and return. We'll
// handle the rest when Lex() gets called again.
if (!aTransition.NextStateIsTerminal() &&
aTransition.ControlFlow() == ControlFlowStrategy::YIELD) {
mYieldingToState = Some(aTransition.NextState());
return Some(LexerResult(Yield::OUTPUT_AVAILABLE));
}
// Update our transition.
mTransition = aTransition;
// Get rid of anything left over from the previous state.
mBuffer.clear();
mYieldingToState = Nothing();
mUnbufferedState = Nothing();
// If we reached a terminal state, let the caller know.
if (mTransition.NextStateIsTerminal()) {
return Some(LexerResult(mTransition.NextStateAsTerminal()));
}
// If we're entering an unbuffered state, record how long we'll stay in it.
if (mTransition.Buffering() == BufferingStrategy::UNBUFFERED) {
mUnbufferedState.emplace(mTransition.Size());
}
return Nothing(); // Keep processing.
}
// State that tracks our position within an unbuffered read.
struct UnbufferedState
{
explicit UnbufferedState(size_t aBytesRemaining)
: mBytesRemaining(aBytesRemaining)
, mBytesConsumedInCurrentChunk(0)
{ }
size_t mBytesRemaining;
size_t mBytesConsumedInCurrentChunk;
};
Vector<char, InlineBufferSize> mBuffer;
LexerTransition<State> mTransition;
const LexerTransition<State> mTruncatedTransition;
Maybe<State> mYieldingToState;
Maybe<UnbufferedState> mUnbufferedState;
};
} // namespace image
} // namespace mozilla
#endif // mozilla_image_StreamingLexer_h

1164
image/SurfaceCache.cpp Normal file

File diff suppressed because it is too large Load diff

427
image/SurfaceCache.h Normal file
View file

@ -0,0 +1,427 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* 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/. */
/**
* SurfaceCache is a service for caching temporary surfaces and decoded image
* data in imagelib.
*/
#ifndef mozilla_image_SurfaceCache_h
#define mozilla_image_SurfaceCache_h
#include "mozilla/Maybe.h" // for Maybe
#include "mozilla/NotNull.h"
#include "mozilla/MemoryReporting.h" // for MallocSizeOf
#include "mozilla/HashFunctions.h" // for HashGeneric and AddToHash
#include "gfx2DGlue.h"
#include "gfxPoint.h" // for gfxSize
#include "nsCOMPtr.h" // for already_AddRefed
#include "mozilla/gfx/Point.h" // for mozilla::gfx::IntSize
#include "mozilla/gfx/2D.h" // for SourceSurface
#include "PlaybackType.h"
#include "SurfaceFlags.h"
#include "SVGImageContext.h" // for SVGImageContext
namespace mozilla {
namespace image {
class Image;
class ISurfaceProvider;
class LookupResult;
class SurfaceCacheImpl;
struct SurfaceMemoryCounter;
/*
* ImageKey contains the information we need to look up all SurfaceCache entries
* for a particular image.
*/
typedef Image* ImageKey;
/*
* SurfaceKey contains the information we need to look up a specific
* SurfaceCache entry. Together with an ImageKey, this uniquely identifies the
* surface.
*
* Callers should construct a SurfaceKey using the appropriate helper function
* for their image type - either RasterSurfaceKey or VectorSurfaceKey.
*/
class SurfaceKey
{
typedef gfx::IntSize IntSize;
public:
bool operator==(const SurfaceKey& aOther) const
{
return aOther.mSize == mSize &&
aOther.mSVGContext == mSVGContext &&
aOther.mPlayback == mPlayback &&
aOther.mFlags == mFlags;
}
uint32_t Hash() const
{
uint32_t hash = HashGeneric(mSize.width, mSize.height);
hash = AddToHash(hash, mSVGContext.map(HashSIC).valueOr(0));
hash = AddToHash(hash, uint8_t(mPlayback), uint32_t(mFlags));
return hash;
}
const IntSize& Size() const { return mSize; }
Maybe<SVGImageContext> SVGContext() const { return mSVGContext; }
PlaybackType Playback() const { return mPlayback; }
SurfaceFlags Flags() const { return mFlags; }
private:
SurfaceKey(const IntSize& aSize,
const Maybe<SVGImageContext>& aSVGContext,
PlaybackType aPlayback,
SurfaceFlags aFlags)
: mSize(aSize)
, mSVGContext(aSVGContext)
, mPlayback(aPlayback)
, mFlags(aFlags)
{ }
static uint32_t HashSIC(const SVGImageContext& aSIC) {
return aSIC.Hash();
}
friend SurfaceKey RasterSurfaceKey(const IntSize&, SurfaceFlags, PlaybackType);
friend SurfaceKey VectorSurfaceKey(const IntSize&,
const Maybe<SVGImageContext>&);
IntSize mSize;
Maybe<SVGImageContext> mSVGContext;
PlaybackType mPlayback;
SurfaceFlags mFlags;
};
inline SurfaceKey
RasterSurfaceKey(const gfx::IntSize& aSize,
SurfaceFlags aFlags,
PlaybackType aPlayback)
{
return SurfaceKey(aSize, Nothing(), aPlayback, aFlags);
}
inline SurfaceKey
VectorSurfaceKey(const gfx::IntSize& aSize,
const Maybe<SVGImageContext>& aSVGContext)
{
// We don't care about aFlags for VectorImage because none of the flags we
// have right now influence VectorImage's rendering. If we add a new flag that
// *does* affect how a VectorImage renders, we'll have to change this.
// Similarly, we don't accept a PlaybackType parameter because we don't
// currently cache frames of animated SVG images.
return SurfaceKey(aSize, aSVGContext, PlaybackType::eStatic,
DefaultSurfaceFlags());
}
/**
* AvailabilityState is used to track whether an ISurfaceProvider has a surface
* available or is just a placeholder.
*
* To ensure that availability changes are atomic (and especially that internal
* SurfaceCache code doesn't have to deal with asynchronous availability
* changes), an ISurfaceProvider which starts as a placeholder can only reveal
* the fact that it now has a surface available via a call to
* SurfaceCache::SurfaceAvailable().
*/
class AvailabilityState
{
public:
static AvailabilityState StartAvailable() { return AvailabilityState(true); }
static AvailabilityState StartAsPlaceholder() { return AvailabilityState(false); }
bool IsAvailable() const { return mIsAvailable; }
bool IsPlaceholder() const { return !mIsAvailable; }
private:
friend class SurfaceCacheImpl;
explicit AvailabilityState(bool aIsAvailable) : mIsAvailable(aIsAvailable) { }
void SetAvailable() { mIsAvailable = true; }
bool mIsAvailable;
};
enum class InsertOutcome : uint8_t {
SUCCESS, // Success (but see Insert documentation).
FAILURE, // Couldn't insert (e.g., for capacity reasons).
FAILURE_ALREADY_PRESENT // A surface with the same key is already present.
};
/**
* SurfaceCache is an ImageLib-global service that allows caching of decoded
* image surfaces, temporary surfaces (e.g. for caching rotated or clipped
* versions of images), or dynamically generated surfaces (e.g. for animations).
* SurfaceCache entries normally expire from the cache automatically if they go
* too long without being accessed.
*
* Because SurfaceCache must support both normal surfaces and dynamically
* generated surfaces, it does not actually hold surfaces directly. Instead, it
* holds ISurfaceProvider objects which can provide access to a surface when
* requested; SurfaceCache doesn't care about the details of how this is
* accomplished.
*
* Sometime it's useful to temporarily prevent entries from expiring from the
* cache. This is most often because losing the data could harm the user
* experience (for example, we often don't want to allow surfaces that are
* currently visible to expire) or because it's not possible to rematerialize
* the surface. SurfaceCache supports this through the use of image locking; see
* the comments for Insert() and LockImage() for more details.
*
* Any image which stores surfaces in the SurfaceCache *must* ensure that it
* calls RemoveImage() before it is destroyed. See the comments for
* RemoveImage() for more details.
*/
struct SurfaceCache
{
typedef gfx::IntSize IntSize;
/**
* Initialize static data. Called during imagelib module initialization.
*/
static void Initialize();
/**
* Release static data. Called during imagelib module shutdown.
*/
static void Shutdown();
/**
* Looks up the requested cache entry and returns a drawable reference to its
* associated surface.
*
* If the image associated with the cache entry is locked, then the entry will
* be locked before it is returned.
*
* If a matching ISurfaceProvider was found in the cache, but SurfaceCache
* couldn't obtain a surface from it (e.g. because it had stored its surface
* in a volatile buffer which was discarded by the OS) then it is
* automatically removed from the cache and an empty LookupResult is returned.
* Note that this will never happen to ISurfaceProviders associated with a
* locked image; SurfaceCache tells such ISurfaceProviders to keep a strong
* references to their data internally.
*
* @param aImageKey Key data identifying which image the cache entry
* belongs to.
* @param aSurfaceKey Key data which uniquely identifies the requested
* cache entry.
* @return a LookupResult which will contain a DrawableSurface
* if the cache entry was found.
*/
static LookupResult Lookup(const ImageKey aImageKey,
const SurfaceKey& aSurfaceKey);
/**
* Looks up the best matching cache entry and returns a drawable reference to
* its associated surface.
*
* The result may vary from the requested cache entry only in terms of size.
*
* @param aImageKey Key data identifying which image the cache entry
* belongs to.
* @param aSurfaceKey Key data which uniquely identifies the requested
* cache entry.
* @return a LookupResult which will contain a DrawableSurface
* if a cache entry similar to the one the caller
* requested could be found. Callers can use
* LookupResult::IsExactMatch() to check whether the
* returned surface exactly matches @aSurfaceKey.
*/
static LookupResult LookupBestMatch(const ImageKey aImageKey,
const SurfaceKey& aSurfaceKey);
/**
* Insert an ISurfaceProvider into the cache. If an entry with the same
* ImageKey and SurfaceKey is already in the cache, Insert returns
* FAILURE_ALREADY_PRESENT. If a matching placeholder is already present, it
* is replaced.
*
* Cache entries will never expire as long as they remain locked, but if they
* become unlocked, they can expire either because the SurfaceCache runs out
* of capacity or because they've gone too long without being used. When it
* is first inserted, a cache entry is locked if its associated image is
* locked. When that image is later unlocked, the cache entry becomes
* unlocked too. To become locked again at that point, two things must happen:
* the image must become locked again (via LockImage()), and the cache entry
* must be touched again (via one of the Lookup() functions).
*
* All of this means that a very particular procedure has to be followed for
* cache entries which cannot be rematerialized. First, they must be inserted
* *after* the image is locked with LockImage(); if you use the other order,
* the cache entry might expire before LockImage() gets called or before the
* entry is touched again by Lookup(). Second, the image they are associated
* with must never be unlocked.
*
* If a cache entry cannot be rematerialized, it may be important to know
* whether it was inserted into the cache successfully. Insert() returns
* FAILURE if it failed to insert the cache entry, which could happen because
* of capacity reasons, or because it was already freed by the OS. If the
* cache entry isn't associated with a locked image, checking for SUCCESS or
* FAILURE is useless: the entry might expire immediately after being
* inserted, even though Insert() returned SUCCESS. Thus, many callers do not
* need to check the result of Insert() at all.
*
* @param aProvider The new cache entry to insert into the cache.
* @return SUCCESS if the cache entry was inserted successfully. (But see above
* for more information about when you should check this.)
* FAILURE if the cache entry could not be inserted, e.g. for capacity
* reasons. (But see above for more information about when you
* should check this.)
* FAILURE_ALREADY_PRESENT if an entry with the same ImageKey and
* SurfaceKey already exists in the cache.
*/
static InsertOutcome Insert(NotNull<ISurfaceProvider*> aProvider);
/**
* Mark the cache entry @aProvider as having an available surface. This turns
* a placeholder cache entry into a normal cache entry. The cache entry
* becomes locked if the associated image is locked; otherwise, it starts in
* the unlocked state.
*
* If the cache entry containing @aProvider has already been evicted from the
* surface cache, this function has no effect.
*
* It's illegal to call this function if @aProvider is not a placeholder; by
* definition, non-placeholder ISurfaceProviders should have a surface
* available already.
*
* @param aProvider The cache entry that now has a surface available.
*/
static void SurfaceAvailable(NotNull<ISurfaceProvider*> aProvider);
/**
* Checks if a surface of a given size could possibly be stored in the cache.
* If CanHold() returns false, Insert() will always fail to insert the
* surface, but the inverse is not true: Insert() may take more information
* into account than just image size when deciding whether to cache the
* surface, so Insert() may still fail even if CanHold() returns true.
*
* Use CanHold() to avoid the need to create a temporary surface when we know
* for sure the cache can't hold it.
*
* @param aSize The dimensions of a surface in pixels.
* @param aBytesPerPixel How many bytes each pixel of the surface requires.
* Defaults to 4, which is appropriate for RGBA or RGBX
* images.
*
* @return false if the surface cache can't hold a surface of that size.
*/
static bool CanHold(const IntSize& aSize, uint32_t aBytesPerPixel = 4);
static bool CanHold(size_t aSize);
/**
* Locks an image. Any of the image's cache entries which are either inserted
* or accessed while the image is locked will not expire.
*
* Locking an image does not automatically lock that image's existing cache
* entries. A call to LockImage() guarantees that entries which are inserted
* afterward will not expire before the next call to UnlockImage() or
* UnlockSurfaces() for that image. Cache entries that are accessed via
* Lookup() or LookupBestMatch() after a LockImage() call will also not expire
* until the next UnlockImage() or UnlockSurfaces() call for that image. Any
* other cache entries owned by the image may expire at any time.
*
* All of an image's cache entries are removed by RemoveImage(), whether the
* image is locked or not.
*
* It's safe to call LockImage() on an image that's already locked; this has
* no effect.
*
* You must always unlock any image you lock. You may do this explicitly by
* calling UnlockImage(), or implicitly by calling RemoveImage(). Since you're
* required to call RemoveImage() when you destroy an image, this doesn't
* impose any additional requirements, but it's preferable to call
* UnlockImage() earlier if it's possible.
*
* @param aImageKey The image to lock.
*/
static void LockImage(const ImageKey aImageKey);
/**
* Unlocks an image, allowing any of its cache entries to expire at any time.
*
* It's OK to call UnlockImage() on an image that's already unlocked; this has
* no effect.
*
* @param aImageKey The image to unlock.
*/
static void UnlockImage(const ImageKey aImageKey);
/**
* Unlocks the existing cache entries of an image, allowing them to expire at
* any time.
*
* This does not unlock the image itself, so accessing the cache entries via
* Lookup() or LookupBestMatch() will lock them again, and prevent them from
* expiring.
*
* This is intended to be used in situations where it's no longer clear that
* all of the cache entries owned by an image are needed. Calling
* UnlockSurfaces() and then taking some action that will cause Lookup() to
* touch any cache entries that are still useful will permit the remaining
* entries to expire from the cache.
*
* If the image is unlocked, this has no effect.
*
* @param aImageKey The image which should have its existing cache entries
* unlocked.
*/
static void UnlockEntries(const ImageKey aImageKey);
/**
* Removes all cache entries (including placeholders) associated with the
* given image from the cache. If the image is locked, it is automatically
* unlocked.
*
* This MUST be called, at a minimum, when an Image which could be storing
* entries in the surface cache is destroyed. If another image were allocated
* at the same address it could result in subtle, difficult-to-reproduce bugs.
*
* @param aImageKey The image which should be removed from the cache.
*/
static void RemoveImage(const ImageKey aImageKey);
/**
* Evicts all evictable entries from the cache.
*
* All entries are evictable except for entries associated with locked images.
* Non-evictable entries can only be removed by RemoveImage().
*/
static void DiscardAll();
/**
* Collects an accounting of the surfaces contained in the SurfaceCache for
* the given image, along with their size and various other metadata.
*
* This is intended for use with memory reporting.
*
* @param aImageKey The image to report memory usage for.
* @param aCounters An array into which the report for each surface will
* be written.
* @param aMallocSizeOf A fallback malloc memory reporting function.
*/
static void CollectSizeOfSurfaces(const ImageKey aImageKey,
nsTArray<SurfaceMemoryCounter>& aCounters,
MallocSizeOf aMallocSizeOf);
/**
* @return maximum capacity of the SurfaceCache in bytes. This is only exposed
* for use by tests; normal code should use CanHold() instead.
*/
static size_t MaximumCapacity();
private:
virtual ~SurfaceCache() = 0; // Forbid instantiation.
};
} // namespace image
} // namespace mozilla
#endif // mozilla_image_SurfaceCache_h

View file

@ -0,0 +1,20 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* 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/. */
#include "SurfaceCacheUtils.h"
#include "SurfaceCache.h"
namespace mozilla {
namespace image {
/* static */ void
SurfaceCacheUtils::DiscardAll()
{
SurfaceCache::DiscardAll();
}
} // namespace image
} // namespace mozilla

34
image/SurfaceCacheUtils.h Normal file
View file

@ -0,0 +1,34 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* 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/. */
#ifndef mozilla_image_SurfaceCacheUtils_h
#define mozilla_image_SurfaceCacheUtils_h
/**
* SurfaceCacheUtils provides an ImageLib-external API to interact with
* ImageLib's SurfaceCache.
*/
namespace mozilla {
namespace image {
class SurfaceCacheUtils
{
public:
/**
* Evicts all evictable entries from the surface cache.
*
* See the documentation for SurfaceCache::DiscardAll() for the details.
*/
static void DiscardAll();
private:
virtual ~SurfaceCacheUtils() = 0; // Forbid instantiation.
};
} // namespace image
} // namespace mozilla
#endif // mozilla_image_SurfaceCacheUtils_h

893
image/SurfaceFilters.h Normal file
View file

@ -0,0 +1,893 @@
/* -*- 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/. */
/**
* This header contains various SurfaceFilter implementations that apply
* transformations to image data, for usage with SurfacePipe.
*/
#ifndef mozilla_image_SurfaceFilters_h
#define mozilla_image_SurfaceFilters_h
#include <algorithm>
#include <stdint.h>
#include <string.h>
#include "mozilla/Likely.h"
#include "mozilla/Maybe.h"
#include "mozilla/UniquePtr.h"
#include "mozilla/gfx/2D.h"
#include "DownscalingFilter.h"
#include "SurfaceCache.h"
#include "SurfacePipe.h"
namespace mozilla {
namespace image {
//////////////////////////////////////////////////////////////////////////////
// DeinterlacingFilter
//////////////////////////////////////////////////////////////////////////////
template <typename PixelType, typename Next> class DeinterlacingFilter;
/**
* A configuration struct for DeinterlacingFilter.
*
* The 'PixelType' template parameter should be either uint32_t (for output to a
* SurfaceSink) or uint8_t (for output to a PalettedSurfaceSink).
*/
template <typename PixelType>
struct DeinterlacingConfig
{
template <typename Next> using Filter = DeinterlacingFilter<PixelType, Next>;
bool mProgressiveDisplay; /// If true, duplicate rows during deinterlacing
/// to make progressive display look better, at
/// the cost of some performance.
};
/**
* DeinterlacingFilter performs deinterlacing by reordering the rows that are
* written to it.
*
* The 'PixelType' template parameter should be either uint32_t (for output to a
* SurfaceSink) or uint8_t (for output to a PalettedSurfaceSink).
*
* The 'Next' template parameter specifies the next filter in the chain.
*/
template <typename PixelType, typename Next>
class DeinterlacingFilter final : public SurfaceFilter
{
public:
DeinterlacingFilter()
: mInputRow(0)
, mOutputRow(0)
, mPass(0)
, mProgressiveDisplay(true)
{ }
template <typename... Rest>
nsresult Configure(const DeinterlacingConfig<PixelType>& aConfig, Rest... aRest)
{
nsresult rv = mNext.Configure(aRest...);
if (NS_FAILED(rv)) {
return rv;
}
if (sizeof(PixelType) == 1 && !mNext.IsValidPalettedPipe()) {
NS_WARNING("Paletted DeinterlacingFilter used with non-paletted pipe?");
return NS_ERROR_INVALID_ARG;
}
if (sizeof(PixelType) == 4 && mNext.IsValidPalettedPipe()) {
NS_WARNING("Non-paletted DeinterlacingFilter used with paletted pipe?");
return NS_ERROR_INVALID_ARG;
}
gfx::IntSize outputSize = mNext.InputSize();
mProgressiveDisplay = aConfig.mProgressiveDisplay;
const uint32_t bufferSize = outputSize.width *
outputSize.height *
sizeof(PixelType);
// Use the size of the SurfaceCache as a heuristic to avoid gigantic
// allocations. Even if DownscalingFilter allowed us to allocate space for
// the output image, the deinterlacing buffer may still be too big, and
// fallible allocation won't always save us in the presence of overcommit.
if (!SurfaceCache::CanHold(bufferSize)) {
return NS_ERROR_OUT_OF_MEMORY;
}
// Allocate the buffer, which contains deinterlaced scanlines of the image.
// The buffer is necessary so that we can output rows which have already
// been deinterlaced again on subsequent passes. Since a later stage in the
// pipeline may be transforming the rows it receives (for example, by
// downscaling them), the rows may no longer exist in their original form on
// the surface itself.
mBuffer.reset(new (fallible) uint8_t[bufferSize]);
if (MOZ_UNLIKELY(!mBuffer)) {
return NS_ERROR_OUT_OF_MEMORY;
}
// Clear the buffer to avoid writing uninitialized memory to the output.
memset(mBuffer.get(), 0, bufferSize);
ConfigureFilter(outputSize, sizeof(PixelType));
return NS_OK;
}
bool IsValidPalettedPipe() const override
{
return sizeof(PixelType) == 1 && mNext.IsValidPalettedPipe();
}
Maybe<SurfaceInvalidRect> TakeInvalidRect() override
{
return mNext.TakeInvalidRect();
}
protected:
uint8_t* DoResetToFirstRow() override
{
mNext.ResetToFirstRow();
mPass = 0;
mInputRow = 0;
mOutputRow = InterlaceOffset(mPass);
return GetRowPointer(mOutputRow);
}
uint8_t* DoAdvanceRow() override
{
if (mPass >= 4) {
return nullptr; // We already finished all passes.
}
if (mInputRow >= InputSize().height) {
return nullptr; // We already got all the input rows we expect.
}
// Duplicate from the first Haeberli row to the remaining Haeberli rows
// within the buffer.
DuplicateRows(HaeberliOutputStartRow(mPass, mProgressiveDisplay, mOutputRow),
HaeberliOutputUntilRow(mPass, mProgressiveDisplay,
InputSize(), mOutputRow));
// Write the current set of Haeberli rows (which contains the current row)
// to the next stage in the pipeline.
OutputRows(HaeberliOutputStartRow(mPass, mProgressiveDisplay, mOutputRow),
HaeberliOutputUntilRow(mPass, mProgressiveDisplay,
InputSize(), mOutputRow));
// Determine which output row the next input row corresponds to.
bool advancedPass = false;
uint32_t stride = InterlaceStride(mPass);
int32_t nextOutputRow = mOutputRow + stride;
while (nextOutputRow >= InputSize().height) {
// Copy any remaining rows from the buffer.
if (!advancedPass) {
OutputRows(HaeberliOutputUntilRow(mPass, mProgressiveDisplay,
InputSize(), mOutputRow),
InputSize().height);
}
// We finished the current pass; advance to the next one.
mPass++;
if (mPass >= 4) {
return nullptr; // Finished all passes.
}
// Tell the next pipeline stage that we're starting the next pass.
mNext.ResetToFirstRow();
// Update our state to reflect the pass change.
advancedPass = true;
stride = InterlaceStride(mPass);
nextOutputRow = InterlaceOffset(mPass);
}
MOZ_ASSERT(nextOutputRow >= 0);
MOZ_ASSERT(nextOutputRow < InputSize().height);
MOZ_ASSERT(HaeberliOutputStartRow(mPass, mProgressiveDisplay,
nextOutputRow) >= 0);
MOZ_ASSERT(HaeberliOutputStartRow(mPass, mProgressiveDisplay,
nextOutputRow) < InputSize().height);
MOZ_ASSERT(HaeberliOutputStartRow(mPass, mProgressiveDisplay,
nextOutputRow) <= nextOutputRow);
MOZ_ASSERT(HaeberliOutputUntilRow(mPass, mProgressiveDisplay,
InputSize(), nextOutputRow) >= 0);
MOZ_ASSERT(HaeberliOutputUntilRow(mPass, mProgressiveDisplay,
InputSize(), nextOutputRow)
<= InputSize().height);
MOZ_ASSERT(HaeberliOutputUntilRow(mPass, mProgressiveDisplay,
InputSize(), nextOutputRow)
> nextOutputRow);
int32_t nextHaeberliOutputRow =
HaeberliOutputStartRow(mPass, mProgressiveDisplay, nextOutputRow);
// Copy rows from the buffer until we reach the desired output row.
if (advancedPass) {
OutputRows(0, nextHaeberliOutputRow);
} else {
OutputRows(HaeberliOutputUntilRow(mPass, mProgressiveDisplay,
InputSize(), mOutputRow),
nextHaeberliOutputRow);
}
// Update our position within the buffer.
mInputRow++;
mOutputRow = nextOutputRow;
// We'll actually write to the first Haeberli output row, then copy it until
// we reach the last Haeberli output row. The assertions above make sure
// this always includes mOutputRow.
return GetRowPointer(nextHaeberliOutputRow);
}
private:
static uint32_t InterlaceOffset(uint32_t aPass)
{
MOZ_ASSERT(aPass < 4, "Invalid pass");
static const uint8_t offset[] = { 0, 4, 2, 1 };
return offset[aPass];
}
static uint32_t InterlaceStride(uint32_t aPass)
{
MOZ_ASSERT(aPass < 4, "Invalid pass");
static const uint8_t stride[] = { 8, 8, 4, 2 };
return stride[aPass];
}
static int32_t HaeberliOutputStartRow(uint32_t aPass,
bool aProgressiveDisplay,
int32_t aOutputRow)
{
MOZ_ASSERT(aPass < 4, "Invalid pass");
static const uint8_t firstRowOffset[] = { 3, 1, 0, 0 };
if (aProgressiveDisplay) {
return std::max(aOutputRow - firstRowOffset[aPass], 0);
} else {
return aOutputRow;
}
}
static int32_t HaeberliOutputUntilRow(uint32_t aPass,
bool aProgressiveDisplay,
const gfx::IntSize& aInputSize,
int32_t aOutputRow)
{
MOZ_ASSERT(aPass < 4, "Invalid pass");
static const uint8_t lastRowOffset[] = { 4, 2, 1, 0 };
if (aProgressiveDisplay) {
return std::min(aOutputRow + lastRowOffset[aPass],
aInputSize.height - 1)
+ 1; // Add one because this is an open interval on the right.
} else {
return aOutputRow + 1;
}
}
void DuplicateRows(int32_t aStart, int32_t aUntil)
{
MOZ_ASSERT(aStart >= 0);
MOZ_ASSERT(aUntil >= 0);
if (aUntil <= aStart || aStart >= InputSize().height) {
return;
}
// The source row is the first row in the range.
const uint8_t* sourceRowPointer = GetRowPointer(aStart);
// We duplicate the source row into each subsequent row in the range.
for (int32_t destRow = aStart + 1 ; destRow < aUntil ; ++destRow) {
uint8_t* destRowPointer = GetRowPointer(destRow);
memcpy(destRowPointer, sourceRowPointer, InputSize().width * sizeof(PixelType));
}
}
void OutputRows(int32_t aStart, int32_t aUntil)
{
MOZ_ASSERT(aStart >= 0);
MOZ_ASSERT(aUntil >= 0);
if (aUntil <= aStart || aStart >= InputSize().height) {
return;
}
for (int32_t rowToOutput = aStart; rowToOutput < aUntil; ++rowToOutput) {
mNext.WriteBuffer(reinterpret_cast<PixelType*>(GetRowPointer(rowToOutput)));
}
}
uint8_t* GetRowPointer(uint32_t aRow) const
{
uint32_t offset = aRow * InputSize().width * sizeof(PixelType);
MOZ_ASSERT(offset < InputSize().width * InputSize().height * sizeof(PixelType),
"Start of row is outside of image");
MOZ_ASSERT(offset + InputSize().width * sizeof(PixelType)
<= InputSize().width * InputSize().height * sizeof(PixelType),
"End of row is outside of image");
return mBuffer.get() + offset;
}
Next mNext; /// The next SurfaceFilter in the chain.
UniquePtr<uint8_t[]> mBuffer; /// The buffer used to store reordered rows.
int32_t mInputRow; /// The current row we're reading. (0-indexed)
int32_t mOutputRow; /// The current row we're writing. (0-indexed)
uint8_t mPass; /// Which pass we're on. (0-indexed)
bool mProgressiveDisplay; /// If true, duplicate rows to optimize for
/// progressive display.
};
//////////////////////////////////////////////////////////////////////////////
// RemoveFrameRectFilter
//////////////////////////////////////////////////////////////////////////////
template <typename Next> class RemoveFrameRectFilter;
/**
* A configuration struct for RemoveFrameRectFilter.
*/
struct RemoveFrameRectConfig
{
template <typename Next> using Filter = RemoveFrameRectFilter<Next>;
gfx::IntRect mFrameRect; /// The surface subrect which contains data.
};
/**
* RemoveFrameRectFilter turns an image with a frame rect that does not match
* its logical size into an image with no frame rect. It does this by writing
* transparent pixels into any padding regions and throwing away excess data.
*
* The 'Next' template parameter specifies the next filter in the chain.
*/
template <typename Next>
class RemoveFrameRectFilter final : public SurfaceFilter
{
public:
RemoveFrameRectFilter()
: mRow(0)
{ }
template <typename... Rest>
nsresult Configure(const RemoveFrameRectConfig& aConfig, Rest... aRest)
{
nsresult rv = mNext.Configure(aRest...);
if (NS_FAILED(rv)) {
return rv;
}
if (mNext.IsValidPalettedPipe()) {
NS_WARNING("RemoveFrameRectFilter used with paletted pipe?");
return NS_ERROR_INVALID_ARG;
}
mFrameRect = mUnclampedFrameRect = aConfig.mFrameRect;
gfx::IntSize outputSize = mNext.InputSize();
// Forbid frame rects with negative size.
if (aConfig.mFrameRect.width < 0 || aConfig.mFrameRect.height < 0) {
return NS_ERROR_INVALID_ARG;
}
// Clamp mFrameRect to the output size.
gfx::IntRect outputRect(0, 0, outputSize.width, outputSize.height);
mFrameRect = mFrameRect.Intersect(outputRect);
// If there's no intersection, |mFrameRect| will be an empty rect positioned
// at the maximum of |inputRect|'s and |aFrameRect|'s coordinates, which is
// not what we want. Force it to (0, 0) in that case.
if (mFrameRect.IsEmpty()) {
mFrameRect.MoveTo(0, 0);
}
// We don't need an intermediate buffer unless the unclamped frame rect
// width is larger than the clamped frame rect width. In that case, the
// caller will end up writing data that won't end up in the final image at
// all, and we'll need a buffer to give that data a place to go.
if (mFrameRect.width < mUnclampedFrameRect.width) {
mBuffer.reset(new (fallible) uint8_t[mUnclampedFrameRect.width *
sizeof(uint32_t)]);
if (MOZ_UNLIKELY(!mBuffer)) {
return NS_ERROR_OUT_OF_MEMORY;
}
memset(mBuffer.get(), 0, mUnclampedFrameRect.width * sizeof(uint32_t));
}
ConfigureFilter(mUnclampedFrameRect.Size(), sizeof(uint32_t));
return NS_OK;
}
Maybe<SurfaceInvalidRect> TakeInvalidRect() override
{
return mNext.TakeInvalidRect();
}
protected:
uint8_t* DoResetToFirstRow() override
{
uint8_t* rowPtr = mNext.ResetToFirstRow();
if (rowPtr == nullptr) {
mRow = mFrameRect.YMost();
return nullptr;
}
mRow = mUnclampedFrameRect.y;
// Advance the next pipeline stage to the beginning of the frame rect,
// outputting blank rows.
if (mFrameRect.y > 0) {
for (int32_t rowToOutput = 0; rowToOutput < mFrameRect.y ; ++rowToOutput) {
mNext.WriteEmptyRow();
}
}
// We're at the beginning of the frame rect now, so return if we're either
// ready for input or we're already done.
rowPtr = mBuffer ? mBuffer.get() : mNext.CurrentRowPointer();
if (!mFrameRect.IsEmpty() || rowPtr == nullptr) {
// Note that the pointer we're returning is for the next row we're
// actually going to write to, but we may discard writes before that point
// if mRow < mFrameRect.y.
return AdjustRowPointer(rowPtr);
}
// We've finished the region specified by the frame rect, but the frame rect
// is empty, so we need to output the rest of the image immediately. Advance
// to the end of the next pipeline stage's buffer, outputting blank rows.
while (mNext.WriteEmptyRow() == WriteState::NEED_MORE_DATA) { }
mRow = mFrameRect.YMost();
return nullptr; // We're done.
}
uint8_t* DoAdvanceRow() override
{
uint8_t* rowPtr = nullptr;
const int32_t currentRow = mRow;
mRow++;
if (currentRow < mFrameRect.y) {
// This row is outside of the frame rect, so just drop it on the floor.
rowPtr = mBuffer ? mBuffer.get() : mNext.CurrentRowPointer();
return AdjustRowPointer(rowPtr);
} else if (currentRow >= mFrameRect.YMost()) {
NS_WARNING("RemoveFrameRectFilter: Advancing past end of frame rect");
return nullptr;
}
// If we had to buffer, copy the data. Otherwise, just advance the row.
if (mBuffer) {
// We write from the beginning of the buffer unless |mUnclampedFrameRect.x|
// is negative; if that's the case, we have to skip the portion of the
// unclamped frame rect that's outside the row.
uint32_t* source = reinterpret_cast<uint32_t*>(mBuffer.get()) -
std::min(mUnclampedFrameRect.x, 0);
// We write |mFrameRect.width| columns starting at |mFrameRect.x|; we've
// already clamped these values to the size of the output, so we don't
// have to worry about bounds checking here (though WriteBuffer() will do
// it for us in any case).
WriteState state = mNext.WriteBuffer(source, mFrameRect.x, mFrameRect.width);
rowPtr = state == WriteState::NEED_MORE_DATA ? mBuffer.get()
: nullptr;
} else {
rowPtr = mNext.AdvanceRow();
}
// If there's still more data coming or we're already done, just adjust the
// pointer and return.
if (mRow < mFrameRect.YMost() || rowPtr == nullptr) {
return AdjustRowPointer(rowPtr);
}
// We've finished the region specified by the frame rect. Advance to the end
// of the next pipeline stage's buffer, outputting blank rows.
while (mNext.WriteEmptyRow() == WriteState::NEED_MORE_DATA) { }
mRow = mFrameRect.YMost();
return nullptr; // We're done.
}
private:
uint8_t* AdjustRowPointer(uint8_t* aNextRowPointer) const
{
if (mBuffer) {
MOZ_ASSERT(aNextRowPointer == mBuffer.get() || aNextRowPointer == nullptr);
return aNextRowPointer; // No adjustment needed for an intermediate buffer.
}
if (mFrameRect.IsEmpty() ||
mRow >= mFrameRect.YMost() ||
aNextRowPointer == nullptr) {
return nullptr; // Nothing left to write.
}
return aNextRowPointer + mFrameRect.x * sizeof(uint32_t);
}
Next mNext; /// The next SurfaceFilter in the chain.
gfx::IntRect mFrameRect; /// The surface subrect which contains data,
/// clamped to the image size.
gfx::IntRect mUnclampedFrameRect; /// The frame rect before clamping.
UniquePtr<uint8_t[]> mBuffer; /// The intermediate buffer, if one is
/// necessary because the frame rect width
/// is larger than the image's logical width.
int32_t mRow; /// The row in unclamped frame rect space
/// that we're currently writing.
};
//////////////////////////////////////////////////////////////////////////////
// ADAM7InterpolatingFilter
//////////////////////////////////////////////////////////////////////////////
template <typename Next> class ADAM7InterpolatingFilter;
/**
* A configuration struct for ADAM7InterpolatingFilter.
*/
struct ADAM7InterpolatingConfig
{
template <typename Next> using Filter = ADAM7InterpolatingFilter<Next>;
};
/**
* ADAM7InterpolatingFilter performs bilinear interpolation over an ADAM7
* interlaced image.
*
* ADAM7 breaks up the image into 8x8 blocks. On each of the 7 passes, a new set
* of pixels in each block receives their final values, according to the
* following pattern:
*
* 1 6 4 6 2 6 4 6
* 7 7 7 7 7 7 7 7
* 5 6 5 6 5 6 5 6
* 7 7 7 7 7 7 7 7
* 3 6 4 6 3 6 4 6
* 7 7 7 7 7 7 7 7
* 5 6 5 6 5 6 5 6
* 7 7 7 7 7 7 7 7
*
* When rendering the pixels that have not yet received their final values, we
* can get much better intermediate results if we interpolate between
* the pixels we *have* gotten so far. This filter performs bilinear
* interpolation by first performing linear interpolation horizontally for each
* "important" row (which we'll define as a row that has received any pixels
* with final values at all) and then performing linear interpolation vertically
* to produce pixel values for rows which aren't important on the current pass.
*
* Note that this filter totally ignores the data which is written to rows which
* aren't important on the current pass! It's fine to write nothing at all for
* these rows, although doing so won't cause any harm.
*
* XXX(seth): In bug 1280552 we'll add a SIMD implementation for this filter.
*
* The 'Next' template parameter specifies the next filter in the chain.
*/
template <typename Next>
class ADAM7InterpolatingFilter final : public SurfaceFilter
{
public:
ADAM7InterpolatingFilter()
: mPass(0) // The current pass, in the range 1..7. Starts at 0 so that
// DoResetToFirstRow() doesn't have to special case the first pass.
, mRow(0)
{ }
template <typename... Rest>
nsresult Configure(const ADAM7InterpolatingConfig& aConfig, Rest... aRest)
{
nsresult rv = mNext.Configure(aRest...);
if (NS_FAILED(rv)) {
return rv;
}
if (mNext.IsValidPalettedPipe()) {
NS_WARNING("ADAM7InterpolatingFilter used with paletted pipe?");
return NS_ERROR_INVALID_ARG;
}
// We have two intermediate buffers, one for the previous row with final
// pixel values and one for the row that the previous filter in the chain is
// currently writing to.
size_t inputWidthInBytes = mNext.InputSize().width * sizeof(uint32_t);
mPreviousRow.reset(new (fallible) uint8_t[inputWidthInBytes]);
if (MOZ_UNLIKELY(!mPreviousRow)) {
return NS_ERROR_OUT_OF_MEMORY;
}
mCurrentRow.reset(new (fallible) uint8_t[inputWidthInBytes]);
if (MOZ_UNLIKELY(!mCurrentRow)) {
return NS_ERROR_OUT_OF_MEMORY;
}
memset(mPreviousRow.get(), 0, inputWidthInBytes);
memset(mCurrentRow.get(), 0, inputWidthInBytes);
ConfigureFilter(mNext.InputSize(), sizeof(uint32_t));
return NS_OK;
}
Maybe<SurfaceInvalidRect> TakeInvalidRect() override
{
return mNext.TakeInvalidRect();
}
protected:
uint8_t* DoResetToFirstRow() override
{
mRow = 0;
mPass = std::min(mPass + 1, 7);
uint8_t* rowPtr = mNext.ResetToFirstRow();
if (mPass == 7) {
// Short circuit this filter on the final pass, since all pixels have
// their final values at that point.
return rowPtr;
}
return mCurrentRow.get();
}
uint8_t* DoAdvanceRow() override
{
MOZ_ASSERT(0 < mPass && mPass <= 7, "Invalid pass");
int32_t currentRow = mRow;
++mRow;
if (mPass == 7) {
// On the final pass we short circuit this filter totally.
return mNext.AdvanceRow();
}
const int32_t lastImportantRow = LastImportantRow(InputSize().height, mPass);
if (currentRow > lastImportantRow) {
return nullptr; // This pass is already complete.
}
if (!IsImportantRow(currentRow, mPass)) {
// We just ignore whatever the caller gives us for these rows. We'll
// interpolate them in later.
return mCurrentRow.get();
}
// This is an important row. We need to perform horizontal interpolation for
// these rows.
InterpolateHorizontally(mCurrentRow.get(), InputSize().width, mPass);
// Interpolate vertically between the previous important row and the current
// important row. We skip this if the current row is 0 (which is always an
// important row), because in that case there is no previous important row
// to interpolate with.
if (currentRow != 0) {
InterpolateVertically(mPreviousRow.get(), mCurrentRow.get(), mPass, mNext);
}
// Write out the current row itself, which, being an important row, does not
// need vertical interpolation.
uint32_t* currentRowAsPixels = reinterpret_cast<uint32_t*>(mCurrentRow.get());
mNext.WriteBuffer(currentRowAsPixels);
if (currentRow == lastImportantRow) {
// This is the last important row, which completes this pass. Note that
// for very small images, this may be the first row! Since there won't be
// another important row, there's nothing to interpolate with vertically,
// so we just duplicate this row until the end of the image.
while (mNext.WriteBuffer(currentRowAsPixels) == WriteState::NEED_MORE_DATA) { }
// All of the remaining rows in the image were determined above, so we're done.
return nullptr;
}
// The current row is now the previous important row; save it.
Swap(mPreviousRow, mCurrentRow);
MOZ_ASSERT(mRow < InputSize().height, "Reached the end of the surface without "
"hitting the last important row?");
return mCurrentRow.get();
}
private:
static void InterpolateVertically(uint8_t* aPreviousRow,
uint8_t* aCurrentRow,
uint8_t aPass,
SurfaceFilter& aNext)
{
const float* weights = InterpolationWeights(ImportantRowStride(aPass));
// We need to interpolate vertically to generate the rows between the
// previous important row and the next one. Recall that important rows are
// rows which contain at least some final pixels; see
// InterpolateHorizontally() for some additional explanation as to what that
// means. Note that we've already written out the previous important row, so
// we start the iteration at 1.
for (int32_t outRow = 1; outRow < ImportantRowStride(aPass); ++outRow) {
const float weight = weights[outRow];
// We iterate through the previous and current important row every time we
// write out an interpolated row, so we need to copy the pointers.
uint8_t* prevRowBytes = aPreviousRow;
uint8_t* currRowBytes = aCurrentRow;
// Write out the interpolated pixels. Interpolation is componentwise.
aNext.template WritePixelsToRow<uint32_t>([&]{
uint32_t pixel = 0;
auto* component = reinterpret_cast<uint8_t*>(&pixel);
*component++ = InterpolateByte(*prevRowBytes++, *currRowBytes++, weight);
*component++ = InterpolateByte(*prevRowBytes++, *currRowBytes++, weight);
*component++ = InterpolateByte(*prevRowBytes++, *currRowBytes++, weight);
*component++ = InterpolateByte(*prevRowBytes++, *currRowBytes++, weight);
return AsVariant(pixel);
});
}
}
static void InterpolateHorizontally(uint8_t* aRow, int32_t aWidth, uint8_t aPass)
{
// Collect the data we'll need to perform horizontal interpolation. The
// terminology here bears some explanation: a "final pixel" is a pixel which
// has received its final value. On each pass, a new set of pixels receives
// their final value; see the diagram above of the 8x8 pattern that ADAM7
// uses. Any pixel which hasn't received its final value on this pass
// derives its value from either horizontal or vertical interpolation
// instead.
const size_t finalPixelStride = FinalPixelStride(aPass);
const size_t finalPixelStrideBytes = finalPixelStride * sizeof(uint32_t);
const size_t lastFinalPixel = LastFinalPixel(aWidth, aPass);
const size_t lastFinalPixelBytes = lastFinalPixel * sizeof(uint32_t);
const float* weights = InterpolationWeights(finalPixelStride);
// Interpolate blocks of pixels which lie between two final pixels.
// Horizontal interpolation is done in place, as we'll need the results
// later when we vertically interpolate.
for (size_t blockBytes = 0;
blockBytes < lastFinalPixelBytes;
blockBytes += finalPixelStrideBytes) {
uint8_t* finalPixelA = aRow + blockBytes;
uint8_t* finalPixelB = aRow + blockBytes + finalPixelStrideBytes;
MOZ_ASSERT(finalPixelA < aRow + aWidth * sizeof(uint32_t),
"Running off end of buffer");
MOZ_ASSERT(finalPixelB < aRow + aWidth * sizeof(uint32_t),
"Running off end of buffer");
// Interpolate the individual pixels componentwise. Note that we start
// iteration at 1 since we don't need to apply any interpolation to the
// first pixel in the block, which has its final value.
for (size_t pixelIndex = 1; pixelIndex < finalPixelStride; ++pixelIndex) {
const float weight = weights[pixelIndex];
uint8_t* pixel = aRow + blockBytes + pixelIndex * sizeof(uint32_t);
MOZ_ASSERT(pixel < aRow + aWidth * sizeof(uint32_t), "Running off end of buffer");
for (size_t component = 0; component < sizeof(uint32_t); ++component) {
pixel[component] =
InterpolateByte(finalPixelA[component], finalPixelB[component], weight);
}
}
}
// For the pixels after the last final pixel in the row, there isn't a
// second final pixel to interpolate with, so just duplicate.
uint32_t* rowPixels = reinterpret_cast<uint32_t*>(aRow);
uint32_t pixelToDuplicate = rowPixels[lastFinalPixel];
for (int32_t pixelIndex = lastFinalPixel + 1;
pixelIndex < aWidth;
++pixelIndex) {
MOZ_ASSERT(pixelIndex < aWidth, "Running off end of buffer");
rowPixels[pixelIndex] = pixelToDuplicate;
}
}
static uint8_t InterpolateByte(uint8_t aByteA, uint8_t aByteB, float aWeight)
{
return uint8_t(aByteA * aWeight + aByteB * (1.0f - aWeight));
}
static int32_t ImportantRowStride(uint8_t aPass)
{
MOZ_ASSERT(0 < aPass && aPass <= 7, "Invalid pass");
// The stride between important rows for each pass, with a dummy value for
// the nonexistent pass 0.
static int32_t strides[] = { 1, 8, 8, 4, 4, 2, 2, 1 };
return strides[aPass];
}
static bool IsImportantRow(int32_t aRow, uint8_t aPass)
{
MOZ_ASSERT(aRow >= 0);
// Whether the row is important comes down to divisibility by the stride for
// this pass, which is always a power of 2, so we can check using a mask.
int32_t mask = ImportantRowStride(aPass) - 1;
return (aRow & mask) == 0;
}
static int32_t LastImportantRow(int32_t aHeight, uint8_t aPass)
{
MOZ_ASSERT(aHeight > 0);
// We can find the last important row using the same mask trick as above.
int32_t lastRow = aHeight - 1;
int32_t mask = ImportantRowStride(aPass) - 1;
return lastRow - (lastRow & mask);
}
static size_t FinalPixelStride(uint8_t aPass)
{
MOZ_ASSERT(0 < aPass && aPass <= 7, "Invalid pass");
// The stride between the final pixels in important rows for each pass, with
// a dummy value for the nonexistent pass 0.
static size_t strides[] = { 1, 8, 4, 4, 2, 2, 1, 1 };
return strides[aPass];
}
static size_t LastFinalPixel(int32_t aWidth, uint8_t aPass)
{
MOZ_ASSERT(aWidth >= 0);
// Again, we can use the mask trick above to find the last important pixel.
int32_t lastColumn = aWidth - 1;
size_t mask = FinalPixelStride(aPass) - 1;
return lastColumn - (lastColumn & mask);
}
static const float* InterpolationWeights(int32_t aStride)
{
// Precalculated interpolation weights. These are used to interpolate
// between final pixels or between important rows. Although no interpolation
// is actually applied to the previous final pixel or important row value,
// the arrays still start with 1.0f, which is always skipped, primarily
// because otherwise |stride1Weights| would have zero elements.
static float stride8Weights[] =
{ 1.0f, 7 / 8.0f, 6 / 8.0f, 5 / 8.0f, 4 / 8.0f, 3 / 8.0f, 2 / 8.0f, 1 / 8.0f };
static float stride4Weights[] = { 1.0f, 3 / 4.0f, 2 / 4.0f, 1 / 4.0f };
static float stride2Weights[] = { 1.0f, 1 / 2.0f };
static float stride1Weights[] = { 1.0f };
switch (aStride) {
case 8: return stride8Weights;
case 4: return stride4Weights;
case 2: return stride2Weights;
case 1: return stride1Weights;
default: MOZ_CRASH();
}
}
Next mNext; /// The next SurfaceFilter in the chain.
UniquePtr<uint8_t[]> mPreviousRow; /// The last important row (i.e., row with
/// final pixel values) that got written to.
UniquePtr<uint8_t[]> mCurrentRow; /// The row that's being written to right
/// now.
uint8_t mPass; /// Which ADAM7 pass we're on. Valid passes
/// are 1..7 during processing and 0 prior
/// to configuraiton.
int32_t mRow; /// The row we're currently reading.
};
} // namespace image
} // namespace mozilla
#endif // mozilla_image_SurfaceFilters_h

73
image/SurfaceFlags.h Normal file
View file

@ -0,0 +1,73 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* 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/. */
#ifndef mozilla_image_SurfaceFlags_h
#define mozilla_image_SurfaceFlags_h
#include "imgIContainer.h"
#include "mozilla/TypedEnumBits.h"
namespace mozilla {
namespace image {
/**
* Flags that change the output a decoder generates. Because different
* combinations of these flags result in logically different surfaces, these
* flags must be taken into account in SurfaceCache lookups.
*/
enum class SurfaceFlags : uint8_t
{
NO_PREMULTIPLY_ALPHA = 1 << 0,
NO_COLORSPACE_CONVERSION = 1 << 1
};
MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(SurfaceFlags)
/**
* @return the default set of surface flags.
*/
inline SurfaceFlags
DefaultSurfaceFlags()
{
return SurfaceFlags();
}
/**
* Given a set of imgIContainer FLAG_* flags, returns a set of SurfaceFlags with
* the corresponding flags set.
*/
inline SurfaceFlags
ToSurfaceFlags(uint32_t aFlags)
{
SurfaceFlags flags = DefaultSurfaceFlags();
if (aFlags & imgIContainer::FLAG_DECODE_NO_PREMULTIPLY_ALPHA) {
flags |= SurfaceFlags::NO_PREMULTIPLY_ALPHA;
}
if (aFlags & imgIContainer::FLAG_DECODE_NO_COLORSPACE_CONVERSION) {
flags |= SurfaceFlags::NO_COLORSPACE_CONVERSION;
}
return flags;
}
/**
* Given a set of SurfaceFlags, returns a set of imgIContainer FLAG_* flags with
* the corresponding flags set.
*/
inline uint32_t
FromSurfaceFlags(SurfaceFlags aFlags)
{
uint32_t flags = imgIContainer::DECODE_FLAGS_DEFAULT;
if (aFlags & SurfaceFlags::NO_PREMULTIPLY_ALPHA) {
flags |= imgIContainer::FLAG_DECODE_NO_PREMULTIPLY_ALPHA;
}
if (aFlags & SurfaceFlags::NO_COLORSPACE_CONVERSION) {
flags |= imgIContainer::FLAG_DECODE_NO_COLORSPACE_CONVERSION;
}
return flags;
}
} // namespace image
} // namespace mozilla
#endif // mozilla_image_SurfaceFlags_h

199
image/SurfacePipe.cpp Normal file
View file

@ -0,0 +1,199 @@
/* -*- 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/. */
#include "SurfacePipe.h"
#include <utility>
#include "mozilla/ClearOnShutdown.h"
#include "mozilla/DebugOnly.h"
#include "Decoder.h"
namespace mozilla {
namespace image {
using namespace gfx;
using std::min;
/* static */ UniquePtr<NullSurfaceSink> NullSurfaceSink::sSingleton;
/* static */ NullSurfaceSink*
NullSurfaceSink::Singleton()
{
if (!sSingleton) {
MOZ_ASSERT(NS_IsMainThread());
sSingleton = MakeUnique<NullSurfaceSink>();
ClearOnShutdown(&sSingleton);
DebugOnly<nsresult> rv = sSingleton->Configure(NullSurfaceConfig { });
MOZ_ASSERT(NS_SUCCEEDED(rv), "Couldn't configure a NullSurfaceSink?");
}
return sSingleton.get();
}
nsresult
NullSurfaceSink::Configure(const NullSurfaceConfig& aConfig)
{
// Note that the choice of uint32_t as the pixel size here is more or less
// arbitrary, since you cannot write to a NullSurfaceSink anyway, but uint32_t
// is a natural choice since most SurfacePipes will be for BGRA/BGRX surfaces.
ConfigureFilter(IntSize(), sizeof(uint32_t));
return NS_OK;
}
Maybe<SurfaceInvalidRect>
AbstractSurfaceSink::TakeInvalidRect()
{
if (mInvalidRect.IsEmpty()) {
return Nothing();
}
SurfaceInvalidRect invalidRect;
invalidRect.mInputSpaceRect = invalidRect.mOutputSpaceRect = mInvalidRect;
// Forget about the invalid rect we're returning.
mInvalidRect = IntRect();
return Some(invalidRect);
}
uint8_t*
AbstractSurfaceSink::DoResetToFirstRow()
{
mRow = 0;
return GetRowPointer();
}
uint8_t*
AbstractSurfaceSink::DoAdvanceRow()
{
if (mRow >= uint32_t(InputSize().height)) {
return nullptr;
}
// If we're vertically flipping the output, we need to flip the invalid rect. Since we're
// dealing with an axis-aligned rect, only the y coordinate needs to change.
int32_t invalidY = mFlipVertically
? InputSize().height - (mRow + 1)
: mRow;
mInvalidRect.UnionRect(mInvalidRect,
IntRect(0, invalidY, InputSize().width, 1));
mRow = min(uint32_t(InputSize().height), mRow + 1);
return mRow < uint32_t(InputSize().height) ? GetRowPointer()
: nullptr;
}
nsresult
SurfaceSink::Configure(const SurfaceConfig& aConfig)
{
// For non-paletted surfaces, the surface size is just the output size.
IntSize surfaceSize = aConfig.mOutputSize;
// Non-paletted surfaces should not have frame rects, so we just pass
// AllocateFrame() a frame rect which covers the entire surface.
IntRect frameRect(0, 0, surfaceSize.width, surfaceSize.height);
// Allocate the frame.
// XXX(seth): Once every Decoder subclass uses SurfacePipe, we probably want
// to allocate the frame directly here and get rid of Decoder::AllocateFrame
// altogether.
nsresult rv = aConfig.mDecoder->AllocateFrame(aConfig.mFrameNum,
surfaceSize,
frameRect,
aConfig.mFormat);
if (NS_FAILED(rv)) {
return rv;
}
mImageData = aConfig.mDecoder->mImageData;
mImageDataLength = aConfig.mDecoder->mImageDataLength;
mFlipVertically = aConfig.mFlipVertically;
MOZ_ASSERT(mImageData);
MOZ_ASSERT(mImageDataLength ==
uint32_t(surfaceSize.width * surfaceSize.height * sizeof(uint32_t)));
ConfigureFilter(surfaceSize, sizeof(uint32_t));
return NS_OK;
}
uint8_t*
SurfaceSink::GetRowPointer() const
{
// If we're flipping vertically, reverse the order in which we traverse the
// rows.
uint32_t row = mFlipVertically
? InputSize().height - (mRow + 1)
: mRow;
uint8_t* rowPtr = mImageData + row * InputSize().width * sizeof(uint32_t);
MOZ_ASSERT(rowPtr >= mImageData);
MOZ_ASSERT(rowPtr < mImageData + mImageDataLength);
MOZ_ASSERT(rowPtr + InputSize().width * sizeof(uint32_t) <=
mImageData + mImageDataLength);
return rowPtr;
}
nsresult
PalettedSurfaceSink::Configure(const PalettedSurfaceConfig& aConfig)
{
// For paletted surfaces, the surface size is the size of the frame rect.
IntSize surfaceSize = aConfig.mFrameRect.Size();
// Allocate the frame.
// XXX(seth): Once every Decoder subclass uses SurfacePipe, we probably want
// to allocate the frame directly here and get rid of Decoder::AllocateFrame
// altogether.
nsresult rv = aConfig.mDecoder->AllocateFrame(aConfig.mFrameNum,
aConfig.mOutputSize,
aConfig.mFrameRect,
aConfig.mFormat,
aConfig.mPaletteDepth);
if (NS_FAILED(rv)) {
return rv;
}
mImageData = aConfig.mDecoder->mImageData;
mImageDataLength = aConfig.mDecoder->mImageDataLength;
mFlipVertically = aConfig.mFlipVertically;
mFrameRect = aConfig.mFrameRect;
MOZ_ASSERT(mImageData);
MOZ_ASSERT(mImageDataLength ==
uint32_t(mFrameRect.width * mFrameRect.height * sizeof(uint8_t)));
ConfigureFilter(surfaceSize, sizeof(uint8_t));
return NS_OK;
}
uint8_t*
PalettedSurfaceSink::GetRowPointer() const
{
// If we're flipping vertically, reverse the order in which we traverse the
// rows.
uint32_t row = mFlipVertically
? InputSize().height - (mRow + 1)
: mRow;
uint8_t* rowPtr = mImageData + row * InputSize().width * sizeof(uint8_t);
MOZ_ASSERT(rowPtr >= mImageData);
MOZ_ASSERT(rowPtr < mImageData + mImageDataLength);
MOZ_ASSERT(rowPtr + InputSize().width * sizeof(uint8_t) <=
mImageData + mImageDataLength);
return rowPtr;
}
} // namespace image
} // namespace mozilla

793
image/SurfacePipe.h Normal file
View file

@ -0,0 +1,793 @@
/* -*- 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/. */
/**
* A SurfacePipe is a pipeline that consists of a series of SurfaceFilters
* terminating in a SurfaceSink. Each SurfaceFilter transforms the image data in
* some way before the SurfaceSink ultimately writes it to the surface. This
* design allows for each transformation to be tested independently, for the
* transformations to be combined as needed to meet the needs of different
* situations, and for all image decoders to share the same code for these
* transformations.
*
* Writing to the SurfacePipe is done using lambdas that act as generator
* functions. Because the SurfacePipe machinery controls where the writes take
* place, a bug in an image decoder cannot cause a buffer overflow of the
* underlying surface.
*/
#ifndef mozilla_image_SurfacePipe_h
#define mozilla_image_SurfacePipe_h
#include <stdint.h>
#include "nsDebug.h"
#include "mozilla/Likely.h"
#include "mozilla/Maybe.h"
#include "mozilla/Move.h"
#include "mozilla/UniquePtr.h"
#include "mozilla/Unused.h"
#include "mozilla/Variant.h"
#include "mozilla/gfx/2D.h"
namespace mozilla {
namespace image {
class Decoder;
/**
* An invalid rect for a surface. Results are given both in the space of the
* input image (i.e., before any SurfaceFilters are applied) and in the space
* of the output surface (after all SurfaceFilters).
*/
struct SurfaceInvalidRect
{
gfx::IntRect mInputSpaceRect; /// The invalid rect in pre-SurfacePipe space.
gfx::IntRect mOutputSpaceRect; /// The invalid rect in post-SurfacePipe space.
};
/**
* An enum used to allow the lambdas passed to WritePixels() to communicate
* their state to the caller.
*/
enum class WriteState : uint8_t
{
NEED_MORE_DATA, /// The lambda ran out of data.
FINISHED, /// The lambda is done writing to the surface; future writes
/// will fail.
FAILURE /// The lambda encountered an error. The caller may recover
/// if possible and continue to write. (This never indicates
/// an error in the SurfacePipe machinery itself; it's only
/// generated by the lambdas.)
};
/**
* A template alias used to make the return value of WritePixels() lambdas
* (which may return either a pixel value or a WriteState) easier to specify.
*/
template <typename PixelType>
using NextPixel = Variant<PixelType, WriteState>;
/**
* SurfaceFilter is the abstract superclass of SurfacePipe pipeline stages. It
* implements the the code that actually writes to the surface - WritePixels()
* and the other Write*() methods - which are non-virtual for efficiency.
*
* SurfaceFilter's API is nonpublic; only SurfacePipe and other SurfaceFilters
* should use it. Non-SurfacePipe code should use the methods on SurfacePipe.
*
* To implement a SurfaceFilter, it's necessary to subclass SurfaceFilter and
* implement, at a minimum, the pure virtual methods. It's also necessary to
* define a Config struct with a Filter typedef member that identifies the
* matching SurfaceFilter class, and a Configure() template method. See an
* existing SurfaceFilter subclass, such as RemoveFrameRectFilter, for an
* example of how the Configure() method must be implemented. It takes a list of
* Config structs, passes the tail of the list to the next filter in the chain's
* Configure() method, and then uses the head of the list to configure itself. A
* SurfaceFilter's Configure() method must also call
* SurfaceFilter::ConfigureFilter() to provide the Write*() methods with the
* information they need to do their jobs.
*/
class SurfaceFilter
{
public:
SurfaceFilter()
: mRowPointer(nullptr)
, mCol(0)
, mPixelSize(0)
{ }
virtual ~SurfaceFilter() { }
/**
* Reset this surface to the first row. It's legal for this filter to throw
* away any previously written data at this point, as all rows must be written
* to on every pass.
*
* @return a pointer to the buffer for the first row.
*/
uint8_t* ResetToFirstRow()
{
mCol = 0;
mRowPointer = DoResetToFirstRow();
return mRowPointer;
}
/**
* Called by WritePixels() to advance this filter to the next row.
*
* @return a pointer to the buffer for the next row, or nullptr to indicate
* that we've finished the entire surface.
*/
uint8_t* AdvanceRow()
{
mCol = 0;
mRowPointer = DoAdvanceRow();
return mRowPointer;
}
/// @return a pointer to the buffer for the current row.
uint8_t* CurrentRowPointer() const { return mRowPointer; }
/// @return true if we've finished writing to the surface.
bool IsSurfaceFinished() const { return mRowPointer == nullptr; }
/// @return the input size this filter expects.
gfx::IntSize InputSize() const { return mInputSize; }
/**
* Write pixels to the surface one at a time by repeatedly calling a lambda
* that yields pixels. WritePixels() is completely memory safe.
*
* Writing continues until every pixel in the surface has been written to
* (i.e., IsSurfaceFinished() returns true) or the lambda returns a WriteState
* which WritePixels() will return to the caller.
*
* The template parameter PixelType must be uint8_t (for paletted surfaces) or
* uint32_t (for BGRA/BGRX surfaces) and must be in agreement with the pixel
* size passed to ConfigureFilter().
*
* XXX(seth): We'll remove all support for paletted surfaces in bug 1247520,
* which means we can remove the PixelType template parameter from this
* method.
*
* @param aFunc A lambda that functions as a generator, yielding the next
* pixel in the surface each time it's called. The lambda must
* return a NextPixel<PixelType> value.
*
* @return A WriteState value indicating the lambda generator's state.
* WritePixels() itself will return WriteState::FINISHED if writing
* has finished, regardless of the lambda's internal state.
*/
template <typename PixelType, typename Func>
WriteState WritePixels(Func aFunc)
{
Maybe<WriteState> result;
while (!(result = DoWritePixelsToRow<PixelType>(Forward<Func>(aFunc)))) { }
return *result;
}
/**
* A variant of WritePixels() that writes a single row of pixels to the
* surface one at a time by repeatedly calling a lambda that yields pixels.
* WritePixelsToRow() is completely memory safe.
*
* Writing continues until every pixel in the row has been written to. If the
* surface is complete at that pointer, WriteState::FINISHED is returned;
* otherwise, WritePixelsToRow() returns WriteState::NEED_MORE_DATA. The
* lambda can terminate writing early by returning a WriteState itself, which
* WritePixelsToRow() will return to the caller.
*
* The template parameter PixelType must be uint8_t (for paletted surfaces) or
* uint32_t (for BGRA/BGRX surfaces) and must be in agreement with the pixel
* size passed to ConfigureFilter().
*
* XXX(seth): We'll remove all support for paletted surfaces in bug 1247520,
* which means we can remove the PixelType template parameter from this
* method.
*
* @param aFunc A lambda that functions as a generator, yielding the next
* pixel in the surface each time it's called. The lambda must
* return a NextPixel<PixelType> value.
*
* @return A WriteState value indicating the lambda generator's state.
* WritePixels() itself will return WriteState::FINISHED if writing
* the entire surface has finished, or WriteState::NEED_MORE_DATA if
* writing the row has finished, regardless of the lambda's internal
* state.
*/
template <typename PixelType, typename Func>
WriteState WritePixelsToRow(Func aFunc)
{
return DoWritePixelsToRow<PixelType>(Forward<Func>(aFunc))
.valueOr(WriteState::NEED_MORE_DATA);
}
/**
* Write a row to the surface by copying from a buffer. This is bounds checked
* and memory safe with respect to the surface, but care must still be taken
* by the caller not to overread the source buffer. This variant of
* WriteBuffer() requires a source buffer which contains |mInputSize.width|
* pixels.
*
* The template parameter PixelType must be uint8_t (for paletted surfaces) or
* uint32_t (for BGRA/BGRX surfaces) and must be in agreement with the pixel
* size passed to ConfigureFilter().
*
* XXX(seth): We'll remove all support for paletted surfaces in bug 1247520,
* which means we can remove the PixelType template parameter from this
* method.
*
* @param aSource A buffer to copy from. This buffer must be
* |mInputSize.width| pixels wide, which means
* |mInputSize.width * sizeof(PixelType)| bytes. May not be
* null.
*
* @return WriteState::FINISHED if the entire surface has been written to.
* Otherwise, returns WriteState::NEED_MORE_DATA. If a null |aSource|
* value is passed, returns WriteState::FAILURE.
*/
template <typename PixelType>
WriteState WriteBuffer(const PixelType* aSource)
{
return WriteBuffer(aSource, 0, mInputSize.width);
}
/**
* Write a row to the surface by copying from a buffer. This is bounds checked
* and memory safe with respect to the surface, but care must still be taken
* by the caller not to overread the source buffer. This variant of
* WriteBuffer() reads at most @aLength pixels from the buffer and writes them
* to the row starting at @aStartColumn. Any pixels in columns before
* @aStartColumn or after the pixels copied from the buffer are cleared.
*
* Bounds checking failures produce warnings in debug builds because although
* the bounds checking maintains safety, this kind of failure could indicate a
* bug in the calling code.
*
* The template parameter PixelType must be uint8_t (for paletted surfaces) or
* uint32_t (for BGRA/BGRX surfaces) and must be in agreement with the pixel
* size passed to ConfigureFilter().
*
* XXX(seth): We'll remove all support for paletted surfaces in bug 1247520,
* which means we can remove the PixelType template parameter from this
* method.
*
* @param aSource A buffer to copy from. This buffer must be @aLength pixels
* wide, which means |aLength * sizeof(PixelType)| bytes. May
* not be null.
* @param aStartColumn The column to start writing to in the row. Columns
* before this are cleared.
* @param aLength The number of bytes, at most, which may be copied from
* @aSource. Fewer bytes may be copied in practice due to
* bounds checking.
*
* @return WriteState::FINISHED if the entire surface has been written to.
* Otherwise, returns WriteState::NEED_MORE_DATA. If a null |aSource|
* value is passed, returns WriteState::FAILURE.
*/
template <typename PixelType>
WriteState WriteBuffer(const PixelType* aSource,
const size_t aStartColumn,
const size_t aLength)
{
MOZ_ASSERT(mPixelSize == 1 || mPixelSize == 4);
MOZ_ASSERT_IF(mPixelSize == 1, sizeof(PixelType) == sizeof(uint8_t));
MOZ_ASSERT_IF(mPixelSize == 4, sizeof(PixelType) == sizeof(uint32_t));
if (IsSurfaceFinished()) {
return WriteState::FINISHED; // Already done.
}
if (MOZ_UNLIKELY(!aSource)) {
NS_WARNING("Passed a null pointer to WriteBuffer");
return WriteState::FAILURE;
}
PixelType* dest = reinterpret_cast<PixelType*>(mRowPointer);
// Clear the area before |aStartColumn|.
const size_t prefixLength = std::min<size_t>(mInputSize.width, aStartColumn);
if (MOZ_UNLIKELY(prefixLength != aStartColumn)) {
NS_WARNING("Provided starting column is out-of-bounds in WriteBuffer");
}
memset(dest, 0, mInputSize.width * sizeof(PixelType));
dest += prefixLength;
// Write |aLength| pixels from |aSource| into the row, with bounds checking.
const size_t bufferLength =
std::min<size_t>(mInputSize.width - prefixLength, aLength);
if (MOZ_UNLIKELY(bufferLength != aLength)) {
NS_WARNING("Provided buffer length is out-of-bounds in WriteBuffer");
}
memcpy(dest, aSource, bufferLength * sizeof(PixelType));
dest += bufferLength;
// Clear the rest of the row.
const size_t suffixLength = mInputSize.width - (prefixLength + bufferLength);
memset(dest, 0, suffixLength * sizeof(PixelType));
AdvanceRow();
return IsSurfaceFinished() ? WriteState::FINISHED
: WriteState::NEED_MORE_DATA;
}
/**
* Write an empty row to the surface. If some pixels have already been written
* to this row, they'll be discarded.
*
* @return WriteState::FINISHED if the entire surface has been written to.
* Otherwise, returns WriteState::NEED_MORE_DATA.
*/
WriteState WriteEmptyRow()
{
if (IsSurfaceFinished()) {
return WriteState::FINISHED; // Already done.
}
memset(mRowPointer, 0, mInputSize.width * mPixelSize);
AdvanceRow();
return IsSurfaceFinished() ? WriteState::FINISHED
: WriteState::NEED_MORE_DATA;
}
/**
* Write a row to the surface by calling a lambda that uses a pointer to
* directly write to the row. This is unsafe because SurfaceFilter can't
* provide any bounds checking; that's up to the lambda itself. For this
* reason, the other Write*() methods should be preferred whenever it's
* possible to use them; WriteUnsafeComputedRow() should be used only when
* it's absolutely necessary to avoid extra copies or other performance
* penalties.
*
* This method should never be exposed to SurfacePipe consumers; it's strictly
* for use in SurfaceFilters. If external code needs this method, it should
* probably be turned into a SurfaceFilter.
*
* The template parameter PixelType must be uint8_t (for paletted surfaces) or
* uint32_t (for BGRA/BGRX surfaces) and must be in agreement with the pixel
* size passed to ConfigureFilter().
*
* XXX(seth): We'll remove all support for paletted surfaces in bug 1247520,
* which means we can remove the PixelType template parameter from this
* method.
*
* @param aFunc A lambda that writes directly to the row.
*
* @return WriteState::FINISHED if the entire surface has been written to.
* Otherwise, returns WriteState::NEED_MORE_DATA.
*/
template <typename PixelType, typename Func>
WriteState WriteUnsafeComputedRow(Func aFunc)
{
MOZ_ASSERT(mPixelSize == 1 || mPixelSize == 4);
MOZ_ASSERT_IF(mPixelSize == 1, sizeof(PixelType) == sizeof(uint8_t));
MOZ_ASSERT_IF(mPixelSize == 4, sizeof(PixelType) == sizeof(uint32_t));
if (IsSurfaceFinished()) {
return WriteState::FINISHED; // Already done.
}
// Call the provided lambda with a pointer to the buffer for the current
// row. This is unsafe because we can't do any bounds checking; the lambda
// itself has to be responsible for that.
PixelType* rowPtr = reinterpret_cast<PixelType*>(mRowPointer);
aFunc(rowPtr, mInputSize.width);
AdvanceRow();
return IsSurfaceFinished() ? WriteState::FINISHED
: WriteState::NEED_MORE_DATA;
}
//////////////////////////////////////////////////////////////////////////////
// Methods Subclasses Should Override
//////////////////////////////////////////////////////////////////////////////
/// @return true if this SurfaceFilter can be used with paletted surfaces.
virtual bool IsValidPalettedPipe() const { return false; }
/**
* @return a SurfaceInvalidRect representing the region of the surface that
* has been written to since the last time TakeInvalidRect() was
* called, or Nothing() if the region is empty (i.e. nothing has been
* written).
*/
virtual Maybe<SurfaceInvalidRect> TakeInvalidRect() = 0;
protected:
/**
* Called by ResetToFirstRow() to actually perform the reset. It's legal to
* throw away any previously written data at this point, as all rows must be
* written to on every pass.
*/
virtual uint8_t* DoResetToFirstRow() = 0;
/**
* Called by AdvanceRow() to actually advance this filter to the next row.
*
* @return a pointer to the buffer for the next row, or nullptr to indicate
* that we've finished the entire surface.
*/
virtual uint8_t* DoAdvanceRow() = 0;
//////////////////////////////////////////////////////////////////////////////
// Methods For Internal Use By Subclasses
//////////////////////////////////////////////////////////////////////////////
/**
* Called by subclasses' Configure() methods to initialize the configuration
* of this filter. After the filter is configured, calls ResetToFirstRow().
*
* @param aInputSize The input size of this filter, in pixels. The previous
* filter in the chain will expect to write into rows
* |aInputSize.width| pixels wide.
* @param aPixelSize How large, in bytes, each pixel in the surface is. This
* should be either 1 for paletted images or 4 for BGRA/BGRX
* images.
*/
void ConfigureFilter(gfx::IntSize aInputSize, uint8_t aPixelSize)
{
mInputSize = aInputSize;
mPixelSize = aPixelSize;
ResetToFirstRow();
}
private:
/**
* An internal method used to implement both WritePixels() and
* WritePixelsToRow(). Those methods differ only in their behavior after a row
* is successfully written - WritePixels() continues to write another row,
* while WritePixelsToRow() returns to the caller. This method writes a single
* row and returns Some() if we either finished the entire surface or the
* lambda returned a WriteState indicating that we should return to the
* caller. If the row was successfully written without either of those things
* happening, it returns Nothing(), allowing WritePixels() and
* WritePixelsToRow() to implement their respective behaviors.
*/
template <typename PixelType, typename Func>
Maybe<WriteState> DoWritePixelsToRow(Func aFunc)
{
MOZ_ASSERT(mPixelSize == 1 || mPixelSize == 4);
MOZ_ASSERT_IF(mPixelSize == 1, sizeof(PixelType) == sizeof(uint8_t));
MOZ_ASSERT_IF(mPixelSize == 4, sizeof(PixelType) == sizeof(uint32_t));
if (IsSurfaceFinished()) {
return Some(WriteState::FINISHED); // We're already done.
}
PixelType* rowPtr = reinterpret_cast<PixelType*>(mRowPointer);
for (; mCol < mInputSize.width; ++mCol) {
NextPixel<PixelType> result = aFunc();
if (result.template is<PixelType>()) {
rowPtr[mCol] = result.template as<PixelType>();
continue;
}
switch (result.template as<WriteState>()) {
case WriteState::NEED_MORE_DATA:
return Some(WriteState::NEED_MORE_DATA);
case WriteState::FINISHED:
ZeroOutRestOfSurface<PixelType>();
return Some(WriteState::FINISHED);
case WriteState::FAILURE:
// Note that we don't need to record this anywhere, because this
// indicates an error in aFunc, and there's nothing wrong with our
// machinery. The caller can recover as needed and continue writing to
// the row.
return Some(WriteState::FAILURE);
}
}
AdvanceRow(); // We've finished the row.
return IsSurfaceFinished() ? Some(WriteState::FINISHED)
: Nothing();
}
template <typename PixelType>
void ZeroOutRestOfSurface()
{
WritePixels<PixelType>([]{ return AsVariant(PixelType(0)); });
}
gfx::IntSize mInputSize; /// The size of the input this filter expects.
uint8_t* mRowPointer; /// Pointer to the current row or null if finished.
int32_t mCol; /// The current column we're writing to. (0-indexed)
uint8_t mPixelSize; /// How large each pixel in the surface is, in bytes.
};
class NullSurfaceSink;
/// A trivial configuration struct for NullSurfaceSink.
struct NullSurfaceConfig
{
using Filter = NullSurfaceSink;
};
/**
* NullSurfaceSink is a trivial SurfaceFilter implementation that behaves as if
* it were a zero-size SurfaceSink. It's used as the default filter chain for an
* uninitialized SurfacePipe.
*
* To avoid unnecessary allocations when creating SurfacePipe objects,
* NullSurfaceSink is a singleton. (This implies that the implementation must be
* stateless.)
*/
class NullSurfaceSink final : public SurfaceFilter
{
public:
/// Returns the singleton instance of NullSurfaceSink.
static NullSurfaceSink* Singleton();
virtual ~NullSurfaceSink() { }
nsresult Configure(const NullSurfaceConfig& aConfig);
Maybe<SurfaceInvalidRect> TakeInvalidRect() override { return Nothing(); }
protected:
uint8_t* DoResetToFirstRow() override { return nullptr; }
uint8_t* DoAdvanceRow() override { return nullptr; }
private:
static UniquePtr<NullSurfaceSink> sSingleton; /// The singleton instance.
};
/**
* SurfacePipe is the public API that decoders should use to interact with a
* SurfaceFilter pipeline.
*/
class SurfacePipe
{
public:
/// Initialize global state used by all SurfacePipes.
static void Initialize() { NullSurfaceSink::Singleton(); }
SurfacePipe()
: mHead(NullSurfaceSink::Singleton())
{ }
SurfacePipe(SurfacePipe&& aOther)
: mHead(Move(aOther.mHead))
{ }
~SurfacePipe()
{
// Ensure that we don't free the NullSurfaceSink singleton.
if (mHead.get() == NullSurfaceSink::Singleton()) {
Unused << mHead.release();
}
}
SurfacePipe& operator=(SurfacePipe&& aOther)
{
MOZ_ASSERT(this != &aOther);
// Ensure that we don't free the NullSurfaceSink singleton.
if (mHead.get() == NullSurfaceSink::Singleton()) {
Unused << mHead.release();
}
mHead = Move(aOther.mHead);
return *this;
}
/// Begins a new pass, seeking to the first row of the surface.
void ResetToFirstRow() { mHead->ResetToFirstRow(); }
/**
* Write pixels to the surface one at a time by repeatedly calling a lambda
* that yields pixels. WritePixels() is completely memory safe.
*
* @see SurfaceFilter::WritePixels() for the canonical documentation.
*/
template <typename PixelType, typename Func>
WriteState WritePixels(Func aFunc)
{
return mHead->WritePixels<PixelType>(Forward<Func>(aFunc));
}
/**
* A variant of WritePixels() that writes a single row of pixels to the
* surface one at a time by repeatedly calling a lambda that yields pixels.
* WritePixelsToRow() is completely memory safe.
*
* @see SurfaceFilter::WritePixelsToRow() for the canonical documentation.
*/
template <typename PixelType, typename Func>
WriteState WritePixelsToRow(Func aFunc)
{
return mHead->WritePixelsToRow<PixelType>(Forward<Func>(aFunc));
}
/**
* Write a row to the surface by copying from a buffer. This is bounds checked
* and memory safe with respect to the surface, but care must still be taken
* by the caller not to overread the source buffer. This variant of
* WriteBuffer() requires a source buffer which contains |mInputSize.width|
* pixels.
*
* @see SurfaceFilter::WriteBuffer() for the canonical documentation.
*/
template <typename PixelType>
WriteState WriteBuffer(const PixelType* aSource)
{
return mHead->WriteBuffer<PixelType>(aSource);
}
/**
* Write a row to the surface by copying from a buffer. This is bounds checked
* and memory safe with respect to the surface, but care must still be taken
* by the caller not to overread the source buffer. This variant of
* WriteBuffer() reads at most @aLength pixels from the buffer and writes them
* to the row starting at @aStartColumn. Any pixels in columns before
* @aStartColumn or after the pixels copied from the buffer are cleared.
*
* @see SurfaceFilter::WriteBuffer() for the canonical documentation.
*/
template <typename PixelType>
WriteState WriteBuffer(const PixelType* aSource,
const size_t aStartColumn,
const size_t aLength)
{
return mHead->WriteBuffer<PixelType>(aSource, aStartColumn, aLength);
}
/**
* Write an empty row to the surface. If some pixels have already been written
* to this row, they'll be discarded.
*
* @see SurfaceFilter::WriteEmptyRow() for the canonical documentation.
*/
WriteState WriteEmptyRow()
{
return mHead->WriteEmptyRow();
}
/// @return true if we've finished writing to the surface.
bool IsSurfaceFinished() const { return mHead->IsSurfaceFinished(); }
/// @see SurfaceFilter::TakeInvalidRect() for the canonical documentation.
Maybe<SurfaceInvalidRect> TakeInvalidRect() const
{
return mHead->TakeInvalidRect();
}
private:
friend class SurfacePipeFactory;
friend class TestSurfacePipeFactory;
explicit SurfacePipe(UniquePtr<SurfaceFilter>&& aHead)
: mHead(Move(aHead))
{ }
SurfacePipe(const SurfacePipe&) = delete;
SurfacePipe& operator=(const SurfacePipe&) = delete;
UniquePtr<SurfaceFilter> mHead; /// The first filter in the chain.
};
/**
* AbstractSurfaceSink contains shared implementation for both SurfaceSink and
* PalettedSurfaceSink.
*/
class AbstractSurfaceSink : public SurfaceFilter
{
public:
AbstractSurfaceSink()
: mImageData(nullptr)
, mImageDataLength(0)
, mRow(0)
, mFlipVertically(false)
{ }
Maybe<SurfaceInvalidRect> TakeInvalidRect() override final;
protected:
uint8_t* DoResetToFirstRow() override final;
uint8_t* DoAdvanceRow() override final;
virtual uint8_t* GetRowPointer() const = 0;
gfx::IntRect mInvalidRect; /// The region of the surface that has been written
/// to since the last call to TakeInvalidRect().
uint8_t* mImageData; /// A pointer to the beginning of the surface data.
uint32_t mImageDataLength; /// The length of the surface data.
uint32_t mRow; /// The row to which we're writing. (0-indexed)
bool mFlipVertically; /// If true, write the rows from top to bottom.
};
class SurfaceSink;
/// A configuration struct for SurfaceSink.
struct SurfaceConfig
{
using Filter = SurfaceSink;
Decoder* mDecoder; /// Which Decoder to use to allocate the surface.
uint32_t mFrameNum; /// Which frame of animation this surface is for.
gfx::IntSize mOutputSize; /// The size of the surface.
gfx::SurfaceFormat mFormat; /// The surface format (BGRA or BGRX).
bool mFlipVertically; /// If true, write the rows from bottom to top.
};
/**
* A sink for normal (i.e., non-paletted) surfaces. It handles the allocation of
* the surface and protects against buffer overflow. This sink should be used
* for all non-animated images and for the first frame of animated images.
*
* Sinks must always be at the end of the SurfaceFilter chain.
*/
class SurfaceSink final : public AbstractSurfaceSink
{
public:
nsresult Configure(const SurfaceConfig& aConfig);
protected:
uint8_t* GetRowPointer() const override;
};
class PalettedSurfaceSink;
struct PalettedSurfaceConfig
{
using Filter = PalettedSurfaceSink;
Decoder* mDecoder; /// Which Decoder to use to allocate the surface.
uint32_t mFrameNum; /// Which frame of animation this surface is for.
gfx::IntSize mOutputSize; /// The logical size of the surface.
gfx::IntRect mFrameRect; /// The surface subrect which contains data.
gfx::SurfaceFormat mFormat; /// The surface format (BGRA or BGRX).
uint8_t mPaletteDepth; /// The palette depth of this surface.
bool mFlipVertically; /// If true, write the rows from bottom to top.
};
/**
* A sink for paletted surfaces. It handles the allocation of the surface and
* protects against buffer overflow. This sink can be used for frames of
* animated images except the first.
*
* Sinks must always be at the end of the SurfaceFilter chain.
*
* XXX(seth): We'll remove all support for paletted surfaces in bug 1247520,
* which means we can remove PalettedSurfaceSink entirely.
*/
class PalettedSurfaceSink final : public AbstractSurfaceSink
{
public:
bool IsValidPalettedPipe() const override { return true; }
nsresult Configure(const PalettedSurfaceConfig& aConfig);
protected:
uint8_t* GetRowPointer() const override;
private:
/**
* The surface subrect which contains data. Note that the surface size we
* actually allocate is the size of the frame rect, not the logical size of
* the surface.
*/
gfx::IntRect mFrameRect;
};
} // namespace image
} // namespace mozilla
#endif // mozilla_image_SurfacePipe_h

249
image/SurfacePipeFactory.h Normal file
View file

@ -0,0 +1,249 @@
/* -*- 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/. */
#ifndef mozilla_image_SurfacePipeFactory_h
#define mozilla_image_SurfacePipeFactory_h
#include "SurfacePipe.h"
#include "SurfaceFilters.h"
namespace mozilla {
namespace image {
namespace detail {
/**
* FilterPipeline is a helper template for SurfacePipeFactory that determines
* the full type of the sequence of SurfaceFilters that a sequence of
* configuration structs corresponds to. To make this work, all configuration
* structs must include a typedef 'Filter' that identifies the SurfaceFilter
* they configure.
*/
template <typename... Configs>
struct FilterPipeline;
template <typename Config, typename... Configs>
struct FilterPipeline<Config, Configs...>
{
typedef typename Config::template Filter<typename FilterPipeline<Configs...>::Type> Type;
};
template <typename Config>
struct FilterPipeline<Config>
{
typedef typename Config::Filter Type;
};
} // namespace detail
/**
* Flags for SurfacePipeFactory, used in conjuction with the factory functions
* in SurfacePipeFactory to enable or disable various SurfacePipe
* functionality.
*/
enum class SurfacePipeFlags
{
DEINTERLACE = 1 << 0, // If set, deinterlace the image.
ADAM7_INTERPOLATE = 1 << 1, // If set, the caller is deinterlacing the
// image using ADAM7, and we may want to
// interpolate it for better intermediate results.
FLIP_VERTICALLY = 1 << 2, // If set, flip the image vertically.
PROGRESSIVE_DISPLAY = 1 << 3 // If set, we expect the image to be displayed
// progressively. This enables features that
// result in a better user experience for
// progressive display but which may be more
// computationally expensive.
};
MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(SurfacePipeFlags)
class SurfacePipeFactory
{
public:
/**
* Creates and initializes a normal (i.e., non-paletted) SurfacePipe.
*
* @param aDecoder The decoder whose current frame the SurfacePipe will write
* to.
* @param aFrameNum Which frame the SurfacePipe will write to. This will be 0
* for non-animated images.
* @param aInputSize The original size of the image.
* @param aOutputSize The size the SurfacePipe should output. Must be the same
* as @aInputSize or smaller. If smaller, the image will be
* downscaled during decoding.
* @param aFrameRect The portion of the image that actually contains data.
* @param aFormat The surface format of the image; generally B8G8R8A8 or
* B8G8R8X8.
* @param aFlags Flags enabling or disabling various functionality for the
* SurfacePipe; see the SurfacePipeFlags documentation for more
* information.
*
* @return A SurfacePipe if the parameters allowed one to be created
* successfully, or Nothing() if the SurfacePipe could not be
* initialized.
*/
static Maybe<SurfacePipe>
CreateSurfacePipe(Decoder* aDecoder,
uint32_t aFrameNum,
const nsIntSize& aInputSize,
const nsIntSize& aOutputSize,
const nsIntRect& aFrameRect,
gfx::SurfaceFormat aFormat,
SurfacePipeFlags aFlags)
{
const bool deinterlace = bool(aFlags & SurfacePipeFlags::DEINTERLACE);
const bool flipVertically = bool(aFlags & SurfacePipeFlags::FLIP_VERTICALLY);
const bool progressiveDisplay = bool(aFlags & SurfacePipeFlags::PROGRESSIVE_DISPLAY);
const bool downscale = aInputSize != aOutputSize;
const bool removeFrameRect =
!aFrameRect.IsEqualEdges(nsIntRect(0, 0, aInputSize.width, aInputSize.height));
// Don't interpolate if we're sure we won't show this surface to the user
// until it's completely decoded. The final pass of an ADAM7 image doesn't
// need interpolation, so we only need to interpolate if we'll be displaying
// the image while it's still being decoded.
const bool adam7Interpolate = bool(aFlags & SurfacePipeFlags::ADAM7_INTERPOLATE) &&
progressiveDisplay;
if (deinterlace && adam7Interpolate) {
MOZ_ASSERT_UNREACHABLE("ADAM7 deinterlacing is handled by libpng");
return Nothing();
}
// Construct configurations for the SurfaceFilters. Note that the order of
// these filters is significant. We want to deinterlace or interpolate raw
// input rows, before any other transformations, and we want to remove the
// frame rect (which may involve adding blank rows or columns to the image)
// before any downscaling, so that the new rows and columns are taken into
// account.
DeinterlacingConfig<uint32_t> deinterlacingConfig { progressiveDisplay };
ADAM7InterpolatingConfig interpolatingConfig;
RemoveFrameRectConfig removeFrameRectConfig { aFrameRect };
DownscalingConfig downscalingConfig { aInputSize, aFormat };
SurfaceConfig surfaceConfig { aDecoder, aFrameNum, aOutputSize,
aFormat, flipVertically };
Maybe<SurfacePipe> pipe;
if (downscale) {
if (removeFrameRect) {
if (deinterlace) {
pipe = MakePipe(deinterlacingConfig, removeFrameRectConfig,
downscalingConfig, surfaceConfig);
} else if (adam7Interpolate) {
pipe = MakePipe(interpolatingConfig, removeFrameRectConfig,
downscalingConfig, surfaceConfig);
} else { // (deinterlace and adam7Interpolate are false)
pipe = MakePipe(removeFrameRectConfig, downscalingConfig, surfaceConfig);
}
} else { // (removeFrameRect is false)
if (deinterlace) {
pipe = MakePipe(deinterlacingConfig, downscalingConfig, surfaceConfig);
} else if (adam7Interpolate) {
pipe = MakePipe(interpolatingConfig, downscalingConfig, surfaceConfig);
} else { // (deinterlace and adam7Interpolate are false)
pipe = MakePipe(downscalingConfig, surfaceConfig);
}
}
} else { // (downscale is false)
if (removeFrameRect) {
if (deinterlace) {
pipe = MakePipe(deinterlacingConfig, removeFrameRectConfig, surfaceConfig);
} else if (adam7Interpolate) {
pipe = MakePipe(interpolatingConfig, removeFrameRectConfig, surfaceConfig);
} else { // (deinterlace and adam7Interpolate are false)
pipe = MakePipe(removeFrameRectConfig, surfaceConfig);
}
} else { // (removeFrameRect is false)
if (deinterlace) {
pipe = MakePipe(deinterlacingConfig, surfaceConfig);
} else if (adam7Interpolate) {
pipe = MakePipe(interpolatingConfig, surfaceConfig);
} else { // (deinterlace and adam7Interpolate are false)
pipe = MakePipe(surfaceConfig);
}
}
}
return pipe;
}
/**
* Creates and initializes a paletted SurfacePipe.
*
* XXX(seth): We'll remove all support for paletted surfaces in bug 1247520,
* which means we can remove CreatePalettedSurfacePipe() entirely.
*
* @param aDecoder The decoder whose current frame the SurfacePipe will write
* to.
* @param aFrameNum Which frame the SurfacePipe will write to. This will be 0
* for non-animated images.
* @param aInputSize The original size of the image.
* @param aFrameRect The portion of the image that actually contains data.
* @param aFormat The surface format of the image; generally B8G8R8A8 or
* B8G8R8X8.
* @param aPaletteDepth The palette depth of the image.
* @param aFlags Flags enabling or disabling various functionality for the
* SurfacePipe; see the SurfacePipeFlags documentation for more
* information.
*
* @return A SurfacePipe if the parameters allowed one to be created
* successfully, or Nothing() if the SurfacePipe could not be
* initialized.
*/
static Maybe<SurfacePipe>
CreatePalettedSurfacePipe(Decoder* aDecoder,
uint32_t aFrameNum,
const nsIntSize& aInputSize,
const nsIntRect& aFrameRect,
gfx::SurfaceFormat aFormat,
uint8_t aPaletteDepth,
SurfacePipeFlags aFlags)
{
const bool deinterlace = bool(aFlags & SurfacePipeFlags::DEINTERLACE);
const bool flipVertically = bool(aFlags & SurfacePipeFlags::FLIP_VERTICALLY);
const bool progressiveDisplay = bool(aFlags & SurfacePipeFlags::PROGRESSIVE_DISPLAY);
// Construct configurations for the SurfaceFilters.
DeinterlacingConfig<uint8_t> deinterlacingConfig { progressiveDisplay };
PalettedSurfaceConfig palettedSurfaceConfig { aDecoder, aFrameNum, aInputSize,
aFrameRect, aFormat, aPaletteDepth,
flipVertically };
Maybe<SurfacePipe> pipe;
if (deinterlace) {
pipe = MakePipe(deinterlacingConfig, palettedSurfaceConfig);
} else {
pipe = MakePipe(palettedSurfaceConfig);
}
return pipe;
}
private:
template <typename... Configs>
static Maybe<SurfacePipe>
MakePipe(Configs... aConfigs)
{
auto pipe = MakeUnique<typename detail::FilterPipeline<Configs...>::Type>();
nsresult rv = pipe->Configure(aConfigs...);
if (NS_FAILED(rv)) {
return Nothing();
}
return Some(SurfacePipe { Move(pipe) } );
}
virtual ~SurfacePipeFactory() = 0;
};
} // namespace image
} // namespace mozilla
#endif // mozilla_image_SurfacePipeFactory_h

1350
image/VectorImage.cpp Normal file

File diff suppressed because it is too large Load diff

140
image/VectorImage.h Normal file
View file

@ -0,0 +1,140 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* 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/. */
#ifndef mozilla_image_VectorImage_h
#define mozilla_image_VectorImage_h
#include "Image.h"
#include "nsIStreamListener.h"
#include "mozilla/MemoryReporting.h"
class nsIRequest;
class gfxDrawable;
namespace mozilla {
namespace image {
struct SVGDrawingParameters;
class SVGDocumentWrapper;
class SVGRootRenderingObserver;
class SVGLoadEventListener;
class SVGParseCompleteListener;
class VectorImage final : public ImageResource,
public nsIStreamListener
{
public:
NS_DECL_ISUPPORTS
NS_DECL_NSIREQUESTOBSERVER
NS_DECL_NSISTREAMLISTENER
NS_DECL_IMGICONTAINER
// (no public constructor - use ImageFactory)
// Methods inherited from Image
virtual size_t SizeOfSourceWithComputedFallback(MallocSizeOf aMallocSizeOf)
const override;
virtual void CollectSizeOfSurfaces(nsTArray<SurfaceMemoryCounter>& aCounters,
MallocSizeOf aMallocSizeOf) const override;
virtual nsresult OnImageDataAvailable(nsIRequest* aRequest,
nsISupports* aContext,
nsIInputStream* aInStr,
uint64_t aSourceOffset,
uint32_t aCount) override;
virtual nsresult OnImageDataComplete(nsIRequest* aRequest,
nsISupports* aContext,
nsresult aResult,
bool aLastPart) override;
virtual void OnSurfaceDiscarded() override;
/**
* Callback for SVGRootRenderingObserver.
*
* This just sets a dirty flag that we check in VectorImage::RequestRefresh,
* which is called under the ticks of the refresh driver of any observing
* documents that we may have. Only then (after all animations in this image
* have been updated) do we send out "frame changed" notifications,
*/
void InvalidateObserversOnNextRefreshDriverTick();
// Callback for SVGParseCompleteListener.
void OnSVGDocumentParsed();
// Callbacks for SVGLoadEventListener.
void OnSVGDocumentLoaded();
void OnSVGDocumentError();
virtual void ReportUseCounters() override;
protected:
explicit VectorImage(ImageURL* aURI = nullptr);
virtual ~VectorImage();
virtual nsresult StartAnimation() override;
virtual nsresult StopAnimation() override;
virtual bool ShouldAnimate() override;
private:
/// Attempt to find a cached surface matching @aParams in the SurfaceCache.
already_AddRefed<gfxDrawable>
LookupCachedSurface(const SVGDrawingParameters& aParams);
void CreateSurfaceAndShow(const SVGDrawingParameters& aParams,
gfx::BackendType aBackend);
void Show(gfxDrawable* aDrawable, const SVGDrawingParameters& aParams);
nsresult Init(const char* aMimeType, uint32_t aFlags);
/**
* In catastrophic circumstances like a GPU driver crash, we may lose our
* surfaces even if they're locked. RecoverFromLossOfSurfaces discards all
* existing surfaces, allowing us to recover.
*/
void RecoverFromLossOfSurfaces();
void CancelAllListeners();
void SendInvalidationNotifications();
RefPtr<SVGDocumentWrapper> mSVGDocumentWrapper;
RefPtr<SVGRootRenderingObserver> mRenderingObserver;
RefPtr<SVGLoadEventListener> mLoadEventListener;
RefPtr<SVGParseCompleteListener> mParseCompleteListener;
/// Count of locks on this image (roughly correlated to visible instances).
uint32_t mLockCount;
// Stored result from the Necko load of the image, which we save in
// OnImageDataComplete if the underlying SVG document isn't loaded. If we save
// this, we actually notify this progress (and clear this value) in
// OnSVGDocumentLoaded or OnSVGDocumentError.
Maybe<Progress> mLoadProgress;
bool mIsInitialized; // Have we been initialized?
bool mDiscardable; // Are we discardable?
bool mIsFullyLoaded; // Has the SVG document finished
// loading?
bool mIsDrawing; // Are we currently drawing?
bool mHaveAnimations; // Is our SVG content SMIL-animated?
// (Only set after mIsFullyLoaded.)
bool mHasPendingInvalidation; // Invalidate observers next refresh
// driver tick.
friend class ImageFactory;
};
inline NS_IMETHODIMP VectorImage::GetAnimationMode(uint16_t* aAnimationMode) {
return GetAnimationModeInternal(aAnimationMode);
}
inline NS_IMETHODIMP VectorImage::SetAnimationMode(uint16_t aAnimationMode) {
return SetAnimationModeInternal(aAnimationMode);
}
} // namespace image
} // namespace mozilla
#endif // mozilla_image_VectorImage_h

22
image/build/moz.build Normal file
View file

@ -0,0 +1,22 @@
# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
# vim: set filetype=python:
# 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/.
EXPORTS += [
'nsImageModule.h',
]
SOURCES += [
'nsImageModule.cpp',
]
FINAL_LIBRARY = 'xul'
LOCAL_INCLUDES += [
'/image',
'/image/encoders/bmp',
'/image/encoders/ico',
'/image/encoders/jpeg',
'/image/encoders/png',
]

View file

@ -0,0 +1,136 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
*
* 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/. */
#include "nsImageModule.h"
#include "mozilla/ModuleUtils.h"
#include "nsMimeTypes.h"
#include "DecodePool.h"
#include "ImageFactory.h"
#include "ShutdownTracker.h"
#include "SurfaceCache.h"
#include "SurfacePipe.h"
#include "gfxPrefs.h"
#include "imgLoader.h"
#include "imgRequest.h"
#include "imgRequestProxy.h"
#include "imgTools.h"
#include "nsICOEncoder.h"
#include "nsPNGEncoder.h"
#include "nsJPEGEncoder.h"
#include "nsBMPEncoder.h"
// objects that just require generic constructors
using namespace mozilla::image;
// XXX We would like to get rid of the imgLoader factory constructor. See the
// comment documenting the imgLoader constructor.
NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(imgLoader, Init)
NS_GENERIC_FACTORY_CONSTRUCTOR(imgRequestProxy)
NS_GENERIC_FACTORY_CONSTRUCTOR(imgTools)
NS_GENERIC_FACTORY_CONSTRUCTOR(nsICOEncoder)
NS_GENERIC_FACTORY_CONSTRUCTOR(nsJPEGEncoder)
NS_GENERIC_FACTORY_CONSTRUCTOR(nsPNGEncoder)
NS_GENERIC_FACTORY_CONSTRUCTOR(nsBMPEncoder)
NS_DEFINE_NAMED_CID(NS_IMGLOADER_CID);
NS_DEFINE_NAMED_CID(NS_IMGREQUESTPROXY_CID);
NS_DEFINE_NAMED_CID(NS_IMGTOOLS_CID);
NS_DEFINE_NAMED_CID(NS_ICOENCODER_CID);
NS_DEFINE_NAMED_CID(NS_JPEGENCODER_CID);
NS_DEFINE_NAMED_CID(NS_PNGENCODER_CID);
NS_DEFINE_NAMED_CID(NS_BMPENCODER_CID);
static const mozilla::Module::CIDEntry kImageCIDs[] = {
{ &kNS_IMGLOADER_CID, false, nullptr, imgLoaderConstructor, },
{ &kNS_IMGREQUESTPROXY_CID, false, nullptr, imgRequestProxyConstructor, },
{ &kNS_IMGTOOLS_CID, false, nullptr, imgToolsConstructor, },
{ &kNS_ICOENCODER_CID, false, nullptr, nsICOEncoderConstructor, },
{ &kNS_JPEGENCODER_CID, false, nullptr, nsJPEGEncoderConstructor, },
{ &kNS_PNGENCODER_CID, false, nullptr, nsPNGEncoderConstructor, },
{ &kNS_BMPENCODER_CID, false, nullptr, nsBMPEncoderConstructor, },
{ nullptr }
};
static const mozilla::Module::ContractIDEntry kImageContracts[] = {
{ "@mozilla.org/image/cache;1", &kNS_IMGLOADER_CID },
{ "@mozilla.org/image/loader;1", &kNS_IMGLOADER_CID },
{ "@mozilla.org/image/request;1", &kNS_IMGREQUESTPROXY_CID },
{ "@mozilla.org/image/tools;1", &kNS_IMGTOOLS_CID },
{ "@mozilla.org/image/encoder;2?type=" IMAGE_ICO_MS, &kNS_ICOENCODER_CID },
{ "@mozilla.org/image/encoder;2?type=" IMAGE_JPEG, &kNS_JPEGENCODER_CID },
{ "@mozilla.org/image/encoder;2?type=" IMAGE_PNG, &kNS_PNGENCODER_CID },
{ "@mozilla.org/image/encoder;2?type=" IMAGE_BMP, &kNS_BMPENCODER_CID },
{ nullptr }
};
static const mozilla::Module::CategoryEntry kImageCategories[] = {
{ "Gecko-Content-Viewers", IMAGE_GIF, "@mozilla.org/content/document-loader-factory;1" },
{ "Gecko-Content-Viewers", IMAGE_JPEG, "@mozilla.org/content/document-loader-factory;1" },
{ "Gecko-Content-Viewers", IMAGE_PJPEG, "@mozilla.org/content/document-loader-factory;1" },
{ "Gecko-Content-Viewers", IMAGE_JPG, "@mozilla.org/content/document-loader-factory;1" },
{ "Gecko-Content-Viewers", IMAGE_ICO, "@mozilla.org/content/document-loader-factory;1" },
{ "Gecko-Content-Viewers", IMAGE_ICO_MS, "@mozilla.org/content/document-loader-factory;1" },
{ "Gecko-Content-Viewers", IMAGE_BMP, "@mozilla.org/content/document-loader-factory;1" },
{ "Gecko-Content-Viewers", IMAGE_BMP_MS, "@mozilla.org/content/document-loader-factory;1" },
{ "Gecko-Content-Viewers", IMAGE_ICON_MS, "@mozilla.org/content/document-loader-factory;1" },
{ "Gecko-Content-Viewers", IMAGE_PNG, "@mozilla.org/content/document-loader-factory;1" },
{ "Gecko-Content-Viewers", IMAGE_APNG, "@mozilla.org/content/document-loader-factory;1" },
{ "Gecko-Content-Viewers", IMAGE_X_PNG, "@mozilla.org/content/document-loader-factory;1" },
{ "content-sniffing-services", "@mozilla.org/image/loader;1", "@mozilla.org/image/loader;1" },
{ nullptr }
};
static bool sInitialized = false;
nsresult
mozilla::image::EnsureModuleInitialized()
{
MOZ_ASSERT(NS_IsMainThread());
if (sInitialized) {
return NS_OK;
}
// Make sure the preferences are initialized
gfxPrefs::GetSingleton();
mozilla::image::ShutdownTracker::Initialize();
mozilla::image::ImageFactory::Initialize();
mozilla::image::DecodePool::Initialize();
mozilla::image::SurfaceCache::Initialize();
mozilla::image::SurfacePipe::Initialize();
imgLoader::GlobalInit();
sInitialized = true;
return NS_OK;
}
void
mozilla::image::ShutdownModule()
{
if (!sInitialized) {
return;
}
imgLoader::Shutdown();
mozilla::image::SurfaceCache::Shutdown();
sInitialized = false;
}
static const mozilla::Module kImageModule = {
mozilla::Module::kVersion,
kImageCIDs,
kImageContracts,
kImageCategories,
nullptr,
mozilla::image::EnsureModuleInitialized,
// We need to be careful about shutdown ordering to avoid intermittent crashes
// when hashtable enumeration decides to destroy modules in an unfortunate
// order. So our shutdown is invoked explicitly during layout module shutdown.
nullptr
};
NSMODULE_DEFN(nsImageLib2Module) = &kImageModule;

View file

@ -0,0 +1,20 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* 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/. */
#ifndef mozilla_image_build_nsImageModule_h
#define mozilla_image_build_nsImageModule_h
#include "nsError.h"
namespace mozilla {
namespace image {
nsresult EnsureModuleInitialized();
void ShutdownModule();
} /* namespace image */
} /* namespace mozilla */
#endif // mozilla_image_build_nsImageModule_h

331
image/decoders/EXIF.cpp Normal file
View file

@ -0,0 +1,331 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* 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/. */
#include "EXIF.h"
#include "mozilla/EndianUtils.h"
namespace mozilla {
namespace image {
// Section references in this file refer to the EXIF v2.3 standard, also known
// as CIPA DC-008-Translation-2010.
// See Section 4.6.4, Table 4.
// Typesafe enums are intentionally not used here since we're comparing to raw
// integers produced by parsing.
enum EXIFTag
{
OrientationTag = 0x112,
};
// See Section 4.6.2.
enum EXIFType
{
ByteType = 1,
ASCIIType = 2,
ShortType = 3,
LongType = 4,
RationalType = 5,
UndefinedType = 7,
SignedLongType = 9,
SignedRational = 10,
};
static const char* EXIFHeader = "Exif\0\0";
static const uint32_t EXIFHeaderLength = 6;
/////////////////////////////////////////////////////////////
// Parse EXIF data, typically found in a JPEG's APP1 segment.
/////////////////////////////////////////////////////////////
EXIFData
EXIFParser::ParseEXIF(const uint8_t* aData, const uint32_t aLength)
{
if (!Initialize(aData, aLength)) {
return EXIFData();
}
if (!ParseEXIFHeader()) {
return EXIFData();
}
uint32_t offsetIFD;
if (!ParseTIFFHeader(offsetIFD)) {
return EXIFData();
}
JumpTo(offsetIFD);
Orientation orientation;
if (!ParseIFD0(orientation)) {
return EXIFData();
}
// We only care about orientation at this point, so we don't bother with the
// other IFDs. If we got this far we're done.
return EXIFData(orientation);
}
/////////////////////////////////////////////////////////
// Parse the EXIF header. (Section 4.7.2, Figure 30)
/////////////////////////////////////////////////////////
bool
EXIFParser::ParseEXIFHeader()
{
return MatchString(EXIFHeader, EXIFHeaderLength);
}
/////////////////////////////////////////////////////////
// Parse the TIFF header. (Section 4.5.2, Table 1)
/////////////////////////////////////////////////////////
bool
EXIFParser::ParseTIFFHeader(uint32_t& aIFD0OffsetOut)
{
// Determine byte order.
if (MatchString("MM\0*", 4)) {
mByteOrder = ByteOrder::BigEndian;
} else if (MatchString("II*\0", 4)) {
mByteOrder = ByteOrder::LittleEndian;
} else {
return false;
}
// Determine offset of the 0th IFD. (It shouldn't be greater than 64k, which
// is the maximum size of the entry APP1 segment.)
uint32_t ifd0Offset;
if (!ReadUInt32(ifd0Offset) || ifd0Offset > 64 * 1024) {
return false;
}
// The IFD offset is relative to the beginning of the TIFF header, which
// begins after the EXIF header, so we need to increase the offset
// appropriately.
aIFD0OffsetOut = ifd0Offset + EXIFHeaderLength;
return true;
}
/////////////////////////////////////////////////////////
// Parse the entries in IFD0. (Section 4.6.2)
/////////////////////////////////////////////////////////
bool
EXIFParser::ParseIFD0(Orientation& aOrientationOut)
{
uint16_t entryCount;
if (!ReadUInt16(entryCount)) {
return false;
}
for (uint16_t entry = 0 ; entry < entryCount ; ++entry) {
// Read the fields of the entry.
uint16_t tag;
if (!ReadUInt16(tag)) {
return false;
}
// Right now, we only care about orientation, so we immediately skip to the
// next entry if we find anything else.
if (tag != OrientationTag) {
Advance(10);
continue;
}
uint16_t type;
if (!ReadUInt16(type)) {
return false;
}
uint32_t count;
if (!ReadUInt32(count)) {
return false;
}
// We should have an orientation value here; go ahead and parse it.
if (!ParseOrientation(type, count, aOrientationOut)) {
return false;
}
// Since the orientation is all we care about, we're done.
return true;
}
// We didn't find an orientation field in the IFD. That's OK; we assume the
// default orientation in that case.
aOrientationOut = Orientation();
return true;
}
bool
EXIFParser::ParseOrientation(uint16_t aType, uint32_t aCount, Orientation& aOut)
{
// Sanity check the type and count.
if (aType != ShortType || aCount != 1) {
return false;
}
uint16_t value;
if (!ReadUInt16(value)) {
return false;
}
switch (value) {
case 1: aOut = Orientation(Angle::D0, Flip::Unflipped); break;
case 2: aOut = Orientation(Angle::D0, Flip::Horizontal); break;
case 3: aOut = Orientation(Angle::D180, Flip::Unflipped); break;
case 4: aOut = Orientation(Angle::D180, Flip::Horizontal); break;
case 5: aOut = Orientation(Angle::D90, Flip::Horizontal); break;
case 6: aOut = Orientation(Angle::D90, Flip::Unflipped); break;
case 7: aOut = Orientation(Angle::D270, Flip::Horizontal); break;
case 8: aOut = Orientation(Angle::D270, Flip::Unflipped); break;
default: return false;
}
// This is a 32-bit field, but the orientation value only occupies the first
// 16 bits. We need to advance another 16 bits to consume the entire field.
Advance(2);
return true;
}
bool
EXIFParser::Initialize(const uint8_t* aData, const uint32_t aLength)
{
if (aData == nullptr) {
return false;
}
// An APP1 segment larger than 64k violates the JPEG standard.
if (aLength > 64 * 1024) {
return false;
}
mStart = mCurrent = aData;
mLength = mRemainingLength = aLength;
mByteOrder = ByteOrder::Unknown;
return true;
}
void
EXIFParser::Advance(const uint32_t aDistance)
{
if (mRemainingLength >= aDistance) {
mCurrent += aDistance;
mRemainingLength -= aDistance;
} else {
mCurrent = mStart;
mRemainingLength = 0;
}
}
void
EXIFParser::JumpTo(const uint32_t aOffset)
{
if (mLength >= aOffset) {
mCurrent = mStart + aOffset;
mRemainingLength = mLength - aOffset;
} else {
mCurrent = mStart;
mRemainingLength = 0;
}
}
bool
EXIFParser::MatchString(const char* aString, const uint32_t aLength)
{
if (mRemainingLength < aLength) {
return false;
}
for (uint32_t i = 0 ; i < aLength ; ++i) {
if (mCurrent[i] != aString[i]) {
return false;
}
}
Advance(aLength);
return true;
}
bool
EXIFParser::MatchUInt16(const uint16_t aValue)
{
if (mRemainingLength < 2) {
return false;
}
bool matched;
switch (mByteOrder) {
case ByteOrder::LittleEndian:
matched = LittleEndian::readUint16(mCurrent) == aValue;
break;
case ByteOrder::BigEndian:
matched = BigEndian::readUint16(mCurrent) == aValue;
break;
default:
NS_NOTREACHED("Should know the byte order by now");
matched = false;
}
if (matched) {
Advance(2);
}
return matched;
}
bool
EXIFParser::ReadUInt16(uint16_t& aValue)
{
if (mRemainingLength < 2) {
return false;
}
bool matched = true;
switch (mByteOrder) {
case ByteOrder::LittleEndian:
aValue = LittleEndian::readUint16(mCurrent);
break;
case ByteOrder::BigEndian:
aValue = BigEndian::readUint16(mCurrent);
break;
default:
NS_NOTREACHED("Should know the byte order by now");
matched = false;
}
if (matched) {
Advance(2);
}
return matched;
}
bool
EXIFParser::ReadUInt32(uint32_t& aValue)
{
if (mRemainingLength < 4) {
return false;
}
bool matched = true;
switch (mByteOrder) {
case ByteOrder::LittleEndian:
aValue = LittleEndian::readUint32(mCurrent);
break;
case ByteOrder::BigEndian:
aValue = BigEndian::readUint32(mCurrent);
break;
default:
NS_NOTREACHED("Should know the byte order by now");
matched = false;
}
if (matched) {
Advance(4);
}
return matched;
}
} // namespace image
} // namespace mozilla

75
image/decoders/EXIF.h Normal file
View file

@ -0,0 +1,75 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* 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/. */
#ifndef mozilla_image_decoders_EXIF_h
#define mozilla_image_decoders_EXIF_h
#include <stdint.h>
#include "nsDebug.h"
#include "Orientation.h"
namespace mozilla {
namespace image {
enum class ByteOrder : uint8_t {
Unknown,
LittleEndian,
BigEndian
};
struct EXIFData
{
EXIFData() { }
explicit EXIFData(Orientation aOrientation) : orientation(aOrientation) { }
const Orientation orientation;
};
class EXIFParser
{
public:
static EXIFData
Parse(const uint8_t* aData, const uint32_t aLength)
{
EXIFParser parser;
return parser.ParseEXIF(aData, aLength);
}
private:
EXIFParser()
: mStart(nullptr)
, mCurrent(nullptr)
, mLength(0)
, mRemainingLength(0)
, mByteOrder(ByteOrder::Unknown)
{ }
EXIFData ParseEXIF(const uint8_t* aData, const uint32_t aLength);
bool ParseEXIFHeader();
bool ParseTIFFHeader(uint32_t& aIFD0OffsetOut);
bool ParseIFD0(Orientation& aOrientationOut);
bool ParseOrientation(uint16_t aType, uint32_t aCount, Orientation& aOut);
bool Initialize(const uint8_t* aData, const uint32_t aLength);
void Advance(const uint32_t aDistance);
void JumpTo(const uint32_t aOffset);
bool MatchString(const char* aString, const uint32_t aLength);
bool MatchUInt16(const uint16_t aValue);
bool ReadUInt16(uint16_t& aOut);
bool ReadUInt32(uint32_t& aOut);
const uint8_t* mStart;
const uint8_t* mCurrent;
uint32_t mLength;
uint32_t mRemainingLength;
ByteOrder mByteOrder;
};
} // namespace image
} // namespace mozilla
#endif // mozilla_image_decoders_EXIF_h

65
image/decoders/GIF2.h Normal file
View file

@ -0,0 +1,65 @@
/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* 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/. */
#ifndef mozilla_image_decoders_GIF2_H
#define mozilla_image_decoders_GIF2_H
#define MAX_LZW_BITS 12
#define MAX_BITS 4097 // 2^MAX_LZW_BITS+1
#define MAX_COLORS 256
#define MIN_HOLD_SIZE 256
enum { GIF_TRAILER = 0x3B }; // ';'
enum { GIF_IMAGE_SEPARATOR = 0x2C }; // ','
enum { GIF_EXTENSION_INTRODUCER = 0x21 }; // '!'
enum { GIF_GRAPHIC_CONTROL_LABEL = 0xF9 };
enum { GIF_APPLICATION_EXTENSION_LABEL = 0xFF };
// A GIF decoder's state
typedef struct gif_struct {
// LZW decoder state machine
uint8_t* stackp; // Current stack pointer
int datasize;
int codesize;
int codemask;
int avail; // Index of next available slot in dictionary
int oldcode;
uint8_t firstchar;
int bits; // Number of unread bits in "datum"
int32_t datum; // 32-bit input buffer
// Output state machine
int64_t pixels_remaining; // Pixels remaining to be output.
// Parameters for image frame currently being decoded
int tpixel; // Index of transparent pixel
int32_t disposal_method; // Restore to background, leave in place, etc.
uint32_t* local_colormap; // Per-image colormap
int local_colormap_size; // Size of local colormap array.
uint32_t delay_time; // Display time, in milliseconds,
// for this image in a multi-image GIF
// Global (multi-image) state
int version; // Either 89 for GIF89 or 87 for GIF87
int32_t screen_width; // Logical screen width & height
int32_t screen_height;
uint8_t global_colormap_depth; // Depth of global colormap array
uint16_t global_colormap_count; // Number of colors in global colormap
int images_decoded; // Counts images for multi-part GIFs
int loop_count; // Netscape specific extension block to control
// the number of animation loops a GIF
// renders.
bool is_transparent; // TRUE, if tpixel is valid
uint16_t prefix[MAX_BITS]; // LZW decoding tables
uint32_t global_colormap[MAX_COLORS]; // Default colormap if local not
// supplied
uint8_t suffix[MAX_BITS]; // LZW decoding tables
uint8_t stack[MAX_BITS]; // Base of LZW decoder stack
} gif_struct;
#endif // mozilla_image_decoders_GIF2_H

195
image/decoders/iccjpeg.c Normal file
View file

@ -0,0 +1,195 @@
/* 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/. */
/*
* iccjpeg.c
*
* This file provides code to read and write International Color Consortium
* (ICC) device profiles embedded in JFIF JPEG image files. The ICC has
* defined a standard format for including such data in JPEG "APP2" markers.
* The code given here does not know anything about the internal structure
* of the ICC profile data; it just knows how to put the profile data into
* a JPEG file being written, or get it back out when reading.
*
* This code depends on new features added to the IJG JPEG library as of
* IJG release 6b; it will not compile or work with older IJG versions.
*
* NOTE: this code would need surgery to work on 16-bit-int machines
* with ICC profiles exceeding 64K bytes in size. If you need to do that,
* change all the "unsigned int" variables to "INT32". You'll also need
* to find a malloc() replacement that can allocate more than 64K.
*/
#include "iccjpeg.h"
#include <stdlib.h> /* define malloc() */
/*
* Since an ICC profile can be larger than the maximum size of a JPEG marker
* (64K), we need provisions to split it into multiple markers. The format
* defined by the ICC specifies one or more APP2 markers containing the
* following data:
* Identifying string ASCII "ICC_PROFILE\0" (12 bytes)
* Marker sequence number 1 for first APP2, 2 for next, etc (1 byte)
* Number of markers Total number of APP2's used (1 byte)
* Profile data (remainder of APP2 data)
* Decoders should use the marker sequence numbers to reassemble the profile,
* rather than assuming that the APP2 markers appear in the correct sequence.
*/
#define ICC_MARKER (JPEG_APP0 + 2) /* JPEG marker code for ICC */
#define ICC_OVERHEAD_LEN 14 /* size of non-profile data in APP2 */
#define MAX_BYTES_IN_MARKER 65533 /* maximum data len of a JPEG marker */
#define MAX_DATA_BYTES_IN_MARKER (MAX_BYTES_IN_MARKER - ICC_OVERHEAD_LEN)
/*
* Prepare for reading an ICC profile
*/
void
setup_read_icc_profile (j_decompress_ptr cinfo)
{
/* Tell the library to keep any APP2 data it may find */
jpeg_save_markers(cinfo, ICC_MARKER, 0xFFFF);
}
/*
* Handy subroutine to test whether a saved marker is an ICC profile marker.
*/
static boolean
marker_is_icc (jpeg_saved_marker_ptr marker)
{
return
marker->marker == ICC_MARKER &&
marker->data_length >= ICC_OVERHEAD_LEN &&
/* verify the identifying string */
GETJOCTET(marker->data[0]) == 0x49 &&
GETJOCTET(marker->data[1]) == 0x43 &&
GETJOCTET(marker->data[2]) == 0x43 &&
GETJOCTET(marker->data[3]) == 0x5F &&
GETJOCTET(marker->data[4]) == 0x50 &&
GETJOCTET(marker->data[5]) == 0x52 &&
GETJOCTET(marker->data[6]) == 0x4F &&
GETJOCTET(marker->data[7]) == 0x46 &&
GETJOCTET(marker->data[8]) == 0x49 &&
GETJOCTET(marker->data[9]) == 0x4C &&
GETJOCTET(marker->data[10]) == 0x45 &&
GETJOCTET(marker->data[11]) == 0x0;
}
/*
* See if there was an ICC profile in the JPEG file being read;
* if so, reassemble and return the profile data.
*
* TRUE is returned if an ICC profile was found, FALSE if not.
* If TRUE is returned, *icc_data_ptr is set to point to the
* returned data, and *icc_data_len is set to its length.
*
* IMPORTANT: the data at **icc_data_ptr has been allocated with malloc()
* and must be freed by the caller with free() when the caller no longer
* needs it. (Alternatively, we could write this routine to use the
* IJG library's memory allocator, so that the data would be freed implicitly
* at jpeg_finish_decompress() time. But it seems likely that many apps
* will prefer to have the data stick around after decompression finishes.)
*
* NOTE: if the file contains invalid ICC APP2 markers, we just silently
* return FALSE. You might want to issue an error message instead.
*/
boolean
read_icc_profile (j_decompress_ptr cinfo,
JOCTET** icc_data_ptr,
unsigned int* icc_data_len)
{
jpeg_saved_marker_ptr marker;
int num_markers = 0;
int seq_no;
JOCTET* icc_data;
unsigned int total_length;
#define MAX_SEQ_NO 255 /* sufficient since marker numbers are bytes */
char marker_present[MAX_SEQ_NO+1]; /* 1 if marker found */
unsigned int data_length[MAX_SEQ_NO+1]; /* size of profile data in marker */
unsigned int data_offset[MAX_SEQ_NO+1]; /* offset for data in marker */
*icc_data_ptr = NULL; /* avoid confusion if FALSE return */
*icc_data_len = 0;
/* This first pass over the saved markers discovers whether there are
* any ICC markers and verifies the consistency of the marker numbering.
*/
for (seq_no = 1; seq_no <= MAX_SEQ_NO; seq_no++) {
marker_present[seq_no] = 0;
}
for (marker = cinfo->marker_list; marker != NULL; marker = marker->next) {
if (marker_is_icc(marker)) {
if (num_markers == 0) {
num_markers = GETJOCTET(marker->data[13]);
} else if (num_markers != GETJOCTET(marker->data[13])) {
return FALSE; /* inconsistent num_markers fields */
}
seq_no = GETJOCTET(marker->data[12]);
if (seq_no <= 0 || seq_no > num_markers) {
return FALSE; /* bogus sequence number */
}
if (marker_present[seq_no]) {
return FALSE; /* duplicate sequence numbers */
}
marker_present[seq_no] = 1;
data_length[seq_no] = marker->data_length - ICC_OVERHEAD_LEN;
}
}
if (num_markers == 0) {
return FALSE;
}
/* Check for missing markers, count total space needed,
* compute offset of each marker's part of the data.
*/
total_length = 0;
for (seq_no = 1; seq_no <= num_markers; seq_no++) {
if (marker_present[seq_no] == 0) {
return FALSE; /* missing sequence number */
}
data_offset[seq_no] = total_length;
total_length += data_length[seq_no];
}
if (total_length <= 0) {
return FALSE; /* found only empty markers? */
}
/* Allocate space for assembled data */
icc_data = (JOCTET*) malloc(total_length * sizeof(JOCTET));
if (icc_data == NULL) {
return FALSE; /* oops, out of memory */
}
/* and fill it in */
for (marker = cinfo->marker_list; marker != NULL; marker = marker->next) {
if (marker_is_icc(marker)) {
JOCTET FAR* src_ptr;
JOCTET* dst_ptr;
unsigned int length;
seq_no = GETJOCTET(marker->data[12]);
dst_ptr = icc_data + data_offset[seq_no];
src_ptr = marker->data + ICC_OVERHEAD_LEN;
length = data_length[seq_no];
while (length--) {
*dst_ptr++ = *src_ptr++;
}
}
}
*icc_data_ptr = icc_data;
*icc_data_len = total_length;
return TRUE;
}

67
image/decoders/iccjpeg.h Normal file
View file

@ -0,0 +1,67 @@
/* 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/. */
/*
* iccjpeg.h
*
* This file provides code to read and write International Color Consortium
* (ICC) device profiles embedded in JFIF JPEG image files. The ICC has
* defined a standard format for including such data in JPEG "APP2" markers.
* The code given here does not know anything about the internal structure
* of the ICC profile data; it just knows how to put the profile data into
* a JPEG file being written, or get it back out when reading.
*
* This code depends on new features added to the IJG JPEG library as of
* IJG release 6b; it will not compile or work with older IJG versions.
*
* NOTE: this code would need surgery to work on 16-bit-int machines
* with ICC profiles exceeding 64K bytes in size. See iccprofile.c
* for details.
*/
#ifndef mozilla_image_decoders_iccjpeg_h
#define mozilla_image_decoders_iccjpeg_h
#include <stdio.h> /* needed to define "FILE", "NULL" */
#include "jpeglib.h"
/*
* Reading a JPEG file that may contain an ICC profile requires two steps:
*
* 1. After jpeg_create_decompress() but before jpeg_read_header(),
* call setup_read_icc_profile(). This routine tells the IJG library
* to save in memory any APP2 markers it may find in the file.
*
* 2. After jpeg_read_header(), call read_icc_profile() to find out
* whether there was a profile and obtain it if so.
*/
/*
* Prepare for reading an ICC profile
*/
extern void setup_read_icc_profile JPP((j_decompress_ptr cinfo));
/*
* See if there was an ICC profile in the JPEG file being read;
* if so, reassemble and return the profile data.
*
* TRUE is returned if an ICC profile was found, FALSE if not.
* If TRUE is returned, *icc_data_ptr is set to point to the
* returned data, and *icc_data_len is set to its length.
*
* IMPORTANT: the data at **icc_data_ptr has been allocated with malloc()
* and must be freed by the caller with free() when the caller no longer
* needs it. (Alternatively, we could write this routine to use the
* IJG library's memory allocator, so that the data would be freed implicitly
* at jpeg_finish_decompress() time. But it seems likely that many apps
* will prefer to have the data stick around after decompression finishes.)
*/
extern boolean read_icc_profile JPP((j_decompress_ptr cinfo,
JOCTET** icc_data_ptr,
unsigned int* icc_data_len));
#endif // mozilla_image_decoders_iccjpeg_h

View file

@ -0,0 +1,13 @@
# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
# vim: set filetype=python:
# 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/.
SOURCES += [
'nsIconChannel.cpp',
]
include('/ipc/chromium/chromium-config.mozbuild')
FINAL_LIBRARY = 'xul'

View file

@ -0,0 +1,145 @@
/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/* 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/. */
#include <stdlib.h>
#include "mozilla/dom/ContentChild.h"
#include "nsMimeTypes.h"
#include "nsIURL.h"
#include "nsXULAppAPI.h"
#include "AndroidBridge.h"
#include "nsIconChannel.h"
#include "nsIStringStream.h"
#include "nsNetUtil.h"
#include "nsComponentManagerUtils.h"
#include "nsNullPrincipal.h"
NS_IMPL_ISUPPORTS(nsIconChannel,
nsIRequest,
nsIChannel)
using namespace mozilla;
using mozilla::dom::ContentChild;
static nsresult
GetIconForExtension(const nsACString& aFileExt, uint32_t aIconSize,
uint8_t* const aBuf)
{
if (!AndroidBridge::Bridge()) {
return NS_ERROR_FAILURE;
}
AndroidBridge::Bridge()->GetIconForExtension(aFileExt, aIconSize, aBuf);
return NS_OK;
}
static nsresult
CallRemoteGetIconForExtension(const nsACString& aFileExt, uint32_t aIconSize,
uint8_t* const aBuf)
{
NS_ENSURE_TRUE(aBuf != nullptr, NS_ERROR_NULL_POINTER);
// An array has to be used to get data from remote process
InfallibleTArray<uint8_t> bits;
uint32_t bufSize = aIconSize * aIconSize * 4;
if (!ContentChild::GetSingleton()->SendGetIconForExtension(
PromiseFlatCString(aFileExt), aIconSize, &bits)) {
return NS_ERROR_FAILURE;
}
NS_ASSERTION(bits.Length() == bufSize, "Pixels array is incomplete");
if (bits.Length() != bufSize) {
return NS_ERROR_FAILURE;
}
memcpy(aBuf, bits.Elements(), bufSize);
return NS_OK;
}
static nsresult
moz_icon_to_channel(nsIURI* aURI, const nsACString& aFileExt,
uint32_t aIconSize, nsIChannel** aChannel)
{
NS_ENSURE_TRUE(aIconSize < 256 && aIconSize > 0, NS_ERROR_UNEXPECTED);
int width = aIconSize;
int height = aIconSize;
// moz-icon data should have two bytes for the size,
// then the ARGB pixel values with pre-multiplied Alpha
const int channels = 4;
long int buf_size = 2 + channels * height * width;
uint8_t* const buf = (uint8_t*)moz_xmalloc(buf_size);
NS_ENSURE_TRUE(buf, NS_ERROR_OUT_OF_MEMORY);
uint8_t* out = buf;
*(out++) = width;
*(out++) = height;
nsresult rv;
if (XRE_IsParentProcess()) {
rv = GetIconForExtension(aFileExt, aIconSize, out);
} else {
rv = CallRemoteGetIconForExtension(aFileExt, aIconSize, out);
}
NS_ENSURE_SUCCESS(rv, rv);
// Encode the RGBA data
const uint8_t* in = out;
for (int y = 0; y < height; ++y) {
for (int x = 0; x < width; ++x) {
uint8_t r = *(in++);
uint8_t g = *(in++);
uint8_t b = *(in++);
uint8_t a = *(in++);
#define DO_PREMULTIPLY(c_) uint8_t(uint16_t(c_) * uint16_t(a) / uint16_t(255))
*(out++) = DO_PREMULTIPLY(b);
*(out++) = DO_PREMULTIPLY(g);
*(out++) = DO_PREMULTIPLY(r);
*(out++) = a;
#undef DO_PREMULTIPLY
}
}
nsCOMPtr<nsIStringInputStream> stream =
do_CreateInstance("@mozilla.org/io/string-input-stream;1", &rv);
NS_ENSURE_SUCCESS(rv, rv);
rv = stream->AdoptData((char*)buf, buf_size);
NS_ENSURE_SUCCESS(rv, rv);
// nsIconProtocolHandler::NewChannel2 will provide the correct loadInfo for
// this iconChannel. Use the most restrictive security settings for the
// temporary loadInfo to make sure the channel can not be openend.
nsCOMPtr<nsIPrincipal> nullPrincipal = nsNullPrincipal::Create();
return NS_NewInputStreamChannel(aChannel,
aURI,
stream,
nullPrincipal,
nsILoadInfo::SEC_REQUIRE_SAME_ORIGIN_DATA_IS_BLOCKED,
nsIContentPolicy::TYPE_INTERNAL_IMAGE,
NS_LITERAL_CSTRING(IMAGE_ICON_MS));
}
nsresult
nsIconChannel::Init(nsIURI* aURI)
{
nsCOMPtr<nsIMozIconURI> iconURI = do_QueryInterface(aURI);
NS_ASSERTION(iconURI, "URI is not an nsIMozIconURI");
nsAutoCString stockIcon;
iconURI->GetStockIcon(stockIcon);
uint32_t desiredImageSize;
iconURI->GetImageSize(&desiredImageSize);
nsAutoCString iconFileExt;
iconURI->GetFileExtension(iconFileExt);
return moz_icon_to_channel(iconURI, iconFileExt, desiredImageSize,
getter_AddRefs(mRealChannel));
}

View file

@ -0,0 +1,46 @@
/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/* 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/. */
#ifndef mozilla_image_decoders_icon_android_nsIconChannel_h
#define mozilla_image_decoders_icon_android_nsIconChannel_h
#include "mozilla/Attributes.h"
#include "nsIChannel.h"
#include "nsIURI.h"
#include "nsIIconURI.h"
#include "nsCOMPtr.h"
/**
* This class is the Android implementation of nsIconChannel.
* It asks Android for an icon, and creates a new channel for
* that file to which all calls will be proxied.
*/
class nsIconChannel final : public nsIChannel {
public:
NS_DECL_ISUPPORTS
NS_FORWARD_NSIREQUEST(mRealChannel->)
NS_FORWARD_NSICHANNEL(mRealChannel->)
nsIconChannel() { }
/**
* Called by nsIconProtocolHandler after it creates this channel.
* Must be called before calling any other function on this object.
* If this method fails, no other function must be called on this object.
*/
nsresult Init(nsIURI* aURI);
private:
~nsIconChannel() { }
/**
* The channel to the temp icon file (e.g. to /tmp/2qy9wjqw.html).
* Will always be non-null after a successful Init.
*/
nsCOMPtr<nsIChannel> mRealChannel;
};
#endif // mozilla_image_decoders_icon_android_nsIconChannel_h

View file

@ -0,0 +1,16 @@
# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
# vim: set filetype=python:
# 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/.
SOURCES += [
'nsIconChannel.cpp',
]
FINAL_LIBRARY = 'xul'
if CONFIG['MOZ_ENABLE_GNOMEUI']:
CXXFLAGS += CONFIG['MOZ_GNOMEUI_CFLAGS']
else:
CXXFLAGS += CONFIG['TK_CFLAGS']

View file

@ -0,0 +1,427 @@
/* vim:set ts=2 sw=2 sts=2 cin et: */
/* 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/. */
#include "nsIconChannel.h"
#include <stdlib.h>
#include <unistd.h>
#include "mozilla/DebugOnly.h"
#include "mozilla/EndianUtils.h"
#include <algorithm>
#ifdef MOZ_ENABLE_GIO
#include <gio/gio.h>
#endif
#include <gtk/gtk.h>
#include "nsMimeTypes.h"
#include "nsIMIMEService.h"
#include "nsServiceManagerUtils.h"
#include "nsNetUtil.h"
#include "nsComponentManagerUtils.h"
#include "nsIStringStream.h"
#include "nsServiceManagerUtils.h"
#include "nsNullPrincipal.h"
#include "nsIURL.h"
#include "prlink.h"
NS_IMPL_ISUPPORTS(nsIconChannel,
nsIRequest,
nsIChannel)
static nsresult
moz_gdk_pixbuf_to_channel(GdkPixbuf* aPixbuf, nsIURI* aURI,
nsIChannel** aChannel)
{
int width = gdk_pixbuf_get_width(aPixbuf);
int height = gdk_pixbuf_get_height(aPixbuf);
NS_ENSURE_TRUE(height < 256 && width < 256 && height > 0 && width > 0 &&
gdk_pixbuf_get_colorspace(aPixbuf) == GDK_COLORSPACE_RGB &&
gdk_pixbuf_get_bits_per_sample(aPixbuf) == 8 &&
gdk_pixbuf_get_has_alpha(aPixbuf) &&
gdk_pixbuf_get_n_channels(aPixbuf) == 4,
NS_ERROR_UNEXPECTED);
const int n_channels = 4;
gsize buf_size = 2 + n_channels * height * width;
uint8_t* const buf = (uint8_t*)moz_xmalloc(buf_size);
NS_ENSURE_TRUE(buf, NS_ERROR_OUT_OF_MEMORY);
uint8_t* out = buf;
*(out++) = width;
*(out++) = height;
const guchar* const pixels = gdk_pixbuf_get_pixels(aPixbuf);
int rowextra = gdk_pixbuf_get_rowstride(aPixbuf) - width * n_channels;
// encode the RGB data and the A data
const guchar* in = pixels;
for (int y = 0; y < height; ++y, in += rowextra) {
for (int x = 0; x < width; ++x) {
uint8_t r = *(in++);
uint8_t g = *(in++);
uint8_t b = *(in++);
uint8_t a = *(in++);
#define DO_PREMULTIPLY(c_) uint8_t(uint16_t(c_) * uint16_t(a) / uint16_t(255))
#if MOZ_LITTLE_ENDIAN
*(out++) = DO_PREMULTIPLY(b);
*(out++) = DO_PREMULTIPLY(g);
*(out++) = DO_PREMULTIPLY(r);
*(out++) = a;
#else
*(out++) = a;
*(out++) = DO_PREMULTIPLY(r);
*(out++) = DO_PREMULTIPLY(g);
*(out++) = DO_PREMULTIPLY(b);
#endif
#undef DO_PREMULTIPLY
}
}
NS_ASSERTION(out == buf + buf_size, "size miscalculation");
nsresult rv;
nsCOMPtr<nsIStringInputStream> stream =
do_CreateInstance("@mozilla.org/io/string-input-stream;1", &rv);
// Prevent the leaking of buf
if (NS_WARN_IF(NS_FAILED(rv))) {
free(buf);
return rv;
}
// stream takes ownership of buf and will free it on destruction.
// This function cannot fail.
rv = stream->AdoptData((char*)buf, buf_size);
// If this no longer holds then re-examine buf's lifetime.
MOZ_ASSERT(NS_SUCCEEDED(rv));
NS_ENSURE_SUCCESS(rv, rv);
// nsIconProtocolHandler::NewChannel2 will provide the correct loadInfo for
// this iconChannel. Use the most restrictive security settings for the
// temporary loadInfo to make sure the channel can not be openend.
nsCOMPtr<nsIPrincipal> nullPrincipal = nsNullPrincipal::Create();
return NS_NewInputStreamChannel(aChannel,
aURI,
stream,
nullPrincipal,
nsILoadInfo::SEC_REQUIRE_SAME_ORIGIN_DATA_IS_BLOCKED,
nsIContentPolicy::TYPE_INTERNAL_IMAGE,
NS_LITERAL_CSTRING(IMAGE_ICON_MS));
}
static GtkWidget* gProtoWindow = nullptr;
static GtkWidget* gStockImageWidget = nullptr;
static void
ensure_stock_image_widget()
{
// Only the style of the GtkImage needs to be used, but the widget is kept
// to track dynamic style changes.
if (!gProtoWindow) {
gProtoWindow = gtk_window_new(GTK_WINDOW_POPUP);
GtkWidget* protoLayout = gtk_fixed_new();
gtk_container_add(GTK_CONTAINER(gProtoWindow), protoLayout);
gStockImageWidget = gtk_image_new();
gtk_container_add(GTK_CONTAINER(protoLayout), gStockImageWidget);
gtk_widget_ensure_style(gStockImageWidget);
}
}
static GtkIconSize
moz_gtk_icon_size(const char* name)
{
if (strcmp(name, "button") == 0) {
return GTK_ICON_SIZE_BUTTON;
}
if (strcmp(name, "menu") == 0) {
return GTK_ICON_SIZE_MENU;
}
if (strcmp(name, "toolbar") == 0) {
return GTK_ICON_SIZE_LARGE_TOOLBAR;
}
if (strcmp(name, "toolbarsmall") == 0) {
return GTK_ICON_SIZE_SMALL_TOOLBAR;
}
if (strcmp(name, "dnd") == 0) {
return GTK_ICON_SIZE_DND;
}
if (strcmp(name, "dialog") == 0) {
return GTK_ICON_SIZE_DIALOG;
}
return GTK_ICON_SIZE_MENU;
}
#ifdef MOZ_ENABLE_GIO
static int32_t
GetIconSize(nsIMozIconURI* aIconURI)
{
nsAutoCString iconSizeString;
aIconURI->GetIconSize(iconSizeString);
if (iconSizeString.IsEmpty()) {
uint32_t size;
mozilla::DebugOnly<nsresult> rv = aIconURI->GetImageSize(&size);
NS_ASSERTION(NS_SUCCEEDED(rv), "GetImageSize failed");
return size;
} else {
int size;
GtkIconSize icon_size = moz_gtk_icon_size(iconSizeString.get());
gtk_icon_size_lookup(icon_size, &size, nullptr);
return size;
}
}
/* Scale icon buffer to preferred size */
static nsresult
ScaleIconBuf(GdkPixbuf** aBuf, int32_t iconSize)
{
// Scale buffer only if width or height differ from preferred size
if (gdk_pixbuf_get_width(*aBuf) != iconSize &&
gdk_pixbuf_get_height(*aBuf) != iconSize) {
GdkPixbuf* scaled = gdk_pixbuf_scale_simple(*aBuf, iconSize, iconSize,
GDK_INTERP_BILINEAR);
// replace original buffer by scaled
g_object_unref(*aBuf);
*aBuf = scaled;
if (!scaled) {
return NS_ERROR_OUT_OF_MEMORY;
}
}
return NS_OK;
}
nsresult
nsIconChannel::InitWithGIO(nsIMozIconURI* aIconURI)
{
GIcon *icon = nullptr;
nsCOMPtr<nsIURL> fileURI;
// Read icon content
aIconURI->GetIconURL(getter_AddRefs(fileURI));
// Get icon for file specified by URI
if (fileURI) {
bool isFile;
nsAutoCString spec;
fileURI->GetAsciiSpec(spec);
if (NS_SUCCEEDED(fileURI->SchemeIs("file", &isFile)) && isFile) {
GFile* file = g_file_new_for_uri(spec.get());
GFileInfo* fileInfo = g_file_query_info(file,
G_FILE_ATTRIBUTE_STANDARD_ICON,
G_FILE_QUERY_INFO_NONE,
nullptr, nullptr);
g_object_unref(file);
if (fileInfo) {
// icon from g_content_type_get_icon doesn't need unref
icon = g_file_info_get_icon(fileInfo);
if (icon) {
g_object_ref(icon);
}
g_object_unref(fileInfo);
}
}
}
// Try to get icon by using MIME type
if (!icon) {
nsAutoCString type;
aIconURI->GetContentType(type);
// Try to get MIME type from file extension by using nsIMIMEService
if (type.IsEmpty()) {
nsCOMPtr<nsIMIMEService> ms(do_GetService("@mozilla.org/mime;1"));
if (ms) {
nsAutoCString fileExt;
aIconURI->GetFileExtension(fileExt);
ms->GetTypeFromExtension(fileExt, type);
}
}
char* ctype = nullptr; // character representation of content type
if (!type.IsEmpty()) {
ctype = g_content_type_from_mime_type(type.get());
}
if (ctype) {
icon = g_content_type_get_icon(ctype);
g_free(ctype);
}
}
// Get default icon theme
GtkIconTheme* iconTheme = gtk_icon_theme_get_default();
GtkIconInfo* iconInfo = nullptr;
// Get icon size
int32_t iconSize = GetIconSize(aIconURI);
if (icon) {
// Use icon and theme to get GtkIconInfo
iconInfo = gtk_icon_theme_lookup_by_gicon(iconTheme,
icon, iconSize,
(GtkIconLookupFlags)0);
g_object_unref(icon);
}
if (!iconInfo) {
// Mozilla's mimetype lookup failed. Try the "unknown" icon.
iconInfo = gtk_icon_theme_lookup_icon(iconTheme,
"unknown", iconSize,
(GtkIconLookupFlags)0);
if (!iconInfo) {
return NS_ERROR_NOT_AVAILABLE;
}
}
// Create a GdkPixbuf buffer containing icon and scale it
GdkPixbuf* buf = gtk_icon_info_load_icon(iconInfo, nullptr);
gtk_icon_info_free(iconInfo);
if (!buf) {
return NS_ERROR_UNEXPECTED;
}
nsresult rv = ScaleIconBuf(&buf, iconSize);
NS_ENSURE_SUCCESS(rv, rv);
rv = moz_gdk_pixbuf_to_channel(buf, aIconURI,
getter_AddRefs(mRealChannel));
g_object_unref(buf);
return rv;
}
#endif // MOZ_ENABLE_GIO
nsresult
nsIconChannel::Init(nsIURI* aURI)
{
nsCOMPtr<nsIMozIconURI> iconURI = do_QueryInterface(aURI);
NS_ASSERTION(iconURI, "URI is not an nsIMozIconURI");
nsAutoCString stockIcon;
iconURI->GetStockIcon(stockIcon);
if (stockIcon.IsEmpty()) {
#ifdef MOZ_ENABLE_GIO
return InitWithGIO(iconURI);
#else
return NS_ERROR_NOT_AVAILABLE;
#endif
}
// Search for stockIcon
nsAutoCString iconSizeString;
iconURI->GetIconSize(iconSizeString);
nsAutoCString iconStateString;
iconURI->GetIconState(iconStateString);
GtkIconSize icon_size = moz_gtk_icon_size(iconSizeString.get());
GtkStateType state = iconStateString.EqualsLiteral("disabled") ?
GTK_STATE_INSENSITIVE : GTK_STATE_NORMAL;
// First lookup the icon by stock id and text direction.
GtkTextDirection direction = GTK_TEXT_DIR_NONE;
if (StringEndsWith(stockIcon, NS_LITERAL_CSTRING("-ltr"))) {
direction = GTK_TEXT_DIR_LTR;
} else if (StringEndsWith(stockIcon, NS_LITERAL_CSTRING("-rtl"))) {
direction = GTK_TEXT_DIR_RTL;
}
bool forceDirection = direction != GTK_TEXT_DIR_NONE;
nsAutoCString stockID;
bool useIconName = false;
if (!forceDirection) {
direction = gtk_widget_get_default_direction();
stockID = stockIcon;
} else {
// GTK versions < 2.22 use icon names from concatenating stock id with
// -(rtl|ltr), which is how the moz-icon stock name is interpreted here.
stockID = Substring(stockIcon, 0, stockIcon.Length() - 4);
// However, if we lookup bidi icons by the stock name, then GTK versions
// >= 2.22 will use a bidi lookup convention that most icon themes do not
// yet follow. Therefore, we first check to see if the theme supports the
// old icon name as this will have bidi support (if found).
GtkIconTheme* icon_theme = gtk_icon_theme_get_default();
// Micking what gtk_icon_set_render_icon does with sizes, though it's not
// critical as icons will be scaled to suit size. It just means we follow
// the same pathes and so share caches.
gint width, height;
if (gtk_icon_size_lookup(icon_size, &width, &height)) {
gint size = std::min(width, height);
// We use gtk_icon_theme_lookup_icon() without
// GTK_ICON_LOOKUP_USE_BUILTIN instead of gtk_icon_theme_has_icon() so
// we don't pick up fallback icons added by distributions for backward
// compatibility.
GtkIconInfo* icon =
gtk_icon_theme_lookup_icon(icon_theme, stockIcon.get(),
size, (GtkIconLookupFlags)0);
if (icon) {
useIconName = true;
gtk_icon_info_free(icon);
}
}
}
ensure_stock_image_widget();
GtkStyle* style = gtk_widget_get_style(gStockImageWidget);
GtkIconSet* icon_set = nullptr;
if (!useIconName) {
icon_set = gtk_style_lookup_icon_set(style, stockID.get());
}
if (!icon_set) {
// Either we have choosen icon-name lookup for a bidi icon, or stockIcon is
// not a stock id so we assume it is an icon name.
useIconName = true;
// Creating a GtkIconSet is a convenient way to allow the style to
// render the icon, possibly with variations suitable for insensitive
// states.
icon_set = gtk_icon_set_new();
GtkIconSource* icon_source = gtk_icon_source_new();
gtk_icon_source_set_icon_name(icon_source, stockIcon.get());
gtk_icon_set_add_source(icon_set, icon_source);
gtk_icon_source_free(icon_source);
}
GdkPixbuf* icon =
gtk_icon_set_render_icon(icon_set, style, direction, state,
icon_size, gStockImageWidget, nullptr);
if (useIconName) {
gtk_icon_set_unref(icon_set);
}
// According to documentation, gtk_icon_set_render_icon() never returns
// nullptr, but it does return nullptr when we have the problem reported
// here: https://bugzilla.gnome.org/show_bug.cgi?id=629878#c13
if (!icon) {
return NS_ERROR_NOT_AVAILABLE;
}
nsresult rv = moz_gdk_pixbuf_to_channel(icon, iconURI,
getter_AddRefs(mRealChannel));
g_object_unref(icon);
return rv;
}
void
nsIconChannel::Shutdown() {
if (gProtoWindow) {
gtk_widget_destroy(gProtoWindow);
gProtoWindow = nullptr;
gStockImageWidget = nullptr;
}
}

View file

@ -0,0 +1,43 @@
/* 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/. */
#ifndef mozilla_image_decoders_icon_gtk_nsIconChannel_h
#define mozilla_image_decoders_icon_gtk_nsIconChannel_h
#include "mozilla/Attributes.h"
#include "nsIChannel.h"
#include "nsIStreamListener.h"
#include "nsIURI.h"
#include "nsIIconURI.h"
#include "nsCOMPtr.h"
/// This class is the gnome implementation of nsIconChannel. It basically asks
/// gtk/gnome for an icon, saves it as a tmp icon, and creates a new channel for
/// that file to which all calls will be proxied.
class nsIconChannel final : public nsIChannel
{
public:
NS_DECL_ISUPPORTS
NS_FORWARD_NSIREQUEST(mRealChannel->)
NS_FORWARD_NSICHANNEL(mRealChannel->)
nsIconChannel() { }
static void Shutdown();
/// Called by nsIconProtocolHandler after it creates this channel.
/// Must be called before calling any other function on this object.
/// If this method fails, no other function must be called on this object.
nsresult Init(nsIURI* aURI);
private:
~nsIconChannel() { }
/// The channel to the temp icon file (e.g. to /tmp/2qy9wjqw.html).
/// Will always be non-null after a successful Init.
nsCOMPtr<nsIChannel> mRealChannel;
nsresult InitWithGIO(nsIMozIconURI* aIconURI);
};
#endif // mozilla_image_decoders_icon_gtk_nsIconChannel_h

View file

@ -0,0 +1,11 @@
# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
# vim: set filetype=python:
# 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/.
SOURCES += [
'nsIconChannelCocoa.mm',
]
FINAL_LIBRARY = 'xul'

View file

@ -0,0 +1,59 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*-
*
* 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/. */
#ifndef mozilla_image_encoders_icon_mac_nsIconChannel_h
#define mozilla_image_encoders_icon_mac_nsIconChannel_h
#include "mozilla/Attributes.h"
#include "nsCOMPtr.h"
#include "nsXPIDLString.h"
#include "nsIChannel.h"
#include "nsILoadGroup.h"
#include "nsILoadInfo.h"
#include "nsIInterfaceRequestor.h"
#include "nsIInterfaceRequestorUtils.h"
#include "nsIInputStreamPump.h"
#include "nsIStreamListener.h"
#include "nsIURI.h"
class nsIFile;
class nsIconChannel final : public nsIChannel, public nsIStreamListener
{
public:
NS_DECL_THREADSAFE_ISUPPORTS
NS_DECL_NSIREQUEST
NS_DECL_NSICHANNEL
NS_DECL_NSIREQUESTOBSERVER
NS_DECL_NSISTREAMLISTENER
nsIconChannel();
nsresult Init(nsIURI* uri);
protected:
virtual ~nsIconChannel();
nsCOMPtr<nsIURI> mUrl;
nsCOMPtr<nsIURI> mOriginalURI;
nsCOMPtr<nsILoadGroup> mLoadGroup;
nsCOMPtr<nsIInterfaceRequestor> mCallbacks;
nsCOMPtr<nsISupports> mOwner;
nsCOMPtr<nsILoadInfo> mLoadInfo;
nsCOMPtr<nsIInputStreamPump> mPump;
nsCOMPtr<nsIStreamListener> mListener;
nsresult MakeInputStream(nsIInputStream** _retval, bool nonBlocking);
nsresult ExtractIconInfoFromUrl(nsIFile** aLocalFile,
uint32_t* aDesiredImageSize,
nsACString& aContentType,
nsACString& aFileExtension);
};
#endif // mozilla_image_encoders_icon_mac_nsIconChannel_h

View file

@ -0,0 +1,565 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*-
*
* 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/. */
#include "nsContentUtils.h"
#include "nsIconChannel.h"
#include "mozilla/EndianUtils.h"
#include "nsIIconURI.h"
#include "nsIServiceManager.h"
#include "nsIInterfaceRequestor.h"
#include "nsIInterfaceRequestorUtils.h"
#include "nsXPIDLString.h"
#include "nsMimeTypes.h"
#include "nsMemory.h"
#include "nsIStringStream.h"
#include "nsIURL.h"
#include "nsNetCID.h"
#include "nsIPipe.h"
#include "nsIOutputStream.h"
#include "nsIMIMEService.h"
#include "nsCExternalHandlerService.h"
#include "nsILocalFileMac.h"
#include "nsIFileURL.h"
#include "nsTArray.h"
#include "nsObjCExceptions.h"
#include "nsProxyRelease.h"
#include "nsContentSecurityManager.h"
#include <Cocoa/Cocoa.h>
// nsIconChannel methods
nsIconChannel::nsIconChannel()
{
}
nsIconChannel::~nsIconChannel()
{
if (mLoadInfo) {
NS_ReleaseOnMainThread(mLoadInfo.forget());
}
}
NS_IMPL_ISUPPORTS(nsIconChannel,
nsIChannel,
nsIRequest,
nsIRequestObserver,
nsIStreamListener)
nsresult
nsIconChannel::Init(nsIURI* uri)
{
NS_ASSERTION(uri, "no uri");
mUrl = uri;
mOriginalURI = uri;
nsresult rv;
mPump = do_CreateInstance(NS_INPUTSTREAMPUMP_CONTRACTID, &rv);
return rv;
}
////////////////////////////////////////////////////////////////////////////////
// nsIRequest methods:
NS_IMETHODIMP
nsIconChannel::GetName(nsACString& result)
{
return mUrl->GetSpec(result);
}
NS_IMETHODIMP
nsIconChannel::IsPending(bool* result)
{
return mPump->IsPending(result);
}
NS_IMETHODIMP
nsIconChannel::GetStatus(nsresult* status)
{
return mPump->GetStatus(status);
}
NS_IMETHODIMP
nsIconChannel::Cancel(nsresult status)
{
return mPump->Cancel(status);
}
NS_IMETHODIMP
nsIconChannel::Suspend(void)
{
return mPump->Suspend();
}
NS_IMETHODIMP
nsIconChannel::Resume(void)
{
return mPump->Resume();
}
// nsIRequestObserver methods
NS_IMETHODIMP
nsIconChannel::OnStartRequest(nsIRequest* aRequest,
nsISupports* aContext)
{
if (mListener) {
return mListener->OnStartRequest(this, aContext);
}
return NS_OK;
}
NS_IMETHODIMP
nsIconChannel::OnStopRequest(nsIRequest* aRequest,
nsISupports* aContext,
nsresult aStatus)
{
if (mListener) {
mListener->OnStopRequest(this, aContext, aStatus);
mListener = nullptr;
}
// Remove from load group
if (mLoadGroup) {
mLoadGroup->RemoveRequest(this, nullptr, aStatus);
}
return NS_OK;
}
// nsIStreamListener methods
NS_IMETHODIMP
nsIconChannel::OnDataAvailable(nsIRequest* aRequest,
nsISupports* aContext,
nsIInputStream* aStream,
uint64_t aOffset,
uint32_t aCount)
{
if (mListener) {
return mListener->OnDataAvailable(this, aContext, aStream, aOffset, aCount);
}
return NS_OK;
}
////////////////////////////////////////////////////////////////////////////////
// nsIChannel methods:
NS_IMETHODIMP
nsIconChannel::GetOriginalURI(nsIURI** aURI)
{
*aURI = mOriginalURI;
NS_ADDREF(*aURI);
return NS_OK;
}
NS_IMETHODIMP
nsIconChannel::SetOriginalURI(nsIURI* aURI)
{
NS_ENSURE_ARG_POINTER(aURI);
mOriginalURI = aURI;
return NS_OK;
}
NS_IMETHODIMP
nsIconChannel::GetURI(nsIURI** aURI)
{
*aURI = mUrl;
NS_IF_ADDREF(*aURI);
return NS_OK;
}
NS_IMETHODIMP
nsIconChannel::Open(nsIInputStream** _retval)
{
return MakeInputStream(_retval, false);
}
NS_IMETHODIMP
nsIconChannel::Open2(nsIInputStream** aStream)
{
nsCOMPtr<nsIStreamListener> listener;
nsresult rv = nsContentSecurityManager::doContentSecurityCheck(this, listener);
NS_ENSURE_SUCCESS(rv, rv);
return Open(aStream);
}
nsresult
nsIconChannel::ExtractIconInfoFromUrl(nsIFile** aLocalFile,
uint32_t* aDesiredImageSize,
nsACString& aContentType,
nsACString& aFileExtension)
{
nsresult rv = NS_OK;
nsCOMPtr<nsIMozIconURI> iconURI (do_QueryInterface(mUrl, &rv));
NS_ENSURE_SUCCESS(rv, rv);
iconURI->GetImageSize(aDesiredImageSize);
iconURI->GetContentType(aContentType);
iconURI->GetFileExtension(aFileExtension);
nsCOMPtr<nsIURL> url;
rv = iconURI->GetIconURL(getter_AddRefs(url));
if (NS_FAILED(rv) || !url) return NS_OK;
nsCOMPtr<nsIFileURL> fileURL = do_QueryInterface(url, &rv);
if (NS_FAILED(rv) || !fileURL) return NS_OK;
nsCOMPtr<nsIFile> file;
rv = fileURL->GetFile(getter_AddRefs(file));
if (NS_FAILED(rv) || !file) return NS_OK;
nsCOMPtr<nsILocalFileMac> localFileMac (do_QueryInterface(file, &rv));
if (NS_FAILED(rv) || !localFileMac) return NS_OK;
*aLocalFile = file;
NS_IF_ADDREF(*aLocalFile);
return NS_OK;
}
NS_IMETHODIMP
nsIconChannel::AsyncOpen(nsIStreamListener* aListener,
nsISupports* ctxt)
{
MOZ_ASSERT(!mLoadInfo ||
mLoadInfo->GetSecurityMode() == 0 ||
mLoadInfo->GetInitialSecurityCheckDone() ||
(mLoadInfo->GetSecurityMode() == nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL &&
nsContentUtils::IsSystemPrincipal(mLoadInfo->LoadingPrincipal())),
"security flags in loadInfo but asyncOpen2() not called");
nsCOMPtr<nsIInputStream> inStream;
nsresult rv = MakeInputStream(getter_AddRefs(inStream), true);
NS_ENSURE_SUCCESS(rv, rv);
// Init our stream pump
rv = mPump->Init(inStream, int64_t(-1), int64_t(-1), 0, 0, false);
NS_ENSURE_SUCCESS(rv, rv);
rv = mPump->AsyncRead(this, ctxt);
if (NS_SUCCEEDED(rv)) {
// Store our real listener
mListener = aListener;
// Add ourself to the load group, if available
if (mLoadGroup) {
mLoadGroup->AddRequest(this, nullptr);
}
}
return rv;
}
NS_IMETHODIMP
nsIconChannel::AsyncOpen2(nsIStreamListener* aListener)
{
nsCOMPtr<nsIStreamListener> listener = aListener;
nsresult rv = nsContentSecurityManager::doContentSecurityCheck(this, listener);
NS_ENSURE_SUCCESS(rv, rv);
return AsyncOpen(listener, nullptr);
}
nsresult
nsIconChannel::MakeInputStream(nsIInputStream** _retval,
bool aNonBlocking)
{
NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
nsXPIDLCString contentType;
nsAutoCString fileExt;
nsCOMPtr<nsIFile> fileloc; // file we want an icon for
uint32_t desiredImageSize;
nsresult rv = ExtractIconInfoFromUrl(getter_AddRefs(fileloc),
&desiredImageSize, contentType, fileExt);
NS_ENSURE_SUCCESS(rv, rv);
bool fileExists = false;
if (fileloc) {
// ensure that we DO NOT resolve aliases, very important for file views
fileloc->SetFollowLinks(false);
fileloc->Exists(&fileExists);
}
NSImage* iconImage = nil;
// first try to get the icon from the file if it exists
if (fileExists) {
nsCOMPtr<nsILocalFileMac> localFileMac(do_QueryInterface(fileloc, &rv));
NS_ENSURE_SUCCESS(rv, rv);
CFURLRef macURL;
if (NS_SUCCEEDED(localFileMac->GetCFURL(&macURL))) {
iconImage = [[NSWorkspace sharedWorkspace]
iconForFile:[(NSURL*)macURL path]];
::CFRelease(macURL);
}
}
// if we don't have an icon yet try to get one by extension
if (!iconImage && !fileExt.IsEmpty()) {
NSString* fileExtension = [NSString stringWithUTF8String:fileExt.get()];
iconImage = [[NSWorkspace sharedWorkspace] iconForFileType:fileExtension];
}
// If we still don't have an icon, get the generic document icon.
if (!iconImage) {
iconImage = [[NSWorkspace sharedWorkspace]
iconForFileType:NSFileTypeUnknown];
}
if (!iconImage) {
return NS_ERROR_FAILURE;
}
// we have an icon now, size it
NSRect desiredSizeRect = NSMakeRect(0, 0, desiredImageSize, desiredImageSize);
[iconImage setSize:desiredSizeRect.size];
[iconImage lockFocus];
NSBitmapImageRep* bitmapRep = [[[NSBitmapImageRep alloc]
initWithFocusedViewRect:desiredSizeRect]
autorelease];
[iconImage unlockFocus];
// we expect the following things to be true about our bitmapRep
NS_ENSURE_TRUE(![bitmapRep isPlanar] &&
// Not necessarily: on a HiDPI-capable system, we'll get
// a 2x bitmap
// (unsigned int)[bitmapRep bytesPerPlane] ==
// desiredImageSize * desiredImageSize * 4 &&
[bitmapRep bitsPerPixel] == 32 &&
[bitmapRep samplesPerPixel] == 4 &&
[bitmapRep hasAlpha] == YES,
NS_ERROR_UNEXPECTED);
// check what size we actually got, and ensure it isn't too big to return
uint32_t actualImageSize = [bitmapRep bytesPerRow] / 4;
NS_ENSURE_TRUE(actualImageSize < 256, NS_ERROR_UNEXPECTED);
// now we can validate the amount of data
NS_ENSURE_TRUE((unsigned int)[bitmapRep bytesPerPlane] ==
actualImageSize * actualImageSize * 4,
NS_ERROR_UNEXPECTED);
// rgba, pre-multiplied data
uint8_t* bitmapRepData = (uint8_t*)[bitmapRep bitmapData];
// create our buffer
int32_t bufferCapacity = 2 + [bitmapRep bytesPerPlane];
AutoTArray<uint8_t, 3 + 16 * 16 * 5> iconBuffer; // initial size is for
// 16x16
iconBuffer.SetLength(bufferCapacity);
uint8_t* iconBufferPtr = iconBuffer.Elements();
// write header data into buffer
*iconBufferPtr++ = actualImageSize;
*iconBufferPtr++ = actualImageSize;
uint32_t dataCount = [bitmapRep bytesPerPlane];
uint32_t index = 0;
while (index < dataCount) {
// get data from the bitmap
uint8_t r = bitmapRepData[index++];
uint8_t g = bitmapRepData[index++];
uint8_t b = bitmapRepData[index++];
uint8_t a = bitmapRepData[index++];
// write data out to our buffer
// non-cairo uses native image format, but the A channel is ignored.
// cairo uses ARGB (highest to lowest bits)
#if MOZ_LITTLE_ENDIAN
*iconBufferPtr++ = b;
*iconBufferPtr++ = g;
*iconBufferPtr++ = r;
*iconBufferPtr++ = a;
#else
*iconBufferPtr++ = a;
*iconBufferPtr++ = r;
*iconBufferPtr++ = g;
*iconBufferPtr++ = b;
#endif
}
NS_ASSERTION(iconBufferPtr == iconBuffer.Elements() + bufferCapacity,
"buffer size miscalculation");
// Now, create a pipe and stuff our data into it
nsCOMPtr<nsIInputStream> inStream;
nsCOMPtr<nsIOutputStream> outStream;
rv = NS_NewPipe(getter_AddRefs(inStream), getter_AddRefs(outStream),
bufferCapacity, bufferCapacity, aNonBlocking);
if (NS_SUCCEEDED(rv)) {
uint32_t written;
rv = outStream->Write((char*)iconBuffer.Elements(), bufferCapacity,
&written);
if (NS_SUCCEEDED(rv)) {
NS_IF_ADDREF(*_retval = inStream);
}
}
// Drop notification callbacks to prevent cycles.
mCallbacks = nullptr;
return NS_OK;
NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
}
NS_IMETHODIMP
nsIconChannel::GetLoadFlags(uint32_t* aLoadAttributes)
{
return mPump->GetLoadFlags(aLoadAttributes);
}
NS_IMETHODIMP
nsIconChannel::SetLoadFlags(uint32_t aLoadAttributes)
{
return mPump->SetLoadFlags(aLoadAttributes);
}
NS_IMETHODIMP
nsIconChannel::GetContentType(nsACString& aContentType)
{
aContentType.AssignLiteral(IMAGE_ICON_MS);
return NS_OK;
}
NS_IMETHODIMP
nsIconChannel::SetContentType(const nsACString& aContentType)
{
//It doesn't make sense to set the content-type on this type
// of channel...
return NS_ERROR_FAILURE;
}
NS_IMETHODIMP
nsIconChannel::GetContentCharset(nsACString& aContentCharset)
{
aContentCharset.AssignLiteral(IMAGE_ICON_MS);
return NS_OK;
}
NS_IMETHODIMP
nsIconChannel::SetContentCharset(const nsACString& aContentCharset)
{
//It doesn't make sense to set the content-type on this type
// of channel...
return NS_ERROR_FAILURE;
}
NS_IMETHODIMP
nsIconChannel::GetContentDisposition(uint32_t* aContentDisposition)
{
return NS_ERROR_NOT_AVAILABLE;
}
NS_IMETHODIMP
nsIconChannel::SetContentDisposition(uint32_t aContentDisposition)
{
return NS_ERROR_NOT_AVAILABLE;
}
NS_IMETHODIMP
nsIconChannel::
GetContentDispositionFilename(nsAString& aContentDispositionFilename)
{
return NS_ERROR_NOT_AVAILABLE;
}
NS_IMETHODIMP
nsIconChannel::
SetContentDispositionFilename(const nsAString& aContentDispositionFilename)
{
return NS_ERROR_NOT_AVAILABLE;
}
NS_IMETHODIMP
nsIconChannel::
GetContentDispositionHeader(nsACString& aContentDispositionHeader)
{
return NS_ERROR_NOT_AVAILABLE;
}
NS_IMETHODIMP
nsIconChannel::GetContentLength(int64_t* aContentLength)
{
*aContentLength = 0;
return NS_ERROR_FAILURE;
}
NS_IMETHODIMP
nsIconChannel::SetContentLength(int64_t aContentLength)
{
NS_NOTREACHED("nsIconChannel::SetContentLength");
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP
nsIconChannel::GetLoadGroup(nsILoadGroup** aLoadGroup)
{
*aLoadGroup = mLoadGroup;
NS_IF_ADDREF(*aLoadGroup);
return NS_OK;
}
NS_IMETHODIMP
nsIconChannel::SetLoadGroup(nsILoadGroup* aLoadGroup)
{
mLoadGroup = aLoadGroup;
return NS_OK;
}
NS_IMETHODIMP
nsIconChannel::GetOwner(nsISupports** aOwner)
{
*aOwner = mOwner.get();
NS_IF_ADDREF(*aOwner);
return NS_OK;
}
NS_IMETHODIMP
nsIconChannel::SetOwner(nsISupports* aOwner)
{
mOwner = aOwner;
return NS_OK;
}
NS_IMETHODIMP
nsIconChannel::GetLoadInfo(nsILoadInfo** aLoadInfo)
{
NS_IF_ADDREF(*aLoadInfo = mLoadInfo);
return NS_OK;
}
NS_IMETHODIMP
nsIconChannel::SetLoadInfo(nsILoadInfo* aLoadInfo)
{
mLoadInfo = aLoadInfo;
return NS_OK;
}
NS_IMETHODIMP
nsIconChannel::
GetNotificationCallbacks(nsIInterfaceRequestor** aNotificationCallbacks)
{
*aNotificationCallbacks = mCallbacks.get();
NS_IF_ADDREF(*aNotificationCallbacks);
return NS_OK;
}
NS_IMETHODIMP
nsIconChannel::
SetNotificationCallbacks(nsIInterfaceRequestor* aNotificationCallbacks)
{
mCallbacks = aNotificationCallbacks;
return NS_OK;
}
NS_IMETHODIMP
nsIconChannel::GetSecurityInfo(nsISupports** aSecurityInfo)
{
*aSecurityInfo = nullptr;
return NS_OK;
}

View file

@ -0,0 +1,32 @@
# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
# vim: set filetype=python:
# 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/.
UNIFIED_SOURCES += [
'nsIconModule.cpp',
'nsIconProtocolHandler.cpp',
'nsIconURI.cpp',
]
FINAL_LIBRARY = 'xul'
include('/ipc/chromium/chromium-config.mozbuild')
platform = None
if 'gtk' in CONFIG['MOZ_WIDGET_TOOLKIT']:
platform = 'gtk'
if CONFIG['OS_ARCH'] == 'WINNT':
platform = 'win'
if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa':
platform = 'mac'
if CONFIG['OS_TARGET'] == 'Android':
platform = 'android'
if platform:
LOCAL_INCLUDES += [platform]

View file

@ -0,0 +1,60 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
*
* 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/. */
#include "mozilla/ModuleUtils.h"
#include "nsServiceManagerUtils.h"
#include "nsIconProtocolHandler.h"
#include "nsIconURI.h"
#include "nsIconChannel.h"
// objects that just require generic constructors
//*****************************************************************************
// Protocol CIDs
#define NS_ICONPROTOCOL_CID { 0xd0f9db12, 0x249c, 0x11d5, \
{ 0x99, 0x5, 0x0, 0x10, 0x83, 0x1, 0xe, 0x9b } }
NS_GENERIC_FACTORY_CONSTRUCTOR(nsIconProtocolHandler)
NS_GENERIC_FACTORY_CONSTRUCTOR(nsMozIconURI)
NS_DEFINE_NAMED_CID(NS_ICONPROTOCOL_CID);
NS_DEFINE_NAMED_CID(NS_MOZICONURI_CID);
static const mozilla::Module::CIDEntry kIconCIDs[] = {
{ &kNS_ICONPROTOCOL_CID, false, nullptr, nsIconProtocolHandlerConstructor },
{ &kNS_MOZICONURI_CID, false, nullptr, nsMozIconURIConstructor },
{ nullptr }
};
static const mozilla::Module::ContractIDEntry kIconContracts[] = {
{ NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX "moz-icon", &kNS_ICONPROTOCOL_CID },
{ nullptr }
};
static const mozilla::Module::CategoryEntry kIconCategories[] = {
{ nullptr }
};
static void
IconDecoderModuleDtor()
{
#if (MOZ_WIDGET_GTK == 2)
nsIconChannel::Shutdown();
#endif
}
static const mozilla::Module kIconModule = {
mozilla::Module::kVersion,
kIconCIDs,
kIconContracts,
kIconCategories,
nullptr,
nullptr,
IconDecoderModuleDtor
};
NSMODULE_DEFN(nsIconDecoderModule) = &kIconModule;

View file

@ -0,0 +1,126 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*-
*
* 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/. */
#include "nsIconProtocolHandler.h"
#include "nsIconChannel.h"
#include "nsIconURI.h"
#include "nsIURL.h"
#include "nsCRT.h"
#include "nsCOMPtr.h"
#include "nsIComponentManager.h"
#include "nsIServiceManager.h"
#include "nsNetCID.h"
///////////////////////////////////////////////////////////////////////////////
nsIconProtocolHandler::nsIconProtocolHandler()
{ }
nsIconProtocolHandler::~nsIconProtocolHandler()
{ }
NS_IMPL_ISUPPORTS(nsIconProtocolHandler, nsIProtocolHandler,
nsISupportsWeakReference)
///////////////////////////////////////////////////////////////////////////////
// nsIProtocolHandler methods:
NS_IMETHODIMP
nsIconProtocolHandler::GetScheme(nsACString& result)
{
result = "moz-icon";
return NS_OK;
}
NS_IMETHODIMP
nsIconProtocolHandler::GetDefaultPort(int32_t* result)
{
*result = 0;
return NS_OK;
}
NS_IMETHODIMP
nsIconProtocolHandler::AllowPort(int32_t port,
const char* scheme,
bool* _retval)
{
// don't override anything.
*_retval = false;
return NS_OK;
}
NS_IMETHODIMP
nsIconProtocolHandler::GetProtocolFlags(uint32_t* result)
{
*result = URI_NORELATIVE | URI_NOAUTH | URI_IS_UI_RESOURCE |
URI_IS_LOCAL_RESOURCE;
return NS_OK;
}
NS_IMETHODIMP
nsIconProtocolHandler::NewURI(const nsACString& aSpec,
const char* aOriginCharset, // ignored
nsIURI* aBaseURI,
nsIURI** result)
{
nsCOMPtr<nsIMozIconURI> uri = new nsMozIconURI();
if (!uri) return NS_ERROR_OUT_OF_MEMORY;
nsresult rv = uri->SetSpec(aSpec);
if (NS_FAILED(rv)) return rv;
nsCOMPtr<nsIURL> iconURL;
uri->GetIconURL(getter_AddRefs(iconURL));
if (iconURL) {
uri = new nsNestedMozIconURI();
rv = uri->SetSpec(aSpec);
if (NS_FAILED(rv)) {
return rv;
}
}
NS_ADDREF(*result = uri);
return NS_OK;
}
NS_IMETHODIMP
nsIconProtocolHandler::NewChannel2(nsIURI* url,
nsILoadInfo* aLoadInfo,
nsIChannel** result)
{
NS_ENSURE_ARG_POINTER(url);
nsIconChannel* channel = new nsIconChannel;
if (!channel) {
return NS_ERROR_OUT_OF_MEMORY;
}
NS_ADDREF(channel);
nsresult rv = channel->Init(url);
if (NS_FAILED(rv)) {
NS_RELEASE(channel);
return rv;
}
// set the loadInfo on the new channel
rv = channel->SetLoadInfo(aLoadInfo);
if (NS_FAILED(rv)) {
NS_RELEASE(channel);
return rv;
}
*result = channel;
return NS_OK;
}
NS_IMETHODIMP
nsIconProtocolHandler::NewChannel(nsIURI* url, nsIChannel** result)
{
return NewChannel2(url, nullptr, result);
}
////////////////////////////////////////////////////////////////////////////////

View file

@ -0,0 +1,26 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/* 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/. */
#ifndef mozilla_image_decoders_icon_nsIconProtocolHandler_h
#define mozilla_image_decoders_icon_nsIconProtocolHandler_h
#include "nsWeakReference.h"
#include "nsIProtocolHandler.h"
class nsIconProtocolHandler : public nsIProtocolHandler,
public nsSupportsWeakReference
{
public:
NS_DECL_ISUPPORTS
NS_DECL_NSIPROTOCOLHANDLER
// nsIconProtocolHandler methods:
nsIconProtocolHandler();
protected:
virtual ~nsIconProtocolHandler();
};
#endif // mozilla_image_decoders_icon_nsIconProtocolHandler_h

View file

@ -0,0 +1,699 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
* vim: set sw=2 sts=2 ts=2 et 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/. */
#include "nsIconURI.h"
#include "mozilla/ArrayUtils.h"
#include "mozilla/ipc/URIUtils.h"
#include "mozilla/Sprintf.h"
#include "nsIIOService.h"
#include "nsIURL.h"
#include "nsNetUtil.h"
#include "plstr.h"
#include <stdlib.h>
using namespace mozilla;
using namespace mozilla::ipc;
#define DEFAULT_IMAGE_SIZE 16
#if defined(MAX_PATH)
#define SANE_FILE_NAME_LEN MAX_PATH
#elif defined(PATH_MAX)
#define SANE_FILE_NAME_LEN PATH_MAX
#else
#define SANE_FILE_NAME_LEN 1024
#endif
// helper function for parsing out attributes like size, and contentType
// from the icon url.
static void extractAttributeValue(const char* aSearchString,
const char* aAttributeName,
nsCString& aResult);
static const char* kSizeStrings[] =
{
"button",
"toolbar",
"toolbarsmall",
"menu",
"dnd",
"dialog"
};
static const char* kStateStrings[] =
{
"normal",
"disabled"
};
////////////////////////////////////////////////////////////////////////////////
nsMozIconURI::nsMozIconURI()
: mSize(DEFAULT_IMAGE_SIZE),
mIconSize(-1),
mIconState(-1)
{ }
nsMozIconURI::~nsMozIconURI()
{ }
NS_IMPL_ISUPPORTS(nsMozIconURI, nsIMozIconURI, nsIURI, nsIIPCSerializableURI)
#define MOZICON_SCHEME "moz-icon:"
#define MOZICON_SCHEME_LEN (sizeof(MOZICON_SCHEME) - 1)
////////////////////////////////////////////////////////////////////////////////
// nsIURI methods:
NS_IMETHODIMP
nsMozIconURI::GetSpec(nsACString& aSpec)
{
aSpec = MOZICON_SCHEME;
if (mIconURL) {
nsAutoCString fileIconSpec;
nsresult rv = mIconURL->GetSpec(fileIconSpec);
NS_ENSURE_SUCCESS(rv, rv);
aSpec += fileIconSpec;
} else if (!mStockIcon.IsEmpty()) {
aSpec += "//stock/";
aSpec += mStockIcon;
} else {
aSpec += "//";
aSpec += mFileName;
}
aSpec += "?size=";
if (mIconSize >= 0) {
aSpec += kSizeStrings[mIconSize];
} else {
char buf[20];
SprintfLiteral(buf, "%d", mSize);
aSpec.Append(buf);
}
if (mIconState >= 0) {
aSpec += "&state=";
aSpec += kStateStrings[mIconState];
}
if (!mContentType.IsEmpty()) {
aSpec += "&contentType=";
aSpec += mContentType.get();
}
return NS_OK;
}
NS_IMETHODIMP
nsMozIconURI::GetSpecIgnoringRef(nsACString& result)
{
return GetSpec(result);
}
NS_IMETHODIMP
nsMozIconURI::GetHasRef(bool* result)
{
*result = false;
return NS_OK;
}
// takes a string like ?size=32&contentType=text/html and returns a new string
// containing just the attribute value. i.e you could pass in this string with
// an attribute name of 'size=', this will return 32
// Assumption: attribute pairs in the string are separated by '&'.
void
extractAttributeValue(const char* aSearchString,
const char* aAttributeName,
nsCString& aResult)
{
//NS_ENSURE_ARG_POINTER(extractAttributeValue);
aResult.Truncate();
if (aSearchString && aAttributeName) {
// search the string for attributeName
uint32_t attributeNameSize = strlen(aAttributeName);
const char* startOfAttribute = PL_strcasestr(aSearchString, aAttributeName);
if (startOfAttribute &&
( *(startOfAttribute-1) == '?' || *(startOfAttribute-1) == '&') ) {
startOfAttribute += attributeNameSize; // skip over the attributeName
// is there something after the attribute name
if (*startOfAttribute) {
const char* endofAttribute = strchr(startOfAttribute, '&');
if (endofAttribute) {
aResult.Assign(Substring(startOfAttribute, endofAttribute));
} else {
aResult.Assign(startOfAttribute);
}
} // if we have a attribute value
} // if we have a attribute name
} // if we got non-null search string and attribute name values
}
NS_IMETHODIMP
nsMozIconURI::SetSpec(const nsACString& aSpec)
{
// Reset everything to default values.
mIconURL = nullptr;
mSize = DEFAULT_IMAGE_SIZE;
mContentType.Truncate();
mFileName.Truncate();
mStockIcon.Truncate();
mIconSize = -1;
mIconState = -1;
nsAutoCString iconSpec(aSpec);
if (!Substring(iconSpec, 0,
MOZICON_SCHEME_LEN).EqualsLiteral(MOZICON_SCHEME)) {
return NS_ERROR_MALFORMED_URI;
}
int32_t questionMarkPos = iconSpec.Find("?");
if (questionMarkPos != -1 &&
static_cast<int32_t>(iconSpec.Length()) > (questionMarkPos + 1)) {
extractAttributeValue(iconSpec.get(), "contentType=", mContentType);
nsAutoCString sizeString;
extractAttributeValue(iconSpec.get(), "size=", sizeString);
if (!sizeString.IsEmpty()) {
const char* sizeStr = sizeString.get();
for (uint32_t i = 0; i < ArrayLength(kSizeStrings); i++) {
if (PL_strcasecmp(sizeStr, kSizeStrings[i]) == 0) {
mIconSize = i;
break;
}
}
int32_t sizeValue = atoi(sizeString.get());
if (sizeValue > 0) {
mSize = sizeValue;
}
}
nsAutoCString stateString;
extractAttributeValue(iconSpec.get(), "state=", stateString);
if (!stateString.IsEmpty()) {
const char* stateStr = stateString.get();
for (uint32_t i = 0; i < ArrayLength(kStateStrings); i++) {
if (PL_strcasecmp(stateStr, kStateStrings[i]) == 0) {
mIconState = i;
break;
}
}
}
}
int32_t pathLength = iconSpec.Length() - MOZICON_SCHEME_LEN;
if (questionMarkPos != -1) {
pathLength = questionMarkPos - MOZICON_SCHEME_LEN;
}
if (pathLength < 3) {
return NS_ERROR_MALFORMED_URI;
}
nsAutoCString iconPath(Substring(iconSpec, MOZICON_SCHEME_LEN, pathLength));
// Icon URI path can have three forms:
// (1) //stock/<icon-identifier>
// (2) //<some dummy file with an extension>
// (3) a valid URL
if (!strncmp("//stock/", iconPath.get(), 8)) {
mStockIcon.Assign(Substring(iconPath, 8));
// An icon identifier must always be specified.
if (mStockIcon.IsEmpty()) {
return NS_ERROR_MALFORMED_URI;
}
return NS_OK;
}
if (StringBeginsWith(iconPath, NS_LITERAL_CSTRING("//"))) {
// Sanity check this supposed dummy file name.
if (iconPath.Length() > SANE_FILE_NAME_LEN) {
return NS_ERROR_MALFORMED_URI;
}
iconPath.Cut(0, 2);
mFileName.Assign(iconPath);
}
nsresult rv;
nsCOMPtr<nsIIOService> ioService(do_GetService(NS_IOSERVICE_CONTRACTID, &rv));
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsIURI> uri;
ioService->NewURI(iconPath, nullptr, nullptr, getter_AddRefs(uri));
mIconURL = do_QueryInterface(uri);
if (mIconURL) {
mFileName.Truncate();
} else if (mFileName.IsEmpty()) {
return NS_ERROR_MALFORMED_URI;
}
return NS_OK;
}
NS_IMETHODIMP
nsMozIconURI::GetPrePath(nsACString& prePath)
{
prePath = MOZICON_SCHEME;
return NS_OK;
}
NS_IMETHODIMP
nsMozIconURI::GetScheme(nsACString& aScheme)
{
aScheme = "moz-icon";
return NS_OK;
}
NS_IMETHODIMP
nsMozIconURI::SetScheme(const nsACString& aScheme)
{
// doesn't make sense to set the scheme of a moz-icon URL
return NS_ERROR_FAILURE;
}
NS_IMETHODIMP
nsMozIconURI::GetUsername(nsACString& aUsername)
{
return NS_ERROR_FAILURE;
}
NS_IMETHODIMP
nsMozIconURI::SetUsername(const nsACString& aUsername)
{
return NS_ERROR_FAILURE;
}
NS_IMETHODIMP
nsMozIconURI::GetPassword(nsACString& aPassword)
{
return NS_ERROR_FAILURE;
}
NS_IMETHODIMP
nsMozIconURI::SetPassword(const nsACString& aPassword)
{
return NS_ERROR_FAILURE;
}
NS_IMETHODIMP
nsMozIconURI::GetUserPass(nsACString& aUserPass)
{
return NS_ERROR_FAILURE;
}
NS_IMETHODIMP
nsMozIconURI::SetUserPass(const nsACString& aUserPass)
{
return NS_ERROR_FAILURE;
}
NS_IMETHODIMP
nsMozIconURI::GetHostPort(nsACString& aHostPort)
{
return NS_ERROR_FAILURE;
}
NS_IMETHODIMP
nsMozIconURI::SetHostPort(const nsACString& aHostPort)
{
return NS_ERROR_FAILURE;
}
NS_IMETHODIMP
nsMozIconURI::SetHostAndPort(const nsACString& aHostPort)
{
return NS_ERROR_FAILURE;
}
NS_IMETHODIMP
nsMozIconURI::GetHost(nsACString& aHost)
{
return NS_ERROR_FAILURE;
}
NS_IMETHODIMP
nsMozIconURI::SetHost(const nsACString& aHost)
{
return NS_ERROR_FAILURE;
}
NS_IMETHODIMP
nsMozIconURI::GetPort(int32_t* aPort)
{
return NS_ERROR_FAILURE;
}
NS_IMETHODIMP
nsMozIconURI::SetPort(int32_t aPort)
{
return NS_ERROR_FAILURE;
}
NS_IMETHODIMP
nsMozIconURI::GetPath(nsACString& aPath)
{
aPath.Truncate();
return NS_OK;
}
NS_IMETHODIMP
nsMozIconURI::SetPath(const nsACString& aPath)
{
return NS_ERROR_FAILURE;
}
NS_IMETHODIMP
nsMozIconURI::GetRef(nsACString& aRef)
{
aRef.Truncate();
return NS_OK;
}
NS_IMETHODIMP
nsMozIconURI::SetRef(const nsACString& aRef)
{
return NS_ERROR_FAILURE;
}
NS_IMETHODIMP
nsMozIconURI::Equals(nsIURI* other, bool* result)
{
*result = false;
NS_ENSURE_ARG_POINTER(other);
NS_PRECONDITION(result, "null pointer");
nsAutoCString spec1;
nsAutoCString spec2;
nsresult rv = GetSpec(spec1);
NS_ENSURE_SUCCESS(rv, rv);
rv = other->GetSpec(spec2);
NS_ENSURE_SUCCESS(rv, rv);
if (!PL_strcasecmp(spec1.get(), spec2.get())) {
*result = true;
} else {
*result = false;
}
return NS_OK;
}
NS_IMETHODIMP
nsMozIconURI::EqualsExceptRef(nsIURI* other, bool* result)
{
// GetRef/SetRef not supported by nsMozIconURI, so
// EqualsExceptRef() is the same as Equals().
return Equals(other, result);
}
NS_IMETHODIMP
nsMozIconURI::SchemeIs(const char* aScheme, bool* aEquals)
{
NS_ENSURE_ARG_POINTER(aEquals);
if (!aScheme) {
return NS_ERROR_INVALID_ARG;
}
*aEquals = PL_strcasecmp("moz-icon", aScheme) ? false : true;
return NS_OK;
}
NS_IMETHODIMP
nsMozIconURI::Clone(nsIURI** result)
{
nsCOMPtr<nsIURL> newIconURL;
if (mIconURL) {
nsCOMPtr<nsIURI> newURI;
nsresult rv = mIconURL->Clone(getter_AddRefs(newURI));
if (NS_FAILED(rv)) {
return rv;
}
newIconURL = do_QueryInterface(newURI, &rv);
if (NS_FAILED(rv)) {
return rv;
}
}
nsMozIconURI* uri = new nsMozIconURI();
newIconURL.swap(uri->mIconURL);
uri->mSize = mSize;
uri->mContentType = mContentType;
uri->mFileName = mFileName;
uri->mStockIcon = mStockIcon;
uri->mIconSize = mIconSize;
uri->mIconState = mIconState;
NS_ADDREF(*result = uri);
return NS_OK;
}
NS_IMETHODIMP
nsMozIconURI::CloneIgnoringRef(nsIURI** result)
{
// GetRef/SetRef not supported by nsMozIconURI, so
// CloneIgnoringRef() is the same as Clone().
return Clone(result);
}
NS_IMETHODIMP
nsMozIconURI::CloneWithNewRef(const nsACString& newRef, nsIURI** result)
{
// GetRef/SetRef not supported by nsMozIconURI, so
// CloneWithNewRef() is the same as Clone().
return Clone(result);
}
NS_IMETHODIMP
nsMozIconURI::Resolve(const nsACString& relativePath, nsACString& result)
{
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP
nsMozIconURI::GetAsciiSpec(nsACString& aSpecA)
{
return GetSpec(aSpecA);
}
NS_IMETHODIMP
nsMozIconURI::GetAsciiHostPort(nsACString& aHostPortA)
{
return GetHostPort(aHostPortA);
}
NS_IMETHODIMP
nsMozIconURI::GetAsciiHost(nsACString& aHostA)
{
return GetHost(aHostA);
}
NS_IMETHODIMP
nsMozIconURI::GetOriginCharset(nsACString& result)
{
result.Truncate();
return NS_OK;
}
////////////////////////////////////////////////////////////////////////////////
// nsIIconUri methods:
NS_IMETHODIMP
nsMozIconURI::GetIconURL(nsIURL** aFileUrl)
{
*aFileUrl = mIconURL;
NS_IF_ADDREF(*aFileUrl);
return NS_OK;
}
NS_IMETHODIMP
nsMozIconURI::SetIconURL(nsIURL* aFileUrl)
{
// this isn't called anywhere, needs to go through SetSpec parsing
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP
nsMozIconURI::GetImageSize(uint32_t* aImageSize)
// measured by # of pixels in a row. defaults to 16.
{
*aImageSize = mSize;
return NS_OK;
}
NS_IMETHODIMP
nsMozIconURI::SetImageSize(uint32_t aImageSize)
// measured by # of pixels in a row. defaults to 16.
{
mSize = aImageSize;
return NS_OK;
}
NS_IMETHODIMP
nsMozIconURI::GetContentType(nsACString& aContentType)
{
aContentType = mContentType;
return NS_OK;
}
NS_IMETHODIMP
nsMozIconURI::SetContentType(const nsACString& aContentType)
{
mContentType = aContentType;
return NS_OK;
}
NS_IMETHODIMP
nsMozIconURI::GetFileExtension(nsACString& aFileExtension)
{
// First, try to get the extension from mIconURL if we have one
if (mIconURL) {
nsAutoCString fileExt;
if (NS_SUCCEEDED(mIconURL->GetFileExtension(fileExt))) {
if (!fileExt.IsEmpty()) {
// unfortunately, this code doesn't give us the required '.' in
// front of the extension so we have to do it ourselves.
aFileExtension.Assign('.');
aFileExtension.Append(fileExt);
}
}
return NS_OK;
}
if (!mFileName.IsEmpty()) {
// truncate the extension out of the file path...
const char* chFileName = mFileName.get(); // get the underlying buffer
const char* fileExt = strrchr(chFileName, '.');
if (!fileExt) {
return NS_OK;
}
aFileExtension = fileExt;
}
return NS_OK;
}
NS_IMETHODIMP
nsMozIconURI::GetStockIcon(nsACString& aStockIcon)
{
aStockIcon = mStockIcon;
return NS_OK;
}
NS_IMETHODIMP
nsMozIconURI::GetIconSize(nsACString& aSize)
{
if (mIconSize >= 0) {
aSize = kSizeStrings[mIconSize];
} else {
aSize.Truncate();
}
return NS_OK;
}
NS_IMETHODIMP
nsMozIconURI::GetIconState(nsACString& aState)
{
if (mIconState >= 0) {
aState = kStateStrings[mIconState];
} else {
aState.Truncate();
}
return NS_OK;
}
////////////////////////////////////////////////////////////////////////////////
// nsIIPCSerializableURI methods:
void
nsMozIconURI::Serialize(URIParams& aParams)
{
IconURIParams params;
if (mIconURL) {
URIParams iconURLParams;
SerializeURI(mIconURL, iconURLParams);
if (iconURLParams.type() == URIParams::T__None) {
// Serialization failed, bail.
return;
}
params.uri() = iconURLParams;
} else {
params.uri() = void_t();
}
params.size() = mSize;
params.fileName() = mFileName;
params.stockIcon() = mStockIcon;
params.iconSize() = mIconSize;
params.iconState() = mIconState;
aParams = params;
}
bool
nsMozIconURI::Deserialize(const URIParams& aParams)
{
if (aParams.type() != URIParams::TIconURIParams) {
MOZ_ASSERT_UNREACHABLE("Received unknown URI from other process!");
return false;
}
const IconURIParams& params = aParams.get_IconURIParams();
if (params.uri().type() != OptionalURIParams::Tvoid_t) {
nsCOMPtr<nsIURI> uri = DeserializeURI(params.uri().get_URIParams());
mIconURL = do_QueryInterface(uri);
if (!mIconURL) {
MOZ_ASSERT_UNREACHABLE("bad nsIURI passed");
return false;
}
}
mSize = params.size();
mContentType = params.contentType();
mFileName = params.fileName();
mStockIcon = params.stockIcon();
mIconSize = params.iconSize();
mIconState = params.iconState();
return true;
}
////////////////////////////////////////////////////////////
// Nested version of nsIconURI
nsNestedMozIconURI::nsNestedMozIconURI()
{ }
nsNestedMozIconURI::~nsNestedMozIconURI()
{ }
NS_IMPL_ISUPPORTS_INHERITED(nsNestedMozIconURI, nsMozIconURI, nsINestedURI)
NS_IMETHODIMP
nsNestedMozIconURI::GetInnerURI(nsIURI** aURI)
{
nsCOMPtr<nsIURI> iconURL = do_QueryInterface(mIconURL);
if (iconURL) {
iconURL.forget(aURI);
} else {
*aURI = nullptr;
}
return NS_OK;
}
NS_IMETHODIMP
nsNestedMozIconURI::GetInnermostURI(nsIURI** aURI)
{
return NS_ImplGetInnermostURI(this, aURI);
}

View file

@ -0,0 +1,63 @@
/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
*
* 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/. */
#ifndef mozilla_image_decoders_icon_nsIconURI_h
#define mozilla_image_decoders_icon_nsIconURI_h
#include "nsIIconURI.h"
#include "nsCOMPtr.h"
#include "nsString.h"
#include "nsIIPCSerializableURI.h"
#include "nsINestedURI.h"
class nsMozIconURI : public nsIMozIconURI
, public nsIIPCSerializableURI
{
public:
NS_DECL_THREADSAFE_ISUPPORTS
NS_DECL_NSIURI
NS_DECL_NSIMOZICONURI
NS_DECL_NSIIPCSERIALIZABLEURI
// nsMozIconURI
nsMozIconURI();
protected:
virtual ~nsMozIconURI();
nsCOMPtr<nsIURL> mIconURL; // a URL that we want the icon for
uint32_t mSize; // the # of pixels in a row that we want for this image.
// Typically 16, 32, 128, etc.
nsCString mContentType; // optional field explicitly specifying the content
// type
nsCString mFileName; // for if we don't have an actual file path, we're just
// given a filename with an extension
nsCString mStockIcon;
int32_t mIconSize; // -1 if not specified, otherwise index into
// kSizeStrings
int32_t mIconState; // -1 if not specified, otherwise index into
// kStateStrings
};
// For moz-icon URIs that point to an actual file on disk and are
// therefore nested URIs
class nsNestedMozIconURI final : public nsMozIconURI
, public nsINestedURI
{
NS_DECL_ISUPPORTS_INHERITED
NS_FORWARD_NSIURI(nsMozIconURI::)
NS_FORWARD_NSIMOZICONURI(nsMozIconURI::)
NS_FORWARD_NSIIPCSERIALIZABLEURI(nsMozIconURI::)
NS_DECL_NSINESTEDURI
nsNestedMozIconURI();
protected:
virtual ~nsNestedMozIconURI();
};
#endif // mozilla_image_decoders_icon_nsIconURI_h

View file

@ -0,0 +1,11 @@
# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
# vim: set filetype=python:
# 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/.
SOURCES += [
'nsIconChannel.cpp',
]
FINAL_LIBRARY = 'xul'

View file

@ -0,0 +1,841 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*-
*
* 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/. */
#include "mozilla/ArrayUtils.h"
#include "nsIconChannel.h"
#include "nsIIconURI.h"
#include "nsIServiceManager.h"
#include "nsIInterfaceRequestor.h"
#include "nsIInterfaceRequestorUtils.h"
#include "nsXPIDLString.h"
#include "nsReadableUtils.h"
#include "nsMimeTypes.h"
#include "nsMemory.h"
#include "nsIStringStream.h"
#include "nsIURL.h"
#include "nsIOutputStream.h"
#include "nsIPipe.h"
#include "nsNetCID.h"
#include "nsIFile.h"
#include "nsIFileURL.h"
#include "nsIMIMEService.h"
#include "nsCExternalHandlerService.h"
#include "nsDirectoryServiceDefs.h"
#include "nsProxyRelease.h"
#include "nsContentSecurityManager.h"
#include "nsContentUtils.h"
#ifdef _WIN32_WINNT
#undef _WIN32_WINNT
#endif
#define _WIN32_WINNT 0x0600
// we need windows.h to read out registry information...
#include <windows.h>
#include <shellapi.h>
#include <shlobj.h>
#include <objbase.h>
#include <wchar.h>
using namespace mozilla;
struct ICONFILEHEADER {
uint16_t ifhReserved;
uint16_t ifhType;
uint16_t ifhCount;
};
struct ICONENTRY {
int8_t ieWidth;
int8_t ieHeight;
uint8_t ieColors;
uint8_t ieReserved;
uint16_t iePlanes;
uint16_t ieBitCount;
uint32_t ieSizeImage;
uint32_t ieFileOffset;
};
// Match stock icons with names
static SHSTOCKICONID
GetStockIconIDForName(const nsACString& aStockName)
{
return aStockName.EqualsLiteral("uac-shield") ? SIID_SHIELD :
SIID_INVALID;
}
// nsIconChannel methods
nsIconChannel::nsIconChannel()
{
}
nsIconChannel::~nsIconChannel()
{
if (mLoadInfo) {
NS_ReleaseOnMainThread(mLoadInfo.forget());
}
}
NS_IMPL_ISUPPORTS(nsIconChannel,
nsIChannel,
nsIRequest,
nsIRequestObserver,
nsIStreamListener)
nsresult
nsIconChannel::Init(nsIURI* uri)
{
NS_ASSERTION(uri, "no uri");
mUrl = uri;
mOriginalURI = uri;
nsresult rv;
mPump = do_CreateInstance(NS_INPUTSTREAMPUMP_CONTRACTID, &rv);
return rv;
}
////////////////////////////////////////////////////////////////////////////////
// nsIRequest methods:
NS_IMETHODIMP
nsIconChannel::GetName(nsACString& result)
{
return mUrl->GetSpec(result);
}
NS_IMETHODIMP
nsIconChannel::IsPending(bool* result)
{
return mPump->IsPending(result);
}
NS_IMETHODIMP
nsIconChannel::GetStatus(nsresult* status)
{
return mPump->GetStatus(status);
}
NS_IMETHODIMP
nsIconChannel::Cancel(nsresult status)
{
return mPump->Cancel(status);
}
NS_IMETHODIMP
nsIconChannel::Suspend(void)
{
return mPump->Suspend();
}
NS_IMETHODIMP
nsIconChannel::Resume(void)
{
return mPump->Resume();
}
NS_IMETHODIMP
nsIconChannel::GetLoadGroup(nsILoadGroup** aLoadGroup)
{
*aLoadGroup = mLoadGroup;
NS_IF_ADDREF(*aLoadGroup);
return NS_OK;
}
NS_IMETHODIMP
nsIconChannel::SetLoadGroup(nsILoadGroup* aLoadGroup)
{
mLoadGroup = aLoadGroup;
return NS_OK;
}
NS_IMETHODIMP
nsIconChannel::GetLoadFlags(uint32_t* aLoadAttributes)
{
return mPump->GetLoadFlags(aLoadAttributes);
}
NS_IMETHODIMP
nsIconChannel::SetLoadFlags(uint32_t aLoadAttributes)
{
return mPump->SetLoadFlags(aLoadAttributes);
}
////////////////////////////////////////////////////////////////////////////////
// nsIChannel methods:
NS_IMETHODIMP
nsIconChannel::GetOriginalURI(nsIURI** aURI)
{
*aURI = mOriginalURI;
NS_ADDREF(*aURI);
return NS_OK;
}
NS_IMETHODIMP
nsIconChannel::SetOriginalURI(nsIURI* aURI)
{
NS_ENSURE_ARG_POINTER(aURI);
mOriginalURI = aURI;
return NS_OK;
}
NS_IMETHODIMP
nsIconChannel::GetURI(nsIURI** aURI)
{
*aURI = mUrl;
NS_IF_ADDREF(*aURI);
return NS_OK;
}
NS_IMETHODIMP
nsIconChannel::Open(nsIInputStream** _retval)
{
return MakeInputStream(_retval, false);
}
NS_IMETHODIMP
nsIconChannel::Open2(nsIInputStream** aStream)
{
nsCOMPtr<nsIStreamListener> listener;
nsresult rv = nsContentSecurityManager::doContentSecurityCheck(this, listener);
NS_ENSURE_SUCCESS(rv, rv);
return Open(aStream);
}
nsresult
nsIconChannel::ExtractIconInfoFromUrl(nsIFile** aLocalFile,
uint32_t* aDesiredImageSize, nsCString& aContentType,
nsCString& aFileExtension)
{
nsresult rv = NS_OK;
nsCOMPtr<nsIMozIconURI> iconURI (do_QueryInterface(mUrl, &rv));
NS_ENSURE_SUCCESS(rv, rv);
iconURI->GetImageSize(aDesiredImageSize);
iconURI->GetContentType(aContentType);
iconURI->GetFileExtension(aFileExtension);
nsCOMPtr<nsIURL> url;
rv = iconURI->GetIconURL(getter_AddRefs(url));
if (NS_FAILED(rv) || !url) return NS_OK;
nsCOMPtr<nsIFileURL> fileURL = do_QueryInterface(url, &rv);
if (NS_FAILED(rv) || !fileURL) return NS_OK;
nsCOMPtr<nsIFile> file;
rv = fileURL->GetFile(getter_AddRefs(file));
if (NS_FAILED(rv) || !file) return NS_OK;
return file->Clone(aLocalFile);
}
NS_IMETHODIMP
nsIconChannel::AsyncOpen(nsIStreamListener* aListener,
nsISupports* ctxt)
{
MOZ_ASSERT(!mLoadInfo ||
mLoadInfo->GetSecurityMode() == 0 ||
mLoadInfo->GetInitialSecurityCheckDone() ||
(mLoadInfo->GetSecurityMode() == nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL &&
nsContentUtils::IsSystemPrincipal(mLoadInfo->LoadingPrincipal())),
"security flags in loadInfo but asyncOpen2() not called");
nsCOMPtr<nsIInputStream> inStream;
nsresult rv = MakeInputStream(getter_AddRefs(inStream), true);
if (NS_FAILED(rv)) {
return rv;
}
// Init our streampump
rv = mPump->Init(inStream, int64_t(-1), int64_t(-1), 0, 0, false);
if (NS_FAILED(rv)) {
return rv;
}
rv = mPump->AsyncRead(this, ctxt);
if (NS_SUCCEEDED(rv)) {
// Store our real listener
mListener = aListener;
// Add ourself to the load group, if available
if (mLoadGroup) {
mLoadGroup->AddRequest(this, nullptr);
}
}
return rv;
}
NS_IMETHODIMP
nsIconChannel::AsyncOpen2(nsIStreamListener* aListener)
{
nsCOMPtr<nsIStreamListener> listener = aListener;
nsresult rv = nsContentSecurityManager::doContentSecurityCheck(this, listener);
NS_ENSURE_SUCCESS(rv, rv);
return AsyncOpen(listener, nullptr);
}
static DWORD
GetSpecialFolderIcon(nsIFile* aFile, int aFolder,
SHFILEINFOW* aSFI, UINT aInfoFlags)
{
DWORD shellResult = 0;
if (!aFile) {
return shellResult;
}
wchar_t fileNativePath[MAX_PATH];
nsAutoString fileNativePathStr;
aFile->GetPath(fileNativePathStr);
::GetShortPathNameW(fileNativePathStr.get(), fileNativePath,
ArrayLength(fileNativePath));
LPITEMIDLIST idList;
HRESULT hr = ::SHGetSpecialFolderLocation(nullptr, aFolder, &idList);
if (SUCCEEDED(hr)) {
wchar_t specialNativePath[MAX_PATH];
::SHGetPathFromIDListW(idList, specialNativePath);
::GetShortPathNameW(specialNativePath, specialNativePath,
ArrayLength(specialNativePath));
if (!wcsicmp(fileNativePath, specialNativePath)) {
aInfoFlags |= (SHGFI_PIDL | SHGFI_SYSICONINDEX);
shellResult = ::SHGetFileInfoW((LPCWSTR)(LPCITEMIDLIST)idList, 0,
aSFI,
sizeof(*aSFI), aInfoFlags);
}
}
CoTaskMemFree(idList);
return shellResult;
}
static UINT
GetSizeInfoFlag(uint32_t aDesiredImageSize)
{
return
(UINT) (aDesiredImageSize > 16 ? SHGFI_SHELLICONSIZE : SHGFI_SMALLICON);
}
nsresult
nsIconChannel::GetHIconFromFile(HICON* hIcon)
{
nsXPIDLCString contentType;
nsCString fileExt;
nsCOMPtr<nsIFile> localFile; // file we want an icon for
uint32_t desiredImageSize;
nsresult rv = ExtractIconInfoFromUrl(getter_AddRefs(localFile),
&desiredImageSize, contentType,
fileExt);
NS_ENSURE_SUCCESS(rv, rv);
// if the file exists, we are going to use it's real attributes...
// otherwise we only want to use it for it's extension...
SHFILEINFOW sfi;
UINT infoFlags = SHGFI_ICON;
bool fileExists = false;
nsAutoString filePath;
CopyASCIItoUTF16(fileExt, filePath);
if (localFile) {
rv = localFile->Normalize();
NS_ENSURE_SUCCESS(rv, rv);
localFile->GetPath(filePath);
if (filePath.Length() < 2 || filePath[1] != ':') {
return NS_ERROR_MALFORMED_URI; // UNC
}
if (filePath.Last() == ':') {
filePath.Append('\\');
} else {
localFile->Exists(&fileExists);
if (!fileExists) {
localFile->GetLeafName(filePath);
}
}
}
if (!fileExists) {
infoFlags |= SHGFI_USEFILEATTRIBUTES;
}
infoFlags |= GetSizeInfoFlag(desiredImageSize);
// if we have a content type... then use it! but for existing files,
// we want to show their real icon.
if (!fileExists && !contentType.IsEmpty()) {
nsCOMPtr<nsIMIMEService> mimeService
(do_GetService(NS_MIMESERVICE_CONTRACTID, &rv));
NS_ENSURE_SUCCESS(rv, rv);
nsAutoCString defFileExt;
mimeService->GetPrimaryExtension(contentType, fileExt, defFileExt);
// If the mime service does not know about this mime type, we show
// the generic icon.
// In any case, we need to insert a '.' before the extension.
filePath = NS_LITERAL_STRING(".") +
NS_ConvertUTF8toUTF16(defFileExt);
}
// Is this the "Desktop" folder?
DWORD shellResult = GetSpecialFolderIcon(localFile, CSIDL_DESKTOP,
&sfi, infoFlags);
if (!shellResult) {
// Is this the "My Documents" folder?
shellResult = GetSpecialFolderIcon(localFile, CSIDL_PERSONAL,
&sfi, infoFlags);
}
// There are other "Special Folders" and Namespace entities that we
// are not fetching icons for, see:
// http://msdn.microsoft.com/library/default.asp?url=/library/en-us/
// shellcc/platform/shell/reference/enums/csidl.asp
// If we ever need to get them, code to do so would be inserted here.
// Not a special folder, or something else failed above.
if (!shellResult) {
shellResult = ::SHGetFileInfoW(filePath.get(),
FILE_ATTRIBUTE_ARCHIVE,
&sfi, sizeof(sfi), infoFlags);
}
if (shellResult && sfi.hIcon) {
*hIcon = sfi.hIcon;
} else {
rv = NS_ERROR_NOT_AVAILABLE;
}
return rv;
}
nsresult
nsIconChannel::GetStockHIcon(nsIMozIconURI* aIconURI,
HICON* hIcon)
{
nsresult rv = NS_OK;
// We can only do this on Vista or above
HMODULE hShellDLL = ::LoadLibraryW(L"shell32.dll");
decltype(SHGetStockIconInfo)* pSHGetStockIconInfo =
(decltype(SHGetStockIconInfo)*) ::GetProcAddress(hShellDLL,
"SHGetStockIconInfo");
if (pSHGetStockIconInfo) {
uint32_t desiredImageSize;
aIconURI->GetImageSize(&desiredImageSize);
nsAutoCString stockIcon;
aIconURI->GetStockIcon(stockIcon);
SHSTOCKICONID stockIconID = GetStockIconIDForName(stockIcon);
if (stockIconID == SIID_INVALID) {
return NS_ERROR_NOT_AVAILABLE;
}
UINT infoFlags = SHGSI_ICON;
infoFlags |= GetSizeInfoFlag(desiredImageSize);
SHSTOCKICONINFO sii = {0};
sii.cbSize = sizeof(sii);
HRESULT hr = pSHGetStockIconInfo(stockIconID, infoFlags, &sii);
if (SUCCEEDED(hr)) {
*hIcon = sii.hIcon;
} else {
rv = NS_ERROR_FAILURE;
}
} else {
rv = NS_ERROR_NOT_AVAILABLE;
}
if (hShellDLL) {
::FreeLibrary(hShellDLL);
}
return rv;
}
// Given a BITMAPINFOHEADER, returns the size of the color table.
static int
GetColorTableSize(BITMAPINFOHEADER* aHeader)
{
int colorTableSize = -1;
// http://msdn.microsoft.com/en-us/library/dd183376%28v=VS.85%29.aspx
switch (aHeader->biBitCount) {
case 0:
colorTableSize = 0;
break;
case 1:
colorTableSize = 2 * sizeof(RGBQUAD);
break;
case 4:
case 8: {
// The maximum possible size for the color table is 2**bpp, so check for
// that and fail if we're not in those bounds
unsigned int maxEntries = 1 << (aHeader->biBitCount);
if (aHeader->biClrUsed > 0 && aHeader->biClrUsed <= maxEntries) {
colorTableSize = aHeader->biClrUsed * sizeof(RGBQUAD);
} else if (aHeader->biClrUsed == 0) {
colorTableSize = maxEntries * sizeof(RGBQUAD);
}
break;
}
case 16:
case 32:
// If we have BI_BITFIELDS compression, we would normally need 3 DWORDS for
// the bitfields mask which would be stored in the color table; However,
// we instead force the bitmap to request data of type BI_RGB so the color
// table should be of size 0.
// Setting aHeader->biCompression = BI_RGB forces the later call to
// GetDIBits to return to us BI_RGB data.
if (aHeader->biCompression == BI_BITFIELDS) {
aHeader->biCompression = BI_RGB;
}
colorTableSize = 0;
break;
case 24:
colorTableSize = 0;
break;
}
if (colorTableSize < 0) {
NS_WARNING("Unable to figure out the color table size for this bitmap");
}
return colorTableSize;
}
// Given a header and a size, creates a freshly allocated BITMAPINFO structure.
// It is the caller's responsibility to null-check and delete the structure.
static BITMAPINFO*
CreateBitmapInfo(BITMAPINFOHEADER* aHeader, size_t aColorTableSize)
{
BITMAPINFO* bmi = (BITMAPINFO*) ::operator new(sizeof(BITMAPINFOHEADER) +
aColorTableSize,
mozilla::fallible);
if (bmi) {
memcpy(bmi, aHeader, sizeof(BITMAPINFOHEADER));
memset(bmi->bmiColors, 0, aColorTableSize);
}
return bmi;
}
nsresult
nsIconChannel::MakeInputStream(nsIInputStream** _retval, bool aNonBlocking)
{
// Check whether the icon requested's a file icon or a stock icon
nsresult rv = NS_ERROR_NOT_AVAILABLE;
// GetDIBits does not exist on windows mobile.
HICON hIcon = nullptr;
nsCOMPtr<nsIMozIconURI> iconURI(do_QueryInterface(mUrl, &rv));
NS_ENSURE_SUCCESS(rv, rv);
nsAutoCString stockIcon;
iconURI->GetStockIcon(stockIcon);
if (!stockIcon.IsEmpty()) {
rv = GetStockHIcon(iconURI, &hIcon);
} else {
rv = GetHIconFromFile(&hIcon);
}
NS_ENSURE_SUCCESS(rv, rv);
if (hIcon) {
// we got a handle to an icon. Now we want to get a bitmap for the icon
// using GetIconInfo....
ICONINFO iconInfo;
if (GetIconInfo(hIcon, &iconInfo)) {
// we got the bitmaps, first find out their size
HDC hDC = CreateCompatibleDC(nullptr); // get a device context for
// the screen.
BITMAPINFOHEADER maskHeader = {sizeof(BITMAPINFOHEADER)};
BITMAPINFOHEADER colorHeader = {sizeof(BITMAPINFOHEADER)};
int colorTableSize, maskTableSize;
if (GetDIBits(hDC, iconInfo.hbmMask, 0, 0, nullptr,
(BITMAPINFO*)&maskHeader, DIB_RGB_COLORS) &&
GetDIBits(hDC, iconInfo.hbmColor, 0, 0, nullptr,
(BITMAPINFO*)&colorHeader, DIB_RGB_COLORS) &&
maskHeader.biHeight == colorHeader.biHeight &&
maskHeader.biWidth == colorHeader.biWidth &&
colorHeader.biBitCount > 8 &&
colorHeader.biSizeImage > 0 &&
colorHeader.biWidth >= 0 && colorHeader.biWidth <= 255 &&
colorHeader.biHeight >= 0 && colorHeader.biHeight <= 255 &&
maskHeader.biSizeImage > 0 &&
(colorTableSize = GetColorTableSize(&colorHeader)) >= 0 &&
(maskTableSize = GetColorTableSize(&maskHeader)) >= 0) {
uint32_t iconSize = sizeof(ICONFILEHEADER) +
sizeof(ICONENTRY) +
sizeof(BITMAPINFOHEADER) +
colorHeader.biSizeImage +
maskHeader.biSizeImage;
UniquePtr<char[]> buffer = MakeUnique<char[]>(iconSize);
if (!buffer) {
rv = NS_ERROR_OUT_OF_MEMORY;
} else {
char* whereTo = buffer.get();
int howMuch;
// the data starts with an icon file header
ICONFILEHEADER iconHeader;
iconHeader.ifhReserved = 0;
iconHeader.ifhType = 1;
iconHeader.ifhCount = 1;
howMuch = sizeof(ICONFILEHEADER);
memcpy(whereTo, &iconHeader, howMuch);
whereTo += howMuch;
// followed by the single icon entry
ICONENTRY iconEntry;
iconEntry.ieWidth = static_cast<int8_t>(colorHeader.biWidth);
iconEntry.ieHeight = static_cast<int8_t>(colorHeader.biHeight);
iconEntry.ieColors = 0;
iconEntry.ieReserved = 0;
iconEntry.iePlanes = 1;
iconEntry.ieBitCount = colorHeader.biBitCount;
iconEntry.ieSizeImage = sizeof(BITMAPINFOHEADER) +
colorHeader.biSizeImage +
maskHeader.biSizeImage;
iconEntry.ieFileOffset = sizeof(ICONFILEHEADER) + sizeof(ICONENTRY);
howMuch = sizeof(ICONENTRY);
memcpy(whereTo, &iconEntry, howMuch);
whereTo += howMuch;
// followed by the bitmap info header
// (doubling the height because icons have two bitmaps)
colorHeader.biHeight *= 2;
colorHeader.biSizeImage += maskHeader.biSizeImage;
howMuch = sizeof(BITMAPINFOHEADER);
memcpy(whereTo, &colorHeader, howMuch);
whereTo += howMuch;
colorHeader.biHeight /= 2;
colorHeader.biSizeImage -= maskHeader.biSizeImage;
// followed by the XOR bitmap data (colorHeader)
// (you'd expect the color table to come here, but it apparently
// doesn't)
BITMAPINFO* colorInfo = CreateBitmapInfo(&colorHeader,
colorTableSize);
if (colorInfo && GetDIBits(hDC, iconInfo.hbmColor, 0,
colorHeader.biHeight, whereTo, colorInfo,
DIB_RGB_COLORS)) {
whereTo += colorHeader.biSizeImage;
// and finally the AND bitmap data (maskHeader)
BITMAPINFO* maskInfo = CreateBitmapInfo(&maskHeader, maskTableSize);
if (maskInfo && GetDIBits(hDC, iconInfo.hbmMask, 0,
maskHeader.biHeight, whereTo, maskInfo,
DIB_RGB_COLORS)) {
// Now, create a pipe and stuff our data into it
nsCOMPtr<nsIInputStream> inStream;
nsCOMPtr<nsIOutputStream> outStream;
rv = NS_NewPipe(getter_AddRefs(inStream),
getter_AddRefs(outStream),
iconSize, iconSize, aNonBlocking);
if (NS_SUCCEEDED(rv)) {
uint32_t written;
rv = outStream->Write(buffer.get(), iconSize, &written);
if (NS_SUCCEEDED(rv)) {
NS_ADDREF(*_retval = inStream);
}
}
} // if we got bitmap bits
delete maskInfo;
} // if we got mask bits
delete colorInfo;
} // if we allocated the buffer
} // if we got mask size
DeleteDC(hDC);
DeleteObject(iconInfo.hbmColor);
DeleteObject(iconInfo.hbmMask);
} // if we got icon info
DestroyIcon(hIcon);
} // if we got an hIcon
// If we didn't make a stream, then fail.
if (!*_retval && NS_SUCCEEDED(rv)) {
rv = NS_ERROR_NOT_AVAILABLE;
}
return rv;
}
NS_IMETHODIMP
nsIconChannel::GetContentType(nsACString& aContentType)
{
aContentType.AssignLiteral(IMAGE_ICO);
return NS_OK;
}
NS_IMETHODIMP
nsIconChannel::SetContentType(const nsACString& aContentType)
{
// It doesn't make sense to set the content-type on this type
// of channel...
return NS_ERROR_FAILURE;
}
NS_IMETHODIMP nsIconChannel::GetContentCharset(nsACString& aContentCharset)
{
aContentCharset.Truncate();
return NS_OK;
}
NS_IMETHODIMP
nsIconChannel::SetContentCharset(const nsACString& aContentCharset)
{
// It doesn't make sense to set the content-charset on this type
// of channel...
return NS_ERROR_FAILURE;
}
NS_IMETHODIMP
nsIconChannel::GetContentDisposition(uint32_t* aContentDisposition)
{
return NS_ERROR_NOT_AVAILABLE;
}
NS_IMETHODIMP
nsIconChannel::SetContentDisposition(uint32_t aContentDisposition)
{
return NS_ERROR_NOT_AVAILABLE;
}
NS_IMETHODIMP
nsIconChannel::
GetContentDispositionFilename(nsAString& aContentDispositionFilename)
{
return NS_ERROR_NOT_AVAILABLE;
}
NS_IMETHODIMP
nsIconChannel::
SetContentDispositionFilename(const nsAString& aContentDispositionFilename)
{
return NS_ERROR_NOT_AVAILABLE;
}
NS_IMETHODIMP
nsIconChannel::
GetContentDispositionHeader(nsACString& aContentDispositionHeader)
{
return NS_ERROR_NOT_AVAILABLE;
}
NS_IMETHODIMP
nsIconChannel::GetContentLength(int64_t* aContentLength)
{
*aContentLength = 0;
return NS_ERROR_FAILURE;
}
NS_IMETHODIMP
nsIconChannel::SetContentLength(int64_t aContentLength)
{
NS_NOTREACHED("nsIconChannel::SetContentLength");
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP
nsIconChannel::GetOwner(nsISupports** aOwner)
{
*aOwner = mOwner.get();
NS_IF_ADDREF(*aOwner);
return NS_OK;
}
NS_IMETHODIMP
nsIconChannel::SetOwner(nsISupports* aOwner)
{
mOwner = aOwner;
return NS_OK;
}
NS_IMETHODIMP
nsIconChannel::GetLoadInfo(nsILoadInfo** aLoadInfo)
{
NS_IF_ADDREF(*aLoadInfo = mLoadInfo);
return NS_OK;
}
NS_IMETHODIMP
nsIconChannel::SetLoadInfo(nsILoadInfo* aLoadInfo)
{
mLoadInfo = aLoadInfo;
return NS_OK;
}
NS_IMETHODIMP
nsIconChannel::
GetNotificationCallbacks(nsIInterfaceRequestor** aNotificationCallbacks)
{
*aNotificationCallbacks = mCallbacks.get();
NS_IF_ADDREF(*aNotificationCallbacks);
return NS_OK;
}
NS_IMETHODIMP
nsIconChannel::
SetNotificationCallbacks(nsIInterfaceRequestor* aNotificationCallbacks)
{
mCallbacks = aNotificationCallbacks;
return NS_OK;
}
NS_IMETHODIMP
nsIconChannel::GetSecurityInfo(nsISupports** aSecurityInfo)
{
*aSecurityInfo = nullptr;
return NS_OK;
}
// nsIRequestObserver methods
NS_IMETHODIMP nsIconChannel::OnStartRequest(nsIRequest* aRequest,
nsISupports* aContext)
{
if (mListener) {
return mListener->OnStartRequest(this, aContext);
}
return NS_OK;
}
NS_IMETHODIMP
nsIconChannel::OnStopRequest(nsIRequest* aRequest,
nsISupports* aContext,
nsresult aStatus)
{
if (mListener) {
mListener->OnStopRequest(this, aContext, aStatus);
mListener = nullptr;
}
// Remove from load group
if (mLoadGroup) {
mLoadGroup->RemoveRequest(this, nullptr, aStatus);
}
// Drop notification callbacks to prevent cycles.
mCallbacks = nullptr;
return NS_OK;
}
// nsIStreamListener methods
NS_IMETHODIMP
nsIconChannel::OnDataAvailable(nsIRequest* aRequest,
nsISupports* aContext,
nsIInputStream* aStream,
uint64_t aOffset,
uint32_t aCount)
{
if (mListener) {
return mListener->OnDataAvailable(this, aContext, aStream, aOffset, aCount);
}
return NS_OK;
}

Some files were not shown because too many files have changed in this diff Show more