Fix typo in 9b54bd30006c008b4a951331b273613d5bac3abf
[pm.git] / image / src / imgRequest.cpp
bloba38bc30f801d95be6091766f7d41118913a82f86
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"
15 #include "Image.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)
45 PRLogModuleInfo*
46 GetImgLog()
48 static PRLogModuleInfo* sImgLog;
49 if (!sImgLog)
50 sImgLog = PR_NewLogModule("imgRequest");
51 return sImgLog;
53 #define LOG_TEST(level) (GetImgLog() && PR_LOG_TEST(GetImgLog(), (level)))
54 #else
55 #define LOG_TEST(level) false
56 #endif
58 NS_IMPL_ISUPPORTS(imgRequest,
59 nsIStreamListener, nsIRequestObserver,
60 nsIThreadRetargetableStreamListener,
61 nsIChannelEventSink,
62 nsIInterfaceRequestor,
63 nsIAsyncVerifyRedirectCallback)
65 imgRequest::imgRequest(imgLoader* aLoader)
66 : mLoader(aLoader)
67 , mProgressTracker(new ProgressTracker())
68 , mValidator(nullptr)
69 , mMutex("imgRequest")
70 , mInnerWindowId(0)
71 , mCORSMode(imgIRequest::CORS_NONE)
72 , mReferrerPolicy(mozilla::net::RP_Default)
73 , mImageErrorCode(NS_OK)
74 , mDecodeRequested(false)
75 , mIsMultiPartChannel(false)
76 , mGotData(false)
77 , mIsInCache(false)
78 , mNewPartPending(false)
79 { }
81 imgRequest::~imgRequest()
83 if (mLoader) {
84 mLoader->RemoveFromUncachedImages(this);
86 if (mURI) {
87 nsAutoCString spec;
88 mURI->GetSpec(spec);
89 LOG_FUNC_WITH_PARAM(GetImgLog(), "imgRequest::~imgRequest()", "keyuri", spec.get());
90 } else
91 LOG_FUNC(GetImgLog(), "imgRequest::~imgRequest()");
94 nsresult imgRequest::Init(nsIURI *aURI,
95 nsIURI *aCurrentURI,
96 nsIRequest *aRequest,
97 nsIChannel *aChannel,
98 imgCacheEntry *aCacheEntry,
99 void *aLoadId,
100 nsIPrincipal* aLoadingPrincipal,
101 int32_t aCORSMode,
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;
119 mRequest = aRequest;
120 mChannel = aChannel;
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;
136 SetLoadId(aLoadId);
138 return NS_OK;
141 void imgRequest::ClearLoader() {
142 mLoader = nullptr;
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);
159 if (mImage) {
160 MOZ_ASSERT(!mProgressTracker,
161 "Should have given mProgressTracker to mImage");
162 return mImage->GetProgressTracker();
163 } else {
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();
172 void
173 imgRequest::SetImage(Image* aImage)
175 MutexAutoLock lock(mMutex);
176 mImage = aImage;
179 void
180 imgRequest::SetProgressTracker(ProgressTracker* aProgressTracker)
182 MutexAutoLock lock(mMutex);
183 mProgressTracker = aProgressTracker;
186 void imgRequest::SetCacheEntry(imgCacheEntry *entry)
188 mCacheEntry = 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
209 // proxies.
210 nsRefPtr<ProgressTracker> progressTracker = GetProgressTracker();
211 if (progressTracker->ObserverCount() == 0) {
212 MOZ_ASSERT(mURI, "Trying to SetHasProxies without key uri.");
213 if (mLoader) {
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))
236 return NS_OK;
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.
242 if (mCacheEntry) {
243 MOZ_ASSERT(mURI, "Removing last observer without key uri.");
245 if (mLoader) {
246 mLoader->SetHasNoProxies(this, mCacheEntry);
249 #if defined(PR_LOGGING)
250 else {
251 nsAutoCString spec;
252 mURI->GetSpec(spec);
253 LOG_MSG_WITH_PARAM(GetImgLog(), "imgRequest::RemoveProxy no cache entry", "uri", spec.get());
255 #endif
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);
278 return NS_OK;
281 void imgRequest::CancelAndAbort(nsresult aStatus)
283 LOG_SCOPE(GetImgLog(), "imgRequest::CancelAndAbort");
285 Cancel(aStatus);
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.
290 if (mChannel) {
291 mChannel->SetNotificationCallbacks(mPrevChannelSink);
292 mPrevChannelSink = nullptr;
296 class imgRequestMainThreadCancel : public nsRunnable
298 public:
299 imgRequestMainThreadCancel(imgRequest *aImgRequest, nsresult aStatus)
300 : mImgRequest(aImgRequest)
301 , mStatus(aStatus)
303 MOZ_ASSERT(!NS_IsMainThread(), "Create me off main thread only!");
304 MOZ_ASSERT(aImgRequest);
307 NS_IMETHOD Run()
309 MOZ_ASSERT(NS_IsMainThread(), "I should be running on the main thread!");
310 mImgRequest->ContinueCancel(mStatus);
311 return NS_OK;
313 private:
314 nsRefPtr<imgRequest> mImgRequest;
315 nsresult mStatus;
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);
325 } else {
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);
337 RemoveFromCache();
339 if (mRequest && !(progressTracker->GetProgress() & FLAG_LAST_PART_COMPLETE)) {
340 mRequest->Cancel(aStatus);
344 class imgRequestMainThreadEvict : public nsRunnable
346 public:
347 explicit imgRequestMainThreadEvict(imgRequest *aImgRequest)
348 : mImgRequest(aImgRequest)
350 MOZ_ASSERT(!NS_IsMainThread(), "Create me off main thread only!");
351 MOZ_ASSERT(aImgRequest);
354 NS_IMETHOD Run()
356 MOZ_ASSERT(NS_IsMainThread(), "I should be running on the main thread!");
357 mImgRequest->ContinueEvict();
358 return NS_OK;
360 private:
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()) {
371 ContinueEvict();
372 } else {
373 NS_DispatchToMainThread(new imgRequestMainThreadEvict(this));
377 // Helper-method used by EvictFromCache()
378 void imgRequest::ContinueEvict()
380 MOZ_ASSERT(NS_IsMainThread());
382 RemoveFromCache();
385 nsresult imgRequest::GetURI(ImageURL **aURI)
387 MOZ_ASSERT(aURI);
389 LOG_FUNC(GetImgLog(), "imgRequest::GetURI");
391 if (mURI) {
392 *aURI = mURI;
393 NS_ADDREF(*aURI);
394 return NS_OK;
397 return NS_ERROR_FAILURE;
400 nsresult imgRequest::GetCurrentURI(nsIURI **aURI)
402 MOZ_ASSERT(aURI);
404 LOG_FUNC(GetImgLog(), "imgRequest::GetCurrentURI");
406 if (mCurrentURI) {
407 *aURI = mCurrentURI;
408 NS_ADDREF(*aURI);
409 return NS_OK;
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);
427 return NS_OK;
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.
436 if (mCacheEntry) {
437 mLoader->RemoveFromCache(mCacheEntry);
438 } else {
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);
456 if (p)
457 p->GetPriority(&priority);
458 return 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))
472 return;
474 nsCOMPtr<nsISupportsPriority> p = do_QueryInterface(mChannel);
475 if (p)
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()
487 if (mCacheEntry) {
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 */
497 if (aCacheEntry) {
498 nsCOMPtr<nsICachingChannel> cacheChannel(do_QueryInterface(aRequest));
499 if (cacheChannel) {
500 nsCOMPtr<nsISupports> cacheToken;
501 cacheChannel->GetCacheToken(getter_AddRefs(cacheToken));
502 if (cacheToken) {
503 nsCOMPtr<nsICacheEntry> entryDesc(do_QueryInterface(cacheToken));
504 if (entryDesc) {
505 uint32_t expiration;
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));
520 if (httpChannel) {
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"),
533 cacheHeader);
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.
542 if (bMustRevalidate)
543 aCacheEntry->SetMustValidate(bMustRevalidate);
546 // We always need to validate file URIs.
547 nsCOMPtr<nsIChannel> channel = do_QueryInterface(aRequest);
548 if (channel) {
549 nsCOMPtr<nsIURI> uri;
550 channel->GetURI(getter_AddRefs(uri));
551 bool isfile = false;
552 uri->SchemeIs("file", &isfile);
553 if (isfile)
554 aCacheEntry->SetMustValidate(isfile);
559 namespace { // anon
561 already_AddRefed<nsIApplicationCache>
562 GetApplicationCache(nsIRequest* aRequest)
564 nsresult rv;
566 nsCOMPtr<nsIApplicationCacheChannel> appCacheChan = do_QueryInterface(aRequest);
567 if (!appCacheChan) {
568 return nullptr;
571 bool fromAppCache;
572 rv = appCacheChan->GetLoadedFromApplicationCache(&fromAppCache);
573 NS_ENSURE_SUCCESS(rv, nullptr);
575 if (!fromAppCache) {
576 return nullptr;
579 nsCOMPtr<nsIApplicationCache> appCache;
580 rv = appCacheChan->GetApplicationCache(getter_AddRefs(appCache));
581 NS_ENSURE_SUCCESS(rv, nullptr);
583 return appCache.forget();
586 } // anon
588 bool
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)
596 return false;
598 // In a rare case it may happen that two objects still refer
599 // the same application cache version.
600 if (newAppCache && mApplicationCache) {
601 nsresult rv;
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)
610 return false;
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.
616 return true;
619 nsresult
620 imgRequest::LockImage()
622 nsRefPtr<Image> image = GetImage();
623 return image->LockImage();
626 nsresult
627 imgRequest::UnlockImage()
629 nsRefPtr<Image> image = GetImage();
630 return image->UnlockImage();
633 nsresult
634 imgRequest::RequestDecode()
636 // If we've initialized our image, we can request a decode.
637 nsRefPtr<Image> image = GetImage();
638 if (image) {
639 return image->RequestDecode();
642 // Otherwise, flag to do it when we get the image
643 mDecodeRequested = true;
645 return NS_OK;
648 nsresult
649 imgRequest::StartDecoding()
651 // If we've initialized our image, we can request a decode.
652 nsRefPtr<Image> image = GetImage();
653 if (image) {
654 return image->StartDecoding();
657 // Otherwise, flag to do it when we get the image
658 mDecodeRequested = true;
660 return NS_OK;
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();
675 if (mpchan) {
676 mIsMultiPartChannel = true;
677 } else {
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
696 if (!mRequest) {
697 NS_ASSERTION(mpchan,
698 "We should have an mRequest here unless we're multipart");
699 nsCOMPtr<nsIChannel> chan;
700 mpchan->GetBaseChannel(getter_AddRefs(chan));
701 mRequest = chan;
704 nsCOMPtr<nsIChannel> channel(do_QueryInterface(aRequest));
705 if (channel)
706 channel->GetSecurityInfo(getter_AddRefs(mSecurityInfo));
708 /* Get our principal */
709 nsCOMPtr<nsIChannel> chan(do_QueryInterface(aRequest));
710 if (chan) {
711 nsCOMPtr<nsIScriptSecurityManager> secMan = nsContentUtils::GetSecurityManager();
712 if (secMan) {
713 nsresult rv = secMan->GetChannelResultPrincipal(chan,
714 getter_AddRefs(mPrincipal));
715 if (NS_FAILED(rv)) {
716 return rv;
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"));
749 return NS_OK;
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.
762 if (mRequest) {
763 mRequest = nullptr; // we no longer need the request
766 // stop holding a ref to the channel, since we don't need it anymore
767 if (mChannel) {
768 mChannel->SetNotificationCallbacks(mPrevChannelSink);
769 mPrevChannelSink = nullptr;
770 mChannel = nullptr;
773 bool lastPart = true;
774 nsCOMPtr<nsIMultiPartChannel> mpchan(do_QueryInterface(aRequest));
775 if (mpchan)
776 mpchan->GetIsLastPart(&lastPart);
778 bool isPartial = false;
779 nsRefPtr<Image> image = GetImage();
780 if (image && (status == NS_ERROR_NET_PARTIAL_TRANSFER)) {
781 isPartial = true;
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.
788 if (image) {
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
794 // more meaningful.
795 if (NS_FAILED(rv) && NS_SUCCEEDED(status))
796 status = rv;
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();
810 else {
811 mImageErrorCode = status;
813 // if the error isn't "just" a partial transfer
814 // stops animations, removes from cache
815 this->Cancel(status);
818 if (!image) {
819 // We have to fire the OnStopRequest notifications ourselves because there's
820 // no image capable of doing so.
821 Progress progress =
822 LoadCompleteProgress(lastPart, /* aError = */ false, status);
824 nsRefPtr<ProgressTracker> progressTracker = GetProgressTracker();
825 progressTracker->SyncNotifyProgress(progress);
828 mTimedChannel = nullptr;
829 return NS_OK;
832 struct mimetype_closure
834 nsACString* newType;
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 **/
842 NS_IMETHODIMP
843 imgRequest::CheckListenerChain()
845 // TODO Might need more checking here.
846 NS_ASSERTION(NS_IsMainThread(), "Should be on the main thread!");
847 return NS_OK;
850 /** nsIStreamListener methods **/
852 /* void onDataAvailable (in nsIRequest request, in nsISupports ctxt, in nsIInputStream inStr, in unsigned long long sourceOffset, in unsigned long count); */
853 NS_IMETHODIMP
854 imgRequest::OnDataAvailable(nsIRequest *aRequest, nsISupports *ctxt,
855 nsIInputStream *inStr, uint64_t sourceOffset,
856 uint32_t count)
858 LOG_SCOPE_WITH_PARAM(GetImgLog(), "imgRequest::OnDataAvailable", "count", count);
860 NS_ASSERTION(aRequest, "imgRequest::OnDataAvailable -- no request!");
862 nsresult rv;
863 mGotData = true;
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. :(
878 uint32_t out;
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;
886 if (chan) {
887 rv = chan->GetContentType(newType);
890 if (NS_FAILED(rv)) {
891 PR_LOG(GetImgLog(), PR_LOG_ERROR,
892 ("[this=%p] imgRequest::OnDataAvailable -- Content type unavailable from the channel\n",
893 this));
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;
904 SetProperties(chan);
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));
928 if (!image) {
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);
932 SetImage(image);
933 progressTracker = nullptr;
934 SetProgressTracker(nullptr);
935 } else {
936 // Transition to the new part.
937 static_cast<MultipartImage*>(image.get())->BeginTransitionToPart(partImage);
939 } else {
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.
944 image =
945 ImageFactory::CreateImage(aRequest, progressTracker, mContentType,
946 mURI, /* aIsMultipart = */ false,
947 static_cast<uint32_t>(mInnerWindowId));
948 SetImage(image);
949 progressTracker = nullptr;
950 SetProgressTracker(nullptr);
953 if (firstPart) {
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);
979 if (NS_FAILED(rv)) {
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;
987 return NS_OK;
990 class SetPropertiesEvent : public nsRunnable
992 public:
993 SetPropertiesEvent(imgRequest* aImgRequest, nsIChannel* aChan)
994 : mImgRequest(aImgRequest)
995 , mChan(aChan)
997 MOZ_ASSERT(!NS_IsMainThread(), "Should be created off the main thread");
998 MOZ_ASSERT(aImgRequest, "aImgRequest cannot be null");
1000 NS_IMETHOD Run()
1002 MOZ_ASSERT(NS_IsMainThread(), "Should run on the main thread only");
1003 MOZ_ASSERT(mImgRequest, "mImgRequest cannot be null");
1004 mImgRequest->SetProperties(mChan);
1005 return NS_OK;
1007 private:
1008 nsRefPtr<imgRequest> mImgRequest;
1009 nsCOMPtr<nsIChannel> mChan;
1012 void
1013 imgRequest::SetProperties(nsIChannel* aChan)
1015 // Force execution on main thread since some property objects are non
1016 // threadsafe.
1017 if (!NS_IsMainThread()) {
1018 NS_DispatchToMainThread(new SetPropertiesEvent(this, aChan));
1019 return;
1021 /* set our mimetype as a property */
1022 nsCOMPtr<nsISupportsCString> contentType(do_CreateInstance("@mozilla.org/supports-cstring;1"));
1023 if (contentType) {
1024 contentType->SetData(mContentType);
1025 mProperties->Set("type", contentType);
1028 /* set our content disposition as a property */
1029 nsAutoCString disposition;
1030 if (aChan) {
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,
1043 void* data,
1044 const char* fromRawSegment,
1045 uint32_t toOffset,
1046 uint32_t count,
1047 uint32_t *writeCount)
1049 mimetype_closure* closure = static_cast<mimetype_closure*>(data);
1051 NS_ASSERTION(closure, "closure is null!");
1053 if (count > 0)
1054 imgLoader::GetMimeTypeFromContent(fromRawSegment, count, *closure->newType);
1056 *writeCount = 0;
1057 return NS_ERROR_FAILURE;
1061 /** nsIInterfaceRequestor methods **/
1063 NS_IMETHODIMP
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 **/
1075 NS_IMETHODIMP
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));
1091 if (sink) {
1092 nsresult rv = sink->AsyncOnChannelRedirect(oldChannel, newChannel, flags,
1093 this);
1094 if (NS_FAILED(rv)) {
1095 mRedirectCallback = nullptr;
1096 mNewRedirectChannel = nullptr;
1098 return rv;
1101 (void) OnRedirectVerifyCallback(NS_OK);
1102 return NS_OK;
1105 NS_IMETHODIMP
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;
1115 return NS_OK;
1118 mChannel = mNewRedirectChannel;
1119 mTimedChannel = do_QueryInterface(mChannel);
1120 mNewRedirectChannel = nullptr;
1122 if (LOG_TEST(PR_LOG_DEBUG)) {
1123 nsAutoCString spec;
1124 if (mCurrentURI)
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)) {
1134 nsAutoCString spec;
1135 if (mCurrentURI)
1136 mCurrentURI->GetSpec(spec);
1137 LOG_MSG_WITH_PARAM(GetImgLog(), "imgRequest::OnChannelRedirect", "new", spec.get());
1140 bool doesNotReturnData = false;
1141 nsresult rv =
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;
1151 return NS_OK;
1154 mRedirectCallback->OnRedirectVerifyCallback(NS_OK);
1155 mRedirectCallback = nullptr;
1156 return NS_OK;