forked from mirrors/gecko-dev
		
	 be0c1a4f55
			
		
	
	
		be0c1a4f55
		
	
	
	
	
		
			
			Differential Revision: https://phabricator.services.mozilla.com/D2893 --HG-- extra : moz-landing-system : lando
		
			
				
	
	
		
			1123 lines
		
	
	
	
		
			37 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			1123 lines
		
	
	
	
		
			37 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 | |
| /* This Source Code Form is subject to the terms of the Mozilla Public
 | |
|  * License, v. 2.0. If a copy of the MPL was not distributed with this
 | |
|  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 | |
| 
 | |
| #include "nsPrefetchService.h"
 | |
| 
 | |
| #include "mozilla/AsyncEventDispatcher.h"
 | |
| #include "mozilla/Attributes.h"
 | |
| #include "mozilla/CORSMode.h"
 | |
| #include "mozilla/dom/ClientInfo.h"
 | |
| #include "mozilla/dom/HTMLLinkElement.h"
 | |
| #include "mozilla/dom/ServiceWorkerDescriptor.h"
 | |
| #include "mozilla/Preferences.h"
 | |
| 
 | |
| #include "nsICacheEntry.h"
 | |
| #include "nsIServiceManager.h"
 | |
| #include "nsICategoryManager.h"
 | |
| #include "nsIObserverService.h"
 | |
| #include "nsIWebProgress.h"
 | |
| #include "nsCURILoader.h"
 | |
| #include "nsICacheInfoChannel.h"
 | |
| #include "nsIHttpChannel.h"
 | |
| #include "nsIURL.h"
 | |
| #include "nsISimpleEnumerator.h"
 | |
| #include "nsISupportsPriority.h"
 | |
| #include "nsNetUtil.h"
 | |
| #include "nsString.h"
 | |
| #include "nsReadableUtils.h"
 | |
| #include "nsStreamUtils.h"
 | |
| #include "nsAutoPtr.h"
 | |
| #include "prtime.h"
 | |
| #include "mozilla/Logging.h"
 | |
| #include "plstr.h"
 | |
| #include "nsIAsyncVerifyRedirectCallback.h"
 | |
| #include "nsINode.h"
 | |
| #include "nsIDocument.h"
 | |
| #include "nsContentUtils.h"
 | |
| #include "nsStyleLinkElement.h"
 | |
| #include "mozilla/AsyncEventDispatcher.h"
 | |
| 
 | |
| using namespace mozilla;
 | |
| 
 | |
| //
 | |
| // To enable logging (see mozilla/Logging.h for full details):
 | |
| //
 | |
| //    set MOZ_LOG=nsPrefetch:5
 | |
| //    set MOZ_LOG_FILE=prefetch.log
 | |
| //
 | |
| // this enables LogLevel::Debug level information and places all output in
 | |
| // the file prefetch.log
 | |
| //
 | |
| static LazyLogModule gPrefetchLog("nsPrefetch");
 | |
| 
 | |
| #undef LOG
 | |
| #define LOG(args) MOZ_LOG(gPrefetchLog, mozilla::LogLevel::Debug, args)
 | |
| 
 | |
| #undef LOG_ENABLED
 | |
| #define LOG_ENABLED() MOZ_LOG_TEST(gPrefetchLog, mozilla::LogLevel::Debug)
 | |
| 
 | |
| #define PREFETCH_PREF "network.prefetch-next"
 | |
| #define PRELOAD_PREF "network.preload"
 | |
| #define PARALLELISM_PREF "network.prefetch-next.parallelism"
 | |
| #define AGGRESSIVE_PREF "network.prefetch-next.aggressive"
 | |
| 
 | |
| //-----------------------------------------------------------------------------
 | |
| // helpers
 | |
| //-----------------------------------------------------------------------------
 | |
| 
 | |
| static inline uint32_t
 | |
| PRTimeToSeconds(PRTime t_usec)
 | |
| {
 | |
|     PRTime usec_per_sec = PR_USEC_PER_SEC;
 | |
|     return uint32_t(t_usec /= usec_per_sec);
 | |
| }
 | |
| 
 | |
| #define NowInSeconds() PRTimeToSeconds(PR_Now())
 | |
| 
 | |
| //-----------------------------------------------------------------------------
 | |
| // nsPrefetchNode <public>
 | |
| //-----------------------------------------------------------------------------
 | |
| 
 | |
| nsPrefetchNode::nsPrefetchNode(nsPrefetchService *aService,
 | |
|                                nsIURI *aURI,
 | |
|                                nsIURI *aReferrerURI,
 | |
|                                nsINode *aSource,
 | |
|                                nsContentPolicyType aPolicyType,
 | |
|                                bool aPreload)
 | |
|     : mURI(aURI)
 | |
|     , mReferrerURI(aReferrerURI)
 | |
|     , mPolicyType(aPolicyType)
 | |
|     , mPreload(aPreload)
 | |
|     , mService(aService)
 | |
|     , mChannel(nullptr)
 | |
|     , mBytesRead(0)
 | |
|     , mShouldFireLoadEvent(false)
 | |
| {
 | |
|     nsCOMPtr<nsIWeakReference> source = do_GetWeakReference(aSource);
 | |
|     mSources.AppendElement(source);
 | |
| }
 | |
| 
 | |
| nsresult
 | |
| nsPrefetchNode::OpenChannel()
 | |
| {
 | |
|     if (mSources.IsEmpty()) {
 | |
|         // Don't attempt to prefetch if we don't have a source node
 | |
|         // (which should never happen).
 | |
|         return NS_ERROR_FAILURE;
 | |
|     }
 | |
|     nsCOMPtr<nsINode> source;
 | |
|     while (!mSources.IsEmpty() && !(source = do_QueryReferent(mSources.ElementAt(0)))) {
 | |
|         // If source is null remove it.
 | |
|         // (which should never happen).
 | |
|         mSources.RemoveElementAt(0);
 | |
|     }
 | |
| 
 | |
|     if (!source) {
 | |
|         // Don't attempt to prefetch if we don't have a source node
 | |
|         // (which should never happen).
 | |
| 
 | |
|         return NS_ERROR_FAILURE;
 | |
|     }
 | |
|     nsCOMPtr<nsILoadGroup> loadGroup = source->OwnerDoc()->GetDocumentLoadGroup();
 | |
|     CORSMode corsMode = CORS_NONE;
 | |
|     net::ReferrerPolicy referrerPolicy = net::RP_Unset;
 | |
|     if (auto* link = dom::HTMLLinkElement::FromNode(source)) {
 | |
|       corsMode = link->GetCORSMode();
 | |
|       referrerPolicy = link->GetReferrerPolicyAsEnum();
 | |
|     }
 | |
| 
 | |
|     if (referrerPolicy == net::RP_Unset) {
 | |
|       referrerPolicy = source->OwnerDoc()->GetReferrerPolicy();
 | |
|     }
 | |
| 
 | |
|     uint32_t securityFlags;
 | |
|     if (corsMode == CORS_NONE) {
 | |
|       securityFlags = nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_INHERITS;
 | |
|     } else {
 | |
|       securityFlags = nsILoadInfo::SEC_REQUIRE_CORS_DATA_INHERITS;
 | |
|       if (corsMode == CORS_USE_CREDENTIALS) {
 | |
|         securityFlags |= nsILoadInfo::SEC_COOKIES_INCLUDE;
 | |
|       }
 | |
|     }
 | |
|     nsresult rv = NS_NewChannelInternal(getter_AddRefs(mChannel),
 | |
|                                         mURI,
 | |
|                                         source,
 | |
|                                         source->NodePrincipal(),
 | |
|                                         nullptr,   //aTriggeringPrincipal
 | |
|                                         Maybe<ClientInfo>(),
 | |
|                                         Maybe<ServiceWorkerDescriptor>(),
 | |
|                                         securityFlags,
 | |
|                                         mPolicyType,
 | |
|                                         nullptr,   // aPerformanceStorage
 | |
|                                         loadGroup, // aLoadGroup
 | |
|                                         this,      // aCallbacks
 | |
|                                         nsIRequest::LOAD_BACKGROUND |
 | |
|                                         nsICachingChannel::LOAD_ONLY_IF_MODIFIED);
 | |
| 
 | |
|     NS_ENSURE_SUCCESS(rv, rv);
 | |
| 
 | |
|     // configure HTTP specific stuff
 | |
|     nsCOMPtr<nsIHttpChannel> httpChannel =
 | |
|         do_QueryInterface(mChannel);
 | |
|     if (httpChannel) {
 | |
|         rv = httpChannel->SetReferrerWithPolicy(mReferrerURI, referrerPolicy);
 | |
|         MOZ_ASSERT(NS_SUCCEEDED(rv));
 | |
|         rv = httpChannel->SetRequestHeader(NS_LITERAL_CSTRING("X-Moz"),
 | |
|                                            NS_LITERAL_CSTRING("prefetch"),
 | |
|                                            false);
 | |
|         MOZ_ASSERT(NS_SUCCEEDED(rv));
 | |
|     }
 | |
| 
 | |
|     // Reduce the priority of prefetch network requests.
 | |
|     nsCOMPtr<nsISupportsPriority> priorityChannel = do_QueryInterface(mChannel);
 | |
|     if (priorityChannel) {
 | |
|       priorityChannel->AdjustPriority(nsISupportsPriority::PRIORITY_LOWEST);
 | |
|     }
 | |
| 
 | |
|     rv = mChannel->AsyncOpen2(this);
 | |
|     if (NS_WARN_IF(NS_FAILED(rv))) {
 | |
|       // Drop the ref to the channel, because we don't want to end up with
 | |
|       // cycles through it.
 | |
|       mChannel = nullptr;
 | |
|     }
 | |
|     return rv;
 | |
| }
 | |
| 
 | |
| nsresult
 | |
| nsPrefetchNode::CancelChannel(nsresult error)
 | |
| {
 | |
|     mChannel->Cancel(error);
 | |
|     mChannel = nullptr;
 | |
| 
 | |
|     return NS_OK;
 | |
| }
 | |
| 
 | |
| //-----------------------------------------------------------------------------
 | |
| // nsPrefetchNode::nsISupports
 | |
| //-----------------------------------------------------------------------------
 | |
| 
 | |
| NS_IMPL_ISUPPORTS(nsPrefetchNode,
 | |
|                   nsIRequestObserver,
 | |
|                   nsIStreamListener,
 | |
|                   nsIInterfaceRequestor,
 | |
|                   nsIChannelEventSink,
 | |
|                   nsIRedirectResultListener)
 | |
| 
 | |
| //-----------------------------------------------------------------------------
 | |
| // nsPrefetchNode::nsIStreamListener
 | |
| //-----------------------------------------------------------------------------
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| nsPrefetchNode::OnStartRequest(nsIRequest *aRequest,
 | |
|                                nsISupports *aContext)
 | |
| {
 | |
|     nsresult rv;
 | |
| 
 | |
|     nsCOMPtr<nsIHttpChannel> httpChannel =
 | |
|         do_QueryInterface(aRequest, &rv);
 | |
|     if (NS_FAILED(rv)) return rv;
 | |
| 
 | |
|     // if the load is cross origin without CORS, or the CORS access is rejected,
 | |
|     // always fire load event to avoid leaking site information.
 | |
|     nsCOMPtr<nsILoadInfo> loadInfo = httpChannel->GetLoadInfo();
 | |
|     if (loadInfo) {
 | |
|         mShouldFireLoadEvent = loadInfo->GetTainting() == LoadTainting::Opaque ||
 | |
|                                (loadInfo->GetTainting() == LoadTainting::CORS &&
 | |
|                                 (NS_FAILED(httpChannel->GetStatus(&rv)) ||
 | |
|                                  NS_FAILED(rv)));
 | |
|     }
 | |
| 
 | |
|     // no need to prefetch http error page
 | |
|     bool requestSucceeded;
 | |
|     if (NS_FAILED(httpChannel->GetRequestSucceeded(&requestSucceeded)) ||
 | |
|         !requestSucceeded) {
 | |
|       return NS_BINDING_ABORTED;
 | |
|     }
 | |
| 
 | |
|     nsCOMPtr<nsICacheInfoChannel> cacheInfoChannel =
 | |
|         do_QueryInterface(aRequest, &rv);
 | |
|     if (NS_FAILED(rv)) return rv;
 | |
|  
 | |
|     // no need to prefetch a document that is already in the cache
 | |
|     bool fromCache;
 | |
|     if (NS_SUCCEEDED(cacheInfoChannel->IsFromCache(&fromCache)) &&
 | |
|         fromCache) {
 | |
|         LOG(("document is already in the cache; canceling prefetch\n"));
 | |
|         // although it's canceled we still want to fire load event
 | |
|         mShouldFireLoadEvent = true;
 | |
|         return NS_BINDING_ABORTED;
 | |
|     }
 | |
| 
 | |
|     //
 | |
|     // no need to prefetch a document that must be requested fresh each
 | |
|     // and every time.
 | |
|     //
 | |
|     uint32_t expTime;
 | |
|     if (NS_SUCCEEDED(cacheInfoChannel->GetCacheTokenExpirationTime(&expTime))) {
 | |
|         if (NowInSeconds() >= expTime) {
 | |
|             LOG(("document cannot be reused from cache; "
 | |
|                  "canceling prefetch\n"));
 | |
|             return NS_BINDING_ABORTED;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     return NS_OK;
 | |
| }
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| nsPrefetchNode::OnDataAvailable(nsIRequest *aRequest,
 | |
|                                 nsISupports *aContext,
 | |
|                                 nsIInputStream *aStream,
 | |
|                                 uint64_t aOffset,
 | |
|                                 uint32_t aCount)
 | |
| {
 | |
|     uint32_t bytesRead = 0;
 | |
|     aStream->ReadSegments(NS_DiscardSegment, nullptr, aCount, &bytesRead);
 | |
|     mBytesRead += bytesRead;
 | |
|     LOG(("prefetched %u bytes [offset=%" PRIu64 "]\n", bytesRead, aOffset));
 | |
|     return NS_OK;
 | |
| }
 | |
| 
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| nsPrefetchNode::OnStopRequest(nsIRequest *aRequest,
 | |
|                               nsISupports *aContext,
 | |
|                               nsresult aStatus)
 | |
| {
 | |
|     LOG(("done prefetching [status=%" PRIx32 "]\n", static_cast<uint32_t>(aStatus)));
 | |
| 
 | |
|     if (mBytesRead == 0 && aStatus == NS_OK && mChannel) {
 | |
|         // we didn't need to read (because LOAD_ONLY_IF_MODIFIED was
 | |
|         // specified), but the object should report loadedSize as if it
 | |
|         // did.
 | |
|         mChannel->GetContentLength(&mBytesRead);
 | |
|     }
 | |
| 
 | |
|     mService->NotifyLoadCompleted(this);
 | |
|     mService->DispatchEvent(this, mShouldFireLoadEvent || NS_SUCCEEDED(aStatus));
 | |
|     mService->RemoveNodeAndMaybeStartNextPrefetchURI(this);
 | |
|     return NS_OK;
 | |
| }
 | |
| 
 | |
| //-----------------------------------------------------------------------------
 | |
| // nsPrefetchNode::nsIInterfaceRequestor
 | |
| //-----------------------------------------------------------------------------
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| nsPrefetchNode::GetInterface(const nsIID &aIID, void **aResult)
 | |
| {
 | |
|     if (aIID.Equals(NS_GET_IID(nsIChannelEventSink))) {
 | |
|         NS_ADDREF_THIS();
 | |
|         *aResult = static_cast<nsIChannelEventSink *>(this);
 | |
|         return NS_OK;
 | |
|     }
 | |
| 
 | |
|     if (aIID.Equals(NS_GET_IID(nsIRedirectResultListener))) {
 | |
|         NS_ADDREF_THIS();
 | |
|         *aResult = static_cast<nsIRedirectResultListener *>(this);
 | |
|         return NS_OK;
 | |
|     }
 | |
| 
 | |
|     return NS_ERROR_NO_INTERFACE;
 | |
| }
 | |
| 
 | |
| //-----------------------------------------------------------------------------
 | |
| // nsPrefetchNode::nsIChannelEventSink
 | |
| //-----------------------------------------------------------------------------
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| nsPrefetchNode::AsyncOnChannelRedirect(nsIChannel *aOldChannel,
 | |
|                                        nsIChannel *aNewChannel,
 | |
|                                        uint32_t aFlags,
 | |
|                                        nsIAsyncVerifyRedirectCallback *callback)
 | |
| {
 | |
|     nsCOMPtr<nsIURI> newURI;
 | |
|     nsresult rv = aNewChannel->GetURI(getter_AddRefs(newURI));
 | |
|     if (NS_FAILED(rv))
 | |
|         return rv;
 | |
| 
 | |
|     bool match;
 | |
|     rv = newURI->SchemeIs("http", &match); 
 | |
|     if (NS_FAILED(rv) || !match) {
 | |
|         rv = newURI->SchemeIs("https", &match); 
 | |
|         if (NS_FAILED(rv) || !match) {
 | |
|             LOG(("rejected: URL is not of type http/https\n"));
 | |
|             return NS_ERROR_ABORT;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     // HTTP request headers are not automatically forwarded to the new channel.
 | |
|     nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aNewChannel);
 | |
|     NS_ENSURE_STATE(httpChannel);
 | |
| 
 | |
|     rv = httpChannel->SetRequestHeader(NS_LITERAL_CSTRING("X-Moz"),
 | |
|                                        NS_LITERAL_CSTRING("prefetch"),
 | |
|                                        false);
 | |
|     MOZ_ASSERT(NS_SUCCEEDED(rv));
 | |
| 
 | |
|     // Assign to mChannel after we get notification about success of the
 | |
|     // redirect in OnRedirectResult.
 | |
|     mRedirectChannel = aNewChannel;
 | |
| 
 | |
|     callback->OnRedirectVerifyCallback(NS_OK);
 | |
|     return NS_OK;
 | |
| }
 | |
| 
 | |
| //-----------------------------------------------------------------------------
 | |
| // nsPrefetchNode::nsIRedirectResultListener
 | |
| //-----------------------------------------------------------------------------
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| nsPrefetchNode::OnRedirectResult(bool proceeding)
 | |
| {
 | |
|     if (proceeding && mRedirectChannel)
 | |
|         mChannel = mRedirectChannel;
 | |
| 
 | |
|     mRedirectChannel = nullptr;
 | |
| 
 | |
|     return NS_OK;
 | |
| }
 | |
| 
 | |
| //-----------------------------------------------------------------------------
 | |
| // nsPrefetchService <public>
 | |
| //-----------------------------------------------------------------------------
 | |
| 
 | |
| nsPrefetchService::nsPrefetchService()
 | |
|     : mMaxParallelism(6)
 | |
|     , mStopCount(0)
 | |
|     , mHaveProcessed(false)
 | |
|     , mPrefetchDisabled(true)
 | |
|     , mPreloadDisabled(true)
 | |
|     , mAggressive(false)
 | |
| {
 | |
| }
 | |
| 
 | |
| nsPrefetchService::~nsPrefetchService()
 | |
| {
 | |
|     Preferences::RemoveObserver(this, PREFETCH_PREF);
 | |
|     Preferences::RemoveObserver(this, PRELOAD_PREF);
 | |
|     Preferences::RemoveObserver(this, PARALLELISM_PREF);
 | |
|     Preferences::RemoveObserver(this, AGGRESSIVE_PREF);
 | |
|     // cannot reach destructor if prefetch in progress (listener owns reference
 | |
|     // to this service)
 | |
|     EmptyPrefetchQueue();
 | |
| }
 | |
| 
 | |
| nsresult
 | |
| nsPrefetchService::Init()
 | |
| {
 | |
|     nsresult rv;
 | |
| 
 | |
|     // read prefs and hook up pref observer
 | |
|     mPrefetchDisabled = !Preferences::GetBool(PREFETCH_PREF, !mPrefetchDisabled);
 | |
|     Preferences::AddWeakObserver(this, PREFETCH_PREF);
 | |
| 
 | |
|     mPreloadDisabled = !Preferences::GetBool(PRELOAD_PREF, !mPreloadDisabled);
 | |
|     Preferences::AddWeakObserver(this, PRELOAD_PREF);
 | |
| 
 | |
|     mMaxParallelism = Preferences::GetInt(PARALLELISM_PREF, mMaxParallelism);
 | |
|     if (mMaxParallelism < 1) {
 | |
|         mMaxParallelism = 1;
 | |
|     }
 | |
|     Preferences::AddWeakObserver(this, PARALLELISM_PREF);
 | |
| 
 | |
|     mAggressive = Preferences::GetBool(AGGRESSIVE_PREF, false);
 | |
|     Preferences::AddWeakObserver(this, AGGRESSIVE_PREF);
 | |
| 
 | |
|     // Observe xpcom-shutdown event
 | |
|     nsCOMPtr<nsIObserverService> observerService =
 | |
|       mozilla::services::GetObserverService();
 | |
|     if (!observerService)
 | |
|       return NS_ERROR_FAILURE;
 | |
| 
 | |
|     rv = observerService->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, true);
 | |
|     NS_ENSURE_SUCCESS(rv, rv);
 | |
| 
 | |
|     if (!mPrefetchDisabled || !mPreloadDisabled) {
 | |
|         AddProgressListener();
 | |
|     }
 | |
| 
 | |
|     return NS_OK;
 | |
| }
 | |
| 
 | |
| void
 | |
| nsPrefetchService::RemoveNodeAndMaybeStartNextPrefetchURI(nsPrefetchNode *aFinished)
 | |
| {
 | |
|     if (aFinished) {
 | |
|         mCurrentNodes.RemoveElement(aFinished);
 | |
|     }
 | |
| 
 | |
|     if ((!mStopCount && mHaveProcessed) || mAggressive) {
 | |
|         ProcessNextPrefetchURI();
 | |
|     }
 | |
| }
 | |
| 
 | |
| void
 | |
| nsPrefetchService::ProcessNextPrefetchURI()
 | |
| {
 | |
|     if (mCurrentNodes.Length() >= static_cast<uint32_t>(mMaxParallelism)) {
 | |
|         // We already have enough prefetches going on, so hold off
 | |
|         // for now.
 | |
|         return;
 | |
|     }
 | |
| 
 | |
|     nsresult rv;
 | |
| 
 | |
|     do {
 | |
|         if (mPrefetchQueue.empty()) {
 | |
|           break;
 | |
|         }
 | |
|         RefPtr<nsPrefetchNode> node = mPrefetchQueue.front().forget();
 | |
|         mPrefetchQueue.pop_front();
 | |
| 
 | |
|         if (LOG_ENABLED()) {
 | |
|             LOG(("ProcessNextPrefetchURI [%s]\n",
 | |
|                  node->mURI->GetSpecOrDefault().get())); }
 | |
| 
 | |
|         //
 | |
|         // if opening the channel fails (e.g. security check returns an error),
 | |
|         // send an error event and then just skip to the next uri
 | |
|         //
 | |
|         rv = node->OpenChannel();
 | |
|         if (NS_SUCCEEDED(rv)) {
 | |
|             mCurrentNodes.AppendElement(node);
 | |
|         } else {
 | |
|           DispatchEvent(node, false);
 | |
|         }
 | |
|     }
 | |
|     while (NS_FAILED(rv));
 | |
| }
 | |
| 
 | |
| void
 | |
| nsPrefetchService::NotifyLoadRequested(nsPrefetchNode *node)
 | |
| {
 | |
|     nsCOMPtr<nsIObserverService> observerService =
 | |
|       mozilla::services::GetObserverService();
 | |
|     if (!observerService)
 | |
|       return;
 | |
| 
 | |
|     observerService->NotifyObservers(static_cast<nsIStreamListener*>(node),
 | |
|                                      (node->mPreload) ? "preload-load-requested"
 | |
|                                                       : "prefetch-load-requested",
 | |
|                                      nullptr);
 | |
| }
 | |
| 
 | |
| void
 | |
| nsPrefetchService::NotifyLoadCompleted(nsPrefetchNode *node)
 | |
| {
 | |
|     nsCOMPtr<nsIObserverService> observerService =
 | |
|       mozilla::services::GetObserverService();
 | |
|     if (!observerService)
 | |
|       return;
 | |
| 
 | |
|     observerService->NotifyObservers(static_cast<nsIStreamListener*>(node),
 | |
|                                      (node->mPreload) ? "preload-load-completed"
 | |
|                                                       : "prefetch-load-completed",
 | |
|                                      nullptr);
 | |
| }
 | |
| 
 | |
| void
 | |
| nsPrefetchService::DispatchEvent(nsPrefetchNode *node, bool aSuccess)
 | |
| {
 | |
|     for (uint32_t i = 0; i < node->mSources.Length(); i++) {
 | |
|       nsCOMPtr<nsINode> domNode = do_QueryReferent(node->mSources.ElementAt(i));
 | |
|       if (domNode && domNode->IsInComposedDoc()) {
 | |
|         // We don't dispatch synchronously since |node| might be in a DocGroup
 | |
|         // that we're not allowed to touch. (Our network request happens in the
 | |
|         // DocGroup of one of the mSources nodes--not necessarily this one).
 | |
|         RefPtr<AsyncEventDispatcher> dispatcher =
 | |
|           new AsyncEventDispatcher(domNode,
 | |
|                                    aSuccess ?
 | |
|                                     NS_LITERAL_STRING("load") :
 | |
|                                     NS_LITERAL_STRING("error"),
 | |
|                                    CanBubble::eNo);
 | |
|         dispatcher->RequireNodeInDocument();
 | |
|         dispatcher->PostDOMEvent();
 | |
|       }
 | |
|     }
 | |
| }
 | |
| 
 | |
| //-----------------------------------------------------------------------------
 | |
| // nsPrefetchService <private>
 | |
| //-----------------------------------------------------------------------------
 | |
| 
 | |
| void
 | |
| nsPrefetchService::AddProgressListener()
 | |
| {
 | |
|     // Register as an observer for the document loader  
 | |
|     nsCOMPtr<nsIWebProgress> progress = 
 | |
|         do_GetService(NS_DOCUMENTLOADER_SERVICE_CONTRACTID);
 | |
|     if (progress)
 | |
|         progress->AddProgressListener(this, nsIWebProgress::NOTIFY_STATE_DOCUMENT);
 | |
| }
 | |
| 
 | |
| void
 | |
| nsPrefetchService::RemoveProgressListener()
 | |
| {
 | |
|     // Register as an observer for the document loader  
 | |
|     nsCOMPtr<nsIWebProgress> progress =
 | |
|         do_GetService(NS_DOCUMENTLOADER_SERVICE_CONTRACTID);
 | |
|     if (progress)
 | |
|         progress->RemoveProgressListener(this);
 | |
| }
 | |
| 
 | |
| nsresult
 | |
| nsPrefetchService::EnqueueURI(nsIURI *aURI,
 | |
|                               nsIURI *aReferrerURI,
 | |
|                               nsINode *aSource,
 | |
|                               nsPrefetchNode **aNode)
 | |
| {
 | |
|     RefPtr<nsPrefetchNode> node = new nsPrefetchNode(this, aURI, aReferrerURI,
 | |
|                                                      aSource,
 | |
|                                                      nsIContentPolicy::TYPE_OTHER,
 | |
|                                                      false);
 | |
|     mPrefetchQueue.push_back(node);
 | |
|     node.forget(aNode);
 | |
|     return NS_OK;
 | |
| }
 | |
| 
 | |
| void
 | |
| nsPrefetchService::EmptyPrefetchQueue()
 | |
| {
 | |
|     while (!mPrefetchQueue.empty()) {
 | |
|         mPrefetchQueue.pop_back();
 | |
|     }
 | |
| }
 | |
| 
 | |
| void
 | |
| nsPrefetchService::StartPrefetching()
 | |
| {
 | |
|     //
 | |
|     // at initialization time we might miss the first DOCUMENT START
 | |
|     // notification, so we have to be careful to avoid letting our
 | |
|     // stop count go negative.
 | |
|     //
 | |
|     if (mStopCount > 0)
 | |
|         mStopCount--;
 | |
| 
 | |
|     LOG(("StartPrefetching [stopcount=%d]\n", mStopCount));
 | |
| 
 | |
|     // only start prefetching after we've received enough DOCUMENT
 | |
|     // STOP notifications.  we do this inorder to defer prefetching
 | |
|     // until after all sub-frames have finished loading.
 | |
|     if (!mStopCount) {
 | |
|         mHaveProcessed = true;
 | |
|         while (!mPrefetchQueue.empty() &&
 | |
|                mCurrentNodes.Length() < static_cast<uint32_t>(mMaxParallelism)) {
 | |
|             ProcessNextPrefetchURI();
 | |
|         }
 | |
|     }
 | |
| }
 | |
| 
 | |
| void
 | |
| nsPrefetchService::StopPrefetching()
 | |
| {
 | |
|     mStopCount++;
 | |
| 
 | |
|     LOG(("StopPrefetching [stopcount=%d]\n", mStopCount));
 | |
| 
 | |
|     // When we start a load, we need to stop all prefetches that has been
 | |
|     // added by the old load, therefore call StopAll only at the moment we
 | |
|     // switch to a new page load (i.e. mStopCount == 1).
 | |
|     // TODO: do not stop prefetches that are relevant for the new load.
 | |
|     if (mStopCount == 1) {
 | |
|         StopAll();
 | |
|     }
 | |
| }
 | |
| 
 | |
| void
 | |
| nsPrefetchService::StopCurrentPrefetchsPreloads(bool aPreload)
 | |
| {
 | |
|     for (int32_t i = mCurrentNodes.Length() - 1; i >= 0; --i) {
 | |
|         if (mCurrentNodes[i]->mPreload == aPreload) {
 | |
|             mCurrentNodes[i]->CancelChannel(NS_BINDING_ABORTED);
 | |
|             mCurrentNodes.RemoveElementAt(i);
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     if (!aPreload) {
 | |
|         EmptyPrefetchQueue();
 | |
|     }
 | |
| }
 | |
| 
 | |
| void
 | |
| nsPrefetchService::StopAll()
 | |
| {
 | |
|     for (uint32_t i = 0; i < mCurrentNodes.Length(); ++i) {
 | |
|         mCurrentNodes[i]->CancelChannel(NS_BINDING_ABORTED);
 | |
|     }
 | |
|     mCurrentNodes.Clear();
 | |
|     EmptyPrefetchQueue();
 | |
| }
 | |
| 
 | |
| nsresult
 | |
| nsPrefetchService::CheckURIScheme(nsIURI *aURI, nsIURI *aReferrerURI)
 | |
| {
 | |
|     //
 | |
|     // XXX we should really be asking the protocol handler if it supports
 | |
|     // caching, so we can determine if there is any value to prefetching.
 | |
|     // for now, we'll only prefetch http and https links since we know that's
 | |
|     // the most common case.
 | |
|     //
 | |
|     bool match;
 | |
|     nsresult rv = aURI->SchemeIs("http", &match);
 | |
|     if (NS_FAILED(rv) || !match) {
 | |
|         rv = aURI->SchemeIs("https", &match);
 | |
|         if (NS_FAILED(rv) || !match) {
 | |
|             LOG(("rejected: URL is not of type http/https\n"));
 | |
|             return NS_ERROR_ABORT;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     // 
 | |
|     // the referrer URI must be http:
 | |
|     //
 | |
|     rv = aReferrerURI->SchemeIs("http", &match);
 | |
|     if (NS_FAILED(rv) || !match) {
 | |
|         rv = aReferrerURI->SchemeIs("https", &match);
 | |
|         if (NS_FAILED(rv) || !match) {
 | |
|             LOG(("rejected: referrer URL is neither http nor https\n"));
 | |
|             return NS_ERROR_ABORT;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     return NS_OK;
 | |
| }
 | |
| 
 | |
| //-----------------------------------------------------------------------------
 | |
| // nsPrefetchService::nsISupports
 | |
| //-----------------------------------------------------------------------------
 | |
| 
 | |
| NS_IMPL_ISUPPORTS(nsPrefetchService,
 | |
|                   nsIPrefetchService,
 | |
|                   nsIWebProgressListener,
 | |
|                   nsIObserver,
 | |
|                   nsISupportsWeakReference)
 | |
| 
 | |
| //-----------------------------------------------------------------------------
 | |
| // nsPrefetchService::nsIPrefetchService
 | |
| //-----------------------------------------------------------------------------
 | |
| 
 | |
| nsresult
 | |
| nsPrefetchService::Preload(nsIURI *aURI,
 | |
|                            nsIURI *aReferrerURI,
 | |
|                            nsINode *aSource,
 | |
|                            nsContentPolicyType aPolicyType)
 | |
| {
 | |
|     NS_ENSURE_ARG_POINTER(aURI);
 | |
|     NS_ENSURE_ARG_POINTER(aReferrerURI);
 | |
|     if (LOG_ENABLED()) {
 | |
|         LOG(("PreloadURI [%s]\n", aURI->GetSpecOrDefault().get()));
 | |
|     }
 | |
| 
 | |
|     if (mPreloadDisabled) {
 | |
|         LOG(("rejected: preload service is disabled\n"));
 | |
|         return NS_ERROR_ABORT;
 | |
|     }
 | |
| 
 | |
|     nsresult rv = CheckURIScheme(aURI, aReferrerURI);
 | |
|     if (NS_FAILED(rv)) {
 | |
|         return rv;
 | |
|     }
 | |
| 
 | |
|     // XXX we might want to either leverage nsIProtocolHandler::protocolFlags
 | |
|     // or possibly nsIRequest::loadFlags to determine if this URI should be
 | |
|     // prefetched.
 | |
|     //
 | |
| 
 | |
|     if (aPolicyType == nsIContentPolicy::TYPE_INVALID) {
 | |
|         if (aSource && aSource->IsInComposedDoc()) {
 | |
|             RefPtr<AsyncEventDispatcher> asyncDispatcher =
 | |
|                 new AsyncEventDispatcher(aSource,
 | |
|                                          NS_LITERAL_STRING("error"),
 | |
|                                          CanBubble::eNo,
 | |
|                                          ChromeOnlyDispatch::eNo);
 | |
|             asyncDispatcher->RunDOMEventWhenSafe();
 | |
|         }
 | |
|         return NS_OK;
 | |
|     }
 | |
| 
 | |
|     //
 | |
|     // Check whether it is being preloaded.
 | |
|     //
 | |
|     for (uint32_t i = 0; i < mCurrentNodes.Length(); ++i) {
 | |
|         bool equals;
 | |
|         if ((mCurrentNodes[i]->mPolicyType == aPolicyType) &&
 | |
|             NS_SUCCEEDED(mCurrentNodes[i]->mURI->Equals(aURI, &equals)) &&
 | |
|             equals) {
 | |
|             nsCOMPtr<nsIWeakReference> source = do_GetWeakReference(aSource);
 | |
|             if (mCurrentNodes[i]->mSources.IndexOf(source) ==
 | |
|                 mCurrentNodes[i]->mSources.NoIndex) {
 | |
|                 LOG(("URL is already being preloaded, add a new reference "
 | |
|                      "document\n"));
 | |
|                 mCurrentNodes[i]->mSources.AppendElement(source);
 | |
|                 return NS_OK;
 | |
|             } else {
 | |
|                 LOG(("URL is already being preloaded by this document"));
 | |
|                 return NS_ERROR_ABORT;
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     LOG(("This is a preload, so start loading immediately.\n"));
 | |
|     RefPtr<nsPrefetchNode> enqueuedNode;
 | |
|     enqueuedNode = new nsPrefetchNode(this, aURI, aReferrerURI,
 | |
|                                       aSource, aPolicyType, true);
 | |
| 
 | |
|     NotifyLoadRequested(enqueuedNode);
 | |
|     rv = enqueuedNode->OpenChannel();
 | |
|     if (NS_SUCCEEDED(rv)) {
 | |
|         mCurrentNodes.AppendElement(enqueuedNode);
 | |
|     } else {
 | |
|         if (aSource && aSource->IsInComposedDoc()) {
 | |
|             RefPtr<AsyncEventDispatcher> asyncDispatcher =
 | |
|                 new AsyncEventDispatcher(aSource,
 | |
|                                          NS_LITERAL_STRING("error"),
 | |
|                                          CanBubble::eNo,
 | |
|                                          ChromeOnlyDispatch::eNo);
 | |
|             asyncDispatcher->RunDOMEventWhenSafe();
 | |
|         }
 | |
|     }
 | |
|     return NS_OK;
 | |
| }
 | |
| 
 | |
| nsresult
 | |
| nsPrefetchService::Prefetch(nsIURI *aURI,
 | |
|                             nsIURI *aReferrerURI,
 | |
|                             nsINode *aSource,
 | |
|                             bool aExplicit)
 | |
| {
 | |
|     NS_ENSURE_ARG_POINTER(aURI);
 | |
|     NS_ENSURE_ARG_POINTER(aReferrerURI);
 | |
| 
 | |
|     if (LOG_ENABLED()) {
 | |
|         LOG(("PrefetchURI [%s]\n", aURI->GetSpecOrDefault().get()));
 | |
|     }
 | |
| 
 | |
|     if (mPrefetchDisabled) {
 | |
|         LOG(("rejected: prefetch service is disabled\n"));
 | |
|         return NS_ERROR_ABORT;
 | |
|     }
 | |
| 
 | |
|     nsresult rv = CheckURIScheme(aURI, aReferrerURI);
 | |
|     if (NS_FAILED(rv)) {
 | |
|         return rv;
 | |
|     }
 | |
| 
 | |
|     // XXX we might want to either leverage nsIProtocolHandler::protocolFlags
 | |
|     // or possibly nsIRequest::loadFlags to determine if this URI should be
 | |
|     // prefetched.
 | |
|     //
 | |
| 
 | |
|     // skip URLs that contain query strings, except URLs for which prefetching
 | |
|     // has been explicitly requested.
 | |
|     if (!aExplicit) {
 | |
|         nsCOMPtr<nsIURL> url(do_QueryInterface(aURI, &rv));
 | |
|         if (NS_FAILED(rv)) return rv;
 | |
|         nsAutoCString query;
 | |
|         rv = url->GetQuery(query);
 | |
|         if (NS_FAILED(rv) || !query.IsEmpty()) {
 | |
|             LOG(("rejected: URL has a query string\n"));
 | |
|             return NS_ERROR_ABORT;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     //
 | |
|     // Check whether it is being prefetched.
 | |
|     //
 | |
|     for (uint32_t i = 0; i < mCurrentNodes.Length(); ++i) {
 | |
|         bool equals;
 | |
|         if (NS_SUCCEEDED(mCurrentNodes[i]->mURI->Equals(aURI, &equals)) &&
 | |
|             equals) {
 | |
|             nsCOMPtr<nsIWeakReference> source = do_GetWeakReference(aSource);
 | |
|             if (mCurrentNodes[i]->mSources.IndexOf(source) ==
 | |
|                 mCurrentNodes[i]->mSources.NoIndex) {
 | |
|                 LOG(("URL is already being prefetched, add a new reference "
 | |
|                      "document\n"));
 | |
|                 mCurrentNodes[i]->mSources.AppendElement(source);
 | |
|                 return NS_OK;
 | |
|             } else {
 | |
|                 LOG(("URL is already being prefetched by this document"));
 | |
|                 return NS_ERROR_ABORT;
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     //
 | |
|     // Check whether it is on the prefetch queue.
 | |
|     //
 | |
|     for (std::deque<RefPtr<nsPrefetchNode>>::iterator nodeIt = mPrefetchQueue.begin();
 | |
|          nodeIt != mPrefetchQueue.end(); nodeIt++) {
 | |
|         bool equals;
 | |
|         RefPtr<nsPrefetchNode> node = nodeIt->get();
 | |
|         if (NS_SUCCEEDED(node->mURI->Equals(aURI, &equals)) && equals) {
 | |
|             nsCOMPtr<nsIWeakReference> source = do_GetWeakReference(aSource);
 | |
|             if (node->mSources.IndexOf(source) ==
 | |
|                 node->mSources.NoIndex) {
 | |
|                 LOG(("URL is already being prefetched, add a new reference "
 | |
|                      "document\n"));
 | |
|                 node->mSources.AppendElement(do_GetWeakReference(aSource));
 | |
|                 return NS_OK;
 | |
|             } else {
 | |
|                 LOG(("URL is already being prefetched by this document"));
 | |
|                 return NS_ERROR_ABORT;
 | |
|             }
 | |
| 
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     RefPtr<nsPrefetchNode> enqueuedNode;
 | |
|     rv = EnqueueURI(aURI, aReferrerURI, aSource,
 | |
|                     getter_AddRefs(enqueuedNode));
 | |
|     NS_ENSURE_SUCCESS(rv, rv);
 | |
| 
 | |
|     NotifyLoadRequested(enqueuedNode);
 | |
| 
 | |
|     // if there are no pages loading, kick off the request immediately
 | |
|     if ((!mStopCount && mHaveProcessed) || mAggressive) {
 | |
|         ProcessNextPrefetchURI();
 | |
|     }
 | |
| 
 | |
|     return NS_OK;
 | |
| }
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| nsPrefetchService::CancelPrefetchPreloadURI(nsIURI* aURI,
 | |
|                                             nsINode* aSource)
 | |
| {
 | |
|     NS_ENSURE_ARG_POINTER(aURI);
 | |
| 
 | |
|     if (LOG_ENABLED()) {
 | |
|         LOG(("CancelPrefetchURI [%s]\n", aURI->GetSpecOrDefault().get()));
 | |
|     }
 | |
| 
 | |
|     //
 | |
|     // look in current prefetches
 | |
|     //
 | |
|     for (uint32_t i = 0; i < mCurrentNodes.Length(); ++i) {
 | |
|         bool equals;
 | |
|         if (NS_SUCCEEDED(mCurrentNodes[i]->mURI->Equals(aURI, &equals)) &&
 | |
|             equals) {
 | |
|             nsCOMPtr<nsIWeakReference> source = do_GetWeakReference(aSource);
 | |
|             if (mCurrentNodes[i]->mSources.IndexOf(source) !=
 | |
|                 mCurrentNodes[i]->mSources.NoIndex) {
 | |
|                 mCurrentNodes[i]->mSources.RemoveElement(source);
 | |
|                 if (mCurrentNodes[i]->mSources.IsEmpty()) {
 | |
|                     mCurrentNodes[i]->CancelChannel(NS_BINDING_ABORTED);
 | |
|                     mCurrentNodes.RemoveElementAt(i);
 | |
|                 }
 | |
|                 return NS_OK;
 | |
|             }
 | |
|             return NS_ERROR_FAILURE;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     //
 | |
|     // look into the prefetch queue
 | |
|     //
 | |
|     for (std::deque<RefPtr<nsPrefetchNode>>::iterator nodeIt = mPrefetchQueue.begin();
 | |
|          nodeIt != mPrefetchQueue.end(); nodeIt++) {
 | |
|         bool equals;
 | |
|         RefPtr<nsPrefetchNode> node = nodeIt->get();
 | |
|         if (NS_SUCCEEDED(node->mURI->Equals(aURI, &equals)) && equals) {
 | |
|             nsCOMPtr<nsIWeakReference> source = do_GetWeakReference(aSource);
 | |
|             if (node->mSources.IndexOf(source) !=
 | |
|                 node->mSources.NoIndex) {
 | |
| 
 | |
| #ifdef DEBUG
 | |
|                 int32_t inx = node->mSources.IndexOf(source);
 | |
|                 nsCOMPtr<nsINode> domNode =
 | |
|                     do_QueryReferent(node->mSources.ElementAt(inx));
 | |
|                 MOZ_ASSERT(domNode);
 | |
| #endif
 | |
| 
 | |
|                 node->mSources.RemoveElement(source);
 | |
|                 if (node->mSources.IsEmpty()) {
 | |
|                     mPrefetchQueue.erase(nodeIt);
 | |
|                 }
 | |
|                 return NS_OK;
 | |
|             }
 | |
|             return NS_ERROR_FAILURE;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     // not found!
 | |
|     return NS_ERROR_FAILURE;
 | |
| }
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| nsPrefetchService::PreloadURI(nsIURI *aURI,
 | |
|                               nsIURI *aReferrerURI,
 | |
|                               nsINode *aSource,
 | |
|                               nsContentPolicyType aPolicyType)
 | |
| {
 | |
|     return Preload(aURI, aReferrerURI, aSource, aPolicyType);
 | |
| }
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| nsPrefetchService::PrefetchURI(nsIURI *aURI,
 | |
|                                nsIURI *aReferrerURI,
 | |
|                                nsINode *aSource,
 | |
|                                bool aExplicit)
 | |
| {
 | |
|     return Prefetch(aURI, aReferrerURI, aSource, aExplicit);
 | |
| }
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| nsPrefetchService::HasMoreElements(bool *aHasMore)
 | |
| {
 | |
|     *aHasMore = (mCurrentNodes.Length() || !mPrefetchQueue.empty());
 | |
|     return NS_OK;
 | |
| }
 | |
| 
 | |
| //-----------------------------------------------------------------------------
 | |
| // nsPrefetchService::nsIWebProgressListener
 | |
| //-----------------------------------------------------------------------------
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| nsPrefetchService::OnProgressChange(nsIWebProgress *aProgress,
 | |
|                                     nsIRequest *aRequest, 
 | |
|                                     int32_t curSelfProgress, 
 | |
|                                     int32_t maxSelfProgress, 
 | |
|                                     int32_t curTotalProgress, 
 | |
|                                     int32_t maxTotalProgress)
 | |
| {
 | |
|     MOZ_ASSERT_UNREACHABLE("notification excluded in AddProgressListener(...)");
 | |
|     return NS_OK;
 | |
| }
 | |
| 
 | |
| NS_IMETHODIMP 
 | |
| nsPrefetchService::OnStateChange(nsIWebProgress* aWebProgress, 
 | |
|                                  nsIRequest *aRequest, 
 | |
|                                  uint32_t progressStateFlags, 
 | |
|                                  nsresult aStatus)
 | |
| {
 | |
|     if (progressStateFlags & STATE_IS_DOCUMENT) {
 | |
|         if (progressStateFlags & STATE_STOP)
 | |
|             StartPrefetching();
 | |
|         else if (progressStateFlags & STATE_START)
 | |
|             StopPrefetching();
 | |
|     }
 | |
|             
 | |
|     return NS_OK;
 | |
| }
 | |
| 
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| nsPrefetchService::OnLocationChange(nsIWebProgress* aWebProgress,
 | |
|                                     nsIRequest* aRequest,
 | |
|                                     nsIURI *location,
 | |
|                                     uint32_t aFlags)
 | |
| {
 | |
|     MOZ_ASSERT_UNREACHABLE("notification excluded in AddProgressListener(...)");
 | |
|     return NS_OK;
 | |
| }
 | |
| 
 | |
| NS_IMETHODIMP 
 | |
| nsPrefetchService::OnStatusChange(nsIWebProgress* aWebProgress,
 | |
|                                   nsIRequest* aRequest,
 | |
|                                   nsresult aStatus,
 | |
|                                   const char16_t* aMessage)
 | |
| {
 | |
|     MOZ_ASSERT_UNREACHABLE("notification excluded in AddProgressListener(...)");
 | |
|     return NS_OK;
 | |
| }
 | |
| 
 | |
| NS_IMETHODIMP 
 | |
| nsPrefetchService::OnSecurityChange(nsIWebProgress *aWebProgress, 
 | |
|                                     nsIRequest *aRequest, 
 | |
|                                     uint32_t state)
 | |
| {
 | |
|     MOZ_ASSERT_UNREACHABLE("notification excluded in AddProgressListener(...)");
 | |
|     return NS_OK;
 | |
| }
 | |
| 
 | |
| //-----------------------------------------------------------------------------
 | |
| // nsPrefetchService::nsIObserver
 | |
| //-----------------------------------------------------------------------------
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| nsPrefetchService::Observe(nsISupports     *aSubject,
 | |
|                            const char      *aTopic,
 | |
|                            const char16_t *aData)
 | |
| {
 | |
|     LOG(("nsPrefetchService::Observe [topic=%s]\n", aTopic));
 | |
| 
 | |
|     if (!strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) {
 | |
|         StopAll();
 | |
|         mPrefetchDisabled = true;
 | |
|         mPreloadDisabled = true;
 | |
|     }
 | |
|     else if (!strcmp(aTopic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID)) {
 | |
|         const nsCString converted = NS_ConvertUTF16toUTF8(aData);
 | |
|         const char* pref = converted.get();
 | |
|         if (!strcmp(pref, PREFETCH_PREF)) {
 | |
|             if (Preferences::GetBool(PREFETCH_PREF, false)) {
 | |
|                 if (mPrefetchDisabled) {
 | |
|                     LOG(("enabling prefetching\n"));
 | |
|                     mPrefetchDisabled = false;
 | |
|                     if (mPreloadDisabled) {
 | |
|                         AddProgressListener();
 | |
|                     }
 | |
|                 }
 | |
|             } else {
 | |
|                 if (!mPrefetchDisabled) {
 | |
|                     LOG(("disabling prefetching\n"));
 | |
|                     StopCurrentPrefetchsPreloads(false);
 | |
|                     mPrefetchDisabled = true;
 | |
|                     if (mPreloadDisabled) {
 | |
|                         RemoveProgressListener();
 | |
|                     }
 | |
|                 }
 | |
|             }
 | |
|         } else if (!strcmp(pref, PRELOAD_PREF)) {
 | |
|             if (Preferences::GetBool(PRELOAD_PREF, false)) {
 | |
|                 if (mPreloadDisabled) {
 | |
|                     LOG(("enabling preloading\n"));
 | |
|                     mPreloadDisabled = false;
 | |
|                     if (mPrefetchDisabled) {
 | |
|                         AddProgressListener();
 | |
|                     }
 | |
|                 }
 | |
|             } else {
 | |
|                 if (!mPreloadDisabled) {
 | |
|                   LOG(("disabling preloading\n"));
 | |
|                   StopCurrentPrefetchsPreloads(true);
 | |
|                   mPreloadDisabled = true;
 | |
|                   if (mPrefetchDisabled) {
 | |
|                       RemoveProgressListener();
 | |
|                   }
 | |
|                 }
 | |
|             }
 | |
|         } else if (!strcmp(pref, PARALLELISM_PREF)) {
 | |
|             mMaxParallelism = Preferences::GetInt(PARALLELISM_PREF, mMaxParallelism);
 | |
|             if (mMaxParallelism < 1) {
 | |
|                 mMaxParallelism = 1;
 | |
|             }
 | |
|             // If our parallelism has increased, go ahead and kick off enough
 | |
|             // prefetches to fill up our allowance. If we're now over our
 | |
|             // allowance, we'll just silently let some of them finish to get
 | |
|             // back below our limit.
 | |
|             while (((!mStopCount && mHaveProcessed) || mAggressive) &&
 | |
|                    !mPrefetchQueue.empty() &&
 | |
|                    mCurrentNodes.Length() < static_cast<uint32_t>(mMaxParallelism)) {
 | |
|                 ProcessNextPrefetchURI();
 | |
|             }
 | |
|         } else if (!strcmp(pref, AGGRESSIVE_PREF)) {
 | |
|             mAggressive = Preferences::GetBool(AGGRESSIVE_PREF, false);
 | |
|             // in aggressive mode, start prefetching immediately
 | |
|             if (mAggressive) {
 | |
|                 while (mStopCount && !mPrefetchQueue.empty() &&
 | |
|                        mCurrentNodes.Length() < static_cast<uint32_t>(mMaxParallelism)) {
 | |
|                     ProcessNextPrefetchURI();
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     return NS_OK;
 | |
| }
 | |
| 
 | |
| // vim: ts=4 sw=4 expandtab
 |