Bug 1851094 - Remove layout.css.grid-item-baxis-measurement.enabled pref r=dholbert
[gecko.git] / netwerk / base / Predictor.cpp
blob0832e4ff74a50fd0317e54a30f0ecaf40324fb38
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 MOZ_DIAGNOSTIC_ASSERT(gNeckoChild);
492 PREDICTOR_LOG((" called on child process"));
493 // If two different threads are predicting concurently, this will be
494 // overwritten. Thankfully, we only use this in tests, which will
495 // overwrite mVerifier perhaps multiple times for each individual test;
496 // however, within each test, the multiple predict calls should have the
497 // same verifier.
498 if (verifier) {
499 PREDICTOR_LOG((" was given a verifier"));
500 mChildVerifier = verifier;
502 PREDICTOR_LOG((" forwarding to parent process"));
503 gNeckoChild->SendPredPredict(targetURI, sourceURI, reason, originAttributes,
504 verifier);
505 return NS_OK;
508 PREDICTOR_LOG((" called on parent process"));
510 if (!mInitialized) {
511 PREDICTOR_LOG((" not initialized"));
512 return NS_OK;
515 if (!StaticPrefs::network_predictor_enabled()) {
516 PREDICTOR_LOG((" not enabled"));
517 return NS_OK;
520 if (originAttributes.mPrivateBrowsingId > 0) {
521 // Don't want to do anything in PB mode
522 PREDICTOR_LOG((" in PB mode"));
523 return NS_OK;
526 if (!IsNullOrHttp(targetURI) || !IsNullOrHttp(sourceURI)) {
527 // Nothing we can do for non-HTTP[S] schemes
528 PREDICTOR_LOG((" got non-http[s] URI"));
529 return NS_OK;
532 // Ensure we've been given the appropriate arguments for the kind of
533 // prediction we're being asked to do
534 nsCOMPtr<nsIURI> uriKey = targetURI;
535 nsCOMPtr<nsIURI> originKey;
536 switch (reason) {
537 case nsINetworkPredictor::PREDICT_LINK:
538 if (!targetURI || !sourceURI) {
539 PREDICTOR_LOG((" link invalid URI state"));
540 return NS_ERROR_INVALID_ARG;
542 // Link hover is a special case where we can predict without hitting the
543 // db, so let's go ahead and fire off that prediction here.
544 PredictForLink(targetURI, sourceURI, originAttributes, verifier);
545 return NS_OK;
546 case nsINetworkPredictor::PREDICT_LOAD:
547 if (!targetURI || sourceURI) {
548 PREDICTOR_LOG((" load invalid URI state"));
549 return NS_ERROR_INVALID_ARG;
551 break;
552 case nsINetworkPredictor::PREDICT_STARTUP:
553 if (targetURI || sourceURI) {
554 PREDICTOR_LOG((" startup invalid URI state"));
555 return NS_ERROR_INVALID_ARG;
557 uriKey = mStartupURI;
558 originKey = mStartupURI;
559 break;
560 default:
561 PREDICTOR_LOG((" invalid reason"));
562 return NS_ERROR_INVALID_ARG;
565 Predictor::Reason argReason{};
566 argReason.mPredict = reason;
568 // First we open the regular cache entry, to ensure we don't gum up the works
569 // waiting on the less-important predictor-only cache entry
570 RefPtr<Predictor::Action> uriAction = new Predictor::Action(
571 Predictor::Action::IS_FULL_URI, Predictor::Action::DO_PREDICT, argReason,
572 targetURI, nullptr, verifier, this);
573 nsAutoCString uriKeyStr;
574 uriKey->GetAsciiSpec(uriKeyStr);
575 PREDICTOR_LOG((" Predict uri=%s reason=%d action=%p", uriKeyStr.get(),
576 reason, uriAction.get()));
578 nsCOMPtr<nsICacheStorage> cacheDiskStorage;
580 RefPtr<LoadContextInfo> lci = new LoadContextInfo(false, originAttributes);
582 nsresult rv = mCacheStorageService->DiskCacheStorage(
583 lci, getter_AddRefs(cacheDiskStorage));
584 NS_ENSURE_SUCCESS(rv, rv);
586 uint32_t openFlags =
587 nsICacheStorage::OPEN_READONLY | nsICacheStorage::OPEN_SECRETLY |
588 nsICacheStorage::OPEN_PRIORITY | nsICacheStorage::CHECK_MULTITHREADED;
589 cacheDiskStorage->AsyncOpenURI(uriKey, ""_ns, openFlags, uriAction);
591 // Now we do the origin-only (and therefore predictor-only) entry
592 nsCOMPtr<nsIURI> targetOrigin;
593 rv = ExtractOrigin(uriKey, getter_AddRefs(targetOrigin));
594 NS_ENSURE_SUCCESS(rv, rv);
595 if (!originKey) {
596 originKey = targetOrigin;
599 RefPtr<Predictor::Action> originAction = new Predictor::Action(
600 Predictor::Action::IS_ORIGIN, Predictor::Action::DO_PREDICT, argReason,
601 targetOrigin, nullptr, verifier, this);
602 nsAutoCString originKeyStr;
603 originKey->GetAsciiSpec(originKeyStr);
604 PREDICTOR_LOG((" Predict origin=%s reason=%d action=%p",
605 originKeyStr.get(), reason, originAction.get()));
606 openFlags = nsICacheStorage::OPEN_READONLY | nsICacheStorage::OPEN_SECRETLY |
607 nsICacheStorage::CHECK_MULTITHREADED;
608 cacheDiskStorage->AsyncOpenURI(originKey,
609 nsLiteralCString(PREDICTOR_ORIGIN_EXTENSION),
610 openFlags, originAction);
612 PREDICTOR_LOG((" predict returning"));
613 return NS_OK;
616 bool Predictor::PredictInternal(PredictorPredictReason reason,
617 nsICacheEntry* entry, bool isNew, bool fullUri,
618 nsIURI* targetURI,
619 nsINetworkPredictorVerifier* verifier,
620 uint8_t stackCount) {
621 MOZ_ASSERT(NS_IsMainThread());
623 PREDICTOR_LOG(("Predictor::PredictInternal"));
624 bool rv = false;
626 nsCOMPtr<nsILoadContextInfo> lci;
627 entry->GetLoadContextInfo(getter_AddRefs(lci));
629 if (!lci) {
630 return rv;
633 if (reason == nsINetworkPredictor::PREDICT_LOAD) {
634 MaybeLearnForStartup(targetURI, fullUri, *lci->OriginAttributesPtr());
637 if (isNew) {
638 // nothing else we can do here
639 PREDICTOR_LOG((" new entry"));
640 return rv;
643 switch (reason) {
644 case nsINetworkPredictor::PREDICT_LOAD:
645 rv = PredictForPageload(entry, targetURI, stackCount, fullUri, verifier);
646 break;
647 case nsINetworkPredictor::PREDICT_STARTUP:
648 rv = PredictForStartup(entry, fullUri, verifier);
649 break;
650 default:
651 PREDICTOR_LOG((" invalid reason"));
652 MOZ_ASSERT(false, "Got unexpected value for prediction reason");
655 return rv;
658 void Predictor::PredictForLink(nsIURI* targetURI, nsIURI* sourceURI,
659 const OriginAttributes& originAttributes,
660 nsINetworkPredictorVerifier* verifier) {
661 MOZ_ASSERT(NS_IsMainThread());
663 PREDICTOR_LOG(("Predictor::PredictForLink"));
664 if (!mSpeculativeService) {
665 PREDICTOR_LOG((" missing speculative service"));
666 return;
669 if (!StaticPrefs::network_predictor_enable_hover_on_ssl()) {
670 if (sourceURI->SchemeIs("https")) {
671 // We don't want to predict from an HTTPS page, to avoid info leakage
672 PREDICTOR_LOG((" Not predicting for link hover - on an SSL page"));
673 return;
677 nsCOMPtr<nsIPrincipal> principal =
678 BasePrincipal::CreateContentPrincipal(targetURI, originAttributes);
680 mSpeculativeService->SpeculativeConnect(targetURI, principal, nullptr, false);
681 if (verifier) {
682 PREDICTOR_LOG((" sending verification"));
683 verifier->OnPredictPreconnect(targetURI);
687 // This is the driver for prediction based on a new pageload.
688 static const uint8_t MAX_PAGELOAD_DEPTH = 10;
689 bool Predictor::PredictForPageload(nsICacheEntry* entry, nsIURI* targetURI,
690 uint8_t stackCount, bool fullUri,
691 nsINetworkPredictorVerifier* verifier) {
692 MOZ_ASSERT(NS_IsMainThread());
694 PREDICTOR_LOG(("Predictor::PredictForPageload"));
696 if (stackCount > MAX_PAGELOAD_DEPTH) {
697 PREDICTOR_LOG((" exceeded recursion depth!"));
698 return false;
701 uint32_t lastLoad;
702 nsresult rv = entry->GetLastFetched(&lastLoad);
703 NS_ENSURE_SUCCESS(rv, false);
705 int32_t globalDegradation = CalculateGlobalDegradation(lastLoad);
706 PREDICTOR_LOG((" globalDegradation = %d", globalDegradation));
708 uint32_t loadCount;
709 rv = entry->GetFetchCount(&loadCount);
710 NS_ENSURE_SUCCESS(rv, false);
712 nsCOMPtr<nsILoadContextInfo> lci;
714 rv = entry->GetLoadContextInfo(getter_AddRefs(lci));
715 NS_ENSURE_SUCCESS(rv, false);
717 nsCOMPtr<nsIURI> redirectURI;
718 if (WouldRedirect(entry, loadCount, lastLoad, globalDegradation,
719 getter_AddRefs(redirectURI))) {
720 mPreconnects.AppendElement(redirectURI);
721 Predictor::Reason reason{};
722 reason.mPredict = nsINetworkPredictor::PREDICT_LOAD;
723 RefPtr<Predictor::Action> redirectAction = new Predictor::Action(
724 Predictor::Action::IS_FULL_URI, Predictor::Action::DO_PREDICT, reason,
725 redirectURI, nullptr, verifier, this, stackCount + 1);
726 nsAutoCString redirectUriString;
727 redirectURI->GetAsciiSpec(redirectUriString);
729 nsCOMPtr<nsICacheStorage> cacheDiskStorage;
731 rv = mCacheStorageService->DiskCacheStorage(
732 lci, getter_AddRefs(cacheDiskStorage));
733 NS_ENSURE_SUCCESS(rv, false);
735 PREDICTOR_LOG((" Predict redirect uri=%s action=%p",
736 redirectUriString.get(), redirectAction.get()));
737 uint32_t openFlags =
738 nsICacheStorage::OPEN_READONLY | nsICacheStorage::OPEN_SECRETLY |
739 nsICacheStorage::OPEN_PRIORITY | nsICacheStorage::CHECK_MULTITHREADED;
740 cacheDiskStorage->AsyncOpenURI(redirectURI, ""_ns, openFlags,
741 redirectAction);
742 return RunPredictions(nullptr, *lci->OriginAttributesPtr(), verifier);
745 CalculatePredictions(entry, targetURI, lastLoad, loadCount, globalDegradation,
746 fullUri);
748 return RunPredictions(targetURI, *lci->OriginAttributesPtr(), verifier);
751 // This is the driver for predicting at browser startup time based on pages that
752 // have previously been loaded close to startup.
753 bool Predictor::PredictForStartup(nsICacheEntry* entry, bool fullUri,
754 nsINetworkPredictorVerifier* verifier) {
755 MOZ_ASSERT(NS_IsMainThread());
757 PREDICTOR_LOG(("Predictor::PredictForStartup"));
759 nsCOMPtr<nsILoadContextInfo> lci;
761 nsresult rv = entry->GetLoadContextInfo(getter_AddRefs(lci));
762 NS_ENSURE_SUCCESS(rv, false);
764 int32_t globalDegradation = CalculateGlobalDegradation(mLastStartupTime);
765 CalculatePredictions(entry, nullptr, mLastStartupTime, mStartupCount,
766 globalDegradation, fullUri);
767 return RunPredictions(nullptr, *lci->OriginAttributesPtr(), verifier);
770 // This calculates how much to degrade our confidence in our data based on
771 // the last time this top-level resource was loaded. This "global degradation"
772 // applies to *all* subresources we have associated with the top-level
773 // resource. This will be in addition to any reduction in confidence we have
774 // associated with a particular subresource.
775 int32_t Predictor::CalculateGlobalDegradation(uint32_t lastLoad) {
776 MOZ_ASSERT(NS_IsMainThread());
778 int32_t globalDegradation;
779 uint32_t delta = NOW_IN_SECONDS() - lastLoad;
780 if (delta < ONE_DAY) {
781 globalDegradation = StaticPrefs::network_predictor_page_degradation_day();
782 } else if (delta < ONE_WEEK) {
783 globalDegradation = StaticPrefs::network_predictor_page_degradation_week();
784 } else if (delta < ONE_MONTH) {
785 globalDegradation = StaticPrefs::network_predictor_page_degradation_month();
786 } else if (delta < ONE_YEAR) {
787 globalDegradation = StaticPrefs::network_predictor_page_degradation_year();
788 } else {
789 globalDegradation = StaticPrefs::network_predictor_page_degradation_max();
792 Telemetry::Accumulate(Telemetry::PREDICTOR_GLOBAL_DEGRADATION,
793 globalDegradation);
794 return globalDegradation;
797 // This calculates our overall confidence that a particular subresource will be
798 // loaded as part of a top-level load.
799 // @param hitCount - the number of times we have loaded this subresource as part
800 // of this top-level load
801 // @param hitsPossible - the number of times we have performed this top-level
802 // load
803 // @param lastHit - the timestamp of the last time we loaded this subresource as
804 // part of this top-level load
805 // @param lastPossible - the timestamp of the last time we performed this
806 // top-level load
807 // @param globalDegradation - the degradation for this top-level load as
808 // determined by CalculateGlobalDegradation
809 int32_t Predictor::CalculateConfidence(uint32_t hitCount, uint32_t hitsPossible,
810 uint32_t lastHit, uint32_t lastPossible,
811 int32_t globalDegradation) {
812 MOZ_ASSERT(NS_IsMainThread());
814 Telemetry::AutoCounter<Telemetry::PREDICTOR_PREDICTIONS_CALCULATED>
815 predictionsCalculated;
816 ++predictionsCalculated;
818 if (!hitsPossible) {
819 return 0;
822 int32_t baseConfidence = (hitCount * 100) / hitsPossible;
823 int32_t maxConfidence = 100;
824 int32_t confidenceDegradation = 0;
826 if (lastHit < lastPossible) {
827 // We didn't load this subresource the last time this top-level load was
828 // performed, so let's not bother preconnecting (at the very least).
829 maxConfidence =
830 StaticPrefs::network_predictor_preconnect_min_confidence() - 1;
832 // Now calculate how much we want to degrade our confidence based on how
833 // long it's been between the last time we did this top-level load and the
834 // last time this top-level load included this subresource.
835 PRTime delta = lastPossible - lastHit;
836 if (delta == 0) {
837 confidenceDegradation = 0;
838 } else if (delta < ONE_DAY) {
839 confidenceDegradation =
840 StaticPrefs::network_predictor_subresource_degradation_day();
841 } else if (delta < ONE_WEEK) {
842 confidenceDegradation =
843 StaticPrefs::network_predictor_subresource_degradation_week();
844 } else if (delta < ONE_MONTH) {
845 confidenceDegradation =
846 StaticPrefs::network_predictor_subresource_degradation_month();
847 } else if (delta < ONE_YEAR) {
848 confidenceDegradation =
849 StaticPrefs::network_predictor_subresource_degradation_year();
850 } else {
851 confidenceDegradation =
852 StaticPrefs::network_predictor_subresource_degradation_max();
853 maxConfidence = 0;
857 // Calculate our confidence and clamp it to between 0 and maxConfidence
858 // (<= 100)
859 int32_t confidence =
860 baseConfidence - confidenceDegradation - globalDegradation;
861 confidence = std::max(confidence, 0);
862 confidence = std::min(confidence, maxConfidence);
864 Telemetry::Accumulate(Telemetry::PREDICTOR_BASE_CONFIDENCE, baseConfidence);
865 Telemetry::Accumulate(Telemetry::PREDICTOR_SUBRESOURCE_DEGRADATION,
866 confidenceDegradation);
867 Telemetry::Accumulate(Telemetry::PREDICTOR_CONFIDENCE, confidence);
868 return confidence;
871 static void MakeMetadataEntry(const uint32_t hitCount, const uint32_t lastHit,
872 const uint32_t flags, nsCString& newValue) {
873 newValue.Truncate();
874 newValue.AppendInt(METADATA_VERSION);
875 newValue.Append(',');
876 newValue.AppendInt(hitCount);
877 newValue.Append(',');
878 newValue.AppendInt(lastHit);
879 newValue.Append(',');
880 newValue.AppendInt(flags);
883 // On every page load, the rolling window gets shifted by one bit, leaving the
884 // lowest bit at 0, to indicate that the subresource in question has not been
885 // seen on the most recent page load. If, at some point later during the page
886 // load, the subresource is seen again, we will then set the lowest bit to 1.
887 // This is how we keep track of how many of the last n pageloads (for n <= 20) a
888 // particular subresource has been seen. The rolling window is kept in the upper
889 // 20 bits of the flags element of the metadata. This saves 12 bits for regular
890 // old flags.
891 void Predictor::UpdateRollingLoadCount(nsICacheEntry* entry,
892 const uint32_t flags, const char* key,
893 const uint32_t hitCount,
894 const uint32_t lastHit) {
895 // Extract just the rolling load count from the flags, shift it to clear the
896 // lowest bit, and put the new value with the existing flags.
897 uint32_t rollingLoadCount = flags & ~kFlagsMask;
898 rollingLoadCount <<= 1;
899 uint32_t newFlags = (flags & kFlagsMask) | rollingLoadCount;
901 // Finally, update the metadata on the cache entry.
902 nsAutoCString newValue;
903 MakeMetadataEntry(hitCount, lastHit, newFlags, newValue);
904 entry->SetMetaDataElement(key, newValue.BeginReading());
907 uint32_t Predictor::ClampedPrefetchRollingLoadCount() {
908 int32_t n = StaticPrefs::network_predictor_prefetch_rolling_load_count();
909 if (n < 0) {
910 return 0;
912 if (n > kMaxPrefetchRollingLoadCount) {
913 return kMaxPrefetchRollingLoadCount;
915 return n;
918 void Predictor::CalculatePredictions(nsICacheEntry* entry, nsIURI* referrer,
919 uint32_t lastLoad, uint32_t loadCount,
920 int32_t globalDegradation, bool fullUri) {
921 MOZ_ASSERT(NS_IsMainThread());
923 // Since the visitor gets called under a cache lock, all we do there is get
924 // copies of the keys/values we care about, and then do the real work here
925 entry->VisitMetaData(this);
926 nsTArray<nsCString> keysToOperateOn = std::move(mKeysToOperateOn),
927 valuesToOperateOn = std::move(mValuesToOperateOn);
929 MOZ_ASSERT(keysToOperateOn.Length() == valuesToOperateOn.Length());
930 for (size_t i = 0; i < keysToOperateOn.Length(); ++i) {
931 const char* key = keysToOperateOn[i].BeginReading();
932 const char* value = valuesToOperateOn[i].BeginReading();
934 nsCString uri;
935 uint32_t hitCount, lastHit, flags;
936 if (!ParseMetaDataEntry(key, value, uri, hitCount, lastHit, flags)) {
937 // This failed, get rid of it so we don't waste space
938 entry->SetMetaDataElement(key, nullptr);
939 continue;
942 int32_t confidence = CalculateConfidence(hitCount, loadCount, lastHit,
943 lastLoad, globalDegradation);
944 if (fullUri) {
945 UpdateRollingLoadCount(entry, flags, key, hitCount, lastHit);
947 PREDICTOR_LOG(("CalculatePredictions key=%s value=%s confidence=%d", key,
948 value, confidence));
949 PrefetchIgnoreReason reason = PREFETCH_OK;
950 if (!fullUri) {
951 // Not full URI - don't prefetch! No sense in it!
952 PREDICTOR_LOG((" forcing non-cacheability - not full URI"));
953 if (flags & FLAG_PREFETCHABLE) {
954 // This only applies if we had somehow otherwise marked this
955 // prefetchable.
956 reason = NOT_FULL_URI;
958 flags &= ~FLAG_PREFETCHABLE;
959 } else if (!referrer) {
960 // No referrer means we can't prefetch, so pretend it's non-cacheable,
961 // no matter what.
962 PREDICTOR_LOG((" forcing non-cacheability - no referrer"));
963 if (flags & FLAG_PREFETCHABLE) {
964 // This only applies if we had somehow otherwise marked this
965 // prefetchable.
966 reason = NO_REFERRER;
968 flags &= ~FLAG_PREFETCHABLE;
969 } else {
970 uint32_t expectedRollingLoadCount =
971 (1 << ClampedPrefetchRollingLoadCount()) - 1;
972 expectedRollingLoadCount <<= kRollingLoadOffset;
973 if ((flags & expectedRollingLoadCount) != expectedRollingLoadCount) {
974 PREDICTOR_LOG((" forcing non-cacheability - missed a load"));
975 if (flags & FLAG_PREFETCHABLE) {
976 // This only applies if we had somehow otherwise marked this
977 // prefetchable.
978 reason = MISSED_A_LOAD;
980 flags &= ~FLAG_PREFETCHABLE;
984 PREDICTOR_LOG((" setting up prediction"));
985 SetupPrediction(confidence, flags, uri, reason);
989 // (Maybe) adds a predictive action to the prediction runner, based on our
990 // calculated confidence for the subresource in question.
991 void Predictor::SetupPrediction(int32_t confidence, uint32_t flags,
992 const nsCString& uri,
993 PrefetchIgnoreReason earlyReason) {
994 MOZ_ASSERT(NS_IsMainThread());
996 nsresult rv = NS_OK;
997 PREDICTOR_LOG(
998 ("SetupPrediction enable-prefetch=%d prefetch-min-confidence=%d "
999 "preconnect-min-confidence=%d preresolve-min-confidence=%d "
1000 "flags=%d confidence=%d uri=%s",
1001 StaticPrefs::network_predictor_enable_prefetch(),
1002 StaticPrefs::network_predictor_prefetch_min_confidence(),
1003 StaticPrefs::network_predictor_preconnect_min_confidence(),
1004 StaticPrefs::network_predictor_preresolve_min_confidence(), flags,
1005 confidence, uri.get()));
1007 bool prefetchOk = !!(flags & FLAG_PREFETCHABLE);
1008 PrefetchIgnoreReason reason = earlyReason;
1009 if (prefetchOk && !StaticPrefs::network_predictor_enable_prefetch()) {
1010 prefetchOk = false;
1011 reason = PREFETCH_DISABLED;
1012 } else if (prefetchOk && !ClampedPrefetchRollingLoadCount() &&
1013 confidence <
1014 StaticPrefs::network_predictor_prefetch_min_confidence()) {
1015 prefetchOk = false;
1016 if (!ClampedPrefetchRollingLoadCount()) {
1017 reason = PREFETCH_DISABLED_VIA_COUNT;
1018 } else {
1019 reason = CONFIDENCE_TOO_LOW;
1023 // prefetchOk == false and reason == PREFETCH_OK indicates that the reason
1024 // we aren't prefetching this item is because it was marked un-prefetchable in
1025 // our metadata. We already have separate telemetry on that decision, so we
1026 // aren't going to accumulate more here. Right now we only care about why
1027 // something we had marked prefetchable isn't being prefetched.
1028 if (!prefetchOk && reason != PREFETCH_OK) {
1029 Telemetry::Accumulate(Telemetry::PREDICTOR_PREFETCH_IGNORE_REASON, reason);
1032 if (prefetchOk) {
1033 nsCOMPtr<nsIURI> prefetchURI;
1034 rv = NS_NewURI(getter_AddRefs(prefetchURI), uri);
1035 if (NS_SUCCEEDED(rv)) {
1036 mPrefetches.AppendElement(prefetchURI);
1038 } else if (confidence >=
1039 StaticPrefs::network_predictor_preconnect_min_confidence()) {
1040 nsCOMPtr<nsIURI> preconnectURI;
1041 rv = NS_NewURI(getter_AddRefs(preconnectURI), uri);
1042 if (NS_SUCCEEDED(rv)) {
1043 mPreconnects.AppendElement(preconnectURI);
1045 } else if (confidence >=
1046 StaticPrefs::network_predictor_preresolve_min_confidence()) {
1047 nsCOMPtr<nsIURI> preresolveURI;
1048 rv = NS_NewURI(getter_AddRefs(preresolveURI), uri);
1049 if (NS_SUCCEEDED(rv)) {
1050 mPreresolves.AppendElement(preresolveURI);
1054 if (NS_FAILED(rv)) {
1055 PREDICTOR_LOG(
1056 (" NS_NewURI returned 0x%" PRIx32, static_cast<uint32_t>(rv)));
1060 nsresult Predictor::Prefetch(nsIURI* uri, nsIURI* referrer,
1061 const OriginAttributes& originAttributes,
1062 nsINetworkPredictorVerifier* verifier) {
1063 nsAutoCString strUri, strReferrer;
1064 uri->GetAsciiSpec(strUri);
1065 referrer->GetAsciiSpec(strReferrer);
1066 PREDICTOR_LOG(("Predictor::Prefetch uri=%s referrer=%s verifier=%p",
1067 strUri.get(), strReferrer.get(), verifier));
1068 nsCOMPtr<nsIChannel> channel;
1069 nsresult rv = NS_NewChannel(
1070 getter_AddRefs(channel), uri, nsContentUtils::GetSystemPrincipal(),
1071 nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL,
1072 nsIContentPolicy::TYPE_OTHER, nullptr, /* nsICookieJarSettings */
1073 nullptr, /* aPerformanceStorage */
1074 nullptr, /* aLoadGroup */
1075 nullptr, /* aCallbacks */
1076 nsIRequest::LOAD_BACKGROUND);
1078 if (NS_FAILED(rv)) {
1079 PREDICTOR_LOG(
1080 (" NS_NewChannel failed rv=0x%" PRIX32, static_cast<uint32_t>(rv)));
1081 return rv;
1084 nsCOMPtr<nsILoadInfo> loadInfo = channel->LoadInfo();
1085 rv = loadInfo->SetOriginAttributes(originAttributes);
1087 if (NS_FAILED(rv)) {
1088 PREDICTOR_LOG(
1089 (" Set originAttributes into loadInfo failed rv=0x%" PRIX32,
1090 static_cast<uint32_t>(rv)));
1091 return rv;
1094 nsCOMPtr<nsIHttpChannel> httpChannel;
1095 httpChannel = do_QueryInterface(channel);
1096 if (!httpChannel) {
1097 PREDICTOR_LOG((" Could not get HTTP Channel from new channel!"));
1098 return NS_ERROR_UNEXPECTED;
1101 nsCOMPtr<nsIReferrerInfo> referrerInfo = new dom::ReferrerInfo(referrer);
1102 rv = httpChannel->SetReferrerInfoWithoutClone(referrerInfo);
1103 NS_ENSURE_SUCCESS(rv, rv);
1104 // XXX - set a header here to indicate this is a prefetch?
1106 nsCOMPtr<nsIStreamListener> listener =
1107 new PrefetchListener(verifier, uri, this);
1108 PREDICTOR_LOG((" calling AsyncOpen listener=%p channel=%p", listener.get(),
1109 channel.get()));
1110 rv = channel->AsyncOpen(listener);
1111 if (NS_FAILED(rv)) {
1112 PREDICTOR_LOG(
1113 (" AsyncOpen failed rv=0x%" PRIX32, static_cast<uint32_t>(rv)));
1116 return rv;
1119 // Runs predictions that have been set up.
1120 bool Predictor::RunPredictions(nsIURI* referrer,
1121 const OriginAttributes& originAttributes,
1122 nsINetworkPredictorVerifier* verifier) {
1123 MOZ_ASSERT(NS_IsMainThread(), "Running prediction off main thread");
1125 PREDICTOR_LOG(("Predictor::RunPredictions"));
1127 bool predicted = false;
1128 uint32_t len, i;
1130 nsTArray<nsCOMPtr<nsIURI>> prefetches = std::move(mPrefetches),
1131 preconnects = std::move(mPreconnects),
1132 preresolves = std::move(mPreresolves);
1134 Telemetry::AutoCounter<Telemetry::PREDICTOR_TOTAL_PREDICTIONS>
1135 totalPredictions;
1136 Telemetry::AutoCounter<Telemetry::PREDICTOR_TOTAL_PREFETCHES> totalPrefetches;
1137 Telemetry::AutoCounter<Telemetry::PREDICTOR_TOTAL_PRECONNECTS>
1138 totalPreconnects;
1139 Telemetry::AutoCounter<Telemetry::PREDICTOR_TOTAL_PRERESOLVES>
1140 totalPreresolves;
1142 len = prefetches.Length();
1143 for (i = 0; i < len; ++i) {
1144 PREDICTOR_LOG((" doing prefetch"));
1145 nsCOMPtr<nsIURI> uri = prefetches[i];
1146 if (NS_SUCCEEDED(Prefetch(uri, referrer, originAttributes, verifier))) {
1147 ++totalPredictions;
1148 ++totalPrefetches;
1149 predicted = true;
1153 len = preconnects.Length();
1154 for (i = 0; i < len; ++i) {
1155 PREDICTOR_LOG((" doing preconnect"));
1156 nsCOMPtr<nsIURI> uri = preconnects[i];
1157 ++totalPredictions;
1158 ++totalPreconnects;
1159 nsCOMPtr<nsIPrincipal> principal =
1160 BasePrincipal::CreateContentPrincipal(uri, originAttributes);
1161 mSpeculativeService->SpeculativeConnect(uri, principal, this, false);
1162 predicted = true;
1163 if (verifier) {
1164 PREDICTOR_LOG((" sending preconnect verification"));
1165 verifier->OnPredictPreconnect(uri);
1169 len = preresolves.Length();
1170 for (i = 0; i < len; ++i) {
1171 nsCOMPtr<nsIURI> uri = preresolves[i];
1172 ++totalPredictions;
1173 ++totalPreresolves;
1174 nsAutoCString hostname;
1175 uri->GetAsciiHost(hostname);
1176 PREDICTOR_LOG((" doing preresolve %s", hostname.get()));
1177 nsCOMPtr<nsICancelable> tmpCancelable;
1178 mDnsService->AsyncResolveNative(
1179 hostname, nsIDNSService::RESOLVE_TYPE_DEFAULT,
1180 (nsIDNSService::RESOLVE_PRIORITY_MEDIUM |
1181 nsIDNSService::RESOLVE_SPECULATE),
1182 nullptr, mDNSListener, nullptr, originAttributes,
1183 getter_AddRefs(tmpCancelable));
1185 // Fetch HTTPS RR if needed.
1186 if (StaticPrefs::network_dns_upgrade_with_https_rr() ||
1187 StaticPrefs::network_dns_use_https_rr_as_altsvc()) {
1188 mDnsService->AsyncResolveNative(
1189 hostname, nsIDNSService::RESOLVE_TYPE_HTTPSSVC,
1190 (nsIDNSService::RESOLVE_PRIORITY_MEDIUM |
1191 nsIDNSService::RESOLVE_SPECULATE),
1192 nullptr, mDNSListener, nullptr, originAttributes,
1193 getter_AddRefs(tmpCancelable));
1196 predicted = true;
1197 if (verifier) {
1198 PREDICTOR_LOG((" sending preresolve verification"));
1199 verifier->OnPredictDNS(uri);
1203 return predicted;
1206 // Find out if a top-level page is likely to redirect.
1207 bool Predictor::WouldRedirect(nsICacheEntry* entry, uint32_t loadCount,
1208 uint32_t lastLoad, int32_t globalDegradation,
1209 nsIURI** redirectURI) {
1210 // TODO - not doing redirects for first go around
1211 MOZ_ASSERT(NS_IsMainThread());
1213 return false;
1216 NS_IMETHODIMP
1217 Predictor::Learn(nsIURI* targetURI, nsIURI* sourceURI,
1218 PredictorLearnReason reason,
1219 JS::Handle<JS::Value> originAttributes, JSContext* aCx) {
1220 OriginAttributes attrs;
1222 if (!originAttributes.isObject() || !attrs.Init(aCx, originAttributes)) {
1223 return NS_ERROR_INVALID_ARG;
1226 return LearnNative(targetURI, sourceURI, reason, attrs);
1229 // Called from the main thread to update the database
1230 NS_IMETHODIMP
1231 Predictor::LearnNative(nsIURI* targetURI, nsIURI* sourceURI,
1232 PredictorLearnReason reason,
1233 const OriginAttributes& originAttributes) {
1234 MOZ_ASSERT(NS_IsMainThread(),
1235 "Predictor interface methods must be called on the main thread");
1237 PREDICTOR_LOG(("Predictor::Learn"));
1239 if (IsNeckoChild()) {
1240 MOZ_DIAGNOSTIC_ASSERT(gNeckoChild);
1242 PREDICTOR_LOG((" called on child process"));
1244 RefPtr<PredictorLearnRunnable> runnable = new PredictorLearnRunnable(
1245 targetURI, sourceURI, reason, originAttributes);
1246 SchedulerGroup::Dispatch(TaskCategory::Other, runnable.forget());
1248 return NS_OK;
1251 PREDICTOR_LOG((" called on parent process"));
1253 if (!mInitialized) {
1254 PREDICTOR_LOG((" not initialized"));
1255 return NS_OK;
1258 if (!StaticPrefs::network_predictor_enabled()) {
1259 PREDICTOR_LOG((" not enabled"));
1260 return NS_OK;
1263 if (originAttributes.mPrivateBrowsingId > 0) {
1264 // Don't want to do anything in PB mode
1265 PREDICTOR_LOG((" in PB mode"));
1266 return NS_OK;
1269 if (!IsNullOrHttp(targetURI) || !IsNullOrHttp(sourceURI)) {
1270 PREDICTOR_LOG((" got non-HTTP[S] URI"));
1271 return NS_ERROR_INVALID_ARG;
1274 nsCOMPtr<nsIURI> targetOrigin;
1275 nsCOMPtr<nsIURI> sourceOrigin;
1276 nsCOMPtr<nsIURI> uriKey;
1277 nsCOMPtr<nsIURI> originKey;
1278 nsresult rv;
1280 switch (reason) {
1281 case nsINetworkPredictor::LEARN_LOAD_TOPLEVEL:
1282 if (!targetURI || sourceURI) {
1283 PREDICTOR_LOG((" load toplevel invalid URI state"));
1284 return NS_ERROR_INVALID_ARG;
1286 rv = ExtractOrigin(targetURI, getter_AddRefs(targetOrigin));
1287 NS_ENSURE_SUCCESS(rv, rv);
1288 uriKey = targetURI;
1289 originKey = targetOrigin;
1290 break;
1291 case nsINetworkPredictor::LEARN_STARTUP:
1292 if (!targetURI || sourceURI) {
1293 PREDICTOR_LOG((" startup invalid URI state"));
1294 return NS_ERROR_INVALID_ARG;
1296 rv = ExtractOrigin(targetURI, getter_AddRefs(targetOrigin));
1297 NS_ENSURE_SUCCESS(rv, rv);
1298 uriKey = mStartupURI;
1299 originKey = mStartupURI;
1300 break;
1301 case nsINetworkPredictor::LEARN_LOAD_REDIRECT:
1302 case nsINetworkPredictor::LEARN_LOAD_SUBRESOURCE:
1303 if (!targetURI || !sourceURI) {
1304 PREDICTOR_LOG((" redirect/subresource invalid URI state"));
1305 return NS_ERROR_INVALID_ARG;
1307 rv = ExtractOrigin(targetURI, getter_AddRefs(targetOrigin));
1308 NS_ENSURE_SUCCESS(rv, rv);
1309 rv = ExtractOrigin(sourceURI, getter_AddRefs(sourceOrigin));
1310 NS_ENSURE_SUCCESS(rv, rv);
1311 uriKey = sourceURI;
1312 originKey = sourceOrigin;
1313 break;
1314 default:
1315 PREDICTOR_LOG((" invalid reason"));
1316 return NS_ERROR_INVALID_ARG;
1319 Telemetry::AutoCounter<Telemetry::PREDICTOR_LEARN_ATTEMPTS> learnAttempts;
1320 ++learnAttempts;
1322 Predictor::Reason argReason{};
1323 argReason.mLearn = reason;
1325 // We always open the full uri (general cache) entry first, so we don't gum up
1326 // the works waiting on predictor-only entries to open
1327 RefPtr<Predictor::Action> uriAction = new Predictor::Action(
1328 Predictor::Action::IS_FULL_URI, Predictor::Action::DO_LEARN, argReason,
1329 targetURI, sourceURI, nullptr, this);
1330 nsAutoCString uriKeyStr, targetUriStr, sourceUriStr;
1331 uriKey->GetAsciiSpec(uriKeyStr);
1332 targetURI->GetAsciiSpec(targetUriStr);
1333 if (sourceURI) {
1334 sourceURI->GetAsciiSpec(sourceUriStr);
1336 PREDICTOR_LOG(
1337 (" Learn uriKey=%s targetURI=%s sourceURI=%s reason=%d "
1338 "action=%p",
1339 uriKeyStr.get(), targetUriStr.get(), sourceUriStr.get(), reason,
1340 uriAction.get()));
1342 nsCOMPtr<nsICacheStorage> cacheDiskStorage;
1344 RefPtr<LoadContextInfo> lci = new LoadContextInfo(false, originAttributes);
1346 rv = mCacheStorageService->DiskCacheStorage(lci,
1347 getter_AddRefs(cacheDiskStorage));
1348 NS_ENSURE_SUCCESS(rv, rv);
1350 // For learning full URI things, we *always* open readonly and secretly, as we
1351 // rely on actual pageloads to update the entry's metadata for us.
1352 uint32_t uriOpenFlags = nsICacheStorage::OPEN_READONLY |
1353 nsICacheStorage::OPEN_SECRETLY |
1354 nsICacheStorage::CHECK_MULTITHREADED;
1355 if (reason == nsINetworkPredictor::LEARN_LOAD_TOPLEVEL) {
1356 // Learning for toplevel we want to open the full uri entry priority, since
1357 // it's likely this entry will be used soon anyway, and we want this to be
1358 // opened ASAP.
1359 uriOpenFlags |= nsICacheStorage::OPEN_PRIORITY;
1361 cacheDiskStorage->AsyncOpenURI(uriKey, ""_ns, uriOpenFlags, uriAction);
1363 // Now we open the origin-only (and therefore predictor-only) entry
1364 RefPtr<Predictor::Action> originAction = new Predictor::Action(
1365 Predictor::Action::IS_ORIGIN, Predictor::Action::DO_LEARN, argReason,
1366 targetOrigin, sourceOrigin, nullptr, this);
1367 nsAutoCString originKeyStr, targetOriginStr, sourceOriginStr;
1368 originKey->GetAsciiSpec(originKeyStr);
1369 targetOrigin->GetAsciiSpec(targetOriginStr);
1370 if (sourceOrigin) {
1371 sourceOrigin->GetAsciiSpec(sourceOriginStr);
1373 PREDICTOR_LOG(
1374 (" Learn originKey=%s targetOrigin=%s sourceOrigin=%s reason=%d "
1375 "action=%p",
1376 originKeyStr.get(), targetOriginStr.get(), sourceOriginStr.get(), reason,
1377 originAction.get()));
1378 uint32_t originOpenFlags;
1379 if (reason == nsINetworkPredictor::LEARN_LOAD_TOPLEVEL) {
1380 // This is the only case when we want to update the 'last used' metadata on
1381 // the cache entry we're getting. This only applies to predictor-specific
1382 // entries.
1383 originOpenFlags =
1384 nsICacheStorage::OPEN_NORMALLY | nsICacheStorage::CHECK_MULTITHREADED;
1385 } else {
1386 originOpenFlags = nsICacheStorage::OPEN_READONLY |
1387 nsICacheStorage::OPEN_SECRETLY |
1388 nsICacheStorage::CHECK_MULTITHREADED;
1390 cacheDiskStorage->AsyncOpenURI(originKey,
1391 nsLiteralCString(PREDICTOR_ORIGIN_EXTENSION),
1392 originOpenFlags, originAction);
1394 PREDICTOR_LOG(("Predictor::Learn returning"));
1395 return NS_OK;
1398 void Predictor::LearnInternal(PredictorLearnReason reason, nsICacheEntry* entry,
1399 bool isNew, bool fullUri, nsIURI* targetURI,
1400 nsIURI* sourceURI) {
1401 MOZ_ASSERT(NS_IsMainThread());
1403 PREDICTOR_LOG(("Predictor::LearnInternal"));
1405 nsCString junk;
1406 if (!fullUri && reason == nsINetworkPredictor::LEARN_LOAD_TOPLEVEL &&
1407 NS_FAILED(
1408 entry->GetMetaDataElement(SEEN_META_DATA, getter_Copies(junk)))) {
1409 // This is an origin-only entry that we haven't seen before. Let's mark it
1410 // as seen.
1411 PREDICTOR_LOG((" marking new origin entry as seen"));
1412 nsresult rv = entry->SetMetaDataElement(SEEN_META_DATA, "1");
1413 if (NS_FAILED(rv)) {
1414 PREDICTOR_LOG((" failed to mark origin entry seen"));
1415 return;
1418 // Need to ensure someone else can get to the entry if necessary
1419 entry->MetaDataReady();
1420 return;
1423 switch (reason) {
1424 case nsINetworkPredictor::LEARN_LOAD_TOPLEVEL:
1425 // This case only exists to be used during tests - code outside the
1426 // predictor tests should NEVER call Learn with LEARN_LOAD_TOPLEVEL.
1427 // The predictor xpcshell test needs this branch, however, because we
1428 // have no real page loads in xpcshell, and this is how we fake it up
1429 // so that all the work that normally happens behind the scenes in a
1430 // page load can be done for testing purposes.
1431 if (fullUri && StaticPrefs::network_predictor_doing_tests()) {
1432 PREDICTOR_LOG(
1433 (" WARNING - updating rolling load count. "
1434 "If you see this outside tests, you did it wrong"));
1436 // Since the visitor gets called under a cache lock, all we do there is
1437 // get copies of the keys/values we care about, and then do the real
1438 // work here
1439 entry->VisitMetaData(this);
1440 nsTArray<nsCString> keysToOperateOn = std::move(mKeysToOperateOn),
1441 valuesToOperateOn = std::move(mValuesToOperateOn);
1443 MOZ_ASSERT(keysToOperateOn.Length() == valuesToOperateOn.Length());
1444 for (size_t i = 0; i < keysToOperateOn.Length(); ++i) {
1445 const char* key = keysToOperateOn[i].BeginReading();
1446 const char* value = valuesToOperateOn[i].BeginReading();
1448 nsCString uri;
1449 uint32_t hitCount, lastHit, flags;
1450 if (!ParseMetaDataEntry(key, value, uri, hitCount, lastHit, flags)) {
1451 // This failed, get rid of it so we don't waste space
1452 entry->SetMetaDataElement(key, nullptr);
1453 continue;
1455 UpdateRollingLoadCount(entry, flags, key, hitCount, lastHit);
1457 } else {
1458 PREDICTOR_LOG((" nothing to do for toplevel"));
1460 break;
1461 case nsINetworkPredictor::LEARN_LOAD_REDIRECT:
1462 if (fullUri) {
1463 LearnForRedirect(entry, targetURI);
1465 break;
1466 case nsINetworkPredictor::LEARN_LOAD_SUBRESOURCE:
1467 LearnForSubresource(entry, targetURI);
1468 break;
1469 case nsINetworkPredictor::LEARN_STARTUP:
1470 LearnForStartup(entry, targetURI);
1471 break;
1472 default:
1473 PREDICTOR_LOG((" unexpected reason value"));
1474 MOZ_ASSERT(false, "Got unexpected value for learn reason!");
1478 NS_IMPL_ISUPPORTS(Predictor::SpaceCleaner, nsICacheEntryMetaDataVisitor)
1480 NS_IMETHODIMP
1481 Predictor::SpaceCleaner::OnMetaDataElement(const char* key, const char* value) {
1482 MOZ_ASSERT(NS_IsMainThread());
1484 if (!IsURIMetadataElement(key)) {
1485 // This isn't a bit of metadata we care about
1486 return NS_OK;
1489 nsCString uri;
1490 uint32_t hitCount, lastHit, flags;
1491 bool ok =
1492 mPredictor->ParseMetaDataEntry(key, value, uri, hitCount, lastHit, flags);
1494 if (!ok) {
1495 // Couldn't parse this one, just get rid of it
1496 nsCString nsKey;
1497 nsKey.AssignASCII(key);
1498 mLongKeysToDelete.AppendElement(nsKey);
1499 return NS_OK;
1502 uint32_t uriLength = uri.Length();
1503 if (uriLength > StaticPrefs::network_predictor_max_uri_length()) {
1504 // Default to getting rid of URIs that are too long and were put in before
1505 // we had our limit on URI length, in order to free up some space.
1506 nsCString nsKey;
1507 nsKey.AssignASCII(key);
1508 mLongKeysToDelete.AppendElement(nsKey);
1509 return NS_OK;
1512 if (!mLRUKeyToDelete || lastHit < mLRUStamp) {
1513 mLRUKeyToDelete = key;
1514 mLRUStamp = lastHit;
1517 return NS_OK;
1520 void Predictor::SpaceCleaner::Finalize(nsICacheEntry* entry) {
1521 MOZ_ASSERT(NS_IsMainThread());
1523 if (mLRUKeyToDelete) {
1524 entry->SetMetaDataElement(mLRUKeyToDelete, nullptr);
1527 for (size_t i = 0; i < mLongKeysToDelete.Length(); ++i) {
1528 entry->SetMetaDataElement(mLongKeysToDelete[i].BeginReading(), nullptr);
1532 // Called when a subresource has been hit from a top-level load. Uses the two
1533 // helper functions above to update the database appropriately.
1534 void Predictor::LearnForSubresource(nsICacheEntry* entry, nsIURI* targetURI) {
1535 MOZ_ASSERT(NS_IsMainThread());
1537 PREDICTOR_LOG(("Predictor::LearnForSubresource"));
1539 uint32_t lastLoad;
1540 nsresult rv = entry->GetLastFetched(&lastLoad);
1541 NS_ENSURE_SUCCESS_VOID(rv);
1543 uint32_t loadCount;
1544 rv = entry->GetFetchCount(&loadCount);
1545 NS_ENSURE_SUCCESS_VOID(rv);
1547 nsCString key;
1548 key.AssignLiteral(META_DATA_PREFIX);
1549 nsCString uri;
1550 targetURI->GetAsciiSpec(uri);
1551 key.Append(uri);
1552 if (uri.Length() > StaticPrefs::network_predictor_max_uri_length()) {
1553 // We do this to conserve space/prevent OOMs
1554 PREDICTOR_LOG((" uri too long!"));
1555 entry->SetMetaDataElement(key.BeginReading(), nullptr);
1556 return;
1559 nsCString value;
1560 rv = entry->GetMetaDataElement(key.BeginReading(), getter_Copies(value));
1562 uint32_t hitCount, lastHit, flags;
1563 bool isNewResource =
1564 (NS_FAILED(rv) ||
1565 !ParseMetaDataEntry(key.BeginReading(), value.BeginReading(), uri,
1566 hitCount, lastHit, flags));
1568 int32_t resourceCount = 0;
1569 if (isNewResource) {
1570 // This is a new addition
1571 PREDICTOR_LOG((" new resource"));
1572 nsCString s;
1573 rv = entry->GetMetaDataElement(RESOURCE_META_DATA, getter_Copies(s));
1574 if (NS_SUCCEEDED(rv)) {
1575 resourceCount = atoi(s.BeginReading());
1577 if (resourceCount >=
1578 StaticPrefs::network_predictor_max_resources_per_entry()) {
1579 RefPtr<Predictor::SpaceCleaner> cleaner =
1580 new Predictor::SpaceCleaner(this);
1581 entry->VisitMetaData(cleaner);
1582 cleaner->Finalize(entry);
1583 } else {
1584 ++resourceCount;
1586 nsAutoCString count;
1587 count.AppendInt(resourceCount);
1588 rv = entry->SetMetaDataElement(RESOURCE_META_DATA, count.BeginReading());
1589 if (NS_FAILED(rv)) {
1590 PREDICTOR_LOG((" failed to update resource count"));
1591 return;
1593 hitCount = 1;
1594 flags = 0;
1595 } else {
1596 PREDICTOR_LOG((" existing resource"));
1597 hitCount = std::min(hitCount + 1, loadCount);
1600 // Update the rolling load count to mark this sub-resource as seen on the
1601 // most-recent pageload so it can be eligible for prefetch (assuming all
1602 // the other stars align).
1603 flags |= (1 << kRollingLoadOffset);
1605 nsCString newValue;
1606 MakeMetadataEntry(hitCount, lastLoad, flags, newValue);
1607 rv = entry->SetMetaDataElement(key.BeginReading(), newValue.BeginReading());
1608 PREDICTOR_LOG(
1609 (" SetMetaDataElement -> 0x%08" PRIX32, static_cast<uint32_t>(rv)));
1610 if (NS_FAILED(rv) && isNewResource) {
1611 // Roll back the increment to the resource count we made above.
1612 PREDICTOR_LOG((" rolling back resource count update"));
1613 --resourceCount;
1614 if (resourceCount == 0) {
1615 entry->SetMetaDataElement(RESOURCE_META_DATA, nullptr);
1616 } else {
1617 nsAutoCString count;
1618 count.AppendInt(resourceCount);
1619 entry->SetMetaDataElement(RESOURCE_META_DATA, count.BeginReading());
1624 // This is called when a top-level loaded ended up redirecting to a different
1625 // URI so we can keep track of that fact.
1626 void Predictor::LearnForRedirect(nsICacheEntry* entry, nsIURI* targetURI) {
1627 MOZ_ASSERT(NS_IsMainThread());
1629 // TODO - not doing redirects for first go around
1630 PREDICTOR_LOG(("Predictor::LearnForRedirect"));
1633 // This will add a page to our list of startup pages if it's being loaded
1634 // before our startup window has expired.
1635 void Predictor::MaybeLearnForStartup(nsIURI* uri, bool fullUri,
1636 const OriginAttributes& originAttributes) {
1637 MOZ_ASSERT(NS_IsMainThread());
1639 // TODO - not doing startup for first go around
1640 PREDICTOR_LOG(("Predictor::MaybeLearnForStartup"));
1643 // Add information about a top-level load to our list of startup pages
1644 void Predictor::LearnForStartup(nsICacheEntry* entry, nsIURI* targetURI) {
1645 MOZ_ASSERT(NS_IsMainThread());
1647 // These actually do the same set of work, just on different entries, so we
1648 // can pass through to get the real work done here
1649 PREDICTOR_LOG(("Predictor::LearnForStartup"));
1650 LearnForSubresource(entry, targetURI);
1653 bool Predictor::ParseMetaDataEntry(const char* key, const char* value,
1654 nsCString& uri, uint32_t& hitCount,
1655 uint32_t& lastHit, uint32_t& flags) {
1656 MOZ_ASSERT(NS_IsMainThread());
1658 PREDICTOR_LOG(
1659 ("Predictor::ParseMetaDataEntry key=%s value=%s", key ? key : "", value));
1661 const char* comma = strchr(value, ',');
1662 if (!comma) {
1663 PREDICTOR_LOG((" could not find first comma"));
1664 return false;
1667 uint32_t version = static_cast<uint32_t>(atoi(value));
1668 PREDICTOR_LOG((" version -> %u", version));
1670 if (version != METADATA_VERSION) {
1671 PREDICTOR_LOG(
1672 (" metadata version mismatch %u != %u", version, METADATA_VERSION));
1673 return false;
1676 value = comma + 1;
1677 comma = strchr(value, ',');
1678 if (!comma) {
1679 PREDICTOR_LOG((" could not find second comma"));
1680 return false;
1683 hitCount = static_cast<uint32_t>(atoi(value));
1684 PREDICTOR_LOG((" hitCount -> %u", hitCount));
1686 value = comma + 1;
1687 comma = strchr(value, ',');
1688 if (!comma) {
1689 PREDICTOR_LOG((" could not find third comma"));
1690 return false;
1693 lastHit = static_cast<uint32_t>(atoi(value));
1694 PREDICTOR_LOG((" lastHit -> %u", lastHit));
1696 value = comma + 1;
1697 flags = static_cast<uint32_t>(atoi(value));
1698 PREDICTOR_LOG((" flags -> %u", flags));
1700 if (key) {
1701 const char* uriStart = key + (sizeof(META_DATA_PREFIX) - 1);
1702 uri.AssignASCII(uriStart);
1703 PREDICTOR_LOG((" uri -> %s", uriStart));
1704 } else {
1705 uri.Truncate();
1708 return true;
1711 NS_IMETHODIMP
1712 Predictor::Reset() {
1713 MOZ_ASSERT(NS_IsMainThread(),
1714 "Predictor interface methods must be called on the main thread");
1716 PREDICTOR_LOG(("Predictor::Reset"));
1718 if (IsNeckoChild()) {
1719 MOZ_DIAGNOSTIC_ASSERT(gNeckoChild);
1721 PREDICTOR_LOG((" forwarding to parent process"));
1722 gNeckoChild->SendPredReset();
1723 return NS_OK;
1726 PREDICTOR_LOG((" called on parent process"));
1728 if (!mInitialized) {
1729 PREDICTOR_LOG((" not initialized"));
1730 return NS_OK;
1733 if (!StaticPrefs::network_predictor_enabled()) {
1734 PREDICTOR_LOG((" not enabled"));
1735 return NS_OK;
1738 RefPtr<Predictor::Resetter> reset = new Predictor::Resetter(this);
1739 PREDICTOR_LOG((" created a resetter"));
1740 mCacheStorageService->AsyncVisitAllStorages(reset, true);
1741 PREDICTOR_LOG((" Cache async launched, returning now"));
1743 return NS_OK;
1746 NS_IMPL_ISUPPORTS(Predictor::Resetter, nsICacheEntryOpenCallback,
1747 nsICacheEntryMetaDataVisitor, nsICacheStorageVisitor);
1749 Predictor::Resetter::Resetter(Predictor* predictor)
1750 : mEntriesToVisit(0), mPredictor(predictor) {}
1752 NS_IMETHODIMP
1753 Predictor::Resetter::OnCacheEntryCheck(nsICacheEntry* entry, uint32_t* result) {
1754 *result = nsICacheEntryOpenCallback::ENTRY_WANTED;
1755 return NS_OK;
1758 NS_IMETHODIMP
1759 Predictor::Resetter::OnCacheEntryAvailable(nsICacheEntry* entry, bool isNew,
1760 nsresult result) {
1761 MOZ_ASSERT(NS_IsMainThread());
1763 if (NS_FAILED(result)) {
1764 // This can happen when we've tried to open an entry that doesn't exist for
1765 // some non-reset operation, and then get reset shortly thereafter (as
1766 // happens in some of our tests).
1767 --mEntriesToVisit;
1768 if (!mEntriesToVisit) {
1769 Complete();
1771 return NS_OK;
1774 entry->VisitMetaData(this);
1775 nsTArray<nsCString> keysToDelete = std::move(mKeysToDelete);
1777 for (size_t i = 0; i < keysToDelete.Length(); ++i) {
1778 const char* key = keysToDelete[i].BeginReading();
1779 entry->SetMetaDataElement(key, nullptr);
1782 --mEntriesToVisit;
1783 if (!mEntriesToVisit) {
1784 Complete();
1787 return NS_OK;
1790 NS_IMETHODIMP
1791 Predictor::Resetter::OnMetaDataElement(const char* asciiKey,
1792 const char* asciiValue) {
1793 MOZ_ASSERT(NS_IsMainThread());
1795 if (!StringBeginsWith(nsDependentCString(asciiKey),
1796 nsLiteralCString(META_DATA_PREFIX))) {
1797 // Not a metadata entry we care about, carry on
1798 return NS_OK;
1801 nsCString key;
1802 key.AssignASCII(asciiKey);
1803 mKeysToDelete.AppendElement(key);
1805 return NS_OK;
1808 NS_IMETHODIMP
1809 Predictor::Resetter::OnCacheStorageInfo(uint32_t entryCount,
1810 uint64_t consumption, uint64_t capacity,
1811 nsIFile* diskDirectory) {
1812 MOZ_ASSERT(NS_IsMainThread());
1814 return NS_OK;
1817 NS_IMETHODIMP
1818 Predictor::Resetter::OnCacheEntryInfo(nsIURI* uri, const nsACString& idEnhance,
1819 int64_t dataSize, int64_t altDataSize,
1820 uint32_t fetchCount,
1821 uint32_t lastModifiedTime,
1822 uint32_t expirationTime, bool aPinned,
1823 nsILoadContextInfo* aInfo) {
1824 MOZ_ASSERT(NS_IsMainThread());
1826 nsresult rv;
1828 // The predictor will only ever touch entries with no idEnhance ("") or an
1829 // idEnhance of PREDICTOR_ORIGIN_EXTENSION, so we filter out any entries that
1830 // don't match that to avoid doing extra work.
1831 if (idEnhance.EqualsLiteral(PREDICTOR_ORIGIN_EXTENSION)) {
1832 // This is an entry we own, so we can just doom it entirely
1833 nsCOMPtr<nsICacheStorage> cacheDiskStorage;
1835 rv = mPredictor->mCacheStorageService->DiskCacheStorage(
1836 aInfo, getter_AddRefs(cacheDiskStorage));
1838 NS_ENSURE_SUCCESS(rv, rv);
1839 cacheDiskStorage->AsyncDoomURI(uri, idEnhance, nullptr);
1840 } else if (idEnhance.IsEmpty()) {
1841 // This is an entry we don't own, so we have to be a little more careful and
1842 // just get rid of our own metadata entries. Append it to an array of things
1843 // to operate on and then do the operations later so we don't end up calling
1844 // Complete() multiple times/too soon.
1845 ++mEntriesToVisit;
1846 mURIsToVisit.AppendElement(uri);
1847 mInfosToVisit.AppendElement(aInfo);
1850 return NS_OK;
1853 NS_IMETHODIMP
1854 Predictor::Resetter::OnCacheEntryVisitCompleted() {
1855 MOZ_ASSERT(NS_IsMainThread());
1857 nsresult rv;
1859 nsTArray<nsCOMPtr<nsIURI>> urisToVisit = std::move(mURIsToVisit);
1861 MOZ_ASSERT(mEntriesToVisit == urisToVisit.Length());
1863 nsTArray<nsCOMPtr<nsILoadContextInfo>> infosToVisit =
1864 std::move(mInfosToVisit);
1866 MOZ_ASSERT(mEntriesToVisit == infosToVisit.Length());
1868 if (!mEntriesToVisit) {
1869 Complete();
1870 return NS_OK;
1873 uint32_t entriesToVisit = urisToVisit.Length();
1874 for (uint32_t i = 0; i < entriesToVisit; ++i) {
1875 nsCString u;
1876 nsCOMPtr<nsICacheStorage> cacheDiskStorage;
1878 rv = mPredictor->mCacheStorageService->DiskCacheStorage(
1879 infosToVisit[i], getter_AddRefs(cacheDiskStorage));
1880 NS_ENSURE_SUCCESS(rv, rv);
1882 urisToVisit[i]->GetAsciiSpec(u);
1883 rv = cacheDiskStorage->AsyncOpenURI(
1884 urisToVisit[i], ""_ns,
1885 nsICacheStorage::OPEN_READONLY | nsICacheStorage::OPEN_SECRETLY |
1886 nsICacheStorage::CHECK_MULTITHREADED,
1887 this);
1888 if (NS_FAILED(rv)) {
1889 mEntriesToVisit--;
1890 if (!mEntriesToVisit) {
1891 Complete();
1892 return NS_OK;
1897 return NS_OK;
1900 void Predictor::Resetter::Complete() {
1901 MOZ_ASSERT(NS_IsMainThread());
1903 nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
1904 if (!obs) {
1905 PREDICTOR_LOG(("COULD NOT GET OBSERVER SERVICE!"));
1906 return;
1909 obs->NotifyObservers(nullptr, "predictor-reset-complete", nullptr);
1912 // Helper functions to make using the predictor easier from native code
1914 static StaticRefPtr<nsINetworkPredictor> sPredictor;
1916 static nsresult EnsureGlobalPredictor(nsINetworkPredictor** aPredictor) {
1917 MOZ_ASSERT(NS_IsMainThread());
1919 if (!sPredictor) {
1920 nsresult rv;
1921 nsCOMPtr<nsINetworkPredictor> predictor =
1922 do_GetService("@mozilla.org/network/predictor;1", &rv);
1923 NS_ENSURE_SUCCESS(rv, rv);
1924 sPredictor = predictor;
1925 ClearOnShutdown(&sPredictor);
1928 nsCOMPtr<nsINetworkPredictor> predictor = sPredictor.get();
1929 predictor.forget(aPredictor);
1930 return NS_OK;
1933 nsresult PredictorPredict(nsIURI* targetURI, nsIURI* sourceURI,
1934 PredictorPredictReason reason,
1935 const OriginAttributes& originAttributes,
1936 nsINetworkPredictorVerifier* verifier) {
1937 MOZ_ASSERT(NS_IsMainThread());
1939 if (!IsNullOrHttp(targetURI) || !IsNullOrHttp(sourceURI)) {
1940 return NS_OK;
1943 nsCOMPtr<nsINetworkPredictor> predictor;
1944 nsresult rv = EnsureGlobalPredictor(getter_AddRefs(predictor));
1945 NS_ENSURE_SUCCESS(rv, rv);
1947 return predictor->PredictNative(targetURI, sourceURI, reason,
1948 originAttributes, verifier);
1951 nsresult PredictorLearn(nsIURI* targetURI, nsIURI* sourceURI,
1952 PredictorLearnReason reason,
1953 const OriginAttributes& originAttributes) {
1954 MOZ_ASSERT(NS_IsMainThread());
1956 if (!IsNullOrHttp(targetURI) || !IsNullOrHttp(sourceURI)) {
1957 return NS_OK;
1960 nsCOMPtr<nsINetworkPredictor> predictor;
1961 nsresult rv = EnsureGlobalPredictor(getter_AddRefs(predictor));
1962 NS_ENSURE_SUCCESS(rv, rv);
1964 return predictor->LearnNative(targetURI, sourceURI, reason, originAttributes);
1967 nsresult PredictorLearn(nsIURI* targetURI, nsIURI* sourceURI,
1968 PredictorLearnReason reason, nsILoadGroup* loadGroup) {
1969 MOZ_ASSERT(NS_IsMainThread());
1971 if (!IsNullOrHttp(targetURI) || !IsNullOrHttp(sourceURI)) {
1972 return NS_OK;
1975 nsCOMPtr<nsINetworkPredictor> predictor;
1976 nsresult rv = EnsureGlobalPredictor(getter_AddRefs(predictor));
1977 NS_ENSURE_SUCCESS(rv, rv);
1979 nsCOMPtr<nsILoadContext> loadContext;
1980 OriginAttributes originAttributes;
1982 if (loadGroup) {
1983 nsCOMPtr<nsIInterfaceRequestor> callbacks;
1984 loadGroup->GetNotificationCallbacks(getter_AddRefs(callbacks));
1985 if (callbacks) {
1986 loadContext = do_GetInterface(callbacks);
1988 if (loadContext) {
1989 loadContext->GetOriginAttributes(originAttributes);
1994 return predictor->LearnNative(targetURI, sourceURI, reason, originAttributes);
1997 nsresult PredictorLearn(nsIURI* targetURI, nsIURI* sourceURI,
1998 PredictorLearnReason reason, dom::Document* document) {
1999 MOZ_ASSERT(NS_IsMainThread());
2001 if (!IsNullOrHttp(targetURI) || !IsNullOrHttp(sourceURI)) {
2002 return NS_OK;
2005 nsCOMPtr<nsINetworkPredictor> predictor;
2006 nsresult rv = EnsureGlobalPredictor(getter_AddRefs(predictor));
2007 NS_ENSURE_SUCCESS(rv, rv);
2009 OriginAttributes originAttributes;
2011 if (document) {
2012 nsCOMPtr<nsIPrincipal> docPrincipal = document->NodePrincipal();
2014 if (docPrincipal) {
2015 originAttributes = docPrincipal->OriginAttributesRef();
2019 return predictor->LearnNative(targetURI, sourceURI, reason, originAttributes);
2022 nsresult PredictorLearnRedirect(nsIURI* targetURI, nsIChannel* channel,
2023 const OriginAttributes& originAttributes) {
2024 MOZ_ASSERT(NS_IsMainThread());
2026 nsCOMPtr<nsIURI> sourceURI;
2027 nsresult rv = channel->GetOriginalURI(getter_AddRefs(sourceURI));
2028 NS_ENSURE_SUCCESS(rv, rv);
2030 bool sameUri;
2031 rv = targetURI->Equals(sourceURI, &sameUri);
2032 NS_ENSURE_SUCCESS(rv, rv);
2034 if (sameUri) {
2035 return NS_OK;
2038 if (!IsNullOrHttp(targetURI) || !IsNullOrHttp(sourceURI)) {
2039 return NS_OK;
2042 nsCOMPtr<nsINetworkPredictor> predictor;
2043 rv = EnsureGlobalPredictor(getter_AddRefs(predictor));
2044 NS_ENSURE_SUCCESS(rv, rv);
2046 return predictor->LearnNative(targetURI, sourceURI,
2047 nsINetworkPredictor::LEARN_LOAD_REDIRECT,
2048 originAttributes);
2051 // nsINetworkPredictorVerifier
2054 * Call through to the child's verifier (only during tests)
2056 NS_IMETHODIMP
2057 Predictor::OnPredictPrefetch(nsIURI* aURI, uint32_t httpStatus) {
2058 if (IsNeckoChild()) {
2059 if (mChildVerifier) {
2060 // Ideally, we'd assert here. But since we're slowly moving towards a
2061 // world where we have multiple child processes, and only one child
2062 // process will be likely to have a verifier, we have to play it safer.
2063 return mChildVerifier->OnPredictPrefetch(aURI, httpStatus);
2065 return NS_OK;
2068 MOZ_DIAGNOSTIC_ASSERT(aURI, "aURI must not be null");
2070 for (auto* cp : dom::ContentParent::AllProcesses(dom::ContentParent::eLive)) {
2071 PNeckoParent* neckoParent = SingleManagedOrNull(cp->ManagedPNeckoParent());
2072 if (!neckoParent) {
2073 continue;
2075 if (!neckoParent->SendPredOnPredictPrefetch(aURI, httpStatus)) {
2076 return NS_ERROR_NOT_AVAILABLE;
2080 return NS_OK;
2083 NS_IMETHODIMP
2084 Predictor::OnPredictPreconnect(nsIURI* aURI) {
2085 if (IsNeckoChild()) {
2086 if (mChildVerifier) {
2087 // Ideally, we'd assert here. But since we're slowly moving towards a
2088 // world where we have multiple child processes, and only one child
2089 // process will be likely to have a verifier, we have to play it safer.
2090 return mChildVerifier->OnPredictPreconnect(aURI);
2092 return NS_OK;
2095 MOZ_DIAGNOSTIC_ASSERT(aURI, "aURI must not be null");
2097 for (auto* cp : dom::ContentParent::AllProcesses(dom::ContentParent::eLive)) {
2098 PNeckoParent* neckoParent = SingleManagedOrNull(cp->ManagedPNeckoParent());
2099 if (!neckoParent) {
2100 continue;
2102 if (!neckoParent->SendPredOnPredictPreconnect(aURI)) {
2103 return NS_ERROR_NOT_AVAILABLE;
2107 return NS_OK;
2110 NS_IMETHODIMP
2111 Predictor::OnPredictDNS(nsIURI* aURI) {
2112 if (IsNeckoChild()) {
2113 if (mChildVerifier) {
2114 // Ideally, we'd assert here. But since we're slowly moving towards a
2115 // world where we have multiple child processes, and only one child
2116 // process will be likely to have a verifier, we have to play it safer.
2117 return mChildVerifier->OnPredictDNS(aURI);
2119 return NS_OK;
2122 MOZ_DIAGNOSTIC_ASSERT(aURI, "aURI must not be null");
2124 for (auto* cp : dom::ContentParent::AllProcesses(dom::ContentParent::eLive)) {
2125 PNeckoParent* neckoParent = SingleManagedOrNull(cp->ManagedPNeckoParent());
2126 if (!neckoParent) {
2127 continue;
2129 if (!neckoParent->SendPredOnPredictDNS(aURI)) {
2130 return NS_ERROR_NOT_AVAILABLE;
2134 return NS_OK;
2137 // Predictor::PrefetchListener
2138 // nsISupports
2139 NS_IMPL_ISUPPORTS(Predictor::PrefetchListener, nsIStreamListener,
2140 nsIRequestObserver)
2142 // nsIRequestObserver
2143 NS_IMETHODIMP
2144 Predictor::PrefetchListener::OnStartRequest(nsIRequest* aRequest) {
2145 mStartTime = TimeStamp::Now();
2146 return NS_OK;
2149 NS_IMETHODIMP
2150 Predictor::PrefetchListener::OnStopRequest(nsIRequest* aRequest,
2151 nsresult aStatusCode) {
2152 PREDICTOR_LOG(("OnStopRequest this=%p aStatusCode=0x%" PRIX32, this,
2153 static_cast<uint32_t>(aStatusCode)));
2154 NS_ENSURE_ARG(aRequest);
2155 if (NS_FAILED(aStatusCode)) {
2156 return aStatusCode;
2158 Telemetry::AccumulateTimeDelta(Telemetry::PREDICTOR_PREFETCH_TIME,
2159 mStartTime);
2161 nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aRequest);
2162 if (!httpChannel) {
2163 PREDICTOR_LOG((" Could not get HTTP Channel!"));
2164 return NS_ERROR_UNEXPECTED;
2166 nsCOMPtr<nsICachingChannel> cachingChannel = do_QueryInterface(httpChannel);
2167 if (!cachingChannel) {
2168 PREDICTOR_LOG((" Could not get caching channel!"));
2169 return NS_ERROR_UNEXPECTED;
2172 nsresult rv = NS_OK;
2173 uint32_t httpStatus;
2174 rv = httpChannel->GetResponseStatus(&httpStatus);
2175 if (NS_SUCCEEDED(rv) && httpStatus == 200) {
2176 rv = cachingChannel->ForceCacheEntryValidFor(
2177 StaticPrefs::network_predictor_prefetch_force_valid_for());
2178 PREDICTOR_LOG((" forcing entry valid for %d seconds rv=%" PRIX32,
2179 StaticPrefs::network_predictor_prefetch_force_valid_for(),
2180 static_cast<uint32_t>(rv)));
2181 } else {
2182 rv = cachingChannel->ForceCacheEntryValidFor(0);
2183 Telemetry::AccumulateCategorical(
2184 Telemetry::LABELS_PREDICTOR_PREFETCH_USE_STATUS::Not200);
2185 PREDICTOR_LOG((" removing any forced validity rv=%" PRIX32,
2186 static_cast<uint32_t>(rv)));
2189 nsAutoCString reqName;
2190 rv = aRequest->GetName(reqName);
2191 if (NS_FAILED(rv)) {
2192 reqName.AssignLiteral("<unknown>");
2195 PREDICTOR_LOG((" request %s status %u", reqName.get(), httpStatus));
2197 if (mVerifier) {
2198 mVerifier->OnPredictPrefetch(mURI, httpStatus);
2201 return rv;
2204 // nsIStreamListener
2205 NS_IMETHODIMP
2206 Predictor::PrefetchListener::OnDataAvailable(nsIRequest* aRequest,
2207 nsIInputStream* aInputStream,
2208 uint64_t aOffset,
2209 const uint32_t aCount) {
2210 uint32_t result;
2211 return aInputStream->ReadSegments(NS_DiscardSegment, nullptr, aCount,
2212 &result);
2215 // Miscellaneous Predictor
2217 void Predictor::UpdateCacheability(nsIURI* sourceURI, nsIURI* targetURI,
2218 uint32_t httpStatus,
2219 nsHttpRequestHead& requestHead,
2220 nsHttpResponseHead* responseHead,
2221 nsILoadContextInfo* lci, bool isTracking) {
2222 MOZ_ASSERT(NS_IsMainThread());
2224 if (lci && lci->IsPrivate()) {
2225 PREDICTOR_LOG(("Predictor::UpdateCacheability in PB mode - ignoring"));
2226 return;
2229 if (!sourceURI || !targetURI) {
2230 PREDICTOR_LOG(
2231 ("Predictor::UpdateCacheability missing source or target uri"));
2232 return;
2235 if (!IsNullOrHttp(sourceURI) || !IsNullOrHttp(targetURI)) {
2236 PREDICTOR_LOG(("Predictor::UpdateCacheability non-http(s) uri"));
2237 return;
2240 RefPtr<Predictor> self = sSelf;
2241 if (self) {
2242 nsAutoCString method;
2243 requestHead.Method(method);
2245 nsAutoCString vary;
2246 Unused << responseHead->GetHeader(nsHttp::Vary, vary);
2248 nsAutoCString cacheControlHeader;
2249 Unused << responseHead->GetHeader(nsHttp::Cache_Control,
2250 cacheControlHeader);
2251 CacheControlParser cacheControl(cacheControlHeader);
2253 self->UpdateCacheabilityInternal(sourceURI, targetURI, httpStatus, method,
2254 *lci->OriginAttributesPtr(), isTracking,
2255 !vary.IsEmpty(), cacheControl.NoStore());
2259 void Predictor::UpdateCacheabilityInternal(
2260 nsIURI* sourceURI, nsIURI* targetURI, uint32_t httpStatus,
2261 const nsCString& method, const OriginAttributes& originAttributes,
2262 bool isTracking, bool couldVary, bool isNoStore) {
2263 PREDICTOR_LOG(("Predictor::UpdateCacheability httpStatus=%u", httpStatus));
2265 nsresult rv;
2267 if (!mInitialized) {
2268 PREDICTOR_LOG((" not initialized"));
2269 return;
2272 if (!StaticPrefs::network_predictor_enabled()) {
2273 PREDICTOR_LOG((" not enabled"));
2274 return;
2277 nsCOMPtr<nsICacheStorage> cacheDiskStorage;
2279 RefPtr<LoadContextInfo> lci = new LoadContextInfo(false, originAttributes);
2281 rv = mCacheStorageService->DiskCacheStorage(lci,
2282 getter_AddRefs(cacheDiskStorage));
2283 if (NS_FAILED(rv)) {
2284 PREDICTOR_LOG((" cannot get disk cache storage"));
2285 return;
2288 uint32_t openFlags = nsICacheStorage::OPEN_READONLY |
2289 nsICacheStorage::OPEN_SECRETLY |
2290 nsICacheStorage::CHECK_MULTITHREADED;
2291 RefPtr<Predictor::CacheabilityAction> action =
2292 new Predictor::CacheabilityAction(targetURI, httpStatus, method,
2293 isTracking, couldVary, isNoStore, this);
2294 nsAutoCString uri;
2295 targetURI->GetAsciiSpec(uri);
2296 PREDICTOR_LOG((" uri=%s action=%p", uri.get(), action.get()));
2297 cacheDiskStorage->AsyncOpenURI(sourceURI, ""_ns, openFlags, action);
2300 NS_IMPL_ISUPPORTS(Predictor::CacheabilityAction, nsICacheEntryOpenCallback,
2301 nsICacheEntryMetaDataVisitor);
2303 NS_IMETHODIMP
2304 Predictor::CacheabilityAction::OnCacheEntryCheck(nsICacheEntry* entry,
2305 uint32_t* result) {
2306 *result = nsICacheEntryOpenCallback::ENTRY_WANTED;
2307 return NS_OK;
2310 namespace {
2311 enum PrefetchDecisionReason {
2312 PREFETCHABLE,
2313 STATUS_NOT_200,
2314 METHOD_NOT_GET,
2315 URL_HAS_QUERY_STRING,
2316 RESOURCE_IS_TRACKING,
2317 RESOURCE_COULD_VARY,
2318 RESOURCE_IS_NO_STORE
2322 NS_IMETHODIMP
2323 Predictor::CacheabilityAction::OnCacheEntryAvailable(nsICacheEntry* entry,
2324 bool isNew,
2325 nsresult result) {
2326 MOZ_ASSERT(NS_IsMainThread());
2327 // This is being opened read-only, so isNew should always be false
2328 MOZ_ASSERT(!isNew);
2330 PREDICTOR_LOG(("CacheabilityAction::OnCacheEntryAvailable this=%p", this));
2331 if (NS_FAILED(result)) {
2332 // Nothing to do
2333 PREDICTOR_LOG((" nothing to do result=%" PRIX32 " isNew=%d",
2334 static_cast<uint32_t>(result), isNew));
2335 return NS_OK;
2338 nsCString strTargetURI;
2339 nsresult rv = mTargetURI->GetAsciiSpec(strTargetURI);
2340 if (NS_FAILED(rv)) {
2341 PREDICTOR_LOG(
2342 (" GetAsciiSpec returned %" PRIx32, static_cast<uint32_t>(rv)));
2343 return NS_OK;
2346 rv = entry->VisitMetaData(this);
2347 if (NS_FAILED(rv)) {
2348 PREDICTOR_LOG(
2349 (" VisitMetaData returned %" PRIx32, static_cast<uint32_t>(rv)));
2350 return NS_OK;
2353 nsTArray<nsCString> keysToCheck = std::move(mKeysToCheck),
2354 valuesToCheck = std::move(mValuesToCheck);
2356 bool hasQueryString = false;
2357 nsAutoCString query;
2358 if (NS_SUCCEEDED(mTargetURI->GetQuery(query)) && !query.IsEmpty()) {
2359 hasQueryString = true;
2362 MOZ_ASSERT(keysToCheck.Length() == valuesToCheck.Length());
2363 for (size_t i = 0; i < keysToCheck.Length(); ++i) {
2364 const char* key = keysToCheck[i].BeginReading();
2365 const char* value = valuesToCheck[i].BeginReading();
2366 nsCString uri;
2367 uint32_t hitCount, lastHit, flags;
2369 if (!mPredictor->ParseMetaDataEntry(key, value, uri, hitCount, lastHit,
2370 flags)) {
2371 PREDICTOR_LOG((" failed to parse key=%s value=%s", key, value));
2372 continue;
2375 if (strTargetURI.Equals(uri)) {
2376 bool prefetchable = true;
2377 PrefetchDecisionReason reason = PREFETCHABLE;
2379 if (mHttpStatus != 200) {
2380 prefetchable = false;
2381 reason = STATUS_NOT_200;
2382 } else if (!mMethod.EqualsLiteral("GET")) {
2383 prefetchable = false;
2384 reason = METHOD_NOT_GET;
2385 } else if (hasQueryString) {
2386 prefetchable = false;
2387 reason = URL_HAS_QUERY_STRING;
2388 } else if (mIsTracking) {
2389 prefetchable = false;
2390 reason = RESOURCE_IS_TRACKING;
2391 } else if (mCouldVary) {
2392 prefetchable = false;
2393 reason = RESOURCE_COULD_VARY;
2394 } else if (mIsNoStore) {
2395 // We don't set prefetchable = false yet, because we just want to know
2396 // what kind of effect this would have on prefetching.
2397 reason = RESOURCE_IS_NO_STORE;
2400 Telemetry::Accumulate(Telemetry::PREDICTOR_PREFETCH_DECISION_REASON,
2401 reason);
2403 if (prefetchable) {
2404 PREDICTOR_LOG((" marking %s cacheable", key));
2405 flags |= FLAG_PREFETCHABLE;
2406 } else {
2407 PREDICTOR_LOG((" marking %s uncacheable", key));
2408 flags &= ~FLAG_PREFETCHABLE;
2410 nsCString newValue;
2411 MakeMetadataEntry(hitCount, lastHit, flags, newValue);
2412 entry->SetMetaDataElement(key, newValue.BeginReading());
2413 break;
2417 return NS_OK;
2420 NS_IMETHODIMP
2421 Predictor::CacheabilityAction::OnMetaDataElement(const char* asciiKey,
2422 const char* asciiValue) {
2423 MOZ_ASSERT(NS_IsMainThread());
2425 if (!IsURIMetadataElement(asciiKey)) {
2426 return NS_OK;
2429 nsCString key, value;
2430 key.AssignASCII(asciiKey);
2431 value.AssignASCII(asciiValue);
2432 mKeysToCheck.AppendElement(key);
2433 mValuesToCheck.AppendElement(value);
2435 return NS_OK;
2438 } // namespace net
2439 } // namespace mozilla