зеркало из https://github.com/mozilla/gecko-dev.git
2555 строки
83 KiB
C++
2555 строки
83 KiB
C++
/* vim: set ts=2 sts=2 et sw=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 <algorithm>
|
|
|
|
#include "Predictor.h"
|
|
|
|
#include "nsAppDirectoryServiceDefs.h"
|
|
#include "nsICacheStorage.h"
|
|
#include "nsICachingChannel.h"
|
|
#include "nsICancelable.h"
|
|
#include "nsIChannel.h"
|
|
#include "nsContentUtils.h"
|
|
#include "nsIDNSService.h"
|
|
#include "mozilla/dom/Document.h"
|
|
#include "nsIFile.h"
|
|
#include "nsIHttpChannel.h"
|
|
#include "nsIInputStream.h"
|
|
#include "nsILoadContext.h"
|
|
#include "nsILoadContextInfo.h"
|
|
#include "nsILoadGroup.h"
|
|
#include "nsINetworkPredictorVerifier.h"
|
|
#include "nsIObserverService.h"
|
|
#include "nsISpeculativeConnect.h"
|
|
#include "nsITimer.h"
|
|
#include "nsIURI.h"
|
|
#include "nsNetUtil.h"
|
|
#include "nsServiceManagerUtils.h"
|
|
#include "nsStreamUtils.h"
|
|
#include "nsString.h"
|
|
#include "nsThreadUtils.h"
|
|
#include "mozilla/Logging.h"
|
|
|
|
#include "mozilla/Preferences.h"
|
|
#include "mozilla/StaticPrefs_network.h"
|
|
#include "mozilla/Telemetry.h"
|
|
|
|
#include "mozilla/net/NeckoCommon.h"
|
|
#include "mozilla/net/NeckoParent.h"
|
|
|
|
#include "LoadContextInfo.h"
|
|
#include "mozilla/ipc/URIUtils.h"
|
|
#include "SerializedLoadContext.h"
|
|
#include "mozilla/net/NeckoChild.h"
|
|
|
|
#include "mozilla/dom/ContentParent.h"
|
|
#include "mozilla/ClearOnShutdown.h"
|
|
|
|
#include "CacheControlParser.h"
|
|
#include "ReferrerInfo.h"
|
|
|
|
using namespace mozilla;
|
|
|
|
namespace mozilla {
|
|
namespace net {
|
|
|
|
Predictor* Predictor::sSelf = nullptr;
|
|
|
|
static LazyLogModule gPredictorLog("NetworkPredictor");
|
|
|
|
#define PREDICTOR_LOG(args) \
|
|
MOZ_LOG(gPredictorLog, mozilla::LogLevel::Debug, args)
|
|
|
|
#define NOW_IN_SECONDS() static_cast<uint32_t>(PR_Now() / PR_USEC_PER_SEC)
|
|
|
|
static const char PREDICTOR_CLEANED_UP_PREF[] = "network.predictor.cleaned-up";
|
|
|
|
// All these time values are in sec
|
|
static const uint32_t ONE_DAY = 86400U;
|
|
static const uint32_t ONE_WEEK = 7U * ONE_DAY;
|
|
static const uint32_t ONE_MONTH = 30U * ONE_DAY;
|
|
static const uint32_t ONE_YEAR = 365U * ONE_DAY;
|
|
|
|
static const uint32_t STARTUP_WINDOW = 5U * 60U; // 5min
|
|
|
|
// Version of metadata entries we expect
|
|
static const uint32_t METADATA_VERSION = 1;
|
|
|
|
// Flags available in entries
|
|
// FLAG_PREFETCHABLE - we have determined that this item is eligible for
|
|
// prefetch
|
|
static const uint32_t FLAG_PREFETCHABLE = 1 << 0;
|
|
|
|
// We save 12 bits in the "flags" section of our metadata for actual flags, the
|
|
// rest are to keep track of a rolling count of which loads a resource has been
|
|
// used on to determine if we can prefetch that resource or not;
|
|
static const uint8_t kRollingLoadOffset = 12;
|
|
static const int32_t kMaxPrefetchRollingLoadCount = 20;
|
|
static const uint32_t kFlagsMask = ((1 << kRollingLoadOffset) - 1);
|
|
|
|
static bool sEsniEnabled = false;
|
|
|
|
// ID Extensions for cache entries
|
|
#define PREDICTOR_ORIGIN_EXTENSION "predictor-origin"
|
|
|
|
// Get the full origin (scheme, host, port) out of a URI (maybe should be part
|
|
// of nsIURI instead?)
|
|
static nsresult ExtractOrigin(nsIURI* uri, nsIURI** originUri) {
|
|
nsAutoCString s;
|
|
nsresult rv = nsContentUtils::GetASCIIOrigin(uri, s);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
return NS_NewURI(originUri, s);
|
|
}
|
|
|
|
// All URIs we get passed *must* be http or https if they're not null. This
|
|
// helps ensure that.
|
|
static bool IsNullOrHttp(nsIURI* uri) {
|
|
if (!uri) {
|
|
return true;
|
|
}
|
|
|
|
return uri->SchemeIs("http") || uri->SchemeIs("https");
|
|
}
|
|
|
|
// Listener for the speculative DNS requests we'll fire off, which just ignores
|
|
// the result (since we're just trying to warm the cache). This also exists to
|
|
// reduce round-trips to the main thread, by being something threadsafe the
|
|
// Predictor can use.
|
|
|
|
NS_IMPL_ISUPPORTS(Predictor::DNSListener, nsIDNSListener);
|
|
|
|
NS_IMETHODIMP
|
|
Predictor::DNSListener::OnLookupComplete(nsICancelable* request,
|
|
nsIDNSRecord* rec, nsresult status) {
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
Predictor::DNSListener::OnLookupByTypeComplete(nsICancelable* request,
|
|
nsIDNSByTypeRecord* res,
|
|
nsresult status) {
|
|
return NS_OK;
|
|
}
|
|
|
|
// Class to proxy important information from the initial predictor call through
|
|
// the cache API and back into the internals of the predictor. We can't use the
|
|
// predictor itself, as it may have multiple actions in-flight, and each action
|
|
// has different parameters.
|
|
NS_IMPL_ISUPPORTS(Predictor::Action, nsICacheEntryOpenCallback);
|
|
|
|
Predictor::Action::Action(bool fullUri, bool predict, Predictor::Reason reason,
|
|
nsIURI* targetURI, nsIURI* sourceURI,
|
|
nsINetworkPredictorVerifier* verifier,
|
|
Predictor* predictor)
|
|
: mFullUri(fullUri),
|
|
mPredict(predict),
|
|
mTargetURI(targetURI),
|
|
mSourceURI(sourceURI),
|
|
mVerifier(verifier),
|
|
mStackCount(0),
|
|
mPredictor(predictor) {
|
|
mStartTime = TimeStamp::Now();
|
|
if (mPredict) {
|
|
mPredictReason = reason.mPredict;
|
|
} else {
|
|
mLearnReason = reason.mLearn;
|
|
}
|
|
}
|
|
|
|
Predictor::Action::Action(bool fullUri, bool predict, Predictor::Reason reason,
|
|
nsIURI* targetURI, nsIURI* sourceURI,
|
|
nsINetworkPredictorVerifier* verifier,
|
|
Predictor* predictor, uint8_t stackCount)
|
|
: mFullUri(fullUri),
|
|
mPredict(predict),
|
|
mTargetURI(targetURI),
|
|
mSourceURI(sourceURI),
|
|
mVerifier(verifier),
|
|
mStackCount(stackCount),
|
|
mPredictor(predictor) {
|
|
mStartTime = TimeStamp::Now();
|
|
if (mPredict) {
|
|
mPredictReason = reason.mPredict;
|
|
} else {
|
|
mLearnReason = reason.mLearn;
|
|
}
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
Predictor::Action::OnCacheEntryCheck(nsICacheEntry* entry,
|
|
nsIApplicationCache* appCache,
|
|
uint32_t* result) {
|
|
*result = nsICacheEntryOpenCallback::ENTRY_WANTED;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
Predictor::Action::OnCacheEntryAvailable(nsICacheEntry* entry, bool isNew,
|
|
nsIApplicationCache* appCache,
|
|
nsresult result) {
|
|
MOZ_ASSERT(NS_IsMainThread(), "Got cache entry off main thread!");
|
|
|
|
nsAutoCString targetURI, sourceURI;
|
|
mTargetURI->GetAsciiSpec(targetURI);
|
|
if (mSourceURI) {
|
|
mSourceURI->GetAsciiSpec(sourceURI);
|
|
}
|
|
PREDICTOR_LOG(
|
|
("OnCacheEntryAvailable %p called. entry=%p mFullUri=%d mPredict=%d "
|
|
"mPredictReason=%d mLearnReason=%d mTargetURI=%s "
|
|
"mSourceURI=%s mStackCount=%d isNew=%d result=0x%08" PRIx32,
|
|
this, entry, mFullUri, mPredict, mPredictReason, mLearnReason,
|
|
targetURI.get(), sourceURI.get(), mStackCount, isNew,
|
|
static_cast<uint32_t>(result)));
|
|
if (NS_FAILED(result)) {
|
|
PREDICTOR_LOG(
|
|
("OnCacheEntryAvailable %p FAILED to get cache entry (0x%08" PRIX32
|
|
"). Aborting.",
|
|
this, static_cast<uint32_t>(result)));
|
|
return NS_OK;
|
|
}
|
|
Telemetry::AccumulateTimeDelta(Telemetry::PREDICTOR_WAIT_TIME, mStartTime);
|
|
if (mPredict) {
|
|
bool predicted =
|
|
mPredictor->PredictInternal(mPredictReason, entry, isNew, mFullUri,
|
|
mTargetURI, mVerifier, mStackCount);
|
|
Telemetry::AccumulateTimeDelta(Telemetry::PREDICTOR_PREDICT_WORK_TIME,
|
|
mStartTime);
|
|
if (predicted) {
|
|
Telemetry::AccumulateTimeDelta(
|
|
Telemetry::PREDICTOR_PREDICT_TIME_TO_ACTION, mStartTime);
|
|
} else {
|
|
Telemetry::AccumulateTimeDelta(
|
|
Telemetry::PREDICTOR_PREDICT_TIME_TO_INACTION, mStartTime);
|
|
}
|
|
} else {
|
|
mPredictor->LearnInternal(mLearnReason, entry, isNew, mFullUri, mTargetURI,
|
|
mSourceURI);
|
|
Telemetry::AccumulateTimeDelta(Telemetry::PREDICTOR_LEARN_WORK_TIME,
|
|
mStartTime);
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMPL_ISUPPORTS(Predictor, nsINetworkPredictor, nsIObserver,
|
|
nsISpeculativeConnectionOverrider, nsIInterfaceRequestor,
|
|
nsICacheEntryMetaDataVisitor, nsINetworkPredictorVerifier)
|
|
|
|
Predictor::Predictor()
|
|
: mInitialized(false),
|
|
mCleanedUp(false),
|
|
mStartupTime(0),
|
|
mLastStartupTime(0),
|
|
mStartupCount(1) {
|
|
MOZ_ASSERT(!sSelf, "multiple Predictor instances!");
|
|
sSelf = this;
|
|
}
|
|
|
|
Predictor::~Predictor() {
|
|
if (mInitialized) Shutdown();
|
|
|
|
sSelf = nullptr;
|
|
}
|
|
|
|
// Predictor::nsIObserver
|
|
|
|
nsresult Predictor::InstallObserver() {
|
|
MOZ_ASSERT(NS_IsMainThread(), "Installing observer off main thread");
|
|
|
|
nsresult rv = NS_OK;
|
|
nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
|
|
if (!obs) {
|
|
return NS_ERROR_NOT_AVAILABLE;
|
|
}
|
|
|
|
rv = obs->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
mCleanedUp = Preferences::GetBool(PREDICTOR_CLEANED_UP_PREF, false);
|
|
|
|
if (!mCleanedUp) {
|
|
NS_NewTimerWithObserver(getter_AddRefs(mCleanupTimer), this, 60 * 1000,
|
|
nsITimer::TYPE_ONE_SHOT);
|
|
}
|
|
|
|
return rv;
|
|
}
|
|
|
|
void Predictor::RemoveObserver() {
|
|
MOZ_ASSERT(NS_IsMainThread(), "Removing observer off main thread");
|
|
|
|
nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
|
|
if (obs) {
|
|
obs->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID);
|
|
}
|
|
|
|
if (mCleanupTimer) {
|
|
mCleanupTimer->Cancel();
|
|
mCleanupTimer = nullptr;
|
|
}
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
Predictor::Observe(nsISupports* subject, const char* topic,
|
|
const char16_t* data_unicode) {
|
|
nsresult rv = NS_OK;
|
|
MOZ_ASSERT(NS_IsMainThread(),
|
|
"Predictor observing something off main thread!");
|
|
|
|
if (!strcmp(NS_XPCOM_SHUTDOWN_OBSERVER_ID, topic)) {
|
|
Shutdown();
|
|
} else if (!strcmp("timer-callback", topic)) {
|
|
MaybeCleanupOldDBFiles();
|
|
mCleanupTimer = nullptr;
|
|
}
|
|
|
|
return rv;
|
|
}
|
|
|
|
// Predictor::nsISpeculativeConnectionOverrider
|
|
|
|
NS_IMETHODIMP
|
|
Predictor::GetIgnoreIdle(bool* ignoreIdle) {
|
|
*ignoreIdle = true;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
Predictor::GetParallelSpeculativeConnectLimit(
|
|
uint32_t* parallelSpeculativeConnectLimit) {
|
|
*parallelSpeculativeConnectLimit = 6;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
Predictor::GetIsFromPredictor(bool* isFromPredictor) {
|
|
*isFromPredictor = true;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
Predictor::GetAllow1918(bool* allow1918) {
|
|
*allow1918 = false;
|
|
return NS_OK;
|
|
}
|
|
|
|
// Predictor::nsIInterfaceRequestor
|
|
|
|
NS_IMETHODIMP
|
|
Predictor::GetInterface(const nsIID& iid, void** result) {
|
|
return QueryInterface(iid, result);
|
|
}
|
|
|
|
// Predictor::nsICacheEntryMetaDataVisitor
|
|
|
|
#define SEEN_META_DATA "predictor::seen"
|
|
#define RESOURCE_META_DATA "predictor::resource-count"
|
|
#define META_DATA_PREFIX "predictor::"
|
|
|
|
static bool IsURIMetadataElement(const char* key) {
|
|
return StringBeginsWith(nsDependentCString(key),
|
|
NS_LITERAL_CSTRING(META_DATA_PREFIX)) &&
|
|
!NS_LITERAL_CSTRING(SEEN_META_DATA).Equals(key) &&
|
|
!NS_LITERAL_CSTRING(RESOURCE_META_DATA).Equals(key);
|
|
}
|
|
|
|
nsresult Predictor::OnMetaDataElement(const char* asciiKey,
|
|
const char* asciiValue) {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
if (!IsURIMetadataElement(asciiKey)) {
|
|
// This isn't a bit of metadata we care about
|
|
return NS_OK;
|
|
}
|
|
|
|
nsCString key, value;
|
|
key.AssignASCII(asciiKey);
|
|
value.AssignASCII(asciiValue);
|
|
mKeysToOperateOn.AppendElement(key);
|
|
mValuesToOperateOn.AppendElement(value);
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
// Predictor::nsINetworkPredictor
|
|
|
|
nsresult Predictor::Init() {
|
|
MOZ_DIAGNOSTIC_ASSERT(!IsNeckoChild());
|
|
|
|
if (!NS_IsMainThread()) {
|
|
MOZ_ASSERT(false, "Predictor::Init called off the main thread!");
|
|
return NS_ERROR_UNEXPECTED;
|
|
}
|
|
|
|
nsresult rv = NS_OK;
|
|
|
|
rv = InstallObserver();
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
mLastStartupTime = mStartupTime = NOW_IN_SECONDS();
|
|
|
|
if (!mDNSListener) {
|
|
mDNSListener = new DNSListener();
|
|
}
|
|
|
|
mCacheStorageService =
|
|
do_GetService("@mozilla.org/netwerk/cache-storage-service;1", &rv);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
mSpeculativeService = do_GetService("@mozilla.org/network/io-service;1", &rv);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
rv = NS_NewURI(getter_AddRefs(mStartupURI), "predictor://startup");
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
mDnsService = do_GetService("@mozilla.org/network/dns-service;1", &rv);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
Preferences::AddBoolVarCache(&sEsniEnabled, "network.security.esni.enabled");
|
|
|
|
mInitialized = true;
|
|
|
|
return rv;
|
|
}
|
|
|
|
namespace {
|
|
class PredictorOldCleanupRunner : public Runnable {
|
|
public:
|
|
explicit PredictorOldCleanupRunner(nsIFile* dbFile)
|
|
: Runnable("net::PredictorOldCleanupRunner"), mDBFile(dbFile) {}
|
|
|
|
~PredictorOldCleanupRunner() = default;
|
|
|
|
NS_IMETHOD Run() override {
|
|
MOZ_ASSERT(!NS_IsMainThread(), "Cleaning up old files on main thread!");
|
|
nsresult rv = CheckForAndDeleteOldDBFiles();
|
|
|
|
if (NS_SUCCEEDED(rv)) {
|
|
NS_DispatchToMainThread(NS_NewRunnableFunction(
|
|
"net::PredictorOldCleanupMainThread",
|
|
[]() { Preferences::SetBool(PREDICTOR_CLEANED_UP_PREF, true); }));
|
|
}
|
|
return NS_OK;
|
|
}
|
|
|
|
private:
|
|
nsresult CheckForAndDeleteOldDBFiles() {
|
|
nsCOMPtr<nsIFile> oldDBFile;
|
|
nsresult rv = mDBFile->GetParent(getter_AddRefs(oldDBFile));
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
rv = oldDBFile->AppendNative(NS_LITERAL_CSTRING("seer.sqlite"));
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
bool fileExists = false;
|
|
rv = oldDBFile->Exists(&fileExists);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
if (fileExists) {
|
|
rv = oldDBFile->Remove(false);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
}
|
|
|
|
fileExists = false;
|
|
rv = mDBFile->Exists(&fileExists);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
if (fileExists) {
|
|
rv = mDBFile->Remove(false);
|
|
}
|
|
|
|
return rv;
|
|
}
|
|
|
|
nsCOMPtr<nsIFile> mDBFile;
|
|
};
|
|
|
|
class PredictorLearnRunnable final : public Runnable {
|
|
public:
|
|
PredictorLearnRunnable(nsIURI* targetURI, nsIURI* sourceURI,
|
|
PredictorLearnReason reason,
|
|
const OriginAttributes& oa)
|
|
: Runnable("PredictorLearnRunnable"),
|
|
mTargetURI(targetURI),
|
|
mSourceURI(sourceURI),
|
|
mReason(reason),
|
|
mOA(oa) {
|
|
MOZ_DIAGNOSTIC_ASSERT(targetURI, "Must have a target URI");
|
|
}
|
|
|
|
~PredictorLearnRunnable() = default;
|
|
|
|
NS_IMETHOD Run() override {
|
|
if (!gNeckoChild) {
|
|
// This may have gone away between when this runnable was dispatched and
|
|
// when it actually runs, so let's be safe here, even though we asserted
|
|
// earlier.
|
|
PREDICTOR_LOG(("predictor::learn (async) gNeckoChild went away"));
|
|
return NS_OK;
|
|
}
|
|
|
|
PREDICTOR_LOG(("predictor::learn (async) forwarding to parent"));
|
|
gNeckoChild->SendPredLearn(mTargetURI, mSourceURI, mReason, mOA);
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
private:
|
|
nsCOMPtr<nsIURI> mTargetURI;
|
|
nsCOMPtr<nsIURI> mSourceURI;
|
|
PredictorLearnReason mReason;
|
|
const OriginAttributes mOA;
|
|
};
|
|
|
|
} // namespace
|
|
|
|
void Predictor::MaybeCleanupOldDBFiles() {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
if (!StaticPrefs::network_predictor_enabled() || mCleanedUp) {
|
|
return;
|
|
}
|
|
|
|
mCleanedUp = true;
|
|
|
|
// This is used for cleaning up junk left over from the old backend
|
|
// built on top of sqlite, if necessary.
|
|
nsCOMPtr<nsIFile> dbFile;
|
|
nsresult rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
|
|
getter_AddRefs(dbFile));
|
|
NS_ENSURE_SUCCESS_VOID(rv);
|
|
rv = dbFile->AppendNative(NS_LITERAL_CSTRING("netpredictions.sqlite"));
|
|
NS_ENSURE_SUCCESS_VOID(rv);
|
|
|
|
RefPtr<PredictorOldCleanupRunner> runner =
|
|
new PredictorOldCleanupRunner(dbFile);
|
|
NS_DispatchBackgroundTask(runner.forget());
|
|
}
|
|
|
|
void Predictor::Shutdown() {
|
|
if (!NS_IsMainThread()) {
|
|
MOZ_ASSERT(false, "Predictor::Shutdown called off the main thread!");
|
|
return;
|
|
}
|
|
|
|
RemoveObserver();
|
|
|
|
mInitialized = false;
|
|
}
|
|
|
|
nsresult Predictor::Create(nsISupports* aOuter, const nsIID& aIID,
|
|
void** aResult) {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
nsresult rv;
|
|
|
|
if (aOuter != nullptr) {
|
|
return NS_ERROR_NO_AGGREGATION;
|
|
}
|
|
|
|
RefPtr<Predictor> svc = new Predictor();
|
|
if (IsNeckoChild()) {
|
|
NeckoChild::InitNeckoChild();
|
|
|
|
// Child threads only need to be call into the public interface methods
|
|
// so we don't bother with initialization
|
|
return svc->QueryInterface(aIID, aResult);
|
|
}
|
|
|
|
rv = svc->Init();
|
|
if (NS_FAILED(rv)) {
|
|
PREDICTOR_LOG(("Failed to initialize predictor, predictor will be a noop"));
|
|
}
|
|
|
|
// We treat init failure the same as the service being disabled, since this
|
|
// is all an optimization anyway. No need to freak people out. That's why we
|
|
// gladly continue on QI'ing here.
|
|
rv = svc->QueryInterface(aIID, aResult);
|
|
|
|
return rv;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
Predictor::Predict(nsIURI* targetURI, nsIURI* sourceURI,
|
|
PredictorPredictReason reason,
|
|
JS::HandleValue originAttributes,
|
|
nsINetworkPredictorVerifier* verifier, JSContext* aCx) {
|
|
OriginAttributes attrs;
|
|
|
|
if (!originAttributes.isObject() || !attrs.Init(aCx, originAttributes)) {
|
|
return NS_ERROR_INVALID_ARG;
|
|
}
|
|
|
|
return PredictNative(targetURI, sourceURI, reason, attrs, verifier);
|
|
}
|
|
|
|
// Called from the main thread to initiate predictive actions
|
|
NS_IMETHODIMP
|
|
Predictor::PredictNative(nsIURI* targetURI, nsIURI* sourceURI,
|
|
PredictorPredictReason reason,
|
|
const OriginAttributes& originAttributes,
|
|
nsINetworkPredictorVerifier* verifier) {
|
|
MOZ_ASSERT(NS_IsMainThread(),
|
|
"Predictor interface methods must be called on the main thread");
|
|
|
|
PREDICTOR_LOG(("Predictor::Predict"));
|
|
|
|
if (IsNeckoChild()) {
|
|
MOZ_DIAGNOSTIC_ASSERT(gNeckoChild);
|
|
|
|
PREDICTOR_LOG((" called on child process"));
|
|
// If two different threads are predicting concurently, this will be
|
|
// overwritten. Thankfully, we only use this in tests, which will
|
|
// overwrite mVerifier perhaps multiple times for each individual test;
|
|
// however, within each test, the multiple predict calls should have the
|
|
// same verifier.
|
|
if (verifier) {
|
|
PREDICTOR_LOG((" was given a verifier"));
|
|
mChildVerifier = verifier;
|
|
}
|
|
PREDICTOR_LOG((" forwarding to parent process"));
|
|
gNeckoChild->SendPredPredict(targetURI, sourceURI, reason, originAttributes,
|
|
verifier);
|
|
return NS_OK;
|
|
}
|
|
|
|
PREDICTOR_LOG((" called on parent process"));
|
|
|
|
if (!mInitialized) {
|
|
PREDICTOR_LOG((" not initialized"));
|
|
return NS_OK;
|
|
}
|
|
|
|
if (!StaticPrefs::network_predictor_enabled()) {
|
|
PREDICTOR_LOG((" not enabled"));
|
|
return NS_OK;
|
|
}
|
|
|
|
if (originAttributes.mPrivateBrowsingId > 0) {
|
|
// Don't want to do anything in PB mode
|
|
PREDICTOR_LOG((" in PB mode"));
|
|
return NS_OK;
|
|
}
|
|
|
|
if (!IsNullOrHttp(targetURI) || !IsNullOrHttp(sourceURI)) {
|
|
// Nothing we can do for non-HTTP[S] schemes
|
|
PREDICTOR_LOG((" got non-http[s] URI"));
|
|
return NS_OK;
|
|
}
|
|
|
|
// Ensure we've been given the appropriate arguments for the kind of
|
|
// prediction we're being asked to do
|
|
nsCOMPtr<nsIURI> uriKey = targetURI;
|
|
nsCOMPtr<nsIURI> originKey;
|
|
switch (reason) {
|
|
case nsINetworkPredictor::PREDICT_LINK:
|
|
if (!targetURI || !sourceURI) {
|
|
PREDICTOR_LOG((" link invalid URI state"));
|
|
return NS_ERROR_INVALID_ARG;
|
|
}
|
|
// Link hover is a special case where we can predict without hitting the
|
|
// db, so let's go ahead and fire off that prediction here.
|
|
PredictForLink(targetURI, sourceURI, originAttributes, verifier);
|
|
return NS_OK;
|
|
case nsINetworkPredictor::PREDICT_LOAD:
|
|
if (!targetURI || sourceURI) {
|
|
PREDICTOR_LOG((" load invalid URI state"));
|
|
return NS_ERROR_INVALID_ARG;
|
|
}
|
|
break;
|
|
case nsINetworkPredictor::PREDICT_STARTUP:
|
|
if (targetURI || sourceURI) {
|
|
PREDICTOR_LOG((" startup invalid URI state"));
|
|
return NS_ERROR_INVALID_ARG;
|
|
}
|
|
uriKey = mStartupURI;
|
|
originKey = mStartupURI;
|
|
break;
|
|
default:
|
|
PREDICTOR_LOG((" invalid reason"));
|
|
return NS_ERROR_INVALID_ARG;
|
|
}
|
|
|
|
Predictor::Reason argReason;
|
|
argReason.mPredict = reason;
|
|
|
|
// First we open the regular cache entry, to ensure we don't gum up the works
|
|
// waiting on the less-important predictor-only cache entry
|
|
RefPtr<Predictor::Action> uriAction = new Predictor::Action(
|
|
Predictor::Action::IS_FULL_URI, Predictor::Action::DO_PREDICT, argReason,
|
|
targetURI, nullptr, verifier, this);
|
|
nsAutoCString uriKeyStr;
|
|
uriKey->GetAsciiSpec(uriKeyStr);
|
|
PREDICTOR_LOG((" Predict uri=%s reason=%d action=%p", uriKeyStr.get(),
|
|
reason, uriAction.get()));
|
|
|
|
nsCOMPtr<nsICacheStorage> cacheDiskStorage;
|
|
|
|
RefPtr<LoadContextInfo> lci = new LoadContextInfo(false, originAttributes);
|
|
|
|
nsresult rv = mCacheStorageService->DiskCacheStorage(
|
|
lci, false, getter_AddRefs(cacheDiskStorage));
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
uint32_t openFlags =
|
|
nsICacheStorage::OPEN_READONLY | nsICacheStorage::OPEN_SECRETLY |
|
|
nsICacheStorage::OPEN_PRIORITY | nsICacheStorage::CHECK_MULTITHREADED;
|
|
cacheDiskStorage->AsyncOpenURI(uriKey, EmptyCString(), openFlags, uriAction);
|
|
|
|
// Now we do the origin-only (and therefore predictor-only) entry
|
|
nsCOMPtr<nsIURI> targetOrigin;
|
|
rv = ExtractOrigin(uriKey, getter_AddRefs(targetOrigin));
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
if (!originKey) {
|
|
originKey = targetOrigin;
|
|
}
|
|
|
|
RefPtr<Predictor::Action> originAction = new Predictor::Action(
|
|
Predictor::Action::IS_ORIGIN, Predictor::Action::DO_PREDICT, argReason,
|
|
targetOrigin, nullptr, verifier, this);
|
|
nsAutoCString originKeyStr;
|
|
originKey->GetAsciiSpec(originKeyStr);
|
|
PREDICTOR_LOG((" Predict origin=%s reason=%d action=%p",
|
|
originKeyStr.get(), reason, originAction.get()));
|
|
openFlags = nsICacheStorage::OPEN_READONLY | nsICacheStorage::OPEN_SECRETLY |
|
|
nsICacheStorage::CHECK_MULTITHREADED;
|
|
cacheDiskStorage->AsyncOpenURI(originKey,
|
|
NS_LITERAL_CSTRING(PREDICTOR_ORIGIN_EXTENSION),
|
|
openFlags, originAction);
|
|
|
|
PREDICTOR_LOG((" predict returning"));
|
|
return NS_OK;
|
|
}
|
|
|
|
bool Predictor::PredictInternal(PredictorPredictReason reason,
|
|
nsICacheEntry* entry, bool isNew, bool fullUri,
|
|
nsIURI* targetURI,
|
|
nsINetworkPredictorVerifier* verifier,
|
|
uint8_t stackCount) {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
PREDICTOR_LOG(("Predictor::PredictInternal"));
|
|
bool rv = false;
|
|
|
|
nsCOMPtr<nsILoadContextInfo> lci;
|
|
entry->GetLoadContextInfo(getter_AddRefs(lci));
|
|
|
|
if (!lci) {
|
|
return rv;
|
|
}
|
|
|
|
if (reason == nsINetworkPredictor::PREDICT_LOAD) {
|
|
MaybeLearnForStartup(targetURI, fullUri, *lci->OriginAttributesPtr());
|
|
}
|
|
|
|
if (isNew) {
|
|
// nothing else we can do here
|
|
PREDICTOR_LOG((" new entry"));
|
|
return rv;
|
|
}
|
|
|
|
switch (reason) {
|
|
case nsINetworkPredictor::PREDICT_LOAD:
|
|
rv = PredictForPageload(entry, targetURI, stackCount, fullUri, verifier);
|
|
break;
|
|
case nsINetworkPredictor::PREDICT_STARTUP:
|
|
rv = PredictForStartup(entry, fullUri, verifier);
|
|
break;
|
|
default:
|
|
PREDICTOR_LOG((" invalid reason"));
|
|
MOZ_ASSERT(false, "Got unexpected value for prediction reason");
|
|
}
|
|
|
|
return rv;
|
|
}
|
|
|
|
void Predictor::PredictForLink(nsIURI* targetURI, nsIURI* sourceURI,
|
|
const OriginAttributes& originAttributes,
|
|
nsINetworkPredictorVerifier* verifier) {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
PREDICTOR_LOG(("Predictor::PredictForLink"));
|
|
if (!mSpeculativeService) {
|
|
PREDICTOR_LOG((" missing speculative service"));
|
|
return;
|
|
}
|
|
|
|
if (!StaticPrefs::network_predictor_enable_hover_on_ssl()) {
|
|
if (sourceURI->SchemeIs("https")) {
|
|
// We don't want to predict from an HTTPS page, to avoid info leakage
|
|
PREDICTOR_LOG((" Not predicting for link hover - on an SSL page"));
|
|
return;
|
|
}
|
|
}
|
|
|
|
nsCOMPtr<nsIPrincipal> principal =
|
|
BasePrincipal::CreateContentPrincipal(targetURI, originAttributes);
|
|
|
|
mSpeculativeService->SpeculativeConnect(targetURI, principal, nullptr);
|
|
if (verifier) {
|
|
PREDICTOR_LOG((" sending verification"));
|
|
verifier->OnPredictPreconnect(targetURI);
|
|
}
|
|
}
|
|
|
|
// This is the driver for prediction based on a new pageload.
|
|
static const uint8_t MAX_PAGELOAD_DEPTH = 10;
|
|
bool Predictor::PredictForPageload(nsICacheEntry* entry, nsIURI* targetURI,
|
|
uint8_t stackCount, bool fullUri,
|
|
nsINetworkPredictorVerifier* verifier) {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
PREDICTOR_LOG(("Predictor::PredictForPageload"));
|
|
|
|
if (stackCount > MAX_PAGELOAD_DEPTH) {
|
|
PREDICTOR_LOG((" exceeded recursion depth!"));
|
|
return false;
|
|
}
|
|
|
|
uint32_t lastLoad;
|
|
nsresult rv = entry->GetLastFetched(&lastLoad);
|
|
NS_ENSURE_SUCCESS(rv, false);
|
|
|
|
int32_t globalDegradation = CalculateGlobalDegradation(lastLoad);
|
|
PREDICTOR_LOG((" globalDegradation = %d", globalDegradation));
|
|
|
|
int32_t loadCount;
|
|
rv = entry->GetFetchCount(&loadCount);
|
|
NS_ENSURE_SUCCESS(rv, false);
|
|
|
|
nsCOMPtr<nsILoadContextInfo> lci;
|
|
|
|
rv = entry->GetLoadContextInfo(getter_AddRefs(lci));
|
|
NS_ENSURE_SUCCESS(rv, false);
|
|
|
|
nsCOMPtr<nsIURI> redirectURI;
|
|
if (WouldRedirect(entry, loadCount, lastLoad, globalDegradation,
|
|
getter_AddRefs(redirectURI))) {
|
|
mPreconnects.AppendElement(redirectURI);
|
|
Predictor::Reason reason;
|
|
reason.mPredict = nsINetworkPredictor::PREDICT_LOAD;
|
|
RefPtr<Predictor::Action> redirectAction = new Predictor::Action(
|
|
Predictor::Action::IS_FULL_URI, Predictor::Action::DO_PREDICT, reason,
|
|
redirectURI, nullptr, verifier, this, stackCount + 1);
|
|
nsAutoCString redirectUriString;
|
|
redirectURI->GetAsciiSpec(redirectUriString);
|
|
|
|
nsCOMPtr<nsICacheStorage> cacheDiskStorage;
|
|
|
|
rv = mCacheStorageService->DiskCacheStorage(
|
|
lci, false, getter_AddRefs(cacheDiskStorage));
|
|
NS_ENSURE_SUCCESS(rv, false);
|
|
|
|
PREDICTOR_LOG((" Predict redirect uri=%s action=%p",
|
|
redirectUriString.get(), redirectAction.get()));
|
|
uint32_t openFlags =
|
|
nsICacheStorage::OPEN_READONLY | nsICacheStorage::OPEN_SECRETLY |
|
|
nsICacheStorage::OPEN_PRIORITY | nsICacheStorage::CHECK_MULTITHREADED;
|
|
cacheDiskStorage->AsyncOpenURI(redirectURI, EmptyCString(), openFlags,
|
|
redirectAction);
|
|
return RunPredictions(nullptr, *lci->OriginAttributesPtr(), verifier);
|
|
}
|
|
|
|
CalculatePredictions(entry, targetURI, lastLoad, loadCount, globalDegradation,
|
|
fullUri);
|
|
|
|
return RunPredictions(targetURI, *lci->OriginAttributesPtr(), verifier);
|
|
}
|
|
|
|
// This is the driver for predicting at browser startup time based on pages that
|
|
// have previously been loaded close to startup.
|
|
bool Predictor::PredictForStartup(nsICacheEntry* entry, bool fullUri,
|
|
nsINetworkPredictorVerifier* verifier) {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
PREDICTOR_LOG(("Predictor::PredictForStartup"));
|
|
|
|
nsCOMPtr<nsILoadContextInfo> lci;
|
|
|
|
nsresult rv = entry->GetLoadContextInfo(getter_AddRefs(lci));
|
|
NS_ENSURE_SUCCESS(rv, false);
|
|
|
|
int32_t globalDegradation = CalculateGlobalDegradation(mLastStartupTime);
|
|
CalculatePredictions(entry, nullptr, mLastStartupTime, mStartupCount,
|
|
globalDegradation, fullUri);
|
|
return RunPredictions(nullptr, *lci->OriginAttributesPtr(), verifier);
|
|
}
|
|
|
|
// This calculates how much to degrade our confidence in our data based on
|
|
// the last time this top-level resource was loaded. This "global degradation"
|
|
// applies to *all* subresources we have associated with the top-level
|
|
// resource. This will be in addition to any reduction in confidence we have
|
|
// associated with a particular subresource.
|
|
int32_t Predictor::CalculateGlobalDegradation(uint32_t lastLoad) {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
int32_t globalDegradation;
|
|
uint32_t delta = NOW_IN_SECONDS() - lastLoad;
|
|
if (delta < ONE_DAY) {
|
|
globalDegradation = StaticPrefs::network_predictor_page_degradation_day();
|
|
} else if (delta < ONE_WEEK) {
|
|
globalDegradation = StaticPrefs::network_predictor_page_degradation_week();
|
|
} else if (delta < ONE_MONTH) {
|
|
globalDegradation = StaticPrefs::network_predictor_page_degradation_month();
|
|
} else if (delta < ONE_YEAR) {
|
|
globalDegradation = StaticPrefs::network_predictor_page_degradation_year();
|
|
} else {
|
|
globalDegradation = StaticPrefs::network_predictor_page_degradation_max();
|
|
}
|
|
|
|
Telemetry::Accumulate(Telemetry::PREDICTOR_GLOBAL_DEGRADATION,
|
|
globalDegradation);
|
|
return globalDegradation;
|
|
}
|
|
|
|
// This calculates our overall confidence that a particular subresource will be
|
|
// loaded as part of a top-level load.
|
|
// @param hitCount - the number of times we have loaded this subresource as part
|
|
// of this top-level load
|
|
// @param hitsPossible - the number of times we have performed this top-level
|
|
// load
|
|
// @param lastHit - the timestamp of the last time we loaded this subresource as
|
|
// part of this top-level load
|
|
// @param lastPossible - the timestamp of the last time we performed this
|
|
// top-level load
|
|
// @param globalDegradation - the degradation for this top-level load as
|
|
// determined by CalculateGlobalDegradation
|
|
int32_t Predictor::CalculateConfidence(uint32_t hitCount, uint32_t hitsPossible,
|
|
uint32_t lastHit, uint32_t lastPossible,
|
|
int32_t globalDegradation) {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
Telemetry::AutoCounter<Telemetry::PREDICTOR_PREDICTIONS_CALCULATED>
|
|
predictionsCalculated;
|
|
++predictionsCalculated;
|
|
|
|
if (!hitsPossible) {
|
|
return 0;
|
|
}
|
|
|
|
int32_t baseConfidence = (hitCount * 100) / hitsPossible;
|
|
int32_t maxConfidence = 100;
|
|
int32_t confidenceDegradation = 0;
|
|
|
|
if (lastHit < lastPossible) {
|
|
// We didn't load this subresource the last time this top-level load was
|
|
// performed, so let's not bother preconnecting (at the very least).
|
|
maxConfidence =
|
|
StaticPrefs::network_predictor_preconnect_min_confidence() - 1;
|
|
|
|
// Now calculate how much we want to degrade our confidence based on how
|
|
// long it's been between the last time we did this top-level load and the
|
|
// last time this top-level load included this subresource.
|
|
PRTime delta = lastPossible - lastHit;
|
|
if (delta == 0) {
|
|
confidenceDegradation = 0;
|
|
} else if (delta < ONE_DAY) {
|
|
confidenceDegradation =
|
|
StaticPrefs::network_predictor_subresource_degradation_day();
|
|
} else if (delta < ONE_WEEK) {
|
|
confidenceDegradation =
|
|
StaticPrefs::network_predictor_subresource_degradation_week();
|
|
} else if (delta < ONE_MONTH) {
|
|
confidenceDegradation =
|
|
StaticPrefs::network_predictor_subresource_degradation_month();
|
|
} else if (delta < ONE_YEAR) {
|
|
confidenceDegradation =
|
|
StaticPrefs::network_predictor_subresource_degradation_year();
|
|
} else {
|
|
confidenceDegradation =
|
|
StaticPrefs::network_predictor_subresource_degradation_max();
|
|
maxConfidence = 0;
|
|
}
|
|
}
|
|
|
|
// Calculate our confidence and clamp it to between 0 and maxConfidence
|
|
// (<= 100)
|
|
int32_t confidence =
|
|
baseConfidence - confidenceDegradation - globalDegradation;
|
|
confidence = std::max(confidence, 0);
|
|
confidence = std::min(confidence, maxConfidence);
|
|
|
|
Telemetry::Accumulate(Telemetry::PREDICTOR_BASE_CONFIDENCE, baseConfidence);
|
|
Telemetry::Accumulate(Telemetry::PREDICTOR_SUBRESOURCE_DEGRADATION,
|
|
confidenceDegradation);
|
|
Telemetry::Accumulate(Telemetry::PREDICTOR_CONFIDENCE, confidence);
|
|
return confidence;
|
|
}
|
|
|
|
static void MakeMetadataEntry(const uint32_t hitCount, const uint32_t lastHit,
|
|
const uint32_t flags, nsCString& newValue) {
|
|
newValue.Truncate();
|
|
newValue.AppendInt(METADATA_VERSION);
|
|
newValue.Append(',');
|
|
newValue.AppendInt(hitCount);
|
|
newValue.Append(',');
|
|
newValue.AppendInt(lastHit);
|
|
newValue.Append(',');
|
|
newValue.AppendInt(flags);
|
|
}
|
|
|
|
// On every page load, the rolling window gets shifted by one bit, leaving the
|
|
// lowest bit at 0, to indicate that the subresource in question has not been
|
|
// seen on the most recent page load. If, at some point later during the page
|
|
// load, the subresource is seen again, we will then set the lowest bit to 1.
|
|
// This is how we keep track of how many of the last n pageloads (for n <= 20) a
|
|
// particular subresource has been seen. The rolling window is kept in the upper
|
|
// 20 bits of the flags element of the metadata. This saves 12 bits for regular
|
|
// old flags.
|
|
void Predictor::UpdateRollingLoadCount(nsICacheEntry* entry,
|
|
const uint32_t flags, const char* key,
|
|
const uint32_t hitCount,
|
|
const uint32_t lastHit) {
|
|
// Extract just the rolling load count from the flags, shift it to clear the
|
|
// lowest bit, and put the new value with the existing flags.
|
|
uint32_t rollingLoadCount = flags & ~kFlagsMask;
|
|
rollingLoadCount <<= 1;
|
|
uint32_t newFlags = (flags & kFlagsMask) | rollingLoadCount;
|
|
|
|
// Finally, update the metadata on the cache entry.
|
|
nsAutoCString newValue;
|
|
MakeMetadataEntry(hitCount, lastHit, newFlags, newValue);
|
|
entry->SetMetaDataElement(key, newValue.BeginReading());
|
|
}
|
|
|
|
uint32_t Predictor::ClampedPrefetchRollingLoadCount() {
|
|
int32_t n = StaticPrefs::network_predictor_prefetch_rolling_load_count();
|
|
if (n < 0) {
|
|
return 0;
|
|
}
|
|
if (n > kMaxPrefetchRollingLoadCount) {
|
|
return kMaxPrefetchRollingLoadCount;
|
|
}
|
|
return n;
|
|
}
|
|
|
|
void Predictor::CalculatePredictions(nsICacheEntry* entry, nsIURI* referrer,
|
|
uint32_t lastLoad, uint32_t loadCount,
|
|
int32_t globalDegradation, bool fullUri) {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
// Since the visitor gets called under a cache lock, all we do there is get
|
|
// copies of the keys/values we care about, and then do the real work here
|
|
entry->VisitMetaData(this);
|
|
nsTArray<nsCString> keysToOperateOn, valuesToOperateOn;
|
|
keysToOperateOn.SwapElements(mKeysToOperateOn);
|
|
valuesToOperateOn.SwapElements(mValuesToOperateOn);
|
|
|
|
MOZ_ASSERT(keysToOperateOn.Length() == valuesToOperateOn.Length());
|
|
for (size_t i = 0; i < keysToOperateOn.Length(); ++i) {
|
|
const char* key = keysToOperateOn[i].BeginReading();
|
|
const char* value = valuesToOperateOn[i].BeginReading();
|
|
|
|
nsCString uri;
|
|
uint32_t hitCount, lastHit, flags;
|
|
if (!ParseMetaDataEntry(key, value, uri, hitCount, lastHit, flags)) {
|
|
// This failed, get rid of it so we don't waste space
|
|
entry->SetMetaDataElement(key, nullptr);
|
|
continue;
|
|
}
|
|
|
|
int32_t confidence = CalculateConfidence(hitCount, loadCount, lastHit,
|
|
lastLoad, globalDegradation);
|
|
if (fullUri) {
|
|
UpdateRollingLoadCount(entry, flags, key, hitCount, lastHit);
|
|
}
|
|
PREDICTOR_LOG(("CalculatePredictions key=%s value=%s confidence=%d", key,
|
|
value, confidence));
|
|
PrefetchIgnoreReason reason = PREFETCH_OK;
|
|
if (!fullUri) {
|
|
// Not full URI - don't prefetch! No sense in it!
|
|
PREDICTOR_LOG((" forcing non-cacheability - not full URI"));
|
|
if (flags & FLAG_PREFETCHABLE) {
|
|
// This only applies if we had somehow otherwise marked this
|
|
// prefetchable.
|
|
reason = NOT_FULL_URI;
|
|
}
|
|
flags &= ~FLAG_PREFETCHABLE;
|
|
} else if (!referrer) {
|
|
// No referrer means we can't prefetch, so pretend it's non-cacheable,
|
|
// no matter what.
|
|
PREDICTOR_LOG((" forcing non-cacheability - no referrer"));
|
|
if (flags & FLAG_PREFETCHABLE) {
|
|
// This only applies if we had somehow otherwise marked this
|
|
// prefetchable.
|
|
reason = NO_REFERRER;
|
|
}
|
|
flags &= ~FLAG_PREFETCHABLE;
|
|
} else {
|
|
uint32_t expectedRollingLoadCount =
|
|
(1 << ClampedPrefetchRollingLoadCount()) - 1;
|
|
expectedRollingLoadCount <<= kRollingLoadOffset;
|
|
if ((flags & expectedRollingLoadCount) != expectedRollingLoadCount) {
|
|
PREDICTOR_LOG((" forcing non-cacheability - missed a load"));
|
|
if (flags & FLAG_PREFETCHABLE) {
|
|
// This only applies if we had somehow otherwise marked this
|
|
// prefetchable.
|
|
reason = MISSED_A_LOAD;
|
|
}
|
|
flags &= ~FLAG_PREFETCHABLE;
|
|
}
|
|
}
|
|
|
|
PREDICTOR_LOG((" setting up prediction"));
|
|
SetupPrediction(confidence, flags, uri, reason);
|
|
}
|
|
}
|
|
|
|
// (Maybe) adds a predictive action to the prediction runner, based on our
|
|
// calculated confidence for the subresource in question.
|
|
void Predictor::SetupPrediction(int32_t confidence, uint32_t flags,
|
|
const nsCString& uri,
|
|
PrefetchIgnoreReason earlyReason) {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
nsresult rv = NS_OK;
|
|
PREDICTOR_LOG(
|
|
("SetupPrediction enable-prefetch=%d prefetch-min-confidence=%d "
|
|
"preconnect-min-confidence=%d preresolve-min-confidence=%d "
|
|
"flags=%d confidence=%d uri=%s",
|
|
StaticPrefs::network_predictor_enable_prefetch(),
|
|
StaticPrefs::network_predictor_prefetch_min_confidence(),
|
|
StaticPrefs::network_predictor_preconnect_min_confidence(),
|
|
StaticPrefs::network_predictor_preresolve_min_confidence(), flags,
|
|
confidence, uri.get()));
|
|
|
|
bool prefetchOk = !!(flags & FLAG_PREFETCHABLE);
|
|
PrefetchIgnoreReason reason = earlyReason;
|
|
if (prefetchOk && !StaticPrefs::network_predictor_enable_prefetch()) {
|
|
prefetchOk = false;
|
|
reason = PREFETCH_DISABLED;
|
|
} else if (prefetchOk && !ClampedPrefetchRollingLoadCount() &&
|
|
confidence <
|
|
StaticPrefs::network_predictor_prefetch_min_confidence()) {
|
|
prefetchOk = false;
|
|
if (!ClampedPrefetchRollingLoadCount()) {
|
|
reason = PREFETCH_DISABLED_VIA_COUNT;
|
|
} else {
|
|
reason = CONFIDENCE_TOO_LOW;
|
|
}
|
|
}
|
|
|
|
// prefetchOk == false and reason == PREFETCH_OK indicates that the reason
|
|
// we aren't prefetching this item is because it was marked un-prefetchable in
|
|
// our metadata. We already have separate telemetry on that decision, so we
|
|
// aren't going to accumulate more here. Right now we only care about why
|
|
// something we had marked prefetchable isn't being prefetched.
|
|
if (!prefetchOk && reason != PREFETCH_OK) {
|
|
Telemetry::Accumulate(Telemetry::PREDICTOR_PREFETCH_IGNORE_REASON, reason);
|
|
}
|
|
|
|
if (prefetchOk) {
|
|
nsCOMPtr<nsIURI> prefetchURI;
|
|
rv = NS_NewURI(getter_AddRefs(prefetchURI), uri);
|
|
if (NS_SUCCEEDED(rv)) {
|
|
mPrefetches.AppendElement(prefetchURI);
|
|
}
|
|
} else if (confidence >=
|
|
StaticPrefs::network_predictor_preconnect_min_confidence()) {
|
|
nsCOMPtr<nsIURI> preconnectURI;
|
|
rv = NS_NewURI(getter_AddRefs(preconnectURI), uri);
|
|
if (NS_SUCCEEDED(rv)) {
|
|
mPreconnects.AppendElement(preconnectURI);
|
|
}
|
|
} else if (confidence >=
|
|
StaticPrefs::network_predictor_preresolve_min_confidence()) {
|
|
nsCOMPtr<nsIURI> preresolveURI;
|
|
rv = NS_NewURI(getter_AddRefs(preresolveURI), uri);
|
|
if (NS_SUCCEEDED(rv)) {
|
|
mPreresolves.AppendElement(preresolveURI);
|
|
}
|
|
}
|
|
|
|
if (NS_FAILED(rv)) {
|
|
PREDICTOR_LOG(
|
|
(" NS_NewURI returned 0x%" PRIx32, static_cast<uint32_t>(rv)));
|
|
}
|
|
}
|
|
|
|
nsresult Predictor::Prefetch(nsIURI* uri, nsIURI* referrer,
|
|
const OriginAttributes& originAttributes,
|
|
nsINetworkPredictorVerifier* verifier) {
|
|
nsAutoCString strUri, strReferrer;
|
|
uri->GetAsciiSpec(strUri);
|
|
referrer->GetAsciiSpec(strReferrer);
|
|
PREDICTOR_LOG(("Predictor::Prefetch uri=%s referrer=%s verifier=%p",
|
|
strUri.get(), strReferrer.get(), verifier));
|
|
nsCOMPtr<nsIChannel> channel;
|
|
nsresult rv = NS_NewChannel(
|
|
getter_AddRefs(channel), uri, nsContentUtils::GetSystemPrincipal(),
|
|
nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL,
|
|
nsIContentPolicy::TYPE_OTHER, nullptr, /* nsICookieSettings */
|
|
nullptr, /* aPerformanceStorage */
|
|
nullptr, /* aLoadGroup */
|
|
nullptr, /* aCallbacks */
|
|
nsIRequest::LOAD_BACKGROUND);
|
|
|
|
if (NS_FAILED(rv)) {
|
|
PREDICTOR_LOG(
|
|
(" NS_NewChannel failed rv=0x%" PRIX32, static_cast<uint32_t>(rv)));
|
|
return rv;
|
|
}
|
|
|
|
nsCOMPtr<nsILoadInfo> loadInfo = channel->LoadInfo();
|
|
rv = loadInfo->SetOriginAttributes(originAttributes);
|
|
|
|
if (NS_FAILED(rv)) {
|
|
PREDICTOR_LOG(
|
|
(" Set originAttributes into loadInfo failed rv=0x%" PRIX32,
|
|
static_cast<uint32_t>(rv)));
|
|
return rv;
|
|
}
|
|
|
|
nsCOMPtr<nsIHttpChannel> httpChannel;
|
|
httpChannel = do_QueryInterface(channel);
|
|
if (!httpChannel) {
|
|
PREDICTOR_LOG((" Could not get HTTP Channel from new channel!"));
|
|
return NS_ERROR_UNEXPECTED;
|
|
}
|
|
|
|
nsCOMPtr<nsIReferrerInfo> referrerInfo = new ReferrerInfo(referrer);
|
|
rv = httpChannel->SetReferrerInfoWithoutClone(referrerInfo);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
// XXX - set a header here to indicate this is a prefetch?
|
|
|
|
nsCOMPtr<nsIStreamListener> listener =
|
|
new PrefetchListener(verifier, uri, this);
|
|
PREDICTOR_LOG((" calling AsyncOpen listener=%p channel=%p", listener.get(),
|
|
channel.get()));
|
|
rv = channel->AsyncOpen(listener);
|
|
if (NS_FAILED(rv)) {
|
|
PREDICTOR_LOG(
|
|
(" AsyncOpen failed rv=0x%" PRIX32, static_cast<uint32_t>(rv)));
|
|
}
|
|
|
|
return rv;
|
|
}
|
|
|
|
// Runs predictions that have been set up.
|
|
bool Predictor::RunPredictions(nsIURI* referrer,
|
|
const OriginAttributes& originAttributes,
|
|
nsINetworkPredictorVerifier* verifier) {
|
|
MOZ_ASSERT(NS_IsMainThread(), "Running prediction off main thread");
|
|
|
|
PREDICTOR_LOG(("Predictor::RunPredictions"));
|
|
|
|
bool predicted = false;
|
|
uint32_t len, i;
|
|
|
|
nsTArray<nsCOMPtr<nsIURI>> prefetches, preconnects, preresolves;
|
|
prefetches.SwapElements(mPrefetches);
|
|
preconnects.SwapElements(mPreconnects);
|
|
preresolves.SwapElements(mPreresolves);
|
|
|
|
Telemetry::AutoCounter<Telemetry::PREDICTOR_TOTAL_PREDICTIONS>
|
|
totalPredictions;
|
|
Telemetry::AutoCounter<Telemetry::PREDICTOR_TOTAL_PREFETCHES> totalPrefetches;
|
|
Telemetry::AutoCounter<Telemetry::PREDICTOR_TOTAL_PRECONNECTS>
|
|
totalPreconnects;
|
|
Telemetry::AutoCounter<Telemetry::PREDICTOR_TOTAL_PRERESOLVES>
|
|
totalPreresolves;
|
|
|
|
len = prefetches.Length();
|
|
for (i = 0; i < len; ++i) {
|
|
PREDICTOR_LOG((" doing prefetch"));
|
|
nsCOMPtr<nsIURI> uri = prefetches[i];
|
|
if (NS_SUCCEEDED(Prefetch(uri, referrer, originAttributes, verifier))) {
|
|
++totalPredictions;
|
|
++totalPrefetches;
|
|
predicted = true;
|
|
}
|
|
}
|
|
|
|
len = preconnects.Length();
|
|
for (i = 0; i < len; ++i) {
|
|
PREDICTOR_LOG((" doing preconnect"));
|
|
nsCOMPtr<nsIURI> uri = preconnects[i];
|
|
++totalPredictions;
|
|
++totalPreconnects;
|
|
nsCOMPtr<nsIPrincipal> principal =
|
|
BasePrincipal::CreateContentPrincipal(uri, originAttributes);
|
|
mSpeculativeService->SpeculativeConnect(uri, principal, this);
|
|
predicted = true;
|
|
if (verifier) {
|
|
PREDICTOR_LOG((" sending preconnect verification"));
|
|
verifier->OnPredictPreconnect(uri);
|
|
}
|
|
}
|
|
|
|
len = preresolves.Length();
|
|
for (i = 0; i < len; ++i) {
|
|
nsCOMPtr<nsIURI> uri = preresolves[i];
|
|
++totalPredictions;
|
|
++totalPreresolves;
|
|
nsAutoCString hostname;
|
|
uri->GetAsciiHost(hostname);
|
|
PREDICTOR_LOG((" doing preresolve %s", hostname.get()));
|
|
nsCOMPtr<nsICancelable> tmpCancelable;
|
|
mDnsService->AsyncResolveNative(hostname,
|
|
(nsIDNSService::RESOLVE_PRIORITY_MEDIUM |
|
|
nsIDNSService::RESOLVE_SPECULATE),
|
|
mDNSListener, nullptr, originAttributes,
|
|
getter_AddRefs(tmpCancelable));
|
|
|
|
// Fetch esni keys if needed.
|
|
if (sEsniEnabled && uri->SchemeIs("https")) {
|
|
nsAutoCString esniHost;
|
|
esniHost.Append("_esni.");
|
|
esniHost.Append(hostname);
|
|
mDnsService->AsyncResolveByTypeNative(
|
|
esniHost, nsIDNSService::RESOLVE_TYPE_TXT,
|
|
(nsIDNSService::RESOLVE_PRIORITY_MEDIUM |
|
|
nsIDNSService::RESOLVE_SPECULATE),
|
|
mDNSListener, nullptr, originAttributes,
|
|
getter_AddRefs(tmpCancelable));
|
|
}
|
|
|
|
predicted = true;
|
|
if (verifier) {
|
|
PREDICTOR_LOG((" sending preresolve verification"));
|
|
verifier->OnPredictDNS(uri);
|
|
}
|
|
}
|
|
|
|
return predicted;
|
|
}
|
|
|
|
// Find out if a top-level page is likely to redirect.
|
|
bool Predictor::WouldRedirect(nsICacheEntry* entry, uint32_t loadCount,
|
|
uint32_t lastLoad, int32_t globalDegradation,
|
|
nsIURI** redirectURI) {
|
|
// TODO - not doing redirects for first go around
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
return false;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
Predictor::Learn(nsIURI* targetURI, nsIURI* sourceURI,
|
|
PredictorLearnReason reason, JS::HandleValue originAttributes,
|
|
JSContext* aCx) {
|
|
OriginAttributes attrs;
|
|
|
|
if (!originAttributes.isObject() || !attrs.Init(aCx, originAttributes)) {
|
|
return NS_ERROR_INVALID_ARG;
|
|
}
|
|
|
|
return LearnNative(targetURI, sourceURI, reason, attrs);
|
|
}
|
|
|
|
// Called from the main thread to update the database
|
|
NS_IMETHODIMP
|
|
Predictor::LearnNative(nsIURI* targetURI, nsIURI* sourceURI,
|
|
PredictorLearnReason reason,
|
|
const OriginAttributes& originAttributes) {
|
|
MOZ_ASSERT(NS_IsMainThread(),
|
|
"Predictor interface methods must be called on the main thread");
|
|
|
|
PREDICTOR_LOG(("Predictor::Learn"));
|
|
|
|
if (IsNeckoChild()) {
|
|
MOZ_DIAGNOSTIC_ASSERT(gNeckoChild);
|
|
|
|
PREDICTOR_LOG((" called on child process"));
|
|
|
|
RefPtr<PredictorLearnRunnable> runnable = new PredictorLearnRunnable(
|
|
targetURI, sourceURI, reason, originAttributes);
|
|
SystemGroup::Dispatch(TaskCategory::Other, runnable.forget());
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
PREDICTOR_LOG((" called on parent process"));
|
|
|
|
if (!mInitialized) {
|
|
PREDICTOR_LOG((" not initialized"));
|
|
return NS_OK;
|
|
}
|
|
|
|
if (!StaticPrefs::network_predictor_enabled()) {
|
|
PREDICTOR_LOG((" not enabled"));
|
|
return NS_OK;
|
|
}
|
|
|
|
if (originAttributes.mPrivateBrowsingId > 0) {
|
|
// Don't want to do anything in PB mode
|
|
PREDICTOR_LOG((" in PB mode"));
|
|
return NS_OK;
|
|
}
|
|
|
|
if (!IsNullOrHttp(targetURI) || !IsNullOrHttp(sourceURI)) {
|
|
PREDICTOR_LOG((" got non-HTTP[S] URI"));
|
|
return NS_ERROR_INVALID_ARG;
|
|
}
|
|
|
|
nsCOMPtr<nsIURI> targetOrigin;
|
|
nsCOMPtr<nsIURI> sourceOrigin;
|
|
nsCOMPtr<nsIURI> uriKey;
|
|
nsCOMPtr<nsIURI> originKey;
|
|
nsresult rv;
|
|
|
|
switch (reason) {
|
|
case nsINetworkPredictor::LEARN_LOAD_TOPLEVEL:
|
|
if (!targetURI || sourceURI) {
|
|
PREDICTOR_LOG((" load toplevel invalid URI state"));
|
|
return NS_ERROR_INVALID_ARG;
|
|
}
|
|
rv = ExtractOrigin(targetURI, getter_AddRefs(targetOrigin));
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
uriKey = targetURI;
|
|
originKey = targetOrigin;
|
|
break;
|
|
case nsINetworkPredictor::LEARN_STARTUP:
|
|
if (!targetURI || sourceURI) {
|
|
PREDICTOR_LOG((" startup invalid URI state"));
|
|
return NS_ERROR_INVALID_ARG;
|
|
}
|
|
rv = ExtractOrigin(targetURI, getter_AddRefs(targetOrigin));
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
uriKey = mStartupURI;
|
|
originKey = mStartupURI;
|
|
break;
|
|
case nsINetworkPredictor::LEARN_LOAD_REDIRECT:
|
|
case nsINetworkPredictor::LEARN_LOAD_SUBRESOURCE:
|
|
if (!targetURI || !sourceURI) {
|
|
PREDICTOR_LOG((" redirect/subresource invalid URI state"));
|
|
return NS_ERROR_INVALID_ARG;
|
|
}
|
|
rv = ExtractOrigin(targetURI, getter_AddRefs(targetOrigin));
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
rv = ExtractOrigin(sourceURI, getter_AddRefs(sourceOrigin));
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
uriKey = sourceURI;
|
|
originKey = sourceOrigin;
|
|
break;
|
|
default:
|
|
PREDICTOR_LOG((" invalid reason"));
|
|
return NS_ERROR_INVALID_ARG;
|
|
}
|
|
|
|
Telemetry::AutoCounter<Telemetry::PREDICTOR_LEARN_ATTEMPTS> learnAttempts;
|
|
++learnAttempts;
|
|
|
|
Predictor::Reason argReason;
|
|
argReason.mLearn = reason;
|
|
|
|
// We always open the full uri (general cache) entry first, so we don't gum up
|
|
// the works waiting on predictor-only entries to open
|
|
RefPtr<Predictor::Action> uriAction = new Predictor::Action(
|
|
Predictor::Action::IS_FULL_URI, Predictor::Action::DO_LEARN, argReason,
|
|
targetURI, sourceURI, nullptr, this);
|
|
nsAutoCString uriKeyStr, targetUriStr, sourceUriStr;
|
|
uriKey->GetAsciiSpec(uriKeyStr);
|
|
targetURI->GetAsciiSpec(targetUriStr);
|
|
if (sourceURI) {
|
|
sourceURI->GetAsciiSpec(sourceUriStr);
|
|
}
|
|
PREDICTOR_LOG(
|
|
(" Learn uriKey=%s targetURI=%s sourceURI=%s reason=%d "
|
|
"action=%p",
|
|
uriKeyStr.get(), targetUriStr.get(), sourceUriStr.get(), reason,
|
|
uriAction.get()));
|
|
|
|
nsCOMPtr<nsICacheStorage> cacheDiskStorage;
|
|
|
|
RefPtr<LoadContextInfo> lci = new LoadContextInfo(false, originAttributes);
|
|
|
|
rv = mCacheStorageService->DiskCacheStorage(lci, false,
|
|
getter_AddRefs(cacheDiskStorage));
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
// For learning full URI things, we *always* open readonly and secretly, as we
|
|
// rely on actual pageloads to update the entry's metadata for us.
|
|
uint32_t uriOpenFlags = nsICacheStorage::OPEN_READONLY |
|
|
nsICacheStorage::OPEN_SECRETLY |
|
|
nsICacheStorage::CHECK_MULTITHREADED;
|
|
if (reason == nsINetworkPredictor::LEARN_LOAD_TOPLEVEL) {
|
|
// Learning for toplevel we want to open the full uri entry priority, since
|
|
// it's likely this entry will be used soon anyway, and we want this to be
|
|
// opened ASAP.
|
|
uriOpenFlags |= nsICacheStorage::OPEN_PRIORITY;
|
|
}
|
|
cacheDiskStorage->AsyncOpenURI(uriKey, EmptyCString(), uriOpenFlags,
|
|
uriAction);
|
|
|
|
// Now we open the origin-only (and therefore predictor-only) entry
|
|
RefPtr<Predictor::Action> originAction = new Predictor::Action(
|
|
Predictor::Action::IS_ORIGIN, Predictor::Action::DO_LEARN, argReason,
|
|
targetOrigin, sourceOrigin, nullptr, this);
|
|
nsAutoCString originKeyStr, targetOriginStr, sourceOriginStr;
|
|
originKey->GetAsciiSpec(originKeyStr);
|
|
targetOrigin->GetAsciiSpec(targetOriginStr);
|
|
if (sourceOrigin) {
|
|
sourceOrigin->GetAsciiSpec(sourceOriginStr);
|
|
}
|
|
PREDICTOR_LOG(
|
|
(" Learn originKey=%s targetOrigin=%s sourceOrigin=%s reason=%d "
|
|
"action=%p",
|
|
originKeyStr.get(), targetOriginStr.get(), sourceOriginStr.get(), reason,
|
|
originAction.get()));
|
|
uint32_t originOpenFlags;
|
|
if (reason == nsINetworkPredictor::LEARN_LOAD_TOPLEVEL) {
|
|
// This is the only case when we want to update the 'last used' metadata on
|
|
// the cache entry we're getting. This only applies to predictor-specific
|
|
// entries.
|
|
originOpenFlags =
|
|
nsICacheStorage::OPEN_NORMALLY | nsICacheStorage::CHECK_MULTITHREADED;
|
|
} else {
|
|
originOpenFlags = nsICacheStorage::OPEN_READONLY |
|
|
nsICacheStorage::OPEN_SECRETLY |
|
|
nsICacheStorage::CHECK_MULTITHREADED;
|
|
}
|
|
cacheDiskStorage->AsyncOpenURI(originKey,
|
|
NS_LITERAL_CSTRING(PREDICTOR_ORIGIN_EXTENSION),
|
|
originOpenFlags, originAction);
|
|
|
|
PREDICTOR_LOG(("Predictor::Learn returning"));
|
|
return NS_OK;
|
|
}
|
|
|
|
void Predictor::LearnInternal(PredictorLearnReason reason, nsICacheEntry* entry,
|
|
bool isNew, bool fullUri, nsIURI* targetURI,
|
|
nsIURI* sourceURI) {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
PREDICTOR_LOG(("Predictor::LearnInternal"));
|
|
|
|
nsCString junk;
|
|
if (!fullUri && reason == nsINetworkPredictor::LEARN_LOAD_TOPLEVEL &&
|
|
NS_FAILED(
|
|
entry->GetMetaDataElement(SEEN_META_DATA, getter_Copies(junk)))) {
|
|
// This is an origin-only entry that we haven't seen before. Let's mark it
|
|
// as seen.
|
|
PREDICTOR_LOG((" marking new origin entry as seen"));
|
|
nsresult rv = entry->SetMetaDataElement(SEEN_META_DATA, "1");
|
|
if (NS_FAILED(rv)) {
|
|
PREDICTOR_LOG((" failed to mark origin entry seen"));
|
|
return;
|
|
}
|
|
|
|
// Need to ensure someone else can get to the entry if necessary
|
|
entry->MetaDataReady();
|
|
return;
|
|
}
|
|
|
|
switch (reason) {
|
|
case nsINetworkPredictor::LEARN_LOAD_TOPLEVEL:
|
|
// This case only exists to be used during tests - code outside the
|
|
// predictor tests should NEVER call Learn with LEARN_LOAD_TOPLEVEL.
|
|
// The predictor xpcshell test needs this branch, however, because we
|
|
// have no real page loads in xpcshell, and this is how we fake it up
|
|
// so that all the work that normally happens behind the scenes in a
|
|
// page load can be done for testing purposes.
|
|
if (fullUri && StaticPrefs::network_predictor_doing_tests()) {
|
|
PREDICTOR_LOG(
|
|
(" WARNING - updating rolling load count. "
|
|
"If you see this outside tests, you did it wrong"));
|
|
|
|
// Since the visitor gets called under a cache lock, all we do there is
|
|
// get copies of the keys/values we care about, and then do the real
|
|
// work here
|
|
entry->VisitMetaData(this);
|
|
nsTArray<nsCString> keysToOperateOn, valuesToOperateOn;
|
|
keysToOperateOn.SwapElements(mKeysToOperateOn);
|
|
valuesToOperateOn.SwapElements(mValuesToOperateOn);
|
|
|
|
MOZ_ASSERT(keysToOperateOn.Length() == valuesToOperateOn.Length());
|
|
for (size_t i = 0; i < keysToOperateOn.Length(); ++i) {
|
|
const char* key = keysToOperateOn[i].BeginReading();
|
|
const char* value = valuesToOperateOn[i].BeginReading();
|
|
|
|
nsCString uri;
|
|
uint32_t hitCount, lastHit, flags;
|
|
if (!ParseMetaDataEntry(key, value, uri, hitCount, lastHit, flags)) {
|
|
// This failed, get rid of it so we don't waste space
|
|
entry->SetMetaDataElement(key, nullptr);
|
|
continue;
|
|
}
|
|
UpdateRollingLoadCount(entry, flags, key, hitCount, lastHit);
|
|
}
|
|
} else {
|
|
PREDICTOR_LOG((" nothing to do for toplevel"));
|
|
}
|
|
break;
|
|
case nsINetworkPredictor::LEARN_LOAD_REDIRECT:
|
|
if (fullUri) {
|
|
LearnForRedirect(entry, targetURI);
|
|
}
|
|
break;
|
|
case nsINetworkPredictor::LEARN_LOAD_SUBRESOURCE:
|
|
LearnForSubresource(entry, targetURI);
|
|
break;
|
|
case nsINetworkPredictor::LEARN_STARTUP:
|
|
LearnForStartup(entry, targetURI);
|
|
break;
|
|
default:
|
|
PREDICTOR_LOG((" unexpected reason value"));
|
|
MOZ_ASSERT(false, "Got unexpected value for learn reason!");
|
|
}
|
|
}
|
|
|
|
NS_IMPL_ISUPPORTS(Predictor::SpaceCleaner, nsICacheEntryMetaDataVisitor)
|
|
|
|
NS_IMETHODIMP
|
|
Predictor::SpaceCleaner::OnMetaDataElement(const char* key, const char* value) {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
if (!IsURIMetadataElement(key)) {
|
|
// This isn't a bit of metadata we care about
|
|
return NS_OK;
|
|
}
|
|
|
|
nsCString uri;
|
|
uint32_t hitCount, lastHit, flags;
|
|
bool ok =
|
|
mPredictor->ParseMetaDataEntry(key, value, uri, hitCount, lastHit, flags);
|
|
|
|
if (!ok) {
|
|
// Couldn't parse this one, just get rid of it
|
|
nsCString nsKey;
|
|
nsKey.AssignASCII(key);
|
|
mLongKeysToDelete.AppendElement(nsKey);
|
|
return NS_OK;
|
|
}
|
|
|
|
uint32_t uriLength = uri.Length();
|
|
if (uriLength > StaticPrefs::network_predictor_max_uri_length()) {
|
|
// Default to getting rid of URIs that are too long and were put in before
|
|
// we had our limit on URI length, in order to free up some space.
|
|
nsCString nsKey;
|
|
nsKey.AssignASCII(key);
|
|
mLongKeysToDelete.AppendElement(nsKey);
|
|
return NS_OK;
|
|
}
|
|
|
|
if (!mLRUKeyToDelete || lastHit < mLRUStamp) {
|
|
mLRUKeyToDelete = key;
|
|
mLRUStamp = lastHit;
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
void Predictor::SpaceCleaner::Finalize(nsICacheEntry* entry) {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
if (mLRUKeyToDelete) {
|
|
entry->SetMetaDataElement(mLRUKeyToDelete, nullptr);
|
|
}
|
|
|
|
for (size_t i = 0; i < mLongKeysToDelete.Length(); ++i) {
|
|
entry->SetMetaDataElement(mLongKeysToDelete[i].BeginReading(), nullptr);
|
|
}
|
|
}
|
|
|
|
// Called when a subresource has been hit from a top-level load. Uses the two
|
|
// helper functions above to update the database appropriately.
|
|
void Predictor::LearnForSubresource(nsICacheEntry* entry, nsIURI* targetURI) {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
PREDICTOR_LOG(("Predictor::LearnForSubresource"));
|
|
|
|
uint32_t lastLoad;
|
|
nsresult rv = entry->GetLastFetched(&lastLoad);
|
|
NS_ENSURE_SUCCESS_VOID(rv);
|
|
|
|
int32_t loadCount;
|
|
rv = entry->GetFetchCount(&loadCount);
|
|
NS_ENSURE_SUCCESS_VOID(rv);
|
|
|
|
nsCString key;
|
|
key.AssignLiteral(META_DATA_PREFIX);
|
|
nsCString uri;
|
|
targetURI->GetAsciiSpec(uri);
|
|
key.Append(uri);
|
|
if (uri.Length() > StaticPrefs::network_predictor_max_uri_length()) {
|
|
// We do this to conserve space/prevent OOMs
|
|
PREDICTOR_LOG((" uri too long!"));
|
|
entry->SetMetaDataElement(key.BeginReading(), nullptr);
|
|
return;
|
|
}
|
|
|
|
nsCString value;
|
|
rv = entry->GetMetaDataElement(key.BeginReading(), getter_Copies(value));
|
|
|
|
uint32_t hitCount, lastHit, flags;
|
|
bool isNewResource =
|
|
(NS_FAILED(rv) ||
|
|
!ParseMetaDataEntry(key.BeginReading(), value.BeginReading(), uri,
|
|
hitCount, lastHit, flags));
|
|
|
|
int32_t resourceCount = 0;
|
|
if (isNewResource) {
|
|
// This is a new addition
|
|
PREDICTOR_LOG((" new resource"));
|
|
nsCString s;
|
|
rv = entry->GetMetaDataElement(RESOURCE_META_DATA, getter_Copies(s));
|
|
if (NS_SUCCEEDED(rv)) {
|
|
resourceCount = atoi(s.BeginReading());
|
|
}
|
|
if (resourceCount >=
|
|
StaticPrefs::network_predictor_max_resources_per_entry()) {
|
|
RefPtr<Predictor::SpaceCleaner> cleaner =
|
|
new Predictor::SpaceCleaner(this);
|
|
entry->VisitMetaData(cleaner);
|
|
cleaner->Finalize(entry);
|
|
} else {
|
|
++resourceCount;
|
|
}
|
|
nsAutoCString count;
|
|
count.AppendInt(resourceCount);
|
|
rv = entry->SetMetaDataElement(RESOURCE_META_DATA, count.BeginReading());
|
|
if (NS_FAILED(rv)) {
|
|
PREDICTOR_LOG((" failed to update resource count"));
|
|
return;
|
|
}
|
|
hitCount = 1;
|
|
flags = 0;
|
|
} else {
|
|
PREDICTOR_LOG((" existing resource"));
|
|
hitCount = std::min(hitCount + 1, static_cast<uint32_t>(loadCount));
|
|
}
|
|
|
|
// Update the rolling load count to mark this sub-resource as seen on the
|
|
// most-recent pageload so it can be eligible for prefetch (assuming all
|
|
// the other stars align).
|
|
flags |= (1 << kRollingLoadOffset);
|
|
|
|
nsCString newValue;
|
|
MakeMetadataEntry(hitCount, lastLoad, flags, newValue);
|
|
rv = entry->SetMetaDataElement(key.BeginReading(), newValue.BeginReading());
|
|
PREDICTOR_LOG(
|
|
(" SetMetaDataElement -> 0x%08" PRIX32, static_cast<uint32_t>(rv)));
|
|
if (NS_FAILED(rv) && isNewResource) {
|
|
// Roll back the increment to the resource count we made above.
|
|
PREDICTOR_LOG((" rolling back resource count update"));
|
|
--resourceCount;
|
|
if (resourceCount == 0) {
|
|
entry->SetMetaDataElement(RESOURCE_META_DATA, nullptr);
|
|
} else {
|
|
nsAutoCString count;
|
|
count.AppendInt(resourceCount);
|
|
entry->SetMetaDataElement(RESOURCE_META_DATA, count.BeginReading());
|
|
}
|
|
}
|
|
}
|
|
|
|
// This is called when a top-level loaded ended up redirecting to a different
|
|
// URI so we can keep track of that fact.
|
|
void Predictor::LearnForRedirect(nsICacheEntry* entry, nsIURI* targetURI) {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
// TODO - not doing redirects for first go around
|
|
PREDICTOR_LOG(("Predictor::LearnForRedirect"));
|
|
}
|
|
|
|
// This will add a page to our list of startup pages if it's being loaded
|
|
// before our startup window has expired.
|
|
void Predictor::MaybeLearnForStartup(nsIURI* uri, bool fullUri,
|
|
const OriginAttributes& originAttributes) {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
// TODO - not doing startup for first go around
|
|
PREDICTOR_LOG(("Predictor::MaybeLearnForStartup"));
|
|
}
|
|
|
|
// Add information about a top-level load to our list of startup pages
|
|
void Predictor::LearnForStartup(nsICacheEntry* entry, nsIURI* targetURI) {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
// These actually do the same set of work, just on different entries, so we
|
|
// can pass through to get the real work done here
|
|
PREDICTOR_LOG(("Predictor::LearnForStartup"));
|
|
LearnForSubresource(entry, targetURI);
|
|
}
|
|
|
|
bool Predictor::ParseMetaDataEntry(const char* key, const char* value,
|
|
nsCString& uri, uint32_t& hitCount,
|
|
uint32_t& lastHit, uint32_t& flags) {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
PREDICTOR_LOG(
|
|
("Predictor::ParseMetaDataEntry key=%s value=%s", key ? key : "", value));
|
|
|
|
const char* comma = strchr(value, ',');
|
|
if (!comma) {
|
|
PREDICTOR_LOG((" could not find first comma"));
|
|
return false;
|
|
}
|
|
|
|
uint32_t version = static_cast<uint32_t>(atoi(value));
|
|
PREDICTOR_LOG((" version -> %u", version));
|
|
|
|
if (version != METADATA_VERSION) {
|
|
PREDICTOR_LOG(
|
|
(" metadata version mismatch %u != %u", version, METADATA_VERSION));
|
|
return false;
|
|
}
|
|
|
|
value = comma + 1;
|
|
comma = strchr(value, ',');
|
|
if (!comma) {
|
|
PREDICTOR_LOG((" could not find second comma"));
|
|
return false;
|
|
}
|
|
|
|
hitCount = static_cast<uint32_t>(atoi(value));
|
|
PREDICTOR_LOG((" hitCount -> %u", hitCount));
|
|
|
|
value = comma + 1;
|
|
comma = strchr(value, ',');
|
|
if (!comma) {
|
|
PREDICTOR_LOG((" could not find third comma"));
|
|
return false;
|
|
}
|
|
|
|
lastHit = static_cast<uint32_t>(atoi(value));
|
|
PREDICTOR_LOG((" lastHit -> %u", lastHit));
|
|
|
|
value = comma + 1;
|
|
flags = static_cast<uint32_t>(atoi(value));
|
|
PREDICTOR_LOG((" flags -> %u", flags));
|
|
|
|
if (key) {
|
|
const char* uriStart = key + (sizeof(META_DATA_PREFIX) - 1);
|
|
uri.AssignASCII(uriStart);
|
|
PREDICTOR_LOG((" uri -> %s", uriStart));
|
|
} else {
|
|
uri.Truncate();
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
Predictor::Reset() {
|
|
MOZ_ASSERT(NS_IsMainThread(),
|
|
"Predictor interface methods must be called on the main thread");
|
|
|
|
PREDICTOR_LOG(("Predictor::Reset"));
|
|
|
|
if (IsNeckoChild()) {
|
|
MOZ_DIAGNOSTIC_ASSERT(gNeckoChild);
|
|
|
|
PREDICTOR_LOG((" forwarding to parent process"));
|
|
gNeckoChild->SendPredReset();
|
|
return NS_OK;
|
|
}
|
|
|
|
PREDICTOR_LOG((" called on parent process"));
|
|
|
|
if (!mInitialized) {
|
|
PREDICTOR_LOG((" not initialized"));
|
|
return NS_OK;
|
|
}
|
|
|
|
if (!StaticPrefs::network_predictor_enabled()) {
|
|
PREDICTOR_LOG((" not enabled"));
|
|
return NS_OK;
|
|
}
|
|
|
|
RefPtr<Predictor::Resetter> reset = new Predictor::Resetter(this);
|
|
PREDICTOR_LOG((" created a resetter"));
|
|
mCacheStorageService->AsyncVisitAllStorages(reset, true);
|
|
PREDICTOR_LOG((" Cache async launched, returning now"));
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMPL_ISUPPORTS(Predictor::Resetter, nsICacheEntryOpenCallback,
|
|
nsICacheEntryMetaDataVisitor, nsICacheStorageVisitor);
|
|
|
|
Predictor::Resetter::Resetter(Predictor* predictor)
|
|
: mEntriesToVisit(0), mPredictor(predictor) {}
|
|
|
|
NS_IMETHODIMP
|
|
Predictor::Resetter::OnCacheEntryCheck(nsICacheEntry* entry,
|
|
nsIApplicationCache* appCache,
|
|
uint32_t* result) {
|
|
*result = nsICacheEntryOpenCallback::ENTRY_WANTED;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
Predictor::Resetter::OnCacheEntryAvailable(nsICacheEntry* entry, bool isNew,
|
|
nsIApplicationCache* appCache,
|
|
nsresult result) {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
if (NS_FAILED(result)) {
|
|
// This can happen when we've tried to open an entry that doesn't exist for
|
|
// some non-reset operation, and then get reset shortly thereafter (as
|
|
// happens in some of our tests).
|
|
--mEntriesToVisit;
|
|
if (!mEntriesToVisit) {
|
|
Complete();
|
|
}
|
|
return NS_OK;
|
|
}
|
|
|
|
entry->VisitMetaData(this);
|
|
nsTArray<nsCString> keysToDelete;
|
|
keysToDelete.SwapElements(mKeysToDelete);
|
|
|
|
for (size_t i = 0; i < keysToDelete.Length(); ++i) {
|
|
const char* key = keysToDelete[i].BeginReading();
|
|
entry->SetMetaDataElement(key, nullptr);
|
|
}
|
|
|
|
--mEntriesToVisit;
|
|
if (!mEntriesToVisit) {
|
|
Complete();
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
Predictor::Resetter::OnMetaDataElement(const char* asciiKey,
|
|
const char* asciiValue) {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
if (!StringBeginsWith(nsDependentCString(asciiKey),
|
|
NS_LITERAL_CSTRING(META_DATA_PREFIX))) {
|
|
// Not a metadata entry we care about, carry on
|
|
return NS_OK;
|
|
}
|
|
|
|
nsCString key;
|
|
key.AssignASCII(asciiKey);
|
|
mKeysToDelete.AppendElement(key);
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
Predictor::Resetter::OnCacheStorageInfo(uint32_t entryCount,
|
|
uint64_t consumption, uint64_t capacity,
|
|
nsIFile* diskDirectory) {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
Predictor::Resetter::OnCacheEntryInfo(nsIURI* uri, const nsACString& idEnhance,
|
|
int64_t dataSize, int32_t fetchCount,
|
|
uint32_t lastModifiedTime,
|
|
uint32_t expirationTime, bool aPinned,
|
|
nsILoadContextInfo* aInfo) {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
nsresult rv;
|
|
|
|
// The predictor will only ever touch entries with no idEnhance ("") or an
|
|
// idEnhance of PREDICTOR_ORIGIN_EXTENSION, so we filter out any entries that
|
|
// don't match that to avoid doing extra work.
|
|
if (idEnhance.EqualsLiteral(PREDICTOR_ORIGIN_EXTENSION)) {
|
|
// This is an entry we own, so we can just doom it entirely
|
|
nsCOMPtr<nsICacheStorage> cacheDiskStorage;
|
|
|
|
rv = mPredictor->mCacheStorageService->DiskCacheStorage(
|
|
aInfo, false, getter_AddRefs(cacheDiskStorage));
|
|
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
cacheDiskStorage->AsyncDoomURI(uri, idEnhance, nullptr);
|
|
} else if (idEnhance.IsEmpty()) {
|
|
// This is an entry we don't own, so we have to be a little more careful and
|
|
// just get rid of our own metadata entries. Append it to an array of things
|
|
// to operate on and then do the operations later so we don't end up calling
|
|
// Complete() multiple times/too soon.
|
|
++mEntriesToVisit;
|
|
mURIsToVisit.AppendElement(uri);
|
|
mInfosToVisit.AppendElement(aInfo);
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
Predictor::Resetter::OnCacheEntryVisitCompleted() {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
nsresult rv;
|
|
|
|
nsTArray<nsCOMPtr<nsIURI>> urisToVisit;
|
|
urisToVisit.SwapElements(mURIsToVisit);
|
|
|
|
MOZ_ASSERT(mEntriesToVisit == urisToVisit.Length());
|
|
|
|
nsTArray<nsCOMPtr<nsILoadContextInfo>> infosToVisit;
|
|
infosToVisit.SwapElements(mInfosToVisit);
|
|
|
|
MOZ_ASSERT(mEntriesToVisit == infosToVisit.Length());
|
|
|
|
if (!mEntriesToVisit) {
|
|
Complete();
|
|
return NS_OK;
|
|
}
|
|
|
|
uint32_t entriesToVisit = urisToVisit.Length();
|
|
for (uint32_t i = 0; i < entriesToVisit; ++i) {
|
|
nsCString u;
|
|
nsCOMPtr<nsICacheStorage> cacheDiskStorage;
|
|
|
|
rv = mPredictor->mCacheStorageService->DiskCacheStorage(
|
|
infosToVisit[i], false, getter_AddRefs(cacheDiskStorage));
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
urisToVisit[i]->GetAsciiSpec(u);
|
|
cacheDiskStorage->AsyncOpenURI(urisToVisit[i], EmptyCString(),
|
|
nsICacheStorage::OPEN_READONLY |
|
|
nsICacheStorage::OPEN_SECRETLY |
|
|
nsICacheStorage::CHECK_MULTITHREADED,
|
|
this);
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
void Predictor::Resetter::Complete() {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
|
|
if (!obs) {
|
|
PREDICTOR_LOG(("COULD NOT GET OBSERVER SERVICE!"));
|
|
return;
|
|
}
|
|
|
|
obs->NotifyObservers(nullptr, "predictor-reset-complete", nullptr);
|
|
}
|
|
|
|
// Helper functions to make using the predictor easier from native code
|
|
|
|
static StaticRefPtr<nsINetworkPredictor> sPredictor;
|
|
|
|
static nsresult EnsureGlobalPredictor(nsINetworkPredictor** aPredictor) {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
if (!sPredictor) {
|
|
nsresult rv;
|
|
nsCOMPtr<nsINetworkPredictor> predictor =
|
|
do_GetService("@mozilla.org/network/predictor;1", &rv);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
sPredictor = predictor;
|
|
ClearOnShutdown(&sPredictor);
|
|
}
|
|
|
|
nsCOMPtr<nsINetworkPredictor> predictor = sPredictor.get();
|
|
predictor.forget(aPredictor);
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult PredictorPredict(nsIURI* targetURI, nsIURI* sourceURI,
|
|
PredictorPredictReason reason,
|
|
const OriginAttributes& originAttributes,
|
|
nsINetworkPredictorVerifier* verifier) {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
if (!IsNullOrHttp(targetURI) || !IsNullOrHttp(sourceURI)) {
|
|
return NS_OK;
|
|
}
|
|
|
|
nsCOMPtr<nsINetworkPredictor> predictor;
|
|
nsresult rv = EnsureGlobalPredictor(getter_AddRefs(predictor));
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
return predictor->PredictNative(targetURI, sourceURI, reason,
|
|
originAttributes, verifier);
|
|
}
|
|
|
|
nsresult PredictorLearn(nsIURI* targetURI, nsIURI* sourceURI,
|
|
PredictorLearnReason reason,
|
|
const OriginAttributes& originAttributes) {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
if (!IsNullOrHttp(targetURI) || !IsNullOrHttp(sourceURI)) {
|
|
return NS_OK;
|
|
}
|
|
|
|
nsCOMPtr<nsINetworkPredictor> predictor;
|
|
nsresult rv = EnsureGlobalPredictor(getter_AddRefs(predictor));
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
return predictor->LearnNative(targetURI, sourceURI, reason, originAttributes);
|
|
}
|
|
|
|
nsresult PredictorLearn(nsIURI* targetURI, nsIURI* sourceURI,
|
|
PredictorLearnReason reason, nsILoadGroup* loadGroup) {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
if (!IsNullOrHttp(targetURI) || !IsNullOrHttp(sourceURI)) {
|
|
return NS_OK;
|
|
}
|
|
|
|
nsCOMPtr<nsINetworkPredictor> predictor;
|
|
nsresult rv = EnsureGlobalPredictor(getter_AddRefs(predictor));
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
nsCOMPtr<nsILoadContext> loadContext;
|
|
OriginAttributes originAttributes;
|
|
|
|
if (loadGroup) {
|
|
nsCOMPtr<nsIInterfaceRequestor> callbacks;
|
|
loadGroup->GetNotificationCallbacks(getter_AddRefs(callbacks));
|
|
if (callbacks) {
|
|
loadContext = do_GetInterface(callbacks);
|
|
|
|
if (loadContext) {
|
|
loadContext->GetOriginAttributes(originAttributes);
|
|
}
|
|
}
|
|
}
|
|
|
|
return predictor->LearnNative(targetURI, sourceURI, reason, originAttributes);
|
|
}
|
|
|
|
nsresult PredictorLearn(nsIURI* targetURI, nsIURI* sourceURI,
|
|
PredictorLearnReason reason, dom::Document* document) {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
if (!IsNullOrHttp(targetURI) || !IsNullOrHttp(sourceURI)) {
|
|
return NS_OK;
|
|
}
|
|
|
|
nsCOMPtr<nsINetworkPredictor> predictor;
|
|
nsresult rv = EnsureGlobalPredictor(getter_AddRefs(predictor));
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
OriginAttributes originAttributes;
|
|
|
|
if (document) {
|
|
nsCOMPtr<nsIPrincipal> docPrincipal = document->NodePrincipal();
|
|
|
|
if (docPrincipal) {
|
|
originAttributes = docPrincipal->OriginAttributesRef();
|
|
}
|
|
}
|
|
|
|
return predictor->LearnNative(targetURI, sourceURI, reason, originAttributes);
|
|
}
|
|
|
|
nsresult PredictorLearnRedirect(nsIURI* targetURI, nsIChannel* channel,
|
|
const OriginAttributes& originAttributes) {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
nsCOMPtr<nsIURI> sourceURI;
|
|
nsresult rv = channel->GetOriginalURI(getter_AddRefs(sourceURI));
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
bool sameUri;
|
|
rv = targetURI->Equals(sourceURI, &sameUri);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
if (sameUri) {
|
|
return NS_OK;
|
|
}
|
|
|
|
if (!IsNullOrHttp(targetURI) || !IsNullOrHttp(sourceURI)) {
|
|
return NS_OK;
|
|
}
|
|
|
|
nsCOMPtr<nsINetworkPredictor> predictor;
|
|
rv = EnsureGlobalPredictor(getter_AddRefs(predictor));
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
return predictor->LearnNative(targetURI, sourceURI,
|
|
nsINetworkPredictor::LEARN_LOAD_REDIRECT,
|
|
originAttributes);
|
|
}
|
|
|
|
// nsINetworkPredictorVerifier
|
|
|
|
/**
|
|
* Call through to the child's verifier (only during tests)
|
|
*/
|
|
NS_IMETHODIMP
|
|
Predictor::OnPredictPrefetch(nsIURI* aURI, uint32_t httpStatus) {
|
|
if (IsNeckoChild()) {
|
|
if (mChildVerifier) {
|
|
// Ideally, we'd assert here. But since we're slowly moving towards a
|
|
// world where we have multiple child processes, and only one child
|
|
// process will be likely to have a verifier, we have to play it safer.
|
|
return mChildVerifier->OnPredictPrefetch(aURI, httpStatus);
|
|
}
|
|
return NS_OK;
|
|
}
|
|
|
|
MOZ_DIAGNOSTIC_ASSERT(aURI, "aURI must not be null");
|
|
|
|
for (auto* cp : ContentParent::AllProcesses(ContentParent::eLive)) {
|
|
PNeckoParent* neckoParent = SingleManagedOrNull(cp->ManagedPNeckoParent());
|
|
if (!neckoParent) {
|
|
continue;
|
|
}
|
|
if (!neckoParent->SendPredOnPredictPrefetch(aURI, httpStatus)) {
|
|
return NS_ERROR_NOT_AVAILABLE;
|
|
}
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
Predictor::OnPredictPreconnect(nsIURI* aURI) {
|
|
if (IsNeckoChild()) {
|
|
if (mChildVerifier) {
|
|
// Ideally, we'd assert here. But since we're slowly moving towards a
|
|
// world where we have multiple child processes, and only one child
|
|
// process will be likely to have a verifier, we have to play it safer.
|
|
return mChildVerifier->OnPredictPreconnect(aURI);
|
|
}
|
|
return NS_OK;
|
|
}
|
|
|
|
MOZ_DIAGNOSTIC_ASSERT(aURI, "aURI must not be null");
|
|
|
|
for (auto* cp : ContentParent::AllProcesses(ContentParent::eLive)) {
|
|
PNeckoParent* neckoParent = SingleManagedOrNull(cp->ManagedPNeckoParent());
|
|
if (!neckoParent) {
|
|
continue;
|
|
}
|
|
if (!neckoParent->SendPredOnPredictPreconnect(aURI)) {
|
|
return NS_ERROR_NOT_AVAILABLE;
|
|
}
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
Predictor::OnPredictDNS(nsIURI* aURI) {
|
|
if (IsNeckoChild()) {
|
|
if (mChildVerifier) {
|
|
// Ideally, we'd assert here. But since we're slowly moving towards a
|
|
// world where we have multiple child processes, and only one child
|
|
// process will be likely to have a verifier, we have to play it safer.
|
|
return mChildVerifier->OnPredictDNS(aURI);
|
|
}
|
|
return NS_OK;
|
|
}
|
|
|
|
MOZ_DIAGNOSTIC_ASSERT(aURI, "aURI must not be null");
|
|
|
|
for (auto* cp : ContentParent::AllProcesses(ContentParent::eLive)) {
|
|
PNeckoParent* neckoParent = SingleManagedOrNull(cp->ManagedPNeckoParent());
|
|
if (!neckoParent) {
|
|
continue;
|
|
}
|
|
if (!neckoParent->SendPredOnPredictDNS(aURI)) {
|
|
return NS_ERROR_NOT_AVAILABLE;
|
|
}
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
// Predictor::PrefetchListener
|
|
// nsISupports
|
|
NS_IMPL_ISUPPORTS(Predictor::PrefetchListener, nsIStreamListener,
|
|
nsIRequestObserver)
|
|
|
|
// nsIRequestObserver
|
|
NS_IMETHODIMP
|
|
Predictor::PrefetchListener::OnStartRequest(nsIRequest* aRequest) {
|
|
mStartTime = TimeStamp::Now();
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
Predictor::PrefetchListener::OnStopRequest(nsIRequest* aRequest,
|
|
nsresult aStatusCode) {
|
|
PREDICTOR_LOG(("OnStopRequest this=%p aStatusCode=0x%" PRIX32, this,
|
|
static_cast<uint32_t>(aStatusCode)));
|
|
NS_ENSURE_ARG(aRequest);
|
|
if (NS_FAILED(aStatusCode)) {
|
|
return aStatusCode;
|
|
}
|
|
Telemetry::AccumulateTimeDelta(Telemetry::PREDICTOR_PREFETCH_TIME,
|
|
mStartTime);
|
|
|
|
nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aRequest);
|
|
if (!httpChannel) {
|
|
PREDICTOR_LOG((" Could not get HTTP Channel!"));
|
|
return NS_ERROR_UNEXPECTED;
|
|
}
|
|
nsCOMPtr<nsICachingChannel> cachingChannel = do_QueryInterface(httpChannel);
|
|
if (!cachingChannel) {
|
|
PREDICTOR_LOG((" Could not get caching channel!"));
|
|
return NS_ERROR_UNEXPECTED;
|
|
}
|
|
|
|
nsresult rv = NS_OK;
|
|
uint32_t httpStatus;
|
|
rv = httpChannel->GetResponseStatus(&httpStatus);
|
|
if (NS_SUCCEEDED(rv) && httpStatus == 200) {
|
|
rv = cachingChannel->ForceCacheEntryValidFor(
|
|
StaticPrefs::network_predictor_prefetch_force_valid_for());
|
|
PREDICTOR_LOG((" forcing entry valid for %d seconds rv=%" PRIX32,
|
|
StaticPrefs::network_predictor_prefetch_force_valid_for(),
|
|
static_cast<uint32_t>(rv)));
|
|
} else {
|
|
rv = cachingChannel->ForceCacheEntryValidFor(0);
|
|
PREDICTOR_LOG((" removing any forced validity rv=%" PRIX32,
|
|
static_cast<uint32_t>(rv)));
|
|
}
|
|
|
|
nsAutoCString reqName;
|
|
rv = aRequest->GetName(reqName);
|
|
if (NS_FAILED(rv)) {
|
|
reqName.AssignLiteral("<unknown>");
|
|
}
|
|
|
|
PREDICTOR_LOG((" request %s status %u", reqName.get(), httpStatus));
|
|
|
|
if (mVerifier) {
|
|
mVerifier->OnPredictPrefetch(mURI, httpStatus);
|
|
}
|
|
|
|
return rv;
|
|
}
|
|
|
|
// nsIStreamListener
|
|
NS_IMETHODIMP
|
|
Predictor::PrefetchListener::OnDataAvailable(nsIRequest* aRequest,
|
|
nsIInputStream* aInputStream,
|
|
uint64_t aOffset,
|
|
const uint32_t aCount) {
|
|
uint32_t result;
|
|
return aInputStream->ReadSegments(NS_DiscardSegment, nullptr, aCount,
|
|
&result);
|
|
}
|
|
|
|
// Miscellaneous Predictor
|
|
|
|
void Predictor::UpdateCacheability(nsIURI* sourceURI, nsIURI* targetURI,
|
|
uint32_t httpStatus,
|
|
nsHttpRequestHead& requestHead,
|
|
nsHttpResponseHead* responseHead,
|
|
nsILoadContextInfo* lci, bool isTracking) {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
if (lci && lci->IsPrivate()) {
|
|
PREDICTOR_LOG(("Predictor::UpdateCacheability in PB mode - ignoring"));
|
|
return;
|
|
}
|
|
|
|
if (!sourceURI || !targetURI) {
|
|
PREDICTOR_LOG(
|
|
("Predictor::UpdateCacheability missing source or target uri"));
|
|
return;
|
|
}
|
|
|
|
if (!IsNullOrHttp(sourceURI) || !IsNullOrHttp(targetURI)) {
|
|
PREDICTOR_LOG(("Predictor::UpdateCacheability non-http(s) uri"));
|
|
return;
|
|
}
|
|
|
|
RefPtr<Predictor> self = sSelf;
|
|
if (self) {
|
|
nsAutoCString method;
|
|
requestHead.Method(method);
|
|
|
|
nsAutoCString vary;
|
|
Unused << responseHead->GetHeader(nsHttp::Vary, vary);
|
|
|
|
nsAutoCString cacheControlHeader;
|
|
Unused << responseHead->GetHeader(nsHttp::Cache_Control,
|
|
cacheControlHeader);
|
|
CacheControlParser cacheControl(cacheControlHeader);
|
|
|
|
self->UpdateCacheabilityInternal(sourceURI, targetURI, httpStatus, method,
|
|
*lci->OriginAttributesPtr(), isTracking,
|
|
!vary.IsEmpty(), cacheControl.NoStore());
|
|
}
|
|
}
|
|
|
|
void Predictor::UpdateCacheabilityInternal(
|
|
nsIURI* sourceURI, nsIURI* targetURI, uint32_t httpStatus,
|
|
const nsCString& method, const OriginAttributes& originAttributes,
|
|
bool isTracking, bool couldVary, bool isNoStore) {
|
|
PREDICTOR_LOG(("Predictor::UpdateCacheability httpStatus=%u", httpStatus));
|
|
|
|
nsresult rv;
|
|
|
|
if (!mInitialized) {
|
|
PREDICTOR_LOG((" not initialized"));
|
|
return;
|
|
}
|
|
|
|
if (!StaticPrefs::network_predictor_enabled()) {
|
|
PREDICTOR_LOG((" not enabled"));
|
|
return;
|
|
}
|
|
|
|
nsCOMPtr<nsICacheStorage> cacheDiskStorage;
|
|
|
|
RefPtr<LoadContextInfo> lci = new LoadContextInfo(false, originAttributes);
|
|
|
|
rv = mCacheStorageService->DiskCacheStorage(lci, false,
|
|
getter_AddRefs(cacheDiskStorage));
|
|
if (NS_FAILED(rv)) {
|
|
PREDICTOR_LOG((" cannot get disk cache storage"));
|
|
return;
|
|
}
|
|
|
|
uint32_t openFlags = nsICacheStorage::OPEN_READONLY |
|
|
nsICacheStorage::OPEN_SECRETLY |
|
|
nsICacheStorage::CHECK_MULTITHREADED;
|
|
RefPtr<Predictor::CacheabilityAction> action =
|
|
new Predictor::CacheabilityAction(targetURI, httpStatus, method,
|
|
isTracking, couldVary, isNoStore, this);
|
|
nsAutoCString uri;
|
|
targetURI->GetAsciiSpec(uri);
|
|
PREDICTOR_LOG((" uri=%s action=%p", uri.get(), action.get()));
|
|
cacheDiskStorage->AsyncOpenURI(sourceURI, EmptyCString(), openFlags, action);
|
|
}
|
|
|
|
NS_IMPL_ISUPPORTS(Predictor::CacheabilityAction, nsICacheEntryOpenCallback,
|
|
nsICacheEntryMetaDataVisitor);
|
|
|
|
NS_IMETHODIMP
|
|
Predictor::CacheabilityAction::OnCacheEntryCheck(nsICacheEntry* entry,
|
|
nsIApplicationCache* appCache,
|
|
uint32_t* result) {
|
|
*result = nsICacheEntryOpenCallback::ENTRY_WANTED;
|
|
return NS_OK;
|
|
}
|
|
|
|
namespace {
|
|
enum PrefetchDecisionReason {
|
|
PREFETCHABLE,
|
|
STATUS_NOT_200,
|
|
METHOD_NOT_GET,
|
|
URL_HAS_QUERY_STRING,
|
|
RESOURCE_IS_TRACKING,
|
|
RESOURCE_COULD_VARY,
|
|
RESOURCE_IS_NO_STORE
|
|
};
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
Predictor::CacheabilityAction::OnCacheEntryAvailable(
|
|
nsICacheEntry* entry, bool isNew, nsIApplicationCache* appCache,
|
|
nsresult result) {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
// This is being opened read-only, so isNew should always be false
|
|
MOZ_ASSERT(!isNew);
|
|
|
|
PREDICTOR_LOG(("CacheabilityAction::OnCacheEntryAvailable this=%p", this));
|
|
if (NS_FAILED(result)) {
|
|
// Nothing to do
|
|
PREDICTOR_LOG((" nothing to do result=%" PRIX32 " isNew=%d",
|
|
static_cast<uint32_t>(result), isNew));
|
|
return NS_OK;
|
|
}
|
|
|
|
nsCString strTargetURI;
|
|
nsresult rv = mTargetURI->GetAsciiSpec(strTargetURI);
|
|
if (NS_FAILED(rv)) {
|
|
PREDICTOR_LOG(
|
|
(" GetAsciiSpec returned %" PRIx32, static_cast<uint32_t>(rv)));
|
|
return NS_OK;
|
|
}
|
|
|
|
rv = entry->VisitMetaData(this);
|
|
if (NS_FAILED(rv)) {
|
|
PREDICTOR_LOG(
|
|
(" VisitMetaData returned %" PRIx32, static_cast<uint32_t>(rv)));
|
|
return NS_OK;
|
|
}
|
|
|
|
nsTArray<nsCString> keysToCheck, valuesToCheck;
|
|
keysToCheck.SwapElements(mKeysToCheck);
|
|
valuesToCheck.SwapElements(mValuesToCheck);
|
|
|
|
bool hasQueryString = false;
|
|
nsAutoCString query;
|
|
if (NS_SUCCEEDED(mTargetURI->GetQuery(query)) && !query.IsEmpty()) {
|
|
hasQueryString = true;
|
|
}
|
|
|
|
MOZ_ASSERT(keysToCheck.Length() == valuesToCheck.Length());
|
|
for (size_t i = 0; i < keysToCheck.Length(); ++i) {
|
|
const char* key = keysToCheck[i].BeginReading();
|
|
const char* value = valuesToCheck[i].BeginReading();
|
|
nsCString uri;
|
|
uint32_t hitCount, lastHit, flags;
|
|
|
|
if (!mPredictor->ParseMetaDataEntry(key, value, uri, hitCount, lastHit,
|
|
flags)) {
|
|
PREDICTOR_LOG((" failed to parse key=%s value=%s", key, value));
|
|
continue;
|
|
}
|
|
|
|
if (strTargetURI.Equals(uri)) {
|
|
bool prefetchable = true;
|
|
PrefetchDecisionReason reason = PREFETCHABLE;
|
|
|
|
if (mHttpStatus != 200) {
|
|
prefetchable = false;
|
|
reason = STATUS_NOT_200;
|
|
} else if (!mMethod.EqualsLiteral("GET")) {
|
|
prefetchable = false;
|
|
reason = METHOD_NOT_GET;
|
|
} else if (hasQueryString) {
|
|
prefetchable = false;
|
|
reason = URL_HAS_QUERY_STRING;
|
|
} else if (mIsTracking) {
|
|
prefetchable = false;
|
|
reason = RESOURCE_IS_TRACKING;
|
|
} else if (mCouldVary) {
|
|
prefetchable = false;
|
|
reason = RESOURCE_COULD_VARY;
|
|
} else if (mIsNoStore) {
|
|
// We don't set prefetchable = false yet, because we just want to know
|
|
// what kind of effect this would have on prefetching.
|
|
reason = RESOURCE_IS_NO_STORE;
|
|
}
|
|
|
|
Telemetry::Accumulate(Telemetry::PREDICTOR_PREFETCH_DECISION_REASON,
|
|
reason);
|
|
|
|
if (prefetchable) {
|
|
PREDICTOR_LOG((" marking %s cacheable", key));
|
|
flags |= FLAG_PREFETCHABLE;
|
|
} else {
|
|
PREDICTOR_LOG((" marking %s uncacheable", key));
|
|
flags &= ~FLAG_PREFETCHABLE;
|
|
}
|
|
nsCString newValue;
|
|
MakeMetadataEntry(hitCount, lastHit, flags, newValue);
|
|
entry->SetMetaDataElement(key, newValue.BeginReading());
|
|
break;
|
|
}
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
Predictor::CacheabilityAction::OnMetaDataElement(const char* asciiKey,
|
|
const char* asciiValue) {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
if (!IsURIMetadataElement(asciiKey)) {
|
|
return NS_OK;
|
|
}
|
|
|
|
nsCString key, value;
|
|
key.AssignASCII(asciiKey);
|
|
value.AssignASCII(asciiValue);
|
|
mKeysToCheck.AppendElement(key);
|
|
mValuesToCheck.AppendElement(value);
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
} // namespace net
|
|
} // namespace mozilla
|