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/. */
7 #include "imgRequest.h"
8 #include "ImageLogging.h"
10 #include "imgLoader.h"
11 #include "imgRequestProxy.h"
12 #include "DecodePool.h"
13 #include "ProgressTracker.h"
14 #include "ImageFactory.h"
16 #include "MultipartImage.h"
17 #include "RasterImage.h"
19 #include "nsIChannel.h"
20 #include "nsICachingChannel.h"
21 #include "nsIThreadRetargetableRequest.h"
22 #include "nsIInputStream.h"
23 #include "nsIMultiPartChannel.h"
24 #include "nsIHttpChannel.h"
25 #include "nsIApplicationCache.h"
26 #include "nsIApplicationCacheChannel.h"
27 #include "nsMimeTypes.h"
29 #include "nsIInterfaceRequestorUtils.h"
30 #include "nsISupportsPrimitives.h"
31 #include "nsIScriptSecurityManager.h"
32 #include "nsContentUtils.h"
34 #include "nsICacheEntry.h"
36 #include "plstr.h" // PL_strcasestr(...)
37 #include "nsNetUtil.h"
38 #include "nsIProtocolHandler.h"
39 #include "imgIRequest.h"
41 using namespace mozilla
;
42 using namespace mozilla::image
;
44 #if defined(PR_LOGGING)
48 static PRLogModuleInfo
* sImgLog
;
50 sImgLog
= PR_NewLogModule("imgRequest");
53 #define LOG_TEST(level) (GetImgLog() && PR_LOG_TEST(GetImgLog(), (level)))
55 #define LOG_TEST(level) false
58 NS_IMPL_ISUPPORTS(imgRequest
,
59 nsIStreamListener
, nsIRequestObserver
,
60 nsIThreadRetargetableStreamListener
,
62 nsIInterfaceRequestor
,
63 nsIAsyncVerifyRedirectCallback
)
65 imgRequest::imgRequest(imgLoader
* aLoader
)
67 , mProgressTracker(new ProgressTracker())
69 , mMutex("imgRequest")
71 , mCORSMode(imgIRequest::CORS_NONE
)
72 , mReferrerPolicy(mozilla::net::RP_Default
)
73 , mImageErrorCode(NS_OK
)
74 , mDecodeRequested(false)
75 , mIsMultiPartChannel(false)
78 , mNewPartPending(false)
81 imgRequest::~imgRequest()
84 mLoader
->RemoveFromUncachedImages(this);
89 LOG_FUNC_WITH_PARAM(GetImgLog(), "imgRequest::~imgRequest()", "keyuri", spec
.get());
91 LOG_FUNC(GetImgLog(), "imgRequest::~imgRequest()");
94 nsresult
imgRequest::Init(nsIURI
*aURI
,
98 imgCacheEntry
*aCacheEntry
,
100 nsIPrincipal
* aLoadingPrincipal
,
102 ReferrerPolicy aReferrerPolicy
)
104 MOZ_ASSERT(NS_IsMainThread(), "Cannot use nsIURI off main thread!");
106 LOG_FUNC(GetImgLog(), "imgRequest::Init");
108 MOZ_ASSERT(!mImage
, "Multiple calls to init");
109 MOZ_ASSERT(aURI
, "No uri");
110 MOZ_ASSERT(aCurrentURI
, "No current uri");
111 MOZ_ASSERT(aRequest
, "No request");
112 MOZ_ASSERT(aChannel
, "No channel");
114 mProperties
= do_CreateInstance("@mozilla.org/properties;1");
116 // Use ImageURL to ensure access to URI data off main thread.
117 mURI
= new ImageURL(aURI
);
118 mCurrentURI
= aCurrentURI
;
121 mTimedChannel
= do_QueryInterface(mChannel
);
123 mLoadingPrincipal
= aLoadingPrincipal
;
124 mCORSMode
= aCORSMode
;
125 mReferrerPolicy
= aReferrerPolicy
;
127 mChannel
->GetNotificationCallbacks(getter_AddRefs(mPrevChannelSink
));
129 NS_ASSERTION(mPrevChannelSink
!= this,
130 "Initializing with a channel that already calls back to us!");
132 mChannel
->SetNotificationCallbacks(this);
134 mCacheEntry
= aCacheEntry
;
141 void imgRequest::ClearLoader() {
145 already_AddRefed
<Image
>
146 imgRequest::GetImage()
148 MutexAutoLock
lock(mMutex
);
150 nsRefPtr
<Image
> image
= mImage
;
151 return image
.forget();
154 already_AddRefed
<ProgressTracker
>
155 imgRequest::GetProgressTracker()
157 MutexAutoLock
lock(mMutex
);
160 MOZ_ASSERT(!mProgressTracker
,
161 "Should have given mProgressTracker to mImage");
162 return mImage
->GetProgressTracker();
164 MOZ_ASSERT(mProgressTracker
,
165 "Should have mProgressTracker until we create mImage");
166 nsRefPtr
<ProgressTracker
> progressTracker
= mProgressTracker
;
167 MOZ_ASSERT(progressTracker
);
168 return progressTracker
.forget();
173 imgRequest::SetImage(Image
* aImage
)
175 MutexAutoLock
lock(mMutex
);
180 imgRequest::SetProgressTracker(ProgressTracker
* aProgressTracker
)
182 MutexAutoLock
lock(mMutex
);
183 mProgressTracker
= aProgressTracker
;
186 void imgRequest::SetCacheEntry(imgCacheEntry
*entry
)
191 bool imgRequest::HasCacheEntry() const
193 return mCacheEntry
!= nullptr;
196 void imgRequest::ResetCacheEntry()
198 if (HasCacheEntry()) {
199 mCacheEntry
->SetDataSize(0);
203 void imgRequest::AddProxy(imgRequestProxy
*proxy
)
205 NS_PRECONDITION(proxy
, "null imgRequestProxy passed in");
206 LOG_SCOPE_WITH_PARAM(GetImgLog(), "imgRequest::AddProxy", "proxy", proxy
);
208 // If we're empty before adding, we have to tell the loader we now have
210 nsRefPtr
<ProgressTracker
> progressTracker
= GetProgressTracker();
211 if (progressTracker
->ObserverCount() == 0) {
212 MOZ_ASSERT(mURI
, "Trying to SetHasProxies without key uri.");
214 mLoader
->SetHasProxies(this);
218 progressTracker
->AddObserver(proxy
);
221 nsresult
imgRequest::RemoveProxy(imgRequestProxy
*proxy
, nsresult aStatus
)
223 LOG_SCOPE_WITH_PARAM(GetImgLog(), "imgRequest::RemoveProxy", "proxy", proxy
);
225 // This will remove our animation consumers, so after removing
226 // this proxy, we don't end up without proxies with observers, but still
227 // have animation consumers.
228 proxy
->ClearAnimationConsumers();
230 // Let the status tracker do its thing before we potentially call Cancel()
231 // below, because Cancel() may result in OnStopRequest being called back
232 // before Cancel() returns, leaving the image in a different state then the
233 // one it was in at this point.
234 nsRefPtr
<ProgressTracker
> progressTracker
= GetProgressTracker();
235 if (!progressTracker
->RemoveObserver(proxy
))
238 if (progressTracker
->ObserverCount() == 0) {
239 // If we have no observers, there's nothing holding us alive. If we haven't
240 // been cancelled and thus removed from the cache, tell the image loader so
241 // we can be evicted from the cache.
243 MOZ_ASSERT(mURI
, "Removing last observer without key uri.");
246 mLoader
->SetHasNoProxies(this, mCacheEntry
);
249 #if defined(PR_LOGGING)
253 LOG_MSG_WITH_PARAM(GetImgLog(), "imgRequest::RemoveProxy no cache entry", "uri", spec
.get());
257 /* If |aStatus| is a failure code, then cancel the load if it is still in progress.
258 Otherwise, let the load continue, keeping 'this' in the cache with no observers.
259 This way, if a proxy is destroyed without calling cancel on it, it won't leak
260 and won't leave a bad pointer in the observer list.
262 if (!(progressTracker
->GetProgress() & FLAG_LAST_PART_COMPLETE
) &&
263 NS_FAILED(aStatus
)) {
264 LOG_MSG(GetImgLog(), "imgRequest::RemoveProxy", "load in progress. canceling");
266 this->Cancel(NS_BINDING_ABORTED
);
269 /* break the cycle from the cache entry. */
270 mCacheEntry
= nullptr;
273 // If a proxy is removed for a reason other than its owner being
274 // changed, remove the proxy from the loadgroup.
275 if (aStatus
!= NS_IMAGELIB_CHANGING_OWNER
)
276 proxy
->RemoveFromLoadGroup(true);
281 void imgRequest::CancelAndAbort(nsresult aStatus
)
283 LOG_SCOPE(GetImgLog(), "imgRequest::CancelAndAbort");
287 // It's possible for the channel to fail to open after we've set our
288 // notification callbacks. In that case, make sure to break the cycle between
289 // the channel and us, because it won't.
291 mChannel
->SetNotificationCallbacks(mPrevChannelSink
);
292 mPrevChannelSink
= nullptr;
296 class imgRequestMainThreadCancel
: public nsRunnable
299 imgRequestMainThreadCancel(imgRequest
*aImgRequest
, nsresult aStatus
)
300 : mImgRequest(aImgRequest
)
303 MOZ_ASSERT(!NS_IsMainThread(), "Create me off main thread only!");
304 MOZ_ASSERT(aImgRequest
);
309 MOZ_ASSERT(NS_IsMainThread(), "I should be running on the main thread!");
310 mImgRequest
->ContinueCancel(mStatus
);
314 nsRefPtr
<imgRequest
> mImgRequest
;
318 void imgRequest::Cancel(nsresult aStatus
)
320 /* The Cancel() method here should only be called by this class. */
321 LOG_SCOPE(GetImgLog(), "imgRequest::Cancel");
323 if (NS_IsMainThread()) {
324 ContinueCancel(aStatus
);
326 NS_DispatchToMainThread(new imgRequestMainThreadCancel(this, aStatus
));
330 void imgRequest::ContinueCancel(nsresult aStatus
)
332 MOZ_ASSERT(NS_IsMainThread());
334 nsRefPtr
<ProgressTracker
> progressTracker
= GetProgressTracker();
335 progressTracker
->SyncNotifyProgress(FLAG_HAS_ERROR
| FLAG_ONLOAD_UNBLOCKED
);
339 if (mRequest
&& !(progressTracker
->GetProgress() & FLAG_LAST_PART_COMPLETE
)) {
340 mRequest
->Cancel(aStatus
);
344 class imgRequestMainThreadEvict
: public nsRunnable
347 explicit imgRequestMainThreadEvict(imgRequest
*aImgRequest
)
348 : mImgRequest(aImgRequest
)
350 MOZ_ASSERT(!NS_IsMainThread(), "Create me off main thread only!");
351 MOZ_ASSERT(aImgRequest
);
356 MOZ_ASSERT(NS_IsMainThread(), "I should be running on the main thread!");
357 mImgRequest
->ContinueEvict();
361 nsRefPtr
<imgRequest
> mImgRequest
;
364 // EvictFromCache() is written to allowed to get called from any thread
365 void imgRequest::EvictFromCache()
367 /* The EvictFromCache() method here should only be called by this class. */
368 LOG_SCOPE(GetImgLog(), "imgRequest::EvictFromCache");
370 if (NS_IsMainThread()) {
373 NS_DispatchToMainThread(new imgRequestMainThreadEvict(this));
377 // Helper-method used by EvictFromCache()
378 void imgRequest::ContinueEvict()
380 MOZ_ASSERT(NS_IsMainThread());
385 nsresult
imgRequest::GetURI(ImageURL
**aURI
)
389 LOG_FUNC(GetImgLog(), "imgRequest::GetURI");
397 return NS_ERROR_FAILURE
;
400 nsresult
imgRequest::GetCurrentURI(nsIURI
**aURI
)
404 LOG_FUNC(GetImgLog(), "imgRequest::GetCurrentURI");
412 return NS_ERROR_FAILURE
;
415 nsresult
imgRequest::GetImageErrorCode()
417 return mImageErrorCode
;
420 nsresult
imgRequest::GetSecurityInfo(nsISupports
**aSecurityInfo
)
422 LOG_FUNC(GetImgLog(), "imgRequest::GetSecurityInfo");
424 // Missing security info means this is not a security load
425 // i.e. it is not an error when security info is missing
426 NS_IF_ADDREF(*aSecurityInfo
= mSecurityInfo
);
430 void imgRequest::RemoveFromCache()
432 LOG_SCOPE(GetImgLog(), "imgRequest::RemoveFromCache");
434 if (mIsInCache
&& mLoader
) {
435 // mCacheEntry is nulled out when we have no more observers.
437 mLoader
->RemoveFromCache(mCacheEntry
);
439 mLoader
->RemoveFromCache(mURI
);
443 mCacheEntry
= nullptr;
446 bool imgRequest::HasConsumers()
448 nsRefPtr
<ProgressTracker
> progressTracker
= GetProgressTracker();
449 return progressTracker
&& progressTracker
->ObserverCount() > 0;
452 int32_t imgRequest::Priority() const
454 int32_t priority
= nsISupportsPriority::PRIORITY_NORMAL
;
455 nsCOMPtr
<nsISupportsPriority
> p
= do_QueryInterface(mRequest
);
457 p
->GetPriority(&priority
);
461 void imgRequest::AdjustPriority(imgRequestProxy
*proxy
, int32_t delta
)
463 // only the first proxy is allowed to modify the priority of this image load.
465 // XXX(darin): this is probably not the most optimal algorithm as we may want
466 // to increase the priority of requests that have a lot of proxies. the key
467 // concern though is that image loads remain lower priority than other pieces
468 // of content such as link clicks, CSS, and JS.
470 nsRefPtr
<ProgressTracker
> progressTracker
= GetProgressTracker();
471 if (!progressTracker
->FirstObserverIs(proxy
))
474 nsCOMPtr
<nsISupportsPriority
> p
= do_QueryInterface(mChannel
);
476 p
->AdjustPriority(delta
);
479 void imgRequest::SetIsInCache(bool incache
)
481 LOG_FUNC_WITH_PARAM(GetImgLog(), "imgRequest::SetIsCacheable", "incache", incache
);
482 mIsInCache
= incache
;
485 void imgRequest::UpdateCacheEntrySize()
488 nsRefPtr
<Image
> image
= GetImage();
489 size_t size
= image
->SizeOfSourceWithComputedFallback(moz_malloc_size_of
);
490 mCacheEntry
->SetDataSize(size
);
494 void imgRequest::SetCacheValidation(imgCacheEntry
* aCacheEntry
, nsIRequest
* aRequest
)
496 /* get the expires info */
498 nsCOMPtr
<nsICachingChannel
> cacheChannel(do_QueryInterface(aRequest
));
500 nsCOMPtr
<nsISupports
> cacheToken
;
501 cacheChannel
->GetCacheToken(getter_AddRefs(cacheToken
));
503 nsCOMPtr
<nsICacheEntry
> entryDesc(do_QueryInterface(cacheToken
));
506 /* get the expiration time from the caching channel's token */
507 entryDesc
->GetExpirationTime(&expiration
);
509 // Expiration time defaults to 0. We set the expiration time on our
510 // entry if it hasn't been set yet.
511 if (aCacheEntry
->GetExpiryTime() == 0)
512 aCacheEntry
->SetExpiryTime(expiration
);
517 // Determine whether the cache entry must be revalidated when we try to use it.
518 // Currently, only HTTP specifies this information...
519 nsCOMPtr
<nsIHttpChannel
> httpChannel(do_QueryInterface(aRequest
));
521 bool bMustRevalidate
= false;
523 httpChannel
->IsNoStoreResponse(&bMustRevalidate
);
525 if (!bMustRevalidate
) {
526 httpChannel
->IsNoCacheResponse(&bMustRevalidate
);
529 if (!bMustRevalidate
) {
530 nsAutoCString cacheHeader
;
532 httpChannel
->GetResponseHeader(NS_LITERAL_CSTRING("Cache-Control"),
534 if (PL_strcasestr(cacheHeader
.get(), "must-revalidate")) {
535 bMustRevalidate
= true;
539 // Cache entries default to not needing to validate. We ensure that
540 // multiple calls to this function don't override an earlier decision to
541 // validate by making validation a one-way decision.
543 aCacheEntry
->SetMustValidate(bMustRevalidate
);
546 // We always need to validate file URIs.
547 nsCOMPtr
<nsIChannel
> channel
= do_QueryInterface(aRequest
);
549 nsCOMPtr
<nsIURI
> uri
;
550 channel
->GetURI(getter_AddRefs(uri
));
552 uri
->SchemeIs("file", &isfile
);
554 aCacheEntry
->SetMustValidate(isfile
);
561 already_AddRefed
<nsIApplicationCache
>
562 GetApplicationCache(nsIRequest
* aRequest
)
566 nsCOMPtr
<nsIApplicationCacheChannel
> appCacheChan
= do_QueryInterface(aRequest
);
572 rv
= appCacheChan
->GetLoadedFromApplicationCache(&fromAppCache
);
573 NS_ENSURE_SUCCESS(rv
, nullptr);
579 nsCOMPtr
<nsIApplicationCache
> appCache
;
580 rv
= appCacheChan
->GetApplicationCache(getter_AddRefs(appCache
));
581 NS_ENSURE_SUCCESS(rv
, nullptr);
583 return appCache
.forget();
589 imgRequest::CacheChanged(nsIRequest
* aNewRequest
)
591 nsCOMPtr
<nsIApplicationCache
> newAppCache
= GetApplicationCache(aNewRequest
);
593 // Application cache not involved at all or the same app cache involved
594 // in both of the loads (original and new).
595 if (newAppCache
== mApplicationCache
)
598 // In a rare case it may happen that two objects still refer
599 // the same application cache version.
600 if (newAppCache
&& mApplicationCache
) {
603 nsAutoCString oldAppCacheClientId
, newAppCacheClientId
;
604 rv
= mApplicationCache
->GetClientID(oldAppCacheClientId
);
605 NS_ENSURE_SUCCESS(rv
, true);
606 rv
= newAppCache
->GetClientID(newAppCacheClientId
);
607 NS_ENSURE_SUCCESS(rv
, true);
609 if (oldAppCacheClientId
== newAppCacheClientId
)
613 // When we get here, app caches differ or app cache is involved
614 // just in one of the loads what we also consider as a change
615 // in a loading cache.
620 imgRequest::LockImage()
622 nsRefPtr
<Image
> image
= GetImage();
623 return image
->LockImage();
627 imgRequest::UnlockImage()
629 nsRefPtr
<Image
> image
= GetImage();
630 return image
->UnlockImage();
634 imgRequest::RequestDecode()
636 // If we've initialized our image, we can request a decode.
637 nsRefPtr
<Image
> image
= GetImage();
639 return image
->RequestDecode();
642 // Otherwise, flag to do it when we get the image
643 mDecodeRequested
= true;
649 imgRequest::StartDecoding()
651 // If we've initialized our image, we can request a decode.
652 nsRefPtr
<Image
> image
= GetImage();
654 return image
->StartDecoding();
657 // Otherwise, flag to do it when we get the image
658 mDecodeRequested
= true;
663 /** nsIRequestObserver methods **/
665 /* void onStartRequest (in nsIRequest request, in nsISupports ctxt); */
666 NS_IMETHODIMP
imgRequest::OnStartRequest(nsIRequest
*aRequest
, nsISupports
*ctxt
)
668 LOG_SCOPE(GetImgLog(), "imgRequest::OnStartRequest");
670 mNewPartPending
= true;
672 // Figure out if we're multipart.
673 nsCOMPtr
<nsIMultiPartChannel
> mpchan(do_QueryInterface(aRequest
));
674 nsRefPtr
<ProgressTracker
> progressTracker
= GetProgressTracker();
676 mIsMultiPartChannel
= true;
678 MOZ_ASSERT(!mIsMultiPartChannel
, "Something went wrong");
681 // If we're not multipart, we shouldn't have an image yet
682 nsRefPtr
<Image
> image
= GetImage();
683 if (image
&& !mIsMultiPartChannel
) {
684 MOZ_ASSERT_UNREACHABLE("Already have an image for a non-multipart request");
685 Cancel(NS_IMAGELIB_ERROR_FAILURE
);
686 return NS_ERROR_FAILURE
;
690 * If mRequest is null here, then we need to set it so that we'll be able to
691 * cancel it if our Cancel() method is called. Note that this can only
692 * happen for multipart channels. We could simply not null out mRequest for
693 * non-last parts, if GetIsLastPart() were reliable, but it's not. See
694 * https://bugzilla.mozilla.org/show_bug.cgi?id=339610
698 "We should have an mRequest here unless we're multipart");
699 nsCOMPtr
<nsIChannel
> chan
;
700 mpchan
->GetBaseChannel(getter_AddRefs(chan
));
704 nsCOMPtr
<nsIChannel
> channel(do_QueryInterface(aRequest
));
706 channel
->GetSecurityInfo(getter_AddRefs(mSecurityInfo
));
708 /* Get our principal */
709 nsCOMPtr
<nsIChannel
> chan(do_QueryInterface(aRequest
));
711 nsCOMPtr
<nsIScriptSecurityManager
> secMan
= nsContentUtils::GetSecurityManager();
713 nsresult rv
= secMan
->GetChannelResultPrincipal(chan
,
714 getter_AddRefs(mPrincipal
));
721 SetCacheValidation(mCacheEntry
, aRequest
);
723 mApplicationCache
= GetApplicationCache(aRequest
);
725 // Shouldn't we be dead already if this gets hit? Probably multipart/x-mixed-replace...
726 if (progressTracker
->ObserverCount() == 0) {
727 this->Cancel(NS_IMAGELIB_ERROR_FAILURE
);
730 // Try to retarget OnDataAvailable to a decode thread.
731 nsCOMPtr
<nsIHttpChannel
> httpChannel
= do_QueryInterface(aRequest
);
732 nsCOMPtr
<nsIThreadRetargetableRequest
> retargetable
=
733 do_QueryInterface(aRequest
);
734 if (httpChannel
&& retargetable
) {
735 nsAutoCString mimeType
;
736 nsresult rv
= httpChannel
->GetContentType(mimeType
);
737 if (NS_SUCCEEDED(rv
) && !mimeType
.EqualsLiteral(IMAGE_SVG_XML
)) {
738 // Retarget OnDataAvailable to the DecodePool's IO thread.
739 nsCOMPtr
<nsIEventTarget
> target
=
740 DecodePool::Singleton()->GetIOEventTarget();
741 rv
= retargetable
->RetargetDeliveryTo(target
);
743 PR_LOG(GetImgLog(), PR_LOG_WARNING
,
744 ("[this=%p] imgRequest::OnStartRequest -- "
745 "RetargetDeliveryTo rv %d=%s\n",
746 this, rv
, NS_SUCCEEDED(rv
) ? "succeeded" : "failed"));
752 /* void onStopRequest (in nsIRequest request, in nsISupports ctxt, in nsresult status); */
753 NS_IMETHODIMP
imgRequest::OnStopRequest(nsIRequest
*aRequest
, nsISupports
*ctxt
, nsresult status
)
755 LOG_FUNC(GetImgLog(), "imgRequest::OnStopRequest");
756 MOZ_ASSERT(NS_IsMainThread(), "Can't send notifications off-main-thread");
758 // XXXldb What if this is a non-last part of a multipart request?
759 // xxx before we release our reference to mRequest, lets
760 // save the last status that we saw so that the
761 // imgRequestProxy will have access to it.
763 mRequest
= nullptr; // we no longer need the request
766 // stop holding a ref to the channel, since we don't need it anymore
768 mChannel
->SetNotificationCallbacks(mPrevChannelSink
);
769 mPrevChannelSink
= nullptr;
773 bool lastPart
= true;
774 nsCOMPtr
<nsIMultiPartChannel
> mpchan(do_QueryInterface(aRequest
));
776 mpchan
->GetIsLastPart(&lastPart
);
778 bool isPartial
= false;
779 nsRefPtr
<Image
> image
= GetImage();
780 if (image
&& (status
== NS_ERROR_NET_PARTIAL_TRANSFER
)) {
782 status
= NS_OK
; // fake happy face
785 // Tell the image that it has all of the source data. Note that this can
786 // trigger a failure, since the image might be waiting for more non-optional
787 // data and this is the point where we break the news that it's not coming.
789 nsresult rv
= image
->OnImageDataComplete(aRequest
, ctxt
, status
, lastPart
);
791 // If we got an error in the OnImageDataComplete() call, we don't want to
792 // proceed as if nothing bad happened. However, we also want to give
793 // precedence to failure status codes from necko, since presumably they're
795 if (NS_FAILED(rv
) && NS_SUCCEEDED(status
))
799 // If the request went through, update the cache entry size. Otherwise,
800 // cancel the request, which removes us from the cache.
801 if (image
&& NS_SUCCEEDED(status
) && !isPartial
) {
802 // We update the cache entry size here because this is where we finish
803 // loading compressed source data, which is part of our size calculus.
804 UpdateCacheEntrySize();
806 else if (isPartial
) {
807 // Remove the partial image from the cache.
808 this->EvictFromCache();
811 mImageErrorCode
= status
;
813 // if the error isn't "just" a partial transfer
814 // stops animations, removes from cache
815 this->Cancel(status
);
819 // We have to fire the OnStopRequest notifications ourselves because there's
820 // no image capable of doing so.
822 LoadCompleteProgress(lastPart
, /* aError = */ false, status
);
824 nsRefPtr
<ProgressTracker
> progressTracker
= GetProgressTracker();
825 progressTracker
->SyncNotifyProgress(progress
);
828 mTimedChannel
= nullptr;
832 struct mimetype_closure
837 /* prototype for these defined below */
838 static NS_METHOD
sniff_mimetype_callback(nsIInputStream
* in
, void* closure
, const char* fromRawSegment
,
839 uint32_t toOffset
, uint32_t count
, uint32_t *writeCount
);
841 /** nsThreadRetargetableStreamListener methods **/
843 imgRequest::CheckListenerChain()
845 // TODO Might need more checking here.
846 NS_ASSERTION(NS_IsMainThread(), "Should be on the main thread!");
850 /** nsIStreamListener methods **/
852 /* void onDataAvailable (in nsIRequest request, in nsISupports ctxt, in nsIInputStream inStr, in unsigned long long sourceOffset, in unsigned long count); */
854 imgRequest::OnDataAvailable(nsIRequest
*aRequest
, nsISupports
*ctxt
,
855 nsIInputStream
*inStr
, uint64_t sourceOffset
,
858 LOG_SCOPE_WITH_PARAM(GetImgLog(), "imgRequest::OnDataAvailable", "count", count
);
860 NS_ASSERTION(aRequest
, "imgRequest::OnDataAvailable -- no request!");
864 nsRefPtr
<Image
> image
= GetImage();
866 if (mNewPartPending
) {
867 LOG_SCOPE(GetImgLog(), "imgRequest::OnDataAvailable |New part; finding MIME type|");
869 mNewPartPending
= false;
871 mimetype_closure closure
;
872 nsAutoCString newType
;
873 closure
.newType
= &newType
;
875 /* look at the first few bytes and see if we can tell what the data is from that
876 * since servers tend to lie. :(
879 inStr
->ReadSegments(sniff_mimetype_callback
, &closure
, count
, &out
);
881 nsCOMPtr
<nsIChannel
> chan(do_QueryInterface(aRequest
));
882 if (newType
.IsEmpty()) {
883 LOG_SCOPE(GetImgLog(), "imgRequest::OnDataAvailable |sniffing of mimetype failed|");
885 rv
= NS_ERROR_FAILURE
;
887 rv
= chan
->GetContentType(newType
);
891 PR_LOG(GetImgLog(), PR_LOG_ERROR
,
892 ("[this=%p] imgRequest::OnDataAvailable -- Content type unavailable from the channel\n",
895 this->Cancel(NS_IMAGELIB_ERROR_FAILURE
);
897 return NS_BINDING_ABORTED
;
900 LOG_MSG(GetImgLog(), "imgRequest::OnDataAvailable", "Got content type from the channel");
903 mContentType
= newType
;
905 bool firstPart
= !image
;
907 LOG_MSG_WITH_PARAM(GetImgLog(), "imgRequest::OnDataAvailable", "content type", mContentType
.get());
909 // XXX If server lied about mimetype and it's SVG, we may need to copy
910 // the data and dispatch back to the main thread, AND tell the channel to
911 // dispatch there in the future.
913 nsRefPtr
<ProgressTracker
> progressTracker
;
915 MutexAutoLock
lock(mMutex
);
916 progressTracker
= mProgressTracker
;
919 // Create the new image and give it ownership of our ProgressTracker.
920 if (mIsMultiPartChannel
) {
921 // Create the ProgressTracker and image for this part.
922 nsRefPtr
<ProgressTracker
> partProgressTracker
= new ProgressTracker();
923 nsRefPtr
<Image
> partImage
=
924 ImageFactory::CreateImage(aRequest
, partProgressTracker
, mContentType
,
925 mURI
, /* aIsMultipart = */ true,
926 static_cast<uint32_t>(mInnerWindowId
));
929 // First part for a multipart channel. Create the MultipartImage wrapper.
930 MOZ_ASSERT(progressTracker
, "Shouldn't have given away tracker yet");
931 image
= new MultipartImage(partImage
, progressTracker
);
933 progressTracker
= nullptr;
934 SetProgressTracker(nullptr);
936 // Transition to the new part.
937 static_cast<MultipartImage
*>(image
.get())->BeginTransitionToPart(partImage
);
940 MOZ_ASSERT(!image
, "New part for non-multipart channel?");
941 MOZ_ASSERT(progressTracker
, "Shouldn't have given away tracker yet");
943 // Create an image using our progress tracker.
945 ImageFactory::CreateImage(aRequest
, progressTracker
, mContentType
,
946 mURI
, /* aIsMultipart = */ false,
947 static_cast<uint32_t>(mInnerWindowId
));
949 progressTracker
= nullptr;
950 SetProgressTracker(nullptr);
954 // Notify listeners that we have an image.
955 nsRefPtr
<ProgressTracker
> progressTracker
= GetProgressTracker();
956 progressTracker
->OnImageAvailable();
957 MOZ_ASSERT(progressTracker
->HasImage());
960 if (image
->HasError() && !mIsMultiPartChannel
) { // Probably bad mimetype
961 // We allow multipart images to fail to initialize without cancelling the
962 // load because subsequent images might be fine; thus only single part
963 // images end up here.
964 this->Cancel(NS_IMAGELIB_ERROR_FAILURE
);
965 return NS_BINDING_ABORTED
;
968 MOZ_ASSERT(!progressTracker
, "Should've given tracker to image");
969 MOZ_ASSERT(image
, "Should have image");
971 if (mDecodeRequested
) {
972 image
->StartDecoding();
976 // Notify the image that it has new data.
977 rv
= image
->OnImageDataAvailable(aRequest
, ctxt
, inStr
, sourceOffset
, count
);
980 PR_LOG(GetImgLog(), PR_LOG_WARNING
,
981 ("[this=%p] imgRequest::OnDataAvailable -- "
982 "copy to RasterImage failed\n", this));
983 this->Cancel(NS_IMAGELIB_ERROR_FAILURE
);
984 return NS_BINDING_ABORTED
;
990 class SetPropertiesEvent
: public nsRunnable
993 SetPropertiesEvent(imgRequest
* aImgRequest
, nsIChannel
* aChan
)
994 : mImgRequest(aImgRequest
)
997 MOZ_ASSERT(!NS_IsMainThread(), "Should be created off the main thread");
998 MOZ_ASSERT(aImgRequest
, "aImgRequest cannot be null");
1002 MOZ_ASSERT(NS_IsMainThread(), "Should run on the main thread only");
1003 MOZ_ASSERT(mImgRequest
, "mImgRequest cannot be null");
1004 mImgRequest
->SetProperties(mChan
);
1008 nsRefPtr
<imgRequest
> mImgRequest
;
1009 nsCOMPtr
<nsIChannel
> mChan
;
1013 imgRequest::SetProperties(nsIChannel
* aChan
)
1015 // Force execution on main thread since some property objects are non
1017 if (!NS_IsMainThread()) {
1018 NS_DispatchToMainThread(new SetPropertiesEvent(this, aChan
));
1021 /* set our mimetype as a property */
1022 nsCOMPtr
<nsISupportsCString
> contentType(do_CreateInstance("@mozilla.org/supports-cstring;1"));
1024 contentType
->SetData(mContentType
);
1025 mProperties
->Set("type", contentType
);
1028 /* set our content disposition as a property */
1029 nsAutoCString disposition
;
1031 aChan
->GetContentDispositionHeader(disposition
);
1033 if (!disposition
.IsEmpty()) {
1034 nsCOMPtr
<nsISupportsCString
> contentDisposition(do_CreateInstance("@mozilla.org/supports-cstring;1"));
1035 if (contentDisposition
) {
1036 contentDisposition
->SetData(disposition
);
1037 mProperties
->Set("content-disposition", contentDisposition
);
1042 static NS_METHOD
sniff_mimetype_callback(nsIInputStream
* in
,
1044 const char* fromRawSegment
,
1047 uint32_t *writeCount
)
1049 mimetype_closure
* closure
= static_cast<mimetype_closure
*>(data
);
1051 NS_ASSERTION(closure
, "closure is null!");
1054 imgLoader::GetMimeTypeFromContent(fromRawSegment
, count
, *closure
->newType
);
1057 return NS_ERROR_FAILURE
;
1061 /** nsIInterfaceRequestor methods **/
1064 imgRequest::GetInterface(const nsIID
& aIID
, void **aResult
)
1066 if (!mPrevChannelSink
|| aIID
.Equals(NS_GET_IID(nsIChannelEventSink
)))
1067 return QueryInterface(aIID
, aResult
);
1069 NS_ASSERTION(mPrevChannelSink
!= this,
1070 "Infinite recursion - don't keep track of channel sinks that are us!");
1071 return mPrevChannelSink
->GetInterface(aIID
, aResult
);
1074 /** nsIChannelEventSink methods **/
1076 imgRequest::AsyncOnChannelRedirect(nsIChannel
*oldChannel
,
1077 nsIChannel
*newChannel
, uint32_t flags
,
1078 nsIAsyncVerifyRedirectCallback
*callback
)
1080 NS_ASSERTION(mRequest
&& mChannel
, "Got a channel redirect after we nulled out mRequest!");
1081 NS_ASSERTION(mChannel
== oldChannel
, "Got a channel redirect for an unknown channel!");
1082 NS_ASSERTION(newChannel
, "Got a redirect to a NULL channel!");
1084 SetCacheValidation(mCacheEntry
, oldChannel
);
1086 // Prepare for callback
1087 mRedirectCallback
= callback
;
1088 mNewRedirectChannel
= newChannel
;
1090 nsCOMPtr
<nsIChannelEventSink
> sink(do_GetInterface(mPrevChannelSink
));
1092 nsresult rv
= sink
->AsyncOnChannelRedirect(oldChannel
, newChannel
, flags
,
1094 if (NS_FAILED(rv
)) {
1095 mRedirectCallback
= nullptr;
1096 mNewRedirectChannel
= nullptr;
1101 (void) OnRedirectVerifyCallback(NS_OK
);
1106 imgRequest::OnRedirectVerifyCallback(nsresult result
)
1108 NS_ASSERTION(mRedirectCallback
, "mRedirectCallback not set in callback");
1109 NS_ASSERTION(mNewRedirectChannel
, "mNewRedirectChannel not set in callback");
1111 if (NS_FAILED(result
)) {
1112 mRedirectCallback
->OnRedirectVerifyCallback(result
);
1113 mRedirectCallback
= nullptr;
1114 mNewRedirectChannel
= nullptr;
1118 mChannel
= mNewRedirectChannel
;
1119 mTimedChannel
= do_QueryInterface(mChannel
);
1120 mNewRedirectChannel
= nullptr;
1122 if (LOG_TEST(PR_LOG_DEBUG
)) {
1125 mCurrentURI
->GetSpec(spec
);
1126 LOG_MSG_WITH_PARAM(GetImgLog(), "imgRequest::OnChannelRedirect", "old", spec
.get());
1129 // make sure we have a protocol that returns data rather than opens
1130 // an external application, e.g. mailto:
1131 mChannel
->GetURI(getter_AddRefs(mCurrentURI
));
1133 if (LOG_TEST(PR_LOG_DEBUG
)) {
1136 mCurrentURI
->GetSpec(spec
);
1137 LOG_MSG_WITH_PARAM(GetImgLog(), "imgRequest::OnChannelRedirect", "new", spec
.get());
1140 bool doesNotReturnData
= false;
1142 NS_URIChainHasFlags(mCurrentURI
, nsIProtocolHandler::URI_DOES_NOT_RETURN_DATA
,
1143 &doesNotReturnData
);
1145 if (NS_SUCCEEDED(rv
) && doesNotReturnData
)
1146 rv
= NS_ERROR_ABORT
;
1148 if (NS_FAILED(rv
)) {
1149 mRedirectCallback
->OnRedirectVerifyCallback(rv
);
1150 mRedirectCallback
= nullptr;
1154 mRedirectCallback
->OnRedirectVerifyCallback(NS_OK
);
1155 mRedirectCallback
= nullptr;