michael@0: /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* vim: set ts=8 sts=2 et sw=2 tw=80: */ 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 "mozilla/Attributes.h" michael@0: #include "mozilla/Preferences.h" michael@0: #include "mozilla/ClearOnShutdown.h" michael@0: michael@0: #include "ImageLogging.h" michael@0: #include "nsPrintfCString.h" michael@0: #include "imgLoader.h" michael@0: #include "imgRequestProxy.h" michael@0: michael@0: #include "nsCOMPtr.h" michael@0: michael@0: #include "nsContentUtils.h" michael@0: #include "nsCrossSiteListenerProxy.h" michael@0: #include "nsNetUtil.h" michael@0: #include "nsMimeTypes.h" michael@0: #include "nsStreamUtils.h" michael@0: #include "nsIHttpChannel.h" michael@0: #include "nsICachingChannel.h" michael@0: #include "nsIInterfaceRequestor.h" michael@0: #include "nsIProgressEventSink.h" michael@0: #include "nsIChannelEventSink.h" michael@0: #include "nsIAsyncVerifyRedirectCallback.h" michael@0: #include "nsIFileURL.h" michael@0: #include "nsCRT.h" michael@0: #include "nsIDocument.h" michael@0: #include "nsINetworkSeer.h" michael@0: #include "nsIConsoleService.h" michael@0: michael@0: #include "nsIApplicationCache.h" michael@0: #include "nsIApplicationCacheContainer.h" michael@0: michael@0: #include "nsIMemoryReporter.h" michael@0: #include "Image.h" michael@0: #include "DiscardTracker.h" michael@0: michael@0: // we want to explore making the document own the load group michael@0: // so we can associate the document URI with the load group. michael@0: // until this point, we have an evil hack: michael@0: #include "nsIHttpChannelInternal.h" michael@0: #include "nsILoadContext.h" michael@0: #include "nsILoadGroupChild.h" michael@0: michael@0: using namespace mozilla; michael@0: using namespace mozilla::image; michael@0: michael@0: MOZ_DEFINE_MALLOC_SIZE_OF(ImagesMallocSizeOf) michael@0: michael@0: class imgMemoryReporter MOZ_FINAL : public nsIMemoryReporter michael@0: { michael@0: public: michael@0: NS_DECL_ISUPPORTS michael@0: michael@0: NS_IMETHOD CollectReports(nsIMemoryReporterCallback *callback, michael@0: nsISupports *closure) michael@0: { michael@0: AllSizes chrome; michael@0: AllSizes content; michael@0: michael@0: for (uint32_t i = 0; i < mKnownLoaders.Length(); i++) { michael@0: mKnownLoaders[i]->mChromeCache.EnumerateRead(EntryAllSizes, &chrome); michael@0: mKnownLoaders[i]->mCache.EnumerateRead(EntryAllSizes, &content); michael@0: } michael@0: michael@0: #define REPORT(_path, _kind, _amount, _desc) \ michael@0: do { \ michael@0: nsresult rv; \ michael@0: rv = callback->Callback(EmptyCString(), NS_LITERAL_CSTRING(_path), \ michael@0: _kind, UNITS_BYTES, _amount, \ michael@0: NS_LITERAL_CSTRING(_desc), closure); \ michael@0: NS_ENSURE_SUCCESS(rv, rv); \ michael@0: } while (0) michael@0: michael@0: REPORT("explicit/images/chrome/used/raw", michael@0: KIND_HEAP, chrome.mUsedRaw, michael@0: "Memory used by in-use chrome images (compressed data)."); michael@0: michael@0: REPORT("explicit/images/chrome/used/uncompressed-heap", michael@0: KIND_HEAP, chrome.mUsedUncompressedHeap, michael@0: "Memory used by in-use chrome images (uncompressed data)."); michael@0: michael@0: REPORT("explicit/images/chrome/used/uncompressed-nonheap", michael@0: KIND_NONHEAP, chrome.mUsedUncompressedNonheap, michael@0: "Memory used by in-use chrome images (uncompressed data)."); michael@0: michael@0: REPORT("explicit/images/chrome/unused/raw", michael@0: KIND_HEAP, chrome.mUnusedRaw, michael@0: "Memory used by not in-use chrome images (compressed data)."); michael@0: michael@0: REPORT("explicit/images/chrome/unused/uncompressed-heap", michael@0: KIND_HEAP, chrome.mUnusedUncompressedHeap, michael@0: "Memory used by not in-use chrome images (uncompressed data)."); michael@0: michael@0: REPORT("explicit/images/chrome/unused/uncompressed-nonheap", michael@0: KIND_NONHEAP, chrome.mUnusedUncompressedNonheap, michael@0: "Memory used by not in-use chrome images (uncompressed data)."); michael@0: michael@0: REPORT("explicit/images/content/used/raw", michael@0: KIND_HEAP, content.mUsedRaw, michael@0: "Memory used by in-use content images (compressed data)."); michael@0: michael@0: REPORT("explicit/images/content/used/uncompressed-heap", michael@0: KIND_HEAP, content.mUsedUncompressedHeap, michael@0: "Memory used by in-use content images (uncompressed data)."); michael@0: michael@0: REPORT("explicit/images/content/used/uncompressed-nonheap", michael@0: KIND_NONHEAP, content.mUsedUncompressedNonheap, michael@0: "Memory used by in-use content images (uncompressed data)."); michael@0: michael@0: REPORT("explicit/images/content/unused/raw", michael@0: KIND_HEAP, content.mUnusedRaw, michael@0: "Memory used by not in-use content images (compressed data)."); michael@0: michael@0: REPORT("explicit/images/content/unused/uncompressed-heap", michael@0: KIND_HEAP, content.mUnusedUncompressedHeap, michael@0: "Memory used by not in-use content images (uncompressed data)."); michael@0: michael@0: REPORT("explicit/images/content/unused/uncompressed-nonheap", michael@0: KIND_NONHEAP, content.mUnusedUncompressedNonheap, michael@0: "Memory used by not in-use content images (uncompressed data)."); michael@0: michael@0: #undef REPORT michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: static int64_t ImagesContentUsedUncompressedDistinguishedAmount() michael@0: { michael@0: size_t n = 0; michael@0: for (uint32_t i = 0; i < imgLoader::sMemReporter->mKnownLoaders.Length(); i++) { michael@0: imgLoader::sMemReporter->mKnownLoaders[i]->mCache.EnumerateRead(EntryUsedUncompressedSize, &n); michael@0: } michael@0: return n; michael@0: } michael@0: michael@0: void RegisterLoader(imgLoader* aLoader) michael@0: { michael@0: mKnownLoaders.AppendElement(aLoader); michael@0: } michael@0: michael@0: void UnregisterLoader(imgLoader* aLoader) michael@0: { michael@0: mKnownLoaders.RemoveElement(aLoader); michael@0: } michael@0: michael@0: private: michael@0: nsTArray mKnownLoaders; michael@0: michael@0: struct AllSizes { michael@0: size_t mUsedRaw; michael@0: size_t mUsedUncompressedHeap; michael@0: size_t mUsedUncompressedNonheap; michael@0: size_t mUnusedRaw; michael@0: size_t mUnusedUncompressedHeap; michael@0: size_t mUnusedUncompressedNonheap; michael@0: michael@0: AllSizes() { michael@0: memset(this, 0, sizeof(*this)); michael@0: } michael@0: }; michael@0: michael@0: static PLDHashOperator EntryAllSizes(const nsACString&, michael@0: imgCacheEntry *entry, michael@0: void *userArg) michael@0: { michael@0: nsRefPtr req = entry->GetRequest(); michael@0: Image *image = static_cast(req->mImage.get()); michael@0: if (image) { michael@0: AllSizes *sizes = static_cast(userArg); michael@0: if (entry->HasNoProxies()) { michael@0: sizes->mUnusedRaw += michael@0: image->HeapSizeOfSourceWithComputedFallback(ImagesMallocSizeOf); michael@0: sizes->mUnusedUncompressedHeap += michael@0: image->HeapSizeOfDecodedWithComputedFallback(ImagesMallocSizeOf); michael@0: sizes->mUnusedUncompressedNonheap += image->NonHeapSizeOfDecoded(); michael@0: } else { michael@0: sizes->mUsedRaw += michael@0: image->HeapSizeOfSourceWithComputedFallback(ImagesMallocSizeOf); michael@0: sizes->mUsedUncompressedHeap += michael@0: image->HeapSizeOfDecodedWithComputedFallback(ImagesMallocSizeOf); michael@0: sizes->mUsedUncompressedNonheap += image->NonHeapSizeOfDecoded(); michael@0: } michael@0: } michael@0: michael@0: return PL_DHASH_NEXT; michael@0: } michael@0: michael@0: static PLDHashOperator EntryUsedUncompressedSize(const nsACString&, michael@0: imgCacheEntry *entry, michael@0: void *userArg) michael@0: { michael@0: if (!entry->HasNoProxies()) { michael@0: size_t *n = static_cast(userArg); michael@0: nsRefPtr req = entry->GetRequest(); michael@0: Image *image = static_cast(req->mImage.get()); michael@0: if (image) { michael@0: // Both this and EntryAllSizes measure images-content-used-uncompressed michael@0: // memory. This function's measurement is secondary -- the result michael@0: // doesn't go in the "explicit" tree -- so we use moz_malloc_size_of michael@0: // instead of ImagesMallocSizeOf to prevent DMD from seeing it reported michael@0: // twice. michael@0: *n += image->HeapSizeOfDecodedWithComputedFallback(moz_malloc_size_of); michael@0: *n += image->NonHeapSizeOfDecoded(); michael@0: } michael@0: } michael@0: michael@0: return PL_DHASH_NEXT; michael@0: } michael@0: }; michael@0: michael@0: NS_IMPL_ISUPPORTS(imgMemoryReporter, nsIMemoryReporter) michael@0: michael@0: NS_IMPL_ISUPPORTS(nsProgressNotificationProxy, michael@0: nsIProgressEventSink, michael@0: nsIChannelEventSink, michael@0: nsIInterfaceRequestor) michael@0: michael@0: NS_IMETHODIMP michael@0: nsProgressNotificationProxy::OnProgress(nsIRequest* request, michael@0: nsISupports* ctxt, michael@0: uint64_t progress, michael@0: uint64_t progressMax) michael@0: { michael@0: nsCOMPtr loadGroup; michael@0: request->GetLoadGroup(getter_AddRefs(loadGroup)); michael@0: michael@0: nsCOMPtr target; michael@0: NS_QueryNotificationCallbacks(mOriginalCallbacks, michael@0: loadGroup, michael@0: NS_GET_IID(nsIProgressEventSink), michael@0: getter_AddRefs(target)); michael@0: if (!target) michael@0: return NS_OK; michael@0: return target->OnProgress(mImageRequest, ctxt, progress, progressMax); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsProgressNotificationProxy::OnStatus(nsIRequest* request, michael@0: nsISupports* ctxt, michael@0: nsresult status, michael@0: const char16_t* statusArg) michael@0: { michael@0: nsCOMPtr loadGroup; michael@0: request->GetLoadGroup(getter_AddRefs(loadGroup)); michael@0: michael@0: nsCOMPtr target; michael@0: NS_QueryNotificationCallbacks(mOriginalCallbacks, michael@0: loadGroup, michael@0: NS_GET_IID(nsIProgressEventSink), michael@0: getter_AddRefs(target)); michael@0: if (!target) michael@0: return NS_OK; michael@0: return target->OnStatus(mImageRequest, ctxt, status, statusArg); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsProgressNotificationProxy::AsyncOnChannelRedirect(nsIChannel *oldChannel, michael@0: nsIChannel *newChannel, michael@0: uint32_t flags, michael@0: nsIAsyncVerifyRedirectCallback *cb) michael@0: { michael@0: // Tell the original original callbacks about it too michael@0: nsCOMPtr loadGroup; michael@0: newChannel->GetLoadGroup(getter_AddRefs(loadGroup)); michael@0: nsCOMPtr target; michael@0: NS_QueryNotificationCallbacks(mOriginalCallbacks, michael@0: loadGroup, michael@0: NS_GET_IID(nsIChannelEventSink), michael@0: getter_AddRefs(target)); michael@0: if (!target) { michael@0: cb->OnRedirectVerifyCallback(NS_OK); michael@0: return NS_OK; michael@0: } michael@0: michael@0: // Delegate to |target| if set, reusing |cb| michael@0: return target->AsyncOnChannelRedirect(oldChannel, newChannel, flags, cb); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsProgressNotificationProxy::GetInterface(const nsIID& iid, michael@0: void** result) michael@0: { michael@0: if (iid.Equals(NS_GET_IID(nsIProgressEventSink))) { michael@0: *result = static_cast(this); michael@0: NS_ADDREF_THIS(); michael@0: return NS_OK; michael@0: } michael@0: if (iid.Equals(NS_GET_IID(nsIChannelEventSink))) { michael@0: *result = static_cast(this); michael@0: NS_ADDREF_THIS(); michael@0: return NS_OK; michael@0: } michael@0: if (mOriginalCallbacks) michael@0: return mOriginalCallbacks->GetInterface(iid, result); michael@0: return NS_NOINTERFACE; michael@0: } michael@0: michael@0: static void NewRequestAndEntry(bool aForcePrincipalCheckForCacheEntry, imgLoader* aLoader, michael@0: imgRequest **aRequest, imgCacheEntry **aEntry) michael@0: { michael@0: nsRefPtr request = new imgRequest(aLoader); michael@0: nsRefPtr entry = new imgCacheEntry(aLoader, request, aForcePrincipalCheckForCacheEntry); michael@0: request.forget(aRequest); michael@0: entry.forget(aEntry); michael@0: } michael@0: michael@0: static bool ShouldRevalidateEntry(imgCacheEntry *aEntry, michael@0: nsLoadFlags aFlags, michael@0: bool aHasExpired) michael@0: { michael@0: bool bValidateEntry = false; michael@0: michael@0: if (aFlags & nsIRequest::LOAD_BYPASS_CACHE) michael@0: return false; michael@0: michael@0: if (aFlags & nsIRequest::VALIDATE_ALWAYS) { michael@0: bValidateEntry = true; michael@0: } michael@0: else if (aEntry->GetMustValidate()) { michael@0: bValidateEntry = true; michael@0: } michael@0: // michael@0: // The cache entry has expired... Determine whether the stale cache michael@0: // entry can be used without validation... michael@0: // michael@0: else if (aHasExpired) { michael@0: // michael@0: // VALIDATE_NEVER and VALIDATE_ONCE_PER_SESSION allow stale cache michael@0: // entries to be used unless they have been explicitly marked to michael@0: // indicate that revalidation is necessary. michael@0: // michael@0: if (aFlags & (nsIRequest::VALIDATE_NEVER | michael@0: nsIRequest::VALIDATE_ONCE_PER_SESSION)) michael@0: { michael@0: bValidateEntry = false; michael@0: } michael@0: // michael@0: // LOAD_FROM_CACHE allows a stale cache entry to be used... Otherwise, michael@0: // the entry must be revalidated. michael@0: // michael@0: else if (!(aFlags & nsIRequest::LOAD_FROM_CACHE)) { michael@0: bValidateEntry = true; michael@0: } michael@0: } michael@0: michael@0: return bValidateEntry; michael@0: } michael@0: michael@0: // Returns true if this request is compatible with the given CORS mode on the michael@0: // given loading principal, and false if the request may not be reused due michael@0: // to CORS. michael@0: static bool michael@0: ValidateCORSAndPrincipal(imgRequest* request, bool forcePrincipalCheck, michael@0: int32_t corsmode, nsIPrincipal* loadingPrincipal) michael@0: { michael@0: // If the entry's CORS mode doesn't match, or the CORS mode matches but the michael@0: // document principal isn't the same, we can't use this request. michael@0: if (request->GetCORSMode() != corsmode) { michael@0: return false; michael@0: } else if (request->GetCORSMode() != imgIRequest::CORS_NONE || michael@0: forcePrincipalCheck) { michael@0: nsCOMPtr otherprincipal = request->GetLoadingPrincipal(); michael@0: michael@0: // If we previously had a principal, but we don't now, we can't use this michael@0: // request. michael@0: if (otherprincipal && !loadingPrincipal) { michael@0: return false; michael@0: } michael@0: michael@0: if (otherprincipal && loadingPrincipal) { michael@0: bool equals = false; michael@0: otherprincipal->Equals(loadingPrincipal, &equals); michael@0: return equals; michael@0: } michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: static nsresult NewImageChannel(nsIChannel **aResult, michael@0: // If aForcePrincipalCheckForCacheEntry is michael@0: // true, then we will force a principal check michael@0: // even when not using CORS before assuming we michael@0: // have a cache hit on a cache entry that we michael@0: // create for this channel. This is an out michael@0: // param that should be set to true if this michael@0: // channel ends up depending on michael@0: // aLoadingPrincipal and false otherwise. michael@0: bool *aForcePrincipalCheckForCacheEntry, michael@0: nsIURI *aURI, michael@0: nsIURI *aFirstPartyIsolationURI, michael@0: nsIURI *aReferringURI, michael@0: nsILoadGroup *aLoadGroup, michael@0: const nsCString& aAcceptHeader, michael@0: nsLoadFlags aLoadFlags, michael@0: nsIChannelPolicy *aPolicy, michael@0: nsIPrincipal *aLoadingPrincipal) michael@0: { michael@0: nsresult rv; michael@0: nsCOMPtr newHttpChannel; michael@0: michael@0: nsCOMPtr callbacks; michael@0: michael@0: if (aLoadGroup) { michael@0: // Get the notification callbacks from the load group for the new channel. michael@0: // michael@0: // XXX: This is not exactly correct, because the network request could be michael@0: // referenced by multiple windows... However, the new channel needs michael@0: // something. So, using the 'first' notification callbacks is better michael@0: // than nothing... michael@0: // michael@0: aLoadGroup->GetNotificationCallbacks(getter_AddRefs(callbacks)); michael@0: } michael@0: michael@0: // Pass in a nullptr loadgroup because this is the underlying network michael@0: // request. This request may be referenced by several proxy image requests michael@0: // (possibly in different documents). michael@0: // If all of the proxy requests are canceled then this request should be michael@0: // canceled too. michael@0: // michael@0: rv = NS_NewChannel(aResult, michael@0: aURI, // URI michael@0: nullptr, // Cached IOService michael@0: nullptr, // LoadGroup michael@0: callbacks, // Notification Callbacks michael@0: aLoadFlags, michael@0: aPolicy); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: michael@0: *aForcePrincipalCheckForCacheEntry = false; michael@0: michael@0: // Initialize HTTP-specific attributes michael@0: newHttpChannel = do_QueryInterface(*aResult); michael@0: if (newHttpChannel) { michael@0: newHttpChannel->SetRequestHeader(NS_LITERAL_CSTRING("Accept"), michael@0: aAcceptHeader, michael@0: false); michael@0: michael@0: nsCOMPtr httpChannelInternal = do_QueryInterface(newHttpChannel); michael@0: NS_ENSURE_TRUE(httpChannelInternal, NS_ERROR_UNEXPECTED); michael@0: httpChannelInternal->SetDocumentURI(aFirstPartyIsolationURI); michael@0: newHttpChannel->SetReferrer(aReferringURI); michael@0: } michael@0: michael@0: // Image channels are loaded by default with reduced priority. michael@0: nsCOMPtr p = do_QueryInterface(*aResult); michael@0: if (p) { michael@0: uint32_t priority = nsISupportsPriority::PRIORITY_LOW; michael@0: michael@0: if (aLoadFlags & nsIRequest::LOAD_BACKGROUND) michael@0: ++priority; // further reduce priority for background loads michael@0: michael@0: p->AdjustPriority(priority); michael@0: } michael@0: michael@0: bool setOwner = nsContentUtils::SetUpChannelOwner(aLoadingPrincipal, michael@0: *aResult, aURI, false); michael@0: *aForcePrincipalCheckForCacheEntry = setOwner; michael@0: michael@0: // Create a new loadgroup for this new channel, using the old group as michael@0: // the parent. The indirection keeps the channel insulated from cancels, michael@0: // but does allow a way for this revalidation to be associated with at michael@0: // least one base load group for scheduling/caching purposes. michael@0: michael@0: nsCOMPtr loadGroup = do_CreateInstance(NS_LOADGROUP_CONTRACTID); michael@0: nsCOMPtr childLoadGroup = do_QueryInterface(loadGroup); michael@0: if (childLoadGroup) { michael@0: childLoadGroup->SetParentLoadGroup(aLoadGroup); michael@0: } michael@0: (*aResult)->SetLoadGroup(loadGroup); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: static uint32_t SecondsFromPRTime(PRTime prTime) michael@0: { michael@0: return uint32_t(int64_t(prTime) / int64_t(PR_USEC_PER_SEC)); michael@0: } michael@0: michael@0: imgCacheEntry::imgCacheEntry(imgLoader* loader, imgRequest *request, bool forcePrincipalCheck) michael@0: : mLoader(loader), michael@0: mRequest(request), michael@0: mDataSize(0), michael@0: mTouchedTime(SecondsFromPRTime(PR_Now())), michael@0: mExpiryTime(0), michael@0: mMustValidate(false), michael@0: // We start off as evicted so we don't try to update the cache. PutIntoCache michael@0: // will set this to false. michael@0: mEvicted(true), michael@0: mHasNoProxies(true), michael@0: mForcePrincipalCheck(forcePrincipalCheck) michael@0: {} michael@0: michael@0: imgCacheEntry::~imgCacheEntry() michael@0: { michael@0: LOG_FUNC(GetImgLog(), "imgCacheEntry::~imgCacheEntry()"); michael@0: } michael@0: michael@0: void imgCacheEntry::Touch(bool updateTime /* = true */) michael@0: { michael@0: LOG_SCOPE(GetImgLog(), "imgCacheEntry::Touch"); michael@0: michael@0: if (updateTime) michael@0: mTouchedTime = SecondsFromPRTime(PR_Now()); michael@0: michael@0: UpdateCache(); michael@0: } michael@0: michael@0: void imgCacheEntry::UpdateCache(int32_t diff /* = 0 */) michael@0: { michael@0: // Don't update the cache if we've been removed from it or it doesn't care michael@0: // about our size or usage. michael@0: if (!Evicted() && HasNoProxies()) { michael@0: nsRefPtr uri; michael@0: mRequest->GetURI(getter_AddRefs(uri)); michael@0: mLoader->CacheEntriesChanged(uri, diff); michael@0: } michael@0: } michael@0: michael@0: void imgCacheEntry::SetHasNoProxies(bool hasNoProxies) michael@0: { michael@0: #if defined(PR_LOGGING) michael@0: nsRefPtr uri; michael@0: mRequest->GetURI(getter_AddRefs(uri)); michael@0: nsAutoCString spec; michael@0: if (uri) michael@0: uri->GetSpec(spec); michael@0: if (hasNoProxies) michael@0: LOG_FUNC_WITH_PARAM(GetImgLog(), "imgCacheEntry::SetHasNoProxies true", "uri", spec.get()); michael@0: else michael@0: LOG_FUNC_WITH_PARAM(GetImgLog(), "imgCacheEntry::SetHasNoProxies false", "uri", spec.get()); michael@0: #endif michael@0: michael@0: mHasNoProxies = hasNoProxies; michael@0: } michael@0: michael@0: imgCacheQueue::imgCacheQueue() michael@0: : mDirty(false), michael@0: mSize(0) michael@0: {} michael@0: michael@0: void imgCacheQueue::UpdateSize(int32_t diff) michael@0: { michael@0: mSize += diff; michael@0: } michael@0: michael@0: uint32_t imgCacheQueue::GetSize() const michael@0: { michael@0: return mSize; michael@0: } michael@0: michael@0: #include michael@0: using namespace std; michael@0: michael@0: void imgCacheQueue::Remove(imgCacheEntry *entry) michael@0: { michael@0: queueContainer::iterator it = find(mQueue.begin(), mQueue.end(), entry); michael@0: if (it != mQueue.end()) { michael@0: mSize -= (*it)->GetDataSize(); michael@0: mQueue.erase(it); michael@0: MarkDirty(); michael@0: } michael@0: } michael@0: michael@0: void imgCacheQueue::Push(imgCacheEntry *entry) michael@0: { michael@0: mSize += entry->GetDataSize(); michael@0: michael@0: nsRefPtr refptr(entry); michael@0: mQueue.push_back(refptr); michael@0: MarkDirty(); michael@0: } michael@0: michael@0: already_AddRefed imgCacheQueue::Pop() michael@0: { michael@0: if (mQueue.empty()) michael@0: return nullptr; michael@0: if (IsDirty()) michael@0: Refresh(); michael@0: michael@0: nsRefPtr entry = mQueue[0]; michael@0: std::pop_heap(mQueue.begin(), mQueue.end(), imgLoader::CompareCacheEntries); michael@0: mQueue.pop_back(); michael@0: michael@0: mSize -= entry->GetDataSize(); michael@0: return entry.forget(); michael@0: } michael@0: michael@0: void imgCacheQueue::Refresh() michael@0: { michael@0: std::make_heap(mQueue.begin(), mQueue.end(), imgLoader::CompareCacheEntries); michael@0: mDirty = false; michael@0: } michael@0: michael@0: void imgCacheQueue::MarkDirty() michael@0: { michael@0: mDirty = true; michael@0: } michael@0: michael@0: bool imgCacheQueue::IsDirty() michael@0: { michael@0: return mDirty; michael@0: } michael@0: michael@0: uint32_t imgCacheQueue::GetNumElements() const michael@0: { michael@0: return mQueue.size(); michael@0: } michael@0: michael@0: imgCacheQueue::iterator imgCacheQueue::begin() michael@0: { michael@0: return mQueue.begin(); michael@0: } michael@0: imgCacheQueue::const_iterator imgCacheQueue::begin() const michael@0: { michael@0: return mQueue.begin(); michael@0: } michael@0: michael@0: imgCacheQueue::iterator imgCacheQueue::end() michael@0: { michael@0: return mQueue.end(); michael@0: } michael@0: imgCacheQueue::const_iterator imgCacheQueue::end() const michael@0: { michael@0: return mQueue.end(); michael@0: } michael@0: michael@0: nsresult imgLoader::CreateNewProxyForRequest(imgRequest *aRequest, nsILoadGroup *aLoadGroup, michael@0: imgINotificationObserver *aObserver, michael@0: nsLoadFlags aLoadFlags, imgRequestProxy **_retval) michael@0: { michael@0: LOG_SCOPE_WITH_PARAM(GetImgLog(), "imgLoader::CreateNewProxyForRequest", "imgRequest", aRequest); michael@0: michael@0: /* XXX If we move decoding onto separate threads, we should save off the michael@0: calling thread here and pass it off to |proxyRequest| so that it call michael@0: proxy calls to |aObserver|. michael@0: */ michael@0: michael@0: imgRequestProxy *proxyRequest = new imgRequestProxy(); michael@0: NS_ADDREF(proxyRequest); michael@0: michael@0: /* It is important to call |SetLoadFlags()| before calling |Init()| because michael@0: |Init()| adds the request to the loadgroup. michael@0: */ michael@0: proxyRequest->SetLoadFlags(aLoadFlags); michael@0: michael@0: nsRefPtr uri; michael@0: aRequest->GetURI(getter_AddRefs(uri)); michael@0: michael@0: // init adds itself to imgRequest's list of observers michael@0: nsresult rv = proxyRequest->Init(aRequest, aLoadGroup, uri, aObserver); michael@0: if (NS_FAILED(rv)) { michael@0: NS_RELEASE(proxyRequest); michael@0: return rv; michael@0: } michael@0: michael@0: // transfer reference to caller michael@0: *_retval = proxyRequest; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: class imgCacheObserver MOZ_FINAL : public nsIObserver michael@0: { michael@0: public: michael@0: NS_DECL_ISUPPORTS michael@0: NS_DECL_NSIOBSERVER michael@0: }; michael@0: michael@0: NS_IMPL_ISUPPORTS(imgCacheObserver, nsIObserver) michael@0: michael@0: NS_IMETHODIMP michael@0: imgCacheObserver::Observe(nsISupports* aSubject, const char* aTopic, const char16_t* aSomeData) michael@0: { michael@0: if (strcmp(aTopic, "memory-pressure") == 0) { michael@0: DiscardTracker::DiscardAll(); michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: class imgCacheExpirationTracker MOZ_FINAL michael@0: : public nsExpirationTracker michael@0: { michael@0: enum { TIMEOUT_SECONDS = 10 }; michael@0: public: michael@0: imgCacheExpirationTracker(); michael@0: michael@0: protected: michael@0: void NotifyExpired(imgCacheEntry *entry); michael@0: }; michael@0: michael@0: imgCacheExpirationTracker::imgCacheExpirationTracker() michael@0: : nsExpirationTracker(TIMEOUT_SECONDS * 1000) michael@0: {} michael@0: michael@0: void imgCacheExpirationTracker::NotifyExpired(imgCacheEntry *entry) michael@0: { michael@0: // Hold on to a reference to this entry, because the expiration tracker michael@0: // mechanism doesn't. michael@0: nsRefPtr kungFuDeathGrip(entry); michael@0: michael@0: #if defined(PR_LOGGING) michael@0: nsRefPtr req(entry->GetRequest()); michael@0: if (req) { michael@0: nsRefPtr uri; michael@0: req->GetURI(getter_AddRefs(uri)); michael@0: nsAutoCString spec; michael@0: uri->GetSpec(spec); michael@0: LOG_FUNC_WITH_PARAM(GetImgLog(), "imgCacheExpirationTracker::NotifyExpired", "entry", spec.get()); michael@0: } michael@0: #endif michael@0: michael@0: // We can be called multiple times on the same entry. Don't do work multiple michael@0: // times. michael@0: if (!entry->Evicted()) michael@0: entry->Loader()->RemoveFromCache(entry); michael@0: michael@0: entry->Loader()->VerifyCacheSizes(); michael@0: } michael@0: michael@0: imgCacheObserver *gCacheObserver; michael@0: michael@0: double imgLoader::sCacheTimeWeight; michael@0: uint32_t imgLoader::sCacheMaxSize; michael@0: imgMemoryReporter* imgLoader::sMemReporter; michael@0: michael@0: nsCOMPtr imgLoader::sThirdPartyUtilSvc; michael@0: michael@0: NS_IMPL_ISUPPORTS(imgLoader, imgILoader, nsIContentSniffer, imgICache, nsISupportsWeakReference, nsIObserver) michael@0: michael@0: static imgLoader* gSingleton = nullptr; michael@0: static imgLoader* gPBSingleton = nullptr; michael@0: michael@0: imgLoader* michael@0: imgLoader::Singleton() michael@0: { michael@0: if (!gSingleton) michael@0: gSingleton = imgLoader::Create(); michael@0: return gSingleton; michael@0: } michael@0: michael@0: imgLoader* michael@0: imgLoader::PBSingleton() michael@0: { michael@0: if (!gPBSingleton) { michael@0: gPBSingleton = imgLoader::Create(); michael@0: gPBSingleton->RespectPrivacyNotifications(); michael@0: } michael@0: return gPBSingleton; michael@0: } michael@0: michael@0: imgLoader::imgLoader() michael@0: : mRespectPrivacy(false) michael@0: { michael@0: sMemReporter->AddRef(); michael@0: sMemReporter->RegisterLoader(this); michael@0: } michael@0: michael@0: already_AddRefed michael@0: imgLoader::GetInstance() michael@0: { michael@0: static StaticRefPtr singleton; michael@0: if (!singleton) { michael@0: singleton = imgLoader::Create(); michael@0: if (!singleton) michael@0: return nullptr; michael@0: ClearOnShutdown(&singleton); michael@0: } michael@0: nsRefPtr loader = singleton.get(); michael@0: return loader.forget(); michael@0: } michael@0: michael@0: imgLoader::~imgLoader() michael@0: { michael@0: ClearChromeImageCache(); michael@0: ClearImageCache(); michael@0: sMemReporter->UnregisterLoader(this); michael@0: sMemReporter->Release(); michael@0: } michael@0: michael@0: void imgLoader::VerifyCacheSizes() michael@0: { michael@0: #ifdef DEBUG michael@0: if (!mCacheTracker) michael@0: return; michael@0: michael@0: uint32_t cachesize = mCache.Count() + mChromeCache.Count(); michael@0: uint32_t queuesize = mCacheQueue.GetNumElements() + mChromeCacheQueue.GetNumElements(); michael@0: uint32_t trackersize = 0; michael@0: for (nsExpirationTracker::Iterator it(mCacheTracker); it.Next(); ) michael@0: trackersize++; michael@0: NS_ABORT_IF_FALSE(queuesize == trackersize, "Queue and tracker sizes out of sync!"); michael@0: NS_ABORT_IF_FALSE(queuesize <= cachesize, "Queue has more elements than cache!"); michael@0: #endif michael@0: } michael@0: michael@0: imgLoader::imgCacheTable & imgLoader::GetCache(nsIURI *aURI) michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread(), "Cannot use nsIURI off main thread!"); michael@0: bool chrome = false; michael@0: aURI->SchemeIs("chrome", &chrome); michael@0: return chrome ? mChromeCache : mCache; michael@0: } michael@0: michael@0: imgCacheQueue & imgLoader::GetCacheQueue(nsIURI *aURI) michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread(), "Cannot use nsIURI off main thread!"); michael@0: bool chrome = false; michael@0: aURI->SchemeIs("chrome", &chrome); michael@0: return chrome ? mChromeCacheQueue : mCacheQueue; michael@0: michael@0: } michael@0: michael@0: imgLoader::imgCacheTable & imgLoader::GetCache(ImageURL *aURI) michael@0: { michael@0: bool chrome = false; michael@0: aURI->SchemeIs("chrome", &chrome); michael@0: return chrome ? mChromeCache : mCache; michael@0: } michael@0: michael@0: imgCacheQueue & imgLoader::GetCacheQueue(ImageURL *aURI) michael@0: { michael@0: bool chrome = false; michael@0: aURI->SchemeIs("chrome", &chrome); michael@0: return chrome ? mChromeCacheQueue : mCacheQueue; michael@0: } michael@0: michael@0: void imgLoader::GlobalInit() michael@0: { michael@0: gCacheObserver = new imgCacheObserver(); michael@0: NS_ADDREF(gCacheObserver); michael@0: michael@0: nsCOMPtr os = mozilla::services::GetObserverService(); michael@0: if (os) michael@0: os->AddObserver(gCacheObserver, "memory-pressure", false); michael@0: michael@0: int32_t timeweight; michael@0: nsresult rv = Preferences::GetInt("image.cache.timeweight", &timeweight); michael@0: if (NS_SUCCEEDED(rv)) michael@0: sCacheTimeWeight = timeweight / 1000.0; michael@0: else michael@0: sCacheTimeWeight = 0.5; michael@0: michael@0: int32_t cachesize; michael@0: rv = Preferences::GetInt("image.cache.size", &cachesize); michael@0: if (NS_SUCCEEDED(rv)) michael@0: sCacheMaxSize = cachesize; michael@0: else michael@0: sCacheMaxSize = 5 * 1024 * 1024; michael@0: michael@0: sMemReporter = new imgMemoryReporter(); michael@0: RegisterStrongMemoryReporter(sMemReporter); michael@0: RegisterImagesContentUsedUncompressedDistinguishedAmount(imgMemoryReporter::ImagesContentUsedUncompressedDistinguishedAmount); michael@0: michael@0: sThirdPartyUtilSvc = do_GetService(THIRDPARTYUTIL_CONTRACTID); michael@0: } michael@0: michael@0: nsresult imgLoader::InitCache() michael@0: { michael@0: nsCOMPtr os = mozilla::services::GetObserverService(); michael@0: if (!os) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: os->AddObserver(this, "memory-pressure", false); michael@0: os->AddObserver(this, "chrome-flush-skin-caches", false); michael@0: os->AddObserver(this, "chrome-flush-caches", false); michael@0: os->AddObserver(this, "last-pb-context-exited", false); michael@0: os->AddObserver(this, "profile-before-change", false); michael@0: os->AddObserver(this, "xpcom-shutdown", false); michael@0: michael@0: mCacheTracker = new imgCacheExpirationTracker(); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult imgLoader::Init() michael@0: { michael@0: InitCache(); michael@0: michael@0: ReadAcceptHeaderPref(); michael@0: michael@0: Preferences::AddWeakObserver(this, "image.http.accept"); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: imgLoader::RespectPrivacyNotifications() michael@0: { michael@0: mRespectPrivacy = true; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: imgLoader::Observe(nsISupports* aSubject, const char* aTopic, const char16_t* aData) michael@0: { michael@0: // We listen for pref change notifications... michael@0: if (!strcmp(aTopic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID)) { michael@0: if (!strcmp(NS_ConvertUTF16toUTF8(aData).get(), "image.http.accept")) { michael@0: ReadAcceptHeaderPref(); michael@0: } michael@0: michael@0: } else if (strcmp(aTopic, "memory-pressure") == 0) { michael@0: MinimizeCaches(); michael@0: } else if (strcmp(aTopic, "chrome-flush-skin-caches") == 0 || michael@0: strcmp(aTopic, "chrome-flush-caches") == 0) { michael@0: MinimizeCaches(); michael@0: ClearChromeImageCache(); michael@0: } else if (strcmp(aTopic, "last-pb-context-exited") == 0) { michael@0: if (mRespectPrivacy) { michael@0: ClearImageCache(); michael@0: ClearChromeImageCache(); michael@0: } michael@0: } else if (strcmp(aTopic, "profile-before-change") == 0 || michael@0: strcmp(aTopic, "xpcom-shutdown") == 0) { michael@0: mCacheTracker = nullptr; michael@0: } michael@0: michael@0: // (Nothing else should bring us here) michael@0: else { michael@0: NS_ABORT_IF_FALSE(0, "Invalid topic received"); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: void imgLoader::ReadAcceptHeaderPref() michael@0: { michael@0: nsAdoptingCString accept = Preferences::GetCString("image.http.accept"); michael@0: if (accept) michael@0: mAcceptHeader = accept; michael@0: else michael@0: mAcceptHeader = IMAGE_PNG "," IMAGE_WILDCARD ";q=0.8," ANY_WILDCARD ";q=0.5"; michael@0: } michael@0: michael@0: /* void clearCache (in boolean chrome); */ michael@0: NS_IMETHODIMP imgLoader::ClearCache(bool chrome) michael@0: { michael@0: if (chrome) michael@0: return ClearChromeImageCache(); michael@0: else michael@0: return ClearImageCache(); michael@0: } michael@0: michael@0: /* void removeEntry(in nsIURI uri); */ michael@0: NS_IMETHODIMP imgLoader::RemoveEntry(nsIURI *uri) michael@0: { michael@0: if (RemoveMatchingUrlsFromCache(uri)) michael@0: return NS_OK; michael@0: michael@0: return NS_ERROR_NOT_AVAILABLE; michael@0: } michael@0: michael@0: static PLDHashOperator EnumAllEntries(const nsACString&, michael@0: nsRefPtr &aData, michael@0: void *data) michael@0: { michael@0: nsTArray > *entries = michael@0: reinterpret_cast > *>(data); michael@0: michael@0: entries->AppendElement(aData); michael@0: michael@0: return PL_DHASH_NEXT; michael@0: } michael@0: michael@0: /* imgIRequest findEntry(in nsIURI uri); */ michael@0: NS_IMETHODIMP imgLoader::FindEntryProperties(nsIURI *uri, nsIProperties **_retval) michael@0: { michael@0: nsRefPtr entry; michael@0: imgCacheTable &cache = GetCache(uri); michael@0: *_retval = nullptr; michael@0: michael@0: // We must traverse the whole cache in O(N) looking for the first michael@0: // matching URI. michael@0: // michael@0: // TODO: For now, it's ok to pick at random here. The images should be michael@0: // identical unless there is a cache-tracking attack. And even if they michael@0: // are not identical due to attack, this code is only used for save michael@0: // dialogs at this point, so no differentiating info is leaked to michael@0: // content. michael@0: nsTArray > entries; michael@0: cache.Enumerate(EnumAllEntries, &entries); michael@0: michael@0: for (uint32_t i = 0; i < entries.Length(); ++i) { michael@0: bool isEqual = false; michael@0: michael@0: nsRefPtr request = entries[i]->GetRequest(); michael@0: if (request) { michael@0: request->mURI->Equals(uri, &isEqual); michael@0: if (isEqual) { michael@0: if (mCacheTracker && entries[i]->HasNoProxies()) { michael@0: mCacheTracker->MarkUsed(entries[i]); michael@0: } michael@0: *_retval = request->Properties(); michael@0: NS_ADDREF(*_retval); michael@0: break; michael@0: } michael@0: } michael@0: } michael@0: if (*_retval) { michael@0: return NS_OK; michael@0: } michael@0: return NS_ERROR_NOT_AVAILABLE; michael@0: } michael@0: michael@0: void imgLoader::Shutdown() michael@0: { michael@0: NS_IF_RELEASE(gSingleton); michael@0: NS_IF_RELEASE(gPBSingleton); michael@0: NS_RELEASE(gCacheObserver); michael@0: sThirdPartyUtilSvc = nullptr; michael@0: } michael@0: michael@0: nsresult imgLoader::ClearChromeImageCache() michael@0: { michael@0: return EvictEntries(mChromeCache); michael@0: } michael@0: michael@0: nsresult imgLoader::ClearImageCache() michael@0: { michael@0: return EvictEntries(mCache); michael@0: } michael@0: michael@0: void imgLoader::MinimizeCaches() michael@0: { michael@0: EvictEntries(mCacheQueue); michael@0: EvictEntries(mChromeCacheQueue); michael@0: } michael@0: michael@0: bool imgLoader::PutIntoCache(nsAutoCString key, imgCacheEntry *entry) michael@0: { michael@0: LOG_STATIC_FUNC_WITH_PARAM(GetImgLog(), "imgLoader::PutIntoCache", "uri", key.get()); michael@0: imgCacheTable &cache = GetCache(entry->mRequest->mURI); michael@0: imgCacheQueue &queue = GetCacheQueue(entry->mRequest->mURI); michael@0: michael@0: // Check to see if this request already exists in the cache and is being michael@0: // loaded on a different thread. If so, don't allow this entry to be added to michael@0: // the cache. michael@0: nsRefPtr tmpCacheEntry; michael@0: if (cache.Get(key, getter_AddRefs(tmpCacheEntry)) && tmpCacheEntry) { michael@0: PR_LOG(GetImgLog(), PR_LOG_DEBUG, michael@0: ("[this=%p] imgLoader::PutIntoCache -- Element already in the cache", nullptr)); michael@0: nsRefPtr tmpRequest = tmpCacheEntry->GetRequest(); michael@0: michael@0: // If it already exists, and we're putting the same key into the cache, we michael@0: // should remove the old version. michael@0: PR_LOG(GetImgLog(), PR_LOG_DEBUG, michael@0: ("[this=%p] imgLoader::PutIntoCache -- Replacing cached element", nullptr)); michael@0: michael@0: RemoveFromCache(key, cache, queue); michael@0: } else { michael@0: PR_LOG(GetImgLog(), PR_LOG_DEBUG, michael@0: ("[this=%p] imgLoader::PutIntoCache -- Element NOT already in the cache", nullptr)); michael@0: } michael@0: michael@0: cache.Put(key, entry); michael@0: michael@0: // We can be called to resurrect an evicted entry. michael@0: if (entry->Evicted()) michael@0: entry->SetEvicted(false); michael@0: michael@0: // If we're resurrecting an entry with no proxies, put it back in the michael@0: // tracker and queue. michael@0: if (entry->HasNoProxies()) { michael@0: nsresult addrv = NS_OK; michael@0: michael@0: if (mCacheTracker) michael@0: addrv = mCacheTracker->AddObject(entry); michael@0: michael@0: if (NS_SUCCEEDED(addrv)) { michael@0: queue.Push(entry); michael@0: } michael@0: } michael@0: michael@0: nsRefPtr request = entry->GetRequest(); michael@0: request->SetIsInCache(true); michael@0: michael@0: return true; michael@0: } michael@0: michael@0: bool imgLoader::SetHasNoProxies(ImageURL *imgURI, imgCacheEntry *entry) michael@0: { michael@0: #if defined(PR_LOGGING) michael@0: nsAutoCString spec; michael@0: imgURI->GetSpec(spec); michael@0: michael@0: LOG_STATIC_FUNC_WITH_PARAM(GetImgLog(), "imgLoader::SetHasNoProxies", "uri", spec.get()); michael@0: #endif michael@0: michael@0: if (entry->Evicted()) michael@0: return false; michael@0: michael@0: imgCacheQueue &queue = GetCacheQueue(imgURI); michael@0: michael@0: nsresult addrv = NS_OK; michael@0: michael@0: if (mCacheTracker) michael@0: addrv = mCacheTracker->AddObject(entry); michael@0: michael@0: if (NS_SUCCEEDED(addrv)) { michael@0: queue.Push(entry); michael@0: entry->SetHasNoProxies(true); michael@0: } michael@0: michael@0: imgCacheTable &cache = GetCache(imgURI); michael@0: CheckCacheLimits(cache, queue); michael@0: michael@0: return true; michael@0: } michael@0: michael@0: bool imgLoader::SetHasProxies(nsIURI *firstPartyIsolationURI, ImageURL *imgURI) michael@0: { michael@0: VerifyCacheSizes(); michael@0: michael@0: imgCacheTable &cache = GetCache(imgURI); michael@0: michael@0: nsAutoCString spec; michael@0: imgURI->GetSpec(spec); michael@0: michael@0: LOG_STATIC_FUNC_WITH_PARAM(GetImgLog(), "imgLoader::SetHasProxies", "uri", spec.get()); michael@0: michael@0: nsAutoCString key = GetCacheKey(firstPartyIsolationURI, imgURI, nullptr); michael@0: nsRefPtr entry; michael@0: if (cache.Get(key, getter_AddRefs(entry)) && entry && entry->HasNoProxies()) { michael@0: imgCacheQueue &queue = GetCacheQueue(imgURI); michael@0: queue.Remove(entry); michael@0: michael@0: if (mCacheTracker) michael@0: mCacheTracker->RemoveObject(entry); michael@0: michael@0: entry->SetHasNoProxies(false); michael@0: michael@0: return true; michael@0: } michael@0: michael@0: return false; michael@0: } michael@0: michael@0: void imgLoader::CacheEntriesChanged(ImageURL *uri, int32_t sizediff /* = 0 */) michael@0: { michael@0: imgCacheQueue &queue = GetCacheQueue(uri); michael@0: queue.MarkDirty(); michael@0: queue.UpdateSize(sizediff); michael@0: } michael@0: michael@0: void imgLoader::CheckCacheLimits(imgCacheTable &cache, imgCacheQueue &queue) michael@0: { michael@0: if (queue.GetNumElements() == 0) michael@0: NS_ASSERTION(queue.GetSize() == 0, michael@0: "imgLoader::CheckCacheLimits -- incorrect cache size"); michael@0: michael@0: // Remove entries from the cache until we're back under our desired size. michael@0: while (queue.GetSize() >= sCacheMaxSize) { michael@0: // Remove the first entry in the queue. michael@0: nsRefPtr entry(queue.Pop()); michael@0: michael@0: NS_ASSERTION(entry, "imgLoader::CheckCacheLimits -- NULL entry pointer"); michael@0: michael@0: #if defined(PR_LOGGING) michael@0: nsRefPtr req(entry->GetRequest()); michael@0: if (req) { michael@0: nsRefPtr uri; michael@0: req->GetURI(getter_AddRefs(uri)); michael@0: nsAutoCString spec; michael@0: uri->GetSpec(spec); michael@0: LOG_STATIC_FUNC_WITH_PARAM(GetImgLog(), "imgLoader::CheckCacheLimits", "entry", spec.get()); michael@0: } michael@0: #endif michael@0: michael@0: if (entry) michael@0: RemoveFromCache(entry); michael@0: } michael@0: } michael@0: michael@0: bool imgLoader::ValidateRequestWithNewChannel(imgRequest *request, michael@0: nsIURI *aURI, michael@0: nsIURI *aFirstPartyIsolationURI, michael@0: nsIURI *aReferrerURI, michael@0: nsILoadGroup *aLoadGroup, michael@0: imgINotificationObserver *aObserver, michael@0: nsISupports *aCX, michael@0: nsLoadFlags aLoadFlags, michael@0: imgRequestProxy **aProxyRequest, michael@0: nsIChannelPolicy *aPolicy, michael@0: nsIPrincipal* aLoadingPrincipal, michael@0: int32_t aCORSMode) michael@0: { michael@0: // now we need to insert a new channel request object inbetween the real michael@0: // request and the proxy that basically delays loading the image until it michael@0: // gets a 304 or figures out that this needs to be a new request michael@0: michael@0: nsresult rv; michael@0: michael@0: // If we're currently in the middle of validating this request, just hand michael@0: // back a proxy to it; the required work will be done for us. michael@0: if (request->mValidator) { michael@0: rv = CreateNewProxyForRequest(request, aLoadGroup, aObserver, michael@0: aLoadFlags, aProxyRequest); michael@0: if (NS_FAILED(rv)) { michael@0: return false; michael@0: } michael@0: michael@0: if (*aProxyRequest) { michael@0: imgRequestProxy* proxy = static_cast(*aProxyRequest); michael@0: michael@0: // We will send notifications from imgCacheValidator::OnStartRequest(). michael@0: // In the mean time, we must defer notifications because we are added to michael@0: // the imgRequest's proxy list, and we can get extra notifications michael@0: // resulting from methods such as RequestDecode(). See bug 579122. michael@0: proxy->SetNotificationsDeferred(true); michael@0: michael@0: // Attach the proxy without notifying michael@0: request->mValidator->AddProxy(proxy); michael@0: } michael@0: michael@0: return NS_SUCCEEDED(rv); michael@0: michael@0: } else { michael@0: // We will rely on Necko to cache this request when it's possible, and to michael@0: // tell imgCacheValidator::OnStartRequest whether the request came from its michael@0: // cache. michael@0: nsCOMPtr newChannel; michael@0: bool forcePrincipalCheck; michael@0: rv = NewImageChannel(getter_AddRefs(newChannel), michael@0: &forcePrincipalCheck, michael@0: aURI, michael@0: aFirstPartyIsolationURI, michael@0: aReferrerURI, michael@0: aLoadGroup, michael@0: mAcceptHeader, michael@0: aLoadFlags, michael@0: aPolicy, michael@0: aLoadingPrincipal); michael@0: if (NS_FAILED(rv)) { michael@0: return false; michael@0: } michael@0: michael@0: nsRefPtr req; michael@0: rv = CreateNewProxyForRequest(request, aLoadGroup, aObserver, michael@0: aLoadFlags, getter_AddRefs(req)); michael@0: if (NS_FAILED(rv)) { michael@0: return false; michael@0: } michael@0: michael@0: // Make sure that OnStatus/OnProgress calls have the right request set... michael@0: nsRefPtr progressproxy = michael@0: new nsProgressNotificationProxy(newChannel, req); michael@0: if (!progressproxy) michael@0: return false; michael@0: michael@0: nsRefPtr hvc = michael@0: new imgCacheValidator(progressproxy, this, request, aCX, forcePrincipalCheck); michael@0: michael@0: // Casting needed here to get past multiple inheritance. michael@0: nsCOMPtr listener = michael@0: do_QueryInterface(static_cast(hvc)); michael@0: NS_ENSURE_TRUE(listener, false); michael@0: michael@0: // We must set the notification callbacks before setting up the michael@0: // CORS listener, because that's also interested inthe michael@0: // notification callbacks. michael@0: newChannel->SetNotificationCallbacks(hvc); michael@0: michael@0: if (aCORSMode != imgIRequest::CORS_NONE) { michael@0: bool withCredentials = aCORSMode == imgIRequest::CORS_USE_CREDENTIALS; michael@0: nsRefPtr corsproxy = michael@0: new nsCORSListenerProxy(listener, aLoadingPrincipal, withCredentials); michael@0: rv = corsproxy->Init(newChannel); michael@0: if (NS_FAILED(rv)) { michael@0: return false; michael@0: } michael@0: michael@0: listener = corsproxy; michael@0: } michael@0: michael@0: request->mValidator = hvc; michael@0: michael@0: imgRequestProxy* proxy = static_cast michael@0: (static_cast(req.get())); michael@0: michael@0: // We will send notifications from imgCacheValidator::OnStartRequest(). michael@0: // In the mean time, we must defer notifications because we are added to michael@0: // the imgRequest's proxy list, and we can get extra notifications michael@0: // resulting from methods such as RequestDecode(). See bug 579122. michael@0: proxy->SetNotificationsDeferred(true); michael@0: michael@0: // Add the proxy without notifying michael@0: hvc->AddProxy(proxy); michael@0: michael@0: mozilla::net::SeerLearn(aURI, aFirstPartyIsolationURI, michael@0: nsINetworkSeer::LEARN_LOAD_SUBRESOURCE, aLoadGroup); michael@0: michael@0: rv = newChannel->AsyncOpen(listener, nullptr); michael@0: if (NS_SUCCEEDED(rv)) michael@0: NS_ADDREF(*aProxyRequest = req.get()); michael@0: michael@0: return NS_SUCCEEDED(rv); michael@0: } michael@0: } michael@0: michael@0: bool imgLoader::ValidateEntry(imgCacheEntry *aEntry, michael@0: nsIURI *aURI, michael@0: nsIURI *aFirstPartyIsolationURI, michael@0: nsIURI *aReferrerURI, michael@0: nsILoadGroup *aLoadGroup, michael@0: imgINotificationObserver *aObserver, michael@0: nsISupports *aCX, michael@0: nsLoadFlags aLoadFlags, michael@0: bool aCanMakeNewChannel, michael@0: imgRequestProxy **aProxyRequest, michael@0: nsIChannelPolicy *aPolicy, michael@0: nsIPrincipal* aLoadingPrincipal, michael@0: int32_t aCORSMode) michael@0: { michael@0: LOG_SCOPE(GetImgLog(), "imgLoader::ValidateEntry"); michael@0: michael@0: bool hasExpired; michael@0: uint32_t expirationTime = aEntry->GetExpiryTime(); michael@0: if (expirationTime <= SecondsFromPRTime(PR_Now())) { michael@0: hasExpired = true; michael@0: } else { michael@0: hasExpired = false; michael@0: } michael@0: michael@0: nsresult rv; michael@0: michael@0: // Special treatment for file URLs - aEntry has expired if file has changed michael@0: nsCOMPtr fileUrl(do_QueryInterface(aURI)); michael@0: if (fileUrl) { michael@0: uint32_t lastModTime = aEntry->GetTouchedTime(); michael@0: michael@0: nsCOMPtr theFile; michael@0: rv = fileUrl->GetFile(getter_AddRefs(theFile)); michael@0: if (NS_SUCCEEDED(rv)) { michael@0: PRTime fileLastMod; michael@0: rv = theFile->GetLastModifiedTime(&fileLastMod); michael@0: if (NS_SUCCEEDED(rv)) { michael@0: // nsIFile uses millisec, NSPR usec michael@0: fileLastMod *= 1000; michael@0: hasExpired = SecondsFromPRTime((PRTime)fileLastMod) > lastModTime; michael@0: } michael@0: } michael@0: } michael@0: michael@0: nsRefPtr request(aEntry->GetRequest()); michael@0: michael@0: if (!request) michael@0: return false; michael@0: michael@0: if (!ValidateCORSAndPrincipal(request, aEntry->ForcePrincipalCheck(), michael@0: aCORSMode, aLoadingPrincipal)) michael@0: return false; michael@0: michael@0: // Never validate data URIs. michael@0: nsAutoCString scheme; michael@0: aURI->GetScheme(scheme); michael@0: if (scheme.EqualsLiteral("data")) michael@0: return true; michael@0: michael@0: bool validateRequest = false; michael@0: michael@0: // If the request's loadId is the same as the aCX, then it is ok to use michael@0: // this one because it has already been validated for this context. michael@0: // michael@0: // XXX: nullptr seems to be a 'special' key value that indicates that NO michael@0: // validation is required. michael@0: // michael@0: void *key = (void *)aCX; michael@0: if (request->mLoadId != key) { michael@0: // If we would need to revalidate this entry, but we're being told to michael@0: // bypass the cache, we don't allow this entry to be used. michael@0: if (aLoadFlags & nsIRequest::LOAD_BYPASS_CACHE) michael@0: return false; michael@0: michael@0: // Determine whether the cache aEntry must be revalidated... michael@0: validateRequest = ShouldRevalidateEntry(aEntry, aLoadFlags, hasExpired); michael@0: michael@0: PR_LOG(GetImgLog(), PR_LOG_DEBUG, michael@0: ("imgLoader::ValidateEntry validating cache entry. " michael@0: "validateRequest = %d", validateRequest)); michael@0: } michael@0: #if defined(PR_LOGGING) michael@0: else if (!key) { michael@0: nsAutoCString spec; michael@0: aURI->GetSpec(spec); michael@0: michael@0: PR_LOG(GetImgLog(), PR_LOG_DEBUG, michael@0: ("imgLoader::ValidateEntry BYPASSING cache validation for %s " michael@0: "because of NULL LoadID", spec.get())); michael@0: } michael@0: #endif michael@0: michael@0: // We can't use a cached request if it comes from a different michael@0: // application cache than this load is expecting. michael@0: nsCOMPtr appCacheContainer; michael@0: nsCOMPtr requestAppCache; michael@0: nsCOMPtr groupAppCache; michael@0: if ((appCacheContainer = do_GetInterface(request->mRequest))) michael@0: appCacheContainer->GetApplicationCache(getter_AddRefs(requestAppCache)); michael@0: if ((appCacheContainer = do_QueryInterface(aLoadGroup))) michael@0: appCacheContainer->GetApplicationCache(getter_AddRefs(groupAppCache)); michael@0: michael@0: if (requestAppCache != groupAppCache) { michael@0: PR_LOG(GetImgLog(), PR_LOG_DEBUG, michael@0: ("imgLoader::ValidateEntry - Unable to use cached imgRequest " michael@0: "[request=%p] because of mismatched application caches\n", michael@0: address_of(request))); michael@0: return false; michael@0: } michael@0: michael@0: if (validateRequest && aCanMakeNewChannel) { michael@0: LOG_SCOPE(GetImgLog(), "imgLoader::ValidateRequest |cache hit| must validate"); michael@0: michael@0: return ValidateRequestWithNewChannel(request, aURI, aFirstPartyIsolationURI, michael@0: aReferrerURI, aLoadGroup, aObserver, michael@0: aCX, aLoadFlags, aProxyRequest, aPolicy, michael@0: aLoadingPrincipal, aCORSMode); michael@0: } michael@0: michael@0: return !validateRequest; michael@0: } michael@0: michael@0: bool imgLoader::RemoveMatchingUrlsFromCache(nsIURI *aImgURI) michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread(), "Cannot use nsIURI off main thread!"); michael@0: michael@0: if (!aImgURI) return false; michael@0: michael@0: bool rv = true; michael@0: imgCacheTable &cache = GetCache(aImgURI); michael@0: michael@0: // We have to make a temporary, since RemoveFromCache removes the element michael@0: // from the queue, invalidating iterators. michael@0: nsTArray > entries; michael@0: cache.Enumerate(EnumAllEntries, &entries); michael@0: for (uint32_t i = 0; i < entries.Length(); ++i) { michael@0: bool isEqual = false; michael@0: michael@0: entries[i]->mRequest->mURI->Equals(aImgURI, &isEqual); michael@0: if (isEqual && !RemoveFromCache(entries[i])) michael@0: rv = false; michael@0: } michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: bool imgLoader::RemoveFromCache(nsAutoCString key, michael@0: imgCacheTable &cache, michael@0: imgCacheQueue &queue) michael@0: { michael@0: if (key.IsEmpty()) return false; michael@0: michael@0: nsRefPtr entry; michael@0: if (cache.Get(key, getter_AddRefs(entry)) && entry) { michael@0: cache.Remove(key); michael@0: michael@0: NS_ABORT_IF_FALSE(!entry->Evicted(), "Evicting an already-evicted cache entry!"); michael@0: michael@0: // Entries with no proxies are in the tracker. michael@0: if (entry->HasNoProxies()) { michael@0: if (mCacheTracker) michael@0: mCacheTracker->RemoveObject(entry); michael@0: queue.Remove(entry); michael@0: } michael@0: michael@0: entry->SetEvicted(true); michael@0: michael@0: nsRefPtr request = entry->GetRequest(); michael@0: request->SetIsInCache(false); michael@0: michael@0: return true; michael@0: } michael@0: else michael@0: return false; michael@0: } michael@0: michael@0: bool imgLoader::RemoveFromCache(imgCacheEntry *entry) michael@0: { michael@0: LOG_STATIC_FUNC(GetImgLog(), "imgLoader::RemoveFromCache entry"); michael@0: michael@0: nsRefPtr request = entry->GetRequest(); michael@0: if (request) { michael@0: nsRefPtr imgURI; michael@0: if (NS_SUCCEEDED(request->GetURI(getter_AddRefs(imgURI))) && imgURI) { michael@0: nsCOMPtr firstPartyIsolationURI = request->mFirstPartyIsolationURI; michael@0: imgCacheTable &cache = GetCache(imgURI); michael@0: imgCacheQueue &queue = GetCacheQueue(imgURI); michael@0: nsAutoCString spec = GetCacheKey(firstPartyIsolationURI, imgURI, nullptr); michael@0: michael@0: LOG_STATIC_FUNC_WITH_PARAM(GetImgLog(), "imgLoader::RemoveFromCache", "entry's uri", spec.get()); michael@0: michael@0: cache.Remove(spec); michael@0: michael@0: if (entry->HasNoProxies()) { michael@0: LOG_STATIC_FUNC(GetImgLog(), "imgLoader::RemoveFromCache removing from tracker"); michael@0: if (mCacheTracker) michael@0: mCacheTracker->RemoveObject(entry); michael@0: queue.Remove(entry); michael@0: } michael@0: michael@0: entry->SetEvicted(true); michael@0: request->SetIsInCache(false); michael@0: michael@0: return true; michael@0: } michael@0: } michael@0: michael@0: return false; michael@0: } michael@0: michael@0: nsresult imgLoader::EvictEntries(imgCacheTable &aCacheToClear) michael@0: { michael@0: nsresult rv = NS_OK; michael@0: LOG_STATIC_FUNC(GetImgLog(), "imgLoader::EvictEntries table"); michael@0: michael@0: // We have to make a temporary, since RemoveFromCache removes the element michael@0: // from the queue, invalidating iterators. michael@0: nsTArray > entries; michael@0: aCacheToClear.Enumerate(EnumAllEntries, &entries); michael@0: michael@0: for (uint32_t i = 0; i < entries.Length(); ++i) michael@0: if (!RemoveFromCache(entries[i])) michael@0: rv = NS_ERROR_FAILURE; michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: nsresult imgLoader::EvictEntries(imgCacheQueue &aQueueToClear) michael@0: { michael@0: LOG_STATIC_FUNC(GetImgLog(), "imgLoader::EvictEntries queue"); michael@0: michael@0: // We have to make a temporary, since RemoveFromCache removes the element michael@0: // from the queue, invalidating iterators. michael@0: nsTArray > entries(aQueueToClear.GetNumElements()); michael@0: for (imgCacheQueue::const_iterator i = aQueueToClear.begin(); i != aQueueToClear.end(); ++i) michael@0: entries.AppendElement(*i); michael@0: michael@0: for (uint32_t i = 0; i < entries.Length(); ++i) michael@0: if (!RemoveFromCache(entries[i])) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: #define LOAD_FLAGS_CACHE_MASK (nsIRequest::LOAD_BYPASS_CACHE | \ michael@0: nsIRequest::LOAD_FROM_CACHE) michael@0: michael@0: #define LOAD_FLAGS_VALIDATE_MASK (nsIRequest::VALIDATE_ALWAYS | \ michael@0: nsIRequest::VALIDATE_NEVER | \ michael@0: nsIRequest::VALIDATE_ONCE_PER_SESSION) michael@0: michael@0: NS_IMETHODIMP imgLoader::LoadImageXPCOM(nsIURI *aURI, michael@0: nsIURI *aInitialDocumentURI, michael@0: nsIURI *aReferrerURI, michael@0: nsIPrincipal* aLoadingPrincipal, michael@0: nsILoadGroup *aLoadGroup, michael@0: imgINotificationObserver *aObserver, michael@0: nsISupports *aCX, michael@0: nsLoadFlags aLoadFlags, michael@0: nsISupports *aCacheKey, michael@0: nsIChannelPolicy *aPolicy, michael@0: imgIRequest **_retval) michael@0: { michael@0: imgRequestProxy *proxy; michael@0: nsresult result = LoadImage(aURI, michael@0: aInitialDocumentURI, michael@0: aReferrerURI, michael@0: aLoadingPrincipal, michael@0: aLoadGroup, michael@0: aObserver, michael@0: aCX, michael@0: aLoadFlags, michael@0: aCacheKey, michael@0: aPolicy, michael@0: EmptyString(), michael@0: &proxy); michael@0: *_retval = proxy; michael@0: return result; michael@0: } michael@0: michael@0: michael@0: /* imgIRequest loadImage (in nsIURI aURI, in nsIURI aUrlBarURI, in nsIPrincipal loadingPrincipal, in nsILoadGroup aLoadGroup, in imgIDecoderObserver aObserver, in nsISupports aCX, in nsLoadFlags aLoadFlags, in nsISupports cacheKey, in imgIRequest aRequest); */ michael@0: michael@0: nsresult imgLoader::LoadImage(nsIURI *aURI, michael@0: nsIURI *aFirstPartyIsolationURI, michael@0: nsIURI *aReferrerURI, michael@0: nsIPrincipal* aLoadingPrincipal, michael@0: nsILoadGroup *aLoadGroup, michael@0: imgINotificationObserver *aObserver, michael@0: nsISupports *aCX, michael@0: nsLoadFlags aLoadFlags, michael@0: nsISupports *aCacheKey, michael@0: nsIChannelPolicy *aPolicy, michael@0: const nsAString& initiatorType, michael@0: imgRequestProxy **_retval) michael@0: { michael@0: VerifyCacheSizes(); michael@0: michael@0: NS_ASSERTION(aURI, "imgLoader::LoadImage -- NULL URI pointer"); michael@0: michael@0: if (!aURI) michael@0: return NS_ERROR_NULL_POINTER; michael@0: michael@0: bool isIsolated = false; michael@0: nsAutoCString spec = GetCacheKey(aFirstPartyIsolationURI, aURI, &isIsolated); michael@0: michael@0: LOG_SCOPE_WITH_PARAM(GetImgLog(), "imgLoader::LoadImage", "aURI", spec.get()); michael@0: michael@0: *_retval = nullptr; michael@0: michael@0: nsRefPtr request; michael@0: michael@0: nsresult rv; michael@0: nsLoadFlags requestFlags = nsIRequest::LOAD_NORMAL; michael@0: michael@0: #ifdef DEBUG michael@0: bool isPrivate = false; michael@0: michael@0: if (aLoadGroup) { michael@0: nsCOMPtr callbacks; michael@0: aLoadGroup->GetNotificationCallbacks(getter_AddRefs(callbacks)); michael@0: if (callbacks) { michael@0: nsCOMPtr loadContext = do_GetInterface(callbacks); michael@0: isPrivate = loadContext && loadContext->UsePrivateBrowsing(); michael@0: } michael@0: } michael@0: MOZ_ASSERT(isPrivate == mRespectPrivacy); michael@0: #endif michael@0: michael@0: // Get the default load flags from the loadgroup (if possible)... michael@0: if (aLoadGroup) { michael@0: aLoadGroup->GetLoadFlags(&requestFlags); michael@0: } michael@0: // michael@0: // Merge the default load flags with those passed in via aLoadFlags. michael@0: // Currently, *only* the caching, validation and background load flags michael@0: // are merged... michael@0: // michael@0: // The flags in aLoadFlags take precedence over the default flags! michael@0: // michael@0: if (aLoadFlags & LOAD_FLAGS_CACHE_MASK) { michael@0: // Override the default caching flags... michael@0: requestFlags = (requestFlags & ~LOAD_FLAGS_CACHE_MASK) | michael@0: (aLoadFlags & LOAD_FLAGS_CACHE_MASK); michael@0: } michael@0: if (aLoadFlags & LOAD_FLAGS_VALIDATE_MASK) { michael@0: // Override the default validation flags... michael@0: requestFlags = (requestFlags & ~LOAD_FLAGS_VALIDATE_MASK) | michael@0: (aLoadFlags & LOAD_FLAGS_VALIDATE_MASK); michael@0: } michael@0: if (aLoadFlags & nsIRequest::LOAD_BACKGROUND) { michael@0: // Propagate background loading... michael@0: requestFlags |= nsIRequest::LOAD_BACKGROUND; michael@0: } michael@0: michael@0: int32_t corsmode = imgIRequest::CORS_NONE; michael@0: if (aLoadFlags & imgILoader::LOAD_CORS_ANONYMOUS) { michael@0: corsmode = imgIRequest::CORS_ANONYMOUS; michael@0: } else if (aLoadFlags & imgILoader::LOAD_CORS_USE_CREDENTIALS) { michael@0: corsmode = imgIRequest::CORS_USE_CREDENTIALS; michael@0: } michael@0: michael@0: nsRefPtr entry; michael@0: michael@0: // Look in the cache for our URI, and then validate it. michael@0: // XXX For now ignore aCacheKey. We will need it in the future michael@0: // for correctly dealing with image load requests that are a result michael@0: // of post data. michael@0: imgCacheTable &cache = GetCache(aURI); michael@0: michael@0: if (cache.Get(spec, getter_AddRefs(entry)) && entry) { michael@0: if (ValidateEntry(entry, aURI, aFirstPartyIsolationURI, aReferrerURI, michael@0: aLoadGroup, aObserver, aCX, requestFlags, true, michael@0: _retval, aPolicy, aLoadingPrincipal, corsmode)) { michael@0: request = entry->GetRequest(); michael@0: michael@0: // If this entry has no proxies, its request has no reference to the entry. michael@0: if (entry->HasNoProxies()) { michael@0: LOG_FUNC_WITH_PARAM(GetImgLog(), "imgLoader::LoadImage() adding proxyless entry", "uri", spec.get()); michael@0: NS_ABORT_IF_FALSE(!request->HasCacheEntry(), "Proxyless entry's request has cache entry!"); michael@0: request->SetCacheEntry(entry); michael@0: michael@0: if (mCacheTracker) michael@0: mCacheTracker->MarkUsed(entry); michael@0: } michael@0: michael@0: entry->Touch(); michael@0: michael@0: #ifdef DEBUG_joe michael@0: printf("CACHEGET: %d %s %d\n", time(nullptr), spec.get(), entry->SizeOfData()); michael@0: #endif michael@0: } michael@0: else { michael@0: // We can't use this entry. We'll try to load it off the network, and if michael@0: // successful, overwrite the old entry in the cache with a new one. michael@0: entry = nullptr; michael@0: } michael@0: } michael@0: michael@0: // Keep the channel in this scope, so we can adjust its notificationCallbacks michael@0: // later when we create the proxy. michael@0: nsCOMPtr newChannel; michael@0: // If we didn't get a cache hit, we need to load from the network. michael@0: if (!request) { michael@0: LOG_SCOPE(GetImgLog(), "imgLoader::LoadImage |cache miss|"); michael@0: michael@0: bool forcePrincipalCheck; michael@0: rv = NewImageChannel(getter_AddRefs(newChannel), michael@0: &forcePrincipalCheck, michael@0: aURI, michael@0: aFirstPartyIsolationURI, michael@0: aReferrerURI, michael@0: aLoadGroup, michael@0: mAcceptHeader, michael@0: requestFlags, michael@0: aPolicy, michael@0: aLoadingPrincipal); michael@0: if (NS_FAILED(rv)) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: MOZ_ASSERT(NS_UsePrivateBrowsing(newChannel) == mRespectPrivacy); michael@0: michael@0: NewRequestAndEntry(forcePrincipalCheck, this, getter_AddRefs(request), getter_AddRefs(entry)); michael@0: michael@0: PR_LOG(GetImgLog(), PR_LOG_DEBUG, michael@0: ("[this=%p] imgLoader::LoadImage -- Created new imgRequest [request=%p]\n", this, request.get())); michael@0: michael@0: nsCOMPtr channelLoadGroup; michael@0: newChannel->GetLoadGroup(getter_AddRefs(channelLoadGroup)); michael@0: request->Init(aURI, aURI, aFirstPartyIsolationURI, channelLoadGroup, newChannel, entry, aCX, michael@0: aLoadingPrincipal, corsmode); michael@0: michael@0: // Add the initiator type for this image load michael@0: nsCOMPtr timedChannel = do_QueryInterface(newChannel); michael@0: if (timedChannel) { michael@0: timedChannel->SetInitiatorType(initiatorType); michael@0: } michael@0: michael@0: // Pass the inner window ID of the loading document, if possible. michael@0: nsCOMPtr doc = do_QueryInterface(aCX); michael@0: if (doc) { michael@0: request->SetInnerWindowID(doc->InnerWindowID()); michael@0: } michael@0: michael@0: // create the proxy listener michael@0: nsCOMPtr pl = new ProxyListener(request.get()); michael@0: michael@0: // See if we need to insert a CORS proxy between the proxy listener and the michael@0: // request. michael@0: nsCOMPtr listener = pl; michael@0: if (corsmode != imgIRequest::CORS_NONE) { michael@0: PR_LOG(GetImgLog(), PR_LOG_DEBUG, michael@0: ("[this=%p] imgLoader::LoadImage -- Setting up a CORS load", michael@0: this)); michael@0: bool withCredentials = corsmode == imgIRequest::CORS_USE_CREDENTIALS; michael@0: michael@0: nsRefPtr corsproxy = michael@0: new nsCORSListenerProxy(pl, aLoadingPrincipal, withCredentials); michael@0: rv = corsproxy->Init(newChannel); michael@0: if (NS_FAILED(rv)) { michael@0: PR_LOG(GetImgLog(), PR_LOG_DEBUG, michael@0: ("[this=%p] imgLoader::LoadImage -- nsCORSListenerProxy " michael@0: "creation failed: 0x%x\n", this, rv)); michael@0: request->CancelAndAbort(rv); michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: listener = corsproxy; michael@0: } michael@0: michael@0: PR_LOG(GetImgLog(), PR_LOG_DEBUG, michael@0: ("[this=%p] imgLoader::LoadImage -- Calling channel->AsyncOpen()\n", this)); michael@0: michael@0: mozilla::net::SeerLearn(aURI, aFirstPartyIsolationURI, michael@0: nsINetworkSeer::LEARN_LOAD_SUBRESOURCE, aLoadGroup); michael@0: michael@0: nsresult openRes = newChannel->AsyncOpen(listener, nullptr); michael@0: michael@0: if (NS_FAILED(openRes)) { michael@0: PR_LOG(GetImgLog(), PR_LOG_DEBUG, michael@0: ("[this=%p] imgLoader::LoadImage -- AsyncOpen() failed: 0x%x\n", michael@0: this, openRes)); michael@0: request->CancelAndAbort(openRes); michael@0: return openRes; michael@0: } michael@0: michael@0: if (isIsolated) // Try to add the new request into the cache. michael@0: PutIntoCache(spec, entry); michael@0: } else { michael@0: LOG_MSG_WITH_PARAM(GetImgLog(), michael@0: "imgLoader::LoadImage |cache hit|", "request", request); michael@0: } michael@0: michael@0: michael@0: // If we didn't get a proxy when validating the cache entry, we need to create one. michael@0: if (!*_retval) { michael@0: // ValidateEntry() has three return values: "Is valid," "might be valid -- michael@0: // validating over network", and "not valid." If we don't have a _retval, michael@0: // we know ValidateEntry is not validating over the network, so it's safe michael@0: // to SetLoadId here because we know this request is valid for this context. michael@0: // michael@0: // Note, however, that this doesn't guarantee the behaviour we want (one michael@0: // URL maps to the same image on a page) if we load the same image in a michael@0: // different tab (see bug 528003), because its load id will get re-set, and michael@0: // that'll cause us to validate over the network. michael@0: request->SetLoadId(aCX); michael@0: michael@0: LOG_MSG(GetImgLog(), "imgLoader::LoadImage", "creating proxy request."); michael@0: rv = CreateNewProxyForRequest(request, aLoadGroup, aObserver, michael@0: requestFlags, _retval); michael@0: if (NS_FAILED(rv)) { michael@0: return rv; michael@0: } michael@0: michael@0: imgRequestProxy *proxy = *_retval; michael@0: michael@0: // Make sure that OnStatus/OnProgress calls have the right request set, if michael@0: // we did create a channel here. michael@0: if (newChannel) { michael@0: nsCOMPtr requestor( michael@0: new nsProgressNotificationProxy(newChannel, proxy)); michael@0: if (!requestor) michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: newChannel->SetNotificationCallbacks(requestor); michael@0: } michael@0: michael@0: // Note that it's OK to add here even if the request is done. If it is, michael@0: // it'll send a OnStopRequest() to the proxy in imgRequestProxy::Notify and michael@0: // the proxy will be removed from the loadgroup. michael@0: proxy->AddToLoadGroup(); michael@0: michael@0: // If we're loading off the network, explicitly don't notify our proxy, michael@0: // because necko (or things called from necko, such as imgCacheValidator) michael@0: // are going to call our notifications asynchronously, and we can't make it michael@0: // further asynchronous because observers might rely on imagelib completing michael@0: // its work between the channel's OnStartRequest and OnStopRequest. michael@0: if (!newChannel) michael@0: proxy->NotifyListener(); michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: NS_ASSERTION(*_retval, "imgLoader::LoadImage -- no return value"); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsAutoCString imgLoader::GetCacheKey(nsIURI *firstPartyIsolationURI, ImageURL *imgURI, michael@0: bool *isIsolated) michael@0: { michael@0: NS_ASSERTION(imgURI, "imgLoader::GetCacheKey -- NULL imgURI"); michael@0: if (isIsolated) michael@0: *isIsolated = false; michael@0: michael@0: nsAutoCString spec; michael@0: if (imgURI) michael@0: imgURI->GetSpec(spec); michael@0: michael@0: nsAutoCString hostKey; michael@0: if (firstPartyIsolationURI && sThirdPartyUtilSvc) michael@0: sThirdPartyUtilSvc->GetFirstPartyHostForIsolation(firstPartyIsolationURI, hostKey); michael@0: michael@0: if (hostKey.Length() > 0) { michael@0: if (isIsolated) michael@0: *isIsolated = true; michael@0: // Make a new key using host michael@0: // FIXME: This might involve a couple more copies than necessary.. michael@0: // But man, 18 string types? Who knows which one I need to use to do michael@0: // this cheaply.. michael@0: return hostKey + nsAutoCString("&") + spec; michael@0: } else { michael@0: // No hostKey found, so don't isolate image to a first party. michael@0: return spec; michael@0: } michael@0: } michael@0: michael@0: nsAutoCString imgLoader::GetCacheKey(nsIURI *firstPartyIsolationURI, nsIURI* uri, michael@0: bool *isIsolated) { michael@0: nsRefPtr imageURI = new ImageURL(uri); michael@0: return GetCacheKey(firstPartyIsolationURI, imageURI, isIsolated); michael@0: } michael@0: michael@0: /* imgIRequest loadImageWithChannelXPCOM(in nsIChannel channel, in imgINotificationObserver aObserver, in nsISupports cx, out nsIStreamListener); */ michael@0: NS_IMETHODIMP imgLoader::LoadImageWithChannelXPCOM(nsIChannel *channel, imgINotificationObserver *aObserver, nsISupports *aCX, nsIStreamListener **listener, imgIRequest **_retval) michael@0: { michael@0: nsresult result; michael@0: imgRequestProxy* proxy; michael@0: result = LoadImageWithChannel(channel, michael@0: aObserver, michael@0: aCX, michael@0: listener, michael@0: &proxy); michael@0: *_retval = proxy; michael@0: return result; michael@0: } michael@0: michael@0: nsresult imgLoader::LoadImageWithChannel(nsIChannel *channel, imgINotificationObserver *aObserver, nsISupports *aCX, nsIStreamListener **listener, imgRequestProxy **_retval) michael@0: { michael@0: NS_ASSERTION(channel, "imgLoader::LoadImageWithChannel -- NULL channel pointer"); michael@0: michael@0: MOZ_ASSERT(NS_UsePrivateBrowsing(channel) == mRespectPrivacy); michael@0: michael@0: if (!sThirdPartyUtilSvc) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: nsRefPtr request; michael@0: michael@0: nsCOMPtr uri; michael@0: channel->GetURI(getter_AddRefs(uri)); michael@0: michael@0: nsCOMPtr firstPartyIsolationURI; michael@0: sThirdPartyUtilSvc->GetFirstPartyIsolationURI(channel, nullptr, michael@0: getter_AddRefs(firstPartyIsolationURI)); michael@0: michael@0: nsLoadFlags requestFlags = nsIRequest::LOAD_NORMAL; michael@0: channel->GetLoadFlags(&requestFlags); michael@0: michael@0: nsRefPtr entry; michael@0: imgCacheTable &cache = GetCache(uri); michael@0: nsAutoCString key = GetCacheKey(firstPartyIsolationURI, uri, nullptr); michael@0: michael@0: if (requestFlags & nsIRequest::LOAD_BYPASS_CACHE) { michael@0: imgCacheQueue &queue = GetCacheQueue(uri); michael@0: RemoveFromCache(key, cache, queue); michael@0: } else { michael@0: // Look in the cache for our URI, and then validate it. michael@0: // XXX For now ignore aCacheKey. We will need it in the future michael@0: // for correctly dealing with image load requests that are a result michael@0: // of post data. michael@0: michael@0: if (cache.Get(key, getter_AddRefs(entry)) && entry) { michael@0: // We don't want to kick off another network load. So we ask michael@0: // ValidateEntry to only do validation without creating a new proxy. If michael@0: // it says that the entry isn't valid any more, we'll only use the entry michael@0: // we're getting if the channel is loading from the cache anyways. michael@0: // michael@0: // XXX -- should this be changed? it's pretty much verbatim from the old michael@0: // code, but seems nonsensical. michael@0: if (ValidateEntry(entry, uri, nullptr, nullptr, nullptr, aObserver, aCX, michael@0: requestFlags, false, nullptr, nullptr, nullptr, michael@0: imgIRequest::CORS_NONE)) { michael@0: request = entry->GetRequest(); michael@0: } else { michael@0: nsCOMPtr cacheChan(do_QueryInterface(channel)); michael@0: bool bUseCacheCopy; michael@0: michael@0: if (cacheChan) michael@0: cacheChan->IsFromCache(&bUseCacheCopy); michael@0: else michael@0: bUseCacheCopy = false; michael@0: michael@0: if (!bUseCacheCopy) { michael@0: entry = nullptr; michael@0: } else { michael@0: request = entry->GetRequest(); michael@0: } michael@0: } michael@0: michael@0: if (request && entry) { michael@0: // If this entry has no proxies, its request has no reference to the entry. michael@0: if (entry->HasNoProxies()) { michael@0: LOG_FUNC_WITH_PARAM(GetImgLog(), "imgLoader::LoadImageWithChannel() adding proxyless entry", "uri", key.get()); michael@0: NS_ABORT_IF_FALSE(!request->HasCacheEntry(), "Proxyless entry's request has cache entry!"); michael@0: request->SetCacheEntry(entry); michael@0: michael@0: if (mCacheTracker) michael@0: mCacheTracker->MarkUsed(entry); michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: nsCOMPtr loadGroup; michael@0: channel->GetLoadGroup(getter_AddRefs(loadGroup)); michael@0: michael@0: // Filter out any load flags not from nsIRequest michael@0: requestFlags &= nsIRequest::LOAD_REQUESTMASK; michael@0: michael@0: nsresult rv = NS_OK; michael@0: if (request) { michael@0: // we have this in our cache already.. cancel the current (document) load michael@0: michael@0: channel->Cancel(NS_ERROR_PARSED_DATA_CACHED); // this should fire an OnStopRequest michael@0: michael@0: *listener = nullptr; // give them back a null nsIStreamListener michael@0: michael@0: rv = CreateNewProxyForRequest(request, loadGroup, aObserver, michael@0: requestFlags, _retval); michael@0: static_cast(*_retval)->NotifyListener(); michael@0: } else { michael@0: // Default to doing a principal check because we don't know who michael@0: // started that load and whether their principal ended up being michael@0: // inherited on the channel. michael@0: NewRequestAndEntry(true, this, getter_AddRefs(request), getter_AddRefs(entry)); michael@0: michael@0: // We use originalURI here to fulfil the imgIRequest contract on GetURI. michael@0: nsCOMPtr originalURI; michael@0: channel->GetOriginalURI(getter_AddRefs(originalURI)); michael@0: michael@0: // No principal specified here, because we're not passed one. michael@0: request->Init(originalURI, uri, firstPartyIsolationURI, channel, channel, entry, michael@0: aCX, nullptr, imgIRequest::CORS_NONE); michael@0: michael@0: ProxyListener *pl = new ProxyListener(static_cast(request.get())); michael@0: NS_ADDREF(pl); michael@0: michael@0: *listener = static_cast(pl); michael@0: NS_ADDREF(*listener); michael@0: michael@0: NS_RELEASE(pl); michael@0: michael@0: bool isIsolated = false; michael@0: nsAutoCString cacheKey = GetCacheKey(firstPartyIsolationURI, originalURI, &isIsolated); michael@0: if (isIsolated) // Try to add the new request into the cache. michael@0: PutIntoCache(cacheKey, entry); michael@0: michael@0: rv = CreateNewProxyForRequest(request, loadGroup, aObserver, michael@0: requestFlags, _retval); michael@0: michael@0: // Explicitly don't notify our proxy, because we're loading off the michael@0: // network, and necko (or things called from necko, such as michael@0: // imgCacheValidator) are going to call our notifications asynchronously, michael@0: // and we can't make it further asynchronous because observers might rely michael@0: // on imagelib completing its work between the channel's OnStartRequest and michael@0: // OnStopRequest. michael@0: } michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: bool imgLoader::SupportImageWithMimeType(const char* aMimeType) michael@0: { michael@0: nsAutoCString mimeType(aMimeType); michael@0: ToLowerCase(mimeType); michael@0: return Image::GetDecoderType(mimeType.get()) != Image::eDecoderType_unknown; michael@0: } michael@0: michael@0: NS_IMETHODIMP imgLoader::GetMIMETypeFromContent(nsIRequest* aRequest, michael@0: const uint8_t* aContents, michael@0: uint32_t aLength, michael@0: nsACString& aContentType) michael@0: { michael@0: return GetMimeTypeFromContent((const char*)aContents, aLength, aContentType); michael@0: } michael@0: michael@0: /* static */ michael@0: nsresult imgLoader::GetMimeTypeFromContent(const char* aContents, uint32_t aLength, nsACString& aContentType) michael@0: { michael@0: /* Is it a GIF? */ michael@0: if (aLength >= 6 && (!nsCRT::strncmp(aContents, "GIF87a", 6) || michael@0: !nsCRT::strncmp(aContents, "GIF89a", 6))) michael@0: { michael@0: aContentType.AssignLiteral(IMAGE_GIF); michael@0: } michael@0: michael@0: /* or a PNG? */ michael@0: else if (aLength >= 8 && ((unsigned char)aContents[0]==0x89 && michael@0: (unsigned char)aContents[1]==0x50 && michael@0: (unsigned char)aContents[2]==0x4E && michael@0: (unsigned char)aContents[3]==0x47 && michael@0: (unsigned char)aContents[4]==0x0D && michael@0: (unsigned char)aContents[5]==0x0A && michael@0: (unsigned char)aContents[6]==0x1A && michael@0: (unsigned char)aContents[7]==0x0A)) michael@0: { michael@0: aContentType.AssignLiteral(IMAGE_PNG); michael@0: } michael@0: michael@0: /* maybe a JPEG (JFIF)? */ michael@0: /* JFIF files start with SOI APP0 but older files can start with SOI DQT michael@0: * so we test for SOI followed by any marker, i.e. FF D8 FF michael@0: * this will also work for SPIFF JPEG files if they appear in the future. michael@0: * michael@0: * (JFIF is 0XFF 0XD8 0XFF 0XE0 0X4A 0X46 0X49 0X46 0X00) michael@0: */ michael@0: else if (aLength >= 3 && michael@0: ((unsigned char)aContents[0])==0xFF && michael@0: ((unsigned char)aContents[1])==0xD8 && michael@0: ((unsigned char)aContents[2])==0xFF) michael@0: { michael@0: aContentType.AssignLiteral(IMAGE_JPEG); michael@0: } michael@0: michael@0: /* or how about ART? */ michael@0: /* ART begins with JG (4A 47). Major version offset 2. michael@0: * Minor version offset 3. Offset 4 must be nullptr. michael@0: */ michael@0: else if (aLength >= 5 && michael@0: ((unsigned char) aContents[0])==0x4a && michael@0: ((unsigned char) aContents[1])==0x47 && michael@0: ((unsigned char) aContents[4])==0x00 ) michael@0: { michael@0: aContentType.AssignLiteral(IMAGE_ART); michael@0: } michael@0: michael@0: else if (aLength >= 2 && !nsCRT::strncmp(aContents, "BM", 2)) { michael@0: aContentType.AssignLiteral(IMAGE_BMP); michael@0: } michael@0: michael@0: // ICOs always begin with a 2-byte 0 followed by a 2-byte 1. michael@0: // CURs begin with 2-byte 0 followed by 2-byte 2. michael@0: else if (aLength >= 4 && (!memcmp(aContents, "\000\000\001\000", 4) || michael@0: !memcmp(aContents, "\000\000\002\000", 4))) { michael@0: aContentType.AssignLiteral(IMAGE_ICO); michael@0: } michael@0: michael@0: else { michael@0: /* none of the above? I give up */ michael@0: return NS_ERROR_NOT_AVAILABLE; michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: /** michael@0: * proxy stream listener class used to handle multipart/x-mixed-replace michael@0: */ michael@0: michael@0: #include "nsIRequest.h" michael@0: #include "nsIStreamConverterService.h" michael@0: michael@0: NS_IMPL_ISUPPORTS(ProxyListener, michael@0: nsIStreamListener, michael@0: nsIThreadRetargetableStreamListener, michael@0: nsIRequestObserver) michael@0: michael@0: ProxyListener::ProxyListener(nsIStreamListener *dest) : michael@0: mDestListener(dest) michael@0: { michael@0: /* member initializers and constructor code */ michael@0: } michael@0: michael@0: ProxyListener::~ProxyListener() michael@0: { michael@0: /* destructor code */ michael@0: } michael@0: michael@0: michael@0: /** nsIRequestObserver methods **/ michael@0: michael@0: /* void onStartRequest (in nsIRequest request, in nsISupports ctxt); */ michael@0: NS_IMETHODIMP ProxyListener::OnStartRequest(nsIRequest *aRequest, nsISupports *ctxt) michael@0: { michael@0: if (!mDestListener) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: nsCOMPtr channel(do_QueryInterface(aRequest)); michael@0: if (channel) { michael@0: nsAutoCString contentType; michael@0: nsresult rv = channel->GetContentType(contentType); michael@0: michael@0: if (!contentType.IsEmpty()) { michael@0: /* If multipart/x-mixed-replace content, we'll insert a MIME decoder michael@0: in the pipeline to handle the content and pass it along to our michael@0: original listener. michael@0: */ michael@0: if (NS_LITERAL_CSTRING("multipart/x-mixed-replace").Equals(contentType)) { michael@0: michael@0: nsCOMPtr convServ(do_GetService("@mozilla.org/streamConverters;1", &rv)); michael@0: if (NS_SUCCEEDED(rv)) { michael@0: nsCOMPtr toListener(mDestListener); michael@0: nsCOMPtr fromListener; michael@0: michael@0: rv = convServ->AsyncConvertData("multipart/x-mixed-replace", michael@0: "*/*", michael@0: toListener, michael@0: nullptr, michael@0: getter_AddRefs(fromListener)); michael@0: if (NS_SUCCEEDED(rv)) michael@0: mDestListener = fromListener; michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: return mDestListener->OnStartRequest(aRequest, ctxt); michael@0: } michael@0: michael@0: /* void onStopRequest (in nsIRequest request, in nsISupports ctxt, in nsresult status); */ michael@0: NS_IMETHODIMP ProxyListener::OnStopRequest(nsIRequest *aRequest, nsISupports *ctxt, nsresult status) michael@0: { michael@0: if (!mDestListener) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: return mDestListener->OnStopRequest(aRequest, ctxt, status); 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: ProxyListener::OnDataAvailable(nsIRequest *aRequest, nsISupports *ctxt, michael@0: nsIInputStream *inStr, uint64_t sourceOffset, michael@0: uint32_t count) michael@0: { michael@0: if (!mDestListener) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: return mDestListener->OnDataAvailable(aRequest, ctxt, inStr, sourceOffset, count); michael@0: } michael@0: michael@0: /** nsThreadRetargetableStreamListener methods **/ michael@0: NS_IMETHODIMP michael@0: ProxyListener::CheckListenerChain() michael@0: { michael@0: NS_ASSERTION(NS_IsMainThread(), "Should be on the main thread!"); michael@0: nsresult rv = NS_OK; michael@0: nsCOMPtr retargetableListener = michael@0: do_QueryInterface(mDestListener, &rv); michael@0: if (retargetableListener) { michael@0: rv = retargetableListener->CheckListenerChain(); michael@0: } michael@0: PR_LOG(GetImgLog(), PR_LOG_DEBUG, michael@0: ("ProxyListener::CheckListenerChain %s [this=%p listener=%p rv=%x]", michael@0: (NS_SUCCEEDED(rv) ? "success" : "failure"), michael@0: this, (nsIStreamListener*)mDestListener, rv)); michael@0: return rv; michael@0: } michael@0: michael@0: /** michael@0: * http validate class. check a channel for a 304 michael@0: */ michael@0: michael@0: NS_IMPL_ISUPPORTS(imgCacheValidator, nsIStreamListener, nsIRequestObserver, michael@0: nsIThreadRetargetableStreamListener, michael@0: nsIChannelEventSink, nsIInterfaceRequestor, michael@0: nsIAsyncVerifyRedirectCallback) michael@0: michael@0: imgCacheValidator::imgCacheValidator(nsProgressNotificationProxy* progress, michael@0: imgLoader* loader, imgRequest *request, michael@0: void *aContext, bool forcePrincipalCheckForCacheEntry) michael@0: : mProgressProxy(progress), michael@0: mRequest(request), michael@0: mContext(aContext), michael@0: mImgLoader(loader) michael@0: { michael@0: NewRequestAndEntry(forcePrincipalCheckForCacheEntry, loader, getter_AddRefs(mNewRequest), michael@0: getter_AddRefs(mNewEntry)); michael@0: } michael@0: michael@0: imgCacheValidator::~imgCacheValidator() michael@0: { michael@0: if (mRequest) { michael@0: mRequest->mValidator = nullptr; michael@0: } michael@0: } michael@0: michael@0: void imgCacheValidator::AddProxy(imgRequestProxy *aProxy) michael@0: { michael@0: // aProxy needs to be in the loadgroup since we're validating from michael@0: // the network. michael@0: aProxy->AddToLoadGroup(); michael@0: michael@0: mProxies.AppendObject(aProxy); michael@0: } michael@0: michael@0: /** nsIRequestObserver methods **/ michael@0: michael@0: /* void onStartRequest (in nsIRequest request, in nsISupports ctxt); */ michael@0: NS_IMETHODIMP imgCacheValidator::OnStartRequest(nsIRequest *aRequest, nsISupports *ctxt) michael@0: { michael@0: // If this request is coming from cache and has the same URI as our michael@0: // imgRequest, the request all our proxies are pointing at is valid, and all michael@0: // we have to do is tell them to notify their listeners. michael@0: nsCOMPtr cacheChan(do_QueryInterface(aRequest)); michael@0: nsCOMPtr channel(do_QueryInterface(aRequest)); michael@0: if (cacheChan && channel && !mRequest->CacheChanged(aRequest)) { michael@0: bool isFromCache = false; michael@0: cacheChan->IsFromCache(&isFromCache); michael@0: michael@0: nsCOMPtr channelURI; michael@0: bool sameURI = false; michael@0: channel->GetURI(getter_AddRefs(channelURI)); michael@0: if (channelURI) michael@0: channelURI->Equals(mRequest->mCurrentURI, &sameURI); michael@0: michael@0: if (isFromCache && sameURI) { michael@0: uint32_t count = mProxies.Count(); michael@0: for (int32_t i = count-1; i>=0; i--) { michael@0: imgRequestProxy *proxy = static_cast(mProxies[i]); michael@0: michael@0: // Proxies waiting on cache validation should be deferring notifications. michael@0: // Undefer them. michael@0: NS_ABORT_IF_FALSE(proxy->NotificationsDeferred(), michael@0: "Proxies waiting on cache validation should be " michael@0: "deferring notifications!"); michael@0: proxy->SetNotificationsDeferred(false); michael@0: michael@0: // Notify synchronously, because we're already in OnStartRequest, an michael@0: // asynchronously-called function. michael@0: proxy->SyncNotifyListener(); michael@0: } michael@0: michael@0: // We don't need to load this any more. michael@0: aRequest->Cancel(NS_BINDING_ABORTED); michael@0: michael@0: mRequest->SetLoadId(mContext); michael@0: mRequest->mValidator = nullptr; michael@0: michael@0: mRequest = nullptr; michael@0: michael@0: mNewRequest = nullptr; michael@0: mNewEntry = nullptr; michael@0: michael@0: return NS_OK; michael@0: } michael@0: } michael@0: michael@0: // We can't load out of cache. We have to create a whole new request for the michael@0: // data that's coming in off the channel. michael@0: nsCOMPtr uri; michael@0: { michael@0: nsRefPtr imageURL; michael@0: mRequest->GetURI(getter_AddRefs(imageURL)); michael@0: uri = imageURL->ToIURI(); michael@0: } michael@0: michael@0: #if defined(PR_LOGGING) michael@0: nsAutoCString spec; michael@0: uri->GetSpec(spec); michael@0: LOG_MSG_WITH_PARAM(GetImgLog(), "imgCacheValidator::OnStartRequest creating new request", "uri", spec.get()); michael@0: #endif michael@0: michael@0: int32_t corsmode = mRequest->GetCORSMode(); michael@0: nsCOMPtr loadingPrincipal = mRequest->GetLoadingPrincipal(); michael@0: nsCOMPtr firstPartyIsolationURI = mRequest->mFirstPartyIsolationURI; michael@0: michael@0: // Doom the old request's cache entry michael@0: mRequest->RemoveFromCache(); michael@0: michael@0: mRequest->mValidator = nullptr; michael@0: mRequest = nullptr; michael@0: michael@0: // We use originalURI here to fulfil the imgIRequest contract on GetURI. michael@0: nsCOMPtr originalURI; michael@0: channel->GetOriginalURI(getter_AddRefs(originalURI)); michael@0: mNewRequest->Init(originalURI, uri, firstPartyIsolationURI, aRequest, channel, michael@0: mNewEntry, mContext, loadingPrincipal, corsmode); michael@0: michael@0: mDestListener = new ProxyListener(mNewRequest); michael@0: michael@0: // Try to add the new request into the cache. Note that the entry must be in michael@0: // the cache before the proxies' ownership changes, because adding a proxy michael@0: // changes the caching behaviour for imgRequests. michael@0: bool isIsolated = false; michael@0: nsAutoCString key = mImgLoader->GetCacheKey(firstPartyIsolationURI, originalURI, michael@0: &isIsolated); michael@0: if (isIsolated) michael@0: mImgLoader->PutIntoCache(key, mNewEntry); michael@0: michael@0: uint32_t count = mProxies.Count(); michael@0: for (int32_t i = count-1; i>=0; i--) { michael@0: imgRequestProxy *proxy = static_cast(mProxies[i]); michael@0: proxy->ChangeOwner(mNewRequest); michael@0: michael@0: // Notify synchronously, because we're already in OnStartRequest, an michael@0: // asynchronously-called function. michael@0: proxy->SetNotificationsDeferred(false); michael@0: proxy->SyncNotifyListener(); michael@0: } michael@0: michael@0: mNewRequest = nullptr; michael@0: mNewEntry = nullptr; michael@0: michael@0: return mDestListener->OnStartRequest(aRequest, ctxt); michael@0: } michael@0: michael@0: /* void onStopRequest (in nsIRequest request, in nsISupports ctxt, in nsresult status); */ michael@0: NS_IMETHODIMP imgCacheValidator::OnStopRequest(nsIRequest *aRequest, nsISupports *ctxt, nsresult status) michael@0: { michael@0: if (!mDestListener) michael@0: return NS_OK; michael@0: michael@0: return mDestListener->OnStopRequest(aRequest, ctxt, status); michael@0: } michael@0: michael@0: /** nsIStreamListener methods **/ michael@0: 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: imgCacheValidator::OnDataAvailable(nsIRequest *aRequest, nsISupports *ctxt, michael@0: nsIInputStream *inStr, michael@0: uint64_t sourceOffset, uint32_t count) michael@0: { michael@0: if (!mDestListener) { michael@0: // XXX see bug 113959 michael@0: uint32_t _retval; michael@0: inStr->ReadSegments(NS_DiscardSegment, nullptr, count, &_retval); michael@0: return NS_OK; michael@0: } michael@0: michael@0: return mDestListener->OnDataAvailable(aRequest, ctxt, inStr, sourceOffset, count); michael@0: } michael@0: michael@0: /** nsIThreadRetargetableStreamListener methods **/ michael@0: michael@0: NS_IMETHODIMP michael@0: imgCacheValidator::CheckListenerChain() michael@0: { michael@0: NS_ASSERTION(NS_IsMainThread(), "Should be on the main thread!"); michael@0: nsresult rv = NS_OK; michael@0: nsCOMPtr retargetableListener = michael@0: do_QueryInterface(mDestListener, &rv); michael@0: if (retargetableListener) { michael@0: rv = retargetableListener->CheckListenerChain(); michael@0: } michael@0: PR_LOG(GetImgLog(), PR_LOG_DEBUG, michael@0: ("[this=%p] imgCacheValidator::CheckListenerChain -- rv %d=%s", michael@0: this, NS_SUCCEEDED(rv) ? "succeeded" : "failed", rv)); michael@0: return rv; michael@0: } michael@0: michael@0: /** nsIInterfaceRequestor methods **/ michael@0: michael@0: NS_IMETHODIMP imgCacheValidator::GetInterface(const nsIID & aIID, void **aResult) michael@0: { michael@0: if (aIID.Equals(NS_GET_IID(nsIChannelEventSink))) michael@0: return QueryInterface(aIID, aResult); michael@0: michael@0: return mProgressProxy->GetInterface(aIID, aResult); michael@0: } michael@0: michael@0: // These functions are materially the same as the same functions in imgRequest. michael@0: // We duplicate them because we're verifying whether cache loads are necessary, michael@0: // not unconditionally loading. michael@0: michael@0: /** nsIChannelEventSink methods **/ michael@0: NS_IMETHODIMP imgCacheValidator::AsyncOnChannelRedirect(nsIChannel *oldChannel, michael@0: nsIChannel *newChannel, uint32_t flags, michael@0: nsIAsyncVerifyRedirectCallback *callback) michael@0: { michael@0: // Note all cache information we get from the old channel. michael@0: mNewRequest->SetCacheValidation(mNewEntry, oldChannel); michael@0: michael@0: // Prepare for callback michael@0: mRedirectCallback = callback; michael@0: mRedirectChannel = newChannel; michael@0: michael@0: return mProgressProxy->AsyncOnChannelRedirect(oldChannel, newChannel, flags, this); michael@0: } michael@0: michael@0: NS_IMETHODIMP imgCacheValidator::OnRedirectVerifyCallback(nsresult aResult) michael@0: { michael@0: // If we've already been told to abort, just do so. michael@0: if (NS_FAILED(aResult)) { michael@0: mRedirectCallback->OnRedirectVerifyCallback(aResult); michael@0: mRedirectCallback = nullptr; michael@0: mRedirectChannel = nullptr; michael@0: return NS_OK; michael@0: } 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: nsCOMPtr uri; michael@0: mRedirectChannel->GetURI(getter_AddRefs(uri)); michael@0: bool doesNotReturnData = false; michael@0: NS_URIChainHasFlags(uri, nsIProtocolHandler::URI_DOES_NOT_RETURN_DATA, michael@0: &doesNotReturnData); michael@0: michael@0: nsresult result = NS_OK; michael@0: michael@0: if (doesNotReturnData) { michael@0: result = NS_ERROR_ABORT; michael@0: } michael@0: michael@0: mRedirectCallback->OnRedirectVerifyCallback(result); michael@0: mRedirectCallback = nullptr; michael@0: mRedirectChannel = nullptr; michael@0: return NS_OK; michael@0: }