Thu, 22 Jan 2015 13:21:57 +0100
Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
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 "imgStatusTracker.h"
13 #include "ImageFactory.h"
14 #include "Image.h"
15 #include "RasterImage.h"
17 #include "nsIChannel.h"
18 #include "nsICachingChannel.h"
19 #include "nsIThreadRetargetableRequest.h"
20 #include "nsIInputStream.h"
21 #include "nsIMultiPartChannel.h"
22 #include "nsIHttpChannel.h"
23 #include "nsIApplicationCache.h"
24 #include "nsIApplicationCacheChannel.h"
25 #include "nsMimeTypes.h"
27 #include "nsIInterfaceRequestorUtils.h"
28 #include "nsISupportsPrimitives.h"
29 #include "nsIScriptSecurityManager.h"
30 #include "nsContentUtils.h"
32 #include "nsICacheEntry.h"
34 #include "plstr.h" // PL_strcasestr(...)
35 #include "nsNetUtil.h"
36 #include "nsIProtocolHandler.h"
37 #include "imgIRequest.h"
39 using namespace mozilla;
40 using namespace mozilla::image;
42 #if defined(PR_LOGGING)
43 PRLogModuleInfo *
44 GetImgLog()
45 {
46 static PRLogModuleInfo *sImgLog;
47 if (!sImgLog)
48 sImgLog = PR_NewLogModule("imgRequest");
49 return sImgLog;
50 }
51 #endif
53 NS_IMPL_ISUPPORTS(imgRequest,
54 nsIStreamListener, nsIRequestObserver,
55 nsIThreadRetargetableStreamListener,
56 nsIChannelEventSink,
57 nsIInterfaceRequestor,
58 nsIAsyncVerifyRedirectCallback)
60 imgRequest::imgRequest(imgLoader* aLoader)
61 : mLoader(aLoader)
62 , mStatusTracker(new imgStatusTracker(nullptr))
63 , mValidator(nullptr)
64 , mInnerWindowId(0)
65 , mCORSMode(imgIRequest::CORS_NONE)
66 , mDecodeRequested(false)
67 , mIsMultiPartChannel(false)
68 , mGotData(false)
69 , mIsInCache(false)
70 , mResniffMimeType(false)
71 { }
73 imgRequest::~imgRequest()
74 {
75 if (mURI) {
76 nsAutoCString spec;
77 mURI->GetSpec(spec);
78 LOG_FUNC_WITH_PARAM(GetImgLog(), "imgRequest::~imgRequest()", "keyuri", spec.get());
79 } else
80 LOG_FUNC(GetImgLog(), "imgRequest::~imgRequest()");
81 }
83 nsresult imgRequest::Init(nsIURI *aURI,
84 nsIURI *aCurrentURI,
85 nsIURI *aFirstPartyIsolationURI,
86 nsIRequest *aRequest,
87 nsIChannel *aChannel,
88 imgCacheEntry *aCacheEntry,
89 void *aLoadId,
90 nsIPrincipal* aLoadingPrincipal,
91 int32_t aCORSMode)
92 {
93 MOZ_ASSERT(NS_IsMainThread(), "Cannot use nsIURI off main thread!");
95 LOG_FUNC(GetImgLog(), "imgRequest::Init");
97 NS_ABORT_IF_FALSE(!mImage, "Multiple calls to init");
98 NS_ABORT_IF_FALSE(aURI, "No uri");
99 NS_ABORT_IF_FALSE(aCurrentURI, "No current uri");
100 NS_ABORT_IF_FALSE(aRequest, "No request");
101 NS_ABORT_IF_FALSE(aChannel, "No channel");
103 mProperties = do_CreateInstance("@mozilla.org/properties;1");
105 // Use ImageURL to ensure access to URI data off main thread.
106 mURI = new ImageURL(aURI);
107 mCurrentURI = aCurrentURI;
108 mFirstPartyIsolationURI = aFirstPartyIsolationURI;
109 mRequest = aRequest;
110 mChannel = aChannel;
111 mTimedChannel = do_QueryInterface(mChannel);
113 mLoadingPrincipal = aLoadingPrincipal;
114 mCORSMode = aCORSMode;
116 mChannel->GetNotificationCallbacks(getter_AddRefs(mPrevChannelSink));
118 NS_ASSERTION(mPrevChannelSink != this,
119 "Initializing with a channel that already calls back to us!");
121 mChannel->SetNotificationCallbacks(this);
123 mCacheEntry = aCacheEntry;
125 SetLoadId(aLoadId);
127 return NS_OK;
128 }
130 already_AddRefed<imgStatusTracker>
131 imgRequest::GetStatusTracker()
132 {
133 if (mImage && mGotData) {
134 NS_ABORT_IF_FALSE(!mStatusTracker,
135 "Should have given mStatusTracker to mImage");
136 return mImage->GetStatusTracker();
137 } else {
138 NS_ABORT_IF_FALSE(mStatusTracker,
139 "Should have mStatusTracker until we create mImage");
140 nsRefPtr<imgStatusTracker> statusTracker = mStatusTracker;
141 MOZ_ASSERT(statusTracker);
142 return statusTracker.forget();
143 }
144 }
146 void imgRequest::SetCacheEntry(imgCacheEntry *entry)
147 {
148 mCacheEntry = entry;
149 }
151 bool imgRequest::HasCacheEntry() const
152 {
153 return mCacheEntry != nullptr;
154 }
156 void imgRequest::ResetCacheEntry()
157 {
158 if (HasCacheEntry()) {
159 mCacheEntry->SetDataSize(0);
160 }
161 }
163 void imgRequest::AddProxy(imgRequestProxy *proxy)
164 {
165 NS_PRECONDITION(proxy, "null imgRequestProxy passed in");
166 LOG_SCOPE_WITH_PARAM(GetImgLog(), "imgRequest::AddProxy", "proxy", proxy);
168 // If we're empty before adding, we have to tell the loader we now have
169 // proxies.
170 nsRefPtr<imgStatusTracker> statusTracker = GetStatusTracker();
171 if (statusTracker->ConsumerCount() == 0) {
172 NS_ABORT_IF_FALSE(mURI, "Trying to SetHasProxies without key uri.");
173 mLoader->SetHasProxies(mFirstPartyIsolationURI, mURI);
174 }
176 statusTracker->AddConsumer(proxy);
177 }
179 nsresult imgRequest::RemoveProxy(imgRequestProxy *proxy, nsresult aStatus)
180 {
181 LOG_SCOPE_WITH_PARAM(GetImgLog(), "imgRequest::RemoveProxy", "proxy", proxy);
183 // This will remove our animation consumers, so after removing
184 // this proxy, we don't end up without proxies with observers, but still
185 // have animation consumers.
186 proxy->ClearAnimationConsumers();
188 // Let the status tracker do its thing before we potentially call Cancel()
189 // below, because Cancel() may result in OnStopRequest being called back
190 // before Cancel() returns, leaving the image in a different state then the
191 // one it was in at this point.
192 nsRefPtr<imgStatusTracker> statusTracker = GetStatusTracker();
193 if (!statusTracker->RemoveConsumer(proxy, aStatus))
194 return NS_OK;
196 if (statusTracker->ConsumerCount() == 0) {
197 // If we have no observers, there's nothing holding us alive. If we haven't
198 // been cancelled and thus removed from the cache, tell the image loader so
199 // we can be evicted from the cache.
200 if (mCacheEntry) {
201 NS_ABORT_IF_FALSE(mURI, "Removing last observer without key uri.");
203 mLoader->SetHasNoProxies(mURI, mCacheEntry);
204 }
205 #if defined(PR_LOGGING)
206 else {
207 nsAutoCString spec;
208 mURI->GetSpec(spec);
209 LOG_MSG_WITH_PARAM(GetImgLog(), "imgRequest::RemoveProxy no cache entry", "uri", spec.get());
210 }
211 #endif
213 /* If |aStatus| is a failure code, then cancel the load if it is still in progress.
214 Otherwise, let the load continue, keeping 'this' in the cache with no observers.
215 This way, if a proxy is destroyed without calling cancel on it, it won't leak
216 and won't leave a bad pointer in the observer list.
217 */
218 if (statusTracker->IsLoading() && NS_FAILED(aStatus)) {
219 LOG_MSG(GetImgLog(), "imgRequest::RemoveProxy", "load in progress. canceling");
221 this->Cancel(NS_BINDING_ABORTED);
222 }
224 /* break the cycle from the cache entry. */
225 mCacheEntry = nullptr;
226 }
228 // If a proxy is removed for a reason other than its owner being
229 // changed, remove the proxy from the loadgroup.
230 if (aStatus != NS_IMAGELIB_CHANGING_OWNER)
231 proxy->RemoveFromLoadGroup(true);
233 return NS_OK;
234 }
236 void imgRequest::CancelAndAbort(nsresult aStatus)
237 {
238 LOG_SCOPE(GetImgLog(), "imgRequest::CancelAndAbort");
240 Cancel(aStatus);
242 // It's possible for the channel to fail to open after we've set our
243 // notification callbacks. In that case, make sure to break the cycle between
244 // the channel and us, because it won't.
245 if (mChannel) {
246 mChannel->SetNotificationCallbacks(mPrevChannelSink);
247 mPrevChannelSink = nullptr;
248 }
249 }
251 class imgRequestMainThreadCancel : public nsRunnable
252 {
253 public:
254 imgRequestMainThreadCancel(imgRequest *aImgRequest, nsresult aStatus)
255 : mImgRequest(aImgRequest)
256 , mStatus(aStatus)
257 {
258 MOZ_ASSERT(!NS_IsMainThread(), "Create me off main thread only!");
259 MOZ_ASSERT(aImgRequest);
260 }
262 NS_IMETHOD Run()
263 {
264 MOZ_ASSERT(NS_IsMainThread(), "I should be running on the main thread!");
265 mImgRequest->ContinueCancel(mStatus);
266 return NS_OK;
267 }
268 private:
269 nsRefPtr<imgRequest> mImgRequest;
270 nsresult mStatus;
271 };
273 void imgRequest::Cancel(nsresult aStatus)
274 {
275 /* The Cancel() method here should only be called by this class. */
277 LOG_SCOPE(GetImgLog(), "imgRequest::Cancel");
279 nsRefPtr<imgStatusTracker> statusTracker = GetStatusTracker();
281 statusTracker->MaybeUnblockOnload();
283 statusTracker->RecordCancel();
285 if (NS_IsMainThread()) {
286 ContinueCancel(aStatus);
287 } else {
288 NS_DispatchToMainThread(new imgRequestMainThreadCancel(this, aStatus));
289 }
290 }
292 void imgRequest::ContinueCancel(nsresult aStatus)
293 {
294 MOZ_ASSERT(NS_IsMainThread());
296 RemoveFromCache();
298 nsRefPtr<imgStatusTracker> statusTracker = GetStatusTracker();
299 if (mRequest && statusTracker->IsLoading()) {
300 mRequest->Cancel(aStatus);
301 }
302 }
304 nsresult imgRequest::GetURI(ImageURL **aURI)
305 {
306 MOZ_ASSERT(aURI);
308 LOG_FUNC(GetImgLog(), "imgRequest::GetURI");
310 if (mURI) {
311 *aURI = mURI;
312 NS_ADDREF(*aURI);
313 return NS_OK;
314 }
316 return NS_ERROR_FAILURE;
317 }
319 nsresult imgRequest::GetSecurityInfo(nsISupports **aSecurityInfo)
320 {
321 LOG_FUNC(GetImgLog(), "imgRequest::GetSecurityInfo");
323 // Missing security info means this is not a security load
324 // i.e. it is not an error when security info is missing
325 NS_IF_ADDREF(*aSecurityInfo = mSecurityInfo);
326 return NS_OK;
327 }
329 void imgRequest::RemoveFromCache()
330 {
331 LOG_SCOPE(GetImgLog(), "imgRequest::RemoveFromCache");
333 if (mIsInCache) {
334 // mCacheEntry is nulled out when we have no more observers.
335 if (mCacheEntry)
336 mLoader->RemoveFromCache(mCacheEntry);
337 else {
338 mLoader->RemoveFromCache(mLoader->GetCacheKey(mFirstPartyIsolationURI, mURI, nullptr),
339 mLoader->GetCache(mURI),
340 mLoader->GetCacheQueue(mURI));
341 }
342 }
344 mCacheEntry = nullptr;
345 }
347 int32_t imgRequest::Priority() const
348 {
349 int32_t priority = nsISupportsPriority::PRIORITY_NORMAL;
350 nsCOMPtr<nsISupportsPriority> p = do_QueryInterface(mRequest);
351 if (p)
352 p->GetPriority(&priority);
353 return priority;
354 }
356 void imgRequest::AdjustPriority(imgRequestProxy *proxy, int32_t delta)
357 {
358 // only the first proxy is allowed to modify the priority of this image load.
359 //
360 // XXX(darin): this is probably not the most optimal algorithm as we may want
361 // to increase the priority of requests that have a lot of proxies. the key
362 // concern though is that image loads remain lower priority than other pieces
363 // of content such as link clicks, CSS, and JS.
364 //
365 nsRefPtr<imgStatusTracker> statusTracker = GetStatusTracker();
366 if (!statusTracker->FirstConsumerIs(proxy))
367 return;
369 nsCOMPtr<nsISupportsPriority> p = do_QueryInterface(mChannel);
370 if (p)
371 p->AdjustPriority(delta);
372 }
374 void imgRequest::SetIsInCache(bool incache)
375 {
376 LOG_FUNC_WITH_PARAM(GetImgLog(), "imgRequest::SetIsCacheable", "incache", incache);
377 mIsInCache = incache;
378 }
380 void imgRequest::UpdateCacheEntrySize()
381 {
382 if (mCacheEntry)
383 mCacheEntry->SetDataSize(mImage->SizeOfData());
384 }
386 void imgRequest::SetCacheValidation(imgCacheEntry* aCacheEntry, nsIRequest* aRequest)
387 {
388 /* get the expires info */
389 if (aCacheEntry) {
390 nsCOMPtr<nsICachingChannel> cacheChannel(do_QueryInterface(aRequest));
391 if (cacheChannel) {
392 nsCOMPtr<nsISupports> cacheToken;
393 cacheChannel->GetCacheToken(getter_AddRefs(cacheToken));
394 if (cacheToken) {
395 nsCOMPtr<nsICacheEntry> entryDesc(do_QueryInterface(cacheToken));
396 if (entryDesc) {
397 uint32_t expiration;
398 /* get the expiration time from the caching channel's token */
399 entryDesc->GetExpirationTime(&expiration);
401 // Expiration time defaults to 0. We set the expiration time on our
402 // entry if it hasn't been set yet.
403 if (aCacheEntry->GetExpiryTime() == 0)
404 aCacheEntry->SetExpiryTime(expiration);
405 }
406 }
407 }
409 // Determine whether the cache entry must be revalidated when we try to use it.
410 // Currently, only HTTP specifies this information...
411 nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(aRequest));
412 if (httpChannel) {
413 bool bMustRevalidate = false;
415 httpChannel->IsNoStoreResponse(&bMustRevalidate);
417 if (!bMustRevalidate) {
418 httpChannel->IsNoCacheResponse(&bMustRevalidate);
419 }
421 if (!bMustRevalidate) {
422 nsAutoCString cacheHeader;
424 httpChannel->GetResponseHeader(NS_LITERAL_CSTRING("Cache-Control"),
425 cacheHeader);
426 if (PL_strcasestr(cacheHeader.get(), "must-revalidate")) {
427 bMustRevalidate = true;
428 }
429 }
431 // Cache entries default to not needing to validate. We ensure that
432 // multiple calls to this function don't override an earlier decision to
433 // validate by making validation a one-way decision.
434 if (bMustRevalidate)
435 aCacheEntry->SetMustValidate(bMustRevalidate);
436 }
438 // We always need to validate file URIs.
439 nsCOMPtr<nsIChannel> channel = do_QueryInterface(aRequest);
440 if (channel) {
441 nsCOMPtr<nsIURI> uri;
442 channel->GetURI(getter_AddRefs(uri));
443 bool isfile = false;
444 uri->SchemeIs("file", &isfile);
445 if (isfile)
446 aCacheEntry->SetMustValidate(isfile);
447 }
448 }
449 }
451 namespace { // anon
453 already_AddRefed<nsIApplicationCache>
454 GetApplicationCache(nsIRequest* aRequest)
455 {
456 nsresult rv;
458 nsCOMPtr<nsIApplicationCacheChannel> appCacheChan = do_QueryInterface(aRequest);
459 if (!appCacheChan) {
460 return nullptr;
461 }
463 bool fromAppCache;
464 rv = appCacheChan->GetLoadedFromApplicationCache(&fromAppCache);
465 NS_ENSURE_SUCCESS(rv, nullptr);
467 if (!fromAppCache) {
468 return nullptr;
469 }
471 nsCOMPtr<nsIApplicationCache> appCache;
472 rv = appCacheChan->GetApplicationCache(getter_AddRefs(appCache));
473 NS_ENSURE_SUCCESS(rv, nullptr);
475 return appCache.forget();
476 }
478 } // anon
480 bool
481 imgRequest::CacheChanged(nsIRequest* aNewRequest)
482 {
483 nsCOMPtr<nsIApplicationCache> newAppCache = GetApplicationCache(aNewRequest);
485 // Application cache not involved at all or the same app cache involved
486 // in both of the loads (original and new).
487 if (newAppCache == mApplicationCache)
488 return false;
490 // In a rare case it may happen that two objects still refer
491 // the same application cache version.
492 if (newAppCache && mApplicationCache) {
493 nsresult rv;
495 nsAutoCString oldAppCacheClientId, newAppCacheClientId;
496 rv = mApplicationCache->GetClientID(oldAppCacheClientId);
497 NS_ENSURE_SUCCESS(rv, true);
498 rv = newAppCache->GetClientID(newAppCacheClientId);
499 NS_ENSURE_SUCCESS(rv, true);
501 if (oldAppCacheClientId == newAppCacheClientId)
502 return false;
503 }
505 // When we get here, app caches differ or app cache is involved
506 // just in one of the loads what we also consider as a change
507 // in a loading cache.
508 return true;
509 }
511 nsresult
512 imgRequest::LockImage()
513 {
514 return mImage->LockImage();
515 }
517 nsresult
518 imgRequest::UnlockImage()
519 {
520 return mImage->UnlockImage();
521 }
523 nsresult
524 imgRequest::RequestDecode()
525 {
526 // If we've initialized our image, we can request a decode.
527 if (mImage) {
528 return mImage->RequestDecode();
529 }
531 // Otherwise, flag to do it when we get the image
532 mDecodeRequested = true;
534 return NS_OK;
535 }
537 nsresult
538 imgRequest::StartDecoding()
539 {
540 // If we've initialized our image, we can request a decode.
541 if (mImage) {
542 return mImage->StartDecoding();
543 }
545 // Otherwise, flag to do it when we get the image
546 mDecodeRequested = true;
548 return NS_OK;
549 }
551 /** nsIRequestObserver methods **/
553 /* void onStartRequest (in nsIRequest request, in nsISupports ctxt); */
554 NS_IMETHODIMP imgRequest::OnStartRequest(nsIRequest *aRequest, nsISupports *ctxt)
555 {
556 LOG_SCOPE(GetImgLog(), "imgRequest::OnStartRequest");
558 // Figure out if we're multipart
559 nsCOMPtr<nsIMultiPartChannel> mpchan(do_QueryInterface(aRequest));
560 nsRefPtr<imgStatusTracker> statusTracker = GetStatusTracker();
561 if (mpchan) {
562 mIsMultiPartChannel = true;
563 statusTracker->SetIsMultipart();
564 }
566 // If we're not multipart, we shouldn't have an image yet
567 NS_ABORT_IF_FALSE(mIsMultiPartChannel || !mImage,
568 "Already have an image for non-multipart request");
570 // If we're multipart and about to load another image, signal so we can
571 // detect the mime type in OnDataAvailable.
572 if (mIsMultiPartChannel && mImage) {
573 mResniffMimeType = true;
575 // Tell the image to reinitialize itself. We have to do this in
576 // OnStartRequest so that its state machine is always in a consistent
577 // state.
578 // Note that if our MIME type changes, mImage will be replaced with a
579 // new object.
580 mImage->OnNewSourceData();
581 }
583 /*
584 * If mRequest is null here, then we need to set it so that we'll be able to
585 * cancel it if our Cancel() method is called. Note that this can only
586 * happen for multipart channels. We could simply not null out mRequest for
587 * non-last parts, if GetIsLastPart() were reliable, but it's not. See
588 * https://bugzilla.mozilla.org/show_bug.cgi?id=339610
589 */
590 if (!mRequest) {
591 NS_ASSERTION(mpchan,
592 "We should have an mRequest here unless we're multipart");
593 nsCOMPtr<nsIChannel> chan;
594 mpchan->GetBaseChannel(getter_AddRefs(chan));
595 mRequest = chan;
596 }
598 // Note: refreshing statusTracker in case OnNewSourceData changed it.
599 statusTracker = GetStatusTracker();
600 statusTracker->OnStartRequest();
602 nsCOMPtr<nsIChannel> channel(do_QueryInterface(aRequest));
603 if (channel)
604 channel->GetSecurityInfo(getter_AddRefs(mSecurityInfo));
606 /* Get our principal */
607 nsCOMPtr<nsIChannel> chan(do_QueryInterface(aRequest));
608 if (chan) {
609 nsCOMPtr<nsIScriptSecurityManager> secMan = nsContentUtils::GetSecurityManager();
610 if (secMan) {
611 nsresult rv = secMan->GetChannelPrincipal(chan,
612 getter_AddRefs(mPrincipal));
613 if (NS_FAILED(rv)) {
614 return rv;
615 }
616 }
617 }
619 SetCacheValidation(mCacheEntry, aRequest);
621 mApplicationCache = GetApplicationCache(aRequest);
623 // Shouldn't we be dead already if this gets hit? Probably multipart/x-mixed-replace...
624 if (statusTracker->ConsumerCount() == 0) {
625 this->Cancel(NS_IMAGELIB_ERROR_FAILURE);
626 }
628 // Try to retarget OnDataAvailable to a decode thread.
629 nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aRequest);
630 nsCOMPtr<nsIThreadRetargetableRequest> retargetable =
631 do_QueryInterface(aRequest);
632 if (httpChannel && retargetable &&
633 ImageFactory::CanRetargetOnDataAvailable(mURI, mIsMultiPartChannel)) {
634 nsAutoCString mimeType;
635 nsresult rv = httpChannel->GetContentType(mimeType);
636 if (NS_SUCCEEDED(rv) && !mimeType.EqualsLiteral(IMAGE_SVG_XML)) {
637 // Image object not created until OnDataAvailable, so forward to static
638 // DecodePool directly.
639 nsCOMPtr<nsIEventTarget> target = RasterImage::GetEventTarget();
640 rv = retargetable->RetargetDeliveryTo(target);
641 }
642 PR_LOG(GetImgLog(), PR_LOG_WARNING,
643 ("[this=%p] imgRequest::OnStartRequest -- "
644 "RetargetDeliveryTo rv %d=%s\n",
645 this, rv, NS_SUCCEEDED(rv) ? "succeeded" : "failed"));
646 }
648 return NS_OK;
649 }
651 /* void onStopRequest (in nsIRequest request, in nsISupports ctxt, in nsresult status); */
652 NS_IMETHODIMP imgRequest::OnStopRequest(nsIRequest *aRequest, nsISupports *ctxt, nsresult status)
653 {
654 LOG_FUNC(GetImgLog(), "imgRequest::OnStopRequest");
656 // XXXldb What if this is a non-last part of a multipart request?
657 // xxx before we release our reference to mRequest, lets
658 // save the last status that we saw so that the
659 // imgRequestProxy will have access to it.
660 if (mRequest) {
661 mRequest = nullptr; // we no longer need the request
662 }
664 // stop holding a ref to the channel, since we don't need it anymore
665 if (mChannel) {
666 mChannel->SetNotificationCallbacks(mPrevChannelSink);
667 mPrevChannelSink = nullptr;
668 mChannel = nullptr;
669 }
671 bool lastPart = true;
672 nsCOMPtr<nsIMultiPartChannel> mpchan(do_QueryInterface(aRequest));
673 if (mpchan)
674 mpchan->GetIsLastPart(&lastPart);
676 // Tell the image that it has all of the source data. Note that this can
677 // trigger a failure, since the image might be waiting for more non-optional
678 // data and this is the point where we break the news that it's not coming.
679 if (mImage) {
680 nsresult rv = mImage->OnImageDataComplete(aRequest, ctxt, status, lastPart);
682 // If we got an error in the OnImageDataComplete() call, we don't want to
683 // proceed as if nothing bad happened. However, we also want to give
684 // precedence to failure status codes from necko, since presumably they're
685 // more meaningful.
686 if (NS_FAILED(rv) && NS_SUCCEEDED(status))
687 status = rv;
688 }
690 // If the request went through, update the cache entry size. Otherwise,
691 // cancel the request, which removes us from the cache.
692 if (mImage && NS_SUCCEEDED(status)) {
693 // We update the cache entry size here because this is where we finish
694 // loading compressed source data, which is part of our size calculus.
695 UpdateCacheEntrySize();
696 }
697 else {
698 // stops animations, removes from cache
699 this->Cancel(status);
700 }
702 if (!mImage) {
703 // We have to fire imgStatusTracker::OnStopRequest ourselves because there's
704 // no image capable of doing so.
705 nsRefPtr<imgStatusTracker> statusTracker = GetStatusTracker();
706 statusTracker->OnStopRequest(lastPart, status);
707 }
709 mTimedChannel = nullptr;
710 return NS_OK;
711 }
713 struct mimetype_closure
714 {
715 nsACString* newType;
716 };
718 /* prototype for these defined below */
719 static NS_METHOD sniff_mimetype_callback(nsIInputStream* in, void* closure, const char* fromRawSegment,
720 uint32_t toOffset, uint32_t count, uint32_t *writeCount);
722 /** nsThreadRetargetableStreamListener methods **/
723 NS_IMETHODIMP
724 imgRequest::CheckListenerChain()
725 {
726 // TODO Might need more checking here.
727 NS_ASSERTION(NS_IsMainThread(), "Should be on the main thread!");
728 return NS_OK;
729 }
731 /** nsIStreamListener methods **/
733 /* void onDataAvailable (in nsIRequest request, in nsISupports ctxt, in nsIInputStream inStr, in unsigned long long sourceOffset, in unsigned long count); */
734 NS_IMETHODIMP
735 imgRequest::OnDataAvailable(nsIRequest *aRequest, nsISupports *ctxt,
736 nsIInputStream *inStr, uint64_t sourceOffset,
737 uint32_t count)
738 {
739 LOG_SCOPE_WITH_PARAM(GetImgLog(), "imgRequest::OnDataAvailable", "count", count);
741 NS_ASSERTION(aRequest, "imgRequest::OnDataAvailable -- no request!");
743 nsresult rv;
745 if (!mGotData || mResniffMimeType) {
746 LOG_SCOPE(GetImgLog(), "imgRequest::OnDataAvailable |First time through... finding mimetype|");
748 mGotData = true;
750 // Store and reset this for the invariant that it's always false after
751 // calls to OnDataAvailable (see bug 907575)
752 bool resniffMimeType = mResniffMimeType;
753 mResniffMimeType = false;
755 mimetype_closure closure;
756 nsAutoCString newType;
757 closure.newType = &newType;
759 /* look at the first few bytes and see if we can tell what the data is from that
760 * since servers tend to lie. :(
761 */
762 uint32_t out;
763 inStr->ReadSegments(sniff_mimetype_callback, &closure, count, &out);
765 nsCOMPtr<nsIChannel> chan(do_QueryInterface(aRequest));
766 if (newType.IsEmpty()) {
767 LOG_SCOPE(GetImgLog(), "imgRequest::OnDataAvailable |sniffing of mimetype failed|");
769 rv = NS_ERROR_FAILURE;
770 if (chan) {
771 rv = chan->GetContentType(newType);
772 }
774 if (NS_FAILED(rv)) {
775 PR_LOG(GetImgLog(), PR_LOG_ERROR,
776 ("[this=%p] imgRequest::OnDataAvailable -- Content type unavailable from the channel\n",
777 this));
779 this->Cancel(NS_IMAGELIB_ERROR_FAILURE);
781 return NS_BINDING_ABORTED;
782 }
784 LOG_MSG(GetImgLog(), "imgRequest::OnDataAvailable", "Got content type from the channel");
785 }
787 // If we're a regular image and this is the first call to OnDataAvailable,
788 // this will always be true. If we've resniffed our MIME type (i.e. we're a
789 // multipart/x-mixed-replace image), we have to be able to switch our image
790 // type and decoder.
791 // We always reinitialize for SVGs, because they have no way of
792 // reinitializing themselves.
793 if (mContentType != newType || newType.EqualsLiteral(IMAGE_SVG_XML)) {
794 mContentType = newType;
796 // If we've resniffed our MIME type and it changed, we need to create a
797 // new status tracker to give to the image, because we don't have one of
798 // our own any more.
799 if (resniffMimeType) {
800 NS_ABORT_IF_FALSE(mIsMultiPartChannel, "Resniffing a non-multipart image");
802 nsRefPtr<imgStatusTracker> freshTracker = new imgStatusTracker(nullptr);
803 nsRefPtr<imgStatusTracker> oldStatusTracker = GetStatusTracker();
804 freshTracker->AdoptConsumers(oldStatusTracker);
805 mStatusTracker = freshTracker.forget();
806 }
808 SetProperties(chan);
810 LOG_MSG_WITH_PARAM(GetImgLog(), "imgRequest::OnDataAvailable", "content type", mContentType.get());
812 // XXX If server lied about mimetype and it's SVG, we may need to copy
813 // the data and dispatch back to the main thread, AND tell the channel to
814 // dispatch there in the future.
816 // Now we can create a new image to hold the data. If we don't have a decoder
817 // for this mimetype we'll find out about it here.
818 mImage = ImageFactory::CreateImage(aRequest, mStatusTracker, mContentType,
819 mURI, mIsMultiPartChannel,
820 static_cast<uint32_t>(mInnerWindowId));
822 // Release our copy of the status tracker since the image owns it now.
823 mStatusTracker = nullptr;
825 // Notify listeners that we have an image.
826 // XXX(seth): The name of this notification method is pretty misleading.
827 nsRefPtr<imgStatusTracker> statusTracker = GetStatusTracker();
828 statusTracker->OnDataAvailable();
830 if (mImage->HasError() && !mIsMultiPartChannel) { // Probably bad mimetype
831 // We allow multipart images to fail to initialize without cancelling the
832 // load because subsequent images might be fine; thus only single part
833 // images end up here.
834 this->Cancel(NS_ERROR_FAILURE);
835 return NS_BINDING_ABORTED;
836 }
838 NS_ABORT_IF_FALSE(statusTracker->HasImage(), "Status tracker should have an image!");
839 NS_ABORT_IF_FALSE(mImage, "imgRequest should have an image!");
841 if (mDecodeRequested)
842 mImage->StartDecoding();
843 }
844 }
846 // Notify the image that it has new data.
847 rv = mImage->OnImageDataAvailable(aRequest, ctxt, inStr, sourceOffset, count);
849 if (NS_FAILED(rv)) {
850 PR_LOG(GetImgLog(), PR_LOG_WARNING,
851 ("[this=%p] imgRequest::OnDataAvailable -- "
852 "copy to RasterImage failed\n", this));
853 this->Cancel(NS_IMAGELIB_ERROR_FAILURE);
854 return NS_BINDING_ABORTED;
855 }
857 return NS_OK;
858 }
860 class SetPropertiesEvent : public nsRunnable
861 {
862 public:
863 SetPropertiesEvent(imgRequest* aImgRequest, nsIChannel* aChan)
864 : mImgRequest(aImgRequest)
865 , mChan(aChan)
866 {
867 MOZ_ASSERT(!NS_IsMainThread(), "Should be created off the main thread");
868 MOZ_ASSERT(aImgRequest, "aImgRequest cannot be null");
869 }
870 NS_IMETHOD Run()
871 {
872 MOZ_ASSERT(NS_IsMainThread(), "Should run on the main thread only");
873 MOZ_ASSERT(mImgRequest, "mImgRequest cannot be null");
874 mImgRequest->SetProperties(mChan);
875 return NS_OK;
876 }
877 private:
878 nsRefPtr<imgRequest> mImgRequest;
879 nsCOMPtr<nsIChannel> mChan;
880 };
882 void
883 imgRequest::SetProperties(nsIChannel* aChan)
884 {
885 // Force execution on main thread since some property objects are non
886 // threadsafe.
887 if (!NS_IsMainThread()) {
888 NS_DispatchToMainThread(new SetPropertiesEvent(this, aChan));
889 return;
890 }
891 /* set our mimetype as a property */
892 nsCOMPtr<nsISupportsCString> contentType(do_CreateInstance("@mozilla.org/supports-cstring;1"));
893 if (contentType) {
894 contentType->SetData(mContentType);
895 mProperties->Set("type", contentType);
896 }
898 /* set our content disposition as a property */
899 nsAutoCString disposition;
900 if (aChan) {
901 aChan->GetContentDispositionHeader(disposition);
902 }
903 if (!disposition.IsEmpty()) {
904 nsCOMPtr<nsISupportsCString> contentDisposition(do_CreateInstance("@mozilla.org/supports-cstring;1"));
905 if (contentDisposition) {
906 contentDisposition->SetData(disposition);
907 mProperties->Set("content-disposition", contentDisposition);
908 }
909 }
910 }
912 static NS_METHOD sniff_mimetype_callback(nsIInputStream* in,
913 void* data,
914 const char* fromRawSegment,
915 uint32_t toOffset,
916 uint32_t count,
917 uint32_t *writeCount)
918 {
919 mimetype_closure* closure = static_cast<mimetype_closure*>(data);
921 NS_ASSERTION(closure, "closure is null!");
923 if (count > 0)
924 imgLoader::GetMimeTypeFromContent(fromRawSegment, count, *closure->newType);
926 *writeCount = 0;
927 return NS_ERROR_FAILURE;
928 }
931 /** nsIInterfaceRequestor methods **/
933 NS_IMETHODIMP
934 imgRequest::GetInterface(const nsIID & aIID, void **aResult)
935 {
936 if (!mPrevChannelSink || aIID.Equals(NS_GET_IID(nsIChannelEventSink)))
937 return QueryInterface(aIID, aResult);
939 NS_ASSERTION(mPrevChannelSink != this,
940 "Infinite recursion - don't keep track of channel sinks that are us!");
941 return mPrevChannelSink->GetInterface(aIID, aResult);
942 }
944 /** nsIChannelEventSink methods **/
945 NS_IMETHODIMP
946 imgRequest::AsyncOnChannelRedirect(nsIChannel *oldChannel,
947 nsIChannel *newChannel, uint32_t flags,
948 nsIAsyncVerifyRedirectCallback *callback)
949 {
950 NS_ASSERTION(mRequest && mChannel, "Got a channel redirect after we nulled out mRequest!");
951 NS_ASSERTION(mChannel == oldChannel, "Got a channel redirect for an unknown channel!");
952 NS_ASSERTION(newChannel, "Got a redirect to a NULL channel!");
954 SetCacheValidation(mCacheEntry, oldChannel);
956 // Prepare for callback
957 mRedirectCallback = callback;
958 mNewRedirectChannel = newChannel;
960 nsCOMPtr<nsIChannelEventSink> sink(do_GetInterface(mPrevChannelSink));
961 if (sink) {
962 nsresult rv = sink->AsyncOnChannelRedirect(oldChannel, newChannel, flags,
963 this);
964 if (NS_FAILED(rv)) {
965 mRedirectCallback = nullptr;
966 mNewRedirectChannel = nullptr;
967 }
968 return rv;
969 }
971 (void) OnRedirectVerifyCallback(NS_OK);
972 return NS_OK;
973 }
975 NS_IMETHODIMP
976 imgRequest::OnRedirectVerifyCallback(nsresult result)
977 {
978 NS_ASSERTION(mRedirectCallback, "mRedirectCallback not set in callback");
979 NS_ASSERTION(mNewRedirectChannel, "mNewRedirectChannel not set in callback");
981 if (NS_FAILED(result)) {
982 mRedirectCallback->OnRedirectVerifyCallback(result);
983 mRedirectCallback = nullptr;
984 mNewRedirectChannel = nullptr;
985 return NS_OK;
986 }
988 mChannel = mNewRedirectChannel;
989 mTimedChannel = do_QueryInterface(mChannel);
990 mNewRedirectChannel = nullptr;
992 #if defined(PR_LOGGING)
993 nsAutoCString oldspec;
994 if (mCurrentURI)
995 mCurrentURI->GetSpec(oldspec);
996 LOG_MSG_WITH_PARAM(GetImgLog(), "imgRequest::OnChannelRedirect", "old", oldspec.get());
997 #endif
999 // make sure we have a protocol that returns data rather than opens
1000 // an external application, e.g. mailto:
1001 mChannel->GetURI(getter_AddRefs(mCurrentURI));
1002 bool doesNotReturnData = false;
1003 nsresult rv =
1004 NS_URIChainHasFlags(mCurrentURI, nsIProtocolHandler::URI_DOES_NOT_RETURN_DATA,
1005 &doesNotReturnData);
1007 if (NS_SUCCEEDED(rv) && doesNotReturnData)
1008 rv = NS_ERROR_ABORT;
1010 if (NS_FAILED(rv)) {
1011 mRedirectCallback->OnRedirectVerifyCallback(rv);
1012 mRedirectCallback = nullptr;
1013 return NS_OK;
1014 }
1016 mRedirectCallback->OnRedirectVerifyCallback(NS_OK);
1017 mRedirectCallback = nullptr;
1018 return NS_OK;
1019 }