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 "nsPrefetchService.h" michael@0: #include "nsICacheEntry.h" michael@0: #include "nsIServiceManager.h" michael@0: #include "nsICategoryManager.h" michael@0: #include "nsIObserverService.h" michael@0: #include "nsIWebProgress.h" michael@0: #include "nsCURILoader.h" michael@0: #include "nsICachingChannel.h" michael@0: #include "nsICacheVisitor.h" michael@0: #include "nsIHttpChannel.h" michael@0: #include "nsIURL.h" michael@0: #include "nsISimpleEnumerator.h" michael@0: #include "nsNetUtil.h" michael@0: #include "nsString.h" michael@0: #include "nsXPIDLString.h" michael@0: #include "nsReadableUtils.h" michael@0: #include "nsStreamUtils.h" michael@0: #include "nsAutoPtr.h" michael@0: #include "prtime.h" michael@0: #include "prlog.h" michael@0: #include "plstr.h" michael@0: #include "nsIAsyncVerifyRedirectCallback.h" michael@0: #include "mozilla/Preferences.h" michael@0: #include "mozilla/Attributes.h" michael@0: #include "nsIDOMNode.h" michael@0: #include "nsINode.h" michael@0: #include "nsIDocument.h" michael@0: michael@0: using namespace mozilla; michael@0: michael@0: #if defined(PR_LOGGING) michael@0: // michael@0: // To enable logging (see prlog.h for full details): michael@0: // michael@0: // set NSPR_LOG_MODULES=nsPrefetch:5 michael@0: // set NSPR_LOG_FILE=prefetch.log michael@0: // michael@0: // this enables PR_LOG_ALWAYS level information and places all output in michael@0: // the file http.log michael@0: // michael@0: static PRLogModuleInfo *gPrefetchLog; michael@0: #endif michael@0: michael@0: #undef LOG michael@0: #define LOG(args) PR_LOG(gPrefetchLog, 4, args) michael@0: michael@0: #undef LOG_ENABLED michael@0: #define LOG_ENABLED() PR_LOG_TEST(gPrefetchLog, 4) michael@0: michael@0: #define PREFETCH_PREF "network.prefetch-next" michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: // helpers michael@0: //----------------------------------------------------------------------------- michael@0: michael@0: static inline uint32_t michael@0: PRTimeToSeconds(PRTime t_usec) michael@0: { michael@0: PRTime usec_per_sec = PR_USEC_PER_SEC; michael@0: return uint32_t(t_usec /= usec_per_sec); michael@0: } michael@0: michael@0: #define NowInSeconds() PRTimeToSeconds(PR_Now()) michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: // nsPrefetchQueueEnumerator michael@0: //----------------------------------------------------------------------------- michael@0: class nsPrefetchQueueEnumerator MOZ_FINAL : public nsISimpleEnumerator michael@0: { michael@0: public: michael@0: NS_DECL_ISUPPORTS michael@0: NS_DECL_NSISIMPLEENUMERATOR michael@0: nsPrefetchQueueEnumerator(nsPrefetchService *aService); michael@0: ~nsPrefetchQueueEnumerator(); michael@0: michael@0: private: michael@0: void Increment(); michael@0: michael@0: nsRefPtr mService; michael@0: nsRefPtr mCurrent; michael@0: bool mStarted; michael@0: }; michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: // nsPrefetchQueueEnumerator michael@0: //----------------------------------------------------------------------------- michael@0: nsPrefetchQueueEnumerator::nsPrefetchQueueEnumerator(nsPrefetchService *aService) michael@0: : mService(aService) michael@0: , mStarted(false) michael@0: { michael@0: Increment(); michael@0: } michael@0: michael@0: nsPrefetchQueueEnumerator::~nsPrefetchQueueEnumerator() michael@0: { michael@0: } michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: // nsPrefetchQueueEnumerator::nsISimpleEnumerator michael@0: //----------------------------------------------------------------------------- michael@0: NS_IMETHODIMP michael@0: nsPrefetchQueueEnumerator::HasMoreElements(bool *aHasMore) michael@0: { michael@0: *aHasMore = (mCurrent != nullptr); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsPrefetchQueueEnumerator::GetNext(nsISupports **aItem) michael@0: { michael@0: if (!mCurrent) return NS_ERROR_FAILURE; michael@0: michael@0: NS_ADDREF(*aItem = static_cast(mCurrent.get())); michael@0: michael@0: Increment(); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: // nsPrefetchQueueEnumerator michael@0: //----------------------------------------------------------------------------- michael@0: michael@0: void michael@0: nsPrefetchQueueEnumerator::Increment() michael@0: { michael@0: if (!mStarted) { michael@0: // If the service is currently serving a request, it won't be in michael@0: // the pending queue, so we return it first. If it isn't, we'll michael@0: // just start with the pending queue. michael@0: mStarted = true; michael@0: mCurrent = mService->GetCurrentNode(); michael@0: if (!mCurrent) michael@0: mCurrent = mService->GetQueueHead(); michael@0: return; michael@0: } michael@0: michael@0: if (mCurrent) { michael@0: if (mCurrent == mService->GetCurrentNode()) { michael@0: // If we just returned the node being processed by the service, michael@0: // start with the pending queue michael@0: mCurrent = mService->GetQueueHead(); michael@0: } michael@0: else { michael@0: // Otherwise just advance to the next item in the queue michael@0: mCurrent = mCurrent->mNext; michael@0: } michael@0: } michael@0: } michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: // nsPrefetchQueueEnumerator::nsISupports michael@0: //----------------------------------------------------------------------------- michael@0: michael@0: NS_IMPL_ISUPPORTS(nsPrefetchQueueEnumerator, nsISimpleEnumerator) michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: // nsPrefetchNode michael@0: //----------------------------------------------------------------------------- michael@0: michael@0: nsPrefetchNode::nsPrefetchNode(nsPrefetchService *aService, michael@0: nsIURI *aURI, michael@0: nsIURI *aReferrerURI, michael@0: nsIDOMNode *aSource) michael@0: : mNext(nullptr) michael@0: , mURI(aURI) michael@0: , mReferrerURI(aReferrerURI) michael@0: , mService(aService) michael@0: , mChannel(nullptr) michael@0: , mBytesRead(0) michael@0: { michael@0: mSource = do_GetWeakReference(aSource); michael@0: } michael@0: michael@0: nsresult michael@0: nsPrefetchNode::OpenChannel() michael@0: { michael@0: nsCOMPtr source = do_QueryReferent(mSource); michael@0: if (!source) { michael@0: // Don't attempt to prefetch if we don't have a source node michael@0: // (which should never happen). michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: nsCOMPtr loadGroup = source->OwnerDoc()->GetDocumentLoadGroup(); michael@0: nsresult rv = NS_NewChannel(getter_AddRefs(mChannel), michael@0: mURI, michael@0: nullptr, loadGroup, this, michael@0: nsIRequest::LOAD_BACKGROUND | michael@0: nsICachingChannel::LOAD_ONLY_IF_MODIFIED); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // configure HTTP specific stuff michael@0: nsCOMPtr httpChannel = michael@0: do_QueryInterface(mChannel); michael@0: if (httpChannel) { michael@0: httpChannel->SetReferrer(mReferrerURI); michael@0: httpChannel->SetRequestHeader( michael@0: NS_LITERAL_CSTRING("X-Moz"), michael@0: NS_LITERAL_CSTRING("prefetch"), michael@0: false); michael@0: } michael@0: michael@0: rv = mChannel->AsyncOpen(this, nullptr); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsPrefetchNode::CancelChannel(nsresult error) michael@0: { michael@0: mChannel->Cancel(error); michael@0: mChannel = nullptr; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: // nsPrefetchNode::nsISupports michael@0: //----------------------------------------------------------------------------- michael@0: michael@0: NS_IMPL_ISUPPORTS(nsPrefetchNode, michael@0: nsIRequestObserver, michael@0: nsIStreamListener, michael@0: nsIInterfaceRequestor, michael@0: nsIChannelEventSink, michael@0: nsIRedirectResultListener) michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: // nsPrefetchNode::nsIStreamListener michael@0: //----------------------------------------------------------------------------- michael@0: michael@0: NS_IMETHODIMP michael@0: nsPrefetchNode::OnStartRequest(nsIRequest *aRequest, michael@0: nsISupports *aContext) michael@0: { michael@0: nsresult rv; michael@0: michael@0: nsCOMPtr cachingChannel = michael@0: do_QueryInterface(aRequest, &rv); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: // no need to prefetch a document that is already in the cache michael@0: bool fromCache; michael@0: if (NS_SUCCEEDED(cachingChannel->IsFromCache(&fromCache)) && michael@0: fromCache) { michael@0: LOG(("document is already in the cache; canceling prefetch\n")); michael@0: return NS_BINDING_ABORTED; michael@0: } michael@0: michael@0: // michael@0: // no need to prefetch a document that must be requested fresh each michael@0: // and every time. michael@0: // michael@0: nsCOMPtr cacheToken; michael@0: cachingChannel->GetCacheToken(getter_AddRefs(cacheToken)); michael@0: if (!cacheToken) michael@0: return NS_ERROR_ABORT; // bail, no cache entry michael@0: michael@0: nsCOMPtr entryInfo = michael@0: do_QueryInterface(cacheToken, &rv); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: uint32_t expTime; michael@0: if (NS_SUCCEEDED(entryInfo->GetExpirationTime(&expTime))) { michael@0: if (NowInSeconds() >= expTime) { michael@0: LOG(("document cannot be reused from cache; " michael@0: "canceling prefetch\n")); michael@0: return NS_BINDING_ABORTED; michael@0: } michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsPrefetchNode::OnDataAvailable(nsIRequest *aRequest, michael@0: nsISupports *aContext, michael@0: nsIInputStream *aStream, michael@0: uint64_t aOffset, michael@0: uint32_t aCount) michael@0: { michael@0: uint32_t bytesRead = 0; michael@0: aStream->ReadSegments(NS_DiscardSegment, nullptr, aCount, &bytesRead); michael@0: mBytesRead += bytesRead; michael@0: LOG(("prefetched %u bytes [offset=%llu]\n", bytesRead, aOffset)); michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: NS_IMETHODIMP michael@0: nsPrefetchNode::OnStopRequest(nsIRequest *aRequest, michael@0: nsISupports *aContext, michael@0: nsresult aStatus) michael@0: { michael@0: LOG(("done prefetching [status=%x]\n", aStatus)); michael@0: michael@0: if (mBytesRead == 0 && aStatus == NS_OK) { michael@0: // we didn't need to read (because LOAD_ONLY_IF_MODIFIED was michael@0: // specified), but the object should report loadedSize as if it michael@0: // did. michael@0: mChannel->GetContentLength(&mBytesRead); michael@0: } michael@0: michael@0: mService->NotifyLoadCompleted(this); michael@0: mService->ProcessNextURI(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: // nsPrefetchNode::nsIInterfaceRequestor michael@0: //----------------------------------------------------------------------------- michael@0: michael@0: NS_IMETHODIMP michael@0: nsPrefetchNode::GetInterface(const nsIID &aIID, void **aResult) michael@0: { michael@0: if (aIID.Equals(NS_GET_IID(nsIChannelEventSink))) { michael@0: NS_ADDREF_THIS(); michael@0: *aResult = static_cast(this); michael@0: return NS_OK; michael@0: } michael@0: michael@0: if (aIID.Equals(NS_GET_IID(nsIRedirectResultListener))) { michael@0: NS_ADDREF_THIS(); michael@0: *aResult = static_cast(this); michael@0: return NS_OK; michael@0: } michael@0: michael@0: return NS_ERROR_NO_INTERFACE; michael@0: } michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: // nsPrefetchNode::nsIChannelEventSink michael@0: //----------------------------------------------------------------------------- michael@0: michael@0: NS_IMETHODIMP michael@0: nsPrefetchNode::AsyncOnChannelRedirect(nsIChannel *aOldChannel, michael@0: nsIChannel *aNewChannel, michael@0: uint32_t aFlags, michael@0: nsIAsyncVerifyRedirectCallback *callback) michael@0: { michael@0: nsCOMPtr newURI; michael@0: nsresult rv = aNewChannel->GetURI(getter_AddRefs(newURI)); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: michael@0: bool match; michael@0: rv = newURI->SchemeIs("http", &match); michael@0: if (NS_FAILED(rv) || !match) { michael@0: rv = newURI->SchemeIs("https", &match); michael@0: if (NS_FAILED(rv) || !match) { michael@0: LOG(("rejected: URL is not of type http/https\n")); michael@0: return NS_ERROR_ABORT; michael@0: } michael@0: } michael@0: michael@0: // HTTP request headers are not automatically forwarded to the new channel. michael@0: nsCOMPtr httpChannel = do_QueryInterface(aNewChannel); michael@0: NS_ENSURE_STATE(httpChannel); michael@0: michael@0: httpChannel->SetRequestHeader(NS_LITERAL_CSTRING("X-Moz"), michael@0: NS_LITERAL_CSTRING("prefetch"), michael@0: false); michael@0: michael@0: // Assign to mChannel after we get notification about success of the michael@0: // redirect in OnRedirectResult. michael@0: mRedirectChannel = aNewChannel; michael@0: michael@0: callback->OnRedirectVerifyCallback(NS_OK); michael@0: return NS_OK; michael@0: } michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: // nsPrefetchNode::nsIRedirectResultListener michael@0: //----------------------------------------------------------------------------- michael@0: michael@0: NS_IMETHODIMP michael@0: nsPrefetchNode::OnRedirectResult(bool proceeding) michael@0: { michael@0: if (proceeding && mRedirectChannel) michael@0: mChannel = mRedirectChannel; michael@0: michael@0: mRedirectChannel = nullptr; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: // nsPrefetchService michael@0: //----------------------------------------------------------------------------- michael@0: michael@0: nsPrefetchService::nsPrefetchService() michael@0: : mQueueHead(nullptr) michael@0: , mQueueTail(nullptr) michael@0: , mStopCount(0) michael@0: , mHaveProcessed(false) michael@0: , mDisabled(true) michael@0: { michael@0: } michael@0: michael@0: nsPrefetchService::~nsPrefetchService() michael@0: { michael@0: Preferences::RemoveObserver(this, PREFETCH_PREF); michael@0: // cannot reach destructor if prefetch in progress (listener owns reference michael@0: // to this service) michael@0: EmptyQueue(); michael@0: } michael@0: michael@0: nsresult michael@0: nsPrefetchService::Init() michael@0: { michael@0: #if defined(PR_LOGGING) michael@0: if (!gPrefetchLog) michael@0: gPrefetchLog = PR_NewLogModule("nsPrefetch"); michael@0: #endif michael@0: michael@0: nsresult rv; michael@0: michael@0: // read prefs and hook up pref observer michael@0: mDisabled = !Preferences::GetBool(PREFETCH_PREF, !mDisabled); michael@0: Preferences::AddWeakObserver(this, PREFETCH_PREF); michael@0: michael@0: // Observe xpcom-shutdown event michael@0: nsCOMPtr observerService = michael@0: mozilla::services::GetObserverService(); michael@0: if (!observerService) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: rv = observerService->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, true); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: if (!mDisabled) michael@0: AddProgressListener(); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: nsPrefetchService::ProcessNextURI() michael@0: { michael@0: nsresult rv; michael@0: nsCOMPtr uri, referrer; michael@0: michael@0: mCurrentNode = nullptr; michael@0: michael@0: do { michael@0: rv = DequeueNode(getter_AddRefs(mCurrentNode)); michael@0: michael@0: if (NS_FAILED(rv)) break; michael@0: michael@0: #if defined(PR_LOGGING) michael@0: if (LOG_ENABLED()) { michael@0: nsAutoCString spec; michael@0: mCurrentNode->mURI->GetSpec(spec); michael@0: LOG(("ProcessNextURI [%s]\n", spec.get())); michael@0: } michael@0: #endif michael@0: michael@0: // michael@0: // if opening the channel fails, then just skip to the next uri michael@0: // michael@0: nsRefPtr node = mCurrentNode; michael@0: rv = node->OpenChannel(); michael@0: } michael@0: while (NS_FAILED(rv)); michael@0: } michael@0: michael@0: void michael@0: nsPrefetchService::NotifyLoadRequested(nsPrefetchNode *node) michael@0: { michael@0: nsCOMPtr observerService = michael@0: mozilla::services::GetObserverService(); michael@0: if (!observerService) michael@0: return; michael@0: michael@0: observerService->NotifyObservers(static_cast(node), michael@0: "prefetch-load-requested", nullptr); michael@0: } michael@0: michael@0: void michael@0: nsPrefetchService::NotifyLoadCompleted(nsPrefetchNode *node) michael@0: { michael@0: nsCOMPtr observerService = michael@0: mozilla::services::GetObserverService(); michael@0: if (!observerService) michael@0: return; michael@0: michael@0: observerService->NotifyObservers(static_cast(node), michael@0: "prefetch-load-completed", nullptr); michael@0: } michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: // nsPrefetchService michael@0: //----------------------------------------------------------------------------- michael@0: michael@0: void michael@0: nsPrefetchService::AddProgressListener() michael@0: { michael@0: // Register as an observer for the document loader michael@0: nsCOMPtr progress = michael@0: do_GetService(NS_DOCUMENTLOADER_SERVICE_CONTRACTID); michael@0: if (progress) michael@0: progress->AddProgressListener(this, nsIWebProgress::NOTIFY_STATE_DOCUMENT); michael@0: } michael@0: michael@0: void michael@0: nsPrefetchService::RemoveProgressListener() michael@0: { michael@0: // Register as an observer for the document loader michael@0: nsCOMPtr progress = michael@0: do_GetService(NS_DOCUMENTLOADER_SERVICE_CONTRACTID); michael@0: if (progress) michael@0: progress->RemoveProgressListener(this); michael@0: } michael@0: michael@0: nsresult michael@0: nsPrefetchService::EnqueueNode(nsPrefetchNode *aNode) michael@0: { michael@0: NS_ADDREF(aNode); michael@0: michael@0: if (!mQueueTail) { michael@0: mQueueHead = aNode; michael@0: mQueueTail = aNode; michael@0: } michael@0: else { michael@0: mQueueTail->mNext = aNode; michael@0: mQueueTail = aNode; michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsPrefetchService::EnqueueURI(nsIURI *aURI, michael@0: nsIURI *aReferrerURI, michael@0: nsIDOMNode *aSource, michael@0: nsPrefetchNode **aNode) michael@0: { michael@0: nsPrefetchNode *node = new nsPrefetchNode(this, aURI, aReferrerURI, michael@0: aSource); michael@0: if (!node) michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: michael@0: NS_ADDREF(*aNode = node); michael@0: michael@0: return EnqueueNode(node); michael@0: } michael@0: michael@0: nsresult michael@0: nsPrefetchService::DequeueNode(nsPrefetchNode **node) michael@0: { michael@0: if (!mQueueHead) michael@0: return NS_ERROR_NOT_AVAILABLE; michael@0: michael@0: // give the ref to the caller michael@0: *node = mQueueHead; michael@0: mQueueHead = mQueueHead->mNext; michael@0: (*node)->mNext = nullptr; michael@0: michael@0: if (!mQueueHead) michael@0: mQueueTail = nullptr; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: nsPrefetchService::EmptyQueue() michael@0: { michael@0: do { michael@0: nsRefPtr node; michael@0: DequeueNode(getter_AddRefs(node)); michael@0: } while (mQueueHead); michael@0: } michael@0: michael@0: void michael@0: nsPrefetchService::StartPrefetching() michael@0: { michael@0: // michael@0: // at initialization time we might miss the first DOCUMENT START michael@0: // notification, so we have to be careful to avoid letting our michael@0: // stop count go negative. michael@0: // michael@0: if (mStopCount > 0) michael@0: mStopCount--; michael@0: michael@0: LOG(("StartPrefetching [stopcount=%d]\n", mStopCount)); michael@0: michael@0: // only start prefetching after we've received enough DOCUMENT michael@0: // STOP notifications. we do this inorder to defer prefetching michael@0: // until after all sub-frames have finished loading. michael@0: if (mStopCount == 0 && !mCurrentNode) { michael@0: mHaveProcessed = true; michael@0: ProcessNextURI(); michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsPrefetchService::StopPrefetching() michael@0: { michael@0: mStopCount++; michael@0: michael@0: LOG(("StopPrefetching [stopcount=%d]\n", mStopCount)); michael@0: michael@0: // only kill the prefetch queue if we've actually started prefetching. michael@0: if (!mCurrentNode) michael@0: return; michael@0: michael@0: mCurrentNode->CancelChannel(NS_BINDING_ABORTED); michael@0: mCurrentNode = nullptr; michael@0: EmptyQueue(); michael@0: } michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: // nsPrefetchService::nsISupports michael@0: //----------------------------------------------------------------------------- michael@0: michael@0: NS_IMPL_ISUPPORTS(nsPrefetchService, michael@0: nsIPrefetchService, michael@0: nsIWebProgressListener, michael@0: nsIObserver, michael@0: nsISupportsWeakReference) michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: // nsPrefetchService::nsIPrefetchService michael@0: //----------------------------------------------------------------------------- michael@0: michael@0: nsresult michael@0: nsPrefetchService::Prefetch(nsIURI *aURI, michael@0: nsIURI *aReferrerURI, michael@0: nsIDOMNode *aSource, michael@0: bool aExplicit) michael@0: { michael@0: nsresult rv; michael@0: michael@0: NS_ENSURE_ARG_POINTER(aURI); michael@0: NS_ENSURE_ARG_POINTER(aReferrerURI); michael@0: michael@0: #if defined(PR_LOGGING) michael@0: if (LOG_ENABLED()) { michael@0: nsAutoCString spec; michael@0: aURI->GetSpec(spec); michael@0: LOG(("PrefetchURI [%s]\n", spec.get())); michael@0: } michael@0: #endif michael@0: michael@0: if (mDisabled) { michael@0: LOG(("rejected: prefetch service is disabled\n")); michael@0: return NS_ERROR_ABORT; michael@0: } michael@0: michael@0: // michael@0: // XXX we should really be asking the protocol handler if it supports michael@0: // caching, so we can determine if there is any value to prefetching. michael@0: // for now, we'll only prefetch http links since we know that's the michael@0: // most common case. ignore https links since https content only goes michael@0: // into the memory cache. michael@0: // michael@0: // XXX we might want to either leverage nsIProtocolHandler::protocolFlags michael@0: // or possibly nsIRequest::loadFlags to determine if this URI should be michael@0: // prefetched. michael@0: // michael@0: bool match; michael@0: rv = aURI->SchemeIs("http", &match); michael@0: if (NS_FAILED(rv) || !match) { michael@0: rv = aURI->SchemeIs("https", &match); michael@0: if (NS_FAILED(rv) || !match) { michael@0: LOG(("rejected: URL is not of type http/https\n")); michael@0: return NS_ERROR_ABORT; michael@0: } michael@0: } michael@0: michael@0: // michael@0: // the referrer URI must be http: michael@0: // michael@0: rv = aReferrerURI->SchemeIs("http", &match); michael@0: if (NS_FAILED(rv) || !match) { michael@0: rv = aReferrerURI->SchemeIs("https", &match); michael@0: if (NS_FAILED(rv) || !match) { michael@0: LOG(("rejected: referrer URL is neither http nor https\n")); michael@0: return NS_ERROR_ABORT; michael@0: } michael@0: } michael@0: michael@0: // skip URLs that contain query strings, except URLs for which prefetching michael@0: // has been explicitly requested. michael@0: if (!aExplicit) { michael@0: nsCOMPtr url(do_QueryInterface(aURI, &rv)); michael@0: if (NS_FAILED(rv)) return rv; michael@0: nsAutoCString query; michael@0: rv = url->GetQuery(query); michael@0: if (NS_FAILED(rv) || !query.IsEmpty()) { michael@0: LOG(("rejected: URL has a query string\n")); michael@0: return NS_ERROR_ABORT; michael@0: } michael@0: } michael@0: michael@0: // michael@0: // cancel if being prefetched michael@0: // michael@0: if (mCurrentNode) { michael@0: bool equals; michael@0: if (NS_SUCCEEDED(mCurrentNode->mURI->Equals(aURI, &equals)) && equals) { michael@0: LOG(("rejected: URL is already being prefetched\n")); michael@0: return NS_ERROR_ABORT; michael@0: } michael@0: } michael@0: michael@0: // michael@0: // cancel if already on the prefetch queue michael@0: // michael@0: nsPrefetchNode *node = mQueueHead; michael@0: for (; node; node = node->mNext) { michael@0: bool equals; michael@0: if (NS_SUCCEEDED(node->mURI->Equals(aURI, &equals)) && equals) { michael@0: LOG(("rejected: URL is already on prefetch queue\n")); michael@0: return NS_ERROR_ABORT; michael@0: } michael@0: } michael@0: michael@0: nsRefPtr enqueuedNode; michael@0: rv = EnqueueURI(aURI, aReferrerURI, aSource, michael@0: getter_AddRefs(enqueuedNode)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: NotifyLoadRequested(enqueuedNode); michael@0: michael@0: // if there are no pages loading, kick off the request immediately michael@0: if (mStopCount == 0 && mHaveProcessed) michael@0: ProcessNextURI(); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsPrefetchService::PrefetchURI(nsIURI *aURI, michael@0: nsIURI *aReferrerURI, michael@0: nsIDOMNode *aSource, michael@0: bool aExplicit) michael@0: { michael@0: return Prefetch(aURI, aReferrerURI, aSource, aExplicit); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsPrefetchService::EnumerateQueue(nsISimpleEnumerator **aEnumerator) michael@0: { michael@0: *aEnumerator = new nsPrefetchQueueEnumerator(this); michael@0: if (!*aEnumerator) return NS_ERROR_OUT_OF_MEMORY; michael@0: michael@0: NS_ADDREF(*aEnumerator); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: // nsPrefetchService::nsIWebProgressListener michael@0: //----------------------------------------------------------------------------- michael@0: michael@0: NS_IMETHODIMP michael@0: nsPrefetchService::OnProgressChange(nsIWebProgress *aProgress, michael@0: nsIRequest *aRequest, michael@0: int32_t curSelfProgress, michael@0: int32_t maxSelfProgress, michael@0: int32_t curTotalProgress, michael@0: int32_t maxTotalProgress) michael@0: { michael@0: NS_NOTREACHED("notification excluded in AddProgressListener(...)"); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsPrefetchService::OnStateChange(nsIWebProgress* aWebProgress, michael@0: nsIRequest *aRequest, michael@0: uint32_t progressStateFlags, michael@0: nsresult aStatus) michael@0: { michael@0: if (progressStateFlags & STATE_IS_DOCUMENT) { michael@0: if (progressStateFlags & STATE_STOP) michael@0: StartPrefetching(); michael@0: else if (progressStateFlags & STATE_START) michael@0: StopPrefetching(); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: NS_IMETHODIMP michael@0: nsPrefetchService::OnLocationChange(nsIWebProgress* aWebProgress, michael@0: nsIRequest* aRequest, michael@0: nsIURI *location, michael@0: uint32_t aFlags) michael@0: { michael@0: NS_NOTREACHED("notification excluded in AddProgressListener(...)"); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsPrefetchService::OnStatusChange(nsIWebProgress* aWebProgress, michael@0: nsIRequest* aRequest, michael@0: nsresult aStatus, michael@0: const char16_t* aMessage) michael@0: { michael@0: NS_NOTREACHED("notification excluded in AddProgressListener(...)"); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsPrefetchService::OnSecurityChange(nsIWebProgress *aWebProgress, michael@0: nsIRequest *aRequest, michael@0: uint32_t state) michael@0: { michael@0: NS_NOTREACHED("notification excluded in AddProgressListener(...)"); michael@0: return NS_OK; michael@0: } michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: // nsPrefetchService::nsIObserver michael@0: //----------------------------------------------------------------------------- michael@0: michael@0: NS_IMETHODIMP michael@0: nsPrefetchService::Observe(nsISupports *aSubject, michael@0: const char *aTopic, michael@0: const char16_t *aData) michael@0: { michael@0: LOG(("nsPrefetchService::Observe [topic=%s]\n", aTopic)); michael@0: michael@0: if (!strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) { michael@0: StopPrefetching(); michael@0: EmptyQueue(); michael@0: mDisabled = true; michael@0: } michael@0: else if (!strcmp(aTopic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID)) { michael@0: if (Preferences::GetBool(PREFETCH_PREF, false)) { michael@0: if (mDisabled) { michael@0: LOG(("enabling prefetching\n")); michael@0: mDisabled = false; michael@0: AddProgressListener(); michael@0: } michael@0: } michael@0: else { michael@0: if (!mDisabled) { michael@0: LOG(("disabling prefetching\n")); michael@0: StopPrefetching(); michael@0: EmptyQueue(); michael@0: mDisabled = true; michael@0: RemoveProgressListener(); michael@0: } michael@0: } michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: // vim: ts=4 sw=4 expandtab