fune/uriloader/prefetch/nsPrefetchService.cpp
Noemi Erli 4419e20e14 Backed out 12 changesets (bug 1493563) for failures in test_css-logic-getCssPath.html CLOSED TREE
Backed out changeset d2e83655082f (bug 1493563)
Backed out changeset 1ce58f004593 (bug 1493563)
Backed out changeset 344298c73ee7 (bug 1493563)
Backed out changeset 02b8b073f7d7 (bug 1493563)
Backed out changeset 3ef707008502 (bug 1493563)
Backed out changeset bb2720a401fe (bug 1493563)
Backed out changeset ce0211be57a1 (bug 1493563)
Backed out changeset 83d6c2bf8dc6 (bug 1493563)
Backed out changeset 1844af4cc25b (bug 1493563)
Backed out changeset c8ab17addb7a (bug 1493563)
Backed out changeset a1ff0cd62563 (bug 1493563)
Backed out changeset 932b41e211e0 (bug 1493563)
2018-09-28 21:31:18 +03:00

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