1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/image/src/imgRequest.cpp Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,1019 @@ 1.4 +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- 1.5 + * 1.6 + * This Source Code Form is subject to the terms of the Mozilla Public 1.7 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.8 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.9 + 1.10 +#include "imgRequest.h" 1.11 +#include "ImageLogging.h" 1.12 + 1.13 +#include "imgLoader.h" 1.14 +#include "imgRequestProxy.h" 1.15 +#include "imgStatusTracker.h" 1.16 +#include "ImageFactory.h" 1.17 +#include "Image.h" 1.18 +#include "RasterImage.h" 1.19 + 1.20 +#include "nsIChannel.h" 1.21 +#include "nsICachingChannel.h" 1.22 +#include "nsIThreadRetargetableRequest.h" 1.23 +#include "nsIInputStream.h" 1.24 +#include "nsIMultiPartChannel.h" 1.25 +#include "nsIHttpChannel.h" 1.26 +#include "nsIApplicationCache.h" 1.27 +#include "nsIApplicationCacheChannel.h" 1.28 +#include "nsMimeTypes.h" 1.29 + 1.30 +#include "nsIInterfaceRequestorUtils.h" 1.31 +#include "nsISupportsPrimitives.h" 1.32 +#include "nsIScriptSecurityManager.h" 1.33 +#include "nsContentUtils.h" 1.34 + 1.35 +#include "nsICacheEntry.h" 1.36 + 1.37 +#include "plstr.h" // PL_strcasestr(...) 1.38 +#include "nsNetUtil.h" 1.39 +#include "nsIProtocolHandler.h" 1.40 +#include "imgIRequest.h" 1.41 + 1.42 +using namespace mozilla; 1.43 +using namespace mozilla::image; 1.44 + 1.45 +#if defined(PR_LOGGING) 1.46 +PRLogModuleInfo * 1.47 +GetImgLog() 1.48 +{ 1.49 + static PRLogModuleInfo *sImgLog; 1.50 + if (!sImgLog) 1.51 + sImgLog = PR_NewLogModule("imgRequest"); 1.52 + return sImgLog; 1.53 +} 1.54 +#endif 1.55 + 1.56 +NS_IMPL_ISUPPORTS(imgRequest, 1.57 + nsIStreamListener, nsIRequestObserver, 1.58 + nsIThreadRetargetableStreamListener, 1.59 + nsIChannelEventSink, 1.60 + nsIInterfaceRequestor, 1.61 + nsIAsyncVerifyRedirectCallback) 1.62 + 1.63 +imgRequest::imgRequest(imgLoader* aLoader) 1.64 + : mLoader(aLoader) 1.65 + , mStatusTracker(new imgStatusTracker(nullptr)) 1.66 + , mValidator(nullptr) 1.67 + , mInnerWindowId(0) 1.68 + , mCORSMode(imgIRequest::CORS_NONE) 1.69 + , mDecodeRequested(false) 1.70 + , mIsMultiPartChannel(false) 1.71 + , mGotData(false) 1.72 + , mIsInCache(false) 1.73 + , mResniffMimeType(false) 1.74 +{ } 1.75 + 1.76 +imgRequest::~imgRequest() 1.77 +{ 1.78 + if (mURI) { 1.79 + nsAutoCString spec; 1.80 + mURI->GetSpec(spec); 1.81 + LOG_FUNC_WITH_PARAM(GetImgLog(), "imgRequest::~imgRequest()", "keyuri", spec.get()); 1.82 + } else 1.83 + LOG_FUNC(GetImgLog(), "imgRequest::~imgRequest()"); 1.84 +} 1.85 + 1.86 +nsresult imgRequest::Init(nsIURI *aURI, 1.87 + nsIURI *aCurrentURI, 1.88 + nsIURI *aFirstPartyIsolationURI, 1.89 + nsIRequest *aRequest, 1.90 + nsIChannel *aChannel, 1.91 + imgCacheEntry *aCacheEntry, 1.92 + void *aLoadId, 1.93 + nsIPrincipal* aLoadingPrincipal, 1.94 + int32_t aCORSMode) 1.95 +{ 1.96 + MOZ_ASSERT(NS_IsMainThread(), "Cannot use nsIURI off main thread!"); 1.97 + 1.98 + LOG_FUNC(GetImgLog(), "imgRequest::Init"); 1.99 + 1.100 + NS_ABORT_IF_FALSE(!mImage, "Multiple calls to init"); 1.101 + NS_ABORT_IF_FALSE(aURI, "No uri"); 1.102 + NS_ABORT_IF_FALSE(aCurrentURI, "No current uri"); 1.103 + NS_ABORT_IF_FALSE(aRequest, "No request"); 1.104 + NS_ABORT_IF_FALSE(aChannel, "No channel"); 1.105 + 1.106 + mProperties = do_CreateInstance("@mozilla.org/properties;1"); 1.107 + 1.108 + // Use ImageURL to ensure access to URI data off main thread. 1.109 + mURI = new ImageURL(aURI); 1.110 + mCurrentURI = aCurrentURI; 1.111 + mFirstPartyIsolationURI = aFirstPartyIsolationURI; 1.112 + mRequest = aRequest; 1.113 + mChannel = aChannel; 1.114 + mTimedChannel = do_QueryInterface(mChannel); 1.115 + 1.116 + mLoadingPrincipal = aLoadingPrincipal; 1.117 + mCORSMode = aCORSMode; 1.118 + 1.119 + mChannel->GetNotificationCallbacks(getter_AddRefs(mPrevChannelSink)); 1.120 + 1.121 + NS_ASSERTION(mPrevChannelSink != this, 1.122 + "Initializing with a channel that already calls back to us!"); 1.123 + 1.124 + mChannel->SetNotificationCallbacks(this); 1.125 + 1.126 + mCacheEntry = aCacheEntry; 1.127 + 1.128 + SetLoadId(aLoadId); 1.129 + 1.130 + return NS_OK; 1.131 +} 1.132 + 1.133 +already_AddRefed<imgStatusTracker> 1.134 +imgRequest::GetStatusTracker() 1.135 +{ 1.136 + if (mImage && mGotData) { 1.137 + NS_ABORT_IF_FALSE(!mStatusTracker, 1.138 + "Should have given mStatusTracker to mImage"); 1.139 + return mImage->GetStatusTracker(); 1.140 + } else { 1.141 + NS_ABORT_IF_FALSE(mStatusTracker, 1.142 + "Should have mStatusTracker until we create mImage"); 1.143 + nsRefPtr<imgStatusTracker> statusTracker = mStatusTracker; 1.144 + MOZ_ASSERT(statusTracker); 1.145 + return statusTracker.forget(); 1.146 + } 1.147 +} 1.148 + 1.149 +void imgRequest::SetCacheEntry(imgCacheEntry *entry) 1.150 +{ 1.151 + mCacheEntry = entry; 1.152 +} 1.153 + 1.154 +bool imgRequest::HasCacheEntry() const 1.155 +{ 1.156 + return mCacheEntry != nullptr; 1.157 +} 1.158 + 1.159 +void imgRequest::ResetCacheEntry() 1.160 +{ 1.161 + if (HasCacheEntry()) { 1.162 + mCacheEntry->SetDataSize(0); 1.163 + } 1.164 +} 1.165 + 1.166 +void imgRequest::AddProxy(imgRequestProxy *proxy) 1.167 +{ 1.168 + NS_PRECONDITION(proxy, "null imgRequestProxy passed in"); 1.169 + LOG_SCOPE_WITH_PARAM(GetImgLog(), "imgRequest::AddProxy", "proxy", proxy); 1.170 + 1.171 + // If we're empty before adding, we have to tell the loader we now have 1.172 + // proxies. 1.173 + nsRefPtr<imgStatusTracker> statusTracker = GetStatusTracker(); 1.174 + if (statusTracker->ConsumerCount() == 0) { 1.175 + NS_ABORT_IF_FALSE(mURI, "Trying to SetHasProxies without key uri."); 1.176 + mLoader->SetHasProxies(mFirstPartyIsolationURI, mURI); 1.177 + } 1.178 + 1.179 + statusTracker->AddConsumer(proxy); 1.180 +} 1.181 + 1.182 +nsresult imgRequest::RemoveProxy(imgRequestProxy *proxy, nsresult aStatus) 1.183 +{ 1.184 + LOG_SCOPE_WITH_PARAM(GetImgLog(), "imgRequest::RemoveProxy", "proxy", proxy); 1.185 + 1.186 + // This will remove our animation consumers, so after removing 1.187 + // this proxy, we don't end up without proxies with observers, but still 1.188 + // have animation consumers. 1.189 + proxy->ClearAnimationConsumers(); 1.190 + 1.191 + // Let the status tracker do its thing before we potentially call Cancel() 1.192 + // below, because Cancel() may result in OnStopRequest being called back 1.193 + // before Cancel() returns, leaving the image in a different state then the 1.194 + // one it was in at this point. 1.195 + nsRefPtr<imgStatusTracker> statusTracker = GetStatusTracker(); 1.196 + if (!statusTracker->RemoveConsumer(proxy, aStatus)) 1.197 + return NS_OK; 1.198 + 1.199 + if (statusTracker->ConsumerCount() == 0) { 1.200 + // If we have no observers, there's nothing holding us alive. If we haven't 1.201 + // been cancelled and thus removed from the cache, tell the image loader so 1.202 + // we can be evicted from the cache. 1.203 + if (mCacheEntry) { 1.204 + NS_ABORT_IF_FALSE(mURI, "Removing last observer without key uri."); 1.205 + 1.206 + mLoader->SetHasNoProxies(mURI, mCacheEntry); 1.207 + } 1.208 +#if defined(PR_LOGGING) 1.209 + else { 1.210 + nsAutoCString spec; 1.211 + mURI->GetSpec(spec); 1.212 + LOG_MSG_WITH_PARAM(GetImgLog(), "imgRequest::RemoveProxy no cache entry", "uri", spec.get()); 1.213 + } 1.214 +#endif 1.215 + 1.216 + /* If |aStatus| is a failure code, then cancel the load if it is still in progress. 1.217 + Otherwise, let the load continue, keeping 'this' in the cache with no observers. 1.218 + This way, if a proxy is destroyed without calling cancel on it, it won't leak 1.219 + and won't leave a bad pointer in the observer list. 1.220 + */ 1.221 + if (statusTracker->IsLoading() && NS_FAILED(aStatus)) { 1.222 + LOG_MSG(GetImgLog(), "imgRequest::RemoveProxy", "load in progress. canceling"); 1.223 + 1.224 + this->Cancel(NS_BINDING_ABORTED); 1.225 + } 1.226 + 1.227 + /* break the cycle from the cache entry. */ 1.228 + mCacheEntry = nullptr; 1.229 + } 1.230 + 1.231 + // If a proxy is removed for a reason other than its owner being 1.232 + // changed, remove the proxy from the loadgroup. 1.233 + if (aStatus != NS_IMAGELIB_CHANGING_OWNER) 1.234 + proxy->RemoveFromLoadGroup(true); 1.235 + 1.236 + return NS_OK; 1.237 +} 1.238 + 1.239 +void imgRequest::CancelAndAbort(nsresult aStatus) 1.240 +{ 1.241 + LOG_SCOPE(GetImgLog(), "imgRequest::CancelAndAbort"); 1.242 + 1.243 + Cancel(aStatus); 1.244 + 1.245 + // It's possible for the channel to fail to open after we've set our 1.246 + // notification callbacks. In that case, make sure to break the cycle between 1.247 + // the channel and us, because it won't. 1.248 + if (mChannel) { 1.249 + mChannel->SetNotificationCallbacks(mPrevChannelSink); 1.250 + mPrevChannelSink = nullptr; 1.251 + } 1.252 +} 1.253 + 1.254 +class imgRequestMainThreadCancel : public nsRunnable 1.255 +{ 1.256 +public: 1.257 + imgRequestMainThreadCancel(imgRequest *aImgRequest, nsresult aStatus) 1.258 + : mImgRequest(aImgRequest) 1.259 + , mStatus(aStatus) 1.260 + { 1.261 + MOZ_ASSERT(!NS_IsMainThread(), "Create me off main thread only!"); 1.262 + MOZ_ASSERT(aImgRequest); 1.263 + } 1.264 + 1.265 + NS_IMETHOD Run() 1.266 + { 1.267 + MOZ_ASSERT(NS_IsMainThread(), "I should be running on the main thread!"); 1.268 + mImgRequest->ContinueCancel(mStatus); 1.269 + return NS_OK; 1.270 + } 1.271 +private: 1.272 + nsRefPtr<imgRequest> mImgRequest; 1.273 + nsresult mStatus; 1.274 +}; 1.275 + 1.276 +void imgRequest::Cancel(nsresult aStatus) 1.277 +{ 1.278 + /* The Cancel() method here should only be called by this class. */ 1.279 + 1.280 + LOG_SCOPE(GetImgLog(), "imgRequest::Cancel"); 1.281 + 1.282 + nsRefPtr<imgStatusTracker> statusTracker = GetStatusTracker(); 1.283 + 1.284 + statusTracker->MaybeUnblockOnload(); 1.285 + 1.286 + statusTracker->RecordCancel(); 1.287 + 1.288 + if (NS_IsMainThread()) { 1.289 + ContinueCancel(aStatus); 1.290 + } else { 1.291 + NS_DispatchToMainThread(new imgRequestMainThreadCancel(this, aStatus)); 1.292 + } 1.293 +} 1.294 + 1.295 +void imgRequest::ContinueCancel(nsresult aStatus) 1.296 +{ 1.297 + MOZ_ASSERT(NS_IsMainThread()); 1.298 + 1.299 + RemoveFromCache(); 1.300 + 1.301 + nsRefPtr<imgStatusTracker> statusTracker = GetStatusTracker(); 1.302 + if (mRequest && statusTracker->IsLoading()) { 1.303 + mRequest->Cancel(aStatus); 1.304 + } 1.305 +} 1.306 + 1.307 +nsresult imgRequest::GetURI(ImageURL **aURI) 1.308 +{ 1.309 + MOZ_ASSERT(aURI); 1.310 + 1.311 + LOG_FUNC(GetImgLog(), "imgRequest::GetURI"); 1.312 + 1.313 + if (mURI) { 1.314 + *aURI = mURI; 1.315 + NS_ADDREF(*aURI); 1.316 + return NS_OK; 1.317 + } 1.318 + 1.319 + return NS_ERROR_FAILURE; 1.320 +} 1.321 + 1.322 +nsresult imgRequest::GetSecurityInfo(nsISupports **aSecurityInfo) 1.323 +{ 1.324 + LOG_FUNC(GetImgLog(), "imgRequest::GetSecurityInfo"); 1.325 + 1.326 + // Missing security info means this is not a security load 1.327 + // i.e. it is not an error when security info is missing 1.328 + NS_IF_ADDREF(*aSecurityInfo = mSecurityInfo); 1.329 + return NS_OK; 1.330 +} 1.331 + 1.332 +void imgRequest::RemoveFromCache() 1.333 +{ 1.334 + LOG_SCOPE(GetImgLog(), "imgRequest::RemoveFromCache"); 1.335 + 1.336 + if (mIsInCache) { 1.337 + // mCacheEntry is nulled out when we have no more observers. 1.338 + if (mCacheEntry) 1.339 + mLoader->RemoveFromCache(mCacheEntry); 1.340 + else { 1.341 + mLoader->RemoveFromCache(mLoader->GetCacheKey(mFirstPartyIsolationURI, mURI, nullptr), 1.342 + mLoader->GetCache(mURI), 1.343 + mLoader->GetCacheQueue(mURI)); 1.344 + } 1.345 + } 1.346 + 1.347 + mCacheEntry = nullptr; 1.348 +} 1.349 + 1.350 +int32_t imgRequest::Priority() const 1.351 +{ 1.352 + int32_t priority = nsISupportsPriority::PRIORITY_NORMAL; 1.353 + nsCOMPtr<nsISupportsPriority> p = do_QueryInterface(mRequest); 1.354 + if (p) 1.355 + p->GetPriority(&priority); 1.356 + return priority; 1.357 +} 1.358 + 1.359 +void imgRequest::AdjustPriority(imgRequestProxy *proxy, int32_t delta) 1.360 +{ 1.361 + // only the first proxy is allowed to modify the priority of this image load. 1.362 + // 1.363 + // XXX(darin): this is probably not the most optimal algorithm as we may want 1.364 + // to increase the priority of requests that have a lot of proxies. the key 1.365 + // concern though is that image loads remain lower priority than other pieces 1.366 + // of content such as link clicks, CSS, and JS. 1.367 + // 1.368 + nsRefPtr<imgStatusTracker> statusTracker = GetStatusTracker(); 1.369 + if (!statusTracker->FirstConsumerIs(proxy)) 1.370 + return; 1.371 + 1.372 + nsCOMPtr<nsISupportsPriority> p = do_QueryInterface(mChannel); 1.373 + if (p) 1.374 + p->AdjustPriority(delta); 1.375 +} 1.376 + 1.377 +void imgRequest::SetIsInCache(bool incache) 1.378 +{ 1.379 + LOG_FUNC_WITH_PARAM(GetImgLog(), "imgRequest::SetIsCacheable", "incache", incache); 1.380 + mIsInCache = incache; 1.381 +} 1.382 + 1.383 +void imgRequest::UpdateCacheEntrySize() 1.384 +{ 1.385 + if (mCacheEntry) 1.386 + mCacheEntry->SetDataSize(mImage->SizeOfData()); 1.387 +} 1.388 + 1.389 +void imgRequest::SetCacheValidation(imgCacheEntry* aCacheEntry, nsIRequest* aRequest) 1.390 +{ 1.391 + /* get the expires info */ 1.392 + if (aCacheEntry) { 1.393 + nsCOMPtr<nsICachingChannel> cacheChannel(do_QueryInterface(aRequest)); 1.394 + if (cacheChannel) { 1.395 + nsCOMPtr<nsISupports> cacheToken; 1.396 + cacheChannel->GetCacheToken(getter_AddRefs(cacheToken)); 1.397 + if (cacheToken) { 1.398 + nsCOMPtr<nsICacheEntry> entryDesc(do_QueryInterface(cacheToken)); 1.399 + if (entryDesc) { 1.400 + uint32_t expiration; 1.401 + /* get the expiration time from the caching channel's token */ 1.402 + entryDesc->GetExpirationTime(&expiration); 1.403 + 1.404 + // Expiration time defaults to 0. We set the expiration time on our 1.405 + // entry if it hasn't been set yet. 1.406 + if (aCacheEntry->GetExpiryTime() == 0) 1.407 + aCacheEntry->SetExpiryTime(expiration); 1.408 + } 1.409 + } 1.410 + } 1.411 + 1.412 + // Determine whether the cache entry must be revalidated when we try to use it. 1.413 + // Currently, only HTTP specifies this information... 1.414 + nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(aRequest)); 1.415 + if (httpChannel) { 1.416 + bool bMustRevalidate = false; 1.417 + 1.418 + httpChannel->IsNoStoreResponse(&bMustRevalidate); 1.419 + 1.420 + if (!bMustRevalidate) { 1.421 + httpChannel->IsNoCacheResponse(&bMustRevalidate); 1.422 + } 1.423 + 1.424 + if (!bMustRevalidate) { 1.425 + nsAutoCString cacheHeader; 1.426 + 1.427 + httpChannel->GetResponseHeader(NS_LITERAL_CSTRING("Cache-Control"), 1.428 + cacheHeader); 1.429 + if (PL_strcasestr(cacheHeader.get(), "must-revalidate")) { 1.430 + bMustRevalidate = true; 1.431 + } 1.432 + } 1.433 + 1.434 + // Cache entries default to not needing to validate. We ensure that 1.435 + // multiple calls to this function don't override an earlier decision to 1.436 + // validate by making validation a one-way decision. 1.437 + if (bMustRevalidate) 1.438 + aCacheEntry->SetMustValidate(bMustRevalidate); 1.439 + } 1.440 + 1.441 + // We always need to validate file URIs. 1.442 + nsCOMPtr<nsIChannel> channel = do_QueryInterface(aRequest); 1.443 + if (channel) { 1.444 + nsCOMPtr<nsIURI> uri; 1.445 + channel->GetURI(getter_AddRefs(uri)); 1.446 + bool isfile = false; 1.447 + uri->SchemeIs("file", &isfile); 1.448 + if (isfile) 1.449 + aCacheEntry->SetMustValidate(isfile); 1.450 + } 1.451 + } 1.452 +} 1.453 + 1.454 +namespace { // anon 1.455 + 1.456 +already_AddRefed<nsIApplicationCache> 1.457 +GetApplicationCache(nsIRequest* aRequest) 1.458 +{ 1.459 + nsresult rv; 1.460 + 1.461 + nsCOMPtr<nsIApplicationCacheChannel> appCacheChan = do_QueryInterface(aRequest); 1.462 + if (!appCacheChan) { 1.463 + return nullptr; 1.464 + } 1.465 + 1.466 + bool fromAppCache; 1.467 + rv = appCacheChan->GetLoadedFromApplicationCache(&fromAppCache); 1.468 + NS_ENSURE_SUCCESS(rv, nullptr); 1.469 + 1.470 + if (!fromAppCache) { 1.471 + return nullptr; 1.472 + } 1.473 + 1.474 + nsCOMPtr<nsIApplicationCache> appCache; 1.475 + rv = appCacheChan->GetApplicationCache(getter_AddRefs(appCache)); 1.476 + NS_ENSURE_SUCCESS(rv, nullptr); 1.477 + 1.478 + return appCache.forget(); 1.479 +} 1.480 + 1.481 +} // anon 1.482 + 1.483 +bool 1.484 +imgRequest::CacheChanged(nsIRequest* aNewRequest) 1.485 +{ 1.486 + nsCOMPtr<nsIApplicationCache> newAppCache = GetApplicationCache(aNewRequest); 1.487 + 1.488 + // Application cache not involved at all or the same app cache involved 1.489 + // in both of the loads (original and new). 1.490 + if (newAppCache == mApplicationCache) 1.491 + return false; 1.492 + 1.493 + // In a rare case it may happen that two objects still refer 1.494 + // the same application cache version. 1.495 + if (newAppCache && mApplicationCache) { 1.496 + nsresult rv; 1.497 + 1.498 + nsAutoCString oldAppCacheClientId, newAppCacheClientId; 1.499 + rv = mApplicationCache->GetClientID(oldAppCacheClientId); 1.500 + NS_ENSURE_SUCCESS(rv, true); 1.501 + rv = newAppCache->GetClientID(newAppCacheClientId); 1.502 + NS_ENSURE_SUCCESS(rv, true); 1.503 + 1.504 + if (oldAppCacheClientId == newAppCacheClientId) 1.505 + return false; 1.506 + } 1.507 + 1.508 + // When we get here, app caches differ or app cache is involved 1.509 + // just in one of the loads what we also consider as a change 1.510 + // in a loading cache. 1.511 + return true; 1.512 +} 1.513 + 1.514 +nsresult 1.515 +imgRequest::LockImage() 1.516 +{ 1.517 + return mImage->LockImage(); 1.518 +} 1.519 + 1.520 +nsresult 1.521 +imgRequest::UnlockImage() 1.522 +{ 1.523 + return mImage->UnlockImage(); 1.524 +} 1.525 + 1.526 +nsresult 1.527 +imgRequest::RequestDecode() 1.528 +{ 1.529 + // If we've initialized our image, we can request a decode. 1.530 + if (mImage) { 1.531 + return mImage->RequestDecode(); 1.532 + } 1.533 + 1.534 + // Otherwise, flag to do it when we get the image 1.535 + mDecodeRequested = true; 1.536 + 1.537 + return NS_OK; 1.538 +} 1.539 + 1.540 +nsresult 1.541 +imgRequest::StartDecoding() 1.542 +{ 1.543 + // If we've initialized our image, we can request a decode. 1.544 + if (mImage) { 1.545 + return mImage->StartDecoding(); 1.546 + } 1.547 + 1.548 + // Otherwise, flag to do it when we get the image 1.549 + mDecodeRequested = true; 1.550 + 1.551 + return NS_OK; 1.552 +} 1.553 + 1.554 +/** nsIRequestObserver methods **/ 1.555 + 1.556 +/* void onStartRequest (in nsIRequest request, in nsISupports ctxt); */ 1.557 +NS_IMETHODIMP imgRequest::OnStartRequest(nsIRequest *aRequest, nsISupports *ctxt) 1.558 +{ 1.559 + LOG_SCOPE(GetImgLog(), "imgRequest::OnStartRequest"); 1.560 + 1.561 + // Figure out if we're multipart 1.562 + nsCOMPtr<nsIMultiPartChannel> mpchan(do_QueryInterface(aRequest)); 1.563 + nsRefPtr<imgStatusTracker> statusTracker = GetStatusTracker(); 1.564 + if (mpchan) { 1.565 + mIsMultiPartChannel = true; 1.566 + statusTracker->SetIsMultipart(); 1.567 + } 1.568 + 1.569 + // If we're not multipart, we shouldn't have an image yet 1.570 + NS_ABORT_IF_FALSE(mIsMultiPartChannel || !mImage, 1.571 + "Already have an image for non-multipart request"); 1.572 + 1.573 + // If we're multipart and about to load another image, signal so we can 1.574 + // detect the mime type in OnDataAvailable. 1.575 + if (mIsMultiPartChannel && mImage) { 1.576 + mResniffMimeType = true; 1.577 + 1.578 + // Tell the image to reinitialize itself. We have to do this in 1.579 + // OnStartRequest so that its state machine is always in a consistent 1.580 + // state. 1.581 + // Note that if our MIME type changes, mImage will be replaced with a 1.582 + // new object. 1.583 + mImage->OnNewSourceData(); 1.584 + } 1.585 + 1.586 + /* 1.587 + * If mRequest is null here, then we need to set it so that we'll be able to 1.588 + * cancel it if our Cancel() method is called. Note that this can only 1.589 + * happen for multipart channels. We could simply not null out mRequest for 1.590 + * non-last parts, if GetIsLastPart() were reliable, but it's not. See 1.591 + * https://bugzilla.mozilla.org/show_bug.cgi?id=339610 1.592 + */ 1.593 + if (!mRequest) { 1.594 + NS_ASSERTION(mpchan, 1.595 + "We should have an mRequest here unless we're multipart"); 1.596 + nsCOMPtr<nsIChannel> chan; 1.597 + mpchan->GetBaseChannel(getter_AddRefs(chan)); 1.598 + mRequest = chan; 1.599 + } 1.600 + 1.601 + // Note: refreshing statusTracker in case OnNewSourceData changed it. 1.602 + statusTracker = GetStatusTracker(); 1.603 + statusTracker->OnStartRequest(); 1.604 + 1.605 + nsCOMPtr<nsIChannel> channel(do_QueryInterface(aRequest)); 1.606 + if (channel) 1.607 + channel->GetSecurityInfo(getter_AddRefs(mSecurityInfo)); 1.608 + 1.609 + /* Get our principal */ 1.610 + nsCOMPtr<nsIChannel> chan(do_QueryInterface(aRequest)); 1.611 + if (chan) { 1.612 + nsCOMPtr<nsIScriptSecurityManager> secMan = nsContentUtils::GetSecurityManager(); 1.613 + if (secMan) { 1.614 + nsresult rv = secMan->GetChannelPrincipal(chan, 1.615 + getter_AddRefs(mPrincipal)); 1.616 + if (NS_FAILED(rv)) { 1.617 + return rv; 1.618 + } 1.619 + } 1.620 + } 1.621 + 1.622 + SetCacheValidation(mCacheEntry, aRequest); 1.623 + 1.624 + mApplicationCache = GetApplicationCache(aRequest); 1.625 + 1.626 + // Shouldn't we be dead already if this gets hit? Probably multipart/x-mixed-replace... 1.627 + if (statusTracker->ConsumerCount() == 0) { 1.628 + this->Cancel(NS_IMAGELIB_ERROR_FAILURE); 1.629 + } 1.630 + 1.631 + // Try to retarget OnDataAvailable to a decode thread. 1.632 + nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aRequest); 1.633 + nsCOMPtr<nsIThreadRetargetableRequest> retargetable = 1.634 + do_QueryInterface(aRequest); 1.635 + if (httpChannel && retargetable && 1.636 + ImageFactory::CanRetargetOnDataAvailable(mURI, mIsMultiPartChannel)) { 1.637 + nsAutoCString mimeType; 1.638 + nsresult rv = httpChannel->GetContentType(mimeType); 1.639 + if (NS_SUCCEEDED(rv) && !mimeType.EqualsLiteral(IMAGE_SVG_XML)) { 1.640 + // Image object not created until OnDataAvailable, so forward to static 1.641 + // DecodePool directly. 1.642 + nsCOMPtr<nsIEventTarget> target = RasterImage::GetEventTarget(); 1.643 + rv = retargetable->RetargetDeliveryTo(target); 1.644 + } 1.645 + PR_LOG(GetImgLog(), PR_LOG_WARNING, 1.646 + ("[this=%p] imgRequest::OnStartRequest -- " 1.647 + "RetargetDeliveryTo rv %d=%s\n", 1.648 + this, rv, NS_SUCCEEDED(rv) ? "succeeded" : "failed")); 1.649 + } 1.650 + 1.651 + return NS_OK; 1.652 +} 1.653 + 1.654 +/* void onStopRequest (in nsIRequest request, in nsISupports ctxt, in nsresult status); */ 1.655 +NS_IMETHODIMP imgRequest::OnStopRequest(nsIRequest *aRequest, nsISupports *ctxt, nsresult status) 1.656 +{ 1.657 + LOG_FUNC(GetImgLog(), "imgRequest::OnStopRequest"); 1.658 + 1.659 + // XXXldb What if this is a non-last part of a multipart request? 1.660 + // xxx before we release our reference to mRequest, lets 1.661 + // save the last status that we saw so that the 1.662 + // imgRequestProxy will have access to it. 1.663 + if (mRequest) { 1.664 + mRequest = nullptr; // we no longer need the request 1.665 + } 1.666 + 1.667 + // stop holding a ref to the channel, since we don't need it anymore 1.668 + if (mChannel) { 1.669 + mChannel->SetNotificationCallbacks(mPrevChannelSink); 1.670 + mPrevChannelSink = nullptr; 1.671 + mChannel = nullptr; 1.672 + } 1.673 + 1.674 + bool lastPart = true; 1.675 + nsCOMPtr<nsIMultiPartChannel> mpchan(do_QueryInterface(aRequest)); 1.676 + if (mpchan) 1.677 + mpchan->GetIsLastPart(&lastPart); 1.678 + 1.679 + // Tell the image that it has all of the source data. Note that this can 1.680 + // trigger a failure, since the image might be waiting for more non-optional 1.681 + // data and this is the point where we break the news that it's not coming. 1.682 + if (mImage) { 1.683 + nsresult rv = mImage->OnImageDataComplete(aRequest, ctxt, status, lastPart); 1.684 + 1.685 + // If we got an error in the OnImageDataComplete() call, we don't want to 1.686 + // proceed as if nothing bad happened. However, we also want to give 1.687 + // precedence to failure status codes from necko, since presumably they're 1.688 + // more meaningful. 1.689 + if (NS_FAILED(rv) && NS_SUCCEEDED(status)) 1.690 + status = rv; 1.691 + } 1.692 + 1.693 + // If the request went through, update the cache entry size. Otherwise, 1.694 + // cancel the request, which removes us from the cache. 1.695 + if (mImage && NS_SUCCEEDED(status)) { 1.696 + // We update the cache entry size here because this is where we finish 1.697 + // loading compressed source data, which is part of our size calculus. 1.698 + UpdateCacheEntrySize(); 1.699 + } 1.700 + else { 1.701 + // stops animations, removes from cache 1.702 + this->Cancel(status); 1.703 + } 1.704 + 1.705 + if (!mImage) { 1.706 + // We have to fire imgStatusTracker::OnStopRequest ourselves because there's 1.707 + // no image capable of doing so. 1.708 + nsRefPtr<imgStatusTracker> statusTracker = GetStatusTracker(); 1.709 + statusTracker->OnStopRequest(lastPart, status); 1.710 + } 1.711 + 1.712 + mTimedChannel = nullptr; 1.713 + return NS_OK; 1.714 +} 1.715 + 1.716 +struct mimetype_closure 1.717 +{ 1.718 + nsACString* newType; 1.719 +}; 1.720 + 1.721 +/* prototype for these defined below */ 1.722 +static NS_METHOD sniff_mimetype_callback(nsIInputStream* in, void* closure, const char* fromRawSegment, 1.723 + uint32_t toOffset, uint32_t count, uint32_t *writeCount); 1.724 + 1.725 +/** nsThreadRetargetableStreamListener methods **/ 1.726 +NS_IMETHODIMP 1.727 +imgRequest::CheckListenerChain() 1.728 +{ 1.729 + // TODO Might need more checking here. 1.730 + NS_ASSERTION(NS_IsMainThread(), "Should be on the main thread!"); 1.731 + return NS_OK; 1.732 +} 1.733 + 1.734 +/** nsIStreamListener methods **/ 1.735 + 1.736 +/* void onDataAvailable (in nsIRequest request, in nsISupports ctxt, in nsIInputStream inStr, in unsigned long long sourceOffset, in unsigned long count); */ 1.737 +NS_IMETHODIMP 1.738 +imgRequest::OnDataAvailable(nsIRequest *aRequest, nsISupports *ctxt, 1.739 + nsIInputStream *inStr, uint64_t sourceOffset, 1.740 + uint32_t count) 1.741 +{ 1.742 + LOG_SCOPE_WITH_PARAM(GetImgLog(), "imgRequest::OnDataAvailable", "count", count); 1.743 + 1.744 + NS_ASSERTION(aRequest, "imgRequest::OnDataAvailable -- no request!"); 1.745 + 1.746 + nsresult rv; 1.747 + 1.748 + if (!mGotData || mResniffMimeType) { 1.749 + LOG_SCOPE(GetImgLog(), "imgRequest::OnDataAvailable |First time through... finding mimetype|"); 1.750 + 1.751 + mGotData = true; 1.752 + 1.753 + // Store and reset this for the invariant that it's always false after 1.754 + // calls to OnDataAvailable (see bug 907575) 1.755 + bool resniffMimeType = mResniffMimeType; 1.756 + mResniffMimeType = false; 1.757 + 1.758 + mimetype_closure closure; 1.759 + nsAutoCString newType; 1.760 + closure.newType = &newType; 1.761 + 1.762 + /* look at the first few bytes and see if we can tell what the data is from that 1.763 + * since servers tend to lie. :( 1.764 + */ 1.765 + uint32_t out; 1.766 + inStr->ReadSegments(sniff_mimetype_callback, &closure, count, &out); 1.767 + 1.768 + nsCOMPtr<nsIChannel> chan(do_QueryInterface(aRequest)); 1.769 + if (newType.IsEmpty()) { 1.770 + LOG_SCOPE(GetImgLog(), "imgRequest::OnDataAvailable |sniffing of mimetype failed|"); 1.771 + 1.772 + rv = NS_ERROR_FAILURE; 1.773 + if (chan) { 1.774 + rv = chan->GetContentType(newType); 1.775 + } 1.776 + 1.777 + if (NS_FAILED(rv)) { 1.778 + PR_LOG(GetImgLog(), PR_LOG_ERROR, 1.779 + ("[this=%p] imgRequest::OnDataAvailable -- Content type unavailable from the channel\n", 1.780 + this)); 1.781 + 1.782 + this->Cancel(NS_IMAGELIB_ERROR_FAILURE); 1.783 + 1.784 + return NS_BINDING_ABORTED; 1.785 + } 1.786 + 1.787 + LOG_MSG(GetImgLog(), "imgRequest::OnDataAvailable", "Got content type from the channel"); 1.788 + } 1.789 + 1.790 + // If we're a regular image and this is the first call to OnDataAvailable, 1.791 + // this will always be true. If we've resniffed our MIME type (i.e. we're a 1.792 + // multipart/x-mixed-replace image), we have to be able to switch our image 1.793 + // type and decoder. 1.794 + // We always reinitialize for SVGs, because they have no way of 1.795 + // reinitializing themselves. 1.796 + if (mContentType != newType || newType.EqualsLiteral(IMAGE_SVG_XML)) { 1.797 + mContentType = newType; 1.798 + 1.799 + // If we've resniffed our MIME type and it changed, we need to create a 1.800 + // new status tracker to give to the image, because we don't have one of 1.801 + // our own any more. 1.802 + if (resniffMimeType) { 1.803 + NS_ABORT_IF_FALSE(mIsMultiPartChannel, "Resniffing a non-multipart image"); 1.804 + 1.805 + nsRefPtr<imgStatusTracker> freshTracker = new imgStatusTracker(nullptr); 1.806 + nsRefPtr<imgStatusTracker> oldStatusTracker = GetStatusTracker(); 1.807 + freshTracker->AdoptConsumers(oldStatusTracker); 1.808 + mStatusTracker = freshTracker.forget(); 1.809 + } 1.810 + 1.811 + SetProperties(chan); 1.812 + 1.813 + LOG_MSG_WITH_PARAM(GetImgLog(), "imgRequest::OnDataAvailable", "content type", mContentType.get()); 1.814 + 1.815 + // XXX If server lied about mimetype and it's SVG, we may need to copy 1.816 + // the data and dispatch back to the main thread, AND tell the channel to 1.817 + // dispatch there in the future. 1.818 + 1.819 + // Now we can create a new image to hold the data. If we don't have a decoder 1.820 + // for this mimetype we'll find out about it here. 1.821 + mImage = ImageFactory::CreateImage(aRequest, mStatusTracker, mContentType, 1.822 + mURI, mIsMultiPartChannel, 1.823 + static_cast<uint32_t>(mInnerWindowId)); 1.824 + 1.825 + // Release our copy of the status tracker since the image owns it now. 1.826 + mStatusTracker = nullptr; 1.827 + 1.828 + // Notify listeners that we have an image. 1.829 + // XXX(seth): The name of this notification method is pretty misleading. 1.830 + nsRefPtr<imgStatusTracker> statusTracker = GetStatusTracker(); 1.831 + statusTracker->OnDataAvailable(); 1.832 + 1.833 + if (mImage->HasError() && !mIsMultiPartChannel) { // Probably bad mimetype 1.834 + // We allow multipart images to fail to initialize without cancelling the 1.835 + // load because subsequent images might be fine; thus only single part 1.836 + // images end up here. 1.837 + this->Cancel(NS_ERROR_FAILURE); 1.838 + return NS_BINDING_ABORTED; 1.839 + } 1.840 + 1.841 + NS_ABORT_IF_FALSE(statusTracker->HasImage(), "Status tracker should have an image!"); 1.842 + NS_ABORT_IF_FALSE(mImage, "imgRequest should have an image!"); 1.843 + 1.844 + if (mDecodeRequested) 1.845 + mImage->StartDecoding(); 1.846 + } 1.847 + } 1.848 + 1.849 + // Notify the image that it has new data. 1.850 + rv = mImage->OnImageDataAvailable(aRequest, ctxt, inStr, sourceOffset, count); 1.851 + 1.852 + if (NS_FAILED(rv)) { 1.853 + PR_LOG(GetImgLog(), PR_LOG_WARNING, 1.854 + ("[this=%p] imgRequest::OnDataAvailable -- " 1.855 + "copy to RasterImage failed\n", this)); 1.856 + this->Cancel(NS_IMAGELIB_ERROR_FAILURE); 1.857 + return NS_BINDING_ABORTED; 1.858 + } 1.859 + 1.860 + return NS_OK; 1.861 +} 1.862 + 1.863 +class SetPropertiesEvent : public nsRunnable 1.864 +{ 1.865 +public: 1.866 + SetPropertiesEvent(imgRequest* aImgRequest, nsIChannel* aChan) 1.867 + : mImgRequest(aImgRequest) 1.868 + , mChan(aChan) 1.869 + { 1.870 + MOZ_ASSERT(!NS_IsMainThread(), "Should be created off the main thread"); 1.871 + MOZ_ASSERT(aImgRequest, "aImgRequest cannot be null"); 1.872 + } 1.873 + NS_IMETHOD Run() 1.874 + { 1.875 + MOZ_ASSERT(NS_IsMainThread(), "Should run on the main thread only"); 1.876 + MOZ_ASSERT(mImgRequest, "mImgRequest cannot be null"); 1.877 + mImgRequest->SetProperties(mChan); 1.878 + return NS_OK; 1.879 + } 1.880 +private: 1.881 + nsRefPtr<imgRequest> mImgRequest; 1.882 + nsCOMPtr<nsIChannel> mChan; 1.883 +}; 1.884 + 1.885 +void 1.886 +imgRequest::SetProperties(nsIChannel* aChan) 1.887 +{ 1.888 + // Force execution on main thread since some property objects are non 1.889 + // threadsafe. 1.890 + if (!NS_IsMainThread()) { 1.891 + NS_DispatchToMainThread(new SetPropertiesEvent(this, aChan)); 1.892 + return; 1.893 + } 1.894 + /* set our mimetype as a property */ 1.895 + nsCOMPtr<nsISupportsCString> contentType(do_CreateInstance("@mozilla.org/supports-cstring;1")); 1.896 + if (contentType) { 1.897 + contentType->SetData(mContentType); 1.898 + mProperties->Set("type", contentType); 1.899 + } 1.900 + 1.901 + /* set our content disposition as a property */ 1.902 + nsAutoCString disposition; 1.903 + if (aChan) { 1.904 + aChan->GetContentDispositionHeader(disposition); 1.905 + } 1.906 + if (!disposition.IsEmpty()) { 1.907 + nsCOMPtr<nsISupportsCString> contentDisposition(do_CreateInstance("@mozilla.org/supports-cstring;1")); 1.908 + if (contentDisposition) { 1.909 + contentDisposition->SetData(disposition); 1.910 + mProperties->Set("content-disposition", contentDisposition); 1.911 + } 1.912 + } 1.913 +} 1.914 + 1.915 +static NS_METHOD sniff_mimetype_callback(nsIInputStream* in, 1.916 + void* data, 1.917 + const char* fromRawSegment, 1.918 + uint32_t toOffset, 1.919 + uint32_t count, 1.920 + uint32_t *writeCount) 1.921 +{ 1.922 + mimetype_closure* closure = static_cast<mimetype_closure*>(data); 1.923 + 1.924 + NS_ASSERTION(closure, "closure is null!"); 1.925 + 1.926 + if (count > 0) 1.927 + imgLoader::GetMimeTypeFromContent(fromRawSegment, count, *closure->newType); 1.928 + 1.929 + *writeCount = 0; 1.930 + return NS_ERROR_FAILURE; 1.931 +} 1.932 + 1.933 + 1.934 +/** nsIInterfaceRequestor methods **/ 1.935 + 1.936 +NS_IMETHODIMP 1.937 +imgRequest::GetInterface(const nsIID & aIID, void **aResult) 1.938 +{ 1.939 + if (!mPrevChannelSink || aIID.Equals(NS_GET_IID(nsIChannelEventSink))) 1.940 + return QueryInterface(aIID, aResult); 1.941 + 1.942 + NS_ASSERTION(mPrevChannelSink != this, 1.943 + "Infinite recursion - don't keep track of channel sinks that are us!"); 1.944 + return mPrevChannelSink->GetInterface(aIID, aResult); 1.945 +} 1.946 + 1.947 +/** nsIChannelEventSink methods **/ 1.948 +NS_IMETHODIMP 1.949 +imgRequest::AsyncOnChannelRedirect(nsIChannel *oldChannel, 1.950 + nsIChannel *newChannel, uint32_t flags, 1.951 + nsIAsyncVerifyRedirectCallback *callback) 1.952 +{ 1.953 + NS_ASSERTION(mRequest && mChannel, "Got a channel redirect after we nulled out mRequest!"); 1.954 + NS_ASSERTION(mChannel == oldChannel, "Got a channel redirect for an unknown channel!"); 1.955 + NS_ASSERTION(newChannel, "Got a redirect to a NULL channel!"); 1.956 + 1.957 + SetCacheValidation(mCacheEntry, oldChannel); 1.958 + 1.959 + // Prepare for callback 1.960 + mRedirectCallback = callback; 1.961 + mNewRedirectChannel = newChannel; 1.962 + 1.963 + nsCOMPtr<nsIChannelEventSink> sink(do_GetInterface(mPrevChannelSink)); 1.964 + if (sink) { 1.965 + nsresult rv = sink->AsyncOnChannelRedirect(oldChannel, newChannel, flags, 1.966 + this); 1.967 + if (NS_FAILED(rv)) { 1.968 + mRedirectCallback = nullptr; 1.969 + mNewRedirectChannel = nullptr; 1.970 + } 1.971 + return rv; 1.972 + } 1.973 + 1.974 + (void) OnRedirectVerifyCallback(NS_OK); 1.975 + return NS_OK; 1.976 +} 1.977 + 1.978 +NS_IMETHODIMP 1.979 +imgRequest::OnRedirectVerifyCallback(nsresult result) 1.980 +{ 1.981 + NS_ASSERTION(mRedirectCallback, "mRedirectCallback not set in callback"); 1.982 + NS_ASSERTION(mNewRedirectChannel, "mNewRedirectChannel not set in callback"); 1.983 + 1.984 + if (NS_FAILED(result)) { 1.985 + mRedirectCallback->OnRedirectVerifyCallback(result); 1.986 + mRedirectCallback = nullptr; 1.987 + mNewRedirectChannel = nullptr; 1.988 + return NS_OK; 1.989 + } 1.990 + 1.991 + mChannel = mNewRedirectChannel; 1.992 + mTimedChannel = do_QueryInterface(mChannel); 1.993 + mNewRedirectChannel = nullptr; 1.994 + 1.995 +#if defined(PR_LOGGING) 1.996 + nsAutoCString oldspec; 1.997 + if (mCurrentURI) 1.998 + mCurrentURI->GetSpec(oldspec); 1.999 + LOG_MSG_WITH_PARAM(GetImgLog(), "imgRequest::OnChannelRedirect", "old", oldspec.get()); 1.1000 +#endif 1.1001 + 1.1002 + // make sure we have a protocol that returns data rather than opens 1.1003 + // an external application, e.g. mailto: 1.1004 + mChannel->GetURI(getter_AddRefs(mCurrentURI)); 1.1005 + bool doesNotReturnData = false; 1.1006 + nsresult rv = 1.1007 + NS_URIChainHasFlags(mCurrentURI, nsIProtocolHandler::URI_DOES_NOT_RETURN_DATA, 1.1008 + &doesNotReturnData); 1.1009 + 1.1010 + if (NS_SUCCEEDED(rv) && doesNotReturnData) 1.1011 + rv = NS_ERROR_ABORT; 1.1012 + 1.1013 + if (NS_FAILED(rv)) { 1.1014 + mRedirectCallback->OnRedirectVerifyCallback(rv); 1.1015 + mRedirectCallback = nullptr; 1.1016 + return NS_OK; 1.1017 + } 1.1018 + 1.1019 + mRedirectCallback->OnRedirectVerifyCallback(NS_OK); 1.1020 + mRedirectCallback = nullptr; 1.1021 + return NS_OK; 1.1022 +}