/* -*- 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/. */
// // 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");
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).
// Reduce the priority of prefetch network requests.
nsCOMPtr<nsISupportsPriority> priorityChannel = do_QueryInterface(mChannel); if (priorityChannel) {
priorityChannel->AdjustPriority(nsISupportsPriority::PRIORITY_LOWEST);
}
rv = mChannel->AsyncOpen(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;
}
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->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 (mozilla::net::NowInSeconds() >= expTime) {
LOG(
("document cannot be reused from cache; " "canceling prefetch\n")); return NS_BINDING_ABORTED;
}
}
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);
}
if (!newURI->SchemeIs("http") && !newURI->SchemeIs("https")) {
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);
if (!mPrefetchDisabled) {
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 = std::move(mPrefetchQueue.front());
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::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 ? u"load"_ns : u"error"_ns, CanBubble::eNo);
dispatcher->RequireNodeInDocument();
dispatcher->PostDOMEvent();
}
}
}
void nsPrefetchService::AddProgressListener() { // Register as an observer for the document loader
nsCOMPtr<nsIWebProgress> progress = components::DocLoader::Service(); if (progress)
progress->AddProgressListener(this, nsIWebProgress::NOTIFY_STATE_DOCUMENT);
}
void nsPrefetchService::RemoveProgressListener() { // Register as an observer for the document loader
nsCOMPtr<nsIWebProgress> progress = components::DocLoader::Service(); if (progress) progress->RemoveProgressListener(this);
}
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--;
// 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();
}
}
}
// 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,
nsIReferrerInfo* aReferrerInfo) { // // 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. // if (!aURI->SchemeIs("http") && !aURI->SchemeIs("https")) {
LOG(("rejected: URL is not of type http/https\n")); return NS_ERROR_ABORT;
}
// // the referrer URI must be http: //
nsCOMPtr<nsIURI> referrer = aReferrerInfo->GetOriginalReferrer(); if (!referrer) { return NS_ERROR_ABORT;
}
if (!referrer->SchemeIs("http") && !referrer->SchemeIs("https")) {
LOG(("rejected: referrer URL is neither http nor https\n")); return NS_ERROR_ABORT;
}
// 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) {
nsWeakPtr 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) {
nsWeakPtr 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;
}
}
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung ist noch experimentell.