summaryrefslogtreecommitdiffstats
path: root/netwerk/base/Predictor.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'netwerk/base/Predictor.cpp')
-rw-r--r--netwerk/base/Predictor.cpp2441
1 files changed, 2441 insertions, 0 deletions
diff --git a/netwerk/base/Predictor.cpp b/netwerk/base/Predictor.cpp
new file mode 100644
index 0000000000..a4fb7792ab
--- /dev/null
+++ b/netwerk/base/Predictor.cpp
@@ -0,0 +1,2441 @@
+/* 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/OriginAttributes.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/SchedulerGroup.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)
+
+// 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);
+
+// 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;
+}
+
+// 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, uint32_t* result) {
+ *result = nsICacheEntryOpenCallback::ENTRY_WANTED;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+Predictor::Action::OnCacheEntryAvailable(nsICacheEntry* entry, bool isNew,
+ 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()
+
+{
+ 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);
+
+ 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);
+ }
+}
+
+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();
+ }
+
+ 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),
+ nsLiteralCString(META_DATA_PREFIX)) &&
+ !nsLiteralCString(SEEN_META_DATA).Equals(key) &&
+ !nsLiteralCString(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);
+
+ mInitialized = true;
+
+ return rv;
+}
+
+namespace {
+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::Shutdown() {
+ if (!NS_IsMainThread()) {
+ MOZ_ASSERT(false, "Predictor::Shutdown called off the main thread!");
+ return;
+ }
+
+ RemoveObserver();
+
+ mInitialized = false;
+}
+
+nsresult Predictor::Create(const nsIID& aIID, void** aResult) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ nsresult rv;
+
+ 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::Handle<JS::Value> 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, 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, ""_ns, 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,
+ nsLiteralCString(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));
+
+ uint32_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, 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, ""_ns, 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 = std::move(mKeysToOperateOn),
+ valuesToOperateOn = std::move(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_SEC_CONTEXT_IS_NULL,
+ nsIContentPolicy::TYPE_OTHER, nullptr, /* nsICookieJarSettings */
+ 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 dom::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 = std::move(mPrefetches),
+ preconnects = std::move(mPreconnects),
+ preresolves = std::move(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_TYPE_DEFAULT,
+ (nsIDNSService::RESOLVE_PRIORITY_MEDIUM |
+ nsIDNSService::RESOLVE_SPECULATE),
+ nullptr, mDNSListener, nullptr, originAttributes,
+ getter_AddRefs(tmpCancelable));
+
+ // Fetch HTTPS RR if needed.
+ if (StaticPrefs::network_dns_upgrade_with_https_rr() ||
+ StaticPrefs::network_dns_use_https_rr_as_altsvc()) {
+ mDnsService->AsyncResolveNative(
+ hostname, nsIDNSService::RESOLVE_TYPE_HTTPSSVC,
+ (nsIDNSService::RESOLVE_PRIORITY_MEDIUM |
+ nsIDNSService::RESOLVE_SPECULATE),
+ nullptr, 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::Handle<JS::Value> 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);
+ SchedulerGroup::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,
+ 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, ""_ns, 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,
+ nsLiteralCString(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 = std::move(mKeysToOperateOn),
+ valuesToOperateOn = std::move(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);
+
+ uint32_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, 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, uint32_t* result) {
+ *result = nsICacheEntryOpenCallback::ENTRY_WANTED;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+Predictor::Resetter::OnCacheEntryAvailable(nsICacheEntry* entry, bool isNew,
+ 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 = std::move(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),
+ nsLiteralCString(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, int64_t altDataSize,
+ uint32_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, 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 = std::move(mURIsToVisit);
+
+ MOZ_ASSERT(mEntriesToVisit == urisToVisit.Length());
+
+ nsTArray<nsCOMPtr<nsILoadContextInfo>> infosToVisit =
+ std::move(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], getter_AddRefs(cacheDiskStorage));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ urisToVisit[i]->GetAsciiSpec(u);
+ rv = cacheDiskStorage->AsyncOpenURI(
+ urisToVisit[i], ""_ns,
+ nsICacheStorage::OPEN_READONLY | nsICacheStorage::OPEN_SECRETLY |
+ nsICacheStorage::CHECK_MULTITHREADED,
+ this);
+ if (NS_FAILED(rv)) {
+ mEntriesToVisit--;
+ if (!mEntriesToVisit) {
+ Complete();
+ return NS_OK;
+ }
+ }
+ }
+
+ 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 : dom::ContentParent::AllProcesses(dom::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 : dom::ContentParent::AllProcesses(dom::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 : dom::ContentParent::AllProcesses(dom::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);
+ Telemetry::AccumulateCategorical(
+ Telemetry::LABELS_PREDICTOR_PREFETCH_USE_STATUS::Not200);
+ 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,
+ 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, ""_ns, openFlags, action);
+}
+
+NS_IMPL_ISUPPORTS(Predictor::CacheabilityAction, nsICacheEntryOpenCallback,
+ nsICacheEntryMetaDataVisitor);
+
+NS_IMETHODIMP
+Predictor::CacheabilityAction::OnCacheEntryCheck(nsICacheEntry* entry,
+ 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,
+ 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 = std::move(mKeysToCheck),
+ valuesToCheck = std::move(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