Bug 1646700 [wpt PR 24235] - Update picture-in-picture idlharness test, a=testonly
[gecko.git] / image / imgRequest.cpp
blobbcb9803b245a75ea71bb4bda4b708cb72f9051a5
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 "nsICacheInfoChannel.h"
21 #include "nsIClassOfService.h"
22 #include "mozilla/dom/Document.h"
23 #include "nsIThreadRetargetableRequest.h"
24 #include "nsIInputStream.h"
25 #include "nsIMultiPartChannel.h"
26 #include "nsIHttpChannel.h"
27 #include "nsIApplicationCache.h"
28 #include "nsIApplicationCacheChannel.h"
29 #include "nsMimeTypes.h"
31 #include "nsIInterfaceRequestorUtils.h"
32 #include "nsISupportsPrimitives.h"
33 #include "nsIScriptSecurityManager.h"
34 #include "nsContentUtils.h"
36 #include "plstr.h" // PL_strcasestr(...)
37 #include "prtime.h" // for PR_Now
38 #include "nsNetUtil.h"
39 #include "nsIProtocolHandler.h"
40 #include "imgIRequest.h"
41 #include "nsProperties.h"
43 #include "mozilla/IntegerPrintfMacros.h"
44 #include "mozilla/SizeOfState.h"
46 using namespace mozilla;
47 using namespace mozilla::image;
49 #define LOG_TEST(level) (MOZ_LOG_TEST(gImgLog, (level)))
51 NS_IMPL_ISUPPORTS(imgRequest, nsIStreamListener, nsIRequestObserver,
52 nsIThreadRetargetableStreamListener, nsIChannelEventSink,
53 nsIInterfaceRequestor, nsIAsyncVerifyRedirectCallback)
55 imgRequest::imgRequest(imgLoader* aLoader, const ImageCacheKey& aCacheKey)
56 : mLoader(aLoader),
57 mCacheKey(aCacheKey),
58 mLoadId(nullptr),
59 mFirstProxy(nullptr),
60 mValidator(nullptr),
61 mInnerWindowId(0),
62 mCORSMode(imgIRequest::CORS_NONE),
63 mImageErrorCode(NS_OK),
64 mImageAvailable(false),
65 mIsDeniedCrossSiteCORSRequest(false),
66 mIsCrossSiteNoCORSRequest(false),
67 mMutex("imgRequest"),
68 mProgressTracker(new ProgressTracker()),
69 mIsMultiPartChannel(false),
70 mIsInCache(false),
71 mDecodeRequested(false),
72 mNewPartPending(false),
73 mHadInsecureRedirect(false) {
74 LOG_FUNC(gImgLog, "imgRequest::imgRequest()");
77 imgRequest::~imgRequest() {
78 if (mLoader) {
79 mLoader->RemoveFromUncachedImages(this);
81 if (mURI) {
82 LOG_FUNC_WITH_PARAM(gImgLog, "imgRequest::~imgRequest()", "keyuri", mURI);
83 } else
84 LOG_FUNC(gImgLog, "imgRequest::~imgRequest()");
87 nsresult imgRequest::Init(nsIURI* aURI, nsIURI* aFinalURI,
88 bool aHadInsecureRedirect, nsIRequest* aRequest,
89 nsIChannel* aChannel, imgCacheEntry* aCacheEntry,
90 mozilla::dom::Document* aLoadingDocument,
91 nsIPrincipal* aTriggeringPrincipal, int32_t aCORSMode,
92 nsIReferrerInfo* aReferrerInfo) {
93 MOZ_ASSERT(NS_IsMainThread(), "Cannot use nsIURI off main thread!");
95 LOG_FUNC(gImgLog, "imgRequest::Init");
97 MOZ_ASSERT(!mImage, "Multiple calls to init");
98 MOZ_ASSERT(aURI, "No uri");
99 MOZ_ASSERT(aFinalURI, "No final uri");
100 MOZ_ASSERT(aRequest, "No request");
101 MOZ_ASSERT(aChannel, "No channel");
103 mProperties = new nsProperties();
104 mURI = aURI;
105 mFinalURI = aFinalURI;
106 mRequest = aRequest;
107 mChannel = aChannel;
108 mTimedChannel = do_QueryInterface(mChannel);
109 mTriggeringPrincipal = aTriggeringPrincipal;
110 mCORSMode = aCORSMode;
111 mReferrerInfo = aReferrerInfo;
113 // If the original URI and the final URI are different, check whether the
114 // original URI is secure. We deliberately don't take the final URI into
115 // account, as it needs to be handled using more complicated rules than
116 // earlier elements of the redirect chain.
117 if (aURI != aFinalURI) {
118 bool schemeLocal = false;
119 if (NS_FAILED(NS_URIChainHasFlags(
120 aURI, nsIProtocolHandler::URI_IS_LOCAL_RESOURCE, &schemeLocal)) ||
121 (!aURI->SchemeIs("https") && !aURI->SchemeIs("chrome") &&
122 !schemeLocal)) {
123 mHadInsecureRedirect = true;
127 // imgCacheValidator may have handled redirects before we were created, so we
128 // allow the caller to let us know if any redirects were insecure.
129 mHadInsecureRedirect = mHadInsecureRedirect || aHadInsecureRedirect;
131 mChannel->GetNotificationCallbacks(getter_AddRefs(mPrevChannelSink));
133 NS_ASSERTION(mPrevChannelSink != this,
134 "Initializing with a channel that already calls back to us!");
136 mChannel->SetNotificationCallbacks(this);
138 mCacheEntry = aCacheEntry;
139 mCacheEntry->UpdateLoadTime();
141 SetLoadId(aLoadingDocument);
143 // Grab the inner window ID of the loading document, if possible.
144 if (aLoadingDocument) {
145 mInnerWindowId = aLoadingDocument->InnerWindowID();
148 return NS_OK;
151 void imgRequest::ClearLoader() { mLoader = nullptr; }
153 already_AddRefed<ProgressTracker> imgRequest::GetProgressTracker() const {
154 MutexAutoLock lock(mMutex);
156 if (mImage) {
157 MOZ_ASSERT(!mProgressTracker,
158 "Should have given mProgressTracker to mImage");
159 return mImage->GetProgressTracker();
161 MOZ_ASSERT(mProgressTracker,
162 "Should have mProgressTracker until we create mImage");
163 RefPtr<ProgressTracker> progressTracker = mProgressTracker;
164 MOZ_ASSERT(progressTracker);
165 return progressTracker.forget();
168 void imgRequest::SetCacheEntry(imgCacheEntry* entry) { mCacheEntry = entry; }
170 bool imgRequest::HasCacheEntry() const { return mCacheEntry != nullptr; }
172 void imgRequest::ResetCacheEntry() {
173 if (HasCacheEntry()) {
174 mCacheEntry->SetDataSize(0);
178 void imgRequest::AddProxy(imgRequestProxy* proxy) {
179 MOZ_ASSERT(proxy, "null imgRequestProxy passed in");
180 LOG_SCOPE_WITH_PARAM(gImgLog, "imgRequest::AddProxy", "proxy", proxy);
182 if (!mFirstProxy) {
183 // Save a raw pointer to the first proxy we see, for use in the network
184 // priority logic.
185 mFirstProxy = proxy;
188 // If we're empty before adding, we have to tell the loader we now have
189 // proxies.
190 RefPtr<ProgressTracker> progressTracker = GetProgressTracker();
191 if (progressTracker->ObserverCount() == 0) {
192 MOZ_ASSERT(mURI, "Trying to SetHasProxies without key uri.");
193 if (mLoader) {
194 mLoader->SetHasProxies(this);
198 progressTracker->AddObserver(proxy);
201 nsresult imgRequest::RemoveProxy(imgRequestProxy* proxy, nsresult aStatus) {
202 LOG_SCOPE_WITH_PARAM(gImgLog, "imgRequest::RemoveProxy", "proxy", proxy);
204 // This will remove our animation consumers, so after removing
205 // this proxy, we don't end up without proxies with observers, but still
206 // have animation consumers.
207 proxy->ClearAnimationConsumers();
209 // Let the status tracker do its thing before we potentially call Cancel()
210 // below, because Cancel() may result in OnStopRequest being called back
211 // before Cancel() returns, leaving the image in a different state then the
212 // one it was in at this point.
213 RefPtr<ProgressTracker> progressTracker = GetProgressTracker();
214 if (!progressTracker->RemoveObserver(proxy)) {
215 return NS_OK;
218 if (progressTracker->ObserverCount() == 0) {
219 // If we have no observers, there's nothing holding us alive. If we haven't
220 // been cancelled and thus removed from the cache, tell the image loader so
221 // we can be evicted from the cache.
222 if (mCacheEntry) {
223 MOZ_ASSERT(mURI, "Removing last observer without key uri.");
225 if (mLoader) {
226 mLoader->SetHasNoProxies(this, mCacheEntry);
228 } else {
229 LOG_MSG_WITH_PARAM(gImgLog, "imgRequest::RemoveProxy no cache entry",
230 "uri", mURI);
233 /* If |aStatus| is a failure code, then cancel the load if it is still in
234 progress. Otherwise, let the load continue, keeping 'this' in the cache
235 with no observers. This way, if a proxy is destroyed without calling
236 cancel on it, it won't leak and won't leave a bad pointer in the observer
237 list.
239 if (!(progressTracker->GetProgress() & FLAG_LAST_PART_COMPLETE) &&
240 NS_FAILED(aStatus)) {
241 LOG_MSG(gImgLog, "imgRequest::RemoveProxy",
242 "load in progress. canceling");
244 this->Cancel(NS_BINDING_ABORTED);
247 /* break the cycle from the cache entry. */
248 mCacheEntry = nullptr;
251 return NS_OK;
254 void imgRequest::CancelAndAbort(nsresult aStatus) {
255 LOG_SCOPE(gImgLog, "imgRequest::CancelAndAbort");
257 Cancel(aStatus);
259 // It's possible for the channel to fail to open after we've set our
260 // notification callbacks. In that case, make sure to break the cycle between
261 // the channel and us, because it won't.
262 if (mChannel) {
263 mChannel->SetNotificationCallbacks(mPrevChannelSink);
264 mPrevChannelSink = nullptr;
268 class imgRequestMainThreadCancel : public Runnable {
269 public:
270 imgRequestMainThreadCancel(imgRequest* aImgRequest, nsresult aStatus)
271 : Runnable("imgRequestMainThreadCancel"),
272 mImgRequest(aImgRequest),
273 mStatus(aStatus) {
274 MOZ_ASSERT(!NS_IsMainThread(), "Create me off main thread only!");
275 MOZ_ASSERT(aImgRequest);
278 NS_IMETHOD Run() override {
279 MOZ_ASSERT(NS_IsMainThread(), "I should be running on the main thread!");
280 mImgRequest->ContinueCancel(mStatus);
281 return NS_OK;
284 private:
285 RefPtr<imgRequest> mImgRequest;
286 nsresult mStatus;
289 void imgRequest::Cancel(nsresult aStatus) {
290 /* The Cancel() method here should only be called by this class. */
291 LOG_SCOPE(gImgLog, "imgRequest::Cancel");
293 if (NS_IsMainThread()) {
294 ContinueCancel(aStatus);
295 } else {
296 RefPtr<ProgressTracker> progressTracker = GetProgressTracker();
297 nsCOMPtr<nsIEventTarget> eventTarget = progressTracker->GetEventTarget();
298 nsCOMPtr<nsIRunnable> ev = new imgRequestMainThreadCancel(this, aStatus);
299 eventTarget->Dispatch(ev.forget(), NS_DISPATCH_NORMAL);
303 void imgRequest::ContinueCancel(nsresult aStatus) {
304 MOZ_ASSERT(NS_IsMainThread());
306 RefPtr<ProgressTracker> progressTracker = GetProgressTracker();
307 progressTracker->SyncNotifyProgress(FLAG_HAS_ERROR);
309 RemoveFromCache();
311 if (mRequest && !(progressTracker->GetProgress() & FLAG_LAST_PART_COMPLETE)) {
312 mRequest->Cancel(aStatus);
316 class imgRequestMainThreadEvict : public Runnable {
317 public:
318 explicit imgRequestMainThreadEvict(imgRequest* aImgRequest)
319 : Runnable("imgRequestMainThreadEvict"), mImgRequest(aImgRequest) {
320 MOZ_ASSERT(!NS_IsMainThread(), "Create me off main thread only!");
321 MOZ_ASSERT(aImgRequest);
324 NS_IMETHOD Run() override {
325 MOZ_ASSERT(NS_IsMainThread(), "I should be running on the main thread!");
326 mImgRequest->ContinueEvict();
327 return NS_OK;
330 private:
331 RefPtr<imgRequest> mImgRequest;
334 // EvictFromCache() is written to allowed to get called from any thread
335 void imgRequest::EvictFromCache() {
336 /* The EvictFromCache() method here should only be called by this class. */
337 LOG_SCOPE(gImgLog, "imgRequest::EvictFromCache");
339 if (NS_IsMainThread()) {
340 ContinueEvict();
341 } else {
342 NS_DispatchToMainThread(new imgRequestMainThreadEvict(this));
346 // Helper-method used by EvictFromCache()
347 void imgRequest::ContinueEvict() {
348 MOZ_ASSERT(NS_IsMainThread());
350 RemoveFromCache();
353 void imgRequest::StartDecoding() {
354 MutexAutoLock lock(mMutex);
355 mDecodeRequested = true;
358 bool imgRequest::IsDecodeRequested() const {
359 MutexAutoLock lock(mMutex);
360 return mDecodeRequested;
363 nsresult imgRequest::GetURI(nsIURI** aURI) {
364 MOZ_ASSERT(aURI);
366 LOG_FUNC(gImgLog, "imgRequest::GetURI");
368 if (mURI) {
369 *aURI = mURI;
370 NS_ADDREF(*aURI);
371 return NS_OK;
374 return NS_ERROR_FAILURE;
377 nsresult imgRequest::GetFinalURI(nsIURI** aURI) {
378 MOZ_ASSERT(aURI);
380 LOG_FUNC(gImgLog, "imgRequest::GetFinalURI");
382 if (mFinalURI) {
383 *aURI = mFinalURI;
384 NS_ADDREF(*aURI);
385 return NS_OK;
388 return NS_ERROR_FAILURE;
391 bool imgRequest::IsChrome() const { return mURI->SchemeIs("chrome"); }
393 bool imgRequest::IsData() const { return mURI->SchemeIs("data"); }
395 nsresult imgRequest::GetImageErrorCode() { return mImageErrorCode; }
397 void imgRequest::RemoveFromCache() {
398 LOG_SCOPE(gImgLog, "imgRequest::RemoveFromCache");
400 bool isInCache = false;
403 MutexAutoLock lock(mMutex);
404 isInCache = mIsInCache;
407 if (isInCache && mLoader) {
408 // mCacheEntry is nulled out when we have no more observers.
409 if (mCacheEntry) {
410 mLoader->RemoveFromCache(mCacheEntry);
411 } else {
412 mLoader->RemoveFromCache(mCacheKey);
416 mCacheEntry = nullptr;
419 bool imgRequest::HasConsumers() const {
420 RefPtr<ProgressTracker> progressTracker = GetProgressTracker();
421 return progressTracker && progressTracker->ObserverCount() > 0;
424 already_AddRefed<image::Image> imgRequest::GetImage() const {
425 MutexAutoLock lock(mMutex);
426 RefPtr<image::Image> image = mImage;
427 return image.forget();
430 int32_t imgRequest::Priority() const {
431 int32_t priority = nsISupportsPriority::PRIORITY_NORMAL;
432 nsCOMPtr<nsISupportsPriority> p = do_QueryInterface(mRequest);
433 if (p) {
434 p->GetPriority(&priority);
436 return priority;
439 void imgRequest::AdjustPriority(imgRequestProxy* proxy, int32_t delta) {
440 // only the first proxy is allowed to modify the priority of this image load.
442 // XXX(darin): this is probably not the most optimal algorithm as we may want
443 // to increase the priority of requests that have a lot of proxies. the key
444 // concern though is that image loads remain lower priority than other pieces
445 // of content such as link clicks, CSS, and JS.
447 if (!mFirstProxy || proxy != mFirstProxy) {
448 return;
451 AdjustPriorityInternal(delta);
454 void imgRequest::AdjustPriorityInternal(int32_t aDelta) {
455 nsCOMPtr<nsISupportsPriority> p = do_QueryInterface(mChannel);
456 if (p) {
457 p->AdjustPriority(aDelta);
461 void imgRequest::BoostPriority(uint32_t aCategory) {
462 if (!StaticPrefs::image_layout_network_priority()) {
463 return;
466 uint32_t newRequestedCategory =
467 (mBoostCategoriesRequested & aCategory) ^ aCategory;
468 if (!newRequestedCategory) {
469 // priority boost for each category can only apply once.
470 return;
473 MOZ_LOG(gImgLog, LogLevel::Debug,
474 ("[this=%p] imgRequest::BoostPriority for category %x", this,
475 newRequestedCategory));
477 int32_t delta = 0;
479 if (newRequestedCategory & imgIRequest::CATEGORY_FRAME_INIT) {
480 --delta;
483 if (newRequestedCategory & imgIRequest::CATEGORY_FRAME_STYLE) {
484 --delta;
487 if (newRequestedCategory & imgIRequest::CATEGORY_SIZE_QUERY) {
488 --delta;
491 if (newRequestedCategory & imgIRequest::CATEGORY_DISPLAY) {
492 delta += nsISupportsPriority::PRIORITY_HIGH;
495 AdjustPriorityInternal(delta);
496 mBoostCategoriesRequested |= newRequestedCategory;
499 void imgRequest::SetIsInCache(bool aInCache) {
500 LOG_FUNC_WITH_PARAM(gImgLog, "imgRequest::SetIsCacheable", "aInCache",
501 aInCache);
502 MutexAutoLock lock(mMutex);
503 mIsInCache = aInCache;
506 void imgRequest::UpdateCacheEntrySize() {
507 if (!mCacheEntry) {
508 return;
511 RefPtr<Image> image = GetImage();
512 SizeOfState state(moz_malloc_size_of);
513 size_t size = image->SizeOfSourceWithComputedFallback(state);
514 mCacheEntry->SetDataSize(size);
517 void imgRequest::SetCacheValidation(imgCacheEntry* aCacheEntry,
518 nsIRequest* aRequest) {
519 /* get the expires info */
520 if (!aCacheEntry || aCacheEntry->GetExpiryTime() != 0) {
521 return;
524 auto info = nsContentUtils::GetSubresourceCacheValidationInfo(aRequest);
526 // Expiration time defaults to 0. We set the expiration time on our entry if
527 // it hasn't been set yet.
528 if (!info.mExpirationTime) {
529 // If the channel doesn't support caching, then ensure this expires the
530 // next time it is used.
531 info.mExpirationTime.emplace(nsContentUtils::SecondsFromPRTime(PR_Now()) -
534 aCacheEntry->SetExpiryTime(*info.mExpirationTime);
535 // Cache entries default to not needing to validate. We ensure that
536 // multiple calls to this function don't override an earlier decision to
537 // validate by making validation a one-way decision.
538 if (info.mMustRevalidate) {
539 aCacheEntry->SetMustValidate(info.mMustRevalidate);
543 namespace {
545 already_AddRefed<nsIApplicationCache> GetApplicationCache(
546 nsIRequest* aRequest) {
547 nsresult rv;
549 nsCOMPtr<nsIApplicationCacheChannel> appCacheChan =
550 do_QueryInterface(aRequest);
551 if (!appCacheChan) {
552 return nullptr;
555 bool fromAppCache;
556 rv = appCacheChan->GetLoadedFromApplicationCache(&fromAppCache);
557 NS_ENSURE_SUCCESS(rv, nullptr);
559 if (!fromAppCache) {
560 return nullptr;
563 nsCOMPtr<nsIApplicationCache> appCache;
564 rv = appCacheChan->GetApplicationCache(getter_AddRefs(appCache));
565 NS_ENSURE_SUCCESS(rv, nullptr);
567 return appCache.forget();
570 } // namespace
572 bool imgRequest::CacheChanged(nsIRequest* aNewRequest) {
573 nsCOMPtr<nsIApplicationCache> newAppCache = GetApplicationCache(aNewRequest);
575 // Application cache not involved at all or the same app cache involved
576 // in both of the loads (original and new).
577 if (newAppCache == mApplicationCache) {
578 return false;
581 // In a rare case it may happen that two objects still refer
582 // the same application cache version.
583 if (newAppCache && mApplicationCache) {
584 nsresult rv;
586 nsAutoCString oldAppCacheClientId, newAppCacheClientId;
587 rv = mApplicationCache->GetClientID(oldAppCacheClientId);
588 NS_ENSURE_SUCCESS(rv, true);
589 rv = newAppCache->GetClientID(newAppCacheClientId);
590 NS_ENSURE_SUCCESS(rv, true);
592 if (oldAppCacheClientId == newAppCacheClientId) {
593 return false;
597 // When we get here, app caches differ or app cache is involved
598 // just in one of the loads what we also consider as a change
599 // in a loading cache.
600 return true;
603 bool imgRequest::GetMultipart() const {
604 MutexAutoLock lock(mMutex);
605 return mIsMultiPartChannel;
608 bool imgRequest::HadInsecureRedirect() const {
609 MutexAutoLock lock(mMutex);
610 return mHadInsecureRedirect;
613 /** nsIRequestObserver methods **/
615 NS_IMETHODIMP
616 imgRequest::OnStartRequest(nsIRequest* aRequest) {
617 LOG_SCOPE(gImgLog, "imgRequest::OnStartRequest");
619 RefPtr<Image> image;
621 if (nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aRequest)) {
622 nsresult rv;
623 nsCOMPtr<nsILoadInfo> loadInfo = httpChannel->LoadInfo();
624 mIsDeniedCrossSiteCORSRequest =
625 loadInfo->GetTainting() == LoadTainting::CORS &&
626 (NS_FAILED(httpChannel->GetStatus(&rv)) || NS_FAILED(rv));
627 mIsCrossSiteNoCORSRequest = loadInfo->GetTainting() == LoadTainting::Opaque;
630 // Figure out if we're multipart.
631 nsCOMPtr<nsIMultiPartChannel> multiPartChannel = do_QueryInterface(aRequest);
633 MutexAutoLock lock(mMutex);
635 MOZ_ASSERT(multiPartChannel || !mIsMultiPartChannel,
636 "Stopped being multipart?");
638 mNewPartPending = true;
639 image = mImage;
640 mIsMultiPartChannel = bool(multiPartChannel);
643 // If we're not multipart, we shouldn't have an image yet.
644 if (image && !multiPartChannel) {
645 MOZ_ASSERT_UNREACHABLE("Already have an image for a non-multipart request");
646 Cancel(NS_IMAGELIB_ERROR_FAILURE);
647 return NS_ERROR_FAILURE;
651 * If mRequest is null here, then we need to set it so that we'll be able to
652 * cancel it if our Cancel() method is called. Note that this can only
653 * happen for multipart channels. We could simply not null out mRequest for
654 * non-last parts, if GetIsLastPart() were reliable, but it's not. See
655 * https://bugzilla.mozilla.org/show_bug.cgi?id=339610
657 if (!mRequest) {
658 MOZ_ASSERT(multiPartChannel, "Should have mRequest unless we're multipart");
659 nsCOMPtr<nsIChannel> baseChannel;
660 multiPartChannel->GetBaseChannel(getter_AddRefs(baseChannel));
661 mRequest = baseChannel;
664 nsCOMPtr<nsIChannel> channel(do_QueryInterface(aRequest));
665 if (channel) {
666 /* Get our principal */
667 nsCOMPtr<nsIScriptSecurityManager> secMan =
668 nsContentUtils::GetSecurityManager();
669 if (secMan) {
670 nsresult rv = secMan->GetChannelResultPrincipal(
671 channel, getter_AddRefs(mPrincipal));
672 if (NS_FAILED(rv)) {
673 return rv;
678 SetCacheValidation(mCacheEntry, aRequest);
680 mApplicationCache = GetApplicationCache(aRequest);
682 // Shouldn't we be dead already if this gets hit?
683 // Probably multipart/x-mixed-replace...
684 RefPtr<ProgressTracker> progressTracker = GetProgressTracker();
685 if (progressTracker->ObserverCount() == 0) {
686 this->Cancel(NS_IMAGELIB_ERROR_FAILURE);
689 // Try to retarget OnDataAvailable to a decode thread. We must process data
690 // URIs synchronously as per the spec however.
691 if (!channel || IsData()) {
692 return NS_OK;
695 nsCOMPtr<nsIThreadRetargetableRequest> retargetable =
696 do_QueryInterface(aRequest);
697 if (retargetable) {
698 nsAutoCString mimeType;
699 nsresult rv = channel->GetContentType(mimeType);
700 if (NS_SUCCEEDED(rv) && !mimeType.EqualsLiteral(IMAGE_SVG_XML)) {
701 // Retarget OnDataAvailable to the DecodePool's IO thread.
702 nsCOMPtr<nsIEventTarget> target =
703 DecodePool::Singleton()->GetIOEventTarget();
704 rv = retargetable->RetargetDeliveryTo(target);
706 MOZ_LOG(gImgLog, LogLevel::Warning,
707 ("[this=%p] imgRequest::OnStartRequest -- "
708 "RetargetDeliveryTo rv %" PRIu32 "=%s\n",
709 this, static_cast<uint32_t>(rv),
710 NS_SUCCEEDED(rv) ? "succeeded" : "failed"));
713 return NS_OK;
716 NS_IMETHODIMP
717 imgRequest::OnStopRequest(nsIRequest* aRequest, nsresult status) {
718 LOG_FUNC(gImgLog, "imgRequest::OnStopRequest");
719 MOZ_ASSERT(NS_IsMainThread(), "Can't send notifications off-main-thread");
721 RefPtr<Image> image = GetImage();
723 RefPtr<imgRequest> strongThis = this;
725 if (mIsMultiPartChannel && mNewPartPending) {
726 OnDataAvailable(aRequest, nullptr, 0, 0);
729 // XXXldb What if this is a non-last part of a multipart request?
730 // xxx before we release our reference to mRequest, lets
731 // save the last status that we saw so that the
732 // imgRequestProxy will have access to it.
733 if (mRequest) {
734 mRequest = nullptr; // we no longer need the request
737 // stop holding a ref to the channel, since we don't need it anymore
738 if (mChannel) {
739 mChannel->SetNotificationCallbacks(mPrevChannelSink);
740 mPrevChannelSink = nullptr;
741 mChannel = nullptr;
744 bool lastPart = true;
745 nsCOMPtr<nsIMultiPartChannel> mpchan(do_QueryInterface(aRequest));
746 if (mpchan) {
747 mpchan->GetIsLastPart(&lastPart);
750 bool isPartial = false;
751 if (image && (status == NS_ERROR_NET_PARTIAL_TRANSFER)) {
752 isPartial = true;
753 status = NS_OK; // fake happy face
756 // Tell the image that it has all of the source data. Note that this can
757 // trigger a failure, since the image might be waiting for more non-optional
758 // data and this is the point where we break the news that it's not coming.
759 if (image) {
760 nsresult rv =
761 image->OnImageDataComplete(aRequest, nullptr, status, lastPart);
763 // If we got an error in the OnImageDataComplete() call, we don't want to
764 // proceed as if nothing bad happened. However, we also want to give
765 // precedence to failure status codes from necko, since presumably they're
766 // more meaningful.
767 if (NS_FAILED(rv) && NS_SUCCEEDED(status)) {
768 status = rv;
772 // If the request went through, update the cache entry size. Otherwise,
773 // cancel the request, which removes us from the cache.
774 if (image && NS_SUCCEEDED(status) && !isPartial) {
775 // We update the cache entry size here because this is where we finish
776 // loading compressed source data, which is part of our size calculus.
777 UpdateCacheEntrySize();
779 } else if (isPartial) {
780 // Remove the partial image from the cache.
781 this->EvictFromCache();
783 } else {
784 mImageErrorCode = status;
786 // if the error isn't "just" a partial transfer
787 // stops animations, removes from cache
788 this->Cancel(status);
791 if (!image) {
792 // We have to fire the OnStopRequest notifications ourselves because there's
793 // no image capable of doing so.
794 Progress progress =
795 LoadCompleteProgress(lastPart, /* aError = */ false, status);
797 RefPtr<ProgressTracker> progressTracker = GetProgressTracker();
798 progressTracker->SyncNotifyProgress(progress);
801 mTimedChannel = nullptr;
802 return NS_OK;
805 struct mimetype_closure {
806 nsACString* newType;
809 /* prototype for these defined below */
810 static nsresult sniff_mimetype_callback(nsIInputStream* in, void* closure,
811 const char* fromRawSegment,
812 uint32_t toOffset, uint32_t count,
813 uint32_t* writeCount);
815 /** nsThreadRetargetableStreamListener methods **/
816 NS_IMETHODIMP
817 imgRequest::CheckListenerChain() {
818 // TODO Might need more checking here.
819 NS_ASSERTION(NS_IsMainThread(), "Should be on the main thread!");
820 return NS_OK;
823 /** nsIStreamListener methods **/
825 struct NewPartResult final {
826 explicit NewPartResult(image::Image* aExistingImage)
827 : mImage(aExistingImage),
828 mIsFirstPart(!aExistingImage),
829 mSucceeded(false),
830 mShouldResetCacheEntry(false) {}
832 nsAutoCString mContentType;
833 nsAutoCString mContentDisposition;
834 RefPtr<image::Image> mImage;
835 const bool mIsFirstPart;
836 bool mSucceeded;
837 bool mShouldResetCacheEntry;
840 static NewPartResult PrepareForNewPart(nsIRequest* aRequest,
841 nsIInputStream* aInStr, uint32_t aCount,
842 nsIURI* aURI, bool aIsMultipart,
843 image::Image* aExistingImage,
844 ProgressTracker* aProgressTracker,
845 uint32_t aInnerWindowId) {
846 NewPartResult result(aExistingImage);
848 if (aInStr) {
849 mimetype_closure closure;
850 closure.newType = &result.mContentType;
852 // Look at the first few bytes and see if we can tell what the data is from
853 // that since servers tend to lie. :(
854 uint32_t out;
855 aInStr->ReadSegments(sniff_mimetype_callback, &closure, aCount, &out);
858 nsCOMPtr<nsIChannel> chan(do_QueryInterface(aRequest));
859 if (result.mContentType.IsEmpty()) {
860 nsresult rv =
861 chan ? chan->GetContentType(result.mContentType) : NS_ERROR_FAILURE;
862 if (NS_FAILED(rv)) {
863 MOZ_LOG(gImgLog, LogLevel::Error,
864 ("imgRequest::PrepareForNewPart -- "
865 "Content type unavailable from the channel\n"));
866 if (!aIsMultipart) {
867 return result;
872 if (chan) {
873 chan->GetContentDispositionHeader(result.mContentDisposition);
876 MOZ_LOG(gImgLog, LogLevel::Debug,
877 ("imgRequest::PrepareForNewPart -- Got content type %s\n",
878 result.mContentType.get()));
880 // XXX If server lied about mimetype and it's SVG, we may need to copy
881 // the data and dispatch back to the main thread, AND tell the channel to
882 // dispatch there in the future.
884 // Create the new image and give it ownership of our ProgressTracker.
885 if (aIsMultipart) {
886 // Create the ProgressTracker and image for this part.
887 RefPtr<ProgressTracker> progressTracker = new ProgressTracker();
888 RefPtr<image::Image> partImage = image::ImageFactory::CreateImage(
889 aRequest, progressTracker, result.mContentType, aURI,
890 /* aIsMultipart = */ true, aInnerWindowId);
892 if (result.mIsFirstPart) {
893 // First part for a multipart channel. Create the MultipartImage wrapper.
894 MOZ_ASSERT(aProgressTracker, "Shouldn't have given away tracker yet");
895 aProgressTracker->SetIsMultipart();
896 result.mImage = image::ImageFactory::CreateMultipartImage(
897 partImage, aProgressTracker);
898 } else {
899 // Transition to the new part.
900 auto multipartImage = static_cast<MultipartImage*>(aExistingImage);
901 multipartImage->BeginTransitionToPart(partImage);
903 // Reset our cache entry size so it doesn't keep growing without bound.
904 result.mShouldResetCacheEntry = true;
906 } else {
907 MOZ_ASSERT(!aExistingImage, "New part for non-multipart channel?");
908 MOZ_ASSERT(aProgressTracker, "Shouldn't have given away tracker yet");
910 // Create an image using our progress tracker.
911 result.mImage = image::ImageFactory::CreateImage(
912 aRequest, aProgressTracker, result.mContentType, aURI,
913 /* aIsMultipart = */ false, aInnerWindowId);
916 MOZ_ASSERT(result.mImage);
917 if (!result.mImage->HasError() || aIsMultipart) {
918 // We allow multipart images to fail to initialize (which generally
919 // indicates a bad content type) without cancelling the load, because
920 // subsequent parts might be fine.
921 result.mSucceeded = true;
924 return result;
927 class FinishPreparingForNewPartRunnable final : public Runnable {
928 public:
929 FinishPreparingForNewPartRunnable(imgRequest* aImgRequest,
930 NewPartResult&& aResult)
931 : Runnable("FinishPreparingForNewPartRunnable"),
932 mImgRequest(aImgRequest),
933 mResult(aResult) {
934 MOZ_ASSERT(aImgRequest);
937 NS_IMETHOD Run() override {
938 mImgRequest->FinishPreparingForNewPart(mResult);
939 return NS_OK;
942 private:
943 RefPtr<imgRequest> mImgRequest;
944 NewPartResult mResult;
947 void imgRequest::FinishPreparingForNewPart(const NewPartResult& aResult) {
948 MOZ_ASSERT(NS_IsMainThread());
950 mContentType = aResult.mContentType;
952 SetProperties(aResult.mContentType, aResult.mContentDisposition);
954 if (aResult.mIsFirstPart) {
955 // Notify listeners that we have an image.
956 mImageAvailable = true;
957 RefPtr<ProgressTracker> progressTracker = GetProgressTracker();
958 progressTracker->OnImageAvailable();
959 MOZ_ASSERT(progressTracker->HasImage());
962 if (aResult.mShouldResetCacheEntry) {
963 ResetCacheEntry();
966 if (IsDecodeRequested()) {
967 aResult.mImage->StartDecoding(imgIContainer::FLAG_NONE);
971 bool imgRequest::ImageAvailable() const { return mImageAvailable; }
973 NS_IMETHODIMP
974 imgRequest::OnDataAvailable(nsIRequest* aRequest, nsIInputStream* aInStr,
975 uint64_t aOffset, uint32_t aCount) {
976 LOG_SCOPE_WITH_PARAM(gImgLog, "imgRequest::OnDataAvailable", "count", aCount);
978 NS_ASSERTION(aRequest, "imgRequest::OnDataAvailable -- no request!");
980 RefPtr<Image> image;
981 RefPtr<ProgressTracker> progressTracker;
982 bool isMultipart = false;
983 bool newPartPending = false;
985 // Retrieve and update our state.
987 MutexAutoLock lock(mMutex);
988 image = mImage;
989 progressTracker = mProgressTracker;
990 isMultipart = mIsMultiPartChannel;
991 newPartPending = mNewPartPending;
992 mNewPartPending = false;
995 // If this is a new part, we need to sniff its content type and create an
996 // appropriate image.
997 if (newPartPending) {
998 NewPartResult result =
999 PrepareForNewPart(aRequest, aInStr, aCount, mURI, isMultipart, image,
1000 progressTracker, mInnerWindowId);
1001 bool succeeded = result.mSucceeded;
1003 if (result.mImage) {
1004 image = result.mImage;
1005 nsCOMPtr<nsIEventTarget> eventTarget;
1007 // Update our state to reflect this new part.
1009 MutexAutoLock lock(mMutex);
1010 mImage = image;
1012 // We only get an event target if we are not on the main thread, because
1013 // we have to dispatch in that case. If we are on the main thread, but
1014 // on a different scheduler group than ProgressTracker would give us,
1015 // that is okay because nothing in imagelib requires that, just our
1016 // listeners (which have their own checks).
1017 if (!NS_IsMainThread()) {
1018 eventTarget = mProgressTracker->GetEventTarget();
1019 MOZ_ASSERT(eventTarget);
1022 mProgressTracker = nullptr;
1025 // Some property objects are not threadsafe, and we need to send
1026 // OnImageAvailable on the main thread, so finish on the main thread.
1027 if (!eventTarget) {
1028 MOZ_ASSERT(NS_IsMainThread());
1029 FinishPreparingForNewPart(result);
1030 } else {
1031 nsCOMPtr<nsIRunnable> runnable =
1032 new FinishPreparingForNewPartRunnable(this, std::move(result));
1033 eventTarget->Dispatch(CreateMediumHighRunnable(runnable.forget()),
1034 NS_DISPATCH_NORMAL);
1038 if (!succeeded) {
1039 // Something went wrong; probably a content type issue.
1040 Cancel(NS_IMAGELIB_ERROR_FAILURE);
1041 return NS_BINDING_ABORTED;
1045 // Notify the image that it has new data.
1046 if (aInStr) {
1047 nsresult rv =
1048 image->OnImageDataAvailable(aRequest, nullptr, aInStr, aOffset, aCount);
1050 if (NS_FAILED(rv)) {
1051 MOZ_LOG(gImgLog, LogLevel::Warning,
1052 ("[this=%p] imgRequest::OnDataAvailable -- "
1053 "copy to RasterImage failed\n",
1054 this));
1055 Cancel(NS_IMAGELIB_ERROR_FAILURE);
1056 return NS_BINDING_ABORTED;
1060 return NS_OK;
1063 void imgRequest::SetProperties(const nsACString& aContentType,
1064 const nsACString& aContentDisposition) {
1065 /* set our mimetype as a property */
1066 nsCOMPtr<nsISupportsCString> contentType =
1067 do_CreateInstance("@mozilla.org/supports-cstring;1");
1068 if (contentType) {
1069 contentType->SetData(aContentType);
1070 mProperties->Set("type", contentType);
1073 /* set our content disposition as a property */
1074 if (!aContentDisposition.IsEmpty()) {
1075 nsCOMPtr<nsISupportsCString> contentDisposition =
1076 do_CreateInstance("@mozilla.org/supports-cstring;1");
1077 if (contentDisposition) {
1078 contentDisposition->SetData(aContentDisposition);
1079 mProperties->Set("content-disposition", contentDisposition);
1084 static nsresult sniff_mimetype_callback(nsIInputStream* in, void* data,
1085 const char* fromRawSegment,
1086 uint32_t toOffset, uint32_t count,
1087 uint32_t* writeCount) {
1088 mimetype_closure* closure = static_cast<mimetype_closure*>(data);
1090 NS_ASSERTION(closure, "closure is null!");
1092 if (count > 0) {
1093 imgLoader::GetMimeTypeFromContent(fromRawSegment, count, *closure->newType);
1096 *writeCount = 0;
1097 return NS_ERROR_FAILURE;
1100 /** nsIInterfaceRequestor methods **/
1102 NS_IMETHODIMP
1103 imgRequest::GetInterface(const nsIID& aIID, void** aResult) {
1104 if (!mPrevChannelSink || aIID.Equals(NS_GET_IID(nsIChannelEventSink))) {
1105 return QueryInterface(aIID, aResult);
1108 NS_ASSERTION(
1109 mPrevChannelSink != this,
1110 "Infinite recursion - don't keep track of channel sinks that are us!");
1111 return mPrevChannelSink->GetInterface(aIID, aResult);
1114 /** nsIChannelEventSink methods **/
1115 NS_IMETHODIMP
1116 imgRequest::AsyncOnChannelRedirect(nsIChannel* oldChannel,
1117 nsIChannel* newChannel, uint32_t flags,
1118 nsIAsyncVerifyRedirectCallback* callback) {
1119 NS_ASSERTION(mRequest && mChannel,
1120 "Got a channel redirect after we nulled out mRequest!");
1121 NS_ASSERTION(mChannel == oldChannel,
1122 "Got a channel redirect for an unknown channel!");
1123 NS_ASSERTION(newChannel, "Got a redirect to a NULL channel!");
1125 SetCacheValidation(mCacheEntry, oldChannel);
1127 // Prepare for callback
1128 mRedirectCallback = callback;
1129 mNewRedirectChannel = newChannel;
1131 nsCOMPtr<nsIChannelEventSink> sink(do_GetInterface(mPrevChannelSink));
1132 if (sink) {
1133 nsresult rv =
1134 sink->AsyncOnChannelRedirect(oldChannel, newChannel, flags, this);
1135 if (NS_FAILED(rv)) {
1136 mRedirectCallback = nullptr;
1137 mNewRedirectChannel = nullptr;
1139 return rv;
1142 (void)OnRedirectVerifyCallback(NS_OK);
1143 return NS_OK;
1146 NS_IMETHODIMP
1147 imgRequest::OnRedirectVerifyCallback(nsresult result) {
1148 NS_ASSERTION(mRedirectCallback, "mRedirectCallback not set in callback");
1149 NS_ASSERTION(mNewRedirectChannel, "mNewRedirectChannel not set in callback");
1151 if (NS_FAILED(result)) {
1152 mRedirectCallback->OnRedirectVerifyCallback(result);
1153 mRedirectCallback = nullptr;
1154 mNewRedirectChannel = nullptr;
1155 return NS_OK;
1158 mChannel = mNewRedirectChannel;
1159 mTimedChannel = do_QueryInterface(mChannel);
1160 mNewRedirectChannel = nullptr;
1162 if (LOG_TEST(LogLevel::Debug)) {
1163 LOG_MSG_WITH_PARAM(gImgLog, "imgRequest::OnChannelRedirect", "old",
1164 mFinalURI ? mFinalURI->GetSpecOrDefault().get() : "");
1167 // If the previous URI is a non-HTTPS URI, record that fact for later use by
1168 // security code, which needs to know whether there is an insecure load at any
1169 // point in the redirect chain.
1170 bool schemeLocal = false;
1171 if (NS_FAILED(NS_URIChainHasFlags(mFinalURI,
1172 nsIProtocolHandler::URI_IS_LOCAL_RESOURCE,
1173 &schemeLocal)) ||
1174 (!mFinalURI->SchemeIs("https") && !mFinalURI->SchemeIs("chrome") &&
1175 !schemeLocal)) {
1176 MutexAutoLock lock(mMutex);
1178 // The csp directive upgrade-insecure-requests performs an internal redirect
1179 // to upgrade all requests from http to https before any data is fetched
1180 // from the network. Do not pollute mHadInsecureRedirect in case of such an
1181 // internal redirect.
1182 nsCOMPtr<nsILoadInfo> loadInfo = mChannel->LoadInfo();
1183 bool upgradeInsecureRequests =
1184 loadInfo ? loadInfo->GetUpgradeInsecureRequests() ||
1185 loadInfo->GetBrowserUpgradeInsecureRequests()
1186 : false;
1187 if (!upgradeInsecureRequests) {
1188 mHadInsecureRedirect = true;
1192 // Update the final URI.
1193 mChannel->GetURI(getter_AddRefs(mFinalURI));
1195 if (LOG_TEST(LogLevel::Debug)) {
1196 LOG_MSG_WITH_PARAM(gImgLog, "imgRequest::OnChannelRedirect", "new",
1197 mFinalURI ? mFinalURI->GetSpecOrDefault().get() : "");
1200 // Make sure we have a protocol that returns data rather than opens an
1201 // external application, e.g. 'mailto:'.
1202 bool doesNotReturnData = false;
1203 nsresult rv = NS_URIChainHasFlags(
1204 mFinalURI, nsIProtocolHandler::URI_DOES_NOT_RETURN_DATA,
1205 &doesNotReturnData);
1207 if (NS_SUCCEEDED(rv) && doesNotReturnData) {
1208 rv = NS_ERROR_ABORT;
1211 if (NS_FAILED(rv)) {
1212 mRedirectCallback->OnRedirectVerifyCallback(rv);
1213 mRedirectCallback = nullptr;
1214 return NS_OK;
1217 mRedirectCallback->OnRedirectVerifyCallback(NS_OK);
1218 mRedirectCallback = nullptr;
1219 return NS_OK;