1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
3 * This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
9 #include "DecodePool.h"
11 #include "mozilla/gfx/2D.h"
12 #include "mozilla/gfx/Logging.h"
13 #include "mozilla/RefPtr.h"
15 #include "mozilla/dom/Document.h"
17 #include "imgLoader.h"
18 #include "imgICache.h"
19 #include "imgIContainer.h"
20 #include "imgIEncoder.h"
21 #include "nsComponentManagerUtils.h"
22 #include "nsNetUtil.h" // for NS_NewBufferedInputStream
23 #include "nsStreamUtils.h"
24 #include "nsStringStream.h"
25 #include "nsContentUtils.h"
26 #include "nsProxyRelease.h"
27 #include "nsIStreamListener.h"
28 #include "ImageFactory.h"
30 #include "IProgressObserver.h"
31 #include "ScriptedNotificationObserver.h"
32 #include "imgIScriptedNotificationObserver.h"
33 #include "gfxPlatform.h"
34 #include "js/ArrayBuffer.h"
35 #include "js/RootingAPI.h" // JS::{Handle,Rooted}
36 #include "js/Value.h" // JS::Value
37 #include "Orientation.h"
39 using namespace mozilla::gfx
;
46 static nsresult
sniff_mimetype_callback(nsIInputStream
* in
, void* data
,
47 const char* fromRawSegment
,
48 uint32_t toOffset
, uint32_t count
,
49 uint32_t* writeCount
) {
50 nsCString
* mimeType
= static_cast<nsCString
*>(data
);
51 MOZ_ASSERT(mimeType
, "mimeType is null!");
54 imgLoader::GetMimeTypeFromContent(fromRawSegment
, count
, *mimeType
);
58 return NS_ERROR_FAILURE
;
61 class ImageDecoderListener final
: public nsIStreamListener
,
62 public IProgressObserver
,
63 public imgIContainer
{
67 ImageDecoderListener(nsIURI
* aURI
, imgIContainerCallback
* aCallback
,
68 imgINotificationObserver
* aObserver
)
72 mObserver(aObserver
) {
73 MOZ_ASSERT(NS_IsMainThread());
77 OnDataAvailable(nsIRequest
* aRequest
, nsIInputStream
* aInputStream
,
78 uint64_t aOffset
, uint32_t aCount
) override
{
80 nsCOMPtr
<nsIChannel
> channel
= do_QueryInterface(aRequest
);
83 channel
->GetContentType(mimeType
);
86 // Look at the first few bytes and see if we can tell what the data is
87 // from that since servers tend to lie. :(
89 aInputStream
->ReadSegments(sniff_mimetype_callback
, &mimeType
, aCount
,
93 RefPtr
<ProgressTracker
> tracker
= new ProgressTracker();
95 tracker
->AddObserver(this);
98 mImage
= ImageFactory::CreateImage(channel
, tracker
, mimeType
, mURI
,
99 /* aIsMultiPart */ false, 0);
101 if (mImage
->HasError()) {
102 return NS_ERROR_FAILURE
;
106 return mImage
->OnImageDataAvailable(aRequest
, nullptr, aInputStream
,
111 OnStartRequest(nsIRequest
* aRequest
) override
{ return NS_OK
; }
114 OnStopRequest(nsIRequest
* aRequest
, nsresult aStatus
) override
{
115 // Encouter a fetch error, or no data could be fetched.
116 if (!mImage
|| NS_FAILED(aStatus
)) {
117 mCallback
->OnImageReady(nullptr, mImage
? aStatus
: NS_ERROR_FAILURE
);
121 mImage
->OnImageDataComplete(aRequest
, nullptr, aStatus
, true);
122 nsCOMPtr
<imgIContainer
> container
= this;
123 mCallback
->OnImageReady(container
, aStatus
);
127 virtual void Notify(int32_t aType
,
128 const nsIntRect
* aRect
= nullptr) override
{
130 mObserver
->Notify(nullptr, aType
, aRect
);
134 virtual void OnLoadComplete(bool aLastPart
) override
{}
136 // Other notifications are ignored.
137 virtual void SetHasImage() override
{}
138 virtual bool NotificationsDeferred() const override
{ return false; }
139 virtual void MarkPendingNotify() override
{}
140 virtual void ClearPendingNotify() override
{}
143 NS_FORWARD_IMGICONTAINER(mImage
->)
145 nsresult
GetNativeSizes(nsTArray
<nsIntSize
>& aNativeSizes
) const override
{
146 return mImage
->GetNativeSizes(aNativeSizes
);
149 size_t GetNativeSizesLength() const override
{
150 return mImage
->GetNativeSizesLength();
154 virtual ~ImageDecoderListener() = default;
156 nsCOMPtr
<nsIURI
> mURI
;
157 RefPtr
<image::Image
> mImage
;
158 nsCOMPtr
<imgIContainerCallback
> mCallback
;
159 nsCOMPtr
<imgINotificationObserver
> mObserver
;
162 NS_IMPL_ISUPPORTS(ImageDecoderListener
, nsIStreamListener
, imgIContainer
)
164 class ImageDecoderHelper final
: public Runnable
,
165 public nsIInputStreamCallback
{
167 NS_DECL_ISUPPORTS_INHERITED
169 ImageDecoderHelper(already_AddRefed
<image::Image
> aImage
,
170 already_AddRefed
<nsIInputStream
> aInputStream
,
171 nsIEventTarget
* aEventTarget
,
172 imgIContainerCallback
* aCallback
,
173 nsIEventTarget
* aCallbackEventTarget
)
174 : Runnable("ImageDecoderHelper"),
175 mImage(std::move(aImage
)),
176 mInputStream(std::move(aInputStream
)),
177 mEventTarget(aEventTarget
),
178 mCallback(aCallback
),
179 mCallbackEventTarget(aCallbackEventTarget
),
181 MOZ_ASSERT(NS_IsMainThread());
186 // This runnable is dispatched on the Image thread when reading data, but
187 // at the end, it goes back to the main-thread in order to complete the
189 if (NS_IsMainThread()) {
190 // Let the Image know we've sent all the data.
191 mImage
->OnImageDataComplete(nullptr, nullptr, mStatus
, true);
193 RefPtr
<ProgressTracker
> tracker
= mImage
->GetProgressTracker();
194 tracker
->SyncNotifyProgress(FLAG_LOAD_COMPLETE
);
196 nsCOMPtr
<imgIContainer
> container
;
197 if (NS_SUCCEEDED(mStatus
)) {
201 mCallback
->OnImageReady(container
, mStatus
);
206 nsresult rv
= mInputStream
->Available(&length
);
207 if (rv
== NS_BASE_STREAM_CLOSED
) {
208 return OperationCompleted(NS_OK
);
211 if (NS_WARN_IF(NS_FAILED(rv
))) {
212 return OperationCompleted(rv
);
215 // Nothing else to read, but maybe we just need to wait.
217 nsCOMPtr
<nsIAsyncInputStream
> asyncInputStream
=
218 do_QueryInterface(mInputStream
);
219 if (asyncInputStream
) {
220 rv
= asyncInputStream
->AsyncWait(this, 0, 0, mEventTarget
);
221 if (NS_WARN_IF(NS_FAILED(rv
))) {
222 return OperationCompleted(rv
);
227 // We really have nothing else to read.
229 return OperationCompleted(NS_OK
);
233 // Send the source data to the Image.
234 rv
= mImage
->OnImageDataAvailable(nullptr, nullptr, mInputStream
, 0,
236 if (NS_WARN_IF(NS_FAILED(rv
))) {
237 return OperationCompleted(rv
);
240 rv
= mEventTarget
->Dispatch(this, NS_DISPATCH_NORMAL
);
241 if (NS_WARN_IF(NS_FAILED(rv
))) {
242 return OperationCompleted(rv
);
249 OnInputStreamReady(nsIAsyncInputStream
* aAsyncInputStream
) override
{
250 MOZ_ASSERT(!NS_IsMainThread());
254 nsresult
OperationCompleted(nsresult aStatus
) {
255 MOZ_ASSERT(!NS_IsMainThread());
258 mCallbackEventTarget
->Dispatch(this, NS_DISPATCH_NORMAL
);
263 ~ImageDecoderHelper() {
264 SurfaceCache::ReleaseImageOnMainThread(mImage
.forget());
265 NS_ReleaseOnMainThread("ImageDecoderHelper::mCallback", mCallback
.forget());
268 RefPtr
<image::Image
> mImage
;
270 nsCOMPtr
<nsIInputStream
> mInputStream
;
271 nsCOMPtr
<nsIEventTarget
> mEventTarget
;
272 nsCOMPtr
<imgIContainerCallback
> mCallback
;
273 nsCOMPtr
<nsIEventTarget
> mCallbackEventTarget
;
278 NS_IMPL_ISUPPORTS_INHERITED(ImageDecoderHelper
, Runnable
,
279 nsIInputStreamCallback
)
283 /* ========== imgITools implementation ========== */
285 NS_IMPL_ISUPPORTS(imgTools
, imgITools
)
287 imgTools::imgTools() { /* member initializers and constructor code */
290 imgTools::~imgTools() { /* destructor code */
294 imgTools::DecodeImageFromArrayBuffer(JS::Handle
<JS::Value
> aArrayBuffer
,
295 const nsACString
& aMimeType
,
297 imgIContainer
** aContainer
) {
298 if (!aArrayBuffer
.isObject()) {
299 return NS_ERROR_FAILURE
;
302 JS::Rooted
<JSObject
*> obj(aCx
,
303 JS::UnwrapArrayBuffer(&aArrayBuffer
.toObject()));
305 return NS_ERROR_FAILURE
;
308 uint8_t* bufferData
= nullptr;
309 size_t bufferLength
= 0;
310 bool isSharedMemory
= false;
312 JS::GetArrayBufferLengthAndData(obj
, &bufferLength
, &isSharedMemory
,
315 // Throw for large ArrayBuffers to prevent truncation.
316 if (bufferLength
> INT32_MAX
) {
317 return NS_ERROR_ILLEGAL_VALUE
;
320 return DecodeImageFromBuffer((char*)bufferData
, bufferLength
, aMimeType
,
325 imgTools::DecodeImageFromBuffer(const char* aBuffer
, uint32_t aSize
,
326 const nsACString
& aMimeType
,
327 imgIContainer
** aContainer
) {
328 MOZ_ASSERT(NS_IsMainThread());
330 NS_ENSURE_ARG_POINTER(aBuffer
);
332 // Create a new image container to hold the decoded data.
333 nsAutoCString
mimeType(aMimeType
);
334 RefPtr
<image::Image
> image
=
335 ImageFactory::CreateAnonymousImage(mimeType
, aSize
);
336 RefPtr
<ProgressTracker
> tracker
= image
->GetProgressTracker();
338 if (image
->HasError()) {
339 return NS_ERROR_FAILURE
;
342 // Let's create a temporary inputStream.
343 nsCOMPtr
<nsIInputStream
> stream
;
344 nsresult rv
= NS_NewByteInputStream(
345 getter_AddRefs(stream
), Span(aBuffer
, aSize
), NS_ASSIGNMENT_DEPEND
);
346 NS_ENSURE_SUCCESS(rv
, rv
);
348 MOZ_ASSERT(NS_InputStreamIsBuffered(stream
));
350 rv
= image
->OnImageDataAvailable(nullptr, nullptr, stream
, 0, aSize
);
351 NS_ENSURE_SUCCESS(rv
, rv
);
353 // Let the Image know we've sent all the data.
354 rv
= image
->OnImageDataComplete(nullptr, nullptr, NS_OK
, true);
355 tracker
->SyncNotifyProgress(FLAG_LOAD_COMPLETE
);
356 NS_ENSURE_SUCCESS(rv
, rv
);
359 image
.forget(aContainer
);
364 imgTools::DecodeImageFromChannelAsync(nsIURI
* aURI
, nsIChannel
* aChannel
,
365 imgIContainerCallback
* aCallback
,
366 imgINotificationObserver
* aObserver
) {
367 MOZ_ASSERT(NS_IsMainThread());
369 NS_ENSURE_ARG_POINTER(aURI
);
370 NS_ENSURE_ARG_POINTER(aChannel
);
371 NS_ENSURE_ARG_POINTER(aCallback
);
373 RefPtr
<ImageDecoderListener
> listener
=
374 new ImageDecoderListener(aURI
, aCallback
, aObserver
);
376 return aChannel
->AsyncOpen(listener
);
380 imgTools::DecodeImageAsync(nsIInputStream
* aInStr
, const nsACString
& aMimeType
,
381 imgIContainerCallback
* aCallback
,
382 nsIEventTarget
* aEventTarget
) {
383 MOZ_ASSERT(NS_IsMainThread());
385 NS_ENSURE_ARG_POINTER(aInStr
);
386 NS_ENSURE_ARG_POINTER(aCallback
);
387 NS_ENSURE_ARG_POINTER(aEventTarget
);
391 // Let's continuing the reading on a separate thread.
392 DecodePool
* decodePool
= DecodePool::Singleton();
393 MOZ_ASSERT(decodePool
);
395 RefPtr
<nsIEventTarget
> target
= decodePool
->GetIOEventTarget();
396 NS_ENSURE_TRUE(target
, NS_ERROR_FAILURE
);
398 // Prepare the input stream.
399 nsCOMPtr
<nsIInputStream
> stream
= aInStr
;
400 if (!NS_InputStreamIsBuffered(aInStr
)) {
401 nsCOMPtr
<nsIInputStream
> bufStream
;
402 rv
= NS_NewBufferedInputStream(getter_AddRefs(bufStream
), stream
.forget(),
404 NS_ENSURE_SUCCESS(rv
, rv
);
405 stream
= std::move(bufStream
);
408 // Create a new image container to hold the decoded data.
409 nsAutoCString
mimeType(aMimeType
);
410 RefPtr
<image::Image
> image
= ImageFactory::CreateAnonymousImage(mimeType
, 0);
413 if (image
->HasError()) {
414 return NS_ERROR_FAILURE
;
417 RefPtr
<ImageDecoderHelper
> helper
= new ImageDecoderHelper(
418 image
.forget(), stream
.forget(), target
, aCallback
, aEventTarget
);
419 rv
= target
->Dispatch(helper
.forget(), NS_DISPATCH_NORMAL
);
420 NS_ENSURE_SUCCESS(rv
, rv
);
426 * This takes a DataSourceSurface rather than a SourceSurface because some
427 * of the callers have a DataSourceSurface and we don't want to call
428 * GetDataSurface on such surfaces since that may incur a conversion to
429 * SurfaceType::DATA which we don't need.
431 static nsresult
EncodeImageData(DataSourceSurface
* aDataSurface
,
432 DataSourceSurface::ScopedMap
& aMap
,
433 const nsACString
& aMimeType
,
434 const nsAString
& aOutputOptions
,
435 nsIInputStream
** aStream
) {
436 MOZ_ASSERT(aDataSurface
->GetFormat() == SurfaceFormat::B8G8R8A8
||
437 aDataSurface
->GetFormat() == SurfaceFormat::B8G8R8X8
,
438 "We're assuming B8G8R8A8/X8");
440 // Get an image encoder for the media type
441 nsAutoCString
encoderCID("@mozilla.org/image/encoder;2?type="_ns
+ aMimeType
);
443 nsCOMPtr
<imgIEncoder
> encoder
= do_CreateInstance(encoderCID
.get());
445 return NS_IMAGELIB_ERROR_NO_ENCODER
;
448 IntSize size
= aDataSurface
->GetSize();
449 uint32_t dataLength
= aMap
.GetStride() * size
.height
;
452 nsresult rv
= encoder
->InitFromData(
453 aMap
.GetData(), dataLength
, size
.width
, size
.height
, aMap
.GetStride(),
454 imgIEncoder::INPUT_FORMAT_HOSTARGB
, aOutputOptions
);
455 NS_ENSURE_SUCCESS(rv
, rv
);
457 encoder
.forget(aStream
);
461 static nsresult
EncodeImageData(DataSourceSurface
* aDataSurface
,
462 const nsACString
& aMimeType
,
463 const nsAString
& aOutputOptions
,
464 nsIInputStream
** aStream
) {
465 DataSourceSurface::ScopedMap
map(aDataSurface
, DataSourceSurface::READ
);
466 if (!map
.IsMapped()) {
467 return NS_ERROR_FAILURE
;
470 return EncodeImageData(aDataSurface
, map
, aMimeType
, aOutputOptions
, aStream
);
474 imgTools::EncodeImage(imgIContainer
* aContainer
, const nsACString
& aMimeType
,
475 const nsAString
& aOutputOptions
,
476 nsIInputStream
** aStream
) {
477 // Use frame 0 from the image container.
478 RefPtr
<SourceSurface
> frame
= aContainer
->GetFrame(
479 imgIContainer::FRAME_FIRST
,
480 imgIContainer::FLAG_SYNC_DECODE
| imgIContainer::FLAG_ASYNC_NOTIFY
);
481 NS_ENSURE_TRUE(frame
, NS_ERROR_FAILURE
);
483 RefPtr
<DataSourceSurface
> dataSurface
;
485 if (frame
->GetFormat() == SurfaceFormat::B8G8R8A8
||
486 frame
->GetFormat() == SurfaceFormat::B8G8R8X8
) {
487 dataSurface
= frame
->GetDataSurface();
489 // Convert format to SurfaceFormat::B8G8R8A8
490 dataSurface
= gfxUtils::CopySurfaceToDataSourceSurfaceWithFormat(
491 frame
, SurfaceFormat::B8G8R8A8
);
494 NS_ENSURE_TRUE(dataSurface
, NS_ERROR_FAILURE
);
496 return EncodeImageData(dataSurface
, aMimeType
, aOutputOptions
, aStream
);
500 imgTools::EncodeScaledImage(imgIContainer
* aContainer
,
501 const nsACString
& aMimeType
, int32_t aScaledWidth
,
502 int32_t aScaledHeight
,
503 const nsAString
& aOutputOptions
,
504 nsIInputStream
** aStream
) {
505 NS_ENSURE_ARG(aScaledWidth
>= 0 && aScaledHeight
>= 0);
507 // If no scaled size is specified, we'll just encode the image at its
508 // original size (no scaling).
509 if (aScaledWidth
== 0 && aScaledHeight
== 0) {
510 return EncodeImage(aContainer
, aMimeType
, aOutputOptions
, aStream
);
513 // Retrieve the image's size.
514 int32_t imageWidth
= 0;
515 int32_t imageHeight
= 0;
516 aContainer
->GetWidth(&imageWidth
);
517 aContainer
->GetHeight(&imageHeight
);
519 // If the given width or height is zero we'll replace it with the image's
520 // original dimensions.
521 IntSize
scaledSize(aScaledWidth
== 0 ? imageWidth
: aScaledWidth
,
522 aScaledHeight
== 0 ? imageHeight
: aScaledHeight
);
524 // Use frame 0 from the image container.
525 RefPtr
<SourceSurface
> frame
= aContainer
->GetFrameAtSize(
526 scaledSize
, imgIContainer::FRAME_FIRST
,
527 imgIContainer::FLAG_HIGH_QUALITY_SCALING
|
528 imgIContainer::FLAG_SYNC_DECODE
| imgIContainer::FLAG_ASYNC_NOTIFY
);
529 NS_ENSURE_TRUE(frame
, NS_ERROR_FAILURE
);
531 // If the given surface is the right size/format, we can encode it directly.
532 if (scaledSize
== frame
->GetSize() &&
533 (frame
->GetFormat() == SurfaceFormat::B8G8R8A8
||
534 frame
->GetFormat() == SurfaceFormat::B8G8R8X8
)) {
535 RefPtr
<DataSourceSurface
> dataSurface
= frame
->GetDataSurface();
537 return EncodeImageData(dataSurface
, aMimeType
, aOutputOptions
, aStream
);
541 // Otherwise we need to scale it using a draw target.
542 RefPtr
<DataSourceSurface
> dataSurface
=
543 Factory::CreateDataSourceSurface(scaledSize
, SurfaceFormat::B8G8R8A8
);
544 if (NS_WARN_IF(!dataSurface
)) {
545 return NS_ERROR_FAILURE
;
548 DataSourceSurface::ScopedMap
map(dataSurface
, DataSourceSurface::READ_WRITE
);
549 if (!map
.IsMapped()) {
550 return NS_ERROR_FAILURE
;
553 RefPtr
<DrawTarget
> dt
= Factory::CreateDrawTargetForData(
554 BackendType::SKIA
, map
.GetData(), dataSurface
->GetSize(), map
.GetStride(),
555 SurfaceFormat::B8G8R8A8
);
557 gfxWarning() << "imgTools::EncodeImage failed in CreateDrawTargetForData";
558 return NS_ERROR_OUT_OF_MEMORY
;
561 IntSize frameSize
= frame
->GetSize();
562 dt
->DrawSurface(frame
, Rect(0, 0, scaledSize
.width
, scaledSize
.height
),
563 Rect(0, 0, frameSize
.width
, frameSize
.height
),
564 DrawSurfaceOptions(),
565 DrawOptions(1.0f
, CompositionOp::OP_SOURCE
));
567 return EncodeImageData(dataSurface
, map
, aMimeType
, aOutputOptions
, aStream
);
571 imgTools::EncodeCroppedImage(imgIContainer
* aContainer
,
572 const nsACString
& aMimeType
, int32_t aOffsetX
,
573 int32_t aOffsetY
, int32_t aWidth
, int32_t aHeight
,
574 const nsAString
& aOutputOptions
,
575 nsIInputStream
** aStream
) {
576 NS_ENSURE_ARG(aOffsetX
>= 0 && aOffsetY
>= 0 && aWidth
>= 0 && aHeight
>= 0);
578 // Offsets must be zero when no width and height are given or else we're out
580 NS_ENSURE_ARG(aWidth
+ aHeight
> 0 || aOffsetX
+ aOffsetY
== 0);
582 // If no size is specified then we'll preserve the image's original dimensions
583 // and don't need to crop.
584 if (aWidth
== 0 && aHeight
== 0) {
585 return EncodeImage(aContainer
, aMimeType
, aOutputOptions
, aStream
);
588 // Use frame 0 from the image container.
589 RefPtr
<SourceSurface
> frame
= aContainer
->GetFrame(
590 imgIContainer::FRAME_FIRST
,
591 imgIContainer::FLAG_SYNC_DECODE
| imgIContainer::FLAG_ASYNC_NOTIFY
);
592 NS_ENSURE_TRUE(frame
, NS_ERROR_FAILURE
);
594 int32_t frameWidth
= frame
->GetSize().width
;
595 int32_t frameHeight
= frame
->GetSize().height
;
597 // If the given width or height is zero we'll replace it with the image's
598 // original dimensions.
601 } else if (aHeight
== 0) {
602 aHeight
= frameHeight
;
605 // Check that the given crop rectangle is within image bounds.
606 NS_ENSURE_ARG(frameWidth
>= aOffsetX
+ aWidth
&&
607 frameHeight
>= aOffsetY
+ aHeight
);
609 RefPtr
<DataSourceSurface
> dataSurface
= Factory::CreateDataSourceSurface(
610 IntSize(aWidth
, aHeight
), SurfaceFormat::B8G8R8A8
,
612 if (NS_WARN_IF(!dataSurface
)) {
613 return NS_ERROR_FAILURE
;
616 DataSourceSurface::ScopedMap
map(dataSurface
, DataSourceSurface::READ_WRITE
);
617 if (!map
.IsMapped()) {
618 return NS_ERROR_FAILURE
;
621 RefPtr
<DrawTarget
> dt
= Factory::CreateDrawTargetForData(
622 BackendType::SKIA
, map
.GetData(), dataSurface
->GetSize(), map
.GetStride(),
623 SurfaceFormat::B8G8R8A8
);
626 << "imgTools::EncodeCroppedImage failed in CreateDrawTargetForData";
627 return NS_ERROR_OUT_OF_MEMORY
;
629 dt
->CopySurface(frame
, IntRect(aOffsetX
, aOffsetY
, aWidth
, aHeight
),
632 return EncodeImageData(dataSurface
, map
, aMimeType
, aOutputOptions
, aStream
);
636 imgTools::CreateScriptedObserver(imgIScriptedNotificationObserver
* aInner
,
637 imgINotificationObserver
** aObserver
) {
638 NS_ADDREF(*aObserver
= new ScriptedNotificationObserver(aInner
));
643 imgTools::GetImgLoaderForDocument(dom::Document
* aDoc
, imgILoader
** aLoader
) {
644 NS_IF_ADDREF(*aLoader
= nsContentUtils::GetImgLoaderForDocument(aDoc
));
649 imgTools::GetImgCacheForDocument(dom::Document
* aDoc
, imgICache
** aCache
) {
650 nsCOMPtr
<imgILoader
> loader
;
651 nsresult rv
= GetImgLoaderForDocument(aDoc
, getter_AddRefs(loader
));
652 NS_ENSURE_SUCCESS(rv
, rv
);
653 return CallQueryInterface(loader
, aCache
);
657 } // namespace mozilla