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/. */
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"
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"
29 #include "nsNetUtil.h"
30 #include "nsServiceManagerUtils.h"
31 #include "nsStreamUtils.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
;
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
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
) {
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
) {
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
);
122 Predictor::DNSListener::OnLookupComplete(nsICancelable
* request
,
123 nsIDNSRecord
* rec
, nsresult status
) {
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
)
139 mTargetURI(targetURI
),
140 mSourceURI(sourceURI
),
143 mPredictor(predictor
) {
144 mStartTime
= TimeStamp::Now();
146 mPredictReason
= reason
.mPredict
;
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
)
158 mTargetURI(targetURI
),
159 mSourceURI(sourceURI
),
161 mStackCount(stackCount
),
162 mPredictor(predictor
) {
163 mStartTime
= TimeStamp::Now();
165 mPredictReason
= reason
.mPredict
;
167 mLearnReason
= reason
.mLearn
;
172 Predictor::Action::OnCacheEntryCheck(nsICacheEntry
* entry
, uint32_t* result
) {
173 *result
= nsICacheEntryOpenCallback::ENTRY_WANTED
;
178 Predictor::Action::OnCacheEntryAvailable(nsICacheEntry
* entry
, bool isNew
,
180 MOZ_ASSERT(NS_IsMainThread(), "Got cache entry off main thread!");
182 nsAutoCString targetURI
, sourceURI
;
183 mTargetURI
->GetAsciiSpec(targetURI
);
185 mSourceURI
->GetAsciiSpec(sourceURI
);
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
)) {
196 ("OnCacheEntryAvailable %p FAILED to get cache entry (0x%08" PRIX32
198 this, static_cast<uint32_t>(result
)));
201 Telemetry::AccumulateTimeDelta(Telemetry::PREDICTOR_WAIT_TIME
, mStartTime
);
204 mPredictor
->PredictInternal(mPredictReason
, entry
, isNew
, mFullUri
,
205 mTargetURI
, mVerifier
, mStackCount
);
206 Telemetry::AccumulateTimeDelta(Telemetry::PREDICTOR_PREDICT_WORK_TIME
,
209 Telemetry::AccumulateTimeDelta(
210 Telemetry::PREDICTOR_PREDICT_TIME_TO_ACTION
, mStartTime
);
212 Telemetry::AccumulateTimeDelta(
213 Telemetry::PREDICTOR_PREDICT_TIME_TO_INACTION
, mStartTime
);
216 mPredictor
->LearnInternal(mLearnReason
, entry
, isNew
, mFullUri
, mTargetURI
,
218 Telemetry::AccumulateTimeDelta(Telemetry::PREDICTOR_LEARN_WORK_TIME
,
225 NS_IMPL_ISUPPORTS(Predictor
, nsINetworkPredictor
, nsIObserver
,
226 nsISpeculativeConnectionOverrider
, nsIInterfaceRequestor
,
227 nsICacheEntryMetaDataVisitor
, nsINetworkPredictorVerifier
)
229 Predictor::Predictor()
232 MOZ_ASSERT(!sSelf
, "multiple Predictor instances!");
236 Predictor::~Predictor() {
237 if (mInitialized
) Shutdown();
242 // Predictor::nsIObserver
244 nsresult
Predictor::InstallObserver() {
245 MOZ_ASSERT(NS_IsMainThread(), "Installing observer off main thread");
248 nsCOMPtr
<nsIObserverService
> obs
= mozilla::services::GetObserverService();
250 return NS_ERROR_NOT_AVAILABLE
;
253 rv
= obs
->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID
, false);
254 NS_ENSURE_SUCCESS(rv
, rv
);
259 void Predictor::RemoveObserver() {
260 MOZ_ASSERT(NS_IsMainThread(), "Removing observer off main thread");
262 nsCOMPtr
<nsIObserverService
> obs
= mozilla::services::GetObserverService();
264 obs
->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID
);
269 Predictor::Observe(nsISupports
* subject
, const char* topic
,
270 const char16_t
* data_unicode
) {
272 MOZ_ASSERT(NS_IsMainThread(),
273 "Predictor observing something off main thread!");
275 if (!strcmp(NS_XPCOM_SHUTDOWN_OBSERVER_ID
, topic
)) {
282 // Predictor::nsISpeculativeConnectionOverrider
285 Predictor::GetIgnoreIdle(bool* ignoreIdle
) {
291 Predictor::GetParallelSpeculativeConnectLimit(
292 uint32_t* parallelSpeculativeConnectLimit
) {
293 *parallelSpeculativeConnectLimit
= 6;
298 Predictor::GetIsFromPredictor(bool* isFromPredictor
) {
299 *isFromPredictor
= true;
304 Predictor::GetAllow1918(bool* allow1918
) {
309 // Predictor::nsIInterfaceRequestor
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
338 nsCString key
, value
;
339 key
.AssignASCII(asciiKey
);
340 value
.AssignASCII(asciiValue
);
341 mKeysToOperateOn
.AppendElement(key
);
342 mValuesToOperateOn
.AppendElement(value
);
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
;
359 rv
= InstallObserver();
360 NS_ENSURE_SUCCESS(rv
, rv
);
362 mLastStartupTime
= mStartupTime
= NOW_IN_SECONDS();
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
);
387 class PredictorLearnRunnable final
: public Runnable
{
389 PredictorLearnRunnable(nsIURI
* targetURI
, nsIURI
* sourceURI
,
390 PredictorLearnReason reason
,
391 const OriginAttributes
& oa
)
392 : Runnable("PredictorLearnRunnable"),
393 mTargetURI(targetURI
),
394 mSourceURI(sourceURI
),
397 MOZ_DIAGNOSTIC_ASSERT(targetURI
, "Must have a target URI");
400 ~PredictorLearnRunnable() = default;
402 NS_IMETHOD
Run() override
{
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
407 PREDICTOR_LOG(("predictor::learn (async) gNeckoChild went away"));
411 PREDICTOR_LOG(("predictor::learn (async) forwarding to parent"));
412 gNeckoChild
->SendPredLearn(mTargetURI
, mSourceURI
, mReason
, mOA
);
418 nsCOMPtr
<nsIURI
> mTargetURI
;
419 nsCOMPtr
<nsIURI
> mSourceURI
;
420 PredictorLearnReason mReason
;
421 const OriginAttributes mOA
;
426 void Predictor::Shutdown() {
427 if (!NS_IsMainThread()) {
428 MOZ_ASSERT(false, "Predictor::Shutdown called off the main thread!");
434 mInitialized
= false;
437 nsresult
Predictor::Create(const nsIID
& aIID
, void** aResult
) {
438 MOZ_ASSERT(NS_IsMainThread());
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
);
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
);
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
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
499 PREDICTOR_LOG((" was given a verifier"));
500 mChildVerifier
= verifier
;
502 PREDICTOR_LOG((" forwarding to parent process"));
503 gNeckoChild
->SendPredPredict(targetURI
, sourceURI
, reason
, originAttributes
,
508 PREDICTOR_LOG((" called on parent process"));
511 PREDICTOR_LOG((" not initialized"));
515 if (!StaticPrefs::network_predictor_enabled()) {
516 PREDICTOR_LOG((" not enabled"));
520 if (originAttributes
.mPrivateBrowsingId
> 0) {
521 // Don't want to do anything in PB mode
522 PREDICTOR_LOG((" in PB mode"));
526 if (!IsNullOrHttp(targetURI
) || !IsNullOrHttp(sourceURI
)) {
527 // Nothing we can do for non-HTTP[S] schemes
528 PREDICTOR_LOG((" got non-http[s] URI"));
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
;
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
);
546 case nsINetworkPredictor::PREDICT_LOAD
:
547 if (!targetURI
|| sourceURI
) {
548 PREDICTOR_LOG((" load invalid URI state"));
549 return NS_ERROR_INVALID_ARG
;
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
;
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
);
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
);
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"));
616 bool Predictor::PredictInternal(PredictorPredictReason reason
,
617 nsICacheEntry
* entry
, bool isNew
, bool fullUri
,
619 nsINetworkPredictorVerifier
* verifier
,
620 uint8_t stackCount
) {
621 MOZ_ASSERT(NS_IsMainThread());
623 PREDICTOR_LOG(("Predictor::PredictInternal"));
626 nsCOMPtr
<nsILoadContextInfo
> lci
;
627 entry
->GetLoadContextInfo(getter_AddRefs(lci
));
633 if (reason
== nsINetworkPredictor::PREDICT_LOAD
) {
634 MaybeLearnForStartup(targetURI
, fullUri
, *lci
->OriginAttributesPtr());
638 // nothing else we can do here
639 PREDICTOR_LOG((" new entry"));
644 case nsINetworkPredictor::PREDICT_LOAD
:
645 rv
= PredictForPageload(entry
, targetURI
, stackCount
, fullUri
, verifier
);
647 case nsINetworkPredictor::PREDICT_STARTUP
:
648 rv
= PredictForStartup(entry
, fullUri
, verifier
);
651 PREDICTOR_LOG((" invalid reason"));
652 MOZ_ASSERT(false, "Got unexpected value for prediction reason");
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"));
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"));
677 nsCOMPtr
<nsIPrincipal
> principal
=
678 BasePrincipal::CreateContentPrincipal(targetURI
, originAttributes
);
680 mSpeculativeService
->SpeculativeConnect(targetURI
, principal
, nullptr, false);
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!"));
702 nsresult rv
= entry
->GetLastFetched(&lastLoad
);
703 NS_ENSURE_SUCCESS(rv
, false);
705 int32_t globalDegradation
= CalculateGlobalDegradation(lastLoad
);
706 PREDICTOR_LOG((" globalDegradation = %d", globalDegradation
));
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()));
738 nsICacheStorage::OPEN_READONLY
| nsICacheStorage::OPEN_SECRETLY
|
739 nsICacheStorage::OPEN_PRIORITY
| nsICacheStorage::CHECK_MULTITHREADED
;
740 cacheDiskStorage
->AsyncOpenURI(redirectURI
, ""_ns
, openFlags
,
742 return RunPredictions(nullptr, *lci
->OriginAttributesPtr(), verifier
);
745 CalculatePredictions(entry
, targetURI
, lastLoad
, loadCount
, globalDegradation
,
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();
789 globalDegradation
= StaticPrefs::network_predictor_page_degradation_max();
792 Telemetry::Accumulate(Telemetry::PREDICTOR_GLOBAL_DEGRADATION
,
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
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
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
;
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).
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
;
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();
851 confidenceDegradation
=
852 StaticPrefs::network_predictor_subresource_degradation_max();
857 // Calculate our confidence and clamp it to between 0 and maxConfidence
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
);
871 static void MakeMetadataEntry(const uint32_t hitCount
, const uint32_t lastHit
,
872 const uint32_t flags
, nsCString
& newValue
) {
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
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();
912 if (n
> kMaxPrefetchRollingLoadCount
) {
913 return kMaxPrefetchRollingLoadCount
;
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();
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);
942 int32_t confidence
= CalculateConfidence(hitCount
, loadCount
, lastHit
,
943 lastLoad
, globalDegradation
);
945 UpdateRollingLoadCount(entry
, flags
, key
, hitCount
, lastHit
);
947 PREDICTOR_LOG(("CalculatePredictions key=%s value=%s confidence=%d", key
,
949 PrefetchIgnoreReason reason
= PREFETCH_OK
;
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
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,
962 PREDICTOR_LOG((" forcing non-cacheability - no referrer"));
963 if (flags
& FLAG_PREFETCHABLE
) {
964 // This only applies if we had somehow otherwise marked this
966 reason
= NO_REFERRER
;
968 flags
&= ~FLAG_PREFETCHABLE
;
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
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());
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()) {
1011 reason
= PREFETCH_DISABLED
;
1012 } else if (prefetchOk
&& !ClampedPrefetchRollingLoadCount() &&
1014 StaticPrefs::network_predictor_prefetch_min_confidence()) {
1016 if (!ClampedPrefetchRollingLoadCount()) {
1017 reason
= PREFETCH_DISABLED_VIA_COUNT
;
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
);
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
)) {
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
)) {
1080 (" NS_NewChannel failed rv=0x%" PRIX32
, static_cast<uint32_t>(rv
)));
1084 nsCOMPtr
<nsILoadInfo
> loadInfo
= channel
->LoadInfo();
1085 rv
= loadInfo
->SetOriginAttributes(originAttributes
);
1087 if (NS_FAILED(rv
)) {
1089 (" Set originAttributes into loadInfo failed rv=0x%" PRIX32
,
1090 static_cast<uint32_t>(rv
)));
1094 nsCOMPtr
<nsIHttpChannel
> httpChannel
;
1095 httpChannel
= do_QueryInterface(channel
);
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(),
1110 rv
= channel
->AsyncOpen(listener
);
1111 if (NS_FAILED(rv
)) {
1113 (" AsyncOpen failed rv=0x%" PRIX32
, static_cast<uint32_t>(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;
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
>
1136 Telemetry::AutoCounter
<Telemetry::PREDICTOR_TOTAL_PREFETCHES
> totalPrefetches
;
1137 Telemetry::AutoCounter
<Telemetry::PREDICTOR_TOTAL_PRECONNECTS
>
1139 Telemetry::AutoCounter
<Telemetry::PREDICTOR_TOTAL_PRERESOLVES
>
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
))) {
1153 len
= preconnects
.Length();
1154 for (i
= 0; i
< len
; ++i
) {
1155 PREDICTOR_LOG((" doing preconnect"));
1156 nsCOMPtr
<nsIURI
> uri
= preconnects
[i
];
1159 nsCOMPtr
<nsIPrincipal
> principal
=
1160 BasePrincipal::CreateContentPrincipal(uri
, originAttributes
);
1161 mSpeculativeService
->SpeculativeConnect(uri
, principal
, this, false);
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
];
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
));
1198 PREDICTOR_LOG((" sending preresolve verification"));
1199 verifier
->OnPredictDNS(uri
);
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());
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
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());
1251 PREDICTOR_LOG((" called on parent process"));
1253 if (!mInitialized
) {
1254 PREDICTOR_LOG((" not initialized"));
1258 if (!StaticPrefs::network_predictor_enabled()) {
1259 PREDICTOR_LOG((" not enabled"));
1263 if (originAttributes
.mPrivateBrowsingId
> 0) {
1264 // Don't want to do anything in PB mode
1265 PREDICTOR_LOG((" in PB mode"));
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
;
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
);
1289 originKey
= targetOrigin
;
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
;
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
);
1312 originKey
= sourceOrigin
;
1315 PREDICTOR_LOG((" invalid reason"));
1316 return NS_ERROR_INVALID_ARG
;
1319 Telemetry::AutoCounter
<Telemetry::PREDICTOR_LEARN_ATTEMPTS
> 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
);
1334 sourceURI
->GetAsciiSpec(sourceUriStr
);
1337 (" Learn uriKey=%s targetURI=%s sourceURI=%s reason=%d "
1339 uriKeyStr
.get(), targetUriStr
.get(), sourceUriStr
.get(), reason
,
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
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
);
1371 sourceOrigin
->GetAsciiSpec(sourceOriginStr
);
1374 (" Learn originKey=%s targetOrigin=%s sourceOrigin=%s reason=%d "
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
1384 nsICacheStorage::OPEN_NORMALLY
| nsICacheStorage::CHECK_MULTITHREADED
;
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"));
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"));
1406 if (!fullUri
&& reason
== nsINetworkPredictor::LEARN_LOAD_TOPLEVEL
&&
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
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"));
1418 // Need to ensure someone else can get to the entry if necessary
1419 entry
->MetaDataReady();
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()) {
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
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();
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);
1455 UpdateRollingLoadCount(entry
, flags
, key
, hitCount
, lastHit
);
1458 PREDICTOR_LOG((" nothing to do for toplevel"));
1461 case nsINetworkPredictor::LEARN_LOAD_REDIRECT
:
1463 LearnForRedirect(entry
, targetURI
);
1466 case nsINetworkPredictor::LEARN_LOAD_SUBRESOURCE
:
1467 LearnForSubresource(entry
, targetURI
);
1469 case nsINetworkPredictor::LEARN_STARTUP
:
1470 LearnForStartup(entry
, targetURI
);
1473 PREDICTOR_LOG((" unexpected reason value"));
1474 MOZ_ASSERT(false, "Got unexpected value for learn reason!");
1478 NS_IMPL_ISUPPORTS(Predictor::SpaceCleaner
, nsICacheEntryMetaDataVisitor
)
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
1490 uint32_t hitCount
, lastHit
, flags
;
1492 mPredictor
->ParseMetaDataEntry(key
, value
, uri
, hitCount
, lastHit
, flags
);
1495 // Couldn't parse this one, just get rid of it
1497 nsKey
.AssignASCII(key
);
1498 mLongKeysToDelete
.AppendElement(nsKey
);
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.
1507 nsKey
.AssignASCII(key
);
1508 mLongKeysToDelete
.AppendElement(nsKey
);
1512 if (!mLRUKeyToDelete
|| lastHit
< mLRUStamp
) {
1513 mLRUKeyToDelete
= key
;
1514 mLRUStamp
= lastHit
;
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"));
1540 nsresult rv
= entry
->GetLastFetched(&lastLoad
);
1541 NS_ENSURE_SUCCESS_VOID(rv
);
1544 rv
= entry
->GetFetchCount(&loadCount
);
1545 NS_ENSURE_SUCCESS_VOID(rv
);
1548 key
.AssignLiteral(META_DATA_PREFIX
);
1550 targetURI
->GetAsciiSpec(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);
1560 rv
= entry
->GetMetaDataElement(key
.BeginReading(), getter_Copies(value
));
1562 uint32_t hitCount
, lastHit
, flags
;
1563 bool isNewResource
=
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"));
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
);
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"));
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
);
1606 MakeMetadataEntry(hitCount
, lastLoad
, flags
, newValue
);
1607 rv
= entry
->SetMetaDataElement(key
.BeginReading(), newValue
.BeginReading());
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"));
1614 if (resourceCount
== 0) {
1615 entry
->SetMetaDataElement(RESOURCE_META_DATA
, nullptr);
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());
1659 ("Predictor::ParseMetaDataEntry key=%s value=%s", key
? key
: "", value
));
1661 const char* comma
= strchr(value
, ',');
1663 PREDICTOR_LOG((" could not find first comma"));
1667 uint32_t version
= static_cast<uint32_t>(atoi(value
));
1668 PREDICTOR_LOG((" version -> %u", version
));
1670 if (version
!= METADATA_VERSION
) {
1672 (" metadata version mismatch %u != %u", version
, METADATA_VERSION
));
1677 comma
= strchr(value
, ',');
1679 PREDICTOR_LOG((" could not find second comma"));
1683 hitCount
= static_cast<uint32_t>(atoi(value
));
1684 PREDICTOR_LOG((" hitCount -> %u", hitCount
));
1687 comma
= strchr(value
, ',');
1689 PREDICTOR_LOG((" could not find third comma"));
1693 lastHit
= static_cast<uint32_t>(atoi(value
));
1694 PREDICTOR_LOG((" lastHit -> %u", lastHit
));
1697 flags
= static_cast<uint32_t>(atoi(value
));
1698 PREDICTOR_LOG((" flags -> %u", flags
));
1701 const char* uriStart
= key
+ (sizeof(META_DATA_PREFIX
) - 1);
1702 uri
.AssignASCII(uriStart
);
1703 PREDICTOR_LOG((" uri -> %s", uriStart
));
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();
1726 PREDICTOR_LOG((" called on parent process"));
1728 if (!mInitialized
) {
1729 PREDICTOR_LOG((" not initialized"));
1733 if (!StaticPrefs::network_predictor_enabled()) {
1734 PREDICTOR_LOG((" not enabled"));
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"));
1746 NS_IMPL_ISUPPORTS(Predictor::Resetter
, nsICacheEntryOpenCallback
,
1747 nsICacheEntryMetaDataVisitor
, nsICacheStorageVisitor
);
1749 Predictor::Resetter::Resetter(Predictor
* predictor
)
1750 : mEntriesToVisit(0), mPredictor(predictor
) {}
1753 Predictor::Resetter::OnCacheEntryCheck(nsICacheEntry
* entry
, uint32_t* result
) {
1754 *result
= nsICacheEntryOpenCallback::ENTRY_WANTED
;
1759 Predictor::Resetter::OnCacheEntryAvailable(nsICacheEntry
* entry
, bool isNew
,
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).
1768 if (!mEntriesToVisit
) {
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);
1783 if (!mEntriesToVisit
) {
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
1802 key
.AssignASCII(asciiKey
);
1803 mKeysToDelete
.AppendElement(key
);
1809 Predictor::Resetter::OnCacheStorageInfo(uint32_t entryCount
,
1810 uint64_t consumption
, uint64_t capacity
,
1811 nsIFile
* diskDirectory
) {
1812 MOZ_ASSERT(NS_IsMainThread());
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());
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.
1846 mURIsToVisit
.AppendElement(uri
);
1847 mInfosToVisit
.AppendElement(aInfo
);
1854 Predictor::Resetter::OnCacheEntryVisitCompleted() {
1855 MOZ_ASSERT(NS_IsMainThread());
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
) {
1873 uint32_t entriesToVisit
= urisToVisit
.Length();
1874 for (uint32_t i
= 0; i
< entriesToVisit
; ++i
) {
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
,
1888 if (NS_FAILED(rv
)) {
1890 if (!mEntriesToVisit
) {
1900 void Predictor::Resetter::Complete() {
1901 MOZ_ASSERT(NS_IsMainThread());
1903 nsCOMPtr
<nsIObserverService
> obs
= mozilla::services::GetObserverService();
1905 PREDICTOR_LOG(("COULD NOT GET OBSERVER SERVICE!"));
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());
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
);
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
)) {
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
)) {
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
)) {
1975 nsCOMPtr
<nsINetworkPredictor
> predictor
;
1976 nsresult rv
= EnsureGlobalPredictor(getter_AddRefs(predictor
));
1977 NS_ENSURE_SUCCESS(rv
, rv
);
1979 nsCOMPtr
<nsILoadContext
> loadContext
;
1980 OriginAttributes originAttributes
;
1983 nsCOMPtr
<nsIInterfaceRequestor
> callbacks
;
1984 loadGroup
->GetNotificationCallbacks(getter_AddRefs(callbacks
));
1986 loadContext
= do_GetInterface(callbacks
);
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
)) {
2005 nsCOMPtr
<nsINetworkPredictor
> predictor
;
2006 nsresult rv
= EnsureGlobalPredictor(getter_AddRefs(predictor
));
2007 NS_ENSURE_SUCCESS(rv
, rv
);
2009 OriginAttributes originAttributes
;
2012 nsCOMPtr
<nsIPrincipal
> docPrincipal
= document
->NodePrincipal();
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
);
2031 rv
= targetURI
->Equals(sourceURI
, &sameUri
);
2032 NS_ENSURE_SUCCESS(rv
, rv
);
2038 if (!IsNullOrHttp(targetURI
) || !IsNullOrHttp(sourceURI
)) {
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
,
2051 // nsINetworkPredictorVerifier
2054 * Call through to the child's verifier (only during tests)
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
);
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());
2075 if (!neckoParent
->SendPredOnPredictPrefetch(aURI
, httpStatus
)) {
2076 return NS_ERROR_NOT_AVAILABLE
;
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
);
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());
2102 if (!neckoParent
->SendPredOnPredictPreconnect(aURI
)) {
2103 return NS_ERROR_NOT_AVAILABLE
;
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
);
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());
2129 if (!neckoParent
->SendPredOnPredictDNS(aURI
)) {
2130 return NS_ERROR_NOT_AVAILABLE
;
2137 // Predictor::PrefetchListener
2139 NS_IMPL_ISUPPORTS(Predictor::PrefetchListener
, nsIStreamListener
,
2142 // nsIRequestObserver
2144 Predictor::PrefetchListener::OnStartRequest(nsIRequest
* aRequest
) {
2145 mStartTime
= TimeStamp::Now();
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
)) {
2158 Telemetry::AccumulateTimeDelta(Telemetry::PREDICTOR_PREFETCH_TIME
,
2161 nsCOMPtr
<nsIHttpChannel
> httpChannel
= do_QueryInterface(aRequest
);
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
)));
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
));
2198 mVerifier
->OnPredictPrefetch(mURI
, httpStatus
);
2204 // nsIStreamListener
2206 Predictor::PrefetchListener::OnDataAvailable(nsIRequest
* aRequest
,
2207 nsIInputStream
* aInputStream
,
2209 const uint32_t aCount
) {
2211 return aInputStream
->ReadSegments(NS_DiscardSegment
, nullptr, aCount
,
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"));
2229 if (!sourceURI
|| !targetURI
) {
2231 ("Predictor::UpdateCacheability missing source or target uri"));
2235 if (!IsNullOrHttp(sourceURI
) || !IsNullOrHttp(targetURI
)) {
2236 PREDICTOR_LOG(("Predictor::UpdateCacheability non-http(s) uri"));
2240 RefPtr
<Predictor
> self
= sSelf
;
2242 nsAutoCString method
;
2243 requestHead
.Method(method
);
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
));
2267 if (!mInitialized
) {
2268 PREDICTOR_LOG((" not initialized"));
2272 if (!StaticPrefs::network_predictor_enabled()) {
2273 PREDICTOR_LOG((" not enabled"));
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"));
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);
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
);
2304 Predictor::CacheabilityAction::OnCacheEntryCheck(nsICacheEntry
* entry
,
2306 *result
= nsICacheEntryOpenCallback::ENTRY_WANTED
;
2311 enum PrefetchDecisionReason
{
2315 URL_HAS_QUERY_STRING
,
2316 RESOURCE_IS_TRACKING
,
2317 RESOURCE_COULD_VARY
,
2318 RESOURCE_IS_NO_STORE
2323 Predictor::CacheabilityAction::OnCacheEntryAvailable(nsICacheEntry
* entry
,
2326 MOZ_ASSERT(NS_IsMainThread());
2327 // This is being opened read-only, so isNew should always be false
2330 PREDICTOR_LOG(("CacheabilityAction::OnCacheEntryAvailable this=%p", this));
2331 if (NS_FAILED(result
)) {
2333 PREDICTOR_LOG((" nothing to do result=%" PRIX32
" isNew=%d",
2334 static_cast<uint32_t>(result
), isNew
));
2338 nsCString strTargetURI
;
2339 nsresult rv
= mTargetURI
->GetAsciiSpec(strTargetURI
);
2340 if (NS_FAILED(rv
)) {
2342 (" GetAsciiSpec returned %" PRIx32
, static_cast<uint32_t>(rv
)));
2346 rv
= entry
->VisitMetaData(this);
2347 if (NS_FAILED(rv
)) {
2349 (" VisitMetaData returned %" PRIx32
, static_cast<uint32_t>(rv
)));
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();
2367 uint32_t hitCount
, lastHit
, flags
;
2369 if (!mPredictor
->ParseMetaDataEntry(key
, value
, uri
, hitCount
, lastHit
,
2371 PREDICTOR_LOG((" failed to parse key=%s value=%s", key
, value
));
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
,
2404 PREDICTOR_LOG((" marking %s cacheable", key
));
2405 flags
|= FLAG_PREFETCHABLE
;
2407 PREDICTOR_LOG((" marking %s uncacheable", key
));
2408 flags
&= ~FLAG_PREFETCHABLE
;
2411 MakeMetadataEntry(hitCount
, lastHit
, flags
, newValue
);
2412 entry
->SetMetaDataElement(key
, newValue
.BeginReading());
2421 Predictor::CacheabilityAction::OnMetaDataElement(const char* asciiKey
,
2422 const char* asciiValue
) {
2423 MOZ_ASSERT(NS_IsMainThread());
2425 if (!IsURIMetadataElement(asciiKey
)) {
2429 nsCString key
, value
;
2430 key
.AssignASCII(asciiKey
);
2431 value
.AssignASCII(asciiValue
);
2432 mKeysToCheck
.AppendElement(key
);
2433 mValuesToCheck
.AppendElement(value
);
2439 } // namespace mozilla