michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- michael@0: * michael@0: * This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "imgRequest.h" michael@0: #include "ImageLogging.h" michael@0: michael@0: #include "imgLoader.h" michael@0: #include "imgRequestProxy.h" michael@0: #include "imgStatusTracker.h" michael@0: #include "ImageFactory.h" michael@0: #include "Image.h" michael@0: #include "RasterImage.h" michael@0: michael@0: #include "nsIChannel.h" michael@0: #include "nsICachingChannel.h" michael@0: #include "nsIThreadRetargetableRequest.h" michael@0: #include "nsIInputStream.h" michael@0: #include "nsIMultiPartChannel.h" michael@0: #include "nsIHttpChannel.h" michael@0: #include "nsIApplicationCache.h" michael@0: #include "nsIApplicationCacheChannel.h" michael@0: #include "nsMimeTypes.h" michael@0: michael@0: #include "nsIInterfaceRequestorUtils.h" michael@0: #include "nsISupportsPrimitives.h" michael@0: #include "nsIScriptSecurityManager.h" michael@0: #include "nsContentUtils.h" michael@0: michael@0: #include "nsICacheEntry.h" michael@0: michael@0: #include "plstr.h" // PL_strcasestr(...) michael@0: #include "nsNetUtil.h" michael@0: #include "nsIProtocolHandler.h" michael@0: #include "imgIRequest.h" michael@0: michael@0: using namespace mozilla; michael@0: using namespace mozilla::image; michael@0: michael@0: #if defined(PR_LOGGING) michael@0: PRLogModuleInfo * michael@0: GetImgLog() michael@0: { michael@0: static PRLogModuleInfo *sImgLog; michael@0: if (!sImgLog) michael@0: sImgLog = PR_NewLogModule("imgRequest"); michael@0: return sImgLog; michael@0: } michael@0: #endif michael@0: michael@0: NS_IMPL_ISUPPORTS(imgRequest, michael@0: nsIStreamListener, nsIRequestObserver, michael@0: nsIThreadRetargetableStreamListener, michael@0: nsIChannelEventSink, michael@0: nsIInterfaceRequestor, michael@0: nsIAsyncVerifyRedirectCallback) michael@0: michael@0: imgRequest::imgRequest(imgLoader* aLoader) michael@0: : mLoader(aLoader) michael@0: , mStatusTracker(new imgStatusTracker(nullptr)) michael@0: , mValidator(nullptr) michael@0: , mInnerWindowId(0) michael@0: , mCORSMode(imgIRequest::CORS_NONE) michael@0: , mDecodeRequested(false) michael@0: , mIsMultiPartChannel(false) michael@0: , mGotData(false) michael@0: , mIsInCache(false) michael@0: , mResniffMimeType(false) michael@0: { } michael@0: michael@0: imgRequest::~imgRequest() michael@0: { michael@0: if (mURI) { michael@0: nsAutoCString spec; michael@0: mURI->GetSpec(spec); michael@0: LOG_FUNC_WITH_PARAM(GetImgLog(), "imgRequest::~imgRequest()", "keyuri", spec.get()); michael@0: } else michael@0: LOG_FUNC(GetImgLog(), "imgRequest::~imgRequest()"); michael@0: } michael@0: michael@0: nsresult imgRequest::Init(nsIURI *aURI, michael@0: nsIURI *aCurrentURI, michael@0: nsIURI *aFirstPartyIsolationURI, michael@0: nsIRequest *aRequest, michael@0: nsIChannel *aChannel, michael@0: imgCacheEntry *aCacheEntry, michael@0: void *aLoadId, michael@0: nsIPrincipal* aLoadingPrincipal, michael@0: int32_t aCORSMode) michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread(), "Cannot use nsIURI off main thread!"); michael@0: michael@0: LOG_FUNC(GetImgLog(), "imgRequest::Init"); michael@0: michael@0: NS_ABORT_IF_FALSE(!mImage, "Multiple calls to init"); michael@0: NS_ABORT_IF_FALSE(aURI, "No uri"); michael@0: NS_ABORT_IF_FALSE(aCurrentURI, "No current uri"); michael@0: NS_ABORT_IF_FALSE(aRequest, "No request"); michael@0: NS_ABORT_IF_FALSE(aChannel, "No channel"); michael@0: michael@0: mProperties = do_CreateInstance("@mozilla.org/properties;1"); michael@0: michael@0: // Use ImageURL to ensure access to URI data off main thread. michael@0: mURI = new ImageURL(aURI); michael@0: mCurrentURI = aCurrentURI; michael@0: mFirstPartyIsolationURI = aFirstPartyIsolationURI; michael@0: mRequest = aRequest; michael@0: mChannel = aChannel; michael@0: mTimedChannel = do_QueryInterface(mChannel); michael@0: michael@0: mLoadingPrincipal = aLoadingPrincipal; michael@0: mCORSMode = aCORSMode; michael@0: michael@0: mChannel->GetNotificationCallbacks(getter_AddRefs(mPrevChannelSink)); michael@0: michael@0: NS_ASSERTION(mPrevChannelSink != this, michael@0: "Initializing with a channel that already calls back to us!"); michael@0: michael@0: mChannel->SetNotificationCallbacks(this); michael@0: michael@0: mCacheEntry = aCacheEntry; michael@0: michael@0: SetLoadId(aLoadId); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: already_AddRefed michael@0: imgRequest::GetStatusTracker() michael@0: { michael@0: if (mImage && mGotData) { michael@0: NS_ABORT_IF_FALSE(!mStatusTracker, michael@0: "Should have given mStatusTracker to mImage"); michael@0: return mImage->GetStatusTracker(); michael@0: } else { michael@0: NS_ABORT_IF_FALSE(mStatusTracker, michael@0: "Should have mStatusTracker until we create mImage"); michael@0: nsRefPtr statusTracker = mStatusTracker; michael@0: MOZ_ASSERT(statusTracker); michael@0: return statusTracker.forget(); michael@0: } michael@0: } michael@0: michael@0: void imgRequest::SetCacheEntry(imgCacheEntry *entry) michael@0: { michael@0: mCacheEntry = entry; michael@0: } michael@0: michael@0: bool imgRequest::HasCacheEntry() const michael@0: { michael@0: return mCacheEntry != nullptr; michael@0: } michael@0: michael@0: void imgRequest::ResetCacheEntry() michael@0: { michael@0: if (HasCacheEntry()) { michael@0: mCacheEntry->SetDataSize(0); michael@0: } michael@0: } michael@0: michael@0: void imgRequest::AddProxy(imgRequestProxy *proxy) michael@0: { michael@0: NS_PRECONDITION(proxy, "null imgRequestProxy passed in"); michael@0: LOG_SCOPE_WITH_PARAM(GetImgLog(), "imgRequest::AddProxy", "proxy", proxy); michael@0: michael@0: // If we're empty before adding, we have to tell the loader we now have michael@0: // proxies. michael@0: nsRefPtr statusTracker = GetStatusTracker(); michael@0: if (statusTracker->ConsumerCount() == 0) { michael@0: NS_ABORT_IF_FALSE(mURI, "Trying to SetHasProxies without key uri."); michael@0: mLoader->SetHasProxies(mFirstPartyIsolationURI, mURI); michael@0: } michael@0: michael@0: statusTracker->AddConsumer(proxy); michael@0: } michael@0: michael@0: nsresult imgRequest::RemoveProxy(imgRequestProxy *proxy, nsresult aStatus) michael@0: { michael@0: LOG_SCOPE_WITH_PARAM(GetImgLog(), "imgRequest::RemoveProxy", "proxy", proxy); michael@0: michael@0: // This will remove our animation consumers, so after removing michael@0: // this proxy, we don't end up without proxies with observers, but still michael@0: // have animation consumers. michael@0: proxy->ClearAnimationConsumers(); michael@0: michael@0: // Let the status tracker do its thing before we potentially call Cancel() michael@0: // below, because Cancel() may result in OnStopRequest being called back michael@0: // before Cancel() returns, leaving the image in a different state then the michael@0: // one it was in at this point. michael@0: nsRefPtr statusTracker = GetStatusTracker(); michael@0: if (!statusTracker->RemoveConsumer(proxy, aStatus)) michael@0: return NS_OK; michael@0: michael@0: if (statusTracker->ConsumerCount() == 0) { michael@0: // If we have no observers, there's nothing holding us alive. If we haven't michael@0: // been cancelled and thus removed from the cache, tell the image loader so michael@0: // we can be evicted from the cache. michael@0: if (mCacheEntry) { michael@0: NS_ABORT_IF_FALSE(mURI, "Removing last observer without key uri."); michael@0: michael@0: mLoader->SetHasNoProxies(mURI, mCacheEntry); michael@0: } michael@0: #if defined(PR_LOGGING) michael@0: else { michael@0: nsAutoCString spec; michael@0: mURI->GetSpec(spec); michael@0: LOG_MSG_WITH_PARAM(GetImgLog(), "imgRequest::RemoveProxy no cache entry", "uri", spec.get()); michael@0: } michael@0: #endif michael@0: michael@0: /* If |aStatus| is a failure code, then cancel the load if it is still in progress. michael@0: Otherwise, let the load continue, keeping 'this' in the cache with no observers. michael@0: This way, if a proxy is destroyed without calling cancel on it, it won't leak michael@0: and won't leave a bad pointer in the observer list. michael@0: */ michael@0: if (statusTracker->IsLoading() && NS_FAILED(aStatus)) { michael@0: LOG_MSG(GetImgLog(), "imgRequest::RemoveProxy", "load in progress. canceling"); michael@0: michael@0: this->Cancel(NS_BINDING_ABORTED); michael@0: } michael@0: michael@0: /* break the cycle from the cache entry. */ michael@0: mCacheEntry = nullptr; michael@0: } michael@0: michael@0: // If a proxy is removed for a reason other than its owner being michael@0: // changed, remove the proxy from the loadgroup. michael@0: if (aStatus != NS_IMAGELIB_CHANGING_OWNER) michael@0: proxy->RemoveFromLoadGroup(true); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: void imgRequest::CancelAndAbort(nsresult aStatus) michael@0: { michael@0: LOG_SCOPE(GetImgLog(), "imgRequest::CancelAndAbort"); michael@0: michael@0: Cancel(aStatus); michael@0: michael@0: // It's possible for the channel to fail to open after we've set our michael@0: // notification callbacks. In that case, make sure to break the cycle between michael@0: // the channel and us, because it won't. michael@0: if (mChannel) { michael@0: mChannel->SetNotificationCallbacks(mPrevChannelSink); michael@0: mPrevChannelSink = nullptr; michael@0: } michael@0: } michael@0: michael@0: class imgRequestMainThreadCancel : public nsRunnable michael@0: { michael@0: public: michael@0: imgRequestMainThreadCancel(imgRequest *aImgRequest, nsresult aStatus) michael@0: : mImgRequest(aImgRequest) michael@0: , mStatus(aStatus) michael@0: { michael@0: MOZ_ASSERT(!NS_IsMainThread(), "Create me off main thread only!"); michael@0: MOZ_ASSERT(aImgRequest); michael@0: } michael@0: michael@0: NS_IMETHOD Run() michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread(), "I should be running on the main thread!"); michael@0: mImgRequest->ContinueCancel(mStatus); michael@0: return NS_OK; michael@0: } michael@0: private: michael@0: nsRefPtr mImgRequest; michael@0: nsresult mStatus; michael@0: }; michael@0: michael@0: void imgRequest::Cancel(nsresult aStatus) michael@0: { michael@0: /* The Cancel() method here should only be called by this class. */ michael@0: michael@0: LOG_SCOPE(GetImgLog(), "imgRequest::Cancel"); michael@0: michael@0: nsRefPtr statusTracker = GetStatusTracker(); michael@0: michael@0: statusTracker->MaybeUnblockOnload(); michael@0: michael@0: statusTracker->RecordCancel(); michael@0: michael@0: if (NS_IsMainThread()) { michael@0: ContinueCancel(aStatus); michael@0: } else { michael@0: NS_DispatchToMainThread(new imgRequestMainThreadCancel(this, aStatus)); michael@0: } michael@0: } michael@0: michael@0: void imgRequest::ContinueCancel(nsresult aStatus) michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: michael@0: RemoveFromCache(); michael@0: michael@0: nsRefPtr statusTracker = GetStatusTracker(); michael@0: if (mRequest && statusTracker->IsLoading()) { michael@0: mRequest->Cancel(aStatus); michael@0: } michael@0: } michael@0: michael@0: nsresult imgRequest::GetURI(ImageURL **aURI) michael@0: { michael@0: MOZ_ASSERT(aURI); michael@0: michael@0: LOG_FUNC(GetImgLog(), "imgRequest::GetURI"); michael@0: michael@0: if (mURI) { michael@0: *aURI = mURI; michael@0: NS_ADDREF(*aURI); michael@0: return NS_OK; michael@0: } michael@0: michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: nsresult imgRequest::GetSecurityInfo(nsISupports **aSecurityInfo) michael@0: { michael@0: LOG_FUNC(GetImgLog(), "imgRequest::GetSecurityInfo"); michael@0: michael@0: // Missing security info means this is not a security load michael@0: // i.e. it is not an error when security info is missing michael@0: NS_IF_ADDREF(*aSecurityInfo = mSecurityInfo); michael@0: return NS_OK; michael@0: } michael@0: michael@0: void imgRequest::RemoveFromCache() michael@0: { michael@0: LOG_SCOPE(GetImgLog(), "imgRequest::RemoveFromCache"); michael@0: michael@0: if (mIsInCache) { michael@0: // mCacheEntry is nulled out when we have no more observers. michael@0: if (mCacheEntry) michael@0: mLoader->RemoveFromCache(mCacheEntry); michael@0: else { michael@0: mLoader->RemoveFromCache(mLoader->GetCacheKey(mFirstPartyIsolationURI, mURI, nullptr), michael@0: mLoader->GetCache(mURI), michael@0: mLoader->GetCacheQueue(mURI)); michael@0: } michael@0: } michael@0: michael@0: mCacheEntry = nullptr; michael@0: } michael@0: michael@0: int32_t imgRequest::Priority() const michael@0: { michael@0: int32_t priority = nsISupportsPriority::PRIORITY_NORMAL; michael@0: nsCOMPtr p = do_QueryInterface(mRequest); michael@0: if (p) michael@0: p->GetPriority(&priority); michael@0: return priority; michael@0: } michael@0: michael@0: void imgRequest::AdjustPriority(imgRequestProxy *proxy, int32_t delta) michael@0: { michael@0: // only the first proxy is allowed to modify the priority of this image load. michael@0: // michael@0: // XXX(darin): this is probably not the most optimal algorithm as we may want michael@0: // to increase the priority of requests that have a lot of proxies. the key michael@0: // concern though is that image loads remain lower priority than other pieces michael@0: // of content such as link clicks, CSS, and JS. michael@0: // michael@0: nsRefPtr statusTracker = GetStatusTracker(); michael@0: if (!statusTracker->FirstConsumerIs(proxy)) michael@0: return; michael@0: michael@0: nsCOMPtr p = do_QueryInterface(mChannel); michael@0: if (p) michael@0: p->AdjustPriority(delta); michael@0: } michael@0: michael@0: void imgRequest::SetIsInCache(bool incache) michael@0: { michael@0: LOG_FUNC_WITH_PARAM(GetImgLog(), "imgRequest::SetIsCacheable", "incache", incache); michael@0: mIsInCache = incache; michael@0: } michael@0: michael@0: void imgRequest::UpdateCacheEntrySize() michael@0: { michael@0: if (mCacheEntry) michael@0: mCacheEntry->SetDataSize(mImage->SizeOfData()); michael@0: } michael@0: michael@0: void imgRequest::SetCacheValidation(imgCacheEntry* aCacheEntry, nsIRequest* aRequest) michael@0: { michael@0: /* get the expires info */ michael@0: if (aCacheEntry) { michael@0: nsCOMPtr cacheChannel(do_QueryInterface(aRequest)); michael@0: if (cacheChannel) { michael@0: nsCOMPtr cacheToken; michael@0: cacheChannel->GetCacheToken(getter_AddRefs(cacheToken)); michael@0: if (cacheToken) { michael@0: nsCOMPtr entryDesc(do_QueryInterface(cacheToken)); michael@0: if (entryDesc) { michael@0: uint32_t expiration; michael@0: /* get the expiration time from the caching channel's token */ michael@0: entryDesc->GetExpirationTime(&expiration); michael@0: michael@0: // Expiration time defaults to 0. We set the expiration time on our michael@0: // entry if it hasn't been set yet. michael@0: if (aCacheEntry->GetExpiryTime() == 0) michael@0: aCacheEntry->SetExpiryTime(expiration); michael@0: } michael@0: } michael@0: } michael@0: michael@0: // Determine whether the cache entry must be revalidated when we try to use it. michael@0: // Currently, only HTTP specifies this information... michael@0: nsCOMPtr httpChannel(do_QueryInterface(aRequest)); michael@0: if (httpChannel) { michael@0: bool bMustRevalidate = false; michael@0: michael@0: httpChannel->IsNoStoreResponse(&bMustRevalidate); michael@0: michael@0: if (!bMustRevalidate) { michael@0: httpChannel->IsNoCacheResponse(&bMustRevalidate); michael@0: } michael@0: michael@0: if (!bMustRevalidate) { michael@0: nsAutoCString cacheHeader; michael@0: michael@0: httpChannel->GetResponseHeader(NS_LITERAL_CSTRING("Cache-Control"), michael@0: cacheHeader); michael@0: if (PL_strcasestr(cacheHeader.get(), "must-revalidate")) { michael@0: bMustRevalidate = true; michael@0: } michael@0: } michael@0: michael@0: // Cache entries default to not needing to validate. We ensure that michael@0: // multiple calls to this function don't override an earlier decision to michael@0: // validate by making validation a one-way decision. michael@0: if (bMustRevalidate) michael@0: aCacheEntry->SetMustValidate(bMustRevalidate); michael@0: } michael@0: michael@0: // We always need to validate file URIs. michael@0: nsCOMPtr channel = do_QueryInterface(aRequest); michael@0: if (channel) { michael@0: nsCOMPtr uri; michael@0: channel->GetURI(getter_AddRefs(uri)); michael@0: bool isfile = false; michael@0: uri->SchemeIs("file", &isfile); michael@0: if (isfile) michael@0: aCacheEntry->SetMustValidate(isfile); michael@0: } michael@0: } michael@0: } michael@0: michael@0: namespace { // anon michael@0: michael@0: already_AddRefed michael@0: GetApplicationCache(nsIRequest* aRequest) michael@0: { michael@0: nsresult rv; michael@0: michael@0: nsCOMPtr appCacheChan = do_QueryInterface(aRequest); michael@0: if (!appCacheChan) { michael@0: return nullptr; michael@0: } michael@0: michael@0: bool fromAppCache; michael@0: rv = appCacheChan->GetLoadedFromApplicationCache(&fromAppCache); michael@0: NS_ENSURE_SUCCESS(rv, nullptr); michael@0: michael@0: if (!fromAppCache) { michael@0: return nullptr; michael@0: } michael@0: michael@0: nsCOMPtr appCache; michael@0: rv = appCacheChan->GetApplicationCache(getter_AddRefs(appCache)); michael@0: NS_ENSURE_SUCCESS(rv, nullptr); michael@0: michael@0: return appCache.forget(); michael@0: } michael@0: michael@0: } // anon michael@0: michael@0: bool michael@0: imgRequest::CacheChanged(nsIRequest* aNewRequest) michael@0: { michael@0: nsCOMPtr newAppCache = GetApplicationCache(aNewRequest); michael@0: michael@0: // Application cache not involved at all or the same app cache involved michael@0: // in both of the loads (original and new). michael@0: if (newAppCache == mApplicationCache) michael@0: return false; michael@0: michael@0: // In a rare case it may happen that two objects still refer michael@0: // the same application cache version. michael@0: if (newAppCache && mApplicationCache) { michael@0: nsresult rv; michael@0: michael@0: nsAutoCString oldAppCacheClientId, newAppCacheClientId; michael@0: rv = mApplicationCache->GetClientID(oldAppCacheClientId); michael@0: NS_ENSURE_SUCCESS(rv, true); michael@0: rv = newAppCache->GetClientID(newAppCacheClientId); michael@0: NS_ENSURE_SUCCESS(rv, true); michael@0: michael@0: if (oldAppCacheClientId == newAppCacheClientId) michael@0: return false; michael@0: } michael@0: michael@0: // When we get here, app caches differ or app cache is involved michael@0: // just in one of the loads what we also consider as a change michael@0: // in a loading cache. michael@0: return true; michael@0: } michael@0: michael@0: nsresult michael@0: imgRequest::LockImage() michael@0: { michael@0: return mImage->LockImage(); michael@0: } michael@0: michael@0: nsresult michael@0: imgRequest::UnlockImage() michael@0: { michael@0: return mImage->UnlockImage(); michael@0: } michael@0: michael@0: nsresult michael@0: imgRequest::RequestDecode() michael@0: { michael@0: // If we've initialized our image, we can request a decode. michael@0: if (mImage) { michael@0: return mImage->RequestDecode(); michael@0: } michael@0: michael@0: // Otherwise, flag to do it when we get the image michael@0: mDecodeRequested = true; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: imgRequest::StartDecoding() michael@0: { michael@0: // If we've initialized our image, we can request a decode. michael@0: if (mImage) { michael@0: return mImage->StartDecoding(); michael@0: } michael@0: michael@0: // Otherwise, flag to do it when we get the image michael@0: mDecodeRequested = true; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: /** nsIRequestObserver methods **/ michael@0: michael@0: /* void onStartRequest (in nsIRequest request, in nsISupports ctxt); */ michael@0: NS_IMETHODIMP imgRequest::OnStartRequest(nsIRequest *aRequest, nsISupports *ctxt) michael@0: { michael@0: LOG_SCOPE(GetImgLog(), "imgRequest::OnStartRequest"); michael@0: michael@0: // Figure out if we're multipart michael@0: nsCOMPtr mpchan(do_QueryInterface(aRequest)); michael@0: nsRefPtr statusTracker = GetStatusTracker(); michael@0: if (mpchan) { michael@0: mIsMultiPartChannel = true; michael@0: statusTracker->SetIsMultipart(); michael@0: } michael@0: michael@0: // If we're not multipart, we shouldn't have an image yet michael@0: NS_ABORT_IF_FALSE(mIsMultiPartChannel || !mImage, michael@0: "Already have an image for non-multipart request"); michael@0: michael@0: // If we're multipart and about to load another image, signal so we can michael@0: // detect the mime type in OnDataAvailable. michael@0: if (mIsMultiPartChannel && mImage) { michael@0: mResniffMimeType = true; michael@0: michael@0: // Tell the image to reinitialize itself. We have to do this in michael@0: // OnStartRequest so that its state machine is always in a consistent michael@0: // state. michael@0: // Note that if our MIME type changes, mImage will be replaced with a michael@0: // new object. michael@0: mImage->OnNewSourceData(); michael@0: } michael@0: michael@0: /* michael@0: * If mRequest is null here, then we need to set it so that we'll be able to michael@0: * cancel it if our Cancel() method is called. Note that this can only michael@0: * happen for multipart channels. We could simply not null out mRequest for michael@0: * non-last parts, if GetIsLastPart() were reliable, but it's not. See michael@0: * https://bugzilla.mozilla.org/show_bug.cgi?id=339610 michael@0: */ michael@0: if (!mRequest) { michael@0: NS_ASSERTION(mpchan, michael@0: "We should have an mRequest here unless we're multipart"); michael@0: nsCOMPtr chan; michael@0: mpchan->GetBaseChannel(getter_AddRefs(chan)); michael@0: mRequest = chan; michael@0: } michael@0: michael@0: // Note: refreshing statusTracker in case OnNewSourceData changed it. michael@0: statusTracker = GetStatusTracker(); michael@0: statusTracker->OnStartRequest(); michael@0: michael@0: nsCOMPtr channel(do_QueryInterface(aRequest)); michael@0: if (channel) michael@0: channel->GetSecurityInfo(getter_AddRefs(mSecurityInfo)); michael@0: michael@0: /* Get our principal */ michael@0: nsCOMPtr chan(do_QueryInterface(aRequest)); michael@0: if (chan) { michael@0: nsCOMPtr secMan = nsContentUtils::GetSecurityManager(); michael@0: if (secMan) { michael@0: nsresult rv = secMan->GetChannelPrincipal(chan, michael@0: getter_AddRefs(mPrincipal)); michael@0: if (NS_FAILED(rv)) { michael@0: return rv; michael@0: } michael@0: } michael@0: } michael@0: michael@0: SetCacheValidation(mCacheEntry, aRequest); michael@0: michael@0: mApplicationCache = GetApplicationCache(aRequest); michael@0: michael@0: // Shouldn't we be dead already if this gets hit? Probably multipart/x-mixed-replace... michael@0: if (statusTracker->ConsumerCount() == 0) { michael@0: this->Cancel(NS_IMAGELIB_ERROR_FAILURE); michael@0: } michael@0: michael@0: // Try to retarget OnDataAvailable to a decode thread. michael@0: nsCOMPtr httpChannel = do_QueryInterface(aRequest); michael@0: nsCOMPtr retargetable = michael@0: do_QueryInterface(aRequest); michael@0: if (httpChannel && retargetable && michael@0: ImageFactory::CanRetargetOnDataAvailable(mURI, mIsMultiPartChannel)) { michael@0: nsAutoCString mimeType; michael@0: nsresult rv = httpChannel->GetContentType(mimeType); michael@0: if (NS_SUCCEEDED(rv) && !mimeType.EqualsLiteral(IMAGE_SVG_XML)) { michael@0: // Image object not created until OnDataAvailable, so forward to static michael@0: // DecodePool directly. michael@0: nsCOMPtr target = RasterImage::GetEventTarget(); michael@0: rv = retargetable->RetargetDeliveryTo(target); michael@0: } michael@0: PR_LOG(GetImgLog(), PR_LOG_WARNING, michael@0: ("[this=%p] imgRequest::OnStartRequest -- " michael@0: "RetargetDeliveryTo rv %d=%s\n", michael@0: this, rv, NS_SUCCEEDED(rv) ? "succeeded" : "failed")); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: /* void onStopRequest (in nsIRequest request, in nsISupports ctxt, in nsresult status); */ michael@0: NS_IMETHODIMP imgRequest::OnStopRequest(nsIRequest *aRequest, nsISupports *ctxt, nsresult status) michael@0: { michael@0: LOG_FUNC(GetImgLog(), "imgRequest::OnStopRequest"); michael@0: michael@0: // XXXldb What if this is a non-last part of a multipart request? michael@0: // xxx before we release our reference to mRequest, lets michael@0: // save the last status that we saw so that the michael@0: // imgRequestProxy will have access to it. michael@0: if (mRequest) { michael@0: mRequest = nullptr; // we no longer need the request michael@0: } michael@0: michael@0: // stop holding a ref to the channel, since we don't need it anymore michael@0: if (mChannel) { michael@0: mChannel->SetNotificationCallbacks(mPrevChannelSink); michael@0: mPrevChannelSink = nullptr; michael@0: mChannel = nullptr; michael@0: } michael@0: michael@0: bool lastPart = true; michael@0: nsCOMPtr mpchan(do_QueryInterface(aRequest)); michael@0: if (mpchan) michael@0: mpchan->GetIsLastPart(&lastPart); michael@0: michael@0: // Tell the image that it has all of the source data. Note that this can michael@0: // trigger a failure, since the image might be waiting for more non-optional michael@0: // data and this is the point where we break the news that it's not coming. michael@0: if (mImage) { michael@0: nsresult rv = mImage->OnImageDataComplete(aRequest, ctxt, status, lastPart); michael@0: michael@0: // If we got an error in the OnImageDataComplete() call, we don't want to michael@0: // proceed as if nothing bad happened. However, we also want to give michael@0: // precedence to failure status codes from necko, since presumably they're michael@0: // more meaningful. michael@0: if (NS_FAILED(rv) && NS_SUCCEEDED(status)) michael@0: status = rv; michael@0: } michael@0: michael@0: // If the request went through, update the cache entry size. Otherwise, michael@0: // cancel the request, which removes us from the cache. michael@0: if (mImage && NS_SUCCEEDED(status)) { michael@0: // We update the cache entry size here because this is where we finish michael@0: // loading compressed source data, which is part of our size calculus. michael@0: UpdateCacheEntrySize(); michael@0: } michael@0: else { michael@0: // stops animations, removes from cache michael@0: this->Cancel(status); michael@0: } michael@0: michael@0: if (!mImage) { michael@0: // We have to fire imgStatusTracker::OnStopRequest ourselves because there's michael@0: // no image capable of doing so. michael@0: nsRefPtr statusTracker = GetStatusTracker(); michael@0: statusTracker->OnStopRequest(lastPart, status); michael@0: } michael@0: michael@0: mTimedChannel = nullptr; michael@0: return NS_OK; michael@0: } michael@0: michael@0: struct mimetype_closure michael@0: { michael@0: nsACString* newType; michael@0: }; michael@0: michael@0: /* prototype for these defined below */ michael@0: static NS_METHOD sniff_mimetype_callback(nsIInputStream* in, void* closure, const char* fromRawSegment, michael@0: uint32_t toOffset, uint32_t count, uint32_t *writeCount); michael@0: michael@0: /** nsThreadRetargetableStreamListener methods **/ michael@0: NS_IMETHODIMP michael@0: imgRequest::CheckListenerChain() michael@0: { michael@0: // TODO Might need more checking here. michael@0: NS_ASSERTION(NS_IsMainThread(), "Should be on the main thread!"); michael@0: return NS_OK; michael@0: } michael@0: michael@0: /** nsIStreamListener methods **/ michael@0: michael@0: /* void onDataAvailable (in nsIRequest request, in nsISupports ctxt, in nsIInputStream inStr, in unsigned long long sourceOffset, in unsigned long count); */ michael@0: NS_IMETHODIMP michael@0: imgRequest::OnDataAvailable(nsIRequest *aRequest, nsISupports *ctxt, michael@0: nsIInputStream *inStr, uint64_t sourceOffset, michael@0: uint32_t count) michael@0: { michael@0: LOG_SCOPE_WITH_PARAM(GetImgLog(), "imgRequest::OnDataAvailable", "count", count); michael@0: michael@0: NS_ASSERTION(aRequest, "imgRequest::OnDataAvailable -- no request!"); michael@0: michael@0: nsresult rv; michael@0: michael@0: if (!mGotData || mResniffMimeType) { michael@0: LOG_SCOPE(GetImgLog(), "imgRequest::OnDataAvailable |First time through... finding mimetype|"); michael@0: michael@0: mGotData = true; michael@0: michael@0: // Store and reset this for the invariant that it's always false after michael@0: // calls to OnDataAvailable (see bug 907575) michael@0: bool resniffMimeType = mResniffMimeType; michael@0: mResniffMimeType = false; michael@0: michael@0: mimetype_closure closure; michael@0: nsAutoCString newType; michael@0: closure.newType = &newType; michael@0: michael@0: /* look at the first few bytes and see if we can tell what the data is from that michael@0: * since servers tend to lie. :( michael@0: */ michael@0: uint32_t out; michael@0: inStr->ReadSegments(sniff_mimetype_callback, &closure, count, &out); michael@0: michael@0: nsCOMPtr chan(do_QueryInterface(aRequest)); michael@0: if (newType.IsEmpty()) { michael@0: LOG_SCOPE(GetImgLog(), "imgRequest::OnDataAvailable |sniffing of mimetype failed|"); michael@0: michael@0: rv = NS_ERROR_FAILURE; michael@0: if (chan) { michael@0: rv = chan->GetContentType(newType); michael@0: } michael@0: michael@0: if (NS_FAILED(rv)) { michael@0: PR_LOG(GetImgLog(), PR_LOG_ERROR, michael@0: ("[this=%p] imgRequest::OnDataAvailable -- Content type unavailable from the channel\n", michael@0: this)); michael@0: michael@0: this->Cancel(NS_IMAGELIB_ERROR_FAILURE); michael@0: michael@0: return NS_BINDING_ABORTED; michael@0: } michael@0: michael@0: LOG_MSG(GetImgLog(), "imgRequest::OnDataAvailable", "Got content type from the channel"); michael@0: } michael@0: michael@0: // If we're a regular image and this is the first call to OnDataAvailable, michael@0: // this will always be true. If we've resniffed our MIME type (i.e. we're a michael@0: // multipart/x-mixed-replace image), we have to be able to switch our image michael@0: // type and decoder. michael@0: // We always reinitialize for SVGs, because they have no way of michael@0: // reinitializing themselves. michael@0: if (mContentType != newType || newType.EqualsLiteral(IMAGE_SVG_XML)) { michael@0: mContentType = newType; michael@0: michael@0: // If we've resniffed our MIME type and it changed, we need to create a michael@0: // new status tracker to give to the image, because we don't have one of michael@0: // our own any more. michael@0: if (resniffMimeType) { michael@0: NS_ABORT_IF_FALSE(mIsMultiPartChannel, "Resniffing a non-multipart image"); michael@0: michael@0: nsRefPtr freshTracker = new imgStatusTracker(nullptr); michael@0: nsRefPtr oldStatusTracker = GetStatusTracker(); michael@0: freshTracker->AdoptConsumers(oldStatusTracker); michael@0: mStatusTracker = freshTracker.forget(); michael@0: } michael@0: michael@0: SetProperties(chan); michael@0: michael@0: LOG_MSG_WITH_PARAM(GetImgLog(), "imgRequest::OnDataAvailable", "content type", mContentType.get()); michael@0: michael@0: // XXX If server lied about mimetype and it's SVG, we may need to copy michael@0: // the data and dispatch back to the main thread, AND tell the channel to michael@0: // dispatch there in the future. michael@0: michael@0: // Now we can create a new image to hold the data. If we don't have a decoder michael@0: // for this mimetype we'll find out about it here. michael@0: mImage = ImageFactory::CreateImage(aRequest, mStatusTracker, mContentType, michael@0: mURI, mIsMultiPartChannel, michael@0: static_cast(mInnerWindowId)); michael@0: michael@0: // Release our copy of the status tracker since the image owns it now. michael@0: mStatusTracker = nullptr; michael@0: michael@0: // Notify listeners that we have an image. michael@0: // XXX(seth): The name of this notification method is pretty misleading. michael@0: nsRefPtr statusTracker = GetStatusTracker(); michael@0: statusTracker->OnDataAvailable(); michael@0: michael@0: if (mImage->HasError() && !mIsMultiPartChannel) { // Probably bad mimetype michael@0: // We allow multipart images to fail to initialize without cancelling the michael@0: // load because subsequent images might be fine; thus only single part michael@0: // images end up here. michael@0: this->Cancel(NS_ERROR_FAILURE); michael@0: return NS_BINDING_ABORTED; michael@0: } michael@0: michael@0: NS_ABORT_IF_FALSE(statusTracker->HasImage(), "Status tracker should have an image!"); michael@0: NS_ABORT_IF_FALSE(mImage, "imgRequest should have an image!"); michael@0: michael@0: if (mDecodeRequested) michael@0: mImage->StartDecoding(); michael@0: } michael@0: } michael@0: michael@0: // Notify the image that it has new data. michael@0: rv = mImage->OnImageDataAvailable(aRequest, ctxt, inStr, sourceOffset, count); michael@0: michael@0: if (NS_FAILED(rv)) { michael@0: PR_LOG(GetImgLog(), PR_LOG_WARNING, michael@0: ("[this=%p] imgRequest::OnDataAvailable -- " michael@0: "copy to RasterImage failed\n", this)); michael@0: this->Cancel(NS_IMAGELIB_ERROR_FAILURE); michael@0: return NS_BINDING_ABORTED; michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: class SetPropertiesEvent : public nsRunnable michael@0: { michael@0: public: michael@0: SetPropertiesEvent(imgRequest* aImgRequest, nsIChannel* aChan) michael@0: : mImgRequest(aImgRequest) michael@0: , mChan(aChan) michael@0: { michael@0: MOZ_ASSERT(!NS_IsMainThread(), "Should be created off the main thread"); michael@0: MOZ_ASSERT(aImgRequest, "aImgRequest cannot be null"); michael@0: } michael@0: NS_IMETHOD Run() michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread(), "Should run on the main thread only"); michael@0: MOZ_ASSERT(mImgRequest, "mImgRequest cannot be null"); michael@0: mImgRequest->SetProperties(mChan); michael@0: return NS_OK; michael@0: } michael@0: private: michael@0: nsRefPtr mImgRequest; michael@0: nsCOMPtr mChan; michael@0: }; michael@0: michael@0: void michael@0: imgRequest::SetProperties(nsIChannel* aChan) michael@0: { michael@0: // Force execution on main thread since some property objects are non michael@0: // threadsafe. michael@0: if (!NS_IsMainThread()) { michael@0: NS_DispatchToMainThread(new SetPropertiesEvent(this, aChan)); michael@0: return; michael@0: } michael@0: /* set our mimetype as a property */ michael@0: nsCOMPtr contentType(do_CreateInstance("@mozilla.org/supports-cstring;1")); michael@0: if (contentType) { michael@0: contentType->SetData(mContentType); michael@0: mProperties->Set("type", contentType); michael@0: } michael@0: michael@0: /* set our content disposition as a property */ michael@0: nsAutoCString disposition; michael@0: if (aChan) { michael@0: aChan->GetContentDispositionHeader(disposition); michael@0: } michael@0: if (!disposition.IsEmpty()) { michael@0: nsCOMPtr contentDisposition(do_CreateInstance("@mozilla.org/supports-cstring;1")); michael@0: if (contentDisposition) { michael@0: contentDisposition->SetData(disposition); michael@0: mProperties->Set("content-disposition", contentDisposition); michael@0: } michael@0: } michael@0: } michael@0: michael@0: static NS_METHOD sniff_mimetype_callback(nsIInputStream* in, michael@0: void* data, michael@0: const char* fromRawSegment, michael@0: uint32_t toOffset, michael@0: uint32_t count, michael@0: uint32_t *writeCount) michael@0: { michael@0: mimetype_closure* closure = static_cast(data); michael@0: michael@0: NS_ASSERTION(closure, "closure is null!"); michael@0: michael@0: if (count > 0) michael@0: imgLoader::GetMimeTypeFromContent(fromRawSegment, count, *closure->newType); michael@0: michael@0: *writeCount = 0; michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: michael@0: /** nsIInterfaceRequestor methods **/ michael@0: michael@0: NS_IMETHODIMP michael@0: imgRequest::GetInterface(const nsIID & aIID, void **aResult) michael@0: { michael@0: if (!mPrevChannelSink || aIID.Equals(NS_GET_IID(nsIChannelEventSink))) michael@0: return QueryInterface(aIID, aResult); michael@0: michael@0: NS_ASSERTION(mPrevChannelSink != this, michael@0: "Infinite recursion - don't keep track of channel sinks that are us!"); michael@0: return mPrevChannelSink->GetInterface(aIID, aResult); michael@0: } michael@0: michael@0: /** nsIChannelEventSink methods **/ michael@0: NS_IMETHODIMP michael@0: imgRequest::AsyncOnChannelRedirect(nsIChannel *oldChannel, michael@0: nsIChannel *newChannel, uint32_t flags, michael@0: nsIAsyncVerifyRedirectCallback *callback) michael@0: { michael@0: NS_ASSERTION(mRequest && mChannel, "Got a channel redirect after we nulled out mRequest!"); michael@0: NS_ASSERTION(mChannel == oldChannel, "Got a channel redirect for an unknown channel!"); michael@0: NS_ASSERTION(newChannel, "Got a redirect to a NULL channel!"); michael@0: michael@0: SetCacheValidation(mCacheEntry, oldChannel); michael@0: michael@0: // Prepare for callback michael@0: mRedirectCallback = callback; michael@0: mNewRedirectChannel = newChannel; michael@0: michael@0: nsCOMPtr sink(do_GetInterface(mPrevChannelSink)); michael@0: if (sink) { michael@0: nsresult rv = sink->AsyncOnChannelRedirect(oldChannel, newChannel, flags, michael@0: this); michael@0: if (NS_FAILED(rv)) { michael@0: mRedirectCallback = nullptr; michael@0: mNewRedirectChannel = nullptr; michael@0: } michael@0: return rv; michael@0: } michael@0: michael@0: (void) OnRedirectVerifyCallback(NS_OK); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: imgRequest::OnRedirectVerifyCallback(nsresult result) michael@0: { michael@0: NS_ASSERTION(mRedirectCallback, "mRedirectCallback not set in callback"); michael@0: NS_ASSERTION(mNewRedirectChannel, "mNewRedirectChannel not set in callback"); michael@0: michael@0: if (NS_FAILED(result)) { michael@0: mRedirectCallback->OnRedirectVerifyCallback(result); michael@0: mRedirectCallback = nullptr; michael@0: mNewRedirectChannel = nullptr; michael@0: return NS_OK; michael@0: } michael@0: michael@0: mChannel = mNewRedirectChannel; michael@0: mTimedChannel = do_QueryInterface(mChannel); michael@0: mNewRedirectChannel = nullptr; michael@0: michael@0: #if defined(PR_LOGGING) michael@0: nsAutoCString oldspec; michael@0: if (mCurrentURI) michael@0: mCurrentURI->GetSpec(oldspec); michael@0: LOG_MSG_WITH_PARAM(GetImgLog(), "imgRequest::OnChannelRedirect", "old", oldspec.get()); michael@0: #endif michael@0: michael@0: // make sure we have a protocol that returns data rather than opens michael@0: // an external application, e.g. mailto: michael@0: mChannel->GetURI(getter_AddRefs(mCurrentURI)); michael@0: bool doesNotReturnData = false; michael@0: nsresult rv = michael@0: NS_URIChainHasFlags(mCurrentURI, nsIProtocolHandler::URI_DOES_NOT_RETURN_DATA, michael@0: &doesNotReturnData); michael@0: michael@0: if (NS_SUCCEEDED(rv) && doesNotReturnData) michael@0: rv = NS_ERROR_ABORT; michael@0: michael@0: if (NS_FAILED(rv)) { michael@0: mRedirectCallback->OnRedirectVerifyCallback(rv); michael@0: mRedirectCallback = nullptr; michael@0: return NS_OK; michael@0: } michael@0: michael@0: mRedirectCallback->OnRedirectVerifyCallback(NS_OK); michael@0: mRedirectCallback = nullptr; michael@0: return NS_OK; michael@0: }