Bug 1890277: part 4) Add CSPParser support for the `trusted-types` directive, guarded...
[gecko.git] / netwerk / base / Predictor.cpp
blob0c604d9d9abcd4ead53ee8eb3e964d3f3d7540eb
1 /* vim: set ts=2 sts=2 et sw=2: */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 #include <algorithm>
8 #include "Predictor.h"
10 #include "nsAppDirectoryServiceDefs.h"
11 #include "nsICacheStorage.h"
12 #include "nsICachingChannel.h"
13 #include "nsICancelable.h"
14 #include "nsIChannel.h"
15 #include "nsContentUtils.h"
16 #include "nsIDNSService.h"
17 #include "mozilla/dom/Document.h"
18 #include "nsIFile.h"
19 #include "nsIHttpChannel.h"
20 #include "nsIInputStream.h"
21 #include "nsILoadContext.h"
22 #include "nsILoadContextInfo.h"
23 #include "nsILoadGroup.h"
24 #include "nsINetworkPredictorVerifier.h"
25 #include "nsIObserverService.h"
26 #include "nsISpeculativeConnect.h"
27 #include "nsITimer.h"
28 #include "nsIURI.h"
29 #include "nsNetUtil.h"
30 #include "nsServiceManagerUtils.h"
31 #include "nsStreamUtils.h"
32 #include "nsString.h"
33 #include "nsThreadUtils.h"
34 #include "mozilla/Logging.h"
36 #include "mozilla/OriginAttributes.h"
37 #include "mozilla/Preferences.h"
38 #include "mozilla/SchedulerGroup.h"
39 #include "mozilla/StaticPrefs_network.h"
40 #include "mozilla/Telemetry.h"
42 #include "mozilla/net/NeckoCommon.h"
43 #include "mozilla/net/NeckoParent.h"
45 #include "LoadContextInfo.h"
46 #include "mozilla/ipc/URIUtils.h"
47 #include "SerializedLoadContext.h"
48 #include "mozilla/net/NeckoChild.h"
50 #include "mozilla/dom/ContentParent.h"
51 #include "mozilla/ClearOnShutdown.h"
53 #include "CacheControlParser.h"
54 #include "ReferrerInfo.h"
56 using namespace mozilla;
58 namespace mozilla {
59 namespace net {
61 Predictor* Predictor::sSelf = nullptr;
63 static LazyLogModule gPredictorLog("NetworkPredictor");
65 #define PREDICTOR_LOG(args) \
66 MOZ_LOG(gPredictorLog, mozilla::LogLevel::Debug, args)
68 #define NOW_IN_SECONDS() static_cast<uint32_t>(PR_Now() / PR_USEC_PER_SEC)
70 // All these time values are in sec
71 static const uint32_t ONE_DAY = 86400U;
72 static const uint32_t ONE_WEEK = 7U * ONE_DAY;
73 static const uint32_t ONE_MONTH = 30U * ONE_DAY;
74 static const uint32_t ONE_YEAR = 365U * ONE_DAY;
76 // Version of metadata entries we expect
77 static const uint32_t METADATA_VERSION = 1;
79 // Flags available in entries
80 // FLAG_PREFETCHABLE - we have determined that this item is eligible for
81 // prefetch
82 static const uint32_t FLAG_PREFETCHABLE = 1 << 0;
84 // We save 12 bits in the "flags" section of our metadata for actual flags, the
85 // rest are to keep track of a rolling count of which loads a resource has been
86 // used on to determine if we can prefetch that resource or not;
87 static const uint8_t kRollingLoadOffset = 12;
88 static const int32_t kMaxPrefetchRollingLoadCount = 20;
89 static const uint32_t kFlagsMask = ((1 << kRollingLoadOffset) - 1);
91 // ID Extensions for cache entries
92 #define PREDICTOR_ORIGIN_EXTENSION "predictor-origin"
94 // Get the full origin (scheme, host, port) out of a URI (maybe should be part
95 // of nsIURI instead?)
96 static nsresult ExtractOrigin(nsIURI* uri, nsIURI** originUri) {
97 nsAutoCString s;
98 nsresult rv = nsContentUtils::GetWebExposedOriginSerialization(uri, s);
99 NS_ENSURE_SUCCESS(rv, rv);
101 return NS_NewURI(originUri, s);
104 // All URIs we get passed *must* be http or https if they're not null. This
105 // helps ensure that.
106 static bool IsNullOrHttp(nsIURI* uri) {
107 if (!uri) {
108 return true;
111 return uri->SchemeIs("http") || uri->SchemeIs("https");
114 // Listener for the speculative DNS requests we'll fire off, which just ignores
115 // the result (since we're just trying to warm the cache). This also exists to
116 // reduce round-trips to the main thread, by being something threadsafe the
117 // Predictor can use.
119 NS_IMPL_ISUPPORTS(Predictor::DNSListener, nsIDNSListener);
121 NS_IMETHODIMP
122 Predictor::DNSListener::OnLookupComplete(nsICancelable* request,
123 nsIDNSRecord* rec, nsresult status) {
124 return NS_OK;
127 // Class to proxy important information from the initial predictor call through
128 // the cache API and back into the internals of the predictor. We can't use the
129 // predictor itself, as it may have multiple actions in-flight, and each action
130 // has different parameters.
131 NS_IMPL_ISUPPORTS(Predictor::Action, nsICacheEntryOpenCallback);
133 Predictor::Action::Action(bool fullUri, bool predict, Predictor::Reason reason,
134 nsIURI* targetURI, nsIURI* sourceURI,
135 nsINetworkPredictorVerifier* verifier,
136 Predictor* predictor)
137 : mFullUri(fullUri),
138 mPredict(predict),
139 mTargetURI(targetURI),
140 mSourceURI(sourceURI),
141 mVerifier(verifier),
142 mStackCount(0),
143 mPredictor(predictor) {
144 mStartTime = TimeStamp::Now();
145 if (mPredict) {
146 mPredictReason = reason.mPredict;
147 } else {
148 mLearnReason = reason.mLearn;
152 Predictor::Action::Action(bool fullUri, bool predict, Predictor::Reason reason,
153 nsIURI* targetURI, nsIURI* sourceURI,
154 nsINetworkPredictorVerifier* verifier,
155 Predictor* predictor, uint8_t stackCount)
156 : mFullUri(fullUri),
157 mPredict(predict),
158 mTargetURI(targetURI),
159 mSourceURI(sourceURI),
160 mVerifier(verifier),
161 mStackCount(stackCount),
162 mPredictor(predictor) {
163 mStartTime = TimeStamp::Now();
164 if (mPredict) {
165 mPredictReason = reason.mPredict;
166 } else {
167 mLearnReason = reason.mLearn;
171 NS_IMETHODIMP
172 Predictor::Action::OnCacheEntryCheck(nsICacheEntry* entry, uint32_t* result) {
173 *result = nsICacheEntryOpenCallback::ENTRY_WANTED;
174 return NS_OK;
177 NS_IMETHODIMP
178 Predictor::Action::OnCacheEntryAvailable(nsICacheEntry* entry, bool isNew,
179 nsresult result) {
180 MOZ_ASSERT(NS_IsMainThread(), "Got cache entry off main thread!");
182 nsAutoCString targetURI, sourceURI;
183 mTargetURI->GetAsciiSpec(targetURI);
184 if (mSourceURI) {
185 mSourceURI->GetAsciiSpec(sourceURI);
187 PREDICTOR_LOG(
188 ("OnCacheEntryAvailable %p called. entry=%p mFullUri=%d mPredict=%d "
189 "mPredictReason=%d mLearnReason=%d mTargetURI=%s "
190 "mSourceURI=%s mStackCount=%d isNew=%d result=0x%08" PRIx32,
191 this, entry, mFullUri, mPredict, mPredictReason, mLearnReason,
192 targetURI.get(), sourceURI.get(), mStackCount, isNew,
193 static_cast<uint32_t>(result)));
194 if (NS_FAILED(result)) {
195 PREDICTOR_LOG(
196 ("OnCacheEntryAvailable %p FAILED to get cache entry (0x%08" PRIX32
197 "). Aborting.",
198 this, static_cast<uint32_t>(result)));
199 return NS_OK;
201 Telemetry::AccumulateTimeDelta(Telemetry::PREDICTOR_WAIT_TIME, mStartTime);
202 if (mPredict) {
203 bool predicted =
204 mPredictor->PredictInternal(mPredictReason, entry, isNew, mFullUri,
205 mTargetURI, mVerifier, mStackCount);
206 Telemetry::AccumulateTimeDelta(Telemetry::PREDICTOR_PREDICT_WORK_TIME,
207 mStartTime);
208 if (predicted) {
209 Telemetry::AccumulateTimeDelta(
210 Telemetry::PREDICTOR_PREDICT_TIME_TO_ACTION, mStartTime);
211 } else {
212 Telemetry::AccumulateTimeDelta(
213 Telemetry::PREDICTOR_PREDICT_TIME_TO_INACTION, mStartTime);
215 } else {
216 mPredictor->LearnInternal(mLearnReason, entry, isNew, mFullUri, mTargetURI,
217 mSourceURI);
218 Telemetry::AccumulateTimeDelta(Telemetry::PREDICTOR_LEARN_WORK_TIME,
219 mStartTime);
222 return NS_OK;
225 NS_IMPL_ISUPPORTS(Predictor, nsINetworkPredictor, nsIObserver,
226 nsISpeculativeConnectionOverrider, nsIInterfaceRequestor,
227 nsICacheEntryMetaDataVisitor, nsINetworkPredictorVerifier)
229 Predictor::Predictor()
232 MOZ_ASSERT(!sSelf, "multiple Predictor instances!");
233 sSelf = this;
236 Predictor::~Predictor() {
237 if (mInitialized) Shutdown();
239 sSelf = nullptr;
242 // Predictor::nsIObserver
244 nsresult Predictor::InstallObserver() {
245 MOZ_ASSERT(NS_IsMainThread(), "Installing observer off main thread");
247 nsresult rv = NS_OK;
248 nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
249 if (!obs) {
250 return NS_ERROR_NOT_AVAILABLE;
253 rv = obs->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false);
254 NS_ENSURE_SUCCESS(rv, rv);
256 return rv;
259 void Predictor::RemoveObserver() {
260 MOZ_ASSERT(NS_IsMainThread(), "Removing observer off main thread");
262 nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
263 if (obs) {
264 obs->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID);
268 NS_IMETHODIMP
269 Predictor::Observe(nsISupports* subject, const char* topic,
270 const char16_t* data_unicode) {
271 nsresult rv = NS_OK;
272 MOZ_ASSERT(NS_IsMainThread(),
273 "Predictor observing something off main thread!");
275 if (!strcmp(NS_XPCOM_SHUTDOWN_OBSERVER_ID, topic)) {
276 Shutdown();
279 return rv;
282 // Predictor::nsISpeculativeConnectionOverrider
284 NS_IMETHODIMP
285 Predictor::GetIgnoreIdle(bool* ignoreIdle) {
286 *ignoreIdle = true;
287 return NS_OK;
290 NS_IMETHODIMP
291 Predictor::GetParallelSpeculativeConnectLimit(
292 uint32_t* parallelSpeculativeConnectLimit) {
293 *parallelSpeculativeConnectLimit = 6;
294 return NS_OK;
297 NS_IMETHODIMP
298 Predictor::GetIsFromPredictor(bool* isFromPredictor) {
299 *isFromPredictor = true;
300 return NS_OK;
303 NS_IMETHODIMP
304 Predictor::GetAllow1918(bool* allow1918) {
305 *allow1918 = false;
306 return NS_OK;
309 // Predictor::nsIInterfaceRequestor
311 NS_IMETHODIMP
312 Predictor::GetInterface(const nsIID& iid, void** result) {
313 return QueryInterface(iid, result);
316 // Predictor::nsICacheEntryMetaDataVisitor
318 #define SEEN_META_DATA "predictor::seen"
319 #define RESOURCE_META_DATA "predictor::resource-count"
320 #define META_DATA_PREFIX "predictor::"
322 static bool IsURIMetadataElement(const char* key) {
323 return StringBeginsWith(nsDependentCString(key),
324 nsLiteralCString(META_DATA_PREFIX)) &&
325 !nsLiteralCString(SEEN_META_DATA).Equals(key) &&
326 !nsLiteralCString(RESOURCE_META_DATA).Equals(key);
329 nsresult Predictor::OnMetaDataElement(const char* asciiKey,
330 const char* asciiValue) {
331 MOZ_ASSERT(NS_IsMainThread());
333 if (!IsURIMetadataElement(asciiKey)) {
334 // This isn't a bit of metadata we care about
335 return NS_OK;
338 nsCString key, value;
339 key.AssignASCII(asciiKey);
340 value.AssignASCII(asciiValue);
341 mKeysToOperateOn.AppendElement(key);
342 mValuesToOperateOn.AppendElement(value);
344 return NS_OK;
347 // Predictor::nsINetworkPredictor
349 nsresult Predictor::Init() {
350 MOZ_DIAGNOSTIC_ASSERT(!IsNeckoChild());
352 if (!NS_IsMainThread()) {
353 MOZ_ASSERT(false, "Predictor::Init called off the main thread!");
354 return NS_ERROR_UNEXPECTED;
357 nsresult rv = NS_OK;
359 rv = InstallObserver();
360 NS_ENSURE_SUCCESS(rv, rv);
362 mLastStartupTime = mStartupTime = NOW_IN_SECONDS();
364 if (!mDNSListener) {
365 mDNSListener = new DNSListener();
368 mCacheStorageService =
369 do_GetService("@mozilla.org/netwerk/cache-storage-service;1", &rv);
370 NS_ENSURE_SUCCESS(rv, rv);
372 mSpeculativeService = do_GetService("@mozilla.org/network/io-service;1", &rv);
373 NS_ENSURE_SUCCESS(rv, rv);
375 rv = NS_NewURI(getter_AddRefs(mStartupURI), "predictor://startup");
376 NS_ENSURE_SUCCESS(rv, rv);
378 mDnsService = do_GetService("@mozilla.org/network/dns-service;1", &rv);
379 NS_ENSURE_SUCCESS(rv, rv);
381 mInitialized = true;
383 return rv;
386 namespace {
387 class PredictorLearnRunnable final : public Runnable {
388 public:
389 PredictorLearnRunnable(nsIURI* targetURI, nsIURI* sourceURI,
390 PredictorLearnReason reason,
391 const OriginAttributes& oa)
392 : Runnable("PredictorLearnRunnable"),
393 mTargetURI(targetURI),
394 mSourceURI(sourceURI),
395 mReason(reason),
396 mOA(oa) {
397 MOZ_DIAGNOSTIC_ASSERT(targetURI, "Must have a target URI");
400 ~PredictorLearnRunnable() = default;
402 NS_IMETHOD Run() override {
403 if (!gNeckoChild) {
404 // This may have gone away between when this runnable was dispatched and
405 // when it actually runs, so let's be safe here, even though we asserted
406 // earlier.
407 PREDICTOR_LOG(("predictor::learn (async) gNeckoChild went away"));
408 return NS_OK;
411 PREDICTOR_LOG(("predictor::learn (async) forwarding to parent"));
412 gNeckoChild->SendPredLearn(mTargetURI, mSourceURI, mReason, mOA);
414 return NS_OK;
417 private:
418 nsCOMPtr<nsIURI> mTargetURI;
419 nsCOMPtr<nsIURI> mSourceURI;
420 PredictorLearnReason mReason;
421 const OriginAttributes mOA;
424 } // namespace
426 void Predictor::Shutdown() {
427 if (!NS_IsMainThread()) {
428 MOZ_ASSERT(false, "Predictor::Shutdown called off the main thread!");
429 return;
432 RemoveObserver();
434 mInitialized = false;
437 nsresult Predictor::Create(const nsIID& aIID, void** aResult) {
438 MOZ_ASSERT(NS_IsMainThread());
440 nsresult rv;
442 RefPtr<Predictor> svc = new Predictor();
443 if (IsNeckoChild()) {
444 NeckoChild::InitNeckoChild();
446 // Child threads only need to be call into the public interface methods
447 // so we don't bother with initialization
448 return svc->QueryInterface(aIID, aResult);
451 rv = svc->Init();
452 if (NS_FAILED(rv)) {
453 PREDICTOR_LOG(("Failed to initialize predictor, predictor will be a noop"));
456 // We treat init failure the same as the service being disabled, since this
457 // is all an optimization anyway. No need to freak people out. That's why we
458 // gladly continue on QI'ing here.
459 rv = svc->QueryInterface(aIID, aResult);
461 return rv;
464 NS_IMETHODIMP
465 Predictor::Predict(nsIURI* targetURI, nsIURI* sourceURI,
466 PredictorPredictReason reason,
467 JS::Handle<JS::Value> originAttributes,
468 nsINetworkPredictorVerifier* verifier, JSContext* aCx) {
469 OriginAttributes attrs;
471 if (!originAttributes.isObject() || !attrs.Init(aCx, originAttributes)) {
472 return NS_ERROR_INVALID_ARG;
475 return PredictNative(targetURI, sourceURI, reason, attrs, verifier);
478 // Called from the main thread to initiate predictive actions
479 NS_IMETHODIMP
480 Predictor::PredictNative(nsIURI* targetURI, nsIURI* sourceURI,
481 PredictorPredictReason reason,
482 const OriginAttributes& originAttributes,
483 nsINetworkPredictorVerifier* verifier) {
484 MOZ_ASSERT(NS_IsMainThread(),
485 "Predictor interface methods must be called on the main thread");
487 PREDICTOR_LOG(("Predictor::Predict"));
489 if (IsNeckoChild()) {
490 if (!gNeckoChild) {
491 return NS_ERROR_FAILURE;
494 PREDICTOR_LOG((" called on child process"));
495 // If two different threads are predicting concurently, this will be
496 // overwritten. Thankfully, we only use this in tests, which will
497 // overwrite mVerifier perhaps multiple times for each individual test;
498 // however, within each test, the multiple predict calls should have the
499 // same verifier.
500 if (verifier) {
501 PREDICTOR_LOG((" was given a verifier"));
502 mChildVerifier = verifier;
504 PREDICTOR_LOG((" forwarding to parent process"));
505 gNeckoChild->SendPredPredict(targetURI, sourceURI, reason, originAttributes,
506 verifier);
507 return NS_OK;
510 PREDICTOR_LOG((" called on parent process"));
512 if (!mInitialized) {
513 PREDICTOR_LOG((" not initialized"));
514 return NS_OK;
517 if (!StaticPrefs::network_predictor_enabled()) {
518 PREDICTOR_LOG((" not enabled"));
519 return NS_OK;
522 if (originAttributes.mPrivateBrowsingId > 0) {
523 // Don't want to do anything in PB mode
524 PREDICTOR_LOG((" in PB mode"));
525 return NS_OK;
528 if (!IsNullOrHttp(targetURI) || !IsNullOrHttp(sourceURI)) {
529 // Nothing we can do for non-HTTP[S] schemes
530 PREDICTOR_LOG((" got non-http[s] URI"));
531 return NS_OK;
534 // Ensure we've been given the appropriate arguments for the kind of
535 // prediction we're being asked to do
536 nsCOMPtr<nsIURI> uriKey = targetURI;
537 nsCOMPtr<nsIURI> originKey;
538 switch (reason) {
539 case nsINetworkPredictor::PREDICT_LINK:
540 if (!targetURI || !sourceURI) {
541 PREDICTOR_LOG((" link invalid URI state"));
542 return NS_ERROR_INVALID_ARG;
544 // Link hover is a special case where we can predict without hitting the
545 // db, so let's go ahead and fire off that prediction here.
546 PredictForLink(targetURI, sourceURI, originAttributes, verifier);
547 return NS_OK;
548 case nsINetworkPredictor::PREDICT_LOAD:
549 if (!targetURI || sourceURI) {
550 PREDICTOR_LOG((" load invalid URI state"));
551 return NS_ERROR_INVALID_ARG;
553 break;
554 case nsINetworkPredictor::PREDICT_STARTUP:
555 if (targetURI || sourceURI) {
556 PREDICTOR_LOG((" startup invalid URI state"));
557 return NS_ERROR_INVALID_ARG;
559 uriKey = mStartupURI;
560 originKey = mStartupURI;
561 break;
562 default:
563 PREDICTOR_LOG((" invalid reason"));
564 return NS_ERROR_INVALID_ARG;
567 Predictor::Reason argReason{};
568 argReason.mPredict = reason;
570 // First we open the regular cache entry, to ensure we don't gum up the works
571 // waiting on the less-important predictor-only cache entry
572 RefPtr<Predictor::Action> uriAction = new Predictor::Action(
573 Predictor::Action::IS_FULL_URI, Predictor::Action::DO_PREDICT, argReason,
574 targetURI, nullptr, verifier, this);
575 nsAutoCString uriKeyStr;
576 uriKey->GetAsciiSpec(uriKeyStr);
577 PREDICTOR_LOG((" Predict uri=%s reason=%d action=%p", uriKeyStr.get(),
578 reason, uriAction.get()));
580 nsCOMPtr<nsICacheStorage> cacheDiskStorage;
582 RefPtr<LoadContextInfo> lci = new LoadContextInfo(false, originAttributes);
584 nsresult rv = mCacheStorageService->DiskCacheStorage(
585 lci, getter_AddRefs(cacheDiskStorage));
586 NS_ENSURE_SUCCESS(rv, rv);
588 uint32_t openFlags =
589 nsICacheStorage::OPEN_READONLY | nsICacheStorage::OPEN_SECRETLY |
590 nsICacheStorage::OPEN_PRIORITY | nsICacheStorage::CHECK_MULTITHREADED;
591 cacheDiskStorage->AsyncOpenURI(uriKey, ""_ns, openFlags, uriAction);
593 // Now we do the origin-only (and therefore predictor-only) entry
594 nsCOMPtr<nsIURI> targetOrigin;
595 rv = ExtractOrigin(uriKey, getter_AddRefs(targetOrigin));
596 NS_ENSURE_SUCCESS(rv, rv);
597 if (!originKey) {
598 originKey = targetOrigin;
601 RefPtr<Predictor::Action> originAction = new Predictor::Action(
602 Predictor::Action::IS_ORIGIN, Predictor::Action::DO_PREDICT, argReason,
603 targetOrigin, nullptr, verifier, this);
604 nsAutoCString originKeyStr;
605 originKey->GetAsciiSpec(originKeyStr);
606 PREDICTOR_LOG((" Predict origin=%s reason=%d action=%p",
607 originKeyStr.get(), reason, originAction.get()));
608 openFlags = nsICacheStorage::OPEN_READONLY | nsICacheStorage::OPEN_SECRETLY |
609 nsICacheStorage::CHECK_MULTITHREADED;
610 cacheDiskStorage->AsyncOpenURI(originKey,
611 nsLiteralCString(PREDICTOR_ORIGIN_EXTENSION),
612 openFlags, originAction);
614 PREDICTOR_LOG((" predict returning"));
615 return NS_OK;
618 bool Predictor::PredictInternal(PredictorPredictReason reason,
619 nsICacheEntry* entry, bool isNew, bool fullUri,
620 nsIURI* targetURI,
621 nsINetworkPredictorVerifier* verifier,
622 uint8_t stackCount) {
623 MOZ_ASSERT(NS_IsMainThread());
625 PREDICTOR_LOG(("Predictor::PredictInternal"));
626 bool rv = false;
628 nsCOMPtr<nsILoadContextInfo> lci;
629 entry->GetLoadContextInfo(getter_AddRefs(lci));
631 if (!lci) {
632 return rv;
635 if (reason == nsINetworkPredictor::PREDICT_LOAD) {
636 MaybeLearnForStartup(targetURI, fullUri, *lci->OriginAttributesPtr());
639 if (isNew) {
640 // nothing else we can do here
641 PREDICTOR_LOG((" new entry"));
642 return rv;
645 switch (reason) {
646 case nsINetworkPredictor::PREDICT_LOAD:
647 rv = PredictForPageload(entry, targetURI, stackCount, fullUri, verifier);
648 break;
649 case nsINetworkPredictor::PREDICT_STARTUP:
650 rv = PredictForStartup(entry, fullUri, verifier);
651 break;
652 default:
653 PREDICTOR_LOG((" invalid reason"));
654 MOZ_ASSERT(false, "Got unexpected value for prediction reason");
657 return rv;
660 void Predictor::PredictForLink(nsIURI* targetURI, nsIURI* sourceURI,
661 const OriginAttributes& originAttributes,
662 nsINetworkPredictorVerifier* verifier) {
663 MOZ_ASSERT(NS_IsMainThread());
665 PREDICTOR_LOG(("Predictor::PredictForLink"));
666 if (!mSpeculativeService) {
667 PREDICTOR_LOG((" missing speculative service"));
668 return;
671 if (!StaticPrefs::network_predictor_enable_hover_on_ssl()) {
672 if (sourceURI->SchemeIs("https")) {
673 // We don't want to predict from an HTTPS page, to avoid info leakage
674 PREDICTOR_LOG((" Not predicting for link hover - on an SSL page"));
675 return;
679 nsCOMPtr<nsIPrincipal> principal =
680 BasePrincipal::CreateContentPrincipal(targetURI, originAttributes);
682 mSpeculativeService->SpeculativeConnect(targetURI, principal, nullptr, false);
683 if (verifier) {
684 PREDICTOR_LOG((" sending verification"));
685 verifier->OnPredictPreconnect(targetURI);
689 // This is the driver for prediction based on a new pageload.
690 static const uint8_t MAX_PAGELOAD_DEPTH = 10;
691 bool Predictor::PredictForPageload(nsICacheEntry* entry, nsIURI* targetURI,
692 uint8_t stackCount, bool fullUri,
693 nsINetworkPredictorVerifier* verifier) {
694 MOZ_ASSERT(NS_IsMainThread());
696 PREDICTOR_LOG(("Predictor::PredictForPageload"));
698 if (stackCount > MAX_PAGELOAD_DEPTH) {
699 PREDICTOR_LOG((" exceeded recursion depth!"));
700 return false;
703 uint32_t lastLoad;
704 nsresult rv = entry->GetLastFetched(&lastLoad);
705 NS_ENSURE_SUCCESS(rv, false);
707 int32_t globalDegradation = CalculateGlobalDegradation(lastLoad);
708 PREDICTOR_LOG((" globalDegradation = %d", globalDegradation));
710 uint32_t loadCount;
711 rv = entry->GetFetchCount(&loadCount);
712 NS_ENSURE_SUCCESS(rv, false);
714 nsCOMPtr<nsILoadContextInfo> lci;
716 rv = entry->GetLoadContextInfo(getter_AddRefs(lci));
717 NS_ENSURE_SUCCESS(rv, false);
719 nsCOMPtr<nsIURI> redirectURI;
720 if (WouldRedirect(entry, loadCount, lastLoad, globalDegradation,
721 getter_AddRefs(redirectURI))) {
722 mPreconnects.AppendElement(redirectURI);
723 Predictor::Reason reason{};
724 reason.mPredict = nsINetworkPredictor::PREDICT_LOAD;
725 RefPtr<Predictor::Action> redirectAction = new Predictor::Action(
726 Predictor::Action::IS_FULL_URI, Predictor::Action::DO_PREDICT, reason,
727 redirectURI, nullptr, verifier, this, stackCount + 1);
728 nsAutoCString redirectUriString;
729 redirectURI->GetAsciiSpec(redirectUriString);
731 nsCOMPtr<nsICacheStorage> cacheDiskStorage;
733 rv = mCacheStorageService->DiskCacheStorage(
734 lci, getter_AddRefs(cacheDiskStorage));
735 NS_ENSURE_SUCCESS(rv, false);
737 PREDICTOR_LOG((" Predict redirect uri=%s action=%p",
738 redirectUriString.get(), redirectAction.get()));
739 uint32_t openFlags =
740 nsICacheStorage::OPEN_READONLY | nsICacheStorage::OPEN_SECRETLY |
741 nsICacheStorage::OPEN_PRIORITY | nsICacheStorage::CHECK_MULTITHREADED;
742 cacheDiskStorage->AsyncOpenURI(redirectURI, ""_ns, openFlags,
743 redirectAction);
744 return RunPredictions(nullptr, *lci->OriginAttributesPtr(), verifier);
747 CalculatePredictions(entry, targetURI, lastLoad, loadCount, globalDegradation,
748 fullUri);
750 return RunPredictions(targetURI, *lci->OriginAttributesPtr(), verifier);
753 // This is the driver for predicting at browser startup time based on pages that
754 // have previously been loaded close to startup.
755 bool Predictor::PredictForStartup(nsICacheEntry* entry, bool fullUri,
756 nsINetworkPredictorVerifier* verifier) {
757 MOZ_ASSERT(NS_IsMainThread());
759 PREDICTOR_LOG(("Predictor::PredictForStartup"));
761 nsCOMPtr<nsILoadContextInfo> lci;
763 nsresult rv = entry->GetLoadContextInfo(getter_AddRefs(lci));
764 NS_ENSURE_SUCCESS(rv, false);
766 int32_t globalDegradation = CalculateGlobalDegradation(mLastStartupTime);
767 CalculatePredictions(entry, nullptr, mLastStartupTime, mStartupCount,
768 globalDegradation, fullUri);
769 return RunPredictions(nullptr, *lci->OriginAttributesPtr(), verifier);
772 // This calculates how much to degrade our confidence in our data based on
773 // the last time this top-level resource was loaded. This "global degradation"
774 // applies to *all* subresources we have associated with the top-level
775 // resource. This will be in addition to any reduction in confidence we have
776 // associated with a particular subresource.
777 int32_t Predictor::CalculateGlobalDegradation(uint32_t lastLoad) {
778 MOZ_ASSERT(NS_IsMainThread());
780 int32_t globalDegradation;
781 uint32_t delta = NOW_IN_SECONDS() - lastLoad;
782 if (delta < ONE_DAY) {
783 globalDegradation = StaticPrefs::network_predictor_page_degradation_day();
784 } else if (delta < ONE_WEEK) {
785 globalDegradation = StaticPrefs::network_predictor_page_degradation_week();
786 } else if (delta < ONE_MONTH) {
787 globalDegradation = StaticPrefs::network_predictor_page_degradation_month();
788 } else if (delta < ONE_YEAR) {
789 globalDegradation = StaticPrefs::network_predictor_page_degradation_year();
790 } else {
791 globalDegradation = StaticPrefs::network_predictor_page_degradation_max();
794 Telemetry::Accumulate(Telemetry::PREDICTOR_GLOBAL_DEGRADATION,
795 globalDegradation);
796 return globalDegradation;
799 // This calculates our overall confidence that a particular subresource will be
800 // loaded as part of a top-level load.
801 // @param hitCount - the number of times we have loaded this subresource as part
802 // of this top-level load
803 // @param hitsPossible - the number of times we have performed this top-level
804 // load
805 // @param lastHit - the timestamp of the last time we loaded this subresource as
806 // part of this top-level load
807 // @param lastPossible - the timestamp of the last time we performed this
808 // top-level load
809 // @param globalDegradation - the degradation for this top-level load as
810 // determined by CalculateGlobalDegradation
811 int32_t Predictor::CalculateConfidence(uint32_t hitCount, uint32_t hitsPossible,
812 uint32_t lastHit, uint32_t lastPossible,
813 int32_t globalDegradation) {
814 MOZ_ASSERT(NS_IsMainThread());
816 Telemetry::AutoCounter<Telemetry::PREDICTOR_PREDICTIONS_CALCULATED>
817 predictionsCalculated;
818 ++predictionsCalculated;
820 if (!hitsPossible) {
821 return 0;
824 int32_t baseConfidence = (hitCount * 100) / hitsPossible;
825 int32_t maxConfidence = 100;
826 int32_t confidenceDegradation = 0;
828 if (lastHit < lastPossible) {
829 // We didn't load this subresource the last time this top-level load was
830 // performed, so let's not bother preconnecting (at the very least).
831 maxConfidence =
832 StaticPrefs::network_predictor_preconnect_min_confidence() - 1;
834 // Now calculate how much we want to degrade our confidence based on how
835 // long it's been between the last time we did this top-level load and the
836 // last time this top-level load included this subresource.
837 PRTime delta = lastPossible - lastHit;
838 if (delta == 0) {
839 confidenceDegradation = 0;
840 } else if (delta < ONE_DAY) {
841 confidenceDegradation =
842 StaticPrefs::network_predictor_subresource_degradation_day();
843 } else if (delta < ONE_WEEK) {
844 confidenceDegradation =
845 StaticPrefs::network_predictor_subresource_degradation_week();
846 } else if (delta < ONE_MONTH) {
847 confidenceDegradation =
848 StaticPrefs::network_predictor_subresource_degradation_month();
849 } else if (delta < ONE_YEAR) {
850 confidenceDegradation =
851 StaticPrefs::network_predictor_subresource_degradation_year();
852 } else {
853 confidenceDegradation =
854 StaticPrefs::network_predictor_subresource_degradation_max();
855 maxConfidence = 0;
859 // Calculate our confidence and clamp it to between 0 and maxConfidence
860 // (<= 100)
861 int32_t confidence =
862 baseConfidence - confidenceDegradation - globalDegradation;
863 confidence = std::max(confidence, 0);
864 confidence = std::min(confidence, maxConfidence);
866 Telemetry::Accumulate(Telemetry::PREDICTOR_BASE_CONFIDENCE, baseConfidence);
867 Telemetry::Accumulate(Telemetry::PREDICTOR_SUBRESOURCE_DEGRADATION,
868 confidenceDegradation);
869 Telemetry::Accumulate(Telemetry::PREDICTOR_CONFIDENCE, confidence);
870 return confidence;
873 static void MakeMetadataEntry(const uint32_t hitCount, const uint32_t lastHit,
874 const uint32_t flags, nsCString& newValue) {
875 newValue.Truncate();
876 newValue.AppendInt(METADATA_VERSION);
877 newValue.Append(',');
878 newValue.AppendInt(hitCount);
879 newValue.Append(',');
880 newValue.AppendInt(lastHit);
881 newValue.Append(',');
882 newValue.AppendInt(flags);
885 // On every page load, the rolling window gets shifted by one bit, leaving the
886 // lowest bit at 0, to indicate that the subresource in question has not been
887 // seen on the most recent page load. If, at some point later during the page
888 // load, the subresource is seen again, we will then set the lowest bit to 1.
889 // This is how we keep track of how many of the last n pageloads (for n <= 20) a
890 // particular subresource has been seen. The rolling window is kept in the upper
891 // 20 bits of the flags element of the metadata. This saves 12 bits for regular
892 // old flags.
893 void Predictor::UpdateRollingLoadCount(nsICacheEntry* entry,
894 const uint32_t flags, const char* key,
895 const uint32_t hitCount,
896 const uint32_t lastHit) {
897 // Extract just the rolling load count from the flags, shift it to clear the
898 // lowest bit, and put the new value with the existing flags.
899 uint32_t rollingLoadCount = flags & ~kFlagsMask;
900 rollingLoadCount <<= 1;
901 uint32_t newFlags = (flags & kFlagsMask) | rollingLoadCount;
903 // Finally, update the metadata on the cache entry.
904 nsAutoCString newValue;
905 MakeMetadataEntry(hitCount, lastHit, newFlags, newValue);
906 entry->SetMetaDataElement(key, newValue.BeginReading());
909 uint32_t Predictor::ClampedPrefetchRollingLoadCount() {
910 int32_t n = StaticPrefs::network_predictor_prefetch_rolling_load_count();
911 if (n < 0) {
912 return 0;
914 if (n > kMaxPrefetchRollingLoadCount) {
915 return kMaxPrefetchRollingLoadCount;
917 return n;
920 void Predictor::CalculatePredictions(nsICacheEntry* entry, nsIURI* referrer,
921 uint32_t lastLoad, uint32_t loadCount,
922 int32_t globalDegradation, bool fullUri) {
923 MOZ_ASSERT(NS_IsMainThread());
925 // Since the visitor gets called under a cache lock, all we do there is get
926 // copies of the keys/values we care about, and then do the real work here
927 entry->VisitMetaData(this);
928 nsTArray<nsCString> keysToOperateOn = std::move(mKeysToOperateOn),
929 valuesToOperateOn = std::move(mValuesToOperateOn);
931 MOZ_ASSERT(keysToOperateOn.Length() == valuesToOperateOn.Length());
932 for (size_t i = 0; i < keysToOperateOn.Length(); ++i) {
933 const char* key = keysToOperateOn[i].BeginReading();
934 const char* value = valuesToOperateOn[i].BeginReading();
936 nsCString uri;
937 uint32_t hitCount, lastHit, flags;
938 if (!ParseMetaDataEntry(key, value, uri, hitCount, lastHit, flags)) {
939 // This failed, get rid of it so we don't waste space
940 entry->SetMetaDataElement(key, nullptr);
941 continue;
944 int32_t confidence = CalculateConfidence(hitCount, loadCount, lastHit,
945 lastLoad, globalDegradation);
946 if (fullUri) {
947 UpdateRollingLoadCount(entry, flags, key, hitCount, lastHit);
949 PREDICTOR_LOG(("CalculatePredictions key=%s value=%s confidence=%d", key,
950 value, confidence));
951 PrefetchIgnoreReason reason = PREFETCH_OK;
952 if (!fullUri) {
953 // Not full URI - don't prefetch! No sense in it!
954 PREDICTOR_LOG((" forcing non-cacheability - not full URI"));
955 if (flags & FLAG_PREFETCHABLE) {
956 // This only applies if we had somehow otherwise marked this
957 // prefetchable.
958 reason = NOT_FULL_URI;
960 flags &= ~FLAG_PREFETCHABLE;
961 } else if (!referrer) {
962 // No referrer means we can't prefetch, so pretend it's non-cacheable,
963 // no matter what.
964 PREDICTOR_LOG((" forcing non-cacheability - no referrer"));
965 if (flags & FLAG_PREFETCHABLE) {
966 // This only applies if we had somehow otherwise marked this
967 // prefetchable.
968 reason = NO_REFERRER;
970 flags &= ~FLAG_PREFETCHABLE;
971 } else {
972 uint32_t expectedRollingLoadCount =
973 (1 << ClampedPrefetchRollingLoadCount()) - 1;
974 expectedRollingLoadCount <<= kRollingLoadOffset;
975 if ((flags & expectedRollingLoadCount) != expectedRollingLoadCount) {
976 PREDICTOR_LOG((" forcing non-cacheability - missed a load"));
977 if (flags & FLAG_PREFETCHABLE) {
978 // This only applies if we had somehow otherwise marked this
979 // prefetchable.
980 reason = MISSED_A_LOAD;
982 flags &= ~FLAG_PREFETCHABLE;
986 PREDICTOR_LOG((" setting up prediction"));
987 SetupPrediction(confidence, flags, uri, reason);
991 // (Maybe) adds a predictive action to the prediction runner, based on our
992 // calculated confidence for the subresource in question.
993 void Predictor::SetupPrediction(int32_t confidence, uint32_t flags,
994 const nsCString& uri,
995 PrefetchIgnoreReason earlyReason) {
996 MOZ_ASSERT(NS_IsMainThread());
998 nsresult rv = NS_OK;
999 PREDICTOR_LOG(
1000 ("SetupPrediction enable-prefetch=%d prefetch-min-confidence=%d "
1001 "preconnect-min-confidence=%d preresolve-min-confidence=%d "
1002 "flags=%d confidence=%d uri=%s",
1003 StaticPrefs::network_predictor_enable_prefetch(),
1004 StaticPrefs::network_predictor_prefetch_min_confidence(),
1005 StaticPrefs::network_predictor_preconnect_min_confidence(),
1006 StaticPrefs::network_predictor_preresolve_min_confidence(), flags,
1007 confidence, uri.get()));
1009 bool prefetchOk = !!(flags & FLAG_PREFETCHABLE);
1010 PrefetchIgnoreReason reason = earlyReason;
1011 if (prefetchOk && !StaticPrefs::network_predictor_enable_prefetch()) {
1012 prefetchOk = false;
1013 reason = PREFETCH_DISABLED;
1014 } else if (prefetchOk && !ClampedPrefetchRollingLoadCount() &&
1015 confidence <
1016 StaticPrefs::network_predictor_prefetch_min_confidence()) {
1017 prefetchOk = false;
1018 if (!ClampedPrefetchRollingLoadCount()) {
1019 reason = PREFETCH_DISABLED_VIA_COUNT;
1020 } else {
1021 reason = CONFIDENCE_TOO_LOW;
1025 // prefetchOk == false and reason == PREFETCH_OK indicates that the reason
1026 // we aren't prefetching this item is because it was marked un-prefetchable in
1027 // our metadata. We already have separate telemetry on that decision, so we
1028 // aren't going to accumulate more here. Right now we only care about why
1029 // something we had marked prefetchable isn't being prefetched.
1030 if (!prefetchOk && reason != PREFETCH_OK) {
1031 Telemetry::Accumulate(Telemetry::PREDICTOR_PREFETCH_IGNORE_REASON, reason);
1034 if (prefetchOk) {
1035 nsCOMPtr<nsIURI> prefetchURI;
1036 rv = NS_NewURI(getter_AddRefs(prefetchURI), uri);
1037 if (NS_SUCCEEDED(rv)) {
1038 mPrefetches.AppendElement(prefetchURI);
1040 } else if (confidence >=
1041 StaticPrefs::network_predictor_preconnect_min_confidence()) {
1042 nsCOMPtr<nsIURI> preconnectURI;
1043 rv = NS_NewURI(getter_AddRefs(preconnectURI), uri);
1044 if (NS_SUCCEEDED(rv)) {
1045 mPreconnects.AppendElement(preconnectURI);
1047 } else if (confidence >=
1048 StaticPrefs::network_predictor_preresolve_min_confidence()) {
1049 nsCOMPtr<nsIURI> preresolveURI;
1050 rv = NS_NewURI(getter_AddRefs(preresolveURI), uri);
1051 if (NS_SUCCEEDED(rv)) {
1052 mPreresolves.AppendElement(preresolveURI);
1056 if (NS_FAILED(rv)) {
1057 PREDICTOR_LOG(
1058 (" NS_NewURI returned 0x%" PRIx32, static_cast<uint32_t>(rv)));
1062 nsresult Predictor::Prefetch(nsIURI* uri, nsIURI* referrer,
1063 const OriginAttributes& originAttributes,
1064 nsINetworkPredictorVerifier* verifier) {
1065 nsAutoCString strUri, strReferrer;
1066 uri->GetAsciiSpec(strUri);
1067 referrer->GetAsciiSpec(strReferrer);
1068 PREDICTOR_LOG(("Predictor::Prefetch uri=%s referrer=%s verifier=%p",
1069 strUri.get(), strReferrer.get(), verifier));
1070 nsCOMPtr<nsIChannel> channel;
1071 nsresult rv = NS_NewChannel(
1072 getter_AddRefs(channel), uri, nsContentUtils::GetSystemPrincipal(),
1073 nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL,
1074 nsIContentPolicy::TYPE_OTHER, nullptr, /* nsICookieJarSettings */
1075 nullptr, /* aPerformanceStorage */
1076 nullptr, /* aLoadGroup */
1077 nullptr, /* aCallbacks */
1078 nsIRequest::LOAD_BACKGROUND);
1080 if (NS_FAILED(rv)) {
1081 PREDICTOR_LOG(
1082 (" NS_NewChannel failed rv=0x%" PRIX32, static_cast<uint32_t>(rv)));
1083 return rv;
1086 nsCOMPtr<nsILoadInfo> loadInfo = channel->LoadInfo();
1087 rv = loadInfo->SetOriginAttributes(originAttributes);
1089 if (NS_FAILED(rv)) {
1090 PREDICTOR_LOG(
1091 (" Set originAttributes into loadInfo failed rv=0x%" PRIX32,
1092 static_cast<uint32_t>(rv)));
1093 return rv;
1096 nsCOMPtr<nsIHttpChannel> httpChannel;
1097 httpChannel = do_QueryInterface(channel);
1098 if (!httpChannel) {
1099 PREDICTOR_LOG((" Could not get HTTP Channel from new channel!"));
1100 return NS_ERROR_UNEXPECTED;
1103 nsCOMPtr<nsIReferrerInfo> referrerInfo = new dom::ReferrerInfo(referrer);
1104 rv = httpChannel->SetReferrerInfoWithoutClone(referrerInfo);
1105 NS_ENSURE_SUCCESS(rv, rv);
1106 // XXX - set a header here to indicate this is a prefetch?
1108 nsCOMPtr<nsIStreamListener> listener =
1109 new PrefetchListener(verifier, uri, this);
1110 PREDICTOR_LOG((" calling AsyncOpen listener=%p channel=%p", listener.get(),
1111 channel.get()));
1112 rv = channel->AsyncOpen(listener);
1113 if (NS_FAILED(rv)) {
1114 PREDICTOR_LOG(
1115 (" AsyncOpen failed rv=0x%" PRIX32, static_cast<uint32_t>(rv)));
1118 return rv;
1121 // Runs predictions that have been set up.
1122 bool Predictor::RunPredictions(nsIURI* referrer,
1123 const OriginAttributes& originAttributes,
1124 nsINetworkPredictorVerifier* verifier) {
1125 MOZ_ASSERT(NS_IsMainThread(), "Running prediction off main thread");
1127 PREDICTOR_LOG(("Predictor::RunPredictions"));
1129 bool predicted = false;
1130 uint32_t len, i;
1132 nsTArray<nsCOMPtr<nsIURI>> prefetches = std::move(mPrefetches),
1133 preconnects = std::move(mPreconnects),
1134 preresolves = std::move(mPreresolves);
1136 Telemetry::AutoCounter<Telemetry::PREDICTOR_TOTAL_PREDICTIONS>
1137 totalPredictions;
1138 Telemetry::AutoCounter<Telemetry::PREDICTOR_TOTAL_PREFETCHES> totalPrefetches;
1139 Telemetry::AutoCounter<Telemetry::PREDICTOR_TOTAL_PRECONNECTS>
1140 totalPreconnects;
1141 Telemetry::AutoCounter<Telemetry::PREDICTOR_TOTAL_PRERESOLVES>
1142 totalPreresolves;
1144 len = prefetches.Length();
1145 for (i = 0; i < len; ++i) {
1146 PREDICTOR_LOG((" doing prefetch"));
1147 nsCOMPtr<nsIURI> uri = prefetches[i];
1148 if (NS_SUCCEEDED(Prefetch(uri, referrer, originAttributes, verifier))) {
1149 ++totalPredictions;
1150 ++totalPrefetches;
1151 predicted = true;
1155 len = preconnects.Length();
1156 for (i = 0; i < len; ++i) {
1157 PREDICTOR_LOG((" doing preconnect"));
1158 nsCOMPtr<nsIURI> uri = preconnects[i];
1159 ++totalPredictions;
1160 ++totalPreconnects;
1161 nsCOMPtr<nsIPrincipal> principal =
1162 BasePrincipal::CreateContentPrincipal(uri, originAttributes);
1163 mSpeculativeService->SpeculativeConnect(uri, principal, this, false);
1164 predicted = true;
1165 if (verifier) {
1166 PREDICTOR_LOG((" sending preconnect verification"));
1167 verifier->OnPredictPreconnect(uri);
1171 len = preresolves.Length();
1172 for (i = 0; i < len; ++i) {
1173 nsCOMPtr<nsIURI> uri = preresolves[i];
1174 ++totalPredictions;
1175 ++totalPreresolves;
1176 nsAutoCString hostname;
1177 uri->GetAsciiHost(hostname);
1178 PREDICTOR_LOG((" doing preresolve %s", hostname.get()));
1179 nsCOMPtr<nsICancelable> tmpCancelable;
1180 mDnsService->AsyncResolveNative(
1181 hostname, nsIDNSService::RESOLVE_TYPE_DEFAULT,
1182 (nsIDNSService::RESOLVE_PRIORITY_MEDIUM |
1183 nsIDNSService::RESOLVE_SPECULATE),
1184 nullptr, mDNSListener, nullptr, originAttributes,
1185 getter_AddRefs(tmpCancelable));
1187 // Fetch HTTPS RR if needed.
1188 if (StaticPrefs::network_dns_upgrade_with_https_rr() ||
1189 StaticPrefs::network_dns_use_https_rr_as_altsvc()) {
1190 mDnsService->AsyncResolveNative(
1191 hostname, nsIDNSService::RESOLVE_TYPE_HTTPSSVC,
1192 (nsIDNSService::RESOLVE_PRIORITY_MEDIUM |
1193 nsIDNSService::RESOLVE_SPECULATE),
1194 nullptr, mDNSListener, nullptr, originAttributes,
1195 getter_AddRefs(tmpCancelable));
1198 predicted = true;
1199 if (verifier) {
1200 PREDICTOR_LOG((" sending preresolve verification"));
1201 verifier->OnPredictDNS(uri);
1205 return predicted;
1208 // Find out if a top-level page is likely to redirect.
1209 bool Predictor::WouldRedirect(nsICacheEntry* entry, uint32_t loadCount,
1210 uint32_t lastLoad, int32_t globalDegradation,
1211 nsIURI** redirectURI) {
1212 // TODO - not doing redirects for first go around
1213 MOZ_ASSERT(NS_IsMainThread());
1215 return false;
1218 NS_IMETHODIMP
1219 Predictor::Learn(nsIURI* targetURI, nsIURI* sourceURI,
1220 PredictorLearnReason reason,
1221 JS::Handle<JS::Value> originAttributes, JSContext* aCx) {
1222 OriginAttributes attrs;
1224 if (!originAttributes.isObject() || !attrs.Init(aCx, originAttributes)) {
1225 return NS_ERROR_INVALID_ARG;
1228 return LearnNative(targetURI, sourceURI, reason, attrs);
1231 // Called from the main thread to update the database
1232 NS_IMETHODIMP
1233 Predictor::LearnNative(nsIURI* targetURI, nsIURI* sourceURI,
1234 PredictorLearnReason reason,
1235 const OriginAttributes& originAttributes) {
1236 MOZ_ASSERT(NS_IsMainThread(),
1237 "Predictor interface methods must be called on the main thread");
1239 PREDICTOR_LOG(("Predictor::Learn"));
1241 if (IsNeckoChild()) {
1242 if (!gNeckoChild) {
1243 return NS_ERROR_FAILURE;
1246 PREDICTOR_LOG((" called on child process"));
1248 RefPtr<PredictorLearnRunnable> runnable = new PredictorLearnRunnable(
1249 targetURI, sourceURI, reason, originAttributes);
1250 SchedulerGroup::Dispatch(runnable.forget());
1251 return NS_OK;
1254 PREDICTOR_LOG((" called on parent process"));
1256 if (!mInitialized) {
1257 PREDICTOR_LOG((" not initialized"));
1258 return NS_OK;
1261 if (!StaticPrefs::network_predictor_enabled()) {
1262 PREDICTOR_LOG((" not enabled"));
1263 return NS_OK;
1266 if (originAttributes.mPrivateBrowsingId > 0) {
1267 // Don't want to do anything in PB mode
1268 PREDICTOR_LOG((" in PB mode"));
1269 return NS_OK;
1272 if (!IsNullOrHttp(targetURI) || !IsNullOrHttp(sourceURI)) {
1273 PREDICTOR_LOG((" got non-HTTP[S] URI"));
1274 return NS_ERROR_INVALID_ARG;
1277 nsCOMPtr<nsIURI> targetOrigin;
1278 nsCOMPtr<nsIURI> sourceOrigin;
1279 nsCOMPtr<nsIURI> uriKey;
1280 nsCOMPtr<nsIURI> originKey;
1281 nsresult rv;
1283 switch (reason) {
1284 case nsINetworkPredictor::LEARN_LOAD_TOPLEVEL:
1285 if (!targetURI || sourceURI) {
1286 PREDICTOR_LOG((" load toplevel invalid URI state"));
1287 return NS_ERROR_INVALID_ARG;
1289 rv = ExtractOrigin(targetURI, getter_AddRefs(targetOrigin));
1290 NS_ENSURE_SUCCESS(rv, rv);
1291 uriKey = targetURI;
1292 originKey = targetOrigin;
1293 break;
1294 case nsINetworkPredictor::LEARN_STARTUP:
1295 if (!targetURI || sourceURI) {
1296 PREDICTOR_LOG((" startup invalid URI state"));
1297 return NS_ERROR_INVALID_ARG;
1299 rv = ExtractOrigin(targetURI, getter_AddRefs(targetOrigin));
1300 NS_ENSURE_SUCCESS(rv, rv);
1301 uriKey = mStartupURI;
1302 originKey = mStartupURI;
1303 break;
1304 case nsINetworkPredictor::LEARN_LOAD_REDIRECT:
1305 case nsINetworkPredictor::LEARN_LOAD_SUBRESOURCE:
1306 if (!targetURI || !sourceURI) {
1307 PREDICTOR_LOG((" redirect/subresource invalid URI state"));
1308 return NS_ERROR_INVALID_ARG;
1310 rv = ExtractOrigin(targetURI, getter_AddRefs(targetOrigin));
1311 NS_ENSURE_SUCCESS(rv, rv);
1312 rv = ExtractOrigin(sourceURI, getter_AddRefs(sourceOrigin));
1313 NS_ENSURE_SUCCESS(rv, rv);
1314 uriKey = sourceURI;
1315 originKey = sourceOrigin;
1316 break;
1317 default:
1318 PREDICTOR_LOG((" invalid reason"));
1319 return NS_ERROR_INVALID_ARG;
1322 Telemetry::AutoCounter<Telemetry::PREDICTOR_LEARN_ATTEMPTS> learnAttempts;
1323 ++learnAttempts;
1325 Predictor::Reason argReason{};
1326 argReason.mLearn = reason;
1328 // We always open the full uri (general cache) entry first, so we don't gum up
1329 // the works waiting on predictor-only entries to open
1330 RefPtr<Predictor::Action> uriAction = new Predictor::Action(
1331 Predictor::Action::IS_FULL_URI, Predictor::Action::DO_LEARN, argReason,
1332 targetURI, sourceURI, nullptr, this);
1333 nsAutoCString uriKeyStr, targetUriStr, sourceUriStr;
1334 uriKey->GetAsciiSpec(uriKeyStr);
1335 targetURI->GetAsciiSpec(targetUriStr);
1336 if (sourceURI) {
1337 sourceURI->GetAsciiSpec(sourceUriStr);
1339 PREDICTOR_LOG(
1340 (" Learn uriKey=%s targetURI=%s sourceURI=%s reason=%d "
1341 "action=%p",
1342 uriKeyStr.get(), targetUriStr.get(), sourceUriStr.get(), reason,
1343 uriAction.get()));
1345 nsCOMPtr<nsICacheStorage> cacheDiskStorage;
1347 RefPtr<LoadContextInfo> lci = new LoadContextInfo(false, originAttributes);
1349 rv = mCacheStorageService->DiskCacheStorage(lci,
1350 getter_AddRefs(cacheDiskStorage));
1351 NS_ENSURE_SUCCESS(rv, rv);
1353 // For learning full URI things, we *always* open readonly and secretly, as we
1354 // rely on actual pageloads to update the entry's metadata for us.
1355 uint32_t uriOpenFlags = nsICacheStorage::OPEN_READONLY |
1356 nsICacheStorage::OPEN_SECRETLY |
1357 nsICacheStorage::CHECK_MULTITHREADED;
1358 if (reason == nsINetworkPredictor::LEARN_LOAD_TOPLEVEL) {
1359 // Learning for toplevel we want to open the full uri entry priority, since
1360 // it's likely this entry will be used soon anyway, and we want this to be
1361 // opened ASAP.
1362 uriOpenFlags |= nsICacheStorage::OPEN_PRIORITY;
1364 cacheDiskStorage->AsyncOpenURI(uriKey, ""_ns, uriOpenFlags, uriAction);
1366 // Now we open the origin-only (and therefore predictor-only) entry
1367 RefPtr<Predictor::Action> originAction = new Predictor::Action(
1368 Predictor::Action::IS_ORIGIN, Predictor::Action::DO_LEARN, argReason,
1369 targetOrigin, sourceOrigin, nullptr, this);
1370 nsAutoCString originKeyStr, targetOriginStr, sourceOriginStr;
1371 originKey->GetAsciiSpec(originKeyStr);
1372 targetOrigin->GetAsciiSpec(targetOriginStr);
1373 if (sourceOrigin) {
1374 sourceOrigin->GetAsciiSpec(sourceOriginStr);
1376 PREDICTOR_LOG(
1377 (" Learn originKey=%s targetOrigin=%s sourceOrigin=%s reason=%d "
1378 "action=%p",
1379 originKeyStr.get(), targetOriginStr.get(), sourceOriginStr.get(), reason,
1380 originAction.get()));
1381 uint32_t originOpenFlags;
1382 if (reason == nsINetworkPredictor::LEARN_LOAD_TOPLEVEL) {
1383 // This is the only case when we want to update the 'last used' metadata on
1384 // the cache entry we're getting. This only applies to predictor-specific
1385 // entries.
1386 originOpenFlags =
1387 nsICacheStorage::OPEN_NORMALLY | nsICacheStorage::CHECK_MULTITHREADED;
1388 } else {
1389 originOpenFlags = nsICacheStorage::OPEN_READONLY |
1390 nsICacheStorage::OPEN_SECRETLY |
1391 nsICacheStorage::CHECK_MULTITHREADED;
1393 cacheDiskStorage->AsyncOpenURI(originKey,
1394 nsLiteralCString(PREDICTOR_ORIGIN_EXTENSION),
1395 originOpenFlags, originAction);
1397 PREDICTOR_LOG(("Predictor::Learn returning"));
1398 return NS_OK;
1401 void Predictor::LearnInternal(PredictorLearnReason reason, nsICacheEntry* entry,
1402 bool isNew, bool fullUri, nsIURI* targetURI,
1403 nsIURI* sourceURI) {
1404 MOZ_ASSERT(NS_IsMainThread());
1406 PREDICTOR_LOG(("Predictor::LearnInternal"));
1408 nsCString junk;
1409 if (!fullUri && reason == nsINetworkPredictor::LEARN_LOAD_TOPLEVEL &&
1410 NS_FAILED(
1411 entry->GetMetaDataElement(SEEN_META_DATA, getter_Copies(junk)))) {
1412 // This is an origin-only entry that we haven't seen before. Let's mark it
1413 // as seen.
1414 PREDICTOR_LOG((" marking new origin entry as seen"));
1415 nsresult rv = entry->SetMetaDataElement(SEEN_META_DATA, "1");
1416 if (NS_FAILED(rv)) {
1417 PREDICTOR_LOG((" failed to mark origin entry seen"));
1418 return;
1421 // Need to ensure someone else can get to the entry if necessary
1422 entry->MetaDataReady();
1423 return;
1426 switch (reason) {
1427 case nsINetworkPredictor::LEARN_LOAD_TOPLEVEL:
1428 // This case only exists to be used during tests - code outside the
1429 // predictor tests should NEVER call Learn with LEARN_LOAD_TOPLEVEL.
1430 // The predictor xpcshell test needs this branch, however, because we
1431 // have no real page loads in xpcshell, and this is how we fake it up
1432 // so that all the work that normally happens behind the scenes in a
1433 // page load can be done for testing purposes.
1434 if (fullUri && StaticPrefs::network_predictor_doing_tests()) {
1435 PREDICTOR_LOG(
1436 (" WARNING - updating rolling load count. "
1437 "If you see this outside tests, you did it wrong"));
1439 // Since the visitor gets called under a cache lock, all we do there is
1440 // get copies of the keys/values we care about, and then do the real
1441 // work here
1442 entry->VisitMetaData(this);
1443 nsTArray<nsCString> keysToOperateOn = std::move(mKeysToOperateOn),
1444 valuesToOperateOn = std::move(mValuesToOperateOn);
1446 MOZ_ASSERT(keysToOperateOn.Length() == valuesToOperateOn.Length());
1447 for (size_t i = 0; i < keysToOperateOn.Length(); ++i) {
1448 const char* key = keysToOperateOn[i].BeginReading();
1449 const char* value = valuesToOperateOn[i].BeginReading();
1451 nsCString uri;
1452 uint32_t hitCount, lastHit, flags;
1453 if (!ParseMetaDataEntry(key, value, uri, hitCount, lastHit, flags)) {
1454 // This failed, get rid of it so we don't waste space
1455 entry->SetMetaDataElement(key, nullptr);
1456 continue;
1458 UpdateRollingLoadCount(entry, flags, key, hitCount, lastHit);
1460 } else {
1461 PREDICTOR_LOG((" nothing to do for toplevel"));
1463 break;
1464 case nsINetworkPredictor::LEARN_LOAD_REDIRECT:
1465 if (fullUri) {
1466 LearnForRedirect(entry, targetURI);
1468 break;
1469 case nsINetworkPredictor::LEARN_LOAD_SUBRESOURCE:
1470 LearnForSubresource(entry, targetURI);
1471 break;
1472 case nsINetworkPredictor::LEARN_STARTUP:
1473 LearnForStartup(entry, targetURI);
1474 break;
1475 default:
1476 PREDICTOR_LOG((" unexpected reason value"));
1477 MOZ_ASSERT(false, "Got unexpected value for learn reason!");
1481 NS_IMPL_ISUPPORTS(Predictor::SpaceCleaner, nsICacheEntryMetaDataVisitor)
1483 NS_IMETHODIMP
1484 Predictor::SpaceCleaner::OnMetaDataElement(const char* key, const char* value) {
1485 MOZ_ASSERT(NS_IsMainThread());
1487 if (!IsURIMetadataElement(key)) {
1488 // This isn't a bit of metadata we care about
1489 return NS_OK;
1492 nsCString uri;
1493 uint32_t hitCount, lastHit, flags;
1494 bool ok =
1495 mPredictor->ParseMetaDataEntry(key, value, uri, hitCount, lastHit, flags);
1497 if (!ok) {
1498 // Couldn't parse this one, just get rid of it
1499 nsCString nsKey;
1500 nsKey.AssignASCII(key);
1501 mLongKeysToDelete.AppendElement(nsKey);
1502 return NS_OK;
1505 uint32_t uriLength = uri.Length();
1506 if (uriLength > StaticPrefs::network_predictor_max_uri_length()) {
1507 // Default to getting rid of URIs that are too long and were put in before
1508 // we had our limit on URI length, in order to free up some space.
1509 nsCString nsKey;
1510 nsKey.AssignASCII(key);
1511 mLongKeysToDelete.AppendElement(nsKey);
1512 return NS_OK;
1515 if (!mLRUKeyToDelete || lastHit < mLRUStamp) {
1516 mLRUKeyToDelete = key;
1517 mLRUStamp = lastHit;
1520 return NS_OK;
1523 void Predictor::SpaceCleaner::Finalize(nsICacheEntry* entry) {
1524 MOZ_ASSERT(NS_IsMainThread());
1526 if (mLRUKeyToDelete) {
1527 entry->SetMetaDataElement(mLRUKeyToDelete, nullptr);
1530 for (size_t i = 0; i < mLongKeysToDelete.Length(); ++i) {
1531 entry->SetMetaDataElement(mLongKeysToDelete[i].BeginReading(), nullptr);
1535 // Called when a subresource has been hit from a top-level load. Uses the two
1536 // helper functions above to update the database appropriately.
1537 void Predictor::LearnForSubresource(nsICacheEntry* entry, nsIURI* targetURI) {
1538 MOZ_ASSERT(NS_IsMainThread());
1540 PREDICTOR_LOG(("Predictor::LearnForSubresource"));
1542 uint32_t lastLoad;
1543 nsresult rv = entry->GetLastFetched(&lastLoad);
1544 NS_ENSURE_SUCCESS_VOID(rv);
1546 uint32_t loadCount;
1547 rv = entry->GetFetchCount(&loadCount);
1548 NS_ENSURE_SUCCESS_VOID(rv);
1550 nsCString key;
1551 key.AssignLiteral(META_DATA_PREFIX);
1552 nsCString uri;
1553 targetURI->GetAsciiSpec(uri);
1554 key.Append(uri);
1555 if (uri.Length() > StaticPrefs::network_predictor_max_uri_length()) {
1556 // We do this to conserve space/prevent OOMs
1557 PREDICTOR_LOG((" uri too long!"));
1558 entry->SetMetaDataElement(key.BeginReading(), nullptr);
1559 return;
1562 nsCString value;
1563 rv = entry->GetMetaDataElement(key.BeginReading(), getter_Copies(value));
1565 uint32_t hitCount, lastHit, flags;
1566 bool isNewResource =
1567 (NS_FAILED(rv) ||
1568 !ParseMetaDataEntry(key.BeginReading(), value.BeginReading(), uri,
1569 hitCount, lastHit, flags));
1571 int32_t resourceCount = 0;
1572 if (isNewResource) {
1573 // This is a new addition
1574 PREDICTOR_LOG((" new resource"));
1575 nsCString s;
1576 rv = entry->GetMetaDataElement(RESOURCE_META_DATA, getter_Copies(s));
1577 if (NS_SUCCEEDED(rv)) {
1578 resourceCount = atoi(s.BeginReading());
1580 if (resourceCount >=
1581 StaticPrefs::network_predictor_max_resources_per_entry()) {
1582 RefPtr<Predictor::SpaceCleaner> cleaner =
1583 new Predictor::SpaceCleaner(this);
1584 entry->VisitMetaData(cleaner);
1585 cleaner->Finalize(entry);
1586 } else {
1587 ++resourceCount;
1589 nsAutoCString count;
1590 count.AppendInt(resourceCount);
1591 rv = entry->SetMetaDataElement(RESOURCE_META_DATA, count.BeginReading());
1592 if (NS_FAILED(rv)) {
1593 PREDICTOR_LOG((" failed to update resource count"));
1594 return;
1596 hitCount = 1;
1597 flags = 0;
1598 } else {
1599 PREDICTOR_LOG((" existing resource"));
1600 hitCount = std::min(hitCount + 1, loadCount);
1603 // Update the rolling load count to mark this sub-resource as seen on the
1604 // most-recent pageload so it can be eligible for prefetch (assuming all
1605 // the other stars align).
1606 flags |= (1 << kRollingLoadOffset);
1608 nsCString newValue;
1609 MakeMetadataEntry(hitCount, lastLoad, flags, newValue);
1610 rv = entry->SetMetaDataElement(key.BeginReading(), newValue.BeginReading());
1611 PREDICTOR_LOG(
1612 (" SetMetaDataElement -> 0x%08" PRIX32, static_cast<uint32_t>(rv)));
1613 if (NS_FAILED(rv) && isNewResource) {
1614 // Roll back the increment to the resource count we made above.
1615 PREDICTOR_LOG((" rolling back resource count update"));
1616 --resourceCount;
1617 if (resourceCount == 0) {
1618 entry->SetMetaDataElement(RESOURCE_META_DATA, nullptr);
1619 } else {
1620 nsAutoCString count;
1621 count.AppendInt(resourceCount);
1622 entry->SetMetaDataElement(RESOURCE_META_DATA, count.BeginReading());
1627 // This is called when a top-level loaded ended up redirecting to a different
1628 // URI so we can keep track of that fact.
1629 void Predictor::LearnForRedirect(nsICacheEntry* entry, nsIURI* targetURI) {
1630 MOZ_ASSERT(NS_IsMainThread());
1632 // TODO - not doing redirects for first go around
1633 PREDICTOR_LOG(("Predictor::LearnForRedirect"));
1636 // This will add a page to our list of startup pages if it's being loaded
1637 // before our startup window has expired.
1638 void Predictor::MaybeLearnForStartup(nsIURI* uri, bool fullUri,
1639 const OriginAttributes& originAttributes) {
1640 MOZ_ASSERT(NS_IsMainThread());
1642 // TODO - not doing startup for first go around
1643 PREDICTOR_LOG(("Predictor::MaybeLearnForStartup"));
1646 // Add information about a top-level load to our list of startup pages
1647 void Predictor::LearnForStartup(nsICacheEntry* entry, nsIURI* targetURI) {
1648 MOZ_ASSERT(NS_IsMainThread());
1650 // These actually do the same set of work, just on different entries, so we
1651 // can pass through to get the real work done here
1652 PREDICTOR_LOG(("Predictor::LearnForStartup"));
1653 LearnForSubresource(entry, targetURI);
1656 bool Predictor::ParseMetaDataEntry(const char* key, const char* value,
1657 nsCString& uri, uint32_t& hitCount,
1658 uint32_t& lastHit, uint32_t& flags) {
1659 MOZ_ASSERT(NS_IsMainThread());
1661 PREDICTOR_LOG(
1662 ("Predictor::ParseMetaDataEntry key=%s value=%s", key ? key : "", value));
1664 const char* comma = strchr(value, ',');
1665 if (!comma) {
1666 PREDICTOR_LOG((" could not find first comma"));
1667 return false;
1670 uint32_t version = static_cast<uint32_t>(atoi(value));
1671 PREDICTOR_LOG((" version -> %u", version));
1673 if (version != METADATA_VERSION) {
1674 PREDICTOR_LOG(
1675 (" metadata version mismatch %u != %u", version, METADATA_VERSION));
1676 return false;
1679 value = comma + 1;
1680 comma = strchr(value, ',');
1681 if (!comma) {
1682 PREDICTOR_LOG((" could not find second comma"));
1683 return false;
1686 hitCount = static_cast<uint32_t>(atoi(value));
1687 PREDICTOR_LOG((" hitCount -> %u", hitCount));
1689 value = comma + 1;
1690 comma = strchr(value, ',');
1691 if (!comma) {
1692 PREDICTOR_LOG((" could not find third comma"));
1693 return false;
1696 lastHit = static_cast<uint32_t>(atoi(value));
1697 PREDICTOR_LOG((" lastHit -> %u", lastHit));
1699 value = comma + 1;
1700 flags = static_cast<uint32_t>(atoi(value));
1701 PREDICTOR_LOG((" flags -> %u", flags));
1703 if (key) {
1704 const char* uriStart = key + (sizeof(META_DATA_PREFIX) - 1);
1705 uri.AssignASCII(uriStart);
1706 PREDICTOR_LOG((" uri -> %s", uriStart));
1707 } else {
1708 uri.Truncate();
1711 return true;
1714 NS_IMETHODIMP
1715 Predictor::Reset() {
1716 MOZ_ASSERT(NS_IsMainThread(),
1717 "Predictor interface methods must be called on the main thread");
1719 PREDICTOR_LOG(("Predictor::Reset"));
1721 if (IsNeckoChild()) {
1722 if (!gNeckoChild) {
1723 return NS_ERROR_FAILURE;
1726 PREDICTOR_LOG((" forwarding to parent process"));
1727 gNeckoChild->SendPredReset();
1728 return NS_OK;
1731 PREDICTOR_LOG((" called on parent process"));
1733 if (!mInitialized) {
1734 PREDICTOR_LOG((" not initialized"));
1735 return NS_OK;
1738 if (!StaticPrefs::network_predictor_enabled()) {
1739 PREDICTOR_LOG((" not enabled"));
1740 return NS_OK;
1743 RefPtr<Predictor::Resetter> reset = new Predictor::Resetter(this);
1744 PREDICTOR_LOG((" created a resetter"));
1745 mCacheStorageService->AsyncVisitAllStorages(reset, true);
1746 PREDICTOR_LOG((" Cache async launched, returning now"));
1748 return NS_OK;
1751 NS_IMPL_ISUPPORTS(Predictor::Resetter, nsICacheEntryOpenCallback,
1752 nsICacheEntryMetaDataVisitor, nsICacheStorageVisitor);
1754 Predictor::Resetter::Resetter(Predictor* predictor)
1755 : mEntriesToVisit(0), mPredictor(predictor) {}
1757 NS_IMETHODIMP
1758 Predictor::Resetter::OnCacheEntryCheck(nsICacheEntry* entry, uint32_t* result) {
1759 *result = nsICacheEntryOpenCallback::ENTRY_WANTED;
1760 return NS_OK;
1763 NS_IMETHODIMP
1764 Predictor::Resetter::OnCacheEntryAvailable(nsICacheEntry* entry, bool isNew,
1765 nsresult result) {
1766 MOZ_ASSERT(NS_IsMainThread());
1768 if (NS_FAILED(result)) {
1769 // This can happen when we've tried to open an entry that doesn't exist for
1770 // some non-reset operation, and then get reset shortly thereafter (as
1771 // happens in some of our tests).
1772 --mEntriesToVisit;
1773 if (!mEntriesToVisit) {
1774 Complete();
1776 return NS_OK;
1779 entry->VisitMetaData(this);
1780 nsTArray<nsCString> keysToDelete = std::move(mKeysToDelete);
1782 for (size_t i = 0; i < keysToDelete.Length(); ++i) {
1783 const char* key = keysToDelete[i].BeginReading();
1784 entry->SetMetaDataElement(key, nullptr);
1787 --mEntriesToVisit;
1788 if (!mEntriesToVisit) {
1789 Complete();
1792 return NS_OK;
1795 NS_IMETHODIMP
1796 Predictor::Resetter::OnMetaDataElement(const char* asciiKey,
1797 const char* asciiValue) {
1798 MOZ_ASSERT(NS_IsMainThread());
1800 if (!StringBeginsWith(nsDependentCString(asciiKey),
1801 nsLiteralCString(META_DATA_PREFIX))) {
1802 // Not a metadata entry we care about, carry on
1803 return NS_OK;
1806 nsCString key;
1807 key.AssignASCII(asciiKey);
1808 mKeysToDelete.AppendElement(key);
1810 return NS_OK;
1813 NS_IMETHODIMP
1814 Predictor::Resetter::OnCacheStorageInfo(uint32_t entryCount,
1815 uint64_t consumption, uint64_t capacity,
1816 nsIFile* diskDirectory) {
1817 MOZ_ASSERT(NS_IsMainThread());
1819 return NS_OK;
1822 NS_IMETHODIMP
1823 Predictor::Resetter::OnCacheEntryInfo(nsIURI* uri, const nsACString& idEnhance,
1824 int64_t dataSize, int64_t altDataSize,
1825 uint32_t fetchCount,
1826 uint32_t lastModifiedTime,
1827 uint32_t expirationTime, bool aPinned,
1828 nsILoadContextInfo* aInfo) {
1829 MOZ_ASSERT(NS_IsMainThread());
1831 nsresult rv;
1833 // The predictor will only ever touch entries with no idEnhance ("") or an
1834 // idEnhance of PREDICTOR_ORIGIN_EXTENSION, so we filter out any entries that
1835 // don't match that to avoid doing extra work.
1836 if (idEnhance.EqualsLiteral(PREDICTOR_ORIGIN_EXTENSION)) {
1837 // This is an entry we own, so we can just doom it entirely
1838 nsCOMPtr<nsICacheStorage> cacheDiskStorage;
1840 rv = mPredictor->mCacheStorageService->DiskCacheStorage(
1841 aInfo, getter_AddRefs(cacheDiskStorage));
1843 NS_ENSURE_SUCCESS(rv, rv);
1844 cacheDiskStorage->AsyncDoomURI(uri, idEnhance, nullptr);
1845 } else if (idEnhance.IsEmpty()) {
1846 // This is an entry we don't own, so we have to be a little more careful and
1847 // just get rid of our own metadata entries. Append it to an array of things
1848 // to operate on and then do the operations later so we don't end up calling
1849 // Complete() multiple times/too soon.
1850 ++mEntriesToVisit;
1851 mURIsToVisit.AppendElement(uri);
1852 mInfosToVisit.AppendElement(aInfo);
1855 return NS_OK;
1858 NS_IMETHODIMP
1859 Predictor::Resetter::OnCacheEntryVisitCompleted() {
1860 MOZ_ASSERT(NS_IsMainThread());
1862 nsresult rv;
1864 nsTArray<nsCOMPtr<nsIURI>> urisToVisit = std::move(mURIsToVisit);
1866 MOZ_ASSERT(mEntriesToVisit == urisToVisit.Length());
1868 nsTArray<nsCOMPtr<nsILoadContextInfo>> infosToVisit =
1869 std::move(mInfosToVisit);
1871 MOZ_ASSERT(mEntriesToVisit == infosToVisit.Length());
1873 if (!mEntriesToVisit) {
1874 Complete();
1875 return NS_OK;
1878 uint32_t entriesToVisit = urisToVisit.Length();
1879 for (uint32_t i = 0; i < entriesToVisit; ++i) {
1880 nsCString u;
1881 nsCOMPtr<nsICacheStorage> cacheDiskStorage;
1883 rv = mPredictor->mCacheStorageService->DiskCacheStorage(
1884 infosToVisit[i], getter_AddRefs(cacheDiskStorage));
1885 NS_ENSURE_SUCCESS(rv, rv);
1887 urisToVisit[i]->GetAsciiSpec(u);
1888 rv = cacheDiskStorage->AsyncOpenURI(
1889 urisToVisit[i], ""_ns,
1890 nsICacheStorage::OPEN_READONLY | nsICacheStorage::OPEN_SECRETLY |
1891 nsICacheStorage::CHECK_MULTITHREADED,
1892 this);
1893 if (NS_FAILED(rv)) {
1894 mEntriesToVisit--;
1895 if (!mEntriesToVisit) {
1896 Complete();
1897 return NS_OK;
1902 return NS_OK;
1905 void Predictor::Resetter::Complete() {
1906 MOZ_ASSERT(NS_IsMainThread());
1908 nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
1909 if (!obs) {
1910 PREDICTOR_LOG(("COULD NOT GET OBSERVER SERVICE!"));
1911 return;
1914 obs->NotifyObservers(nullptr, "predictor-reset-complete", nullptr);
1917 // Helper functions to make using the predictor easier from native code
1919 static StaticRefPtr<nsINetworkPredictor> sPredictor;
1921 static nsresult EnsureGlobalPredictor(nsINetworkPredictor** aPredictor) {
1922 MOZ_ASSERT(NS_IsMainThread());
1924 if (!sPredictor) {
1925 nsresult rv;
1926 nsCOMPtr<nsINetworkPredictor> predictor =
1927 do_GetService("@mozilla.org/network/predictor;1", &rv);
1928 NS_ENSURE_SUCCESS(rv, rv);
1929 sPredictor = predictor;
1930 ClearOnShutdown(&sPredictor);
1933 nsCOMPtr<nsINetworkPredictor> predictor = sPredictor.get();
1934 predictor.forget(aPredictor);
1935 return NS_OK;
1938 nsresult PredictorPredict(nsIURI* targetURI, nsIURI* sourceURI,
1939 PredictorPredictReason reason,
1940 const OriginAttributes& originAttributes,
1941 nsINetworkPredictorVerifier* verifier) {
1942 MOZ_ASSERT(NS_IsMainThread());
1944 if (!IsNullOrHttp(targetURI) || !IsNullOrHttp(sourceURI)) {
1945 return NS_OK;
1948 nsCOMPtr<nsINetworkPredictor> predictor;
1949 nsresult rv = EnsureGlobalPredictor(getter_AddRefs(predictor));
1950 NS_ENSURE_SUCCESS(rv, rv);
1952 return predictor->PredictNative(targetURI, sourceURI, reason,
1953 originAttributes, verifier);
1956 nsresult PredictorLearn(nsIURI* targetURI, nsIURI* sourceURI,
1957 PredictorLearnReason reason,
1958 const OriginAttributes& originAttributes) {
1959 MOZ_ASSERT(NS_IsMainThread());
1961 if (!IsNullOrHttp(targetURI) || !IsNullOrHttp(sourceURI)) {
1962 return NS_OK;
1965 nsCOMPtr<nsINetworkPredictor> predictor;
1966 nsresult rv = EnsureGlobalPredictor(getter_AddRefs(predictor));
1967 NS_ENSURE_SUCCESS(rv, rv);
1969 return predictor->LearnNative(targetURI, sourceURI, reason, originAttributes);
1972 nsresult PredictorLearn(nsIURI* targetURI, nsIURI* sourceURI,
1973 PredictorLearnReason reason, nsILoadGroup* loadGroup) {
1974 MOZ_ASSERT(NS_IsMainThread());
1976 if (!IsNullOrHttp(targetURI) || !IsNullOrHttp(sourceURI)) {
1977 return NS_OK;
1980 nsCOMPtr<nsINetworkPredictor> predictor;
1981 nsresult rv = EnsureGlobalPredictor(getter_AddRefs(predictor));
1982 NS_ENSURE_SUCCESS(rv, rv);
1984 nsCOMPtr<nsILoadContext> loadContext;
1985 OriginAttributes originAttributes;
1987 if (loadGroup) {
1988 nsCOMPtr<nsIInterfaceRequestor> callbacks;
1989 loadGroup->GetNotificationCallbacks(getter_AddRefs(callbacks));
1990 if (callbacks) {
1991 loadContext = do_GetInterface(callbacks);
1993 if (loadContext) {
1994 loadContext->GetOriginAttributes(originAttributes);
1999 return predictor->LearnNative(targetURI, sourceURI, reason, originAttributes);
2002 nsresult PredictorLearn(nsIURI* targetURI, nsIURI* sourceURI,
2003 PredictorLearnReason reason, dom::Document* document) {
2004 MOZ_ASSERT(NS_IsMainThread());
2006 if (!IsNullOrHttp(targetURI) || !IsNullOrHttp(sourceURI)) {
2007 return NS_OK;
2010 nsCOMPtr<nsINetworkPredictor> predictor;
2011 nsresult rv = EnsureGlobalPredictor(getter_AddRefs(predictor));
2012 NS_ENSURE_SUCCESS(rv, rv);
2014 OriginAttributes originAttributes;
2016 if (document) {
2017 nsCOMPtr<nsIPrincipal> docPrincipal = document->NodePrincipal();
2019 if (docPrincipal) {
2020 originAttributes = docPrincipal->OriginAttributesRef();
2024 return predictor->LearnNative(targetURI, sourceURI, reason, originAttributes);
2027 nsresult PredictorLearnRedirect(nsIURI* targetURI, nsIChannel* channel,
2028 const OriginAttributes& originAttributes) {
2029 MOZ_ASSERT(NS_IsMainThread());
2031 nsCOMPtr<nsIURI> sourceURI;
2032 nsresult rv = channel->GetOriginalURI(getter_AddRefs(sourceURI));
2033 NS_ENSURE_SUCCESS(rv, rv);
2035 bool sameUri;
2036 rv = targetURI->Equals(sourceURI, &sameUri);
2037 NS_ENSURE_SUCCESS(rv, rv);
2039 if (sameUri) {
2040 return NS_OK;
2043 if (!IsNullOrHttp(targetURI) || !IsNullOrHttp(sourceURI)) {
2044 return NS_OK;
2047 nsCOMPtr<nsINetworkPredictor> predictor;
2048 rv = EnsureGlobalPredictor(getter_AddRefs(predictor));
2049 NS_ENSURE_SUCCESS(rv, rv);
2051 return predictor->LearnNative(targetURI, sourceURI,
2052 nsINetworkPredictor::LEARN_LOAD_REDIRECT,
2053 originAttributes);
2056 // nsINetworkPredictorVerifier
2059 * Call through to the child's verifier (only during tests)
2061 NS_IMETHODIMP
2062 Predictor::OnPredictPrefetch(nsIURI* aURI, uint32_t httpStatus) {
2063 if (IsNeckoChild()) {
2064 if (mChildVerifier) {
2065 // Ideally, we'd assert here. But since we're slowly moving towards a
2066 // world where we have multiple child processes, and only one child
2067 // process will be likely to have a verifier, we have to play it safer.
2068 return mChildVerifier->OnPredictPrefetch(aURI, httpStatus);
2070 return NS_OK;
2073 MOZ_DIAGNOSTIC_ASSERT(aURI, "aURI must not be null");
2075 for (auto* cp : dom::ContentParent::AllProcesses(dom::ContentParent::eLive)) {
2076 PNeckoParent* neckoParent = SingleManagedOrNull(cp->ManagedPNeckoParent());
2077 if (!neckoParent) {
2078 continue;
2080 if (!neckoParent->SendPredOnPredictPrefetch(aURI, httpStatus)) {
2081 return NS_ERROR_NOT_AVAILABLE;
2085 return NS_OK;
2088 NS_IMETHODIMP
2089 Predictor::OnPredictPreconnect(nsIURI* aURI) {
2090 if (IsNeckoChild()) {
2091 if (mChildVerifier) {
2092 // Ideally, we'd assert here. But since we're slowly moving towards a
2093 // world where we have multiple child processes, and only one child
2094 // process will be likely to have a verifier, we have to play it safer.
2095 return mChildVerifier->OnPredictPreconnect(aURI);
2097 return NS_OK;
2100 MOZ_DIAGNOSTIC_ASSERT(aURI, "aURI must not be null");
2102 for (auto* cp : dom::ContentParent::AllProcesses(dom::ContentParent::eLive)) {
2103 PNeckoParent* neckoParent = SingleManagedOrNull(cp->ManagedPNeckoParent());
2104 if (!neckoParent) {
2105 continue;
2107 if (!neckoParent->SendPredOnPredictPreconnect(aURI)) {
2108 return NS_ERROR_NOT_AVAILABLE;
2112 return NS_OK;
2115 NS_IMETHODIMP
2116 Predictor::OnPredictDNS(nsIURI* aURI) {
2117 if (IsNeckoChild()) {
2118 if (mChildVerifier) {
2119 // Ideally, we'd assert here. But since we're slowly moving towards a
2120 // world where we have multiple child processes, and only one child
2121 // process will be likely to have a verifier, we have to play it safer.
2122 return mChildVerifier->OnPredictDNS(aURI);
2124 return NS_OK;
2127 MOZ_DIAGNOSTIC_ASSERT(aURI, "aURI must not be null");
2129 for (auto* cp : dom::ContentParent::AllProcesses(dom::ContentParent::eLive)) {
2130 PNeckoParent* neckoParent = SingleManagedOrNull(cp->ManagedPNeckoParent());
2131 if (!neckoParent) {
2132 continue;
2134 if (!neckoParent->SendPredOnPredictDNS(aURI)) {
2135 return NS_ERROR_NOT_AVAILABLE;
2139 return NS_OK;
2142 // Predictor::PrefetchListener
2143 // nsISupports
2144 NS_IMPL_ISUPPORTS(Predictor::PrefetchListener, nsIStreamListener,
2145 nsIRequestObserver)
2147 // nsIRequestObserver
2148 NS_IMETHODIMP
2149 Predictor::PrefetchListener::OnStartRequest(nsIRequest* aRequest) {
2150 mStartTime = TimeStamp::Now();
2151 return NS_OK;
2154 NS_IMETHODIMP
2155 Predictor::PrefetchListener::OnStopRequest(nsIRequest* aRequest,
2156 nsresult aStatusCode) {
2157 PREDICTOR_LOG(("OnStopRequest this=%p aStatusCode=0x%" PRIX32, this,
2158 static_cast<uint32_t>(aStatusCode)));
2159 NS_ENSURE_ARG(aRequest);
2160 if (NS_FAILED(aStatusCode)) {
2161 return aStatusCode;
2163 Telemetry::AccumulateTimeDelta(Telemetry::PREDICTOR_PREFETCH_TIME,
2164 mStartTime);
2166 nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aRequest);
2167 if (!httpChannel) {
2168 PREDICTOR_LOG((" Could not get HTTP Channel!"));
2169 return NS_ERROR_UNEXPECTED;
2171 nsCOMPtr<nsICachingChannel> cachingChannel = do_QueryInterface(httpChannel);
2172 if (!cachingChannel) {
2173 PREDICTOR_LOG((" Could not get caching channel!"));
2174 return NS_ERROR_UNEXPECTED;
2177 nsresult rv = NS_OK;
2178 uint32_t httpStatus;
2179 rv = httpChannel->GetResponseStatus(&httpStatus);
2180 if (NS_SUCCEEDED(rv) && httpStatus == 200) {
2181 rv = cachingChannel->ForceCacheEntryValidFor(
2182 StaticPrefs::network_predictor_prefetch_force_valid_for());
2183 PREDICTOR_LOG((" forcing entry valid for %d seconds rv=%" PRIX32,
2184 StaticPrefs::network_predictor_prefetch_force_valid_for(),
2185 static_cast<uint32_t>(rv)));
2186 } else {
2187 rv = cachingChannel->ForceCacheEntryValidFor(0);
2188 Telemetry::AccumulateCategorical(
2189 Telemetry::LABELS_PREDICTOR_PREFETCH_USE_STATUS::Not200);
2190 PREDICTOR_LOG((" removing any forced validity rv=%" PRIX32,
2191 static_cast<uint32_t>(rv)));
2194 nsAutoCString reqName;
2195 rv = aRequest->GetName(reqName);
2196 if (NS_FAILED(rv)) {
2197 reqName.AssignLiteral("<unknown>");
2200 PREDICTOR_LOG((" request %s status %u", reqName.get(), httpStatus));
2202 if (mVerifier) {
2203 mVerifier->OnPredictPrefetch(mURI, httpStatus);
2206 return rv;
2209 // nsIStreamListener
2210 NS_IMETHODIMP
2211 Predictor::PrefetchListener::OnDataAvailable(nsIRequest* aRequest,
2212 nsIInputStream* aInputStream,
2213 uint64_t aOffset,
2214 const uint32_t aCount) {
2215 uint32_t result;
2216 return aInputStream->ReadSegments(NS_DiscardSegment, nullptr, aCount,
2217 &result);
2220 // Miscellaneous Predictor
2222 void Predictor::UpdateCacheability(nsIURI* sourceURI, nsIURI* targetURI,
2223 uint32_t httpStatus,
2224 nsHttpRequestHead& requestHead,
2225 nsHttpResponseHead* responseHead,
2226 nsILoadContextInfo* lci, bool isTracking) {
2227 MOZ_ASSERT(NS_IsMainThread());
2229 if (lci && lci->IsPrivate()) {
2230 PREDICTOR_LOG(("Predictor::UpdateCacheability in PB mode - ignoring"));
2231 return;
2234 if (!sourceURI || !targetURI) {
2235 PREDICTOR_LOG(
2236 ("Predictor::UpdateCacheability missing source or target uri"));
2237 return;
2240 if (!IsNullOrHttp(sourceURI) || !IsNullOrHttp(targetURI)) {
2241 PREDICTOR_LOG(("Predictor::UpdateCacheability non-http(s) uri"));
2242 return;
2245 RefPtr<Predictor> self = sSelf;
2246 if (self) {
2247 nsAutoCString method;
2248 requestHead.Method(method);
2250 nsAutoCString vary;
2251 Unused << responseHead->GetHeader(nsHttp::Vary, vary);
2253 nsAutoCString cacheControlHeader;
2254 Unused << responseHead->GetHeader(nsHttp::Cache_Control,
2255 cacheControlHeader);
2256 CacheControlParser cacheControl(cacheControlHeader);
2258 self->UpdateCacheabilityInternal(sourceURI, targetURI, httpStatus, method,
2259 *lci->OriginAttributesPtr(), isTracking,
2260 !vary.IsEmpty(), cacheControl.NoStore());
2264 void Predictor::UpdateCacheabilityInternal(
2265 nsIURI* sourceURI, nsIURI* targetURI, uint32_t httpStatus,
2266 const nsCString& method, const OriginAttributes& originAttributes,
2267 bool isTracking, bool couldVary, bool isNoStore) {
2268 PREDICTOR_LOG(("Predictor::UpdateCacheability httpStatus=%u", httpStatus));
2270 nsresult rv;
2272 if (!mInitialized) {
2273 PREDICTOR_LOG((" not initialized"));
2274 return;
2277 if (!StaticPrefs::network_predictor_enabled()) {
2278 PREDICTOR_LOG((" not enabled"));
2279 return;
2282 nsCOMPtr<nsICacheStorage> cacheDiskStorage;
2284 RefPtr<LoadContextInfo> lci = new LoadContextInfo(false, originAttributes);
2286 rv = mCacheStorageService->DiskCacheStorage(lci,
2287 getter_AddRefs(cacheDiskStorage));
2288 if (NS_FAILED(rv)) {
2289 PREDICTOR_LOG((" cannot get disk cache storage"));
2290 return;
2293 uint32_t openFlags = nsICacheStorage::OPEN_READONLY |
2294 nsICacheStorage::OPEN_SECRETLY |
2295 nsICacheStorage::CHECK_MULTITHREADED;
2296 RefPtr<Predictor::CacheabilityAction> action =
2297 new Predictor::CacheabilityAction(targetURI, httpStatus, method,
2298 isTracking, couldVary, isNoStore, this);
2299 nsAutoCString uri;
2300 targetURI->GetAsciiSpec(uri);
2301 PREDICTOR_LOG((" uri=%s action=%p", uri.get(), action.get()));
2302 cacheDiskStorage->AsyncOpenURI(sourceURI, ""_ns, openFlags, action);
2305 NS_IMPL_ISUPPORTS(Predictor::CacheabilityAction, nsICacheEntryOpenCallback,
2306 nsICacheEntryMetaDataVisitor);
2308 NS_IMETHODIMP
2309 Predictor::CacheabilityAction::OnCacheEntryCheck(nsICacheEntry* entry,
2310 uint32_t* result) {
2311 *result = nsICacheEntryOpenCallback::ENTRY_WANTED;
2312 return NS_OK;
2315 namespace {
2316 enum PrefetchDecisionReason {
2317 PREFETCHABLE,
2318 STATUS_NOT_200,
2319 METHOD_NOT_GET,
2320 URL_HAS_QUERY_STRING,
2321 RESOURCE_IS_TRACKING,
2322 RESOURCE_COULD_VARY,
2323 RESOURCE_IS_NO_STORE
2327 NS_IMETHODIMP
2328 Predictor::CacheabilityAction::OnCacheEntryAvailable(nsICacheEntry* entry,
2329 bool isNew,
2330 nsresult result) {
2331 MOZ_ASSERT(NS_IsMainThread());
2332 // This is being opened read-only, so isNew should always be false
2333 MOZ_ASSERT(!isNew);
2335 PREDICTOR_LOG(("CacheabilityAction::OnCacheEntryAvailable this=%p", this));
2336 if (NS_FAILED(result)) {
2337 // Nothing to do
2338 PREDICTOR_LOG((" nothing to do result=%" PRIX32 " isNew=%d",
2339 static_cast<uint32_t>(result), isNew));
2340 return NS_OK;
2343 nsCString strTargetURI;
2344 nsresult rv = mTargetURI->GetAsciiSpec(strTargetURI);
2345 if (NS_FAILED(rv)) {
2346 PREDICTOR_LOG(
2347 (" GetAsciiSpec returned %" PRIx32, static_cast<uint32_t>(rv)));
2348 return NS_OK;
2351 rv = entry->VisitMetaData(this);
2352 if (NS_FAILED(rv)) {
2353 PREDICTOR_LOG(
2354 (" VisitMetaData returned %" PRIx32, static_cast<uint32_t>(rv)));
2355 return NS_OK;
2358 nsTArray<nsCString> keysToCheck = std::move(mKeysToCheck),
2359 valuesToCheck = std::move(mValuesToCheck);
2361 bool hasQueryString = false;
2362 nsAutoCString query;
2363 if (NS_SUCCEEDED(mTargetURI->GetQuery(query)) && !query.IsEmpty()) {
2364 hasQueryString = true;
2367 MOZ_ASSERT(keysToCheck.Length() == valuesToCheck.Length());
2368 for (size_t i = 0; i < keysToCheck.Length(); ++i) {
2369 const char* key = keysToCheck[i].BeginReading();
2370 const char* value = valuesToCheck[i].BeginReading();
2371 nsCString uri;
2372 uint32_t hitCount, lastHit, flags;
2374 if (!mPredictor->ParseMetaDataEntry(key, value, uri, hitCount, lastHit,
2375 flags)) {
2376 PREDICTOR_LOG((" failed to parse key=%s value=%s", key, value));
2377 continue;
2380 if (strTargetURI.Equals(uri)) {
2381 bool prefetchable = true;
2382 PrefetchDecisionReason reason = PREFETCHABLE;
2384 if (mHttpStatus != 200) {
2385 prefetchable = false;
2386 reason = STATUS_NOT_200;
2387 } else if (!mMethod.EqualsLiteral("GET")) {
2388 prefetchable = false;
2389 reason = METHOD_NOT_GET;
2390 } else if (hasQueryString) {
2391 prefetchable = false;
2392 reason = URL_HAS_QUERY_STRING;
2393 } else if (mIsTracking) {
2394 prefetchable = false;
2395 reason = RESOURCE_IS_TRACKING;
2396 } else if (mCouldVary) {
2397 prefetchable = false;
2398 reason = RESOURCE_COULD_VARY;
2399 } else if (mIsNoStore) {
2400 // We don't set prefetchable = false yet, because we just want to know
2401 // what kind of effect this would have on prefetching.
2402 reason = RESOURCE_IS_NO_STORE;
2405 Telemetry::Accumulate(Telemetry::PREDICTOR_PREFETCH_DECISION_REASON,
2406 reason);
2408 if (prefetchable) {
2409 PREDICTOR_LOG((" marking %s cacheable", key));
2410 flags |= FLAG_PREFETCHABLE;
2411 } else {
2412 PREDICTOR_LOG((" marking %s uncacheable", key));
2413 flags &= ~FLAG_PREFETCHABLE;
2415 nsCString newValue;
2416 MakeMetadataEntry(hitCount, lastHit, flags, newValue);
2417 entry->SetMetaDataElement(key, newValue.BeginReading());
2418 break;
2422 return NS_OK;
2425 NS_IMETHODIMP
2426 Predictor::CacheabilityAction::OnMetaDataElement(const char* asciiKey,
2427 const char* asciiValue) {
2428 MOZ_ASSERT(NS_IsMainThread());
2430 if (!IsURIMetadataElement(asciiKey)) {
2431 return NS_OK;
2434 nsCString key, value;
2435 key.AssignASCII(asciiKey);
2436 value.AssignASCII(asciiValue);
2437 mKeysToCheck.AppendElement(key);
2438 mValuesToCheck.AppendElement(value);
2440 return NS_OK;
2443 } // namespace net
2444 } // namespace mozilla