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/Components.h"
37 #include "mozilla/OriginAttributes.h"
38 #include "mozilla/Preferences.h"
39 #include "mozilla/SchedulerGroup.h"
40 #include "mozilla/StaticPrefs_network.h"
41 #include "mozilla/Telemetry.h"
43 #include "mozilla/net/NeckoCommon.h"
44 #include "mozilla/net/NeckoParent.h"
46 #include "LoadContextInfo.h"
47 #include "mozilla/ipc/URIUtils.h"
48 #include "SerializedLoadContext.h"
49 #include "mozilla/net/NeckoChild.h"
51 #include "mozilla/dom/ContentParent.h"
52 #include "mozilla/ClearOnShutdown.h"
54 #include "CacheControlParser.h"
55 #include "ReferrerInfo.h"
57 using namespace mozilla
;
62 Predictor
* Predictor::sSelf
= nullptr;
64 static LazyLogModule
gPredictorLog("NetworkPredictor");
66 #define PREDICTOR_LOG(args) \
67 MOZ_LOG(gPredictorLog, mozilla::LogLevel::Debug, args)
69 #define NOW_IN_SECONDS() static_cast<uint32_t>(PR_Now() / PR_USEC_PER_SEC)
71 // All these time values are in sec
72 static const uint32_t ONE_DAY
= 86400U;
73 static const uint32_t ONE_WEEK
= 7U * ONE_DAY
;
74 static const uint32_t ONE_MONTH
= 30U * ONE_DAY
;
75 static const uint32_t ONE_YEAR
= 365U * ONE_DAY
;
77 // Version of metadata entries we expect
78 static const uint32_t METADATA_VERSION
= 1;
80 // Flags available in entries
81 // FLAG_PREFETCHABLE - we have determined that this item is eligible for
83 static const uint32_t FLAG_PREFETCHABLE
= 1 << 0;
85 // We save 12 bits in the "flags" section of our metadata for actual flags, the
86 // rest are to keep track of a rolling count of which loads a resource has been
87 // used on to determine if we can prefetch that resource or not;
88 static const uint8_t kRollingLoadOffset
= 12;
89 static const int32_t kMaxPrefetchRollingLoadCount
= 20;
90 static const uint32_t kFlagsMask
= ((1 << kRollingLoadOffset
) - 1);
92 // ID Extensions for cache entries
93 #define PREDICTOR_ORIGIN_EXTENSION "predictor-origin"
95 // Get the full origin (scheme, host, port) out of a URI (maybe should be part
96 // of nsIURI instead?)
97 static nsresult
ExtractOrigin(nsIURI
* uri
, nsIURI
** originUri
) {
99 nsresult rv
= nsContentUtils::GetWebExposedOriginSerialization(uri
, s
);
100 NS_ENSURE_SUCCESS(rv
, rv
);
102 return NS_NewURI(originUri
, s
);
105 // All URIs we get passed *must* be http or https if they're not null. This
106 // helps ensure that.
107 static bool IsNullOrHttp(nsIURI
* uri
) {
112 return uri
->SchemeIs("http") || uri
->SchemeIs("https");
115 // Listener for the speculative DNS requests we'll fire off, which just ignores
116 // the result (since we're just trying to warm the cache). This also exists to
117 // reduce round-trips to the main thread, by being something threadsafe the
118 // Predictor can use.
120 NS_IMPL_ISUPPORTS(Predictor::DNSListener
, nsIDNSListener
);
123 Predictor::DNSListener::OnLookupComplete(nsICancelable
* request
,
124 nsIDNSRecord
* rec
, nsresult status
) {
128 // Class to proxy important information from the initial predictor call through
129 // the cache API and back into the internals of the predictor. We can't use the
130 // predictor itself, as it may have multiple actions in-flight, and each action
131 // has different parameters.
132 NS_IMPL_ISUPPORTS(Predictor::Action
, nsICacheEntryOpenCallback
);
134 Predictor::Action::Action(bool fullUri
, bool predict
, Predictor::Reason reason
,
135 nsIURI
* targetURI
, nsIURI
* sourceURI
,
136 nsINetworkPredictorVerifier
* verifier
,
137 Predictor
* predictor
)
140 mTargetURI(targetURI
),
141 mSourceURI(sourceURI
),
144 mPredictor(predictor
) {
145 mStartTime
= TimeStamp::Now();
147 mPredictReason
= reason
.mPredict
;
149 mLearnReason
= reason
.mLearn
;
153 Predictor::Action::Action(bool fullUri
, bool predict
, Predictor::Reason reason
,
154 nsIURI
* targetURI
, nsIURI
* sourceURI
,
155 nsINetworkPredictorVerifier
* verifier
,
156 Predictor
* predictor
, uint8_t stackCount
)
159 mTargetURI(targetURI
),
160 mSourceURI(sourceURI
),
162 mStackCount(stackCount
),
163 mPredictor(predictor
) {
164 mStartTime
= TimeStamp::Now();
166 mPredictReason
= reason
.mPredict
;
168 mLearnReason
= reason
.mLearn
;
173 Predictor::Action::OnCacheEntryCheck(nsICacheEntry
* entry
, uint32_t* result
) {
174 *result
= nsICacheEntryOpenCallback::ENTRY_WANTED
;
179 Predictor::Action::OnCacheEntryAvailable(nsICacheEntry
* entry
, bool isNew
,
181 MOZ_ASSERT(NS_IsMainThread(), "Got cache entry off main thread!");
183 nsAutoCString targetURI
, sourceURI
;
184 mTargetURI
->GetAsciiSpec(targetURI
);
186 mSourceURI
->GetAsciiSpec(sourceURI
);
189 ("OnCacheEntryAvailable %p called. entry=%p mFullUri=%d mPredict=%d "
190 "mPredictReason=%d mLearnReason=%d mTargetURI=%s "
191 "mSourceURI=%s mStackCount=%d isNew=%d result=0x%08" PRIx32
,
192 this, entry
, mFullUri
, mPredict
, mPredictReason
, mLearnReason
,
193 targetURI
.get(), sourceURI
.get(), mStackCount
, isNew
,
194 static_cast<uint32_t>(result
)));
195 if (NS_FAILED(result
)) {
197 ("OnCacheEntryAvailable %p FAILED to get cache entry (0x%08" PRIX32
199 this, static_cast<uint32_t>(result
)));
202 Telemetry::AccumulateTimeDelta(Telemetry::PREDICTOR_WAIT_TIME
, mStartTime
);
205 mPredictor
->PredictInternal(mPredictReason
, entry
, isNew
, mFullUri
,
206 mTargetURI
, mVerifier
, mStackCount
);
207 Telemetry::AccumulateTimeDelta(Telemetry::PREDICTOR_PREDICT_WORK_TIME
,
210 Telemetry::AccumulateTimeDelta(
211 Telemetry::PREDICTOR_PREDICT_TIME_TO_ACTION
, mStartTime
);
213 Telemetry::AccumulateTimeDelta(
214 Telemetry::PREDICTOR_PREDICT_TIME_TO_INACTION
, mStartTime
);
217 mPredictor
->LearnInternal(mLearnReason
, entry
, isNew
, mFullUri
, mTargetURI
,
219 Telemetry::AccumulateTimeDelta(Telemetry::PREDICTOR_LEARN_WORK_TIME
,
226 NS_IMPL_ISUPPORTS(Predictor
, nsINetworkPredictor
, nsIObserver
,
227 nsISpeculativeConnectionOverrider
, nsIInterfaceRequestor
,
228 nsICacheEntryMetaDataVisitor
, nsINetworkPredictorVerifier
)
230 Predictor::Predictor()
233 MOZ_ASSERT(!sSelf
, "multiple Predictor instances!");
237 Predictor::~Predictor() {
238 if (mInitialized
) Shutdown();
243 // Predictor::nsIObserver
245 nsresult
Predictor::InstallObserver() {
246 MOZ_ASSERT(NS_IsMainThread(), "Installing observer off main thread");
249 nsCOMPtr
<nsIObserverService
> obs
= mozilla::services::GetObserverService();
251 return NS_ERROR_NOT_AVAILABLE
;
254 rv
= obs
->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID
, false);
255 NS_ENSURE_SUCCESS(rv
, rv
);
260 void Predictor::RemoveObserver() {
261 MOZ_ASSERT(NS_IsMainThread(), "Removing observer off main thread");
263 nsCOMPtr
<nsIObserverService
> obs
= mozilla::services::GetObserverService();
265 obs
->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID
);
270 Predictor::Observe(nsISupports
* subject
, const char* topic
,
271 const char16_t
* data_unicode
) {
273 MOZ_ASSERT(NS_IsMainThread(),
274 "Predictor observing something off main thread!");
276 if (!strcmp(NS_XPCOM_SHUTDOWN_OBSERVER_ID
, topic
)) {
283 // Predictor::nsISpeculativeConnectionOverrider
286 Predictor::GetIgnoreIdle(bool* ignoreIdle
) {
292 Predictor::GetParallelSpeculativeConnectLimit(
293 uint32_t* parallelSpeculativeConnectLimit
) {
294 *parallelSpeculativeConnectLimit
= 6;
299 Predictor::GetIsFromPredictor(bool* isFromPredictor
) {
300 *isFromPredictor
= true;
305 Predictor::GetAllow1918(bool* allow1918
) {
310 // Predictor::nsIInterfaceRequestor
313 Predictor::GetInterface(const nsIID
& iid
, void** result
) {
314 return QueryInterface(iid
, result
);
317 // Predictor::nsICacheEntryMetaDataVisitor
319 #define SEEN_META_DATA "predictor::seen"
320 #define RESOURCE_META_DATA "predictor::resource-count"
321 #define META_DATA_PREFIX "predictor::"
323 static bool IsURIMetadataElement(const char* key
) {
324 return StringBeginsWith(nsDependentCString(key
),
325 nsLiteralCString(META_DATA_PREFIX
)) &&
326 !nsLiteralCString(SEEN_META_DATA
).Equals(key
) &&
327 !nsLiteralCString(RESOURCE_META_DATA
).Equals(key
);
330 nsresult
Predictor::OnMetaDataElement(const char* asciiKey
,
331 const char* asciiValue
) {
332 MOZ_ASSERT(NS_IsMainThread());
334 if (!IsURIMetadataElement(asciiKey
)) {
335 // This isn't a bit of metadata we care about
339 nsCString key
, value
;
340 key
.AssignASCII(asciiKey
);
341 value
.AssignASCII(asciiValue
);
342 mKeysToOperateOn
.AppendElement(key
);
343 mValuesToOperateOn
.AppendElement(value
);
348 // Predictor::nsINetworkPredictor
350 nsresult
Predictor::Init() {
351 MOZ_DIAGNOSTIC_ASSERT(!IsNeckoChild());
353 if (!NS_IsMainThread()) {
354 MOZ_ASSERT(false, "Predictor::Init called off the main thread!");
355 return NS_ERROR_UNEXPECTED
;
360 rv
= InstallObserver();
361 NS_ENSURE_SUCCESS(rv
, rv
);
363 mLastStartupTime
= mStartupTime
= NOW_IN_SECONDS();
366 mDNSListener
= new DNSListener();
369 mCacheStorageService
= mozilla::components::CacheStorage::Service(&rv
);
370 NS_ENSURE_SUCCESS(rv
, rv
);
372 mSpeculativeService
= mozilla::components::IO::Service(&rv
);
373 NS_ENSURE_SUCCESS(rv
, rv
);
375 rv
= NS_NewURI(getter_AddRefs(mStartupURI
), "predictor://startup");
376 NS_ENSURE_SUCCESS(rv
, rv
);
378 mDnsService
= mozilla::components::DNS::Service(&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 (!StaticPrefs::network_predictor_enabled()) {
490 PREDICTOR_LOG((" not enabled"));
494 if (IsNeckoChild()) {
496 return NS_ERROR_FAILURE
;
499 PREDICTOR_LOG((" called on child process"));
500 // If two different threads are predicting concurently, this will be
501 // overwritten. Thankfully, we only use this in tests, which will
502 // overwrite mVerifier perhaps multiple times for each individual test;
503 // however, within each test, the multiple predict calls should have the
506 PREDICTOR_LOG((" was given a verifier"));
507 mChildVerifier
= verifier
;
509 PREDICTOR_LOG((" forwarding to parent process"));
510 gNeckoChild
->SendPredPredict(targetURI
, sourceURI
, reason
, originAttributes
,
515 PREDICTOR_LOG((" called on parent process"));
518 PREDICTOR_LOG((" not initialized"));
522 if (originAttributes
.IsPrivateBrowsing()) {
523 // Don't want to do anything in PB mode
524 PREDICTOR_LOG((" in PB mode"));
528 if (!IsNullOrHttp(targetURI
) || !IsNullOrHttp(sourceURI
)) {
529 // Nothing we can do for non-HTTP[S] schemes
530 PREDICTOR_LOG((" got non-http[s] URI"));
534 // Ensure we've been given the appropriate arguments for the kind of
535 // prediction we're being asked to do
536 nsCOMPtr
<nsIURI
> uriKey
= targetURI
;
537 nsCOMPtr
<nsIURI
> originKey
;
539 case nsINetworkPredictor::PREDICT_LINK
:
540 if (!targetURI
|| !sourceURI
) {
541 PREDICTOR_LOG((" link invalid URI state"));
542 return NS_ERROR_INVALID_ARG
;
544 // Link hover is a special case where we can predict without hitting the
545 // db, so let's go ahead and fire off that prediction here.
546 PredictForLink(targetURI
, sourceURI
, originAttributes
, verifier
);
548 case nsINetworkPredictor::PREDICT_LOAD
:
549 if (!targetURI
|| sourceURI
) {
550 PREDICTOR_LOG((" load invalid URI state"));
551 return NS_ERROR_INVALID_ARG
;
554 case nsINetworkPredictor::PREDICT_STARTUP
:
555 if (targetURI
|| sourceURI
) {
556 PREDICTOR_LOG((" startup invalid URI state"));
557 return NS_ERROR_INVALID_ARG
;
559 uriKey
= mStartupURI
;
560 originKey
= mStartupURI
;
563 PREDICTOR_LOG((" invalid reason"));
564 return NS_ERROR_INVALID_ARG
;
567 Predictor::Reason argReason
{};
568 argReason
.mPredict
= reason
;
570 // First we open the regular cache entry, to ensure we don't gum up the works
571 // waiting on the less-important predictor-only cache entry
572 RefPtr
<Predictor::Action
> uriAction
= new Predictor::Action(
573 Predictor::Action::IS_FULL_URI
, Predictor::Action::DO_PREDICT
, argReason
,
574 targetURI
, nullptr, verifier
, this);
575 nsAutoCString uriKeyStr
;
576 uriKey
->GetAsciiSpec(uriKeyStr
);
577 PREDICTOR_LOG((" Predict uri=%s reason=%d action=%p", uriKeyStr
.get(),
578 reason
, uriAction
.get()));
580 nsCOMPtr
<nsICacheStorage
> cacheDiskStorage
;
582 RefPtr
<LoadContextInfo
> lci
= new LoadContextInfo(false, originAttributes
);
584 nsresult rv
= mCacheStorageService
->DiskCacheStorage(
585 lci
, getter_AddRefs(cacheDiskStorage
));
586 NS_ENSURE_SUCCESS(rv
, rv
);
589 nsICacheStorage::OPEN_READONLY
| nsICacheStorage::OPEN_SECRETLY
|
590 nsICacheStorage::OPEN_PRIORITY
| nsICacheStorage::CHECK_MULTITHREADED
;
591 cacheDiskStorage
->AsyncOpenURI(uriKey
, ""_ns
, openFlags
, uriAction
);
593 // Now we do the origin-only (and therefore predictor-only) entry
594 nsCOMPtr
<nsIURI
> targetOrigin
;
595 rv
= ExtractOrigin(uriKey
, getter_AddRefs(targetOrigin
));
596 NS_ENSURE_SUCCESS(rv
, rv
);
598 originKey
= targetOrigin
;
601 RefPtr
<Predictor::Action
> originAction
= new Predictor::Action(
602 Predictor::Action::IS_ORIGIN
, Predictor::Action::DO_PREDICT
, argReason
,
603 targetOrigin
, nullptr, verifier
, this);
604 nsAutoCString originKeyStr
;
605 originKey
->GetAsciiSpec(originKeyStr
);
606 PREDICTOR_LOG((" Predict origin=%s reason=%d action=%p",
607 originKeyStr
.get(), reason
, originAction
.get()));
608 openFlags
= nsICacheStorage::OPEN_READONLY
| nsICacheStorage::OPEN_SECRETLY
|
609 nsICacheStorage::CHECK_MULTITHREADED
;
610 cacheDiskStorage
->AsyncOpenURI(originKey
,
611 nsLiteralCString(PREDICTOR_ORIGIN_EXTENSION
),
612 openFlags
, originAction
);
614 PREDICTOR_LOG((" predict returning"));
618 bool Predictor::PredictInternal(PredictorPredictReason reason
,
619 nsICacheEntry
* entry
, bool isNew
, bool fullUri
,
621 nsINetworkPredictorVerifier
* verifier
,
622 uint8_t stackCount
) {
623 MOZ_ASSERT(NS_IsMainThread());
625 PREDICTOR_LOG(("Predictor::PredictInternal"));
628 nsCOMPtr
<nsILoadContextInfo
> lci
;
629 entry
->GetLoadContextInfo(getter_AddRefs(lci
));
635 if (reason
== nsINetworkPredictor::PREDICT_LOAD
) {
636 MaybeLearnForStartup(targetURI
, fullUri
, *lci
->OriginAttributesPtr());
640 // nothing else we can do here
641 PREDICTOR_LOG((" new entry"));
646 case nsINetworkPredictor::PREDICT_LOAD
:
647 rv
= PredictForPageload(entry
, targetURI
, stackCount
, fullUri
, verifier
);
649 case nsINetworkPredictor::PREDICT_STARTUP
:
650 rv
= PredictForStartup(entry
, fullUri
, verifier
);
653 PREDICTOR_LOG((" invalid reason"));
654 MOZ_ASSERT(false, "Got unexpected value for prediction reason");
660 void Predictor::PredictForLink(nsIURI
* targetURI
, nsIURI
* sourceURI
,
661 const OriginAttributes
& originAttributes
,
662 nsINetworkPredictorVerifier
* verifier
) {
663 MOZ_ASSERT(NS_IsMainThread());
665 PREDICTOR_LOG(("Predictor::PredictForLink"));
666 if (!mSpeculativeService
) {
667 PREDICTOR_LOG((" missing speculative service"));
671 if (!StaticPrefs::network_predictor_enable_hover_on_ssl()) {
672 if (sourceURI
->SchemeIs("https")) {
673 // We don't want to predict from an HTTPS page, to avoid info leakage
674 PREDICTOR_LOG((" Not predicting for link hover - on an SSL page"));
679 nsCOMPtr
<nsIPrincipal
> principal
=
680 BasePrincipal::CreateContentPrincipal(targetURI
, originAttributes
);
682 mSpeculativeService
->SpeculativeConnect(targetURI
, principal
, nullptr, false);
684 PREDICTOR_LOG((" sending verification"));
685 verifier
->OnPredictPreconnect(targetURI
);
689 // This is the driver for prediction based on a new pageload.
690 static const uint8_t MAX_PAGELOAD_DEPTH
= 10;
691 bool Predictor::PredictForPageload(nsICacheEntry
* entry
, nsIURI
* targetURI
,
692 uint8_t stackCount
, bool fullUri
,
693 nsINetworkPredictorVerifier
* verifier
) {
694 MOZ_ASSERT(NS_IsMainThread());
696 PREDICTOR_LOG(("Predictor::PredictForPageload"));
698 if (stackCount
> MAX_PAGELOAD_DEPTH
) {
699 PREDICTOR_LOG((" exceeded recursion depth!"));
704 nsresult rv
= entry
->GetLastFetched(&lastLoad
);
705 NS_ENSURE_SUCCESS(rv
, false);
707 int32_t globalDegradation
= CalculateGlobalDegradation(lastLoad
);
708 PREDICTOR_LOG((" globalDegradation = %d", globalDegradation
));
711 rv
= entry
->GetFetchCount(&loadCount
);
712 NS_ENSURE_SUCCESS(rv
, false);
714 nsCOMPtr
<nsILoadContextInfo
> lci
;
716 rv
= entry
->GetLoadContextInfo(getter_AddRefs(lci
));
717 NS_ENSURE_SUCCESS(rv
, false);
719 nsCOMPtr
<nsIURI
> redirectURI
;
720 if (WouldRedirect(entry
, loadCount
, lastLoad
, globalDegradation
,
721 getter_AddRefs(redirectURI
))) {
722 mPreconnects
.AppendElement(redirectURI
);
723 Predictor::Reason reason
{};
724 reason
.mPredict
= nsINetworkPredictor::PREDICT_LOAD
;
725 RefPtr
<Predictor::Action
> redirectAction
= new Predictor::Action(
726 Predictor::Action::IS_FULL_URI
, Predictor::Action::DO_PREDICT
, reason
,
727 redirectURI
, nullptr, verifier
, this, stackCount
+ 1);
728 nsAutoCString redirectUriString
;
729 redirectURI
->GetAsciiSpec(redirectUriString
);
731 nsCOMPtr
<nsICacheStorage
> cacheDiskStorage
;
733 rv
= mCacheStorageService
->DiskCacheStorage(
734 lci
, getter_AddRefs(cacheDiskStorage
));
735 NS_ENSURE_SUCCESS(rv
, false);
737 PREDICTOR_LOG((" Predict redirect uri=%s action=%p",
738 redirectUriString
.get(), redirectAction
.get()));
740 nsICacheStorage::OPEN_READONLY
| nsICacheStorage::OPEN_SECRETLY
|
741 nsICacheStorage::OPEN_PRIORITY
| nsICacheStorage::CHECK_MULTITHREADED
;
742 cacheDiskStorage
->AsyncOpenURI(redirectURI
, ""_ns
, openFlags
,
744 return RunPredictions(nullptr, *lci
->OriginAttributesPtr(), verifier
);
747 CalculatePredictions(entry
, targetURI
, lastLoad
, loadCount
, globalDegradation
,
750 return RunPredictions(targetURI
, *lci
->OriginAttributesPtr(), verifier
);
753 // This is the driver for predicting at browser startup time based on pages that
754 // have previously been loaded close to startup.
755 bool Predictor::PredictForStartup(nsICacheEntry
* entry
, bool fullUri
,
756 nsINetworkPredictorVerifier
* verifier
) {
757 MOZ_ASSERT(NS_IsMainThread());
759 PREDICTOR_LOG(("Predictor::PredictForStartup"));
761 nsCOMPtr
<nsILoadContextInfo
> lci
;
763 nsresult rv
= entry
->GetLoadContextInfo(getter_AddRefs(lci
));
764 NS_ENSURE_SUCCESS(rv
, false);
766 int32_t globalDegradation
= CalculateGlobalDegradation(mLastStartupTime
);
767 CalculatePredictions(entry
, nullptr, mLastStartupTime
, mStartupCount
,
768 globalDegradation
, fullUri
);
769 return RunPredictions(nullptr, *lci
->OriginAttributesPtr(), verifier
);
772 // This calculates how much to degrade our confidence in our data based on
773 // the last time this top-level resource was loaded. This "global degradation"
774 // applies to *all* subresources we have associated with the top-level
775 // resource. This will be in addition to any reduction in confidence we have
776 // associated with a particular subresource.
777 int32_t Predictor::CalculateGlobalDegradation(uint32_t lastLoad
) {
778 MOZ_ASSERT(NS_IsMainThread());
780 int32_t globalDegradation
;
781 uint32_t delta
= NOW_IN_SECONDS() - lastLoad
;
782 if (delta
< ONE_DAY
) {
783 globalDegradation
= StaticPrefs::network_predictor_page_degradation_day();
784 } else if (delta
< ONE_WEEK
) {
785 globalDegradation
= StaticPrefs::network_predictor_page_degradation_week();
786 } else if (delta
< ONE_MONTH
) {
787 globalDegradation
= StaticPrefs::network_predictor_page_degradation_month();
788 } else if (delta
< ONE_YEAR
) {
789 globalDegradation
= StaticPrefs::network_predictor_page_degradation_year();
791 globalDegradation
= StaticPrefs::network_predictor_page_degradation_max();
794 Telemetry::Accumulate(Telemetry::PREDICTOR_GLOBAL_DEGRADATION
,
796 return globalDegradation
;
799 // This calculates our overall confidence that a particular subresource will be
800 // loaded as part of a top-level load.
801 // @param hitCount - the number of times we have loaded this subresource as part
802 // of this top-level load
803 // @param hitsPossible - the number of times we have performed this top-level
805 // @param lastHit - the timestamp of the last time we loaded this subresource as
806 // part of this top-level load
807 // @param lastPossible - the timestamp of the last time we performed this
809 // @param globalDegradation - the degradation for this top-level load as
810 // determined by CalculateGlobalDegradation
811 int32_t Predictor::CalculateConfidence(uint32_t hitCount
, uint32_t hitsPossible
,
812 uint32_t lastHit
, uint32_t lastPossible
,
813 int32_t globalDegradation
) {
814 MOZ_ASSERT(NS_IsMainThread());
816 Telemetry::AutoCounter
<Telemetry::PREDICTOR_PREDICTIONS_CALCULATED
>
817 predictionsCalculated
;
818 ++predictionsCalculated
;
824 int32_t baseConfidence
= (hitCount
* 100) / hitsPossible
;
825 int32_t maxConfidence
= 100;
826 int32_t confidenceDegradation
= 0;
828 if (lastHit
< lastPossible
) {
829 // We didn't load this subresource the last time this top-level load was
830 // performed, so let's not bother preconnecting (at the very least).
832 StaticPrefs::network_predictor_preconnect_min_confidence() - 1;
834 // Now calculate how much we want to degrade our confidence based on how
835 // long it's been between the last time we did this top-level load and the
836 // last time this top-level load included this subresource.
837 PRTime delta
= lastPossible
- lastHit
;
839 confidenceDegradation
= 0;
840 } else if (delta
< ONE_DAY
) {
841 confidenceDegradation
=
842 StaticPrefs::network_predictor_subresource_degradation_day();
843 } else if (delta
< ONE_WEEK
) {
844 confidenceDegradation
=
845 StaticPrefs::network_predictor_subresource_degradation_week();
846 } else if (delta
< ONE_MONTH
) {
847 confidenceDegradation
=
848 StaticPrefs::network_predictor_subresource_degradation_month();
849 } else if (delta
< ONE_YEAR
) {
850 confidenceDegradation
=
851 StaticPrefs::network_predictor_subresource_degradation_year();
853 confidenceDegradation
=
854 StaticPrefs::network_predictor_subresource_degradation_max();
859 // Calculate our confidence and clamp it to between 0 and maxConfidence
862 baseConfidence
- confidenceDegradation
- globalDegradation
;
863 confidence
= std::max(confidence
, 0);
864 confidence
= std::min(confidence
, maxConfidence
);
866 Telemetry::Accumulate(Telemetry::PREDICTOR_BASE_CONFIDENCE
, baseConfidence
);
867 Telemetry::Accumulate(Telemetry::PREDICTOR_SUBRESOURCE_DEGRADATION
,
868 confidenceDegradation
);
869 Telemetry::Accumulate(Telemetry::PREDICTOR_CONFIDENCE
, confidence
);
873 static void MakeMetadataEntry(const uint32_t hitCount
, const uint32_t lastHit
,
874 const uint32_t flags
, nsCString
& newValue
) {
876 newValue
.AppendInt(METADATA_VERSION
);
877 newValue
.Append(',');
878 newValue
.AppendInt(hitCount
);
879 newValue
.Append(',');
880 newValue
.AppendInt(lastHit
);
881 newValue
.Append(',');
882 newValue
.AppendInt(flags
);
885 // On every page load, the rolling window gets shifted by one bit, leaving the
886 // lowest bit at 0, to indicate that the subresource in question has not been
887 // seen on the most recent page load. If, at some point later during the page
888 // load, the subresource is seen again, we will then set the lowest bit to 1.
889 // This is how we keep track of how many of the last n pageloads (for n <= 20) a
890 // particular subresource has been seen. The rolling window is kept in the upper
891 // 20 bits of the flags element of the metadata. This saves 12 bits for regular
893 void Predictor::UpdateRollingLoadCount(nsICacheEntry
* entry
,
894 const uint32_t flags
, const char* key
,
895 const uint32_t hitCount
,
896 const uint32_t lastHit
) {
897 // Extract just the rolling load count from the flags, shift it to clear the
898 // lowest bit, and put the new value with the existing flags.
899 uint32_t rollingLoadCount
= flags
& ~kFlagsMask
;
900 rollingLoadCount
<<= 1;
901 uint32_t newFlags
= (flags
& kFlagsMask
) | rollingLoadCount
;
903 // Finally, update the metadata on the cache entry.
904 nsAutoCString newValue
;
905 MakeMetadataEntry(hitCount
, lastHit
, newFlags
, newValue
);
906 entry
->SetMetaDataElement(key
, newValue
.BeginReading());
909 uint32_t Predictor::ClampedPrefetchRollingLoadCount() {
910 int32_t n
= StaticPrefs::network_predictor_prefetch_rolling_load_count();
914 if (n
> kMaxPrefetchRollingLoadCount
) {
915 return kMaxPrefetchRollingLoadCount
;
920 void Predictor::CalculatePredictions(nsICacheEntry
* entry
, nsIURI
* referrer
,
921 uint32_t lastLoad
, uint32_t loadCount
,
922 int32_t globalDegradation
, bool fullUri
) {
923 MOZ_ASSERT(NS_IsMainThread());
925 // Since the visitor gets called under a cache lock, all we do there is get
926 // copies of the keys/values we care about, and then do the real work here
927 entry
->VisitMetaData(this);
928 nsTArray
<nsCString
> keysToOperateOn
= std::move(mKeysToOperateOn
),
929 valuesToOperateOn
= std::move(mValuesToOperateOn
);
931 MOZ_ASSERT(keysToOperateOn
.Length() == valuesToOperateOn
.Length());
932 for (size_t i
= 0; i
< keysToOperateOn
.Length(); ++i
) {
933 const char* key
= keysToOperateOn
[i
].BeginReading();
934 const char* value
= valuesToOperateOn
[i
].BeginReading();
937 uint32_t hitCount
, lastHit
, flags
;
938 if (!ParseMetaDataEntry(key
, value
, uri
, hitCount
, lastHit
, flags
)) {
939 // This failed, get rid of it so we don't waste space
940 entry
->SetMetaDataElement(key
, nullptr);
944 int32_t confidence
= CalculateConfidence(hitCount
, loadCount
, lastHit
,
945 lastLoad
, globalDegradation
);
947 UpdateRollingLoadCount(entry
, flags
, key
, hitCount
, lastHit
);
949 PREDICTOR_LOG(("CalculatePredictions key=%s value=%s confidence=%d", key
,
951 PrefetchIgnoreReason reason
= PREFETCH_OK
;
953 // Not full URI - don't prefetch! No sense in it!
954 PREDICTOR_LOG((" forcing non-cacheability - not full URI"));
955 if (flags
& FLAG_PREFETCHABLE
) {
956 // This only applies if we had somehow otherwise marked this
958 reason
= NOT_FULL_URI
;
960 flags
&= ~FLAG_PREFETCHABLE
;
961 } else if (!referrer
) {
962 // No referrer means we can't prefetch, so pretend it's non-cacheable,
964 PREDICTOR_LOG((" forcing non-cacheability - no referrer"));
965 if (flags
& FLAG_PREFETCHABLE
) {
966 // This only applies if we had somehow otherwise marked this
968 reason
= NO_REFERRER
;
970 flags
&= ~FLAG_PREFETCHABLE
;
972 uint32_t expectedRollingLoadCount
=
973 (1 << ClampedPrefetchRollingLoadCount()) - 1;
974 expectedRollingLoadCount
<<= kRollingLoadOffset
;
975 if ((flags
& expectedRollingLoadCount
) != expectedRollingLoadCount
) {
976 PREDICTOR_LOG((" forcing non-cacheability - missed a load"));
977 if (flags
& FLAG_PREFETCHABLE
) {
978 // This only applies if we had somehow otherwise marked this
980 reason
= MISSED_A_LOAD
;
982 flags
&= ~FLAG_PREFETCHABLE
;
986 PREDICTOR_LOG((" setting up prediction"));
987 SetupPrediction(confidence
, flags
, uri
, reason
);
991 // (Maybe) adds a predictive action to the prediction runner, based on our
992 // calculated confidence for the subresource in question.
993 void Predictor::SetupPrediction(int32_t confidence
, uint32_t flags
,
994 const nsCString
& uri
,
995 PrefetchIgnoreReason earlyReason
) {
996 MOZ_ASSERT(NS_IsMainThread());
1000 ("SetupPrediction enable-prefetch=%d prefetch-min-confidence=%d "
1001 "preconnect-min-confidence=%d preresolve-min-confidence=%d "
1002 "flags=%d confidence=%d uri=%s",
1003 StaticPrefs::network_predictor_enable_prefetch(),
1004 StaticPrefs::network_predictor_prefetch_min_confidence(),
1005 StaticPrefs::network_predictor_preconnect_min_confidence(),
1006 StaticPrefs::network_predictor_preresolve_min_confidence(), flags
,
1007 confidence
, uri
.get()));
1009 bool prefetchOk
= !!(flags
& FLAG_PREFETCHABLE
);
1010 PrefetchIgnoreReason reason
= earlyReason
;
1011 if (prefetchOk
&& !StaticPrefs::network_predictor_enable_prefetch()) {
1013 reason
= PREFETCH_DISABLED
;
1014 } else if (prefetchOk
&& !ClampedPrefetchRollingLoadCount() &&
1016 StaticPrefs::network_predictor_prefetch_min_confidence()) {
1018 if (!ClampedPrefetchRollingLoadCount()) {
1019 reason
= PREFETCH_DISABLED_VIA_COUNT
;
1021 reason
= CONFIDENCE_TOO_LOW
;
1025 // prefetchOk == false and reason == PREFETCH_OK indicates that the reason
1026 // we aren't prefetching this item is because it was marked un-prefetchable in
1027 // our metadata. We already have separate telemetry on that decision, so we
1028 // aren't going to accumulate more here. Right now we only care about why
1029 // something we had marked prefetchable isn't being prefetched.
1030 if (!prefetchOk
&& reason
!= PREFETCH_OK
) {
1031 Telemetry::Accumulate(Telemetry::PREDICTOR_PREFETCH_IGNORE_REASON
, reason
);
1035 nsCOMPtr
<nsIURI
> prefetchURI
;
1036 rv
= NS_NewURI(getter_AddRefs(prefetchURI
), uri
);
1037 if (NS_SUCCEEDED(rv
)) {
1038 mPrefetches
.AppendElement(prefetchURI
);
1040 } else if (confidence
>=
1041 StaticPrefs::network_predictor_preconnect_min_confidence()) {
1042 nsCOMPtr
<nsIURI
> preconnectURI
;
1043 rv
= NS_NewURI(getter_AddRefs(preconnectURI
), uri
);
1044 if (NS_SUCCEEDED(rv
)) {
1045 mPreconnects
.AppendElement(preconnectURI
);
1047 } else if (confidence
>=
1048 StaticPrefs::network_predictor_preresolve_min_confidence()) {
1049 nsCOMPtr
<nsIURI
> preresolveURI
;
1050 rv
= NS_NewURI(getter_AddRefs(preresolveURI
), uri
);
1051 if (NS_SUCCEEDED(rv
)) {
1052 mPreresolves
.AppendElement(preresolveURI
);
1056 if (NS_FAILED(rv
)) {
1058 (" NS_NewURI returned 0x%" PRIx32
, static_cast<uint32_t>(rv
)));
1062 nsresult
Predictor::Prefetch(nsIURI
* uri
, nsIURI
* referrer
,
1063 const OriginAttributes
& originAttributes
,
1064 nsINetworkPredictorVerifier
* verifier
) {
1065 nsAutoCString strUri
, strReferrer
;
1066 uri
->GetAsciiSpec(strUri
);
1067 referrer
->GetAsciiSpec(strReferrer
);
1068 PREDICTOR_LOG(("Predictor::Prefetch uri=%s referrer=%s verifier=%p",
1069 strUri
.get(), strReferrer
.get(), verifier
));
1070 nsCOMPtr
<nsIChannel
> channel
;
1071 nsresult rv
= NS_NewChannel(
1072 getter_AddRefs(channel
), uri
, nsContentUtils::GetSystemPrincipal(),
1073 nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL
,
1074 nsIContentPolicy::TYPE_OTHER
, nullptr, /* nsICookieJarSettings */
1075 nullptr, /* aPerformanceStorage */
1076 nullptr, /* aLoadGroup */
1077 nullptr, /* aCallbacks */
1078 nsIRequest::LOAD_BACKGROUND
);
1080 if (NS_FAILED(rv
)) {
1082 (" NS_NewChannel failed rv=0x%" PRIX32
, static_cast<uint32_t>(rv
)));
1086 nsCOMPtr
<nsILoadInfo
> loadInfo
= channel
->LoadInfo();
1087 rv
= loadInfo
->SetOriginAttributes(originAttributes
);
1089 if (NS_FAILED(rv
)) {
1091 (" Set originAttributes into loadInfo failed rv=0x%" PRIX32
,
1092 static_cast<uint32_t>(rv
)));
1096 nsCOMPtr
<nsIHttpChannel
> httpChannel
;
1097 httpChannel
= do_QueryInterface(channel
);
1099 PREDICTOR_LOG((" Could not get HTTP Channel from new channel!"));
1100 return NS_ERROR_UNEXPECTED
;
1103 nsCOMPtr
<nsIReferrerInfo
> referrerInfo
= new dom::ReferrerInfo(referrer
);
1104 rv
= httpChannel
->SetReferrerInfoWithoutClone(referrerInfo
);
1105 NS_ENSURE_SUCCESS(rv
, rv
);
1106 // XXX - set a header here to indicate this is a prefetch?
1108 nsCOMPtr
<nsIStreamListener
> listener
=
1109 new PrefetchListener(verifier
, uri
, this);
1110 PREDICTOR_LOG((" calling AsyncOpen listener=%p channel=%p", listener
.get(),
1112 rv
= channel
->AsyncOpen(listener
);
1113 if (NS_FAILED(rv
)) {
1115 (" AsyncOpen failed rv=0x%" PRIX32
, static_cast<uint32_t>(rv
)));
1121 // Runs predictions that have been set up.
1122 bool Predictor::RunPredictions(nsIURI
* referrer
,
1123 const OriginAttributes
& originAttributes
,
1124 nsINetworkPredictorVerifier
* verifier
) {
1125 MOZ_ASSERT(NS_IsMainThread(), "Running prediction off main thread");
1127 PREDICTOR_LOG(("Predictor::RunPredictions"));
1129 bool predicted
= false;
1132 nsTArray
<nsCOMPtr
<nsIURI
>> prefetches
= std::move(mPrefetches
),
1133 preconnects
= std::move(mPreconnects
),
1134 preresolves
= std::move(mPreresolves
);
1136 Telemetry::AutoCounter
<Telemetry::PREDICTOR_TOTAL_PREDICTIONS
>
1138 Telemetry::AutoCounter
<Telemetry::PREDICTOR_TOTAL_PREFETCHES
> totalPrefetches
;
1139 Telemetry::AutoCounter
<Telemetry::PREDICTOR_TOTAL_PRECONNECTS
>
1141 Telemetry::AutoCounter
<Telemetry::PREDICTOR_TOTAL_PRERESOLVES
>
1144 len
= prefetches
.Length();
1145 for (i
= 0; i
< len
; ++i
) {
1146 PREDICTOR_LOG((" doing prefetch"));
1147 nsCOMPtr
<nsIURI
> uri
= prefetches
[i
];
1148 if (NS_SUCCEEDED(Prefetch(uri
, referrer
, originAttributes
, verifier
))) {
1155 len
= preconnects
.Length();
1156 for (i
= 0; i
< len
; ++i
) {
1157 PREDICTOR_LOG((" doing preconnect"));
1158 nsCOMPtr
<nsIURI
> uri
= preconnects
[i
];
1161 nsCOMPtr
<nsIPrincipal
> principal
=
1162 BasePrincipal::CreateContentPrincipal(uri
, originAttributes
);
1163 mSpeculativeService
->SpeculativeConnect(uri
, principal
, this, false);
1166 PREDICTOR_LOG((" sending preconnect verification"));
1167 verifier
->OnPredictPreconnect(uri
);
1171 len
= preresolves
.Length();
1172 for (i
= 0; i
< len
; ++i
) {
1173 nsCOMPtr
<nsIURI
> uri
= preresolves
[i
];
1176 nsAutoCString hostname
;
1177 uri
->GetAsciiHost(hostname
);
1178 PREDICTOR_LOG((" doing preresolve %s", hostname
.get()));
1179 nsCOMPtr
<nsICancelable
> tmpCancelable
;
1180 mDnsService
->AsyncResolveNative(
1181 hostname
, nsIDNSService::RESOLVE_TYPE_DEFAULT
,
1182 (nsIDNSService::RESOLVE_PRIORITY_MEDIUM
|
1183 nsIDNSService::RESOLVE_SPECULATE
),
1184 nullptr, mDNSListener
, nullptr, originAttributes
,
1185 getter_AddRefs(tmpCancelable
));
1187 // Fetch HTTPS RR if needed.
1188 if (StaticPrefs::network_dns_upgrade_with_https_rr() ||
1189 StaticPrefs::network_dns_use_https_rr_as_altsvc()) {
1190 mDnsService
->AsyncResolveNative(
1191 hostname
, nsIDNSService::RESOLVE_TYPE_HTTPSSVC
,
1192 (nsIDNSService::RESOLVE_PRIORITY_MEDIUM
|
1193 nsIDNSService::RESOLVE_SPECULATE
),
1194 nullptr, mDNSListener
, nullptr, originAttributes
,
1195 getter_AddRefs(tmpCancelable
));
1200 PREDICTOR_LOG((" sending preresolve verification"));
1201 verifier
->OnPredictDNS(uri
);
1208 // Find out if a top-level page is likely to redirect.
1209 bool Predictor::WouldRedirect(nsICacheEntry
* entry
, uint32_t loadCount
,
1210 uint32_t lastLoad
, int32_t globalDegradation
,
1211 nsIURI
** redirectURI
) {
1212 // TODO - not doing redirects for first go around
1213 MOZ_ASSERT(NS_IsMainThread());
1219 Predictor::Learn(nsIURI
* targetURI
, nsIURI
* sourceURI
,
1220 PredictorLearnReason reason
,
1221 JS::Handle
<JS::Value
> originAttributes
, JSContext
* aCx
) {
1222 OriginAttributes attrs
;
1224 if (!originAttributes
.isObject() || !attrs
.Init(aCx
, originAttributes
)) {
1225 return NS_ERROR_INVALID_ARG
;
1228 return LearnNative(targetURI
, sourceURI
, reason
, attrs
);
1231 // Called from the main thread to update the database
1233 Predictor::LearnNative(nsIURI
* targetURI
, nsIURI
* sourceURI
,
1234 PredictorLearnReason reason
,
1235 const OriginAttributes
& originAttributes
) {
1236 MOZ_ASSERT(NS_IsMainThread(),
1237 "Predictor interface methods must be called on the main thread");
1239 PREDICTOR_LOG(("Predictor::Learn"));
1241 if (!StaticPrefs::network_predictor_enabled()) {
1242 PREDICTOR_LOG((" not enabled"));
1246 if (IsNeckoChild()) {
1248 return NS_ERROR_FAILURE
;
1251 PREDICTOR_LOG((" called on child process"));
1253 RefPtr
<PredictorLearnRunnable
> runnable
= new PredictorLearnRunnable(
1254 targetURI
, sourceURI
, reason
, originAttributes
);
1255 SchedulerGroup::Dispatch(runnable
.forget());
1259 PREDICTOR_LOG((" called on parent process"));
1261 if (!mInitialized
) {
1262 PREDICTOR_LOG((" not initialized"));
1266 if (originAttributes
.IsPrivateBrowsing()) {
1267 // Don't want to do anything in PB mode
1268 PREDICTOR_LOG((" in PB mode"));
1272 if (!IsNullOrHttp(targetURI
) || !IsNullOrHttp(sourceURI
)) {
1273 PREDICTOR_LOG((" got non-HTTP[S] URI"));
1274 return NS_ERROR_INVALID_ARG
;
1277 nsCOMPtr
<nsIURI
> targetOrigin
;
1278 nsCOMPtr
<nsIURI
> sourceOrigin
;
1279 nsCOMPtr
<nsIURI
> uriKey
;
1280 nsCOMPtr
<nsIURI
> originKey
;
1284 case nsINetworkPredictor::LEARN_LOAD_TOPLEVEL
:
1285 if (!targetURI
|| sourceURI
) {
1286 PREDICTOR_LOG((" load toplevel invalid URI state"));
1287 return NS_ERROR_INVALID_ARG
;
1289 rv
= ExtractOrigin(targetURI
, getter_AddRefs(targetOrigin
));
1290 NS_ENSURE_SUCCESS(rv
, rv
);
1292 originKey
= targetOrigin
;
1294 case nsINetworkPredictor::LEARN_STARTUP
:
1295 if (!targetURI
|| sourceURI
) {
1296 PREDICTOR_LOG((" startup invalid URI state"));
1297 return NS_ERROR_INVALID_ARG
;
1299 rv
= ExtractOrigin(targetURI
, getter_AddRefs(targetOrigin
));
1300 NS_ENSURE_SUCCESS(rv
, rv
);
1301 uriKey
= mStartupURI
;
1302 originKey
= mStartupURI
;
1304 case nsINetworkPredictor::LEARN_LOAD_REDIRECT
:
1305 case nsINetworkPredictor::LEARN_LOAD_SUBRESOURCE
:
1306 if (!targetURI
|| !sourceURI
) {
1307 PREDICTOR_LOG((" redirect/subresource invalid URI state"));
1308 return NS_ERROR_INVALID_ARG
;
1310 rv
= ExtractOrigin(targetURI
, getter_AddRefs(targetOrigin
));
1311 NS_ENSURE_SUCCESS(rv
, rv
);
1312 rv
= ExtractOrigin(sourceURI
, getter_AddRefs(sourceOrigin
));
1313 NS_ENSURE_SUCCESS(rv
, rv
);
1315 originKey
= sourceOrigin
;
1318 PREDICTOR_LOG((" invalid reason"));
1319 return NS_ERROR_INVALID_ARG
;
1322 Telemetry::AutoCounter
<Telemetry::PREDICTOR_LEARN_ATTEMPTS
> learnAttempts
;
1325 Predictor::Reason argReason
{};
1326 argReason
.mLearn
= reason
;
1328 // We always open the full uri (general cache) entry first, so we don't gum up
1329 // the works waiting on predictor-only entries to open
1330 RefPtr
<Predictor::Action
> uriAction
= new Predictor::Action(
1331 Predictor::Action::IS_FULL_URI
, Predictor::Action::DO_LEARN
, argReason
,
1332 targetURI
, sourceURI
, nullptr, this);
1333 nsAutoCString uriKeyStr
, targetUriStr
, sourceUriStr
;
1334 uriKey
->GetAsciiSpec(uriKeyStr
);
1335 targetURI
->GetAsciiSpec(targetUriStr
);
1337 sourceURI
->GetAsciiSpec(sourceUriStr
);
1340 (" Learn uriKey=%s targetURI=%s sourceURI=%s reason=%d "
1342 uriKeyStr
.get(), targetUriStr
.get(), sourceUriStr
.get(), reason
,
1345 nsCOMPtr
<nsICacheStorage
> cacheDiskStorage
;
1347 RefPtr
<LoadContextInfo
> lci
= new LoadContextInfo(false, originAttributes
);
1349 rv
= mCacheStorageService
->DiskCacheStorage(lci
,
1350 getter_AddRefs(cacheDiskStorage
));
1351 NS_ENSURE_SUCCESS(rv
, rv
);
1353 // For learning full URI things, we *always* open readonly and secretly, as we
1354 // rely on actual pageloads to update the entry's metadata for us.
1355 uint32_t uriOpenFlags
= nsICacheStorage::OPEN_READONLY
|
1356 nsICacheStorage::OPEN_SECRETLY
|
1357 nsICacheStorage::CHECK_MULTITHREADED
;
1358 if (reason
== nsINetworkPredictor::LEARN_LOAD_TOPLEVEL
) {
1359 // Learning for toplevel we want to open the full uri entry priority, since
1360 // it's likely this entry will be used soon anyway, and we want this to be
1362 uriOpenFlags
|= nsICacheStorage::OPEN_PRIORITY
;
1364 cacheDiskStorage
->AsyncOpenURI(uriKey
, ""_ns
, uriOpenFlags
, uriAction
);
1366 // Now we open the origin-only (and therefore predictor-only) entry
1367 RefPtr
<Predictor::Action
> originAction
= new Predictor::Action(
1368 Predictor::Action::IS_ORIGIN
, Predictor::Action::DO_LEARN
, argReason
,
1369 targetOrigin
, sourceOrigin
, nullptr, this);
1370 nsAutoCString originKeyStr
, targetOriginStr
, sourceOriginStr
;
1371 originKey
->GetAsciiSpec(originKeyStr
);
1372 targetOrigin
->GetAsciiSpec(targetOriginStr
);
1374 sourceOrigin
->GetAsciiSpec(sourceOriginStr
);
1377 (" Learn originKey=%s targetOrigin=%s sourceOrigin=%s reason=%d "
1379 originKeyStr
.get(), targetOriginStr
.get(), sourceOriginStr
.get(), reason
,
1380 originAction
.get()));
1381 uint32_t originOpenFlags
;
1382 if (reason
== nsINetworkPredictor::LEARN_LOAD_TOPLEVEL
) {
1383 // This is the only case when we want to update the 'last used' metadata on
1384 // the cache entry we're getting. This only applies to predictor-specific
1387 nsICacheStorage::OPEN_NORMALLY
| nsICacheStorage::CHECK_MULTITHREADED
;
1389 originOpenFlags
= nsICacheStorage::OPEN_READONLY
|
1390 nsICacheStorage::OPEN_SECRETLY
|
1391 nsICacheStorage::CHECK_MULTITHREADED
;
1393 cacheDiskStorage
->AsyncOpenURI(originKey
,
1394 nsLiteralCString(PREDICTOR_ORIGIN_EXTENSION
),
1395 originOpenFlags
, originAction
);
1397 PREDICTOR_LOG(("Predictor::Learn returning"));
1401 void Predictor::LearnInternal(PredictorLearnReason reason
, nsICacheEntry
* entry
,
1402 bool isNew
, bool fullUri
, nsIURI
* targetURI
,
1403 nsIURI
* sourceURI
) {
1404 MOZ_ASSERT(NS_IsMainThread());
1406 PREDICTOR_LOG(("Predictor::LearnInternal"));
1409 if (!fullUri
&& reason
== nsINetworkPredictor::LEARN_LOAD_TOPLEVEL
&&
1411 entry
->GetMetaDataElement(SEEN_META_DATA
, getter_Copies(junk
)))) {
1412 // This is an origin-only entry that we haven't seen before. Let's mark it
1414 PREDICTOR_LOG((" marking new origin entry as seen"));
1415 nsresult rv
= entry
->SetMetaDataElement(SEEN_META_DATA
, "1");
1416 if (NS_FAILED(rv
)) {
1417 PREDICTOR_LOG((" failed to mark origin entry seen"));
1421 // Need to ensure someone else can get to the entry if necessary
1422 entry
->MetaDataReady();
1427 case nsINetworkPredictor::LEARN_LOAD_TOPLEVEL
:
1428 // This case only exists to be used during tests - code outside the
1429 // predictor tests should NEVER call Learn with LEARN_LOAD_TOPLEVEL.
1430 // The predictor xpcshell test needs this branch, however, because we
1431 // have no real page loads in xpcshell, and this is how we fake it up
1432 // so that all the work that normally happens behind the scenes in a
1433 // page load can be done for testing purposes.
1434 if (fullUri
&& StaticPrefs::network_predictor_doing_tests()) {
1436 (" WARNING - updating rolling load count. "
1437 "If you see this outside tests, you did it wrong"));
1439 // Since the visitor gets called under a cache lock, all we do there is
1440 // get copies of the keys/values we care about, and then do the real
1442 entry
->VisitMetaData(this);
1443 nsTArray
<nsCString
> keysToOperateOn
= std::move(mKeysToOperateOn
),
1444 valuesToOperateOn
= std::move(mValuesToOperateOn
);
1446 MOZ_ASSERT(keysToOperateOn
.Length() == valuesToOperateOn
.Length());
1447 for (size_t i
= 0; i
< keysToOperateOn
.Length(); ++i
) {
1448 const char* key
= keysToOperateOn
[i
].BeginReading();
1449 const char* value
= valuesToOperateOn
[i
].BeginReading();
1452 uint32_t hitCount
, lastHit
, flags
;
1453 if (!ParseMetaDataEntry(key
, value
, uri
, hitCount
, lastHit
, flags
)) {
1454 // This failed, get rid of it so we don't waste space
1455 entry
->SetMetaDataElement(key
, nullptr);
1458 UpdateRollingLoadCount(entry
, flags
, key
, hitCount
, lastHit
);
1461 PREDICTOR_LOG((" nothing to do for toplevel"));
1464 case nsINetworkPredictor::LEARN_LOAD_REDIRECT
:
1466 LearnForRedirect(entry
, targetURI
);
1469 case nsINetworkPredictor::LEARN_LOAD_SUBRESOURCE
:
1470 LearnForSubresource(entry
, targetURI
);
1472 case nsINetworkPredictor::LEARN_STARTUP
:
1473 LearnForStartup(entry
, targetURI
);
1476 PREDICTOR_LOG((" unexpected reason value"));
1477 MOZ_ASSERT(false, "Got unexpected value for learn reason!");
1481 NS_IMPL_ISUPPORTS(Predictor::SpaceCleaner
, nsICacheEntryMetaDataVisitor
)
1484 Predictor::SpaceCleaner::OnMetaDataElement(const char* key
, const char* value
) {
1485 MOZ_ASSERT(NS_IsMainThread());
1487 if (!IsURIMetadataElement(key
)) {
1488 // This isn't a bit of metadata we care about
1493 uint32_t hitCount
, lastHit
, flags
;
1495 mPredictor
->ParseMetaDataEntry(key
, value
, uri
, hitCount
, lastHit
, flags
);
1498 // Couldn't parse this one, just get rid of it
1500 nsKey
.AssignASCII(key
);
1501 mLongKeysToDelete
.AppendElement(nsKey
);
1505 uint32_t uriLength
= uri
.Length();
1506 if (uriLength
> StaticPrefs::network_predictor_max_uri_length()) {
1507 // Default to getting rid of URIs that are too long and were put in before
1508 // we had our limit on URI length, in order to free up some space.
1510 nsKey
.AssignASCII(key
);
1511 mLongKeysToDelete
.AppendElement(nsKey
);
1515 if (!mLRUKeyToDelete
|| lastHit
< mLRUStamp
) {
1516 mLRUKeyToDelete
= key
;
1517 mLRUStamp
= lastHit
;
1523 void Predictor::SpaceCleaner::Finalize(nsICacheEntry
* entry
) {
1524 MOZ_ASSERT(NS_IsMainThread());
1526 if (mLRUKeyToDelete
) {
1527 entry
->SetMetaDataElement(mLRUKeyToDelete
, nullptr);
1530 for (size_t i
= 0; i
< mLongKeysToDelete
.Length(); ++i
) {
1531 entry
->SetMetaDataElement(mLongKeysToDelete
[i
].BeginReading(), nullptr);
1535 // Called when a subresource has been hit from a top-level load. Uses the two
1536 // helper functions above to update the database appropriately.
1537 void Predictor::LearnForSubresource(nsICacheEntry
* entry
, nsIURI
* targetURI
) {
1538 MOZ_ASSERT(NS_IsMainThread());
1540 PREDICTOR_LOG(("Predictor::LearnForSubresource"));
1543 nsresult rv
= entry
->GetLastFetched(&lastLoad
);
1544 NS_ENSURE_SUCCESS_VOID(rv
);
1547 rv
= entry
->GetFetchCount(&loadCount
);
1548 NS_ENSURE_SUCCESS_VOID(rv
);
1551 key
.AssignLiteral(META_DATA_PREFIX
);
1553 targetURI
->GetAsciiSpec(uri
);
1555 if (uri
.Length() > StaticPrefs::network_predictor_max_uri_length()) {
1556 // We do this to conserve space/prevent OOMs
1557 PREDICTOR_LOG((" uri too long!"));
1558 entry
->SetMetaDataElement(key
.BeginReading(), nullptr);
1563 rv
= entry
->GetMetaDataElement(key
.BeginReading(), getter_Copies(value
));
1565 uint32_t hitCount
, lastHit
, flags
;
1566 bool isNewResource
=
1568 !ParseMetaDataEntry(key
.BeginReading(), value
.BeginReading(), uri
,
1569 hitCount
, lastHit
, flags
));
1571 int32_t resourceCount
= 0;
1572 if (isNewResource
) {
1573 // This is a new addition
1574 PREDICTOR_LOG((" new resource"));
1576 rv
= entry
->GetMetaDataElement(RESOURCE_META_DATA
, getter_Copies(s
));
1577 if (NS_SUCCEEDED(rv
)) {
1578 resourceCount
= atoi(s
.BeginReading());
1580 if (resourceCount
>=
1581 StaticPrefs::network_predictor_max_resources_per_entry()) {
1582 RefPtr
<Predictor::SpaceCleaner
> cleaner
=
1583 new Predictor::SpaceCleaner(this);
1584 entry
->VisitMetaData(cleaner
);
1585 cleaner
->Finalize(entry
);
1589 nsAutoCString count
;
1590 count
.AppendInt(resourceCount
);
1591 rv
= entry
->SetMetaDataElement(RESOURCE_META_DATA
, count
.BeginReading());
1592 if (NS_FAILED(rv
)) {
1593 PREDICTOR_LOG((" failed to update resource count"));
1599 PREDICTOR_LOG((" existing resource"));
1600 hitCount
= std::min(hitCount
+ 1, loadCount
);
1603 // Update the rolling load count to mark this sub-resource as seen on the
1604 // most-recent pageload so it can be eligible for prefetch (assuming all
1605 // the other stars align).
1606 flags
|= (1 << kRollingLoadOffset
);
1609 MakeMetadataEntry(hitCount
, lastLoad
, flags
, newValue
);
1610 rv
= entry
->SetMetaDataElement(key
.BeginReading(), newValue
.BeginReading());
1612 (" SetMetaDataElement -> 0x%08" PRIX32
, static_cast<uint32_t>(rv
)));
1613 if (NS_FAILED(rv
) && isNewResource
) {
1614 // Roll back the increment to the resource count we made above.
1615 PREDICTOR_LOG((" rolling back resource count update"));
1617 if (resourceCount
== 0) {
1618 entry
->SetMetaDataElement(RESOURCE_META_DATA
, nullptr);
1620 nsAutoCString count
;
1621 count
.AppendInt(resourceCount
);
1622 entry
->SetMetaDataElement(RESOURCE_META_DATA
, count
.BeginReading());
1627 // This is called when a top-level loaded ended up redirecting to a different
1628 // URI so we can keep track of that fact.
1629 void Predictor::LearnForRedirect(nsICacheEntry
* entry
, nsIURI
* targetURI
) {
1630 MOZ_ASSERT(NS_IsMainThread());
1632 // TODO - not doing redirects for first go around
1633 PREDICTOR_LOG(("Predictor::LearnForRedirect"));
1636 // This will add a page to our list of startup pages if it's being loaded
1637 // before our startup window has expired.
1638 void Predictor::MaybeLearnForStartup(nsIURI
* uri
, bool fullUri
,
1639 const OriginAttributes
& originAttributes
) {
1640 MOZ_ASSERT(NS_IsMainThread());
1642 // TODO - not doing startup for first go around
1643 PREDICTOR_LOG(("Predictor::MaybeLearnForStartup"));
1646 // Add information about a top-level load to our list of startup pages
1647 void Predictor::LearnForStartup(nsICacheEntry
* entry
, nsIURI
* targetURI
) {
1648 MOZ_ASSERT(NS_IsMainThread());
1650 // These actually do the same set of work, just on different entries, so we
1651 // can pass through to get the real work done here
1652 PREDICTOR_LOG(("Predictor::LearnForStartup"));
1653 LearnForSubresource(entry
, targetURI
);
1656 bool Predictor::ParseMetaDataEntry(const char* key
, const char* value
,
1657 nsCString
& uri
, uint32_t& hitCount
,
1658 uint32_t& lastHit
, uint32_t& flags
) {
1659 MOZ_ASSERT(NS_IsMainThread());
1662 ("Predictor::ParseMetaDataEntry key=%s value=%s", key
? key
: "", value
));
1664 const char* comma
= strchr(value
, ',');
1666 PREDICTOR_LOG((" could not find first comma"));
1670 uint32_t version
= static_cast<uint32_t>(atoi(value
));
1671 PREDICTOR_LOG((" version -> %u", version
));
1673 if (version
!= METADATA_VERSION
) {
1675 (" metadata version mismatch %u != %u", version
, METADATA_VERSION
));
1680 comma
= strchr(value
, ',');
1682 PREDICTOR_LOG((" could not find second comma"));
1686 hitCount
= static_cast<uint32_t>(atoi(value
));
1687 PREDICTOR_LOG((" hitCount -> %u", hitCount
));
1690 comma
= strchr(value
, ',');
1692 PREDICTOR_LOG((" could not find third comma"));
1696 lastHit
= static_cast<uint32_t>(atoi(value
));
1697 PREDICTOR_LOG((" lastHit -> %u", lastHit
));
1700 flags
= static_cast<uint32_t>(atoi(value
));
1701 PREDICTOR_LOG((" flags -> %u", flags
));
1704 const char* uriStart
= key
+ (sizeof(META_DATA_PREFIX
) - 1);
1705 uri
.AssignASCII(uriStart
);
1706 PREDICTOR_LOG((" uri -> %s", uriStart
));
1715 Predictor::Reset() {
1716 MOZ_ASSERT(NS_IsMainThread(),
1717 "Predictor interface methods must be called on the main thread");
1719 PREDICTOR_LOG(("Predictor::Reset"));
1721 if (!StaticPrefs::network_predictor_enabled()) {
1722 PREDICTOR_LOG((" not enabled"));
1726 if (IsNeckoChild()) {
1728 return NS_ERROR_FAILURE
;
1731 PREDICTOR_LOG((" forwarding to parent process"));
1732 gNeckoChild
->SendPredReset();
1736 PREDICTOR_LOG((" called on parent process"));
1738 if (!mInitialized
) {
1739 PREDICTOR_LOG((" not initialized"));
1743 RefPtr
<Predictor::Resetter
> reset
= new Predictor::Resetter(this);
1744 PREDICTOR_LOG((" created a resetter"));
1745 mCacheStorageService
->AsyncVisitAllStorages(reset
, true);
1746 PREDICTOR_LOG((" Cache async launched, returning now"));
1751 NS_IMPL_ISUPPORTS(Predictor::Resetter
, nsICacheEntryOpenCallback
,
1752 nsICacheEntryMetaDataVisitor
, nsICacheStorageVisitor
);
1754 Predictor::Resetter::Resetter(Predictor
* predictor
)
1755 : mEntriesToVisit(0), mPredictor(predictor
) {}
1758 Predictor::Resetter::OnCacheEntryCheck(nsICacheEntry
* entry
, uint32_t* result
) {
1759 *result
= nsICacheEntryOpenCallback::ENTRY_WANTED
;
1764 Predictor::Resetter::OnCacheEntryAvailable(nsICacheEntry
* entry
, bool isNew
,
1766 MOZ_ASSERT(NS_IsMainThread());
1768 if (NS_FAILED(result
)) {
1769 // This can happen when we've tried to open an entry that doesn't exist for
1770 // some non-reset operation, and then get reset shortly thereafter (as
1771 // happens in some of our tests).
1773 if (!mEntriesToVisit
) {
1779 entry
->VisitMetaData(this);
1780 nsTArray
<nsCString
> keysToDelete
= std::move(mKeysToDelete
);
1782 for (size_t i
= 0; i
< keysToDelete
.Length(); ++i
) {
1783 const char* key
= keysToDelete
[i
].BeginReading();
1784 entry
->SetMetaDataElement(key
, nullptr);
1788 if (!mEntriesToVisit
) {
1796 Predictor::Resetter::OnMetaDataElement(const char* asciiKey
,
1797 const char* asciiValue
) {
1798 MOZ_ASSERT(NS_IsMainThread());
1800 if (!StringBeginsWith(nsDependentCString(asciiKey
),
1801 nsLiteralCString(META_DATA_PREFIX
))) {
1802 // Not a metadata entry we care about, carry on
1807 key
.AssignASCII(asciiKey
);
1808 mKeysToDelete
.AppendElement(key
);
1814 Predictor::Resetter::OnCacheStorageInfo(uint32_t entryCount
,
1815 uint64_t consumption
, uint64_t capacity
,
1816 nsIFile
* diskDirectory
) {
1817 MOZ_ASSERT(NS_IsMainThread());
1823 Predictor::Resetter::OnCacheEntryInfo(nsIURI
* uri
, const nsACString
& idEnhance
,
1824 int64_t dataSize
, int64_t altDataSize
,
1825 uint32_t fetchCount
,
1826 uint32_t lastModifiedTime
,
1827 uint32_t expirationTime
, bool aPinned
,
1828 nsILoadContextInfo
* aInfo
) {
1829 MOZ_ASSERT(NS_IsMainThread());
1833 // The predictor will only ever touch entries with no idEnhance ("") or an
1834 // idEnhance of PREDICTOR_ORIGIN_EXTENSION, so we filter out any entries that
1835 // don't match that to avoid doing extra work.
1836 if (idEnhance
.EqualsLiteral(PREDICTOR_ORIGIN_EXTENSION
)) {
1837 // This is an entry we own, so we can just doom it entirely
1838 nsCOMPtr
<nsICacheStorage
> cacheDiskStorage
;
1840 rv
= mPredictor
->mCacheStorageService
->DiskCacheStorage(
1841 aInfo
, getter_AddRefs(cacheDiskStorage
));
1843 NS_ENSURE_SUCCESS(rv
, rv
);
1844 cacheDiskStorage
->AsyncDoomURI(uri
, idEnhance
, nullptr);
1845 } else if (idEnhance
.IsEmpty()) {
1846 // This is an entry we don't own, so we have to be a little more careful and
1847 // just get rid of our own metadata entries. Append it to an array of things
1848 // to operate on and then do the operations later so we don't end up calling
1849 // Complete() multiple times/too soon.
1851 mURIsToVisit
.AppendElement(uri
);
1852 mInfosToVisit
.AppendElement(aInfo
);
1859 Predictor::Resetter::OnCacheEntryVisitCompleted() {
1860 MOZ_ASSERT(NS_IsMainThread());
1864 nsTArray
<nsCOMPtr
<nsIURI
>> urisToVisit
= std::move(mURIsToVisit
);
1866 MOZ_ASSERT(mEntriesToVisit
== urisToVisit
.Length());
1868 nsTArray
<nsCOMPtr
<nsILoadContextInfo
>> infosToVisit
=
1869 std::move(mInfosToVisit
);
1871 MOZ_ASSERT(mEntriesToVisit
== infosToVisit
.Length());
1873 if (!mEntriesToVisit
) {
1878 uint32_t entriesToVisit
= urisToVisit
.Length();
1879 for (uint32_t i
= 0; i
< entriesToVisit
; ++i
) {
1881 nsCOMPtr
<nsICacheStorage
> cacheDiskStorage
;
1883 rv
= mPredictor
->mCacheStorageService
->DiskCacheStorage(
1884 infosToVisit
[i
], getter_AddRefs(cacheDiskStorage
));
1885 NS_ENSURE_SUCCESS(rv
, rv
);
1887 urisToVisit
[i
]->GetAsciiSpec(u
);
1888 rv
= cacheDiskStorage
->AsyncOpenURI(
1889 urisToVisit
[i
], ""_ns
,
1890 nsICacheStorage::OPEN_READONLY
| nsICacheStorage::OPEN_SECRETLY
|
1891 nsICacheStorage::CHECK_MULTITHREADED
,
1893 if (NS_FAILED(rv
)) {
1895 if (!mEntriesToVisit
) {
1905 void Predictor::Resetter::Complete() {
1906 MOZ_ASSERT(NS_IsMainThread());
1908 nsCOMPtr
<nsIObserverService
> obs
= mozilla::services::GetObserverService();
1910 PREDICTOR_LOG(("COULD NOT GET OBSERVER SERVICE!"));
1914 obs
->NotifyObservers(nullptr, "predictor-reset-complete", nullptr);
1917 // Helper functions to make using the predictor easier from native code
1919 static StaticRefPtr
<nsINetworkPredictor
> sPredictor
;
1921 static nsresult
EnsureGlobalPredictor(nsINetworkPredictor
** aPredictor
) {
1922 MOZ_ASSERT(NS_IsMainThread());
1926 nsCOMPtr
<nsINetworkPredictor
> predictor
;
1927 predictor
= mozilla::components::Predictor::Service(&rv
);
1928 NS_ENSURE_SUCCESS(rv
, rv
);
1929 sPredictor
= predictor
;
1930 ClearOnShutdown(&sPredictor
);
1933 nsCOMPtr
<nsINetworkPredictor
> predictor
= sPredictor
.get();
1934 predictor
.forget(aPredictor
);
1938 nsresult
PredictorPredict(nsIURI
* targetURI
, nsIURI
* sourceURI
,
1939 PredictorPredictReason reason
,
1940 const OriginAttributes
& originAttributes
,
1941 nsINetworkPredictorVerifier
* verifier
) {
1942 MOZ_ASSERT(NS_IsMainThread());
1944 if (!IsNullOrHttp(targetURI
) || !IsNullOrHttp(sourceURI
)) {
1948 nsCOMPtr
<nsINetworkPredictor
> predictor
;
1949 nsresult rv
= EnsureGlobalPredictor(getter_AddRefs(predictor
));
1950 NS_ENSURE_SUCCESS(rv
, rv
);
1952 return predictor
->PredictNative(targetURI
, sourceURI
, reason
,
1953 originAttributes
, verifier
);
1956 nsresult
PredictorLearn(nsIURI
* targetURI
, nsIURI
* sourceURI
,
1957 PredictorLearnReason reason
,
1958 const OriginAttributes
& originAttributes
) {
1959 MOZ_ASSERT(NS_IsMainThread());
1961 if (!IsNullOrHttp(targetURI
) || !IsNullOrHttp(sourceURI
)) {
1965 nsCOMPtr
<nsINetworkPredictor
> predictor
;
1966 nsresult rv
= EnsureGlobalPredictor(getter_AddRefs(predictor
));
1967 NS_ENSURE_SUCCESS(rv
, rv
);
1969 return predictor
->LearnNative(targetURI
, sourceURI
, reason
, originAttributes
);
1972 nsresult
PredictorLearn(nsIURI
* targetURI
, nsIURI
* sourceURI
,
1973 PredictorLearnReason reason
, nsILoadGroup
* loadGroup
) {
1974 MOZ_ASSERT(NS_IsMainThread());
1976 if (!IsNullOrHttp(targetURI
) || !IsNullOrHttp(sourceURI
)) {
1980 nsCOMPtr
<nsINetworkPredictor
> predictor
;
1981 nsresult rv
= EnsureGlobalPredictor(getter_AddRefs(predictor
));
1982 NS_ENSURE_SUCCESS(rv
, rv
);
1984 nsCOMPtr
<nsILoadContext
> loadContext
;
1985 OriginAttributes originAttributes
;
1988 nsCOMPtr
<nsIInterfaceRequestor
> callbacks
;
1989 loadGroup
->GetNotificationCallbacks(getter_AddRefs(callbacks
));
1991 loadContext
= do_GetInterface(callbacks
);
1994 loadContext
->GetOriginAttributes(originAttributes
);
1999 return predictor
->LearnNative(targetURI
, sourceURI
, reason
, originAttributes
);
2002 nsresult
PredictorLearn(nsIURI
* targetURI
, nsIURI
* sourceURI
,
2003 PredictorLearnReason reason
, dom::Document
* document
) {
2004 MOZ_ASSERT(NS_IsMainThread());
2006 if (!IsNullOrHttp(targetURI
) || !IsNullOrHttp(sourceURI
)) {
2010 nsCOMPtr
<nsINetworkPredictor
> predictor
;
2011 nsresult rv
= EnsureGlobalPredictor(getter_AddRefs(predictor
));
2012 NS_ENSURE_SUCCESS(rv
, rv
);
2014 OriginAttributes originAttributes
;
2017 nsCOMPtr
<nsIPrincipal
> docPrincipal
= document
->NodePrincipal();
2020 originAttributes
= docPrincipal
->OriginAttributesRef();
2024 return predictor
->LearnNative(targetURI
, sourceURI
, reason
, originAttributes
);
2027 nsresult
PredictorLearnRedirect(nsIURI
* targetURI
, nsIChannel
* channel
,
2028 const OriginAttributes
& originAttributes
) {
2029 MOZ_ASSERT(NS_IsMainThread());
2031 nsCOMPtr
<nsIURI
> sourceURI
;
2032 nsresult rv
= channel
->GetOriginalURI(getter_AddRefs(sourceURI
));
2033 NS_ENSURE_SUCCESS(rv
, rv
);
2036 rv
= targetURI
->Equals(sourceURI
, &sameUri
);
2037 NS_ENSURE_SUCCESS(rv
, rv
);
2043 if (!IsNullOrHttp(targetURI
) || !IsNullOrHttp(sourceURI
)) {
2047 nsCOMPtr
<nsINetworkPredictor
> predictor
;
2048 rv
= EnsureGlobalPredictor(getter_AddRefs(predictor
));
2049 NS_ENSURE_SUCCESS(rv
, rv
);
2051 return predictor
->LearnNative(targetURI
, sourceURI
,
2052 nsINetworkPredictor::LEARN_LOAD_REDIRECT
,
2056 // nsINetworkPredictorVerifier
2059 * Call through to the child's verifier (only during tests)
2062 Predictor::OnPredictPrefetch(nsIURI
* aURI
, uint32_t httpStatus
) {
2063 if (IsNeckoChild()) {
2064 if (mChildVerifier
) {
2065 // Ideally, we'd assert here. But since we're slowly moving towards a
2066 // world where we have multiple child processes, and only one child
2067 // process will be likely to have a verifier, we have to play it safer.
2068 return mChildVerifier
->OnPredictPrefetch(aURI
, httpStatus
);
2073 MOZ_DIAGNOSTIC_ASSERT(aURI
, "aURI must not be null");
2075 for (auto* cp
: dom::ContentParent::AllProcesses(dom::ContentParent::eLive
)) {
2076 PNeckoParent
* neckoParent
= SingleManagedOrNull(cp
->ManagedPNeckoParent());
2080 if (!neckoParent
->SendPredOnPredictPrefetch(aURI
, httpStatus
)) {
2081 return NS_ERROR_NOT_AVAILABLE
;
2089 Predictor::OnPredictPreconnect(nsIURI
* aURI
) {
2090 if (IsNeckoChild()) {
2091 if (mChildVerifier
) {
2092 // Ideally, we'd assert here. But since we're slowly moving towards a
2093 // world where we have multiple child processes, and only one child
2094 // process will be likely to have a verifier, we have to play it safer.
2095 return mChildVerifier
->OnPredictPreconnect(aURI
);
2100 MOZ_DIAGNOSTIC_ASSERT(aURI
, "aURI must not be null");
2102 for (auto* cp
: dom::ContentParent::AllProcesses(dom::ContentParent::eLive
)) {
2103 PNeckoParent
* neckoParent
= SingleManagedOrNull(cp
->ManagedPNeckoParent());
2107 if (!neckoParent
->SendPredOnPredictPreconnect(aURI
)) {
2108 return NS_ERROR_NOT_AVAILABLE
;
2116 Predictor::OnPredictDNS(nsIURI
* aURI
) {
2117 if (IsNeckoChild()) {
2118 if (mChildVerifier
) {
2119 // Ideally, we'd assert here. But since we're slowly moving towards a
2120 // world where we have multiple child processes, and only one child
2121 // process will be likely to have a verifier, we have to play it safer.
2122 return mChildVerifier
->OnPredictDNS(aURI
);
2127 MOZ_DIAGNOSTIC_ASSERT(aURI
, "aURI must not be null");
2129 for (auto* cp
: dom::ContentParent::AllProcesses(dom::ContentParent::eLive
)) {
2130 PNeckoParent
* neckoParent
= SingleManagedOrNull(cp
->ManagedPNeckoParent());
2134 if (!neckoParent
->SendPredOnPredictDNS(aURI
)) {
2135 return NS_ERROR_NOT_AVAILABLE
;
2142 // Predictor::PrefetchListener
2144 NS_IMPL_ISUPPORTS(Predictor::PrefetchListener
, nsIStreamListener
,
2147 // nsIRequestObserver
2149 Predictor::PrefetchListener::OnStartRequest(nsIRequest
* aRequest
) {
2150 mStartTime
= TimeStamp::Now();
2155 Predictor::PrefetchListener::OnStopRequest(nsIRequest
* aRequest
,
2156 nsresult aStatusCode
) {
2157 PREDICTOR_LOG(("OnStopRequest this=%p aStatusCode=0x%" PRIX32
, this,
2158 static_cast<uint32_t>(aStatusCode
)));
2159 NS_ENSURE_ARG(aRequest
);
2160 if (NS_FAILED(aStatusCode
)) {
2163 Telemetry::AccumulateTimeDelta(Telemetry::PREDICTOR_PREFETCH_TIME
,
2166 nsCOMPtr
<nsIHttpChannel
> httpChannel
= do_QueryInterface(aRequest
);
2168 PREDICTOR_LOG((" Could not get HTTP Channel!"));
2169 return NS_ERROR_UNEXPECTED
;
2171 nsCOMPtr
<nsICachingChannel
> cachingChannel
= do_QueryInterface(httpChannel
);
2172 if (!cachingChannel
) {
2173 PREDICTOR_LOG((" Could not get caching channel!"));
2174 return NS_ERROR_UNEXPECTED
;
2177 nsresult rv
= NS_OK
;
2178 uint32_t httpStatus
;
2179 rv
= httpChannel
->GetResponseStatus(&httpStatus
);
2180 if (NS_SUCCEEDED(rv
) && httpStatus
== 200) {
2181 rv
= cachingChannel
->ForceCacheEntryValidFor(
2182 StaticPrefs::network_predictor_prefetch_force_valid_for());
2183 PREDICTOR_LOG((" forcing entry valid for %d seconds rv=%" PRIX32
,
2184 StaticPrefs::network_predictor_prefetch_force_valid_for(),
2185 static_cast<uint32_t>(rv
)));
2187 rv
= cachingChannel
->ForceCacheEntryValidFor(0);
2188 Telemetry::AccumulateCategorical(
2189 Telemetry::LABELS_PREDICTOR_PREFETCH_USE_STATUS::Not200
);
2190 PREDICTOR_LOG((" removing any forced validity rv=%" PRIX32
,
2191 static_cast<uint32_t>(rv
)));
2194 nsAutoCString reqName
;
2195 rv
= aRequest
->GetName(reqName
);
2196 if (NS_FAILED(rv
)) {
2197 reqName
.AssignLiteral("<unknown>");
2200 PREDICTOR_LOG((" request %s status %u", reqName
.get(), httpStatus
));
2203 mVerifier
->OnPredictPrefetch(mURI
, httpStatus
);
2209 // nsIStreamListener
2211 Predictor::PrefetchListener::OnDataAvailable(nsIRequest
* aRequest
,
2212 nsIInputStream
* aInputStream
,
2214 const uint32_t aCount
) {
2216 return aInputStream
->ReadSegments(NS_DiscardSegment
, nullptr, aCount
,
2220 // Miscellaneous Predictor
2222 void Predictor::UpdateCacheability(nsIURI
* sourceURI
, nsIURI
* targetURI
,
2223 uint32_t httpStatus
,
2224 nsHttpRequestHead
& requestHead
,
2225 nsHttpResponseHead
* responseHead
,
2226 nsILoadContextInfo
* lci
, bool isTracking
) {
2227 MOZ_ASSERT(NS_IsMainThread());
2229 if (lci
&& lci
->IsPrivate()) {
2230 PREDICTOR_LOG(("Predictor::UpdateCacheability in PB mode - ignoring"));
2234 if (!sourceURI
|| !targetURI
) {
2236 ("Predictor::UpdateCacheability missing source or target uri"));
2240 if (!IsNullOrHttp(sourceURI
) || !IsNullOrHttp(targetURI
)) {
2241 PREDICTOR_LOG(("Predictor::UpdateCacheability non-http(s) uri"));
2245 RefPtr
<Predictor
> self
= sSelf
;
2247 nsAutoCString method
;
2248 requestHead
.Method(method
);
2251 Unused
<< responseHead
->GetHeader(nsHttp::Vary
, vary
);
2253 nsAutoCString cacheControlHeader
;
2254 Unused
<< responseHead
->GetHeader(nsHttp::Cache_Control
,
2255 cacheControlHeader
);
2256 CacheControlParser
cacheControl(cacheControlHeader
);
2258 self
->UpdateCacheabilityInternal(sourceURI
, targetURI
, httpStatus
, method
,
2259 *lci
->OriginAttributesPtr(), isTracking
,
2260 !vary
.IsEmpty(), cacheControl
.NoStore());
2264 void Predictor::UpdateCacheabilityInternal(
2265 nsIURI
* sourceURI
, nsIURI
* targetURI
, uint32_t httpStatus
,
2266 const nsCString
& method
, const OriginAttributes
& originAttributes
,
2267 bool isTracking
, bool couldVary
, bool isNoStore
) {
2268 PREDICTOR_LOG(("Predictor::UpdateCacheability httpStatus=%u", httpStatus
));
2272 if (!mInitialized
) {
2273 PREDICTOR_LOG((" not initialized"));
2277 if (!StaticPrefs::network_predictor_enabled()) {
2278 PREDICTOR_LOG((" not enabled"));
2282 nsCOMPtr
<nsICacheStorage
> cacheDiskStorage
;
2284 RefPtr
<LoadContextInfo
> lci
= new LoadContextInfo(false, originAttributes
);
2286 rv
= mCacheStorageService
->DiskCacheStorage(lci
,
2287 getter_AddRefs(cacheDiskStorage
));
2288 if (NS_FAILED(rv
)) {
2289 PREDICTOR_LOG((" cannot get disk cache storage"));
2293 uint32_t openFlags
= nsICacheStorage::OPEN_READONLY
|
2294 nsICacheStorage::OPEN_SECRETLY
|
2295 nsICacheStorage::CHECK_MULTITHREADED
;
2296 RefPtr
<Predictor::CacheabilityAction
> action
=
2297 new Predictor::CacheabilityAction(targetURI
, httpStatus
, method
,
2298 isTracking
, couldVary
, isNoStore
, this);
2300 targetURI
->GetAsciiSpec(uri
);
2301 PREDICTOR_LOG((" uri=%s action=%p", uri
.get(), action
.get()));
2302 cacheDiskStorage
->AsyncOpenURI(sourceURI
, ""_ns
, openFlags
, action
);
2305 NS_IMPL_ISUPPORTS(Predictor::CacheabilityAction
, nsICacheEntryOpenCallback
,
2306 nsICacheEntryMetaDataVisitor
);
2309 Predictor::CacheabilityAction::OnCacheEntryCheck(nsICacheEntry
* entry
,
2311 *result
= nsICacheEntryOpenCallback::ENTRY_WANTED
;
2316 enum PrefetchDecisionReason
{
2320 URL_HAS_QUERY_STRING
,
2321 RESOURCE_IS_TRACKING
,
2322 RESOURCE_COULD_VARY
,
2323 RESOURCE_IS_NO_STORE
2328 Predictor::CacheabilityAction::OnCacheEntryAvailable(nsICacheEntry
* entry
,
2331 MOZ_ASSERT(NS_IsMainThread());
2332 // This is being opened read-only, so isNew should always be false
2335 PREDICTOR_LOG(("CacheabilityAction::OnCacheEntryAvailable this=%p", this));
2336 if (NS_FAILED(result
)) {
2338 PREDICTOR_LOG((" nothing to do result=%" PRIX32
" isNew=%d",
2339 static_cast<uint32_t>(result
), isNew
));
2343 nsCString strTargetURI
;
2344 nsresult rv
= mTargetURI
->GetAsciiSpec(strTargetURI
);
2345 if (NS_FAILED(rv
)) {
2347 (" GetAsciiSpec returned %" PRIx32
, static_cast<uint32_t>(rv
)));
2351 rv
= entry
->VisitMetaData(this);
2352 if (NS_FAILED(rv
)) {
2354 (" VisitMetaData returned %" PRIx32
, static_cast<uint32_t>(rv
)));
2358 nsTArray
<nsCString
> keysToCheck
= std::move(mKeysToCheck
),
2359 valuesToCheck
= std::move(mValuesToCheck
);
2361 bool hasQueryString
= false;
2362 nsAutoCString query
;
2363 if (NS_SUCCEEDED(mTargetURI
->GetQuery(query
)) && !query
.IsEmpty()) {
2364 hasQueryString
= true;
2367 MOZ_ASSERT(keysToCheck
.Length() == valuesToCheck
.Length());
2368 for (size_t i
= 0; i
< keysToCheck
.Length(); ++i
) {
2369 const char* key
= keysToCheck
[i
].BeginReading();
2370 const char* value
= valuesToCheck
[i
].BeginReading();
2372 uint32_t hitCount
, lastHit
, flags
;
2374 if (!mPredictor
->ParseMetaDataEntry(key
, value
, uri
, hitCount
, lastHit
,
2376 PREDICTOR_LOG((" failed to parse key=%s value=%s", key
, value
));
2380 if (strTargetURI
.Equals(uri
)) {
2381 bool prefetchable
= true;
2382 PrefetchDecisionReason reason
= PREFETCHABLE
;
2384 if (mHttpStatus
!= 200) {
2385 prefetchable
= false;
2386 reason
= STATUS_NOT_200
;
2387 } else if (!mMethod
.EqualsLiteral("GET")) {
2388 prefetchable
= false;
2389 reason
= METHOD_NOT_GET
;
2390 } else if (hasQueryString
) {
2391 prefetchable
= false;
2392 reason
= URL_HAS_QUERY_STRING
;
2393 } else if (mIsTracking
) {
2394 prefetchable
= false;
2395 reason
= RESOURCE_IS_TRACKING
;
2396 } else if (mCouldVary
) {
2397 prefetchable
= false;
2398 reason
= RESOURCE_COULD_VARY
;
2399 } else if (mIsNoStore
) {
2400 // We don't set prefetchable = false yet, because we just want to know
2401 // what kind of effect this would have on prefetching.
2402 reason
= RESOURCE_IS_NO_STORE
;
2405 Telemetry::Accumulate(Telemetry::PREDICTOR_PREFETCH_DECISION_REASON
,
2409 PREDICTOR_LOG((" marking %s cacheable", key
));
2410 flags
|= FLAG_PREFETCHABLE
;
2412 PREDICTOR_LOG((" marking %s uncacheable", key
));
2413 flags
&= ~FLAG_PREFETCHABLE
;
2416 MakeMetadataEntry(hitCount
, lastHit
, flags
, newValue
);
2417 entry
->SetMetaDataElement(key
, newValue
.BeginReading());
2426 Predictor::CacheabilityAction::OnMetaDataElement(const char* asciiKey
,
2427 const char* asciiValue
) {
2428 MOZ_ASSERT(NS_IsMainThread());
2430 if (!IsURIMetadataElement(asciiKey
)) {
2434 nsCString key
, value
;
2435 key
.AssignASCII(asciiKey
);
2436 value
.AssignASCII(asciiValue
);
2437 mKeysToCheck
.AppendElement(key
);
2438 mValuesToCheck
.AppendElement(value
);
2444 } // namespace mozilla